From 64780fbda5c70b971421e69e7c6c1e147044def1 Mon Sep 17 00:00:00 2001
From: DBotThePony <dbotthepony@yandex.ru>
Date: Sat, 10 Aug 2024 01:24:15 +0700
Subject: [PATCH] Resolve most of simple enough compilation errors

---
 build.gradle.kts                              |  12 +-
 gradle.properties                             |   2 +-
 settings.gradle.kts                           |   9 -
 .../ru/dbotthepony/mc/otm/datagen/DataGen.kt  |   4 +-
 .../dbotthepony/mc/otm/datagen/tags/Tags.kt   |   2 +
 .../mc/otm/datagen/tags/TagsProvider.kt       |   4 +-
 .../mc/otm/OverdriveThatMatters.java          | 182 ++---
 .../mc/otm/capability/MatteryCapability.java  |  42 +-
 .../mc/otm/client/model/ExosuitModel.java     |   5 -
 .../mc/otm/mixin/MixinInventory.java          |   6 +-
 .../mc/otm/mixin/MixinMinecraft.java          |   6 +-
 .../otm/mixin/MixinPatchProjectileFinder.java |  30 -
 .../dbotthepony/mc/otm/mixin/MixinPlayer.java |  72 +-
 .../ad_astra/EntityOxygenSystemMixin.java     |  31 -
 .../EntityTemperatureSystemMixin.java         |  27 -
 .../compat/ad_astra/OxygenUtilsMixin.java     |  31 -
 .../dbotthepony/mc/otm/GlobalEventHandler.kt  |  70 +-
 .../dbotthepony/mc/otm/ObservedConfigList.kt  |  36 +-
 .../mc/otm/android/AndroidActiveFeature.kt    |   6 +-
 .../mc/otm/android/AndroidFeature.kt          |  23 +-
 .../mc/otm/android/AndroidFeatureType.kt      |  12 +-
 .../mc/otm/android/AndroidResearch.kt         |  47 +-
 .../android/AndroidResearchDataProvider.kt    |   3 +-
 .../otm/android/AndroidResearchDescription.kt |  37 +-
 .../mc/otm/android/AndroidResearchManager.kt  |  38 +-
 .../mc/otm/android/AndroidResearchResult.kt   |  27 +-
 .../mc/otm/android/AndroidResearchType.kt     |  23 +-
 .../otm/android/AndroidSwitchableFeature.kt   |  13 +-
 .../otm/android/feature/AttackBoostFeature.kt |   4 +-
 .../android/feature/EnderTeleporterFeature.kt |  15 +-
 .../android/feature/ExtendedReachFeature.kt   |  25 +-
 .../android/feature/FallDampenersFeature.kt   |  27 +-
 .../otm/android/feature/ItemMagnetFeature.kt  |  29 +-
 .../otm/android/feature/JumpBoostFeature.kt   |  41 +-
 .../feature/LimbOverclockingFeature.kt        |  20 +-
 .../android/feature/NanobotsArmorFeature.kt   |  17 +-
 .../feature/NanobotsRegenerationFeature.kt    |  17 +-
 .../otm/android/feature/NightVisionFeature.kt |   4 +-
 .../otm/android/feature/ShockwaveFeature.kt   |  32 +-
 .../otm/android/feature/StepAssistFeature.kt  |  22 +-
 .../android/feature/SwimBoostersFeature.kt    |  22 +-
 .../dbotthepony/mc/otm/block/MatteryBlock.kt  |  20 +-
 .../mc/otm/block/decorative/FluidTankBlock.kt |   7 +-
 .../decorative/InfiniteWaterSourceBlock.kt    |  21 +-
 .../mc/otm/block/decorative/LaboratoryLamp.kt |   5 +-
 .../mc/otm/block/decorative/PainterBlock.kt   |  19 +-
 .../decorative/TritaniumPressurePlate.kt      |  10 +-
 .../mc/otm/block/entity/ExperienceStorage.kt  |  11 +-
 .../dbotthepony/mc/otm/block/entity/Jobs.kt   |  12 +-
 .../mc/otm/block/entity/MatteryBlockEntity.kt | 422 +++--------
 .../block/entity/MatteryDeviceBlockEntity.kt  | 198 +++--
 .../block/entity/MatteryPoweredBlockEntity.kt |   5 +-
 .../block/entity/MatteryWorkerBlockEntity.kt  |  13 +-
 .../mc/otm/block/entity/RedstoneControl.kt    |   7 +-
 .../entity/blackhole/BlackHoleBlockEntity.kt  |  48 +-
 .../blackhole/ExplosionDebuggerBlockEntity.kt |  49 --
 .../otm/block/entity/blackhole/Explosions.kt  | 687 ------------------
 .../entity/cable/EnergyCableBlockEntity.kt    |  16 +-
 .../block/entity/cable/EnergyCableGraph.kt    |   8 +-
 .../decorative/CargoCrateBlockEntity.kt       |  15 +-
 .../entity/decorative/DevChestBlockEntity.kt  |  15 +-
 .../entity/decorative/FluidTankBlockEntity.kt |  19 +-
 .../entity/decorative/HoloSignBlockEntity.kt  |   5 +-
 .../InfiniteWaterSourceBlockEntity.kt         |  20 +-
 .../entity/decorative/PainterBlockEntity.kt   |  26 +-
 .../entity/matter/MatterBottlerBlockEntity.kt |  12 +-
 .../matter/MatterCapacitorBankBlockEntity.kt  |  19 +-
 .../matter/MatterDecomposerBlockEntity.kt     |   2 +-
 .../matter/MatterEntanglerBlockEntity.kt      |  20 +-
 .../entity/matter/MatterPanelBlockEntity.kt   |  36 +-
 .../matter/MatterReconstructorBlockEntity.kt  |   6 +-
 .../matter/MatterRecyclerBlockEntity.kt       |   2 +-
 .../matter/MatterReplicatorBlockEntity.kt     |   3 +-
 .../entity/matter/MatterScannerBlockEntity.kt |   5 -
 .../matter/PatternStorageBlockEntity.kt       |  26 +-
 .../entity/storage/DriveRackBlockEntity.kt    |   7 +-
 .../entity/storage/DriveViewerBlockEntity.kt  |   4 +-
 .../entity/storage/ItemMonitorBlockEntity.kt  |  80 +-
 .../entity/storage/StorageBusBlockEntity.kt   |  12 +-
 .../block/entity/storage/StorageInterfaces.kt |  14 +-
 .../tech/AbstractPoweredFurnaceBlockEntity.kt |  50 +-
 .../entity/tech/AndroidChargerBlockEntity.kt  |   2 +-
 .../entity/tech/AndroidStationBlockEntity.kt  |  10 +-
 .../entity/tech/BatteryBankBlockEntity.kt     |   8 +-
 .../tech/ChemicalGeneratorBlockEntity.kt      |   3 +-
 .../entity/tech/EnergyCounterBlockEntity.kt   |  43 +-
 .../entity/tech/EnergyHatchBlockEntity.kt     |  13 +-
 .../entity/tech/EssenceStorageBlockEntity.kt  |  19 +-
 .../tech/GravitationStabilizerBlockEntity.kt  |   4 -
 .../block/entity/tech/ItemHatchBlockEntity.kt |   4 +-
 .../entity/tech/MatterHatchBlockEntity.kt     |  11 +-
 .../entity/tech/PlatePressBlockEntity.kt      |   5 +-
 .../mc/otm/block/tech/AndroidStationBlock.kt  |   6 +-
 .../mc/otm/block/tech/EssenceStorageBlock.kt  |   2 +-
 .../otm/capability/AbstractProfiledStorage.kt |  19 +-
 .../ru/dbotthepony/mc/otm/capability/Ext.kt   |  42 +-
 .../mc/otm/capability/IMatteryPlayer.kt       |   8 +
 ...ryPlayerCapability.kt => MatteryPlayer.kt} | 305 +++-----
 .../mc/otm/capability/drive/API.kt            |   5 +-
 .../capability/drive/AbstractMatteryDrive.kt  |  16 +-
 .../mc/otm/capability/drive/DrivePool.kt      |  62 +-
 .../otm/capability/drive/ItemMatteryDrive.kt  |   9 +-
 .../energy/BatteryBackedEnergyStorage.kt      |  33 +-
 .../energy/BlockEnergyStorageImpl.kt          |  19 +-
 .../capability/energy/IEnergyStorageImpl.kt   |   2 +-
 .../energy/IMatteryEnergyStorage.kt           |   2 +-
 .../energy/ItemEnergyStorageImpl.kt           |  31 +-
 .../fluid/AbstractMatteryFluidHandler.kt      |  14 +-
 .../fluid/BlockMatteryFluidHandler.kt         |  60 +-
 .../capability/fluid/FluidHandlerIterator.kt  |   4 +-
 .../fluid/FluidHandlerSpliterator.kt          |   4 +-
 .../fluid/ItemMatteryFluidHandler.kt          |  46 +-
 .../capability/item/CombinedItemHandler.kt    |   2 +-
 .../otm/capability/item/EmptyItemHandler.kt   |   2 +-
 .../otm/capability/item/ProxiedItemHandler.kt |   2 +-
 .../item/UnmodifiableItemHandler.kt           |   2 +-
 .../otm/capability/matter/IMatterStorage.kt   |   5 -
 .../otm/capability/matter/IPatternStorage.kt  |   5 -
 .../capability/matter/MatterStorageImpl.kt    |   7 +-
 .../mc/otm/capability/matter/PatternState.kt  |  18 +-
 .../otm/capability/matter/ReplicationTask.kt  |   7 +-
 .../mc/otm/client/AndroidAbilityKeyMapping.kt |  12 +-
 .../mc/otm/client/AndroidMenuKeyMapping.kt    |  14 +-
 .../mc/otm/client/ClientEventHandler.kt       |  24 +-
 .../mc/otm/client/ClientTickHandler.kt        |  17 +-
 .../dbotthepony/mc/otm/client/MatteryGUI.kt   | 110 ++-
 .../mc/otm/client/render/GlitchRenderer.kt    | 127 ++--
 .../mc/otm/client/render/RenderExtensions.kt  |  19 +-
 .../mc/otm/client/render/RenderHelper.kt      | 133 ++--
 .../mc/otm/client/render/ResearchIcons.kt     |   1 +
 .../mc/otm/client/render/ShockwaveRenderer.kt |  17 +-
 .../mc/otm/client/render/WidgetLocation.kt    |   1 +
 .../mc/otm/client/render/Widgets8.kt          |   1 +
 .../render/blockentity/BlackHoleRenderer.kt   |  22 +-
 .../render/blockentity/FluidTankRenderer.kt   |  49 +-
 .../GravitationStabilizerRenderer.kt          |  14 +-
 .../render/entity/PlasmaProjectileRenderer.kt |  14 +-
 .../render/sprites/AbstractMatterySprite.kt   |  22 +-
 .../otm/client/render/sprites/SpriteType.kt   |   2 +-
 .../client/screen/ExopackInventoryScreen.kt   |   4 +-
 .../mc/otm/client/screen/MatteryScreen.kt     |   9 +-
 .../client/screen/decorative/PainterScreen.kt |   6 +-
 .../client/screen/matter/MatterPanelScreen.kt |   4 +-
 .../otm/client/screen/panels/ColorPicker.kt   |   1 +
 .../client/screen/panels/EffectListPanel.kt   |  12 +-
 .../screen/panels/EntityRendererPanel.kt      |  18 +-
 .../screen/panels/input/TextInputPanel.kt     |  16 +-
 .../screen/panels/slot/AbstractSlotPanel.kt   |   2 +-
 .../panels/slot/UserFilteredSlotPanel.kt      |   2 +-
 .../screen/panels/util/ScrollBarConstants.kt  |   1 +
 .../screen/tech/AndroidStationScreen.kt       |  14 +-
 .../screen/tech/EssenceStorageScreen.kt       |   1 +
 .../client/screen/widget/FluidGaugePanel.kt   |  25 +-
 .../client/screen/widget/MatterGaugePanel.kt  |  14 +-
 .../mc/otm/compat/adastra/AdAstraCompat.kt    |  58 --
 .../mc/otm/compat/cos/CosmeticArmorCompat.kt  |   1 +
 .../jade/providers/MatterStorageProvider.kt   |   1 -
 .../jade/providers/MatteryWorkerProvider.kt   |   1 -
 .../otm/compat/jei/MicrowaveRecipeCategory.kt |   1 +
 .../otm/compat/jei/PainterRecipeCategory.kt   |   1 +
 .../compat/jei/PlatePressRecipeCategory.kt    |   1 +
 .../vanilla/ExtendedInventoryHandler.kt       |  33 +-
 .../mc/otm/compat/vanilla/MatteryChestMenu.kt |  35 +-
 .../mc/otm/config/AbstractConfig.kt           |  19 +-
 .../ru/dbotthepony/mc/otm/config/ConfigExt.kt |  10 +-
 .../mc/otm/config/ObservedConfigValue.kt      |   4 +-
 .../mc/otm/container/ContainerHandler.kt      |   4 +-
 .../mc/otm/container/ContainerHelpers.kt      |   9 +-
 .../mc/otm/container/HandlerFilter.kt         |  45 +-
 .../mc/otm/container/IMatteryContainer.kt     |  10 +
 .../mc/otm/container/ItemFilter.kt            | 179 ++---
 .../mc/otm/container/MatteryContainer.kt      |   8 +-
 .../otm/container/MatteryCraftingContainer.kt |   5 +-
 .../mc/otm/container/UpgradeContainer.kt      |  10 +-
 .../container/util/ItemHandlerSpliterator.kt  |   2 +-
 .../container/util/ItemStackHashStrategy.kt   |   4 +-
 .../kotlin/ru/dbotthepony/mc/otm/core/Ext.kt  | 118 ++-
 .../ru/dbotthepony/mc/otm/core/TooltipList.kt |  74 +-
 .../mc/otm/core/UnOverengineering.kt          |  49 +-
 .../mc/otm/core/collect/AwareItemStack.kt     |   7 +-
 .../otm/core/collect/UUIDIntModifiersMap.kt   |   7 +-
 .../mc/otm/core/math/BlockRotation.kt         |   7 -
 .../dbotthepony/mc/otm/core/math/Decimal.kt   |  12 +-
 .../mc/otm/core/nbt/CompoundTagExt.kt         |   2 -
 .../mc/otm/core/util/ByteBufExtensions.kt     |  14 +-
 .../mc/otm/core/util/FriendlyStreams.kt       |  11 +-
 .../mc/otm/core/util/InvalidableLazy.kt       |  26 +
 .../mc/otm/core/util/ItemSorter.kt            |   3 +-
 .../dbotthepony/mc/otm/core/util/LOHolder.kt  |  20 -
 .../mc/otm/core/util/Savetables.kt            |  70 +-
 .../mc/otm/core/util/StreamCodecs.kt          |  18 +-
 .../mc/otm/data/Codec2RecipeSerializer.kt     | 191 -----
 .../dbotthepony/mc/otm/data/ComponentCodec.kt |  25 -
 .../dbotthepony/mc/otm/data/DecimalCodec.kt   |  24 +-
 .../mc/otm/data/DecimalProvider.kt            |  24 +-
 .../kotlin/ru/dbotthepony/mc/otm/data/Ext.kt  |   7 +-
 .../mc/otm/data/IngredientMatrixCodec.kt      |   6 +-
 .../dbotthepony/mc/otm/data/SingletonCodec.kt |  20 +-
 .../ru/dbotthepony/mc/otm/data/UUIDCodec.kt   |  52 --
 .../mc/otm/data/condition/ChanceCondition.kt  |   5 +-
 .../condition/ChanceWithPlaytimeCondition.kt  |   5 +-
 .../condition/ItemInInventoryCondition.kt     |  25 +-
 .../KilledByRealPlayerOrIndirectly.kt         |   4 +-
 .../mc/otm/data/loot/CopyTileNbtFunction.kt   |   6 +-
 .../mc/otm/data/loot/LootPoolAppender.kt      |  12 +-
 .../mc/otm/entity/MinecartCargoCrate.kt       |   6 +-
 .../mc/otm/entity/PlasmaProjectile.kt         |  14 +-
 .../ru/dbotthepony/mc/otm/graph/GraphNode.kt  |   9 +-
 .../ru/dbotthepony/mc/otm/item/BatteryItem.kt |  11 +-
 .../mc/otm/item/ChestUpgraderItem.kt          |   1 +
 .../mc/otm/item/FluidCapsuleItem.kt           |  47 +-
 .../dbotthepony/mc/otm/item/FluidTankItem.kt  |  37 +-
 .../mc/otm/item/GravitationalDisruptorItem.kt |   4 +-
 .../ru/dbotthepony/mc/otm/item/MatteryItem.kt |   6 +-
 .../ru/dbotthepony/mc/otm/item/PillItem.kt    |  30 +-
 .../otm/item/PortableCondensationDriveItem.kt | 153 ++--
 .../mc/otm/item/ProceduralBatteryItem.kt      |  45 +-
 .../mc/otm/item/QuantumBatteryItem.kt         |  25 +-
 .../dbotthepony/mc/otm/item/SimpleUpgrade.kt  |  31 +-
 .../mc/otm/item/SingleUseBatteryItem.kt       |  24 +-
 .../PortableGravitationStabilizerItem.kt      |   7 +-
 .../item/armor/SimpleTritaniumArmorItem.kt    |  51 +-
 .../mc/otm/item/armor/TritaniumArmorItem.kt   |  61 +-
 .../exopack/AbstractExopackSlotUpgradeItem.kt |  13 +-
 .../mc/otm/item/exopack/ExopackProbeItem.kt   |  17 +-
 .../mc/otm/item/exopack/ExopackUpgradeItem.kt |  15 +-
 .../ProceduralExopackSlotUpgradeItem.kt       |  18 +-
 .../mc/otm/item/matter/CreativePatternItem.kt |  31 +-
 .../mc/otm/item/matter/MatterCapacitorItem.kt |  45 +-
 .../mc/otm/item/matter/MatterDustItem.kt      |  15 +-
 .../mc/otm/item/matter/PatternStorageItem.kt  | 122 +---
 .../mc/otm/item/tool/ExplosiveHammerItem.kt   |  38 +-
 .../mc/otm/item/tool/MatteryAxeItem.kt        |   2 +-
 .../mc/otm/item/weapon/AbstractWeaponItem.kt  | 598 ---------------
 .../mc/otm/item/weapon/EnergySwordItem.kt     |  38 +-
 .../mc/otm/item/weapon/PlasmaRifleItem.kt     |  66 --
 .../mc/otm/item/weapon/PlasmaWeaponItem.kt    | 295 --------
 .../mc/otm/matter/AbstractRegistryAction.kt   |  15 +-
 .../mc/otm/matter/ComputeAction.kt            |  35 +-
 .../dbotthepony/mc/otm/matter/DeleteAction.kt |   9 +-
 .../mc/otm/matter/IMatterFunction.kt          |  15 +-
 .../dbotthepony/mc/otm/matter/InsertAction.kt |   5 +-
 .../mc/otm/matter/MatterDataProvider.kt       |   3 +-
 .../mc/otm/matter/MatterManager.kt            | 260 +++----
 .../mc/otm/matter/RegistryEntries.kt          |   1 +
 .../dbotthepony/mc/otm/matter/UpdateAction.kt |  11 +-
 .../mc/otm/menu/ExopackInventoryMenu.kt       |  12 +-
 .../mc/otm/menu/ISortingSettings.kt           |  23 +-
 .../ru/dbotthepony/mc/otm/menu/MatteryMenu.kt |  55 +-
 .../ru/dbotthepony/mc/otm/menu/Slots.kt       |  13 +-
 .../mc/otm/menu/data/NetworkedItemView.kt     | 104 ++-
 .../mc/otm/menu/decorative/FluidTankMenu.kt   |  27 +-
 .../mc/otm/menu/decorative/PainterMenu.kt     |  18 +-
 .../mc/otm/menu/matter/MatterBottlerMenu.kt   |   6 +-
 .../mc/otm/menu/matter/MatterEntanglerMenu.kt |   6 +-
 .../mc/otm/menu/matter/MatterPanelMenu.kt     |  98 ++-
 .../mc/otm/menu/storage/DriveViewerMenu.kt    |  11 +-
 .../storage/StorageImporterExporterMenu.kt    |   2 +-
 .../mc/otm/menu/tech/AndroidStationMenu.kt    |   8 +-
 .../mc/otm/menu/tech/ChemicalGeneratorMenu.kt |  12 +-
 .../mc/otm/menu/tech/PoweredFurnaceMenu.kt    |   5 +-
 .../mc/otm/menu/widget/FluidGaugeWidget.kt    |   4 +-
 .../mc/otm/network/AndroidPackets.kt          | 400 ++++++++++
 ...orkChannel.kt => BlockEntitySyncPacket.kt} |  77 +-
 .../ru/dbotthepony/mc/otm/network/Ext.kt      |  20 +
 .../mc/otm/network/MatteryNetworkChannel.kt   | 121 ---
 .../network/MatteryPlayerNetworkChannel.kt    | 600 ---------------
 .../mc/otm/network/MatteryPlayerPackets.kt    | 484 ++++++++++++
 .../mc/otm/network/MenuDataPacket.kt          |  56 ++
 .../mc/otm/network/MenuNetworkChannel.kt      |  95 ---
 .../mc/otm/network/NetworkPackets.kt          | 103 +++
 .../mc/otm/network/SetCarriedPacket.kt        |  42 ++
 .../mc/otm/network/ShockwaveEffectPacket.kt   |  41 ++
 .../mc/otm/network/SmokeParticlesPacket.kt    |  57 ++
 .../mc/otm/network/StreamCodecs.kt            |  13 +
 .../mc/otm/network/WeaponNetworkChannel.kt    |  15 -
 .../mc/otm/recipe/IMatteryRecipe.kt           |   3 -
 .../mc/otm/recipe/IngredientMatrix.kt         |  10 +-
 .../mc/otm/recipe/MatterEntanglerRecipe.kt    |   6 -
 .../mc/otm/recipe/MatteryCookingRecipe.kt     |  25 +-
 .../mc/otm/recipe/PainterRecipe.kt            |   2 -
 .../mc/otm/recipe/PlatePressRecipe.kt         |   5 +-
 .../mc/otm/recipe/UpgradeRecipe.kt            |  18 +-
 .../mc/otm/registry/AndroidFeatures.kt        |   6 +-
 .../registry/CapabilitiesRegisterListener.kt  |   7 +
 .../mc/otm/registry/LootModifiers.kt          |   9 +-
 .../mc/otm/registry/MArmorMaterials.kt        |  59 ++
 .../mc/otm/registry/MBlockColors.kt           |   6 +-
 .../mc/otm/registry/MBlockEntities.kt         |  10 +-
 .../dbotthepony/mc/otm/registry/MBlockTags.kt |   2 +-
 .../ru/dbotthepony/mc/otm/registry/MBlocks.kt |  45 +-
 .../mc/otm/registry/MCreativeTabs.kt          |  31 +-
 .../mc/otm/registry/MDamageTypes.kt           |   1 +
 .../mc/otm/registry/MDataComponentTypes.kt    |  71 ++
 .../mc/otm/registry/MDeferredRegister.kt      | 118 ++-
 .../mc/otm/registry/MEntityTypes.kt           |  21 +-
 .../ru/dbotthepony/mc/otm/registry/MFluids.kt |  30 +-
 .../mc/otm/registry/MItemFunctionTypes.kt     |  11 +-
 .../dbotthepony/mc/otm/registry/MItemTags.kt  |   1 +
 .../ru/dbotthepony/mc/otm/registry/MItems.kt  | 107 +--
 .../mc/otm/registry/MLootItemConditions.kt    |   6 +-
 .../ru/dbotthepony/mc/otm/registry/MMenus.kt  | 111 ++-
 .../ru/dbotthepony/mc/otm/registry/MNames.kt  |   2 +-
 .../dbotthepony/mc/otm/registry/MRecipes.kt   |  13 +-
 .../dbotthepony/mc/otm/registry/MRegistry.kt  |  89 +--
 .../mc/otm/registry/MSoundEvents.kt           |  14 +-
 .../mc/otm/registry/RegistryDelegate.kt       |  26 +-
 .../objects/ColoredDecorativeBlock.kt         |   2 -
 .../objects/StripedColoredDecorativeBlock.kt  |   2 -
 .../mc/otm/saveddata/SavedCountingMap.kt      |   3 +-
 .../mc/otm/storage/ItemStorageStack.kt        |   8 +-
 .../mc/otm/storage/StorageStack.kt            |  30 +-
 .../mc/otm/triggers/AndroidResearchTrigger.kt |   3 +-
 .../otm/triggers/AndroidTravelUnderwater.kt   |   3 +-
 .../mc/otm/triggers/ExopackTriggers.kt        |   3 +-
 .../mc/otm/triggers/HurtTrigger.kt            |   4 +-
 .../mc/otm/triggers/ItemTrigger.kt            |   4 +-
 .../mc/otm/triggers/KillAsAndroidTrigger.kt   |  36 +-
 .../mc/otm/triggers/MCriterionTrigger.kt      |  57 +-
 .../triggers/MatteryInventoryChangeTrigger.kt |  13 +-
 .../mc/otm/triggers/NanobotsArmorTrigger.kt   |   4 +-
 .../mc/otm/triggers/SimpleTriggers.kt         |   1 +
 .../mc/otm/triggers/SingletonTrigger.kt       |   4 +-
 .../resources/META-INF/accesstransformer.cfg  | 280 +++----
 ...verdrive_that_matters.ad_astra.mixins.json |  13 -
 .../overdrive_that_matters.mixins.json        |   1 -
 326 files changed, 4859 insertions(+), 7139 deletions(-)
 delete mode 100644 src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPatchProjectileFinder.java
 delete mode 100644 src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityOxygenSystemMixin.java
 delete mode 100644 src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityTemperatureSystemMixin.java
 delete mode 100644 src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/OxygenUtilsMixin.java
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/ExplosionDebuggerBlockEntity.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/Explosions.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/capability/IMatteryPlayer.kt
 rename src/main/kotlin/ru/dbotthepony/mc/otm/capability/{MatteryPlayerCapability.kt => MatteryPlayer.kt} (85%)
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/compat/adastra/AdAstraCompat.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LOHolder.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/data/ComponentCodec.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/AbstractWeaponItem.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaRifleItem.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/AndroidPackets.kt
 rename src/main/kotlin/ru/dbotthepony/mc/otm/network/{GenericNetworkChannel.kt => BlockEntitySyncPacket.kt} (56%)
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/Ext.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuDataPacket.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/NetworkPackets.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/SetCarriedPacket.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/ShockwaveEffectPacket.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/SmokeParticlesPacket.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt
 delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/network/WeaponNetworkChannel.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/registry/CapabilitiesRegisterListener.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/registry/MArmorMaterials.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDataComponentTypes.kt
 delete mode 100644 src/main/resources/overdrive_that_matters.ad_astra.mixins.json

diff --git a/build.gradle.kts b/build.gradle.kts
index 00752cd40..262d19a80 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -109,7 +109,9 @@ tasks.withType(JavaCompile::class.java) {
 sourceSets {
 	create("data") {
 		compileClasspath += sourceSets["main"].output
+		compileClasspath += sourceSets["main"].compileClasspath
 		runtimeClasspath += sourceSets["main"].output
+		runtimeClasspath += sourceSets["main"].runtimeClasspath
 	}
 
 	this["main"].resources {
@@ -189,12 +191,10 @@ dependencies {
 	}
 }
 
-configurations {
-	getByName("dataImplementation").extendsFrom(getByName("implementation"))
-}
-
 minecraft {
-	//accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
+	accessTransformers {
+		files("src/main/resources/META-INF/accesstransformer.cfg")
+	}
 
 	runs {
 		configureEach {
@@ -243,7 +243,7 @@ minecraft {
 
 mixin {
 	config("$mod_id.mixins.json")
-	config("$mod_id.ad_astra.mixins.json")
+	// config("$mod_id.ad_astra.mixins.json")
 }
 
 repositories {
diff --git a/gradle.properties b/gradle.properties
index 3d87f7c07..a2a114d13 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -14,7 +14,7 @@ mc_version=1.21
 jei_mc_version=1.21
 curios_mc_version=1.20.2
 
-forge_gradle_version=[7.0.145,)
+forge_gradle_version=7.0.153
 forge_version=21.0.54-beta
 mixingradle_version=0.7.33
 mixin_version=0.8.5
diff --git a/settings.gradle.kts b/settings.gradle.kts
index f89f10103..7d4171a30 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -24,14 +24,6 @@ buildscript {
 			}
 		}
 
-		maven(url = "https://repo.spongepowered.org/repository/maven-public/") {
-			name = "Spongepowered"
-
-			content {
-				includeGroup("org.spongepowered")
-			}
-		}
-
 		mavenCentral()
 	}
 
@@ -43,7 +35,6 @@ buildscript {
 		classpath(group = "net.neoforged.gradle", name = "userdev", version = forge_gradle_version)
 		classpath(group = "net.neoforged.gradle", name = "mixin", version = forge_gradle_version)
 		classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}")
-		classpath("org.spongepowered:mixingradle:${mixingradle_version}")
 
 		classpath(group = "org.gradle.toolchains", name = "foojay-resolver", version = "0.5.0")
 	}
diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt
index 92894f6aa..356d1a4b9 100644
--- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt
+++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt
@@ -21,6 +21,8 @@ import net.minecraftforge.eventbus.api.SubscribeEvent
 import net.minecraftforge.fml.common.Mod
 import net.minecraftforge.data.event.GatherDataEvent
 import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.fml.common.EventBusSubscriber
+import net.neoforged.fml.common.Mod
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.android.AndroidResearchDataProvider
 import ru.dbotthepony.mc.otm.block.*
@@ -55,7 +57,7 @@ import kotlin.properties.Delegates
 
 internal fun modLocation(string: String) = ResourceLocation(DataGen.MOD_ID, string)
 
-@Mod.EventBusSubscriber(modid = DataGen.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
+@EventBusSubscriber(modid = DataGen.MOD_ID, bus = EventBusSubscriber.Bus.MOD)
 object DataGen {
 	const val MOD_ID = OverdriveThatMatters.MOD_ID
 
diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt
index 7bb46aece..4bda0864d 100644
--- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt
+++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt
@@ -30,6 +30,8 @@ fun addTags(tagsProvider: TagsProvider) {
 	tagsProvider.plates.add("gold", MItems.GOLD_PLATE)
 	tagsProvider.plates.add("carbon", MItems.CARBON_MESH)
 
+	tagsProvider.items.Appender(ItemTags.MEAT).add(MItems.NUTRIENT_PASTE)
+
 	tagsProvider.fluids.forge("experience").add(MFluids.LIQUID_XP).add(MFluids.LIQUID_XP_FLOWING)
 	tagsProvider.fluidTypes.forge("experience").add(MFluids.LIQUID_XP_TYPE)
 
diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/TagsProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/TagsProvider.kt
index 41ad89edd..fc80ba9a7 100644
--- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/TagsProvider.kt
+++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/TagsProvider.kt
@@ -18,7 +18,7 @@ import net.minecraftforge.common.Tags
 import net.minecraftforge.data.event.GatherDataEvent
 import net.minecraftforge.registries.ForgeRegistries
 import net.minecraftforge.registries.IForgeRegistry
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.datagen.DataGen
 import java.util.stream.Stream
 import net.minecraft.data.tags.TagsProvider as MinecraftTagsProvider
@@ -172,7 +172,7 @@ class TagsProvider(private val event: GatherDataEvent) {
 	val mobEffects = Delegate(ForgeRegistries.MOB_EFFECTS)
 	val damageTypes = Delegate(Registries.DAMAGE_TYPE)
 
-	val androidImmuneEffects = mobEffects.Appender(MatteryPlayerCapability.ANDROID_IMMUNE_EFFECTS)
+	val androidImmuneEffects = mobEffects.Appender(MatteryPlayer.ANDROID_IMMUNE_EFFECTS)
 
 	val requiresShovel = blocks.Appender(BlockTags.MINEABLE_WITH_SHOVEL)
 	val requiresAxe = blocks.Appender(BlockTags.MINEABLE_WITH_AXE)
diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java
index 8f0b0c57b..619fc654e 100644
--- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java
+++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java
@@ -3,27 +3,26 @@ package ru.dbotthepony.mc.otm;
 import kotlin.KotlinVersion;
 import net.minecraft.MethodsReturnNonnullByDefault;
 import net.minecraft.resources.ResourceLocation;
-import net.minecraft.world.entity.Entity;
-import net.minecraft.world.item.crafting.Ingredient;
-import net.minecraft.world.level.block.entity.BlockEntity;
-import net.minecraftforge.api.distmarker.Dist;
-import net.minecraftforge.eventbus.api.EventPriority;
-import net.minecraftforge.fml.DistExecutor;
-import net.minecraftforge.fml.ModList;
-import net.minecraftforge.fml.common.Mod;
-import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
-import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
-import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
-import net.minecraftforge.registries.IdMappingEvent;
+import net.neoforged.api.distmarker.Dist;
+import net.neoforged.bus.api.EventPriority;
+import net.neoforged.bus.api.IEventBus;
+import net.neoforged.fml.ModContainer;
+import net.neoforged.fml.ModList;
+import net.neoforged.fml.common.Mod;
+import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
+import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
+import net.neoforged.fml.loading.FMLLoader;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import ru.dbotthepony.mc.otm.android.AndroidResearchDescription;
+import ru.dbotthepony.mc.otm.android.AndroidResearchDescriptions;
 import ru.dbotthepony.mc.otm.android.AndroidResearchManager;
+import ru.dbotthepony.mc.otm.android.AndroidResearchResult;
+import ru.dbotthepony.mc.otm.android.AndroidResearchResults;
 import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature;
 import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity;
-import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue;
 import ru.dbotthepony.mc.otm.block.entity.decorative.DevChestBlockEntity;
-import ru.dbotthepony.mc.otm.capability.MatteryCapability;
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer;
 import ru.dbotthepony.mc.otm.capability.drive.DrivePool;
 import ru.dbotthepony.mc.otm.client.AndroidAbilityKeyMapping;
 import ru.dbotthepony.mc.otm.client.AndroidMenuKeyMapping;
@@ -36,8 +35,8 @@ import ru.dbotthepony.mc.otm.client.model.TritaniumArmorModel;
 import ru.dbotthepony.mc.otm.client.render.ShockwaveRenderer;
 import ru.dbotthepony.mc.otm.client.render.blockentity.BatteryBankRenderer;
 import ru.dbotthepony.mc.otm.client.render.blockentity.MatterBatteryBankRenderer;
-import ru.dbotthepony.mc.otm.compat.adastra.AdAstraCompatKt;
 import ru.dbotthepony.mc.otm.compat.curios.CuriosCompatKt;
+import ru.dbotthepony.mc.otm.compat.vanilla.MatteryChestMenu;
 import ru.dbotthepony.mc.otm.config.AndroidConfig;
 import ru.dbotthepony.mc.otm.config.CablesConfig;
 import ru.dbotthepony.mc.otm.config.ClientConfig;
@@ -47,22 +46,24 @@ import ru.dbotthepony.mc.otm.config.MachinesConfig;
 import ru.dbotthepony.mc.otm.config.ServerCompatConfig;
 import ru.dbotthepony.mc.otm.config.ServerConfig;
 import ru.dbotthepony.mc.otm.config.ToolsConfig;
+import ru.dbotthepony.mc.otm.data.DecimalProvider;
 import ru.dbotthepony.mc.otm.item.ChestUpgraderItem;
 import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem;
 import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem;
 import ru.dbotthepony.mc.otm.item.QuantumBatteryItem;
-import ru.dbotthepony.mc.otm.item.weapon.AbstractWeaponItem;
 import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem;
+import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction;
+import ru.dbotthepony.mc.otm.matter.IMatterFunction;
 import ru.dbotthepony.mc.otm.matter.MatterManager;
 import ru.dbotthepony.mc.otm.network.*;
 import ru.dbotthepony.mc.otm.registry.*;
+import ru.dbotthepony.mc.otm.storage.StorageStack;
 import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger;
 import top.theillusivec4.curios.api.CuriosApi;
 
-import static net.minecraftforge.common.MinecraftForge.EVENT_BUS;
+import static net.neoforged.neoforge.common.NeoForge.EVENT_BUS;
 
 import javax.annotation.ParametersAreNonnullByDefault;
-import java.util.concurrent.atomic.AtomicInteger;
 
 // The value here should match an entry in the META-INF/mods.toml file
 @Mod(OverdriveThatMatters.MOD_ID)
@@ -72,21 +73,9 @@ public final class OverdriveThatMatters {
 	// Directly reference a log4j logger.
 	public static final String MOD_ID = "overdrive_that_matters";
 	private static final Logger LOGGER = LogManager.getLogger();
-	public static final AtomicInteger INGREDIENT_CACHE_INVALIDATION_COUNTER;
-
-	static {
-		try {
-			var f = Ingredient.class.getDeclaredField("INVALIDATION_COUNTER");
-			f.setAccessible(true);
-			INGREDIENT_CACHE_INVALIDATION_COUNTER = (AtomicInteger) f.get(null);
-		} catch (IllegalAccessException | NoSuchFieldException e) {
-			throw new RuntimeException(e);
-		}
-	}
-
 	public static OverdriveThatMatters INSTANCE;
 	public static ResourceLocation loc(String path) {
-		return new ResourceLocation(MOD_ID, path);
+		return ResourceLocation.fromNamespaceAndPath(MOD_ID, path);
 	}
 
 	private static void checkIfKotlinIsInstalled() {
@@ -95,9 +84,9 @@ public final class OverdriveThatMatters {
 		}
 	}
 
-	public OverdriveThatMatters() {
+	public OverdriveThatMatters(IEventBus bus, Dist dist, ModContainer container) {
 		if (INSTANCE != null) {
-			throw new IllegalStateException("yo what the fuck");
+			throw new IllegalStateException("wei wei wei... hello?");
 		}
 
 		try {
@@ -121,35 +110,67 @@ public final class OverdriveThatMatters {
 
 		INSTANCE = this;
 
-		var modBus = FMLJavaModLoadingContext.get().getModEventBus();
+		bus.addListener(MRegistry.INSTANCE::register);
 
-		MRegistry.INSTANCE.initialize(modBus);
-		MatterManager.INSTANCE.initialize(modBus);
+		MBlocks.INSTANCE.register(bus);
+		MFluids.INSTANCE.register(bus);
+		MBlockEntities.INSTANCE.register(bus);
+		MEntityTypes.INSTANCE.register(bus);
+		MMenus.INSTANCE.register(bus);
+		MItems.INSTANCE.register(bus);
+		AndroidFeatures.INSTANCE.register(bus);
+		MSoundEvents.INSTANCE.register(bus);
+		LootModifiers.INSTANCE.register(bus);
+		MItemFunctionTypes.INSTANCE.register(bus);
+		MLootItemConditions.INSTANCE.register(bus);
+		MRecipes.INSTANCE.register(bus);
+		MDataComponentTypes.INSTANCE.register(bus);
+		MArmorMaterials.INSTANCE.register(bus);
 
-		modBus.addListener(EventPriority.HIGHEST, this::setup);
-		modBus.addListener(EventPriority.NORMAL, this::setupClient);
-		modBus.addListener(EventPriority.NORMAL, MatteryCapability::register);
+		StorageStack.Companion.register(bus);
+		MatteryChestMenu.Companion.register(bus);
 
-		DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
-			modBus.addListener(EventPriority.NORMAL, AndroidMenuKeyMapping.INSTANCE::register);
-			modBus.addListener(EventPriority.NORMAL, AndroidAbilityKeyMapping.INSTANCE::register);
-			modBus.addListener(EventPriority.NORMAL, TritaniumArmorModel::register);
-			modBus.addListener(EventPriority.NORMAL, GravitationStabilizerModel::register);
-			modBus.addListener(EventPriority.NORMAL, MCreativeTabs.INSTANCE::register);
+		MCreativeTabs.INSTANCE.initialize(bus);
 
-			modBus.addListener(EventPriority.NORMAL, BatteryBankRenderer.Companion::onRegisterAdditionalModels);
-			modBus.addListener(EventPriority.NORMAL, MatterBatteryBankRenderer.Companion::onRegisterAdditionalModels);
-		});
+		bus.addListener(NetworkPacketsKt::registerNetworkPackets);
 
-		ClientConfig.INSTANCE.register();
-		ServerConfig.INSTANCE.register();
-		CablesConfig.INSTANCE.register();
-		ServerCompatConfig.INSTANCE.register();
-		AndroidConfig.INSTANCE.register();
-		ExopackConfig.INSTANCE.register();
-		ItemsConfig.INSTANCE.register();
-		MachinesConfig.INSTANCE.register();
-		ToolsConfig.INSTANCE.register();
+		DecimalProvider.Companion.register(bus);
+		AndroidResearchDescription.Companion.register(bus);
+		AndroidResearchDescriptions.INSTANCE.register(bus);
+		AndroidResearchResult.Companion.register(bus);
+		AndroidResearchResults.INSTANCE.register(bus);
+
+		AbstractRegistryAction.Companion.register(bus);
+		IMatterFunction.Companion.register(bus);
+
+		MRegistry.INSTANCE.initialize(bus);
+		MatterManager.INSTANCE.initialize(bus);
+
+		bus.addListener(EventPriority.HIGHEST, this::setup);
+		bus.addListener(EventPriority.NORMAL, this::setupClient);
+
+		if (FMLLoader.getDist() == Dist.CLIENT) {
+			bus.addListener(EventPriority.NORMAL, AndroidMenuKeyMapping.INSTANCE::register);
+			bus.addListener(EventPriority.NORMAL, AndroidAbilityKeyMapping.INSTANCE::register);
+			bus.addListener(EventPriority.NORMAL, TritaniumArmorModel::register);
+			bus.addListener(EventPriority.NORMAL, GravitationStabilizerModel::register);
+			bus.addListener(EventPriority.NORMAL, MCreativeTabs.INSTANCE::register);
+
+			bus.addListener(EventPriority.NORMAL, BatteryBankRenderer.Companion::onRegisterAdditionalModels);
+			bus.addListener(EventPriority.NORMAL, MatterBatteryBankRenderer.Companion::onRegisterAdditionalModels);
+
+			MBlockColors.INSTANCE.register(bus);
+		}
+
+		ClientConfig.INSTANCE.register(container);
+		ServerConfig.INSTANCE.register(container);
+		CablesConfig.INSTANCE.register(container);
+		ServerCompatConfig.INSTANCE.register(container);
+		AndroidConfig.INSTANCE.register(container);
+		ExopackConfig.INSTANCE.register(container);
+		ItemsConfig.INSTANCE.register(container);
+		MachinesConfig.INSTANCE.register(container);
+		ToolsConfig.INSTANCE.register(container);
 	}
 
 	private void setup(final FMLCommonSetupEvent event) {
@@ -158,23 +179,22 @@ public final class OverdriveThatMatters {
 		EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopped);
 		EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopping);
 		EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStarting);
-		EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onLevelTick);
-		EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onServerTick);
+		EVENT_BUS.addListener(EventPriority.HIGH, GlobalEventHandlerKt::onLevelTickPre);
+		EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onLevelTickPost);
+		EVENT_BUS.addListener(EventPriority.HIGH, GlobalEventHandlerKt::onServerTickPre);
+		EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onServerTickPost);
 
-		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerTick);
-		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::isMobEffectApplicable);
-		EVENT_BUS.addListener(EventPriority.LOW, MatteryPlayerCapability.Companion::onHurtEvent);
-		EVENT_BUS.addListener(EventPriority.HIGH, MatteryPlayerCapability.Companion::onAttackEvent);
-		EVENT_BUS.addGenericListener(Entity.class, EventPriority.NORMAL, MatteryPlayerCapability.Companion::onAttachCapabilityEvent);
-		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerChangeDimensionEvent);
-		EVENT_BUS.addListener(EventPriority.LOWEST, MatteryPlayerCapability.Companion::onPlayerDeath);
-		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerCloneEvent);
-		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onStartTracking);
-		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onStopTracking);
-		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::addCommands);
+		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayer.Companion::onPlayerTickPre);
+		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayer.Companion::onPlayerTickPost);
+		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayer.Companion::isMobEffectApplicable);
+		EVENT_BUS.addListener(EventPriority.LOW, MatteryPlayer.Companion::onHurtEvent);
+		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayer.Companion::onPlayerChangeDimensionEvent);
+		EVENT_BUS.addListener(EventPriority.LOWEST, MatteryPlayer.Companion::onPlayerDeath);
+		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayer.Companion::onPlayerCloneEvent);
+		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayer.Companion::onStartTracking);
+		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayer.Companion::onStopTracking);
+		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayer.Companion::addCommands);
 
-		EVENT_BUS.addListener(EventPriority.NORMAL, ExplosionQueue.Companion::onWorldTick);
-		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::tick);
 		EVENT_BUS.addListener(EventPriority.NORMAL, QuantumBatteryItem.Companion::tick);
 		EVENT_BUS.addListener(EventPriority.LOWEST, PortableCondensationDriveItem.Companion::onPickupEvent);
 
@@ -191,6 +211,7 @@ public final class OverdriveThatMatters {
 		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onWatch);
 		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onForget);
 		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::playerDisconnected);
+		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::postLevelTick);
 
 		EVENT_BUS.addListener(EventPriority.LOWEST, KillAsAndroidTrigger.INSTANCE::onKill);
 
@@ -203,29 +224,14 @@ public final class OverdriveThatMatters {
 
 		EVENT_BUS.addListener(EventPriority.NORMAL, DevChestBlockEntity.Companion::mappingsChanged);
 
-		MatteryPlayerNetworkChannel.INSTANCE.register();
-		MenuNetworkChannel.INSTANCE.register();
-		WeaponNetworkChannel.INSTANCE.register();
-		GenericNetworkChannel.INSTANCE.register();
-
 		if (ModList.get().isLoaded(CuriosApi.MODID)) {
 			EVENT_BUS.addListener(EventPriority.NORMAL, CuriosCompatKt::onCuriosSlotModifiersUpdated);
 		}
-
-		if (AdAstraCompatKt.isAdAstraLoaded()) {
-			EVENT_BUS.addListener(EventPriority.NORMAL, AdAstraCompatKt::onDamageEvent);
-			EVENT_BUS.addListener(EventPriority.NORMAL, AdAstraCompatKt::onMatteryTick);
-		}
 	}
 
 	private void setupClient(final FMLClientSetupEvent event) {
 		EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::tooltipEvent);
 
-		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::playerRenderHook);
-		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::fovHook);
-		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::clickHook);
-		EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::renderViewModel);
-
 		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryGUI.INSTANCE::onScreenRender);
 		EVENT_BUS.addListener(EventPriority.LOWEST, MatteryGUI.INSTANCE::onOpenGUIEvent);
 		EVENT_BUS.addListener(EventPriority.NORMAL, MatteryGUI.INSTANCE::onRenderGuiEvent);
diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java
index 6d09603f4..8f266908b 100644
--- a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java
+++ b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java
@@ -1,9 +1,12 @@
 package ru.dbotthepony.mc.otm.capability;
 
-import net.minecraftforge.common.capabilities.CapabilityManager;
-import net.minecraftforge.common.capabilities.Capability;
-import net.minecraftforge.common.capabilities.CapabilityToken;
+import net.minecraft.core.Direction;
+import net.minecraft.resources.ResourceLocation;
+import net.neoforged.neoforge.capabilities.BlockCapability;
+import net.neoforged.neoforge.capabilities.ItemCapability;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import ru.dbotthepony.mc.otm.OverdriveThatMatters;
 import ru.dbotthepony.mc.otm.block.entity.cable.EnergyCableBlockEntity;
 import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive;
 import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage;
@@ -12,57 +15,56 @@ import ru.dbotthepony.mc.otm.capability.matter.IReplicationTaskProvider;
 import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage;
 import ru.dbotthepony.mc.otm.graph.matter.MatterNode;
 import ru.dbotthepony.mc.otm.graph.storage.StorageNode;
-import top.theillusivec4.curios.api.type.capability.ICurio;
-import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler;
 
 import javax.annotation.Nonnull;
 
 public class MatteryCapability {
 	@Nonnull
 	@NotNull
-	public static final Capability<IMatteryEnergyStorage> ENERGY = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final BlockCapability<IMatteryEnergyStorage, @Nullable Direction> BLOCK_ENERGY = BlockCapability.createSided(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "energy"), IMatteryEnergyStorage.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<MatteryPlayerCapability> MATTERY_PLAYER = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final ItemCapability<IMatteryEnergyStorage, Void> ITEM_ENERGY = ItemCapability.createVoid(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "energy"), IMatteryEnergyStorage.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<IMatterStorage> MATTER = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final ItemCapability<IMatterStorage, Void> MATTER_ITEM = ItemCapability.createVoid(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "matter"), IMatterStorage.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<MatterNode> MATTER_NODE = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final BlockCapability<IMatterStorage, @Nullable Direction> MATTER_BLOCK = BlockCapability.createSided(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "matter"), IMatterStorage.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<IPatternStorage> PATTERN = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final BlockCapability<MatterNode, @Nullable Direction> MATTER_NODE = BlockCapability.createSided(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "matter_node"), MatterNode.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<IReplicationTaskProvider> TASK = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final ItemCapability<IPatternStorage, Void> PATTERN_ITEM = ItemCapability.createVoid(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "pattern"), IPatternStorage.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<IMatteryDrive<?>> DRIVE = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final BlockCapability<IPatternStorage, @Nullable Direction> PATTERN_BLOCK = BlockCapability.createSided(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "pattern"), IPatternStorage.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<StorageNode> STORAGE_NODE = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final BlockCapability<IReplicationTaskProvider, Void> REPLICATION_TASK = BlockCapability.createVoid(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "replication_task"), IReplicationTaskProvider.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<EnergyCableBlockEntity.Node> ENERGY_CABLE_NODE = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final ItemCapability<IMatteryDrive, Void> CONDENSATION_DRIVE = ItemCapability.createVoid(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "condensation_drive"), IMatteryDrive.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<ICuriosItemHandler> CURIOS_INVENTORY = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final BlockCapability<StorageNode, @Nullable Direction> STORAGE_NODE = BlockCapability.createSided(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "storage_node"), StorageNode.class);
+
+	// TODO: remove this
+	@Nonnull
+	@NotNull
+	public static final BlockCapability<EnergyCableBlockEntity.Node, Void> ENERGY_CABLE_NODE = BlockCapability.createVoid(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "energy_cable_node"), EnergyCableBlockEntity.Node.class);
 
 	@Nonnull
 	@NotNull
-	public static final Capability<ICurio> CURIOS_ITEM = CapabilityManager.get(new CapabilityToken<>() {});
-
-	@Nonnull
-	@NotNull
-	public static final Capability<IMatteryUpgrade> UPGRADE = CapabilityManager.get(new CapabilityToken<>() {});
+	public static final ItemCapability<IMatteryUpgrade, Void> UPGRADE = ItemCapability.createVoid(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "machine_upgrade"), IMatteryUpgrade.class);
 }
diff --git a/src/main/java/ru/dbotthepony/mc/otm/client/model/ExosuitModel.java b/src/main/java/ru/dbotthepony/mc/otm/client/model/ExosuitModel.java
index 3fef0651e..cb9bbadbc 100644
--- a/src/main/java/ru/dbotthepony/mc/otm/client/model/ExosuitModel.java
+++ b/src/main/java/ru/dbotthepony/mc/otm/client/model/ExosuitModel.java
@@ -1,9 +1,7 @@
 package ru.dbotthepony.mc.otm.client.model;
 
-import com.mojang.blaze3d.systems.RenderSystem;
 import com.mojang.blaze3d.vertex.PoseStack;
 import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
-import net.minecraft.client.Minecraft;
 import net.minecraft.client.model.HumanoidModel;
 import net.minecraft.client.model.PlayerModel;
 import net.minecraft.client.model.geom.PartPose;
@@ -21,11 +19,8 @@ import net.minecraft.client.renderer.entity.layers.RenderLayer;
 import net.minecraft.client.renderer.entity.player.PlayerRenderer;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraftforge.client.event.RenderPlayerEvent;
-import org.joml.Vector3f;
-import org.joml.Vector4f;
 import ru.dbotthepony.mc.otm.OverdriveThatMatters;
 import ru.dbotthepony.mc.otm.capability.MatteryCapability;
-import ru.dbotthepony.mc.otm.network.SmokeParticlesPacket;
 
 import javax.annotation.Nonnull;
 import java.util.Set;
diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinInventory.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinInventory.java
index 0b9a709b3..8c69e3245 100644
--- a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinInventory.java
+++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinInventory.java
@@ -18,7 +18,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 import ru.dbotthepony.mc.otm.capability.MatteryCapability;
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer;
 
 import java.util.function.Predicate;
 
@@ -61,7 +61,7 @@ public class MixinInventory {
 		at = @At("TAIL")
 	)
 	private void dropAll(CallbackInfo ci) {
-		MatteryPlayerCapability.inventoryDropAll((Inventory)(Object)this);
+		MatteryPlayer.inventoryDropAll((Inventory)(Object)this);
 	}
 
 	@Inject(
@@ -69,7 +69,7 @@ public class MixinInventory {
 		at = @At("TAIL")
 	)
 	private void clearContent(CallbackInfo ci) {
-		MatteryPlayerCapability.inventoryClearContent((Inventory)(Object)this);
+		MatteryPlayer.inventoryClearContent((Inventory)(Object)this);
 	}
 
 	@Inject(
diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinMinecraft.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinMinecraft.java
index bbf7892bd..0adc06a71 100644
--- a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinMinecraft.java
+++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinMinecraft.java
@@ -5,10 +5,8 @@ import net.minecraft.world.entity.player.Inventory;
 import net.minecraft.world.item.ItemStack;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.Redirect;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer;
 
 @Mixin(Minecraft.class)
 public class MixinMinecraft {
@@ -22,7 +20,7 @@ public class MixinMinecraft {
 	private int pickBlock(Inventory inventory, ItemStack itemStack) {
 		int i = inventory.findSlotMatchingItem(itemStack);
 
-		MatteryPlayerCapability.pickBlockHook(i, itemStack);
+		MatteryPlayer.pickBlockHook(i, itemStack);
 
 		return i;
 	}
diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPatchProjectileFinder.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPatchProjectileFinder.java
deleted file mode 100644
index 4dd8325cb..000000000
--- a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPatchProjectileFinder.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package ru.dbotthepony.mc.otm.mixin;
-
-import net.minecraft.world.entity.player.Player;
-import net.minecraft.world.item.ItemStack;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
-
-@Mixin(Player.class)
-public class MixinPatchProjectileFinder {
-	@Inject(
-		method = "getProjectile(Lnet/minecraft/world/item/ItemStack;)Lnet/minecraft/world/item/ItemStack;",
-		at = @At(
-			value = "INVOKE",
-			target = "net.minecraftforge.common.ForgeHooks.getProjectile(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Lnet/minecraft/world/item/ItemStack;",
-			ordinal = 2,
-			shift = At.Shift.BEFORE,
-			remap = false
-		),
-		cancellable = true)
-	private void exosuitGetProjectileHook(ItemStack weaponItem, CallbackInfoReturnable<ItemStack> hook) {
-		ItemStack result = MatteryPlayerCapability.getProjectileHook((Player) ((Object) this), weaponItem);
-
-		if (result != null) {
-			hook.setReturnValue(result);
-		}
-	}
-}
diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPlayer.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPlayer.java
index 1e112bcde..9cc01fed5 100644
--- a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPlayer.java
+++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPlayer.java
@@ -1,19 +1,85 @@
 package ru.dbotthepony.mc.otm.mixin;
 
+import com.mojang.authlib.GameProfile;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.damagesource.DamageSource;
 import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+import org.jetbrains.annotations.NotNull;
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+import ru.dbotthepony.mc.otm.capability.IMatteryPlayer;
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer;
+
+import java.util.Objects;
 
 @Mixin(Player.class)
-public class MixinPlayer {
+public class MixinPlayer implements IMatteryPlayer {
+	private Player otmSelf() {
+		return (Player) (Object) this;
+	}
+
+	@Inject(
+		method = "getProjectile(Lnet/minecraft/world/item/ItemStack;)Lnet/minecraft/world/item/ItemStack;",
+		at = @At(
+			value = "INVOKE",
+			target = "net.minecraftforge.common.ForgeHooks.getProjectile(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Lnet/minecraft/world/item/ItemStack;",
+			ordinal = 2,
+			shift = At.Shift.BEFORE,
+			remap = false
+		),
+		cancellable = true)
+	private void exosuitGetProjectileHook(ItemStack weaponItem, CallbackInfoReturnable<ItemStack> hook) {
+		ItemStack result = MatteryPlayer.getProjectileHook(otmSelf(), weaponItem);
+
+		if (result != null) {
+			hook.setReturnValue(result);
+		}
+	}
+
 	@Inject(
 		method = "destroyVanishingCursedItems()V",
 		at = @At("TAIL")
 	)
 	private void destroyVanishingCursedItems(CallbackInfo ci) {
-		MatteryPlayerCapability.playerDestroyVanishingCursedItems((Player)(Object)this);
+		MatteryPlayer.playerDestroyVanishingCursedItems(otmSelf());
+	}
+
+	private MatteryPlayer otmPlayer;
+
+	@Inject(
+		method = "<init>(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;FLcom/mojang/authlib/GameProfile;)V",
+		at = @At("TAIL")
+	)
+	private void constructOtmPlayer(Level p_250508_, BlockPos p_250289_, float p_251702_, GameProfile p_252153_, CallbackInfo ci) {
+		otmPlayer = new MatteryPlayer(otmSelf());
+	}
+
+	@NotNull
+	@Override
+	public MatteryPlayer getOtmPlayer() {
+		return Objects.requireNonNull(otmPlayer, "Something went horribly wrong, otmPlayer is null");
+	}
+
+	@Inject(
+		method = "addAdditionalSaveData(Lnet/minecraft/nbt/CompoundTag;)V",
+		at = @At("TAIL")
+	)
+	private void addAdditionalSaveData(CompoundTag data, CallbackInfo ci) {
+		data.put("overdrive_that_matters_player", otmPlayer.serializeNBT(otmSelf().registryAccess()));
+	}
+
+	@Inject(
+		method = "readAdditionalSaveData(Lnet/minecraft/nbt/CompoundTag;)V",
+		at = @At("TAIL")
+	)
+	private void readAdditionalSaveData(CompoundTag data, CallbackInfo ci) {
+		otmPlayer.deserializeNBT(data.getCompound("overdrive_that_matters_player"), otmSelf().registryAccess());
 	}
 }
diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityOxygenSystemMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityOxygenSystemMixin.java
deleted file mode 100644
index 6201620d0..000000000
--- a/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityOxygenSystemMixin.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package ru.dbotthepony.mc.otm.mixin.compat.ad_astra;
-
-import earth.terrarium.ad_astra.common.entity.system.EntityOxygenSystem;
-import net.minecraft.server.level.ServerLevel;
-import net.minecraft.world.entity.LivingEntity;
-import net.minecraft.world.entity.player.Player;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import ru.dbotthepony.mc.otm.capability.MatteryCapability;
-import ru.dbotthepony.mc.otm.config.ServerCompatConfig;
-
-@Mixin(EntityOxygenSystem.class)
-public class EntityOxygenSystemMixin {
-	@Inject(
-		method = "oxygenTick(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/server/level/ServerLevel;)V",
-		at = @At("HEAD"),
-		cancellable = true,
-		remap = false
-	)
-	private static void oxygenTick(LivingEntity entity, ServerLevel level, CallbackInfo hook) {
-		if (entity instanceof Player && ServerCompatConfig.AdAstra.INSTANCE.getANDROIDS_DO_NOT_NEED_OXYGEN()) {
-			entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(it -> {
-				if (it.isAndroid()) {
-					hook.cancel();
-				}
-			});
-		}
-	}
-}
diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityTemperatureSystemMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityTemperatureSystemMixin.java
deleted file mode 100644
index 421069a9c..000000000
--- a/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityTemperatureSystemMixin.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package ru.dbotthepony.mc.otm.mixin.compat.ad_astra;
-
-import earth.terrarium.ad_astra.common.entity.system.EntityTemperatureSystem;
-import earth.terrarium.ad_astra.common.util.ModUtils;
-import net.minecraft.server.level.ServerLevel;
-import net.minecraft.world.entity.LivingEntity;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import ru.dbotthepony.mc.otm.config.ServerCompatConfig;
-
-// STAHP!
-@Mixin(EntityTemperatureSystem.class)
-public class EntityTemperatureSystemMixin {
-	@Inject(
-		method = "temperatureTick(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/server/level/ServerLevel;)V",
-		at = @At("HEAD"),
-		cancellable = true,
-		remap = false
-	)
-	private static void temperatureTick(LivingEntity entity, ServerLevel level, CallbackInfo hook) {
-		if (ServerCompatConfig.AdAstra.INSTANCE.getWHATS_UP_WITH_TEMPERATURE() && !ModUtils.planetHasAtmosphere(level)) {
-			hook.cancel();
-		}
-	}
-}
diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/OxygenUtilsMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/OxygenUtilsMixin.java
deleted file mode 100644
index e8a1a7d57..000000000
--- a/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/OxygenUtilsMixin.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package ru.dbotthepony.mc.otm.mixin.compat.ad_astra;
-
-import earth.terrarium.ad_astra.common.util.OxygenUtils;
-import net.minecraft.world.entity.LivingEntity;
-import net.minecraft.world.entity.player.Player;
-import net.minecraft.world.level.Level;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-import ru.dbotthepony.mc.otm.capability.MatteryCapability;
-import ru.dbotthepony.mc.otm.config.ServerCompatConfig;
-
-@Mixin(OxygenUtils.class)
-public class OxygenUtilsMixin {
-	@Inject(
-		method = "entityHasOxygen(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/LivingEntity;)Z",
-		at = @At("HEAD"),
-		cancellable = true,
-		remap = false
-	)
-	private static void entityHasOxygen(Level level, LivingEntity entity, CallbackInfoReturnable<Boolean> hook) {
-		if (entity instanceof Player && ServerCompatConfig.AdAstra.INSTANCE.getANDROIDS_DO_NOT_NEED_OXYGEN()) {
-			entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(it -> {
-				if (it.isAndroid()) {
-					hook.setReturnValue(true);
-				}
-			});
-		}
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt
index 136e5c99f..a08c1870e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt
@@ -4,16 +4,19 @@
 package ru.dbotthepony.mc.otm
 
 import net.minecraft.client.server.IntegratedServer
+import net.minecraft.core.HolderLookup
 import net.minecraft.server.MinecraftServer
 import net.minecraft.world.level.Level
-import net.minecraftforge.api.distmarker.Dist
-import net.minecraftforge.event.TickEvent
-import net.minecraftforge.event.TickEvent.ServerTickEvent
-import net.minecraftforge.event.TickEvent.LevelTickEvent
-import net.minecraftforge.event.server.ServerAboutToStartEvent
-import net.minecraftforge.event.server.ServerStoppedEvent
-import net.minecraftforge.event.server.ServerStoppingEvent
-import net.minecraftforge.fml.loading.FMLLoader
+import net.neoforged.api.distmarker.Dist
+import net.neoforged.bus.api.EventPriority
+import net.neoforged.bus.api.SubscribeEvent
+import net.neoforged.fml.common.EventBusSubscriber
+import net.neoforged.fml.loading.FMLLoader
+import net.neoforged.neoforge.event.server.ServerAboutToStartEvent
+import net.neoforged.neoforge.event.server.ServerStoppedEvent
+import net.neoforged.neoforge.event.server.ServerStoppingEvent
+import net.neoforged.neoforge.event.tick.LevelTickEvent
+import net.neoforged.neoforge.event.tick.ServerTickEvent
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
 import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
@@ -24,7 +27,6 @@ import ru.dbotthepony.mc.otm.core.util.IConditionalTickable
 import ru.dbotthepony.mc.otm.core.util.ITickable
 import ru.dbotthepony.mc.otm.core.util.TickList
 import ru.dbotthepony.mc.otm.graph.GraphNodeList
-import ru.dbotthepony.mc.otm.network.MatteryNetworkChannel
 import java.util.*
 import java.util.concurrent.atomic.AtomicInteger
 
@@ -43,6 +45,15 @@ val isClient: Boolean by lazy { FMLLoader.getDist() == Dist.CLIENT }
 val UNIVERSE_TICKS get() = postServerTick.ticks
 val Level.ticksPassed get() = postWorldTick.computeIfAbsent(this) { TickList() }.ticks
 
+@Deprecated("Use when absolutely necessary, like context-less methods/properties")
+val Registries: HolderLookup.Provider get() {
+	if (isClient) {
+		return NULLABLE_MINECRAFT_SERVER?.registryAccess() ?: minecraft.level?.registryAccess() ?: throw IllegalStateException("Not in game")
+	} else {
+		return NULLABLE_MINECRAFT_SERVER?.registryAccess() ?: throw IllegalStateException("Not in game")
+	}
+}
+
 fun <V> lazyPerServer(fn: (MinecraftServer) -> V): Lazy<V> {
 	return AtomicallyInvalidatedLazy(serverCounter) {
 		if (!SERVER_IS_LIVE)
@@ -169,31 +180,30 @@ var SERVER_IS_LIVE = false
 
 private val LOGGER = LogManager.getLogger()
 
-fun onServerTick(event: ServerTickEvent) {
-	if (event.phase === TickEvent.Phase.START) {
-		preServerTick.tick()
-		serverThreads.add(Thread.currentThread())
+fun onServerTickPre(event: ServerTickEvent.Pre) {
+	preServerTick.tick()
+	serverThreads.add(Thread.currentThread())
+}
+
+fun onServerTickPost(event: ServerTickEvent.Post) {
+	postServerTick.tick()
+	// чтоб не плодить кучу подписчиков, вызовем напрямую отсюда
+	GraphNodeList.tick()
+	AbstractProfiledStorage.onServerPostTick()
+}
+
+fun onLevelTickPre(event: LevelTickEvent.Pre) {
+	preWorldTick[event.level]?.tick()
+
+	if (event.level.isClientSide) {
+		clientThreads.add(Thread.currentThread())
 	} else {
-		postServerTick.tick()
-		// чтоб не плодить кучу подписчиков, вызовем напрямую отсюда
-		GraphNodeList.tick()
-		AbstractProfiledStorage.onServerPostTick()
+		serverThreads.add(Thread.currentThread())
 	}
 }
 
-fun onLevelTick(event: LevelTickEvent) {
-	if (event.phase === TickEvent.Phase.START) {
-		preWorldTick[event.level]?.tick()
-
-		if (event.side.isClient) {
-			clientThreads.add(Thread.currentThread())
-		} else if (event.side.isServer) {
-			serverThreads.add(Thread.currentThread())
-		}
-	} else {
-		postWorldTick[event.level]?.tick()
-		MatteryBlockEntity.postLevelTick(event)
-	}
+fun onLevelTickPost(event: LevelTickEvent.Post) {
+	postWorldTick[event.level]?.tick()
 }
 
 fun onceServerPre(ticker: ITickable) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigList.kt
index 7a9493536..a227a6282 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigList.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigList.kt
@@ -1,13 +1,13 @@
 package ru.dbotthepony.mc.otm
 
 import it.unimi.dsi.fastutil.ints.IntArrayList
+import net.minecraft.core.Registry
 import net.minecraft.resources.ResourceLocation
-import net.minecraftforge.common.ForgeConfigSpec
-import net.minecraftforge.registries.IForgeRegistry
+import net.neoforged.neoforge.common.ModConfigSpec
 import ru.dbotthepony.mc.otm.config.getValue
 import java.util.LinkedList
 
-abstract class ObservedConfigList<V : Any>(val parent: ForgeConfigSpec.ConfigValue<MutableList<String>>, private val allowNulls: Boolean = false) : AbstractMutableList<V>(), RandomAccess {
+abstract class ObservedConfigList<V : Any>(val parent: ModConfigSpec.ConfigValue<MutableList<String>>, private val allowNulls: Boolean = false) : AbstractMutableList<V>(), RandomAccess {
 	private val rawValue: MutableList<String> by parent
 	private val observedValue = LinkedList<String>()
 	private val parsedView: ArrayList<V> = ArrayList()
@@ -140,33 +140,3 @@ abstract class ObservedConfigList<V : Any>(val parent: ForgeConfigSpec.ConfigVal
 		return parsedView[index]
 	}
 }
-
-fun <T : Any> ForgeConfigSpec.Builder.defineObjectList(
-	name: String,
-	defaultValues: () -> List<T>,
-	write: (value: T) -> Pair<String, T>?,
-	read: (value: String) -> T?
-): ObservedConfigList<T> {
-	val parent = defineListAllowEmpty(name.split('.'), { defaultValues.invoke().mapNotNull { write.invoke(it)?.first }.toMutableList()}, { it is String }) as ForgeConfigSpec.ConfigValue<MutableList<String>>
-
-	return object : ObservedConfigList<T>(parent) {
-		override fun toString(value: T): Pair<String, T>? {
-			return write.invoke(value)
-		}
-
-		override fun fromString(value: String): T? {
-			return read.invoke(value)
-		}
-	}
-}
-
-@Deprecated("Obviously better use tags")
-fun <T : Any> ForgeConfigSpec.Builder.defineForgeObjectList(
-	name: String,
-	defaultValues: () -> List<T>,
-	registry: IForgeRegistry<T>
-): ObservedConfigList<T> {
-	return defineObjectList(name, defaultValues,
-		write = { (registry.getKey(it)?.toString() ?: return@defineObjectList null) to it },
-		read = { registry.getValue(ResourceLocation(it)) })
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidActiveFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidActiveFeature.kt
index 93eea6d6e..ff8d06398 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidActiveFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidActiveFeature.kt
@@ -2,10 +2,10 @@ package ru.dbotthepony.mc.otm.android
 
 import com.mojang.blaze3d.vertex.PoseStack
 import net.minecraft.client.Camera
-import net.minecraftforge.client.event.RenderLevelStageEvent
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import net.neoforged.neoforge.client.event.RenderLevelStageEvent
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 
-abstract class AndroidActiveFeature(type: AndroidFeatureType<*>, android: MatteryPlayerCapability) : AndroidSwitchableFeature(type, android) {
+abstract class AndroidActiveFeature(type: AndroidFeatureType<*>, android: MatteryPlayer) : AndroidSwitchableFeature(type, android) {
 	/**
 	 * return true to send packet to server if [isClient] is true
 	 *
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt
index 9fa3c0348..b7cdbbb44 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt
@@ -1,18 +1,18 @@
 package ru.dbotthepony.mc.otm.android
 
 import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
-import net.minecraftforge.common.util.INBTSerializable
-import net.minecraftforge.event.entity.living.LivingAttackEvent
-import net.minecraftforge.event.entity.living.LivingHurtEvent
+import net.neoforged.neoforge.common.util.INBTSerializable
+import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent
 import ru.dbotthepony.kommons.io.DelegateSyncher
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.core.nbt.set
 import java.io.InputStream
 
-abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: MatteryPlayerCapability) : INBTSerializable<CompoundTag> {
+abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: MatteryPlayer) : INBTSerializable<CompoundTag> {
 	val ply get() = android.ply
 	val syncher = DelegateSyncher()
 	val syncherRemote = syncher.Remote()
@@ -37,8 +37,7 @@ abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: Matt
 	open fun applyModifiers() {}
 	open fun removeModifiers() {}
 
-	open fun onHurt(event: LivingHurtEvent) {}
-	open fun onAttack(event: LivingAttackEvent) {}
+	open fun onHurt(event: LivingIncomingDamageEvent) {}
 
 	open fun collectNetworkPayload(): FastByteArrayOutputStream? {
 		syncher.observe()
@@ -49,23 +48,23 @@ abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: Matt
 		syncher.read(stream)
 	}
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		return CompoundTag().also {
 			it["level"] = level
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag) {
 		level = nbt.getInt("level")
 	}
 }
 
-class DummyAndroidFeature(type: AndroidFeatureType<*>, android: MatteryPlayerCapability) : AndroidFeature(type, android) {
-	override fun serializeNBT(): CompoundTag {
+class DummyAndroidFeature(type: AndroidFeatureType<*>, android: MatteryPlayer) : AndroidFeature(type, android) {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		return CompoundTag()
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag) {
 
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeatureType.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeatureType.kt
index 4a3da406a..43c6d78ca 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeatureType.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeatureType.kt
@@ -4,26 +4,26 @@ import net.minecraft.network.chat.Component
 import net.minecraft.network.chat.ComponentContents
 import net.minecraft.network.chat.MutableComponent
 import net.minecraft.network.chat.contents.TranslatableContents
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.core.getKeyNullable
 import ru.dbotthepony.mc.otm.registry.MRegistry
 
 open class AndroidFeatureType<T : AndroidFeature> {
-	protected val factory: (AndroidFeatureType<T>, MatteryPlayerCapability) -> T
+	protected val factory: (AndroidFeatureType<T>, MatteryPlayer) -> T
 
-	constructor(factory: (MatteryPlayerCapability) -> T) : super() {
+	constructor(factory: (MatteryPlayer) -> T) : super() {
 		this.factory = { _, capability -> factory.invoke(capability) }
 	}
 
-	constructor(factory: (AndroidFeatureType<T>, MatteryPlayerCapability) -> T) : super() {
+	constructor(factory: (AndroidFeatureType<T>, MatteryPlayer) -> T) : super() {
 		this.factory = factory
 	}
 
-	fun create(android: MatteryPlayerCapability): T {
+	fun create(android: MatteryPlayer): T {
 		return factory.invoke(this, android)
 	}
 
-	open fun isApplicable(android: MatteryPlayerCapability) = true
+	open fun isApplicable(android: MatteryPlayer) = true
 
 	val registryName by lazy {
 		MRegistry.ANDROID_FEATURES.getKeyNullable(this)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt
index 30c5a743e..aeb204908 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt
@@ -2,19 +2,19 @@ package ru.dbotthepony.mc.otm.android
 
 import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
 import net.minecraft.ChatFormatting
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.network.chat.Component
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.world.entity.player.Player
-import net.minecraftforge.common.MinecraftForge
-import net.minecraftforge.common.util.INBTSerializable
-import net.minecraftforge.eventbus.api.Event
-import net.minecraftforge.eventbus.api.Event.HasResult
+import net.neoforged.bus.api.Event
+import net.neoforged.neoforge.common.NeoForge
+import net.neoforged.neoforge.common.util.INBTSerializable
 import ru.dbotthepony.kommons.io.DelegateSyncher
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.awareItemsStream
 import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
@@ -25,33 +25,27 @@ import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger
 import java.io.InputStream
 import kotlin.math.absoluteValue
 
-class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlayerCapability) : INBTSerializable<CompoundTag> {
+class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlayer) : INBTSerializable<CompoundTag> {
 	/**
-	 * Fired on main event bus [MinecraftForge.EVENT_BUS]
+	 * Fired on main event bus [NeoForge.EVENT_BUS]
 	 */
 	data class OnResearched(val research: AndroidResearch) : Event()
 
 	/**
-	 * Fired on main event bus [MinecraftForge.EVENT_BUS]
+	 * Fired on main event bus [NeoForge.EVENT_BUS]
 	 */
 	data class OnUnResearched(val research: AndroidResearch) : Event()
 
 	/**
-	 * Fired on main event bus [MinecraftForge.EVENT_BUS]
+	 * Fired on main event bus [NeoForge.EVENT_BUS]
 	 */
 	data class OnRefunded(val research: AndroidResearch) : Event()
 
 	/**
-	 * Fired on main event bus [MinecraftForge.EVENT_BUS]
+	 * Fired on main event bus [NeoForge.EVENT_BUS]
 	 */
 	data class GatherTooltipsEvent(val research: AndroidResearch, val tooltips: MutableList<Component>) : Event()
 
-	/**
-	 * Fired on main event bus [MinecraftForge.EVENT_BUS]
-	 */
-	@HasResult
-	data class ConsumeResearchCost(val research: AndroidResearch, val isSimulating: Boolean) : Event()
-
 	val ply: Player get() = capability.ply
 
 	val syncher = DelegateSyncher()
@@ -84,7 +78,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
 			result.onUnResearched(this)
 		}
 
-		MinecraftForge.EVENT_BUS.post(OnUnResearched(this))
+		NeoForge.EVENT_BUS.post(OnUnResearched(this))
 	}
 
 	fun onResearched() {
@@ -92,7 +86,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
 			result.onResearched(this)
 		}
 
-		MinecraftForge.EVENT_BUS.post(OnResearched(this))
+		NeoForge.EVENT_BUS.post(OnResearched(this))
 	}
 
 	/**
@@ -108,15 +102,6 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
 			return true
 		}
 
-		val event = ConsumeResearchCost(this, simulate)
-		MinecraftForge.EVENT_BUS.post(event)
-
-		if (event.result == Event.Result.ALLOW) {
-			return true
-		} else if (event.result == Event.Result.DENY) {
-			return false
-		}
-
 		if (!simulate && !consumeResearchCost(true)) {
 			return false
 		}
@@ -183,7 +168,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
 			}
 		}
 
-		MinecraftForge.EVENT_BUS.post(OnRefunded(this))
+		NeoForge.EVENT_BUS.post(OnRefunded(this))
 
 		return true
 	}
@@ -300,7 +285,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
 			line.addLines(this, lines)
 		}
 
-		MinecraftForge.EVENT_BUS.post(GatherTooltipsEvent(this, lines))
+		NeoForge.EVENT_BUS.post(GatherTooltipsEvent(this, lines))
 
 		return lines
 	}
@@ -382,14 +367,14 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
 	 */
 	val screenTooltipHeader: Component get() = type.displayName
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		return CompoundTag().also {
 			it["researched"] = isResearched
 			it["tag"] = tag
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag) {
 		isResearched = nbt.getBoolean("researched")
 		tag = nbt.getCompound("tag")
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt
index 98d175bfc..1ca529af2 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt
@@ -6,8 +6,7 @@ import net.minecraft.data.CachedOutput
 import net.minecraft.data.DataProvider
 import net.minecraft.data.PackOutput
 import net.minecraft.resources.ResourceLocation
-import net.minecraftforge.data.event.GatherDataEvent
-import ru.dbotthepony.mc.otm.core.toJson
+import net.neoforged.neoforge.data.event.GatherDataEvent
 import ru.dbotthepony.mc.otm.core.toJsonStrict
 import ru.dbotthepony.mc.otm.core.util.WriteOnce
 import java.util.Collections
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDescription.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDescription.kt
index e5b2f2a81..44b450b0d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDescription.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDescription.kt
@@ -2,11 +2,12 @@ package ru.dbotthepony.mc.otm.android
 
 import com.mojang.datafixers.util.Either
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.ChatFormatting
 import net.minecraft.network.chat.Component
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
+import net.minecraft.network.chat.ComponentSerialization
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.client.ShiftPressedCond
@@ -15,19 +16,19 @@ import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.getValue
 import ru.dbotthepony.mc.otm.core.util.formatPower
-import ru.dbotthepony.mc.otm.data.ComponentCodec
 import ru.dbotthepony.mc.otm.data.SingletonCodec
 import ru.dbotthepony.mc.otm.data.simpleCodec
+import ru.dbotthepony.mc.otm.registry.MDeferredRegister
 import ru.dbotthepony.mc.otm.registry.RegistryDelegate
 
 object AndroidResearchDescriptions {
-	private val registrar = DeferredRegister.create(AndroidResearchDescription.registryKey, OverdriveThatMatters.MOD_ID)
+	private val registrar = MDeferredRegister(AndroidResearchDescription.registryKey, OverdriveThatMatters.MOD_ID)
 
 	init {
 		registrar.register("plain") { PlainAndroidResearchDescription }
 	}
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registrar.register(bus)
 	}
 
@@ -73,11 +74,11 @@ interface AndroidResearchDescription {
 	 * Type, representing raw description populator in registry
 	 */
 	interface Type<T : AndroidResearchDescription> {
-		val codec: Codec<T>
+		val codec: MapCodec<T>
 	}
 
 	abstract class Singleton : AndroidResearchDescription, Type<Singleton> {
-		override val codec = SingletonCodec(this)
+		override val codec: MapCodec<Singleton> = MapCodec.unit(this)
 		override val type: Type<*>
 			get() = this
 	}
@@ -92,8 +93,8 @@ interface AndroidResearchDescription {
 				get() = this@Leveled
 		}
 
-		override val codec: Codec<Instance> by lazy {
-			RecordCodecBuilder.create {
+		override val codec: MapCodec<Instance> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(Codec.INT.fieldOf("level").forGetter(Instance::level)).apply(it, ::Instance)
 			}
 		}
@@ -103,18 +104,16 @@ interface AndroidResearchDescription {
 	val type: Type<*>
 
 	companion object {
-		private val delegate = RegistryDelegate<Type<*>>("android_research_description") {
-			disableSaving()
-		}
+		private val delegate = RegistryDelegate<Type<*>>("android_research_description") {}
 
 		val registry by delegate
 		val registryKey get() = delegate.key
 
 		val CODEC: Codec<AndroidResearchDescription> by lazy {
-			registry.codec.dispatch({ it.type }, { it.codec })
+			registry.byNameCodec().dispatch({ it.type }, { it.codec })
 		}
 
-		internal fun register(bus: IEventBus) {
+		fun register(bus: IEventBus) {
 			bus.addListener(delegate::build)
 		}
 
@@ -140,9 +139,11 @@ object PlainAndroidResearchDescription : AndroidResearchDescription.Type<PlainAn
 
 	fun make(line: Component) = Instance(line)
 
-	override val codec: Codec<Instance> by lazy {
-		Codec
-			.either(ComponentCodec, simpleCodec(::Instance, Instance::line, ComponentCodec))
-			.xmap({ c -> c.map({ Instance(it) }, { it }) }, { c -> Either.left(c.line) })
+	override val codec: MapCodec<Instance> by lazy {
+		RecordCodecBuilder.mapCodec {
+			it.group(
+				ComponentSerialization.CODEC.fieldOf("line").forGetter { it.line }
+			).apply(it, ::Instance)
+		}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt
index 86eaa2846..5190b4dd0 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt
@@ -7,28 +7,31 @@ import com.google.gson.JsonObject
 import com.google.gson.JsonSyntaxException
 import net.minecraft.client.server.IntegratedServer
 import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.resources.ResourceLocation
+import net.minecraft.server.level.ServerPlayer
 import net.minecraft.server.packs.resources.ResourceManager
 import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener
 import net.minecraft.util.profiling.ProfilerFiller
-import net.minecraftforge.event.AddReloadListenerEvent
-import net.minecraftforge.event.OnDatapackSyncEvent
-import net.minecraftforge.network.PacketDistributor
+import net.neoforged.neoforge.event.AddReloadListenerEvent
+import net.neoforged.neoforge.event.OnDatapackSyncEvent
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.mc.otm.MINECRAFT_SERVER
 import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.client.minecraft
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.fromJsonStrict
 import ru.dbotthepony.mc.otm.core.fromNetwork
 import ru.dbotthepony.mc.otm.core.set
 import ru.dbotthepony.mc.otm.core.toNetwork
-import ru.dbotthepony.mc.otm.network.GenericNetworkChannel
-import ru.dbotthepony.mc.otm.network.MNetworkContext
-import ru.dbotthepony.mc.otm.network.MatteryPacket
 import ru.dbotthepony.mc.otm.onceServer
-import java.util.LinkedList
+import java.util.*
 
 object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable<AndroidResearchType> {
 	const val DIRECTORY = "otm_android_research"
@@ -85,7 +88,7 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s
 		if (SERVER_IS_LIVE) {
 			onceServer {
 				for (ply in MINECRAFT_SERVER.playerList.players) {
-					ply.matteryPlayer?.reloadResearch()
+					ply.matteryPlayer.reloadResearch()
 				}
 			}
 		}
@@ -99,21 +102,22 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s
 		val packet = SyncPacket(researchMap.values)
 
 		if (event.player != null) {
-			GenericNetworkChannel.send(event.player!!, packet)
+			PacketDistributor.sendToPlayer(event.player as ServerPlayer, packet)
 		} else {
-			GenericNetworkChannel.send(PacketDistributor.ALL.noArg(), packet)
+			PacketDistributor.sendToAllPlayers(packet)
 		}
 	}
 
-	class SyncPacket(val collection: Collection<AndroidResearchType>) : MatteryPacket {
-		override fun write(buff: FriendlyByteBuf) {
+	val SYNC_TYPE = CustomPacketPayload.Type<SyncPacket>(ResourceLocation(OverdriveThatMatters.MOD_ID, "research_sync"))
+	val SYNC_CODEC: StreamCodec<FriendlyByteBuf, SyncPacket> = StreamCodec.ofMember(SyncPacket::write, ::readSyncPacket)
+
+	class SyncPacket(val collection: Collection<AndroidResearchType>) : CustomPacketPayload {
+		fun write(buff: FriendlyByteBuf) {
 			buff.writeCollection(collection) { a, b -> AndroidResearchType.CODEC.toNetwork(a, b) }
 			LOGGER.debug("Constructed android research registry packet, ${buff.writerIndex()} bytes in size")
 		}
 
-		override fun play(context: MNetworkContext) {
-			context.packetHandled = true
-
+		fun play(context: IPayloadContext) {
 			if (NULLABLE_MINECRAFT_SERVER is IntegratedServer)
 				return
 
@@ -133,6 +137,10 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s
 				minecraft.player?.matteryPlayer?.reloadResearch()
 			}
 		}
+
+		override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+			return SYNC_TYPE
+		}
 	}
 
 	fun readSyncPacket(buff: FriendlyByteBuf): SyncPacket {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchResult.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchResult.kt
index d83324d76..3e2d74dfe 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchResult.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchResult.kt
@@ -2,21 +2,22 @@ package ru.dbotthepony.mc.otm.android
 
 import com.mojang.datafixers.util.Either
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.resources.ResourceLocation
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
+import net.neoforged.bus.api.IEventBus
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.core.getValue
 import ru.dbotthepony.mc.otm.data.SingletonCodec
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
+import ru.dbotthepony.mc.otm.registry.MDeferredRegister
 import ru.dbotthepony.mc.otm.registry.MRegistry
 import ru.dbotthepony.mc.otm.registry.RegistryDelegate
 
 object AndroidResearchResults {
-	private val registrar = DeferredRegister.create(AndroidResearchResult.registryKey, OverdriveThatMatters.MOD_ID)
+	private val registrar = MDeferredRegister(AndroidResearchResult.registryKey, OverdriveThatMatters.MOD_ID)
 
 	init {
 		registrar.register("feature") { AndroidResearchResult.Feature.Companion }
@@ -24,7 +25,7 @@ object AndroidResearchResults {
 	}
 
 	private object NanobotsArmorStrength : AndroidResearchResult.Singleton<NanobotsArmorStrength> {
-		override val codec: Codec<NanobotsArmorStrength> = SingletonCodec(this)
+		override val codec: MapCodec<NanobotsArmorStrength> = MapCodec.unit(this)
 		override val type: AndroidResearchResult.Type<*>
 			get() = this
 
@@ -40,7 +41,7 @@ object AndroidResearchResults {
 	}
 
 	private object NanobotsArmorSpeed : AndroidResearchResult.Singleton<NanobotsArmorSpeed> {
-		override val codec: Codec<NanobotsArmorSpeed> = SingletonCodec(this)
+		override val codec: MapCodec<NanobotsArmorSpeed> = MapCodec.unit(this)
 		override val type: AndroidResearchResult.Type<*>
 			get() = this
 
@@ -58,14 +59,14 @@ object AndroidResearchResults {
 	val NANOBOTS_ARMOR_STRENGTH: AndroidResearchResult.Singleton<*> by registrar.register("nanobots_armor_strength") { NanobotsArmorStrength }
 	val NANOBOTS_ARMOR_SPEED: AndroidResearchResult.Singleton<*> by registrar.register("nanobots_armor_speed") { NanobotsArmorSpeed }
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registrar.register(bus)
 	}
 }
 
 interface AndroidResearchResult {
 	interface Type<T : AndroidResearchResult> {
-		val codec: Codec<T>
+		val codec: MapCodec<T>
 	}
 
 	interface Singleton<T : Singleton<T>> : Type<T>, AndroidResearchResult
@@ -74,7 +75,7 @@ interface AndroidResearchResult {
 	 * Adds specific android feature [id] to target, does nothing if target already has specified feature
 	 */
 	class Feature(val id: ResourceLocation, val optional: Boolean = false) : AndroidResearchResult {
-		val feature = MRegistry.ANDROID_FEATURES.getValue(id) ?: if (optional) null else throw NoSuchElementException("Unknown android feature $id")
+		val feature = MRegistry.ANDROID_FEATURES.get(id) ?: if (optional) null else throw NoSuchElementException("Unknown android feature $id")
 
 		override val type: Type<*>
 			get() = Companion
@@ -108,7 +109,7 @@ interface AndroidResearchResult {
 	 * Increases level of specific android feature [id] by specified amount [levels]
 	 */
 	class FeatureLevel(val id: ResourceLocation, val optional: Boolean = false, val levels: Int = 1) : AndroidResearchResult {
-		val feature = MRegistry.ANDROID_FEATURES.getValue(id) ?: if (optional) null else throw NoSuchElementException("Unknown android feature $id")
+		val feature = MRegistry.ANDROID_FEATURES.get(id) ?: if (optional) null else throw NoSuchElementException("Unknown android feature $id")
 
 		override val type: Type<*>
 			get() = Companion
@@ -165,18 +166,16 @@ interface AndroidResearchResult {
 
 	companion object {
 		private val LOGGER = LogManager.getLogger()
-		private val delegate = RegistryDelegate<Type<*>>("android_research_result") {
-			disableSaving()
-		}
+		private val delegate = RegistryDelegate<Type<*>>("android_research_result") {}
 
 		val registry by delegate
 		val registryKey get() = delegate.key
 
 		val CODEC: Codec<AndroidResearchResult> by lazy {
-			registry.codec.dispatch({ it.type }, { it.codec })
+			registry.byNameCodec().dispatch({ it.type }, { it.codec })
 		}
 
-		internal fun register(bus: IEventBus) {
+		fun register(bus: IEventBus) {
 			bus.addListener(delegate::build)
 		}
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt
index ad2610563..2c87d3625 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt
@@ -8,8 +8,10 @@ import com.mojang.serialization.Codec
 import com.mojang.serialization.codecs.ListCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.network.chat.Component
 import net.minecraft.network.chat.ComponentContents
+import net.minecraft.network.chat.ComponentSerialization
 import net.minecraft.network.chat.MutableComponent
 import net.minecraft.network.chat.contents.TranslatableContents
 import net.minecraft.resources.ResourceLocation
@@ -18,13 +20,10 @@ import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.crafting.Ingredient
 import net.minecraft.world.level.ItemLike
-import net.minecraftforge.registries.ForgeRegistries
 import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite
 import ru.dbotthepony.mc.otm.client.render.sprites.SpriteType
 import ru.dbotthepony.mc.otm.core.collect.ListSet
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
-import ru.dbotthepony.mc.otm.core.isActuallyEmpty
-import ru.dbotthepony.mc.otm.data.ComponentCodec
 import ru.dbotthepony.mc.otm.data.JsonElementCodec
 import ru.dbotthepony.mc.otm.isClient
 import java.util.Optional
@@ -181,7 +180,7 @@ class AndroidResearchType(
 	private val definedItems: List<Pair<Ingredient, Int>> = ImmutableList.copyOf(items)
 
 	val results: ImmutableList<AndroidResearchResult> = ImmutableList.copyOf(results)
-	val items: Stream<out Pair<Ingredient, Int>> get() = definedItems.stream().filter { !it.first.isActuallyEmpty }
+	val items: Stream<out Pair<Ingredient, Int>> get() = definedItems.stream().filter { !it.first.hasNoItems() }
 	val description: ImmutableList<AndroidResearchDescription> = ImmutableList.copyOf(description)
 
 	/**
@@ -387,9 +386,9 @@ class AndroidResearchType(
 			RecordCodecBuilder.create {
 				it.group(
 					ResourceLocation.CODEC.fieldOf("id").forGetter(AndroidResearchType::id),
-					ListCodec(Reference.CODEC).fieldOf("prerequisites").forGetter(AndroidResearchType::prerequisites),
-					ListCodec(Reference.CODEC).fieldOf("blockedBy").forGetter(AndroidResearchType::blockedBy),
-					ListCodec(
+					Codec.list(Reference.CODEC).fieldOf("prerequisites").forGetter(AndroidResearchType::prerequisites),
+					Codec.list(Reference.CODEC).fieldOf("blockedBy").forGetter(AndroidResearchType::blockedBy),
+					Codec.list(
 						RecordCodecBuilder.create<Pair<Ingredient, Int>> {
 							it.group(
 								Ingredient.CODEC.fieldOf("item").forGetter { it.first },
@@ -397,13 +396,13 @@ class AndroidResearchType(
 							).apply(it, ::Pair)
 						}
 					).fieldOf("items").forGetter { it.definedItems },
-					ListCodec(AndroidResearchResult.CODEC).fieldOf("results").forGetter(AndroidResearchType::results),
-					ListCodec(AndroidResearchDescription.CODEC).fieldOf("description").forGetter(AndroidResearchType::description),
+					Codec.list(AndroidResearchResult.CODEC).fieldOf("results").forGetter(AndroidResearchType::results),
+					Codec.list(AndroidResearchDescription.CODEC).fieldOf("description").forGetter(AndroidResearchType::description),
 					Codec.intRange(0, Int.MAX_VALUE).fieldOf("experienceLevels").forGetter(AndroidResearchType::experienceLevels),
-					ComponentCodec.optionalFieldOf("customName").forGetter { Optional.ofNullable(it.customName) },
+					ComponentSerialization.CODEC.optionalFieldOf("customName").forGetter { Optional.ofNullable(it.customName) },
 					JsonElementCodec.xmap({ it as? JsonObject ?: throw JsonSyntaxException("Not a json object: $it") }, { it }).optionalFieldOf("skinIcon").forGetter { Optional.ofNullable(it.skinIcon) },
-					ForgeRegistries.ITEMS.codec.optionalFieldOf("itemIcon").forGetter { Optional.ofNullable(it.itemIcon) },
-					ComponentCodec.optionalFieldOf("iconTextValue").forGetter { Optional.ofNullable(it.iconTextValue) },
+					BuiltInRegistries.ITEM.byNameCodec().optionalFieldOf("itemIcon").forGetter { Optional.ofNullable(it.itemIcon) },
+					ComponentSerialization.CODEC.optionalFieldOf("iconTextValue").forGetter { Optional.ofNullable(it.iconTextValue) },
 				).apply(it, ::AndroidResearchType)
 			}
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt
index 6563b1066..021c03993 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt
@@ -1,17 +1,18 @@
 package ru.dbotthepony.mc.otm.android
 
 import net.minecraft.client.multiplayer.ClientLevel
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.server.level.ServerPlayer
 import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.core.nbt.set
 
-abstract class AndroidSwitchableFeature(type: AndroidFeatureType<*>, android: MatteryPlayerCapability) : AndroidFeature(type, android) {
+abstract class AndroidSwitchableFeature(type: AndroidFeatureType<*>, android: MatteryPlayer) : AndroidFeature(type, android) {
 	var isActive by syncher.boolean(setter = setter@{ access, value ->
 		if (value != access.get()) {
 			access.accept(value)
@@ -52,15 +53,15 @@ abstract class AndroidSwitchableFeature(type: AndroidFeatureType<*>, android: Ma
 	// but it doesn't seem to cause issues?
 	abstract fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor = RGBAColor.WHITE)
 
-	override fun serializeNBT(): CompoundTag {
-		return super.serializeNBT().also {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
+		return super.serializeNBT(registry).also {
 			it["isActive"] = isActive
 			it["cooldown"] = cooldown
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag) {
-		super.deserializeNBT(nbt)
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag) {
+		super.deserializeNBT(registry, nbt)
 		isActive = nbt.getBoolean("isActive")
 		cooldown = nbt.getInt("cooldown")
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoostFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoostFeature.kt
index b04db591f..1a2d2809c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoostFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoostFeature.kt
@@ -3,11 +3,11 @@ package ru.dbotthepony.mc.otm.android.feature
 import net.minecraft.world.entity.ai.attributes.AttributeModifier
 import net.minecraft.world.entity.ai.attributes.Attributes
 import ru.dbotthepony.mc.otm.android.AndroidFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
 import java.util.*
 
-class AttackBoostFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.ATTACK_BOOST, android) {
+class AttackBoostFeature(android: MatteryPlayer) : AndroidFeature(AndroidFeatures.ATTACK_BOOST, android) {
 	override fun applyModifiers() {
 		val modifier = ply.getAttribute(Attributes.ATTACK_DAMAGE)
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/EnderTeleporterFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/EnderTeleporterFeature.kt
index 196470827..dd2b85ce3 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/EnderTeleporterFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/EnderTeleporterFeature.kt
@@ -20,22 +20,23 @@ import net.minecraft.world.phys.HitResult
 import net.minecraft.world.phys.shapes.BooleanOp
 import net.minecraft.world.phys.shapes.CollisionContext
 import net.minecraft.world.phys.shapes.Shapes
-import net.minecraftforge.client.event.RenderLevelStageEvent
-import net.minecraftforge.event.ForgeEventFactory
-import net.minecraftforge.event.entity.living.LivingDeathEvent
+import net.neoforged.neoforge.client.event.RenderLevelStageEvent
+import net.neoforged.neoforge.event.EventHooks
+import net.neoforged.neoforge.event.entity.living.LivingDeathEvent
 import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.android.AndroidActiveFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
-import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource
+import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.ResearchIcons
 import ru.dbotthepony.mc.otm.client.render.linesIgnoreZRenderType
 import ru.dbotthepony.mc.otm.client.render.sprites.sprite
 import ru.dbotthepony.mc.otm.config.AndroidConfig
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.genericPositions
 import ru.dbotthepony.mc.otm.core.holder
 import ru.dbotthepony.mc.otm.core.isFall
@@ -55,7 +56,7 @@ import ru.dbotthepony.mc.otm.triggers.EnderTeleporterFallDeathTrigger
 import java.util.*
 import kotlin.math.sin
 
-class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiveFeature(AndroidFeatures.ENDER_TELEPORTER, capability) {
+class EnderTeleporterFeature(capability: MatteryPlayer) : AndroidActiveFeature(AndroidFeatures.ENDER_TELEPORTER, capability) {
 	var lastTeleport = 0
 		private set
 
@@ -299,7 +300,7 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv
 		val (blockPos) = trace()
 		blockPos ?: return false
 
-		val event = ForgeEventFactory.onEnderTeleport(ply, blockPos.x + 0.5, blockPos.y.toDouble(), blockPos.z + 0.5)
+		val event = EventHooks.onEnderTeleport(ply, blockPos.x + 0.5, blockPos.y.toDouble(), blockPos.z + 0.5)
 
 		if (event.isCanceled) {
 			(ply as ServerPlayer).connection.send(ClientboundSoundEntityPacket(SoundEvents.ITEM_BREAK.holder, SoundSource.PLAYERS, ply, 0.3f, 0.5f, ply.level().random.nextLong()))
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ExtendedReachFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ExtendedReachFeature.kt
index 270e08963..219e31a1a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ExtendedReachFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ExtendedReachFeature.kt
@@ -1,31 +1,26 @@
 package ru.dbotthepony.mc.otm.android.feature
 
 import net.minecraft.world.entity.ai.attributes.AttributeModifier
-import net.minecraftforge.common.ForgeMod
+import net.minecraft.world.entity.ai.attributes.Attributes
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.android.AndroidFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
-import java.util.*
 
-class ExtendedReachFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.EXTENDED_REACH, android) {
+class ExtendedReachFeature(android: MatteryPlayer) : AndroidFeature(AndroidFeatures.EXTENDED_REACH, android) {
 	override fun applyModifiers() {
-		if (!ForgeMod.BLOCK_REACH.isPresent)
-			return
+		val reach = ply.getAttribute(Attributes.BLOCK_INTERACTION_RANGE) ?: return
 
-		val reach = ply.getAttribute(ForgeMod.BLOCK_REACH.get()) ?: return
-
-		reach.removePermanentModifier(MODIFIER_ID)
-		reach.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), level + 1.0, AttributeModifier.Operation.ADDITION))
+		reach.removeModifier(MODIFIER_ID)
+		reach.addPermanentModifier(AttributeModifier(MODIFIER_ID, level + 1.0, AttributeModifier.Operation.ADD_VALUE))
 	}
 
 	override fun removeModifiers() {
-		if (!ForgeMod.BLOCK_REACH.isPresent)
-			return
-
-		ply.getAttribute(ForgeMod.BLOCK_REACH.get())?.removePermanentModifier(MODIFIER_ID)
+		ply.getAttribute(Attributes.BLOCK_INTERACTION_RANGE)?.removeModifier(MODIFIER_ID)
 	}
 
 	companion object {
-		private val MODIFIER_ID = UUID.fromString("4a3fae46-47a8-a03f-857d-f5c2b2c8f2f2")
+		private val MODIFIER_ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "android_extended_reach")
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/FallDampenersFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/FallDampenersFeature.kt
index 4b1b75900..900234f6f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/FallDampenersFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/FallDampenersFeature.kt
@@ -1,24 +1,16 @@
 package ru.dbotthepony.mc.otm.android.feature
 
-import net.minecraft.ChatFormatting
-import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.level.ServerPlayer
-import net.minecraftforge.event.entity.living.LivingAttackEvent
-import net.minecraftforge.event.entity.living.LivingHurtEvent
-import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent
 import ru.dbotthepony.mc.otm.android.AndroidFeature
-import ru.dbotthepony.mc.otm.android.AndroidResearchManager
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.config.AndroidConfig
-import ru.dbotthepony.mc.otm.core.TextComponent
-import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.isFall
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
-import ru.dbotthepony.mc.otm.registry.MNames
 import ru.dbotthepony.mc.otm.triggers.FallDampenersSaveTrigger
 
-class FallDampenersFeature(capability: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.FALL_DAMPENERS, capability) {
-	override fun onHurt(event: LivingHurtEvent) {
+class FallDampenersFeature(capability: MatteryPlayer) : AndroidFeature(AndroidFeatures.FALL_DAMPENERS, capability) {
+	override fun onHurt(event: LivingIncomingDamageEvent) {
 		if (event.source.isFall) {
 			val reduction = (AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_P * (level + 1)).toFloat().coerceIn(0f, 1f)
 			val flat = (AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_F * (level + 1)).toFloat().coerceIn(0f, Float.MAX_VALUE)
@@ -35,15 +27,4 @@ class FallDampenersFeature(capability: MatteryPlayerCapability) : AndroidFeature
 			}
 		}
 	}
-
-	override fun onAttack(event: LivingAttackEvent) {
-		if (event.source.isFall) {
-			val reduction = (AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_P * (level + 1)).toFloat().coerceIn(0f, 1f)
-			val flat = (AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_F * (level + 1)).toFloat().coerceIn(0f, Float.MAX_VALUE)
-
-			if (reduction >= 1f || event.amount <= flat) {
-				event.isCanceled = true
-			}
-		}
-	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt
index bd0e622d1..e83d05050 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt
@@ -2,25 +2,29 @@ package ru.dbotthepony.mc.otm.android.feature
 
 import net.minecraft.client.multiplayer.ClientLevel
 import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
+import net.minecraft.server.level.ServerPlayer
 import net.minecraft.world.entity.Entity
 import net.minecraft.world.entity.item.ItemEntity
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.config.AndroidConfig
 import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.client.render.ResearchIcons
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.math.Vector
 import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid
 import ru.dbotthepony.mc.otm.core.math.minus
 import ru.dbotthepony.mc.otm.core.math.plus
 import ru.dbotthepony.mc.otm.core.position
 import ru.dbotthepony.mc.otm.core.math.times
-import ru.dbotthepony.mc.otm.network.GenericNetworkChannel
-import ru.dbotthepony.mc.otm.network.MNetworkContext
-import ru.dbotthepony.mc.otm.network.MatteryPacket
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
 import java.util.UUID
 import java.util.WeakHashMap
@@ -34,8 +38,8 @@ private data class SharedItemEntityData(val owner: UUID? = null, val age: Int =
 
 private val datatable = WeakHashMap<ItemEntity, SharedItemEntityData>()
 
-class ItemEntityDataPacket(val itemUUID: Int, val owner: UUID? = null, val age: Int = 0, val lifespan: Int = 0, val hasPickupDelay: Boolean = true) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
+class ItemEntityDataPacket(val itemUUID: Int, val owner: UUID? = null, val age: Int = 0, val lifespan: Int = 0, val hasPickupDelay: Boolean = true) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeVarInt(itemUUID)
 		buff.writeBoolean(owner != null)
 		if (owner != null) buff.writeUUID(owner)
@@ -44,20 +48,27 @@ class ItemEntityDataPacket(val itemUUID: Int, val owner: UUID? = null, val age:
 		buff.writeBoolean(hasPickupDelay)
 	}
 
-	override fun play(context: MNetworkContext) {
+	fun play(context: IPayloadContext) {
 		val level = minecraft.player?.level() as ClientLevel? ?: return
 		val entity = level.getEntity(itemUUID) as ItemEntity? ?: return
 		datatable[entity] = SharedItemEntityData(owner, age, lifespan, hasPickupDelay)
 	}
 
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
 	companion object {
+		val TYPE = CustomPacketPayload.Type<ItemEntityDataPacket>(ResourceLocation(OverdriveThatMatters.MOD_ID, "item_entity_data"))
+		val CODEC: StreamCodec<FriendlyByteBuf, ItemEntityDataPacket> = StreamCodec.ofMember(ItemEntityDataPacket::write, ::read)
+
 		fun read(buff: FriendlyByteBuf): ItemEntityDataPacket {
 			return ItemEntityDataPacket(buff.readVarInt(), if (buff.readBoolean()) buff.readUUID() else null, buff.readVarInt(), buff.readVarInt(), buff.readBoolean())
 		}
 	}
 }
 
-class ItemMagnetFeature(capability: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.ITEM_MAGNET, capability) {
+class ItemMagnetFeature(capability: MatteryPlayer) : AndroidSwitchableFeature(AndroidFeatures.ITEM_MAGNET, capability) {
 	private data class ItemPos(var position: Vector, var ticksSinceActivity: Int)
 	private val rememberPositions = WeakHashMap<ItemEntity, ItemPos>()
 
@@ -79,7 +90,7 @@ class ItemMagnetFeature(capability: MatteryPlayerCapability) : AndroidSwitchable
 			ent as ItemEntity
 
 			if (server) {
-				GenericNetworkChannel.send(ply, ItemEntityDataPacket(ent.id, ent.owner?.uuid, ent.age, ent.lifespan, ent.hasPickUpDelay()))
+				PacketDistributor.sendToPlayer(ply as ServerPlayer,  ItemEntityDataPacket(ent.id, ent.owner?.uuid, ent.age, ent.lifespan, ent.hasPickUpDelay()))
 
 				if (!serverPredicate.test(ent)) {
 					continue
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/JumpBoostFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/JumpBoostFeature.kt
index 4d0a3a502..bc8a5512a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/JumpBoostFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/JumpBoostFeature.kt
@@ -1,57 +1,26 @@
 package ru.dbotthepony.mc.otm.android.feature
 
-import net.minecraft.network.FriendlyByteBuf
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.sounds.SoundSource
+import net.neoforged.neoforge.network.PacketDistributor
 import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
-import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.ResearchIcons
 import ru.dbotthepony.mc.otm.config.AndroidConfig
 import ru.dbotthepony.mc.otm.config.ClientConfig
 import ru.dbotthepony.mc.otm.core.math.Vector
 import ru.dbotthepony.mc.otm.core.math.plus
-import ru.dbotthepony.mc.otm.network.GenericNetworkChannel
-import ru.dbotthepony.mc.otm.network.MNetworkContext
-import ru.dbotthepony.mc.otm.network.MatteryPacket
-import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
 import ru.dbotthepony.mc.otm.network.SmokeParticlesPacket
+import ru.dbotthepony.mc.otm.network.TriggerJumpBoostPacket
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
 import ru.dbotthepony.mc.otm.registry.MSoundEvents
 
-object TriggerJumpBoostPacket : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		// no op
-	}
-
-	override fun play(context: MNetworkContext) {
-		val mattery = context.sender?.matteryPlayer ?: return
-
-		if (!mattery.isAndroid)
-			return
-
-		val feature = mattery.getFeature(AndroidFeatures.JUMP_BOOST) ?: return
-
-		if (feature.isActive && feature.cooldown <= 4 && mattery.androidEnergy.extractEnergyExact(AndroidConfig.JumpBoost.ENERGY_COST, false)) {
-			feature.putOnCooldown()
-
-			context.sender.level().playSound(
-				context.sender, context.sender,
-				MSoundEvents.ANDROID_JUMP_BOOST, SoundSource.PLAYERS,
-				1f, 1f
-			)
-
-			GenericNetworkChannel.makeSmoke(context.sender, context.sender.x, context.sender.y, context.sender.z)
-		}
-	}
-}
-
-class JumpBoostFeature(capability: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.JUMP_BOOST, capability) {
+class JumpBoostFeature(capability: MatteryPlayer) : AndroidSwitchableFeature(AndroidFeatures.JUMP_BOOST, capability) {
 	private var tickCooldownClient = false
 
 	override val maxCooldown: Int
@@ -80,7 +49,7 @@ class JumpBoostFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF
 		if (isActive && cooldown <= 0 && old != lastGround && !lastGround && isJumping && isShifting && ply.xRot <= ClientConfig.JUMP_BOOST_LOOK_ANGLE && android.androidEnergy.extractEnergyExact(AndroidConfig.JumpBoost.ENERGY_COST, true)) {
 			ply.deltaMovement += Vector(0.0, AndroidConfig.JumpBoost.POWER * (level + 1) / 20.0, 0.0)
 			putOnCooldown()
-			MatteryPlayerNetworkChannel.sendToServer(TriggerJumpBoostPacket)
+			PacketDistributor.sendToServer(TriggerJumpBoostPacket)
 
 			ply.level().playSound(
 				ply, ply,
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature.kt
index 8cc4a134a..eb8169153 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature.kt
@@ -4,36 +4,38 @@ import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.entity.ai.attributes.AttributeModifier
 import net.minecraft.world.entity.ai.attributes.Attributes
 import net.minecraft.world.entity.player.Player
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.android.AndroidFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
 import java.util.*
 
-class LimbOverclockingFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.LIMB_OVERCLOCKING, android) {
+class LimbOverclockingFeature(android: MatteryPlayer) : AndroidFeature(AndroidFeatures.LIMB_OVERCLOCKING, android) {
 	override fun applyModifiers() {
 		val speed = ply.getAttribute(Attributes.MOVEMENT_SPEED)
 
 		if (speed != null) {
-			speed.removePermanentModifier(MODIFIER_ID)
-			speed.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), (level + 1) * 0.08, AttributeModifier.Operation.MULTIPLY_TOTAL))
+			speed.removeModifier(MODIFIER_ID)
+			speed.addPermanentModifier(AttributeModifier(MODIFIER_ID,(level + 1) * 0.08, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL))
 		}
 
 		val attackSpeed = ply.getAttribute(Attributes.ATTACK_SPEED)
 
 		if (attackSpeed != null) {
-			attackSpeed.removePermanentModifier(MODIFIER_ID)
-			attackSpeed.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), (level + 1) * 0.06, AttributeModifier.Operation.MULTIPLY_TOTAL))
+			attackSpeed.removeModifier(MODIFIER_ID)
+			attackSpeed.addPermanentModifier(AttributeModifier(MODIFIER_ID, (level + 1) * 0.06, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL))
 		}
 	}
 
 	override fun removeModifiers() {
-		ply.getAttribute(Attributes.MOVEMENT_SPEED)?.removePermanentModifier(MODIFIER_ID)
-		ply.getAttribute(Attributes.ATTACK_SPEED)?.removePermanentModifier(MODIFIER_ID)
+		ply.getAttribute(Attributes.MOVEMENT_SPEED)?.removeModifier(MODIFIER_ID)
+		ply.getAttribute(Attributes.ATTACK_SPEED)?.removeModifier(MODIFIER_ID)
 	}
 
 	companion object {
-		private val MODIFIER_ID = UUID.fromString("4a3fae46-e57b-4e20-857d-f5c2b2c8f2f2")
+		private val MODIFIER_ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "limb_overclocking")
 
 		@JvmStatic
 		fun getBrushCooldown(entity: LivingEntity): Int {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt
index d51c19f5f..512117f6b 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt
@@ -1,12 +1,13 @@
 package ru.dbotthepony.mc.otm.android.feature
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.server.level.ServerPlayer
-import net.minecraftforge.event.entity.living.LivingHurtEvent
+import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.android.AndroidFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
 import ru.dbotthepony.mc.otm.core.isBypassArmor
 import ru.dbotthepony.mc.otm.core.math.Decimal
@@ -17,7 +18,7 @@ import ru.dbotthepony.mc.otm.onceServer
 import ru.dbotthepony.mc.otm.triggers.NanobotsArmorTrigger
 import kotlin.math.roundToInt
 
-class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.NANOBOTS_ARMOR, android) {
+class NanobotsArmorFeature(android: MatteryPlayer) : AndroidFeature(AndroidFeatures.NANOBOTS_ARMOR, android) {
 	var strength by syncher.int(
 		setter = setter@{ access, value -> access.accept(value.coerceIn(0 .. 3)) }
 	)
@@ -42,7 +43,7 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An
 		}
 	}
 
-	override fun onHurt(event: LivingHurtEvent) {
+	override fun onHurt(event: LivingIncomingDamageEvent) {
 		ticksPassed = 0
 
 		if (!event.source.isBypassArmor && layers > 0) {
@@ -69,8 +70,8 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An
 		}
 	}
 
-	override fun serializeNBT(): CompoundTag {
-		return super.serializeNBT().also {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
+		return super.serializeNBT(registry).also {
 			it["ticksPassed"] = ticksPassed
 			it["layers"] = layers
 			it["strength"] = strength
@@ -78,8 +79,8 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag) {
-		super.deserializeNBT(nbt)
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag) {
+		super.deserializeNBT(registry, nbt)
 		ticksPassed = nbt.getInt("ticksPassed")
 		layers = nbt.getInt("layers")
 		strength = nbt.getInt("strength")
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegenerationFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegenerationFeature.kt
index b5cd2d73c..dda855383 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegenerationFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegenerationFeature.kt
@@ -1,18 +1,19 @@
 package ru.dbotthepony.mc.otm.android.feature
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.world.level.GameRules
-import net.minecraftforge.event.entity.living.LivingHurtEvent
+import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent
 import ru.dbotthepony.mc.otm.config.AndroidConfig
 import ru.dbotthepony.mc.otm.android.AndroidFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
 import ru.dbotthepony.mc.otm.registry.StatNames
 import ru.dbotthepony.mc.otm.core.nbt.set
 import kotlin.math.roundToInt
 
-class NanobotsRegenerationFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.NANOBOTS_REGENERATION, android) {
+class NanobotsRegenerationFeature(android: MatteryPlayer) : AndroidFeature(AndroidFeatures.NANOBOTS_REGENERATION, android) {
 	private var ticksPassed = 0
 	private var healTicks = 0
 
@@ -41,22 +42,22 @@ class NanobotsRegenerationFeature(android: MatteryPlayerCapability) : AndroidFea
 		}
 	}
 
-	override fun onHurt(event: LivingHurtEvent) {
+	override fun onHurt(event: LivingIncomingDamageEvent) {
 		if (event.amount > 0f) {
 			ticksPassed = 0
 			healTicks = 0
 		}
 	}
 
-	override fun serializeNBT(): CompoundTag {
-		return super.serializeNBT().also {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
+		return super.serializeNBT(registry).also {
 			it["ticksPassed"] = ticksPassed
 			it["healTicks"] = healTicks
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag) {
-		super.deserializeNBT(nbt)
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag) {
+		super.deserializeNBT(registry, nbt)
 		ticksPassed = nbt.getInt("ticksPassed")
 		healTicks = nbt.getInt("healTicks")
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt
index 18572422f..d27e73fca 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt
@@ -4,14 +4,14 @@ import net.minecraft.world.effect.MobEffectInstance
 import net.minecraft.world.effect.MobEffects
 import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.ResearchIcons
 import ru.dbotthepony.mc.otm.config.AndroidConfig
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
 
-class NightVisionFeature(android: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.NIGHT_VISION, android) {
+class NightVisionFeature(android: MatteryPlayer) : AndroidSwitchableFeature(AndroidFeatures.NIGHT_VISION, android) {
 	override val allowToSwitchByPlayerWhileSpectator: Boolean
 		get() = true
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt
index 54751846d..c8642a4a9 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt
@@ -2,13 +2,16 @@ package ru.dbotthepony.mc.otm.android.feature
 
 import it.unimi.dsi.fastutil.objects.ReferenceArraySet
 import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.sounds.SoundSource
 import net.minecraft.world.entity.Entity
 import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.entity.monster.warden.Warden
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
 import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
@@ -25,11 +28,8 @@ import ru.dbotthepony.mc.otm.core.math.plus
 import ru.dbotthepony.mc.otm.core.math.roundToIntVector
 import ru.dbotthepony.mc.otm.core.math.times
 import ru.dbotthepony.mc.otm.core.position
-import ru.dbotthepony.mc.otm.network.MNetworkContext
-import ru.dbotthepony.mc.otm.network.MatteryPacket
-import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
 import ru.dbotthepony.mc.otm.network.ShockwaveEffectPacket
-import ru.dbotthepony.mc.otm.onceServer
+import ru.dbotthepony.mc.otm.network.TriggerShockwavePacket
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
 import ru.dbotthepony.mc.otm.registry.MDamageTypes
 import ru.dbotthepony.mc.otm.registry.MSoundEvents
@@ -39,23 +39,7 @@ import ru.dbotthepony.mc.otm.triggers.ShockwaveTrigger
 import kotlin.math.pow
 import kotlin.math.roundToInt
 
-object TriggerShockwavePacket : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		// no op
-	}
-
-	override fun play(context: MNetworkContext) {
-		val shockwave = context.sender?.matteryPlayer?.getFeature(AndroidFeatures.SHOCKWAVE) ?: return
-
-		if (!shockwave.isOnCooldown && shockwave.isActive && shockwave.airTicks > 0) {
-			onceServer { // delay by one tick so player update its position as well
-				shockwave.shockwave()
-			}
-		}
-	}
-}
-
-class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.SHOCKWAVE, capability) {
+class ShockwaveFeature(capability: MatteryPlayer) : AndroidSwitchableFeature(AndroidFeatures.SHOCKWAVE, capability) {
 	override val maxCooldown: Int
 		get() = AndroidConfig.Shockwave.COOLDOWN
 
@@ -91,7 +75,7 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF
 
 		if (ply is ServerPlayer) {
 			ShockwaveTrigger.trigger(ply as ServerPlayer)
-			MatteryPlayerNetworkChannel.sendTrackingAndSelf(ply, ShockwaveEffectPacket(ply.position))
+			PacketDistributor.sendToPlayersTrackingEntityAndSelf(ply, ShockwaveEffectPacket(ply.position))
 
 			ply.level().playSound(
 				null,
@@ -204,7 +188,7 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF
 					// I HATE SELF-UPDATING PLAYERS
 					// fix "bug" where shockwave doesn't trigger even when player is falling faster than orbiting satellite
 					putOnCooldown()
-					MatteryPlayerNetworkChannel.sendToServer(TriggerShockwavePacket)
+					PacketDistributor.sendToServer(TriggerShockwavePacket)
 				} else {
 					shockwave()
 				}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt
index ecfb5580f..f97573ec5 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt
@@ -1,31 +1,29 @@
 package ru.dbotthepony.mc.otm.android.feature
 
 import net.minecraft.world.entity.ai.attributes.AttributeModifier
-import net.minecraftforge.common.ForgeMod
+import net.minecraft.world.entity.ai.attributes.Attributes
 import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.ResearchIcons
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
-import java.util.*
 
-class StepAssistFeature(android: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.STEP_ASSIST, android) {
+class StepAssistFeature(android: MatteryPlayer) : AndroidSwitchableFeature(AndroidFeatures.STEP_ASSIST, android) {
 	override fun applyModifiers() {
-		if (!ForgeMod.STEP_HEIGHT_ADDITION.isPresent || !isActive)
+		if (!isActive)
 			return
 
-		val reach = ply.getAttribute(ForgeMod.STEP_HEIGHT_ADDITION.get()) ?: return
+		val reach = ply.getAttribute(Attributes.STEP_HEIGHT) ?: return
 
 		reach.removeModifier(MODIFIER_ID)
-		reach.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), (level + 1) * 0.5, AttributeModifier.Operation.ADDITION))
+		reach.addPermanentModifier(AttributeModifier(MODIFIER_ID, (level + 1) * 0.5, AttributeModifier.Operation.ADD_VALUE))
 	}
 
 	override fun removeModifiers() {
-		if (!ForgeMod.STEP_HEIGHT_ADDITION.isPresent)
-			return
-
-		ply.getAttribute(ForgeMod.STEP_HEIGHT_ADDITION.get())?.removeModifier(MODIFIER_ID)
+		ply.getAttribute(Attributes.STEP_HEIGHT)?.removeModifier(MODIFIER_ID)
 	}
 
 	private var isShiftKeyDown = false
@@ -59,6 +57,6 @@ class StepAssistFeature(android: MatteryPlayerCapability) : AndroidSwitchableFea
 	}
 
 	companion object {
-		private val MODIFIER_ID = UUID.fromString("4a3fae46-47a8-a03f-857d-f5c2b2c8f2f4")
+		private val MODIFIER_ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "step_assist_feature")
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/SwimBoostersFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/SwimBoostersFeature.kt
index 60d1b9ce6..acd4e1df9 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/SwimBoostersFeature.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/SwimBoostersFeature.kt
@@ -1,32 +1,30 @@
 package ru.dbotthepony.mc.otm.android.feature
 
 import net.minecraft.world.entity.ai.attributes.AttributeModifier
-import net.minecraftforge.common.ForgeMod
+import net.neoforged.neoforge.common.NeoForgeMod
 import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.ResearchIcons
 import ru.dbotthepony.mc.otm.config.AndroidConfig
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
-import java.util.*
 
-class SwimBoostersFeature(android: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.SWIM_BOOSTERS, android) {
+class SwimBoostersFeature(android: MatteryPlayer) : AndroidSwitchableFeature(AndroidFeatures.SWIM_BOOSTERS, android) {
 	override fun applyModifiers() {
-		if (!ForgeMod.SWIM_SPEED.isPresent || !isActive)
+		if (!isActive)
 			return
 
-		val attr = ply.getAttribute(ForgeMod.SWIM_SPEED.get()) ?: return
+		val attr = ply.getAttribute(NeoForgeMod.SWIM_SPEED) ?: return
 
 		attr.removeModifier(MODIFIER_ID)
-		attr.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), (level + 1) * AndroidConfig.SWIM_BOOSTERS, AttributeModifier.Operation.ADDITION))
+		attr.addPermanentModifier(AttributeModifier(MODIFIER_ID, (level + 1) * AndroidConfig.SWIM_BOOSTERS, AttributeModifier.Operation.ADD_VALUE))
 	}
 
 	override fun removeModifiers() {
-		if (!ForgeMod.SWIM_SPEED.isPresent)
-			return
-
-		ply.getAttribute(ForgeMod.SWIM_SPEED.get())?.removeModifier(MODIFIER_ID)
+		ply.getAttribute(NeoForgeMod.SWIM_SPEED)?.removeModifier(MODIFIER_ID)
 	}
 
 	override fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor) {
@@ -34,6 +32,6 @@ class SwimBoostersFeature(android: MatteryPlayerCapability) : AndroidSwitchableF
 	}
 
 	companion object {
-		private val MODIFIER_ID = UUID.fromString("4a3ffa46-47a8-a03f-857d-f5c2b2c8f2f6")
+		private val MODIFIER_ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "swim_boosters_feature")
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt
index 54865f369..54af818c1 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt
@@ -17,6 +17,7 @@ import net.minecraft.world.InteractionResult
 import net.minecraft.world.MenuProvider
 import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.entity.player.Player
+import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.TooltipFlag
 import net.minecraft.world.level.BlockGetter
@@ -42,7 +43,6 @@ import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom
 import ru.dbotthepony.mc.otm.core.math.component1
 import ru.dbotthepony.mc.otm.core.math.component2
 import ru.dbotthepony.mc.otm.core.math.component3
-import ru.dbotthepony.mc.otm.core.tagNotNull
 import ru.dbotthepony.mc.otm.once
 import java.util.concurrent.Callable
 import java.util.function.Function
@@ -117,13 +117,11 @@ open class MatteryBlock(properties: Properties = DEFAULT_PROPERTIES) : Block(pro
 		return getShapeForEachState(ArrayList(stateDefinition.properties), mapper)
 	}
 
-	@Suppress("OVERRIDE_DEPRECATION")
-	override fun use(
+	override fun useWithoutItem(
 		blockState: BlockState,
 		level: Level,
 		blockPos: BlockPos,
 		ply: Player,
-		hand: InteractionHand,
 		blockHitResult: BlockHitResult
 	): InteractionResult {
 		if (this is EntityBlock && !level.isClientSide) {
@@ -138,8 +136,7 @@ open class MatteryBlock(properties: Properties = DEFAULT_PROPERTIES) : Block(pro
 		if (this is EntityBlock && level.isClientSide)
 			return InteractionResult.SUCCESS
 
-		@Suppress("DEPRECATION")
-		return super.use(blockState, level, blockPos, ply, hand, blockHitResult)
+		return super.useWithoutItem(blockState, level, blockPos, ply, blockHitResult)
 	}
 
 	override fun animateTick(blockState: BlockState, level: Level, blockPos: BlockPos, random: RandomSource) {
@@ -261,9 +258,14 @@ open class MatteryBlock(properties: Properties = DEFAULT_PROPERTIES) : Block(pro
 		}
 	}
 
-	override fun appendHoverText(itemStack: ItemStack, blockAccessor: BlockGetter?, components: MutableList<Component>, tooltipType: TooltipFlag) {
-		super.appendHoverText(itemStack, blockAccessor, components, tooltipType)
-		tooltips.assemble(itemStack, components)
+	override fun appendHoverText(
+		itemStack: ItemStack,
+		p_339606_: Item.TooltipContext,
+		components: MutableList<Component>,
+		tooltipType: TooltipFlag
+	) {
+		super.appendHoverText(itemStack, p_339606_, components, tooltipType)
+		tooltips.assemble(itemStack, p_339606_, components)
 	}
 
 	companion object {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt
index edeb9f709..367f5429f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt
@@ -14,7 +14,7 @@ import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.phys.BlockHitResult
 import net.minecraft.world.phys.shapes.CollisionContext
 import net.minecraft.world.phys.shapes.VoxelShape
-import net.minecraftforge.fluids.FluidUtil
+import net.neoforged.neoforge.fluids.FluidUtil
 import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
 import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
 import ru.dbotthepony.mc.otm.block.getShapeForEachState
@@ -54,12 +54,11 @@ class FluidTankBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), Entity
 		return shapes[state]!!
 	}
 
-	override fun getLightEmission(state: BlockState?, level: BlockGetter?, pos: BlockPos?): Int {
+	override fun getLightEmission(state: BlockState, level: BlockGetter, pos: BlockPos): Int {
 		if (pos == BlockPos.ZERO) return 15
 
 		val lightLevel = super.getLightEmission(state, level, pos)
-
-		val tile = level?.getExistingBlockEntity(pos) ?: lightLevel
+		val tile = level.getBlockEntity(pos)
 
 		if (tile is FluidTankBlockEntity) {
 			val fluid = tile.fluid.fluid
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/InfiniteWaterSourceBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/InfiniteWaterSourceBlock.kt
index d9a0b1524..97e482b91 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/InfiniteWaterSourceBlock.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/InfiniteWaterSourceBlock.kt
@@ -3,7 +3,9 @@ package ru.dbotthepony.mc.otm.block.decorative
 import net.minecraft.core.BlockPos
 import net.minecraft.world.InteractionHand
 import net.minecraft.world.InteractionResult
+import net.minecraft.world.ItemInteractionResult
 import net.minecraft.world.entity.player.Player
+import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.EntityBlock
 import net.minecraft.world.level.block.entity.BlockEntity
@@ -13,7 +15,7 @@ import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.level.material.MapColor
 import net.minecraft.world.level.material.PushReaction
 import net.minecraft.world.phys.BlockHitResult
-import net.minecraftforge.fluids.FluidUtil
+import net.neoforged.neoforge.fluids.FluidUtil
 import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
 import ru.dbotthepony.mc.otm.block.entity.decorative.InfiniteWaterSourceBlockEntity
 
@@ -22,13 +24,20 @@ class InfiniteWaterSourceBlock : RotatableMatteryBlock(Properties.of().destroyTi
 		return InfiniteWaterSourceBlockEntity(p_153215_, p_153216_)
 	}
 
-	@Suppress("OVERRIDE_DEPRECATION")
-	override fun use(blockState: BlockState, level: Level, blockPos: BlockPos, ply: Player, hand: InteractionHand, blockHitResult: BlockHitResult): InteractionResult {
-		if (FluidUtil.interactWithFluidHandler(ply, hand, level, blockPos, blockHitResult.direction)) {
-			return InteractionResult.sidedSuccess(level.isClientSide)
+	override fun useItemOn(
+		p_316304_: ItemStack,
+		p_316362_: BlockState,
+		p_316459_: Level,
+		p_316366_: BlockPos,
+		p_316132_: Player,
+		p_316595_: InteractionHand,
+		p_316140_: BlockHitResult
+	): ItemInteractionResult {
+		if (FluidUtil.interactWithFluidHandler(p_316132_, p_316595_, p_316459_, p_316366_, p_316140_.direction)) {
+			return ItemInteractionResult.sidedSuccess(p_316459_.isClientSide)
 		}
 
-		return super.use(blockState, level, blockPos, ply, hand, blockHitResult)
+		return super.useItemOn(p_316304_, p_316362_, p_316459_, p_316366_, p_316132_, p_316595_, p_316140_)
 	}
 
 	override fun <T : BlockEntity> getTicker(p_153212_: Level, p_153213_: BlockState, p_153214_: BlockEntityType<T>): BlockEntityTicker<T>? {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/LaboratoryLamp.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/LaboratoryLamp.kt
index c8d8d6afe..a2ad72b16 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/LaboratoryLamp.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/LaboratoryLamp.kt
@@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.block.decorative
 import net.minecraft.ChatFormatting
 import net.minecraft.core.BlockPos
 import net.minecraft.network.chat.Component
+import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Items
 import net.minecraft.world.item.TooltipFlag
@@ -122,11 +123,11 @@ class LaboratoryLamp(val invertRedstone: Boolean) : Block(Properties.of().mapCol
 
 	override fun appendHoverText(
 		p_49816_: ItemStack,
-		p_49817_: BlockGetter?,
+		p_339606_: Item.TooltipContext,
 		p_49818_: MutableList<Component>,
 		p_49819_: TooltipFlag
 	) {
-		super.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_)
+		super.appendHoverText(p_49816_, p_339606_, p_49818_, p_49819_)
 		p_49818_.add(TranslatableComponent("${MBlocks.LABORATORY_LAMP.descriptionId}.description").withStyle(ChatFormatting.GRAY))
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/PainterBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/PainterBlock.kt
index d471cda7e..a112f959d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/PainterBlock.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/PainterBlock.kt
@@ -3,7 +3,9 @@ package ru.dbotthepony.mc.otm.block.decorative
 import net.minecraft.core.BlockPos
 import net.minecraft.world.InteractionHand
 import net.minecraft.world.InteractionResult
+import net.minecraft.world.ItemInteractionResult
 import net.minecraft.world.entity.player.Player
+import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.EntityBlock
 import net.minecraft.world.level.block.entity.BlockEntity
@@ -11,7 +13,7 @@ import net.minecraft.world.level.block.entity.BlockEntityTicker
 import net.minecraft.world.level.block.entity.BlockEntityType
 import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.phys.BlockHitResult
-import net.minecraftforge.fluids.FluidUtil
+import net.neoforged.neoforge.fluids.FluidUtil
 import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
 import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity
 
@@ -20,13 +22,20 @@ class PainterBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBl
 		return PainterBlockEntity(p_153215_, p_153216_)
 	}
 
-	@Suppress("OVERRIDE_DEPRECATION")
-	override fun use(blockState: BlockState, level: Level, blockPos: BlockPos, ply: Player, hand: InteractionHand, blockHitResult: BlockHitResult): InteractionResult {
+	override fun useItemOn(
+		p_316304_: ItemStack,
+		blockState: BlockState,
+		level: Level,
+		blockPos: BlockPos,
+		ply: Player,
+		hand: InteractionHand,
+		blockHitResult: BlockHitResult
+	): ItemInteractionResult {
 		if (FluidUtil.interactWithFluidHandler(ply, hand, level, blockPos, blockHitResult.direction)) {
-			return InteractionResult.sidedSuccess(level.isClientSide)
+			return ItemInteractionResult.sidedSuccess(level.isClientSide)
 		}
 
-		return super.use(blockState, level, blockPos, ply, hand, blockHitResult)
+		return super.useItemOn(p_316304_, blockState, level, blockPos, ply, hand, blockHitResult)
 	}
 
 	override fun <T : BlockEntity?> getTicker(p_153212_: Level, p_153213_: BlockState, p_153214_: BlockEntityType<T>): BlockEntityTicker<T>? {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/TritaniumPressurePlate.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/TritaniumPressurePlate.kt
index 68cf09679..a9776fb54 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/TritaniumPressurePlate.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/TritaniumPressurePlate.kt
@@ -1,13 +1,14 @@
 package ru.dbotthepony.mc.otm.block.decorative
 
+import com.mojang.serialization.MapCodec
 import net.minecraft.ChatFormatting
 import net.minecraft.core.BlockPos
 import net.minecraft.network.chat.Component
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.world.item.DyeColor
+import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.TooltipFlag
-import net.minecraft.world.level.BlockGetter
 import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.BasePressurePlateBlock
 import net.minecraft.world.level.block.Block
@@ -18,7 +19,6 @@ import net.minecraft.world.level.block.state.properties.BlockSetType
 import net.minecraft.world.level.block.state.properties.BlockStateProperties
 import ru.dbotthepony.mc.otm.core.TooltipList
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
-import ru.dbotthepony.mc.otm.core.collect.iteratorOf
 import ru.dbotthepony.mc.otm.core.get
 
 class TritaniumPressurePlate(color: DyeColor?) : BasePressurePlateBlock(Properties.of().mapColor(color ?: DyeColor.LIGHT_BLUE).sound(SoundType.METAL).explosionResistance(80f).noOcclusion().destroyTime(3f).requiresCorrectToolForDrops().forceSolidOn().noCollission(), BlockSetType.IRON) {
@@ -26,12 +26,12 @@ class TritaniumPressurePlate(color: DyeColor?) : BasePressurePlateBlock(Properti
 
 	override fun appendHoverText(
 		itemStack: ItemStack,
-		level: BlockGetter?,
+		context: Item.TooltipContext,
 		lines: MutableList<Component>,
 		tooltipType: TooltipFlag
 	) {
-		super.appendHoverText(itemStack, level, lines, tooltipType)
-		tooltips.assemble(itemStack, lines)
+		super.appendHoverText(itemStack, context, lines, tooltipType)
+		tooltips.assemble(itemStack, context, lines)
 	}
 
 	init {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt
index 310f47590..586e344b5 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt
@@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.block.entity
 
 import net.minecraft.core.BlockPos
 import net.minecraft.core.Direction
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.DoubleTag
 import net.minecraft.server.level.ServerLevel
 import net.minecraft.server.level.ServerPlayer
@@ -10,9 +11,9 @@ import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.Block
 import net.minecraft.world.level.block.entity.BlockEntity
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.util.INBTSerializable
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.common.util.INBTSerializable
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.block.INeighbourChangeListener
 import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity
 import ru.dbotthepony.mc.otm.core.math.plus
@@ -98,11 +99,11 @@ class ExperienceStorage(val maxExperience: DoubleSupplier = DoubleSupplier { Dou
 		}
 	}
 
-	override fun serializeNBT(): DoubleTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): DoubleTag {
 		return DoubleTag.valueOf(experience)
 	}
 
-	override fun deserializeNBT(nbt: DoubleTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: DoubleTag?) {
 		experience = (nbt?.asDouble ?: 0.0).coerceAtLeast(0.0)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt
index 840537cf4..a0a922ce6 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt
@@ -3,10 +3,11 @@ package ru.dbotthepony.mc.otm.block.entity
 import com.mojang.datafixers.Products
 import com.mojang.serialization.Codec
 import com.mojang.serialization.codecs.RecordCodecBuilder
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.NbtOps
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.util.INBTSerializable
+import net.neoforged.neoforge.common.util.INBTSerializable
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade
 import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
@@ -17,7 +18,6 @@ import ru.dbotthepony.mc.otm.core.math.weakGreaterThan
 import ru.dbotthepony.mc.otm.core.math.weakLessThan
 import ru.dbotthepony.mc.otm.core.nbt.set
 import ru.dbotthepony.mc.otm.data.DecimalCodec
-import ru.dbotthepony.mc.otm.data.minRange
 
 private fun isReason(status: Any?, reason: Any) = status == null || status == reason
 private val LOGGER = LogManager.getLogger()
@@ -325,12 +325,12 @@ abstract class MachineJobEventLoop<JobType : IJob>(val codec: Codec<JobType>) :
 		OBSERVING
 	}
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		return CompoundTag().also { nbt ->
 			nbt["WorkTicks"] = workTicks
 
 			currentJob?.let {
-				codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get().map(
+				codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).mapOrElse(
 					{
 						nbt["Job"] = it
 					},
@@ -342,14 +342,14 @@ abstract class MachineJobEventLoop<JobType : IJob>(val codec: Codec<JobType>) :
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
 		nbt ?: return
 
 		workTicks = nbt.getDouble("WorkTicks")
 		currentJob = null
 
 		if ("Job" in nbt) {
-			codec.decode(NbtOps.INSTANCE, nbt["Job"]!!).get().map(
+			codec.decode(NbtOps.INSTANCE, nbt["Job"]!!).mapOrElse(
 				{
 					currentJob = it.first
 				},
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt
index a810816c3..9251fb91a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt
@@ -10,6 +10,7 @@ import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap
 import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
 import net.minecraft.core.BlockPos
 import net.minecraft.core.Direction
+import net.minecraft.core.HolderLookup.Provider
 import net.minecraft.core.SectionPos
 import net.minecraft.core.Vec3i
 import net.minecraft.nbt.CompoundTag
@@ -26,15 +27,15 @@ import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity
 import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.level.chunk.LevelChunk
 import net.minecraft.world.phys.Vec3
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.util.LazyOptional
-import net.minecraftforge.energy.IEnergyStorage
-import net.minecraftforge.event.TickEvent.LevelTickEvent
-import net.minecraftforge.event.entity.player.PlayerEvent
-import net.minecraftforge.event.level.ChunkWatchEvent
-import net.minecraftforge.event.level.LevelEvent
-import net.minecraftforge.event.server.ServerStoppingEvent
+import net.neoforged.neoforge.capabilities.BlockCapability
+import net.neoforged.neoforge.capabilities.BlockCapabilityCache
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.event.entity.player.PlayerEvent
+import net.neoforged.neoforge.event.level.ChunkWatchEvent
+import net.neoforged.neoforge.event.level.LevelEvent
+import net.neoforged.neoforge.event.server.ServerStoppingEvent
+import net.neoforged.neoforge.event.tick.LevelTickEvent
+import net.neoforged.neoforge.network.PacketDistributor
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.kommons.io.DelegateSyncher
 import ru.dbotthepony.kommons.util.Listenable
@@ -45,21 +46,20 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
 import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
 import ru.dbotthepony.mc.otm.core.get
-import ru.dbotthepony.mc.otm.core.immutableList
 import ru.dbotthepony.mc.otm.core.math.BlockRotation
 import ru.dbotthepony.mc.otm.core.math.RelativeSide
-import ru.dbotthepony.mc.otm.core.math.minus
 import ru.dbotthepony.mc.otm.core.math.plus
 import ru.dbotthepony.mc.otm.core.util.IntCounter
 import ru.dbotthepony.mc.otm.core.util.Savetables
 import ru.dbotthepony.mc.otm.core.util.TickList
 import ru.dbotthepony.mc.otm.core.util.countingLazy
 import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket
-import ru.dbotthepony.mc.otm.network.GenericNetworkChannel
 import ru.dbotthepony.mc.otm.once
+import ru.dbotthepony.mc.otm.onceServer
 import ru.dbotthepony.mc.otm.sometimeServer
 import java.lang.ref.WeakReference
 import java.util.*
+import java.util.function.BooleanSupplier
 import java.util.function.Consumer
 import java.util.function.Predicate
 import java.util.function.Supplier
@@ -74,7 +74,11 @@ import kotlin.reflect.KProperty
 abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(p_155228_, p_155229_, p_155230_), INeighbourChangeListener {
 	private var isSynchronizing = false
 
-	private val sidelessCaps = Reference2ObjectOpenHashMap<Capability<*>, SidelessCap<*>>()
+	private val sidelessCaps = Reference2ObjectOpenHashMap<BlockCapability<*, *>, Any>()
+	private val sidedCaps = Array(RelativeSide.entries.size) {
+		Reference2ObjectOpenHashMap<BlockCapability<*, *>, Any>()
+	}
+
 	protected val tickList = TickList()
 	protected val blockStateChangesCounter = IntCounter()
 	protected val dirtyListeners = Listenable.Impl<Unit>()
@@ -85,9 +89,6 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
 	val droppableContainers: Set<Container> = Collections.unmodifiableSet(_droppableContainers)
 	val neighbourChangeListeners: Set<INeighbourChangeListener> = Collections.unmodifiableSet(_neighbourChangeListeners)
 
-	private val _sides = EnumMap<RelativeSide, Side>(RelativeSide::class.java)
-	val sides: Map<RelativeSide, Side> = Collections.unmodifiableMap(_sides)
-
 	/**
 	 * Shared savetables, written both to level storage and to item tag
 	 */
@@ -121,10 +122,6 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
 	open fun beforeDroppingItems(oldBlockState: BlockState, level: Level, blockPos: BlockPos, newBlockState: BlockState, movedByPiston: Boolean) {}
 	open fun beforeDestroyedByPlayer(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player) {}
 
-	fun side(side: RelativeSide) = sides[side]!!
-
-	private data class SidelessCap<T : Any>(val cap: T, var optional: LazyOptional<T>)
-
 	open fun tick() {
 		tickList.tick()
 	}
@@ -132,40 +129,45 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
 	/**
 	 * exposes capability when no side is specified
 	 */
-	protected fun <T : Any> exposeSideless(capability: Capability<T>, value: T) {
+	protected fun <T : Any> exposeSideless(capability: BlockCapability<T, *>, value: T) {
 		check(!sidelessCaps.containsKey(capability)) { "Already has globally exposed $capability!" }
-		sidelessCaps[capability] = SidelessCap(value, LazyOptional.of { value })
+		sidelessCaps[capability] = value
 		setChanged()
+		level?.invalidateCapabilities(blockPos)
+	}
+
+	protected fun <T : Any> exposeSided(side: RelativeSide, capability: BlockCapability<T, *>, value: T) {
+		val map = sidedCaps[side.ordinal]
+		check(!map.containsKey(capability)) { "Already has exposed $capability on $side!" }
+		map[capability] = value
+		setChanged()
+		level?.invalidateCapabilities(blockPos)
 	}
 
 	/**
 	 * Exposes capability unconditionally, on all sides and sideless
 	 */
-	protected fun <T : Any> exposeGlobally(capability: Capability<T>, value: T, predicate: Predicate<RelativeSide> = Predicate { true }) {
+	protected fun <T : Any> exposeGlobally(capability: BlockCapability<T, *>, value: T, predicate: Predicate<RelativeSide> = Predicate { true }) {
 		exposeSideless(capability, value)
 
-		for (side in _sides.values)
-			if (predicate.test(side.side))
-				side.Cap(capability, value)
-	}
-
-	protected fun exposeEnergyGlobally(value: IMatteryEnergyStorage, predicate: Predicate<RelativeSide> = Predicate { true }) {
-		exposeGlobally(ForgeCapabilities.ENERGY, value, predicate)
-		exposeGlobally(MatteryCapability.ENERGY, value, predicate)
+		for (side in RelativeSide.entries)
+			if (predicate.test(side))
+				exposeSided(side, capability, value)
 	}
 
 	protected fun exposeEnergySideless(value: IMatteryEnergyStorage) {
-		exposeSideless(ForgeCapabilities.ENERGY, value)
-		exposeSideless(MatteryCapability.ENERGY, value)
+		exposeSideless(Capabilities.EnergyStorage.BLOCK, value)
+		exposeSideless(MatteryCapability.BLOCK_ENERGY, value)
 	}
 
-	protected fun exposeEnergy(side: RelativeSide, value: IMatteryEnergyStorage): ImmutableList<Side.Cap<*>> {
-		return immutableList {
-			val thisSide = _sides[side]!!
+	protected fun exposeEnergyGlobally(value: IMatteryEnergyStorage) {
+		exposeGlobally(Capabilities.EnergyStorage.BLOCK, value)
+		exposeGlobally(MatteryCapability.BLOCK_ENERGY, value)
+	}
 
-			accept(thisSide.Cap(ForgeCapabilities.ENERGY, value))
-			accept(thisSide.Cap(MatteryCapability.ENERGY, value))
-		}
+	protected fun exposeEnergySided(side: RelativeSide, value: IMatteryEnergyStorage) {
+		exposeSided(side, Capabilities.EnergyStorage.BLOCK, value)
+		exposeSided(side, MatteryCapability.BLOCK_ENERGY, value)
 	}
 
 	protected fun waitForServerLevel(lambda: () -> Unit) {
@@ -176,266 +178,38 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
 		}
 	}
 
-	interface SideListener<T> : Supplier<LazyOptional<T>>, Listenable<LazyOptional<T>>
-
-	inner class Side(val side: RelativeSide) {
-		init {
-			check(!_sides.containsKey(side)) { "dafuq are you trying to do" }
-			_sides[side] = this
-		}
-
-		private val caps = Reference2ObjectOpenHashMap<Capability<*>, Cap<*>>()
-		private val subscriptions = Reference2ObjectOpenHashMap<Capability<*>, SubRef<*>>()
-		private val knownLOs = WeakHashSet<LazyOptional<*>>()
-
-		private inner class SubRef<T>(value: LazyOptional<T>) : SideListener<T> {
-			var value: LazyOptional<T> = value
-				set(value) {
-					if (value !== field) {
-						field = value
-						listeners.accept(value)
-					}
-				}
-
-			private val listeners = Listenable.Impl<LazyOptional<T>>()
-
-			override fun addListener(listener: Consumer<LazyOptional<T>>): Listenable.L {
-				val l = listeners.addListener(listener)
-				if (level is ServerLevel) listener.accept(value)
-				return l
-			}
-
-			override fun get(): LazyOptional<T> {
-				return value
-			}
-
-			fun unset() {
-				value = LazyOptional.empty()
-			}
-		}
-
-		fun <T> track(capability: Capability<T>): SideListener<T> {
-			var subref = subscriptions[capability] as SideListener<T>?
-
-			if (subref == null) {
-				subref = SubRef(LazyOptional.empty<Any?>()) as SubRef<T>
-				subscriptions[capability] = subref
-				level?.once { updateTracked(capability) }
-			}
-
-			return subref
-		}
-
-		fun trackEnergy(): SideListener<IEnergyStorage> {
-			return track(ForgeCapabilities.ENERGY)
-		}
-
-		fun updateTracked() {
-			for (key in subscriptions.keys) {
-				// Concurrent Modification safety:
-				// we do not add nor remove keys from map, we only update values
-				updateTracked(key)
-			}
-		}
-
-		private fun updateTracked(capability: Capability<*>) {
-			if (isRemoved || !SERVER_IS_LIVE) return
-			val dir = blockRotation.side2Dir(side)
-			val targetPos = blockPos + dir.normal
-
-			val chunk = level
-				?.chunkSource
-				?.getChunkNow(SectionPos.blockToSectionCoord(targetPos.x), SectionPos.blockToSectionCoord(targetPos.z))
-
-			val subref = subscriptions[capability] as SubRef<Any?>
-
-			if (chunk == null) {
-				subref.unset()
-				level?.once { updateTracked(capability) }
-				return
-			}
-
-			val entity = chunk.getBlockEntity(targetPos)
-
-			if (entity == null) {
-				subref.unset()
-				return
-			}
-
-			val new = entity.getCapability(capability, dir.opposite)
-
-			if (!new.isPresent) {
-				subref.unset()
-				return
-			}
-
-			if (subref.value !== new) {
-				if (knownLOs.add(new)) {
-					val ref = WeakReference(this)
-
-					new.addListener {
-						ref.get()?.updateTracked(capability)
-					}
-				}
-
-				subref.value = new as LazyOptional<Any?>
-			}
-		}
-
-		operator fun <T : Any> get(capability: Capability<T>): Cap<T>? {
-			return caps[capability] as Cap<T>?
-		}
-
-		fun invalidate() {
-			for (cap in caps.values)
-				cap.invalidate()
-		}
-
-		fun revive() {
-			for (cap in caps.values)
-				cap.revive()
-		}
-
-		inner class Cap<T : Any>(val type: Capability<in T>, val capability: T) {
-			init {
-				check(!caps.containsKey(type)) { "Already has capability $type on side $side" }
-				caps[type] = this
-			}
-
-			var isExposed = true
-				private set
-			var isValid = true
-				private set
-			var isRemoved = false
-				private set
-
-			var optional: LazyOptional<T> by object : ReadWriteProperty<Any?, LazyOptional<T>> {
-				private var value: LazyOptional<T>? = null
-
-				override fun getValue(thisRef: Any?, property: KProperty<*>): LazyOptional<T> {
-					if (value == null) {
-						value = LazyOptional.of { capability }
-					}
-
-					return value!!
-				}
-
-				override fun setValue(thisRef: Any?, property: KProperty<*>, value: LazyOptional<T>) {
-					this.value = value
-				}
-			}
-				private set
-
-			fun remove() {
-				if (!isRemoved) {
-					isRemoved = true
-					val removed = caps.remove(type)
-					check(removed == this) { "$removed != $this" }
-					optional.invalidate()
-				}
-			}
-
-			fun close() {
-				if (!isRemoved && isExposed) {
-					isExposed = false
-					optional.invalidate()
-
-					if (SERVER_IS_LIVE)
-						level?.once { if (!this@MatteryBlockEntity.isRemoved) setChanged() }
-				}
-			}
-
-			fun expose() {
-				if (!isRemoved && !isExposed) {
-					isExposed = true
-
-					if (isValid) {
-						optional = LazyOptional.of { capability }
-
-						if (SERVER_IS_LIVE)
-							level?.once { if (!this@MatteryBlockEntity.isRemoved) setChanged() }
-					}
-				}
-			}
-
-			fun invalidate() {
-				if (!isRemoved && isValid) {
-					isValid = false
-					optional.invalidate()
-
-					if (SERVER_IS_LIVE)
-						level?.once { if (!this@MatteryBlockEntity.isRemoved) setChanged() }
-				}
-			}
-
-			fun revive() {
-				if (!isRemoved && !isValid) {
-					isValid = true
-
-					if (isExposed) {
-						optional = LazyOptional.of { capability }
-
-						if (SERVER_IS_LIVE)
-							level?.once { if (!this@MatteryBlockEntity.isRemoved) setChanged() }
-					}
-				}
-			}
-		}
-	}
-
-	val front = Side(RelativeSide.FRONT)
-	val back = Side(RelativeSide.BACK)
-	val left = Side(RelativeSide.LEFT)
-	val right = Side(RelativeSide.RIGHT)
-	val top = Side(RelativeSide.TOP)
-	val bottom = Side(RelativeSide.BOTTOM)
-
-	override fun <T : Any> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
+	fun <T : Any> getCapability(cap: BlockCapability<T, *>, side: Direction?): T? {
 		if (side != null) {
-			return _sides[blockRotation.dir2Side(side)]!![cap]?.optional ?: super.getCapability(cap, side)
+			return sidedCaps[blockRotation.dir2Side(side).ordinal][cap] as T?
 		}
 
-		return sidelessCaps[cap]?.optional?.cast() ?: super.getCapability(cap, side)
+		return sidelessCaps[cap] as T?
 	}
 
-	override fun invalidateCaps() {
-		super.invalidateCaps()
-
-		for (side in sides.values)
-			side.invalidate()
-	}
-
-	override fun reviveCaps() {
-		super.reviveCaps()
-
-		for (side in sides.values)
-			side.revive()
-	}
-
-	final override fun saveAdditional(nbt: CompoundTag) {
-		super.saveAdditional(nbt)
-		saveShared(nbt)
-		saveLevel(nbt)
+	final override fun saveAdditional(nbt: CompoundTag, registry: Provider) {
+		super.saveAdditional(nbt, registry)
+		saveShared(nbt, registry)
+		saveLevel(nbt, registry)
 	}
 
 	/**
 	 * Saved both to item dropped, and to level storage
 	 */
-	open fun saveShared(nbt: CompoundTag) {
-		savetables.serializeNBT(nbt)
+	open fun saveShared(nbt: CompoundTag, registry: Provider) {
+		savetables.serializeNBT(nbt, registry)
 	}
 
 	/**
 	 * Only saved to level storage, discarded when dropped as item
 	 */
-	open fun saveLevel(nbt: CompoundTag) {
-		savetablesLevel.serializeNBT(nbt)
+	open fun saveLevel(nbt: CompoundTag, registry: Provider) {
+		savetablesLevel.serializeNBT(nbt, registry)
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
-		savetables.deserializeNBT(nbt)
-		savetablesLevel.deserializeNBT(nbt)
+	override fun loadAdditional(nbt: CompoundTag, registry: Provider) {
+		super.loadAdditional(nbt, registry)
+		savetables.deserializeNBT(registry, nbt)
+		savetablesLevel.deserializeNBT(registry, nbt)
 	}
 
 	@Suppress("OVERRIDE_DEPRECATION")
@@ -447,21 +221,13 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
 		val new = blockRotation
 
 		if (old != new) {
-			for (side in _sides.values) {
-				side.updateTracked()
-				side.invalidate()
-			}
-		}
-
-		for (side in _sides.values) {
-			side.revive()
+			level?.invalidateCapabilities(blockPos)
+			capabilityCaches.forEach { it.rebuildCache() }
 		}
 	}
 
 	override fun neighborChanged(state: BlockState, level: Level, pos: BlockPos, neighbour: Block, neighbourPos: BlockPos, movedByPiston: Boolean) {
 		_neighbourChangeListeners.forEach { it.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) }
-		val dir = vec2Dir[vecKey(neighbourPos - blockPos)] ?: return
-		_sides[blockRotation.dir2Side(dir)]!!.updateTracked()
 	}
 
 	override fun setChanged() {
@@ -483,6 +249,51 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
 		dirtyListeners.accept(Unit)
 	}
 
+	private val capabilityCaches = ArrayList<CapabilityCache<*>>()
+
+	inner class CapabilityCache<T : Any>(val side: RelativeSide, val capability: BlockCapability<T, in Direction?>) : Supplier<T?>, Listenable<T?> {
+		private var currentVersion = 0
+		private var cache: BlockCapabilityCache<T, in Direction?>? = null
+		private val listeners = Listenable.Impl<T?>()
+
+		override fun addListener(listener: Consumer<T?>): Listenable.L {
+			return listeners.addListener(listener)
+		}
+
+		init {
+			capabilityCaches.add(this)
+		}
+
+		override fun get(): T? {
+			return cache?.capability
+		}
+
+		val isPresent: Boolean
+			get() = cache?.capability != null
+
+		val isEmpty: Boolean
+			get() = cache?.capability == null
+
+		fun rebuildCache() {
+			val level = level as? ServerLevel
+
+			if (level == null) {
+				cache = null
+				return
+			}
+
+			val creationVersion = ++currentVersion
+
+			cache = BlockCapabilityCache.create(
+				capability, level, blockPos,
+				blockRotation.side2Dir(side),
+				{ !isRemoved || creationVersion != currentVersion },
+				// IllegalStateException("Do not call getCapability on an invalid cache or from the invalidation listener!")
+				// what a shame.
+				{ onceServer { if (!isRemoved && creationVersion == currentVersion) listeners.accept(cache?.capability) } })
+		}
+	}
+
 	val syncher = DelegateSyncher()
 	private val synchers = Object2ObjectArrayMap<ServerPlayer, DelegateSyncher.Remote>()
 
@@ -494,30 +305,11 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
 
 		if (!level.isClientSide) {
 			subscribe()
-
-			if (old != null) {
-				for (side in _sides.values) {
-					side.updateTracked()
-					side.invalidate()
-				}
-
-				for (side in _sides.values) {
-					side.revive()
-				}
-			} else {
-				level.once {
-					if (!isRemoved) {
-						for (side in _sides.values) {
-							side.updateTracked()
-						}
-					}
-				}
-			}
-
 			waitForServerLevel.forEach { it.invoke() }
+			capabilityCaches.forEach { it.rebuildCache() }
+			waitForServerLevel.clear()
 		} else {
 			waitForServerLevel.clear()
-
 			BlockEntitySyncPacket.applyBacklog(this)
 		}
 	}
@@ -718,7 +510,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
 		/**
 		 * Returns stream of players watching (tracking) specified [chunk]
 		 */
-		fun watchingPlayers(chunk: LevelChunk) = watchingPlayers(chunk.pos, chunk.level)
+		fun watchingPlayers(chunk: LevelChunk) = watchingPlayers(chunk.pos, chunk.level!!)
 
 		private fun vecKey(value: Vec3i): Int {
 			if (value.x !in -1 .. 1) return -1
@@ -759,7 +551,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
 						val payload = data.write()
 
 						if (payload != null) {
-							GenericNetworkChannel.send(player, BlockEntitySyncPacket(be.blockPos, payload.array, payload.length))
+							PacketDistributor.sendToPlayer(player, BlockEntitySyncPacket(be.blockPos, payload.array, payload.length))
 						}
 					}
 				}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt
index cbb233e24..5c270b8c6 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt
@@ -1,8 +1,11 @@
 package ru.dbotthepony.mc.otm.block.entity
 
 import com.google.common.collect.ImmutableSet
+import com.google.gson.JsonParser
 import net.minecraft.world.level.block.entity.BlockEntityType
 import net.minecraft.core.BlockPos
+import net.minecraft.core.Direction
+import net.minecraft.core.HolderLookup
 import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.MenuProvider
 import net.minecraft.world.entity.player.Inventory
@@ -12,10 +15,12 @@ import net.minecraft.nbt.CompoundTag
 import net.minecraft.network.chat.Component
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.Level
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
-import net.minecraftforge.items.IItemHandler
+import net.minecraft.world.level.block.entity.BlockEntity
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.capabilities.ICapabilityProvider
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.items.IItemHandler
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler
@@ -28,7 +33,6 @@ import ru.dbotthepony.mc.otm.capability.moveEnergy
 import ru.dbotthepony.mc.otm.capability.moveFluid
 import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.core.getValue
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.immutableMap
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.math.RelativeSide
@@ -67,16 +71,16 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 
 	protected open fun redstoneStatusUpdated(newBlocked: Boolean, oldBlocked: Boolean) {}
 
-	override fun saveShared(nbt: CompoundTag) {
-		super.saveShared(nbt)
+	override fun saveShared(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.saveShared(nbt, registry)
 
 		if (customDisplayName != null)
-			nbt.putJson("Name", Component.Serializer.toJsonTree(customDisplayName!!))
+			nbt.putJson("Name", JsonParser.parseString(Component.Serializer.toJson(customDisplayName!!, registry)))
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
-		customDisplayName = nbt.getJson("Name")?.let(Component.Serializer::fromJson)
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
+		customDisplayName = nbt.getJson("Name")?.let { Component.Serializer.fromJson(it, registry) }
 	}
 
 	override fun setLevel(level: Level) {
@@ -102,7 +106,7 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 		val bottomDefault: FlowDirection = possibleModes,
 	) {
 		init {
-			exposeSideless(ForgeCapabilities.FLUID_HANDLER, capability)
+			exposeSideless(Capabilities.FluidHandler.BLOCK, capability)
 		}
 
 		val front = Piece(RelativeSide.FRONT).also { it.flow = frontDefault }
@@ -132,11 +136,18 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 
 		inner class Piece(val side: RelativeSide) : IFluidHandler, ITickable {
 			private val ticker = tickList.Ticker(this)
-			private val controller = side(side).Cap(ForgeCapabilities.FLUID_HANDLER, this)
-			private val neighbour = side(side).track(ForgeCapabilities.FLUID_HANDLER)
+			private val neighbour = CapabilityCache(side, Capabilities.FluidHandler.BLOCK)
+
+			init {
+				exposeSided(side, Capabilities.FluidHandler.BLOCK, this)
+			}
 
 			private fun updateTickerState() {
-				ticker.isEnabled = (automatePull || automatePush) && flow != FlowDirection.NONE && !redstoneControl.isBlockedByRedstone && neighbour.get().isPresent
+				ticker.isEnabled =
+					(automatePull || automatePush) &&
+					flow != FlowDirection.NONE &&
+					!redstoneControl.isBlockedByRedstone &&
+					neighbour.isPresent
 			}
 
 			init {
@@ -152,15 +163,8 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 				if (access.get() != value) {
 					access.accept(value)
 					markDirtyFast()
-
-					if (value == FlowDirection.NONE) {
-						controller.close()
-					} else {
-						controller.close()
-						controller.expose()
-					}
-
 					updateTickerState()
+					level?.invalidateCapabilities(blockPos)
 				}
 			})
 
@@ -193,14 +197,14 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 				if (flow == FlowDirection.NONE || !automatePull && !automatePush || redstoneControl.isBlockedByRedstone)
 					return
 
-				neighbour.get().ifPresentK {
-					if (flow.input && automatePull) {
-						moveFluid(source = it, destination = capability)
-					}
+				val it = neighbour.get() ?: return
 
-					if (flow.output && automatePush) {
-						moveFluid(source = capability, destination = it)
-					}
+				if (flow.input && automatePull) {
+					moveFluid(source = it, destination = capability)
+				}
+
+				if (flow.output && automatePush) {
+					moveFluid(source = capability, destination = it)
 				}
 			}
 
@@ -334,15 +338,12 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 			put(RelativeSide.BOTTOM, bottomDefault)
 		}
 
-		fun invalidate(force: Boolean = false) {
-			for (piece in pieces.values) {
-				piece.invalidate(force)
-			}
-		}
-
 		inner class Piece(val side: RelativeSide, val possibleModes: FlowDirection) : IMatteryEnergyStorage, ITickable {
-			private val capControllers = exposeEnergy(side, this@Piece)
-			private val neighbour = side(side).trackEnergy()
+			private val neighbour = CapabilityCache(side, Capabilities.EnergyStorage.BLOCK)
+
+			init {
+				exposeEnergySided(side, this)
+			}
 
 			override var batteryLevel: Decimal by energy::batteryLevel
 			override val maxBatteryLevel: Decimal by energy::maxBatteryLevel
@@ -356,7 +357,7 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 				ticker.isEnabled = (automatePull || automatePush) &&
 					energyFlow != FlowDirection.NONE &&
 					!redstoneControl.isBlockedByRedstone &&
-					neighbour.get().isPresent &&
+					neighbour.isPresent &&
 					(volatileEnergyValues || energy.batteryLevel.isPositive)
 			}
 
@@ -424,14 +425,14 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 			}
 
 			override fun tick() {
-				neighbour.get().ifPresentK {
-					if (energyFlow.input && automatePull) {
-						moveEnergy(source = it, destination = energy, simulate = false)
-					}
+				val it = neighbour.get() ?: return
 
-					if (energyFlow.output && automatePush) {
-						moveEnergy(source = energy, destination = it, simulate = false)
-					}
+				if (energyFlow.input && automatePull) {
+					moveEnergy(source = it, destination = energy, simulate = false)
+				}
+
+				if (energyFlow.output && automatePush) {
+					moveEnergy(source = energy, destination = it, simulate = false)
 				}
 			}
 
@@ -441,42 +442,10 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 				if (access.get() != value) {
 					access.accept(value)
 					markDirtyFast()
-
-					if (value == FlowDirection.NONE) {
-						for (controller in capControllers)
-							controller.close()
-					} else {
-						for (controller in capControllers) {
-							controller.close()
-							controller.expose()
-						}
-					}
-
 					updateTickerState()
+					level?.invalidateCapabilities(blockPos)
 				}
 			}).delegate
-
-			fun invalidate(force: Boolean = false) {
-				if (force) {
-					for (controller in capControllers) {
-						controller.close()
-						controller.expose()
-					}
-
-					if (energyFlow == FlowDirection.NONE) {
-						for (controller in capControllers) {
-							controller.close()
-						}
-					}
-				} else {
-					if (energyFlow != FlowDirection.NONE) {
-						for (controller in capControllers) {
-							controller.close()
-							controller.expose()
-						}
-					}
-				}
-			}
 		}
 	}
 
@@ -550,7 +519,7 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 			if (battery != null) caps.add(battery)
 
 			sideless = UnmodifiableItemHandler(CombinedItemHandler(caps))
-			exposeSideless(ForgeCapabilities.ITEM_HANDLER, sideless)
+			exposeSideless(Capabilities.ItemHandler.BLOCK, sideless)
 		}
 
 		val front = Piece(RelativeSide.FRONT).also { it.mode = frontDefault }
@@ -585,10 +554,13 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 					updateTickerState()
 				}
 
-			private val capController = side(side).Cap(ForgeCapabilities.ITEM_HANDLER, this)
-			private val neighbour = side(side).track(ForgeCapabilities.ITEM_HANDLER)
+			private val neighbour = CapabilityCache(side, Capabilities.ItemHandler.BLOCK)
 			private val ticker = tickList.Ticker(this)
 
+			init {
+				exposeSided(side, Capabilities.ItemHandler.BLOCK, this)
+			}
+
 			private var innerSlotPull = 0
 			private var outerSlotPull = 0
 
@@ -596,11 +568,12 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 			private var outerSlotPush = 0
 
 			private fun updateTickerState() {
-				ticker.isEnabled = (automatePull || automatePush) && mode != ItemHandlerMode.DISABLED && !redstoneControl.isBlockedByRedstone && currentHandler.slots != 0 && neighbour.get().isPresent
-			}
-
-			init {
-				capController.close()
+				ticker.isEnabled =
+					(automatePull || automatePush) &&
+					mode != ItemHandlerMode.DISABLED &&
+					!redstoneControl.isBlockedByRedstone &&
+					currentHandler.slots != 0 &&
+					neighbour.isPresent
 			}
 
 			var mode by syncher.enum(ItemHandlerMode.DISABLED, setter = { access, value ->
@@ -610,13 +583,6 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 					access.accept(value)
 					markDirtyFast()
 
-					if (value == ItemHandlerMode.DISABLED) {
-						capController.close()
-					} else {
-						capController.close()
-						capController.expose()
-					}
-
 					currentHandler = when (value) {
 						ItemHandlerMode.DISABLED -> EmptyItemHandler
 						ItemHandlerMode.INPUT -> input!!
@@ -624,6 +590,8 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 						ItemHandlerMode.INPUT_OUTPUT -> inputOutput!!
 						ItemHandlerMode.BATTERY -> battery!!
 					}
+
+					level?.invalidateCapabilities(blockPos)
 				}
 			}).delegate
 
@@ -674,33 +642,33 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
 				if (mode == ItemHandlerMode.DISABLED || !automatePull && !automatePush || redstoneControl.isBlockedByRedstone || currentHandler.slots == 0)
 					return
 
-				neighbour.get().ifPresentK {
-					if (it.slots == 0)
-						return
+				val it = neighbour.get() ?: return
 
-					if (automatePull) {
-						if (innerSlotPull !in 0 until currentHandler.slots)
-							innerSlotPull = 0
+				if (it.slots == 0)
+					return
 
-						if (outerSlotPull !in 0 until it.slots)
-							outerSlotPull = 0
+				if (automatePull) {
+					if (innerSlotPull !in 0 until currentHandler.slots)
+						innerSlotPull = 0
 
-						val (outerSlotPull, innerSlotPull) = moveBetweenSlots(it, outerSlotPull, currentHandler, innerSlotPull)
-						this.innerSlotPull = innerSlotPull
-						this.outerSlotPull = outerSlotPull
-					}
+					if (outerSlotPull !in 0 until it.slots)
+						outerSlotPull = 0
 
-					if (automatePush) {
-						if (innerSlotPush !in 0 until currentHandler.slots)
-							innerSlotPush = 0
+					val (outerSlotPull, innerSlotPull) = moveBetweenSlots(it, outerSlotPull, currentHandler, innerSlotPull)
+					this.innerSlotPull = innerSlotPull
+					this.outerSlotPull = outerSlotPull
+				}
 
-						if (outerSlotPush !in 0 until it.slots)
-							outerSlotPush = 0
+				if (automatePush) {
+					if (innerSlotPush !in 0 until currentHandler.slots)
+						innerSlotPush = 0
 
-						val (innerSlotPush, outerSlotPush) = moveBetweenSlots(currentHandler, innerSlotPush, it, outerSlotPush)
-						this.innerSlotPush = innerSlotPush
-						this.outerSlotPush = outerSlotPush
-					}
+					if (outerSlotPush !in 0 until it.slots)
+						outerSlotPush = 0
+
+					val (innerSlotPush, outerSlotPush) = moveBetweenSlots(currentHandler, innerSlotPush, it, outerSlotPush)
+					this.innerSlotPush = innerSlotPush
+					this.outerSlotPush = outerSlotPush
 				}
 			}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt
index 290457998..40f1bd7b9 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt
@@ -3,12 +3,11 @@ package ru.dbotthepony.mc.otm.block.entity
 import net.minecraft.core.BlockPos
 import net.minecraft.world.level.block.entity.BlockEntityType
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import ru.dbotthepony.mc.otm.capability.energy
 import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
 import ru.dbotthepony.mc.otm.capability.extractEnergy
 import ru.dbotthepony.mc.otm.container.HandlerFilter
 import ru.dbotthepony.mc.otm.container.MatteryContainer
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.math.Decimal
 
 abstract class MatteryPoweredBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(p_155228_, p_155229_, p_155230_) {
@@ -31,7 +30,7 @@ abstract class MatteryPoweredBlockEntity(p_155228_: BlockEntityType<*>, p_155229
 		if (demand.isZero) return
 
 		for (stack in batteryContainer) {
-			stack.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
+			stack.energy?.let {
 				val diff = it.extractEnergy(demand, false)
 				energy.receiveEnergy(diff, false)
 				demand -= diff
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt
index 86c7170ac..27eaeb517 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt
@@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.block.entity
 import com.google.common.collect.ImmutableList
 import com.mojang.serialization.Codec
 import net.minecraft.core.BlockPos
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.ListTag
 import net.minecraft.network.chat.Component
@@ -75,26 +76,26 @@ abstract class MatteryWorkerBlockEntity<JobType : IJob>(
 
 	protected open fun onJobTick(status: JobStatus<JobType>, id: Int) {}
 
-	override fun saveShared(nbt: CompoundTag) {
-		super.saveShared(nbt)
+	override fun saveShared(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.saveShared(nbt, registry)
 		nbt["jobs"] = ListTag().also {
 			for ((i, job) in jobEventLoops.withIndex()) {
-				it.add(job.serializeNBT().also {
+				it.add(job.serializeNBT(registry).also {
 					it["_id"] = i
 				})
 			}
 		}
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
 
 		for (v in nbt.getCompoundList("jobs")) {
 			if ("_id" in v) {
 				val id = v.getInt("_id")
 
 				if (id in jobEventLoops.indices) {
-					jobEventLoops[id].deserializeNBT(v)
+					jobEventLoops[id].deserializeNBT(registry, v)
 				}
 			}
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt
index 4a5bb03b9..615c2382a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt
@@ -1,7 +1,8 @@
 package ru.dbotthepony.mc.otm.block.entity
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
-import net.minecraftforge.common.util.INBTSerializable
+import net.neoforged.neoforge.common.util.INBTSerializable
 import ru.dbotthepony.kommons.io.DelegateSyncher
 import ru.dbotthepony.kommons.util.Listenable
 import ru.dbotthepony.kommons.util.getValue
@@ -25,14 +26,14 @@ abstract class AbstractRedstoneControl : INBTSerializable<CompoundTag?>, Listena
 		return listeners.addListener(listener)
 	}
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		return CompoundTag().also {
 			it[SETTING_KEY] = redstoneSetting.toString()
 			it[SIGNAL_KEY] = redstoneSignal
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
 		if (nbt == null) {
 			redstoneSetting = RedstoneSetting.LOW
 			redstoneSignal = 0
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt
index fed7679f2..4daef4b9c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt
@@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.block.entity.blackhole
 import it.unimi.dsi.fastutil.objects.ObjectArraySet
 import net.minecraft.client.Minecraft
 import net.minecraft.core.BlockPos
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.server.level.ServerLevel
 import net.minecraft.server.level.ServerPlayer
@@ -18,13 +19,12 @@ import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.level.levelgen.structure.BoundingBox
 import net.minecraft.world.phys.AABB
 import net.minecraft.world.phys.Vec3
-import net.minecraftforge.common.Tags
+import net.neoforged.neoforge.common.Tags
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.block.BlackHoleBlock
 import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity
 import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
-import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue.Companion.queueForLevel
 import ru.dbotthepony.mc.otm.config.ServerConfig
 import ru.dbotthepony.mc.otm.core.damageType
 import ru.dbotthepony.mc.otm.core.getExplosionResistance
@@ -101,37 +101,6 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
 		val level = level as? ServerLevel ?: return
 
 		level.setBlock(blockPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL)
-
-		if (gravitationStrength > 0.25) {
-			val x0 = blockPos.x + 0.5
-			val y0 = blockPos.y + 0.5
-			val z0 = blockPos.z + 0.5
-			val queue = queueForLevel(level)
-			var radius = 0
-
-			while (radius < Math.ceil(gravitationStrength * 4)) {
-				queue.explodeRing(
-					x0,
-					y0,
-					z0,
-					radius.toDouble(), Math.min(20.0, Math.max(1.0, (gravitationStrength * 4 - radius) * 20)).toFloat()
-				)
-
-				radius++
-			}
-		} else {
-			level.explode(
-				null,
-				MatteryDamageSource(level.registryAccess().damageType(MDamageTypes.HAWKING_RADIATION)),
-				null,
-				blockPos.x + 0.5,
-				blockPos.y + 0.5,
-				blockPos.z + 0.5,
-				gravitationStrength.toFloat() * 60,
-				false,
-				Level.ExplosionInteraction.BLOCK // TODO: 1.19.3
-			)
-		}
 	}
 
 	private fun updateGravStrength() {
@@ -162,19 +131,14 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
 		affectedBoundsAABB = AABB.of(affectedBounds)
 	}
 
-	override fun getRenderBoundingBox(): AABB {
-		return AABB(blockPos.offset(-GravitationStabilizerBlockEntity.RANGE, -GravitationStabilizerBlockEntity.RANGE, -GravitationStabilizerBlockEntity.RANGE), blockPos.offset(
-			GravitationStabilizerBlockEntity.RANGE, GravitationStabilizerBlockEntity.RANGE, GravitationStabilizerBlockEntity.RANGE))
-	}
-
-	override fun saveLevel(nbt: CompoundTag) {
-		super.saveLevel(nbt)
+	override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.saveLevel(nbt, registry)
 		nbt["mass"] = mass.serializeNBT()
 		nbt["spin_direction"] = spinDirection
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
 		mass = nbt.map("mass", Decimal::deserializeNBT) ?: BASELINE_MASS
 		spinDirection = nbt.getBoolean("spin_direction")
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/ExplosionDebuggerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/ExplosionDebuggerBlockEntity.kt
deleted file mode 100644
index cb821cc50..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/ExplosionDebuggerBlockEntity.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package ru.dbotthepony.mc.otm.block.entity.blackhole
-
-import net.minecraft.core.BlockPos
-import net.minecraft.server.level.ServerLevel
-import net.minecraft.world.level.block.Block
-import net.minecraft.world.level.block.Blocks
-import net.minecraft.world.level.block.entity.BlockEntity
-import net.minecraft.world.level.block.state.BlockState
-import net.minecraft.world.phys.Vec3
-import ru.dbotthepony.mc.otm.core.math.plus
-import ru.dbotthepony.mc.otm.core.math.times
-import ru.dbotthepony.mc.otm.registry.MBlockEntities
-
-class BlockEntityExplosionDebugger(p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(MBlockEntities.DEBUG_EXPLOSION_SMALL, p_155229_, p_155230_) {
-	private var hive: ExplosionRayHive? = null
-
-	fun tick() {
-		if (hive == null) {
-			val hive = ExplosionRayHive(level as ServerLevel)
-			this.hive = hive
-			
-			val tx = blockPos.x.toDouble() + 0.5
-			val ty = blockPos.y.toDouble() + 0.5
-			val tz = blockPos.z.toDouble() + 0.5
-			val tpos = Vec3(tx, ty, tz)
-
-			for (normal in ExplosionRayHive.evenlyDistributedPoints(1000)) {
-				hive.addRay(normal + tpos, normal, 200.0)
-			}
-		}
-
-		hive!!.step()
-	}
-}
-
-class BlockEntitySphereDebugger(p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(MBlockEntities.DEBUG_SPHERE_POINTS, p_155229_, p_155230_) {
-	private var placed = false
-
-	fun tick() {
-		if (!placed) {
-			placed = true
-
-			for (normal in ExplosionRayHive.evenlyDistributedPoints(400)) {
-				val multiplied = normal * 20.0
-				level!!.setBlock(blockPos + BlockPos(multiplied.x.toInt(), multiplied.y.toInt(), multiplied.z.toInt()), Blocks.COAL_BLOCK.defaultBlockState(), Block.UPDATE_ALL)
-			}
-		}
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/Explosions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/Explosions.kt
deleted file mode 100644
index ed6136dd2..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/Explosions.kt
+++ /dev/null
@@ -1,687 +0,0 @@
-package ru.dbotthepony.mc.otm.block.entity.blackhole
-
-import net.minecraft.core.BlockPos
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.nbt.DoubleTag
-import net.minecraft.nbt.ListTag
-import net.minecraft.nbt.Tag
-import net.minecraft.server.level.ServerLevel
-import net.minecraft.util.datafix.DataFixTypes
-import net.minecraft.world.level.BlockGetter
-import net.minecraft.world.level.Explosion
-import net.minecraft.world.level.ExplosionDamageCalculator
-import net.minecraft.world.level.Level
-import net.minecraft.world.level.block.Block
-import net.minecraft.world.level.block.Blocks
-import net.minecraft.world.level.block.state.BlockState
-import net.minecraft.world.level.material.FluidState
-import net.minecraft.world.level.saveddata.SavedData
-import net.minecraft.world.phys.Vec3
-import net.minecraftforge.event.TickEvent
-import net.minecraftforge.eventbus.api.SubscribeEvent
-import org.apache.logging.log4j.LogManager
-import ru.dbotthepony.mc.otm.block.BlockExplosionDebugger
-import ru.dbotthepony.mc.otm.core.*
-import ru.dbotthepony.mc.otm.core.math.Vector
-import ru.dbotthepony.mc.otm.core.math.component1
-import ru.dbotthepony.mc.otm.core.math.component2
-import ru.dbotthepony.mc.otm.core.math.component3
-import ru.dbotthepony.mc.otm.core.math.left
-import ru.dbotthepony.mc.otm.core.math.plus
-import ru.dbotthepony.mc.otm.core.math.rotateAroundAxis
-import ru.dbotthepony.mc.otm.core.math.up
-import ru.dbotthepony.mc.otm.core.nbt.set
-import ru.dbotthepony.mc.otm.registry.MDamageTypes
-import ru.dbotthepony.mc.otm.registry.MRegistry
-import ru.dbotthepony.mc.otm.registry.MatteryDamageSource
-import java.util.*
-import kotlin.collections.ArrayList
-import kotlin.collections.HashMap
-import kotlin.math.acos
-import kotlin.math.cos
-import kotlin.math.sin
-import kotlin.math.sqrt
-
-private fun round(v: Double): Int {
-	return (v + 0.5).toInt()
-}
-
-private val sphere = arrayOf(
-	// ядро
-	BlockPos(-1, -1, -1),
-	BlockPos(-1, -1, 0),
-	BlockPos(-1, -1, 1),
-	BlockPos(-1, 0, -1),
-	BlockPos(-1, 0, 0),
-	BlockPos(-1, 0, 1),
-	BlockPos(-1, 1, -1),
-	BlockPos(-1, 1, 0),
-	BlockPos(-1, 1, 1),
-	BlockPos(0, -1, -1),
-	BlockPos(0, -1, 0),
-	BlockPos(0, -1, 1),
-	BlockPos(0, 0, -1),
-	BlockPos(0, 0, 0),
-	BlockPos(0, 0, 1),
-	BlockPos(0, 1, -1),
-	BlockPos(0, 1, 0),
-	BlockPos(0, 1, 1),
-	BlockPos(1, -1, -1),
-	BlockPos(1, -1, 0),
-	BlockPos(1, -1, 1),
-	BlockPos(1, 0, -1),
-	BlockPos(1, 0, 0),
-	BlockPos(1, 0, 1),
-	BlockPos(1, 1, -1),
-	BlockPos(1, 1, 0),
-	BlockPos(1, 1, 1),
-
-	// столбы
-	BlockPos(-2, 0, 0),
-	BlockPos(2, 0, 0),
-	BlockPos(0, 2, 0),
-	BlockPos(0, -2, 0),
-	BlockPos(0, 0, 2),
-	BlockPos(0, 0, -2),
-)
-
-private val initialDirections = arrayOf(
-	Vector(1.0, 0.0, 0.0),
-	Vector(-1.0, 0.0, 0.0),
-	Vector(0.0, 1.0, 0.0),
-	Vector(0.0, -1.0, 0.0),
-	Vector(0.0, 0.0, 1.0),
-	Vector(0.0, 0.0, -1.0),
-
-	Vector(-1.0, -1.0, -1.0).normalize(),
-	Vector(1.0, -1.0, 1.0).normalize(),
-	Vector(1.0, -1.0, -1.0).normalize(),
-	Vector(-1.0, -1.0, 1.0).normalize(),
-	Vector(-1.0, 1.0, -1.0).normalize(),
-	Vector(1.0, 1.0, 1.0).normalize(),
-	Vector(1.0, 1.0, -1.0).normalize(),
-	Vector(-1.0, 1.0, 1.0).normalize(),
-)
-
-class ExplosionSphere(val hive: ExplosionSphereHive, var pos: Vec3, var stepVelocity: Vec3, var force: Double) {
-	val initialPos = pos
-	private var lastSplitPos = pos
-	val level: ServerLevel get() = hive.level
-	val blockPos: BlockPos get() = BlockPos(round(pos.x), round(pos.y), round(pos.z))
-
-	fun travelled(): Double {
-		return pos.distanceTo(initialPos)
-	}
-
-	private fun travelledFromLastSplit(): Double {
-		return pos.distanceTo(lastSplitPos)
-	}
-
-	fun step(): Boolean {
-		if (force <= 0.0) {
-			return false
-		}
-
-		val blockPos = blockPos
-
-		for (point in sphere) {
-			val finalPos = blockPos + point
-			val block = level.getBlockState(finalPos)
-
-			if (!block.isAir && block.block !is BlockExplosionDebugger) {
-				val explosion = Explosion(level, null, null, null, pos.x, pos.y, pos.z, force.toFloat(), false, Explosion.BlockInteraction.DESTROY_WITH_DECAY)
-				val explosionResistance = block.getExplosionResistance(level, blockPos, explosion)
-
-				if (explosionResistance > force) {
-					// поглощено
-					// TODO: вместо полного поглощения отражение
-					force = 0.0
-					return false
-				} else {
-					// взорвано
-					force -= explosionResistance
-
-					// TODO: дропы когда будет добавлена более общая версия
-					level.setBlock(blockPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL)
-				}
-			}
-		}
-
-		pos += stepVelocity
-
-		force -= 0.4
-
-		if (travelledFromLastSplit() >= TRAVEL_TO_SPLIT && force >= 10) {
-			force /= 2.0
-
-			val up = stepVelocity.up()
-			val left = stepVelocity.left()
-
-			val a = stepVelocity.rotateAroundAxis(up, Math.PI / 4)
-			val b = stepVelocity.rotateAroundAxis(up, -Math.PI / 4)
-			val c = stepVelocity.rotateAroundAxis(left, Math.PI / 4)
-			val d = stepVelocity.rotateAroundAxis(left, -Math.PI / 4)
-
-			hive.addRay(pos, a, force)
-			hive.addRay(pos, b, force)
-			hive.addRay(pos, c, force)
-			hive.addRay(pos, d, force)
-
-			lastSplitPos = pos
-		}
-
-		return force > 0.0
-	}
-
-	fun serializeNbt(): CompoundTag {
-		return CompoundTag()
-	}
-
-	companion object {
-		const val TRAVEL_TO_SPLIT = 4.0
-
-		fun deserializeNbt(hive: ExplosionSphereHive, tag: CompoundTag): ExplosionSphere {
-			return ExplosionSphere(hive, Vector.ZERO, Vector.ZERO, 0.0)
-		}
-	}
-}
-
-class ExplosionSphereHive(val level: ServerLevel) {
-	private val spheres = ArrayList<ExplosionSphere>()
-
-	var stepNumber = 0
-		private set
-
-	fun addRay(pos: Vec3, stepVelocity: Vec3, force: Double) {
-		spheres.add(ExplosionSphere(this, pos, stepVelocity, force))
-	}
-
-	fun addDefaultRays(pos: Vec3, force: Double) {
-		for (stepVelocity in initialDirections) {
-			addRay(pos, stepVelocity, force * 8)
-		}
-	}
-
-	fun step() {
-		stepNumber++
-
-		val toRemove = ArrayList<Int>()
-
-		for (i in 0 until spheres.size) {
-			if (!spheres[i].step()) {
-				toRemove.add(i)
-			}
-		}
-
-		for (i in toRemove.size - 1 downTo  0) {
-			spheres.removeAt(toRemove[i])
-		}
-	}
-
-	fun serializeNbt(): CompoundTag {
-		return CompoundTag().also {
-			it["spheres"] = ListTag().also {
-				for (ray in spheres)
-					it.add(ray.serializeNbt())
-			}
-		}
-	}
-
-	fun deserializeNbt(tag: CompoundTag) {
-		(tag["spheres"] as? ListTag)?.also {
-			for (elem in it) {
-				spheres.add(ExplosionSphere.deserializeNbt(this, elem as CompoundTag))
-			}
-		}
-	}
-
-	fun isEmpty(): Boolean {
-		return spheres.isEmpty()
-	}
-}
-
-class ExplosionRay(val hive: ExplosionRayHive, var pos: Vec3, var stepVelocity: Vec3, var force: Double) {
-	val initialPos = pos
-	private var lastSplitPos = pos
-	val level: ServerLevel get() = hive.level
-	val blockPos: BlockPos get() = BlockPos(round(pos.x), round(pos.y), round(pos.z))
-	private var prev: MutableInt? = null
-
-	fun travelled(): Double {
-		return pos.distanceTo(initialPos)
-	}
-
-	private fun travelledFromLastSplit(): Double {
-		return pos.distanceTo(lastSplitPos)
-	}
-
-	fun step(spread: Boolean): Boolean {
-		if (force <= 0.0) {
-			return false
-		}
-
-		val blockPos = blockPos
-
-		if (!level.isInWorldBounds(blockPos))
-			return false
-
-		// val chunk = level.chunkSource.getChunkNow(SectionPos.blockToSectionCoord(blockPos.x), SectionPos.blockToSectionCoord(blockPos.z)) ?: return true
-		val block = level.getBlockState(blockPos)
-
-		if (!block.isAir && block.block !is BlockExplosionDebugger) {
-			val explosion = Explosion(level, null, null, null, pos.x, pos.y, pos.z, force.toFloat(), false, Explosion.BlockInteraction.DESTROY_WITH_DECAY)
-			val explosionResistance = block.getExplosionResistance(level, blockPos, explosion)
-
-			if (explosionResistance > force) {
-				// поглощено
-				// TODO: вместо полного поглощения отражение
-				force = 0.0
-				return false
-			} else {
-				// взорвано
-				force -= explosionResistance
-
-				// TODO: дропы когда будет добавлена более общая версия
-				level.setBlock(blockPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL)
-			}
-		}
-
-		val old = this.blockPos
-		pos += stepVelocity
-		val new = this.blockPos
-
-		if (old != new) {
-			val prev = prev
-
-			if (prev != null) {
-				prev.dec()
-
-				if (prev.value <= 0) {
-					hive.checkKey(old)
-				}
-			}
-
-			this.prev = hive.incDensity(new)
-		}
-
-		force -= 0.4
-
-		if (spread && travelledFromLastSplit() >= TRAVEL_TO_SPLIT * sqrt(travelled() / 9.0) && force >= 10) {
-			force /= 2.0
-
-			val up = stepVelocity.up()
-			val left = stepVelocity.left()
-
-			val a = stepVelocity.rotateAroundAxis(up, Math.PI / 5.5)
-			val b = stepVelocity.rotateAroundAxis(up, -Math.PI / 5.5)
-			val c = stepVelocity.rotateAroundAxis(left, Math.PI / 5.5)
-			val d = stepVelocity.rotateAroundAxis(left, -Math.PI / 5.5)
-
-			hive.addRay(pos, a, force)
-			hive.addRay(pos, b, force)
-			hive.addRay(pos, c, force)
-			hive.addRay(pos, d, force)
-
-			lastSplitPos = pos
-		}
-
-		return force > 0f
-	}
-
-	fun serializeNbt(): CompoundTag {
-		return CompoundTag().also {
-			it["pos"] = ListTag().also {
-				it.add(DoubleTag.valueOf(pos.x))
-				it.add(DoubleTag.valueOf(pos.y))
-				it.add(DoubleTag.valueOf(pos.z))
-			}
-
-			it["stepVelocity"] = ListTag().also {
-				it.add(DoubleTag.valueOf(stepVelocity.x))
-				it.add(DoubleTag.valueOf(stepVelocity.y))
-				it.add(DoubleTag.valueOf(stepVelocity.z))
-			}
-
-			it["force"] = force
-		}
-	}
-
-	companion object {
-		const val TRAVEL_TO_SPLIT = 4.0
-
-		fun deserializeNbt(hive: ExplosionRayHive, tag: CompoundTag): ExplosionRay {
-			val pos = tag["pos"] as ListTag
-			val stepVelocity = tag["stepVelocity"] as ListTag
-
-			return ExplosionRay(hive,
-				Vector((pos[0] as DoubleTag).asDouble, (pos[1] as DoubleTag).asDouble, (pos[2] as DoubleTag).asDouble),
-				Vector((stepVelocity[0] as DoubleTag).asDouble, (stepVelocity[1] as DoubleTag).asDouble, (stepVelocity[2] as DoubleTag).asDouble),
-				(tag["force"] as DoubleTag).asDouble
-			)
-		}
-	}
-}
-
-data class MutableInt(var value: Int = 0) {
-	fun inc() {value++}
-	fun dec() {value--}
-	fun zero() = value <= 0
-}
-
-class ExplosionRayHive(val level: ServerLevel) {
-	private val rays = ArrayList<ExplosionRay>()
-	private val densityMap = HashMap<BlockPos, MutableInt>()
-	var stepNumber = 0
-		private set
-
-	fun incDensity(pos: BlockPos): MutableInt {
-		return densityMap.computeIfAbsent(pos) {MutableInt()}.also(MutableInt::inc)
-	}
-
-	fun decDensity(pos: BlockPos) {
-		val value = densityMap.computeIfAbsent(pos) {MutableInt()}
-		value.dec()
-
-		if (value.zero()) {
-			densityMap.remove(pos)
-		}
-	}
-
-	fun checkKey(pos: BlockPos) {
-		val value = densityMap.computeIfAbsent(pos) {MutableInt()}
-
-		if (value.zero()) {
-			densityMap.remove(pos)
-		}
-	}
-
-	fun addRay(pos: Vec3, stepVelocity: Vec3, force: Double) {
-		rays.add(ExplosionRay(this, pos, stepVelocity, force))
-	}
-
-	fun step() {
-		stepNumber++
-
-		val toRemove = ArrayList<Int>()
-		val density = calculateDensity()
-
-		for (i in 0 until rays.size) {
-			if (!rays[i].step(density < 3.0)) {
-				toRemove.add(i)
-				decDensity(rays[i].blockPos)
-			}
-		}
-
-		for (i in toRemove.size - 1 downTo  0) {
-			rays.removeAt(toRemove[i])
-		}
-
-		if (stepNumber % 4 == 0) {
-			LOGGER.info("At step {} density of hive {} with {} elements is {}", stepNumber, this, rays.size, density)
-		}
-	}
-
-	fun calculateDensity(): Double {
-		return rays.size.toDouble() / densityMap.size
-	}
-
-	fun serializeNbt(): CompoundTag {
-		return CompoundTag().also {
-			it["rays"] = ListTag().also {
-				for (ray in rays)
-					it.add(ray.serializeNbt())
-			}
-		}
-	}
-
-	fun deserializeNbt(tag: CompoundTag) {
-		(tag["rays"] as? ListTag)?.also {
-			for (elem in it) {
-				rays.add(ExplosionRay.deserializeNbt(this, elem as CompoundTag))
-			}
-		}
-	}
-
-	fun isEmpty(): Boolean {
-		return rays.isEmpty()
-	}
-
-	companion object {
-		private val LOGGER = LogManager.getLogger()
-		private val SQUARE_5 = sqrt(5.0)
-
-		fun evenlyDistributedPoints(amount: Int): List<Vec3> {
-			val list = ArrayList<Vec3>()
-
-			for (i in 0 .. amount) {
-				val idx = i.toDouble() / amount
-				val phi = acos(1.0 - 2.0 * idx)
-				val theta = Math.PI * (1.0 + SQUARE_5) * i
-
-				list.add(Vec3(cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi)))
-			}
-
-			return list
-		}
-	}
-}
-
-private object BlackHoleExplosionDamageCalculator : ExplosionDamageCalculator() {
-	override fun getBlockExplosionResistance(
-		explosion: Explosion,
-		getter: BlockGetter,
-		pos: BlockPos,
-		state: BlockState,
-		fstate: FluidState
-	): Optional<Float> {
-		return if (state.isAir && fstate.isEmpty) Optional.empty() else Optional.of(
-			Math.sqrt(
-				Math.max(
-					0f,
-					Math.max(
-						state.getExplosionResistance(getter, pos, explosion),
-						fstate.getExplosionResistance(getter, pos, explosion)
-					)
-				).toDouble()
-			).toFloat()
-		)
-	}
-}
-
-private data class RingExplosion(val x: Double, val y: Double, val z: Double, val radius: Double, val strength: Float) {
-	fun serializeNBT(): CompoundTag {
-		return CompoundTag().also {
-			it["x"] = x
-			it["y"] = y
-			it["z"] = z
-			it["radius"] = radius
-			it["strength"] = strength
-		}
-	}
-
-	fun explode(queue: ExplosionQueue) {
-		for (pos in ExplosionRayHive.evenlyDistributedPoints(radius.toInt() * 80)) {
-			val (x, y, z) = pos
-
-			queue.explode(
-				this.x + x * radius * 15,
-				this.y + y * radius * 15,
-				this.z + z * radius * 15,
-				strength
-			)
-		}
-	}
-
-	companion object {
-		@JvmStatic
-		fun deserializeNBT(tag: CompoundTag): RingExplosion {
-			return RingExplosion(
-				tag.getDouble("x"),
-				tag.getDouble("y"),
-				tag.getDouble("z"),
-				tag.getDouble("radius"),
-				tag.getFloat("strength")
-			)
-		}
-	}
-}
-
-private data class QueuedExplosion(val x: Double, val y: Double, val z: Double, val radius: Float) {
-	fun serializeNBT(): CompoundTag {
-		return CompoundTag().also {
-			it["x"] = x
-			it["y"] = y
-			it["z"] = z
-			it["radius"] = radius
-		}
-	}
-
-	fun explode(level: Level) {
-		level.explode(
-			null,
-			MatteryDamageSource(level.registryAccess().damageType(MDamageTypes.HAWKING_RADIATION)),
-			BlackHoleExplosionDamageCalculator,
-			x,
-			y,
-			z,
-			radius,
-			false,
-			Level.ExplosionInteraction.BLOCK // TODO: 1.19.3
-		)
-	}
-
-	companion object {
-		fun deserializeNBT(tag: CompoundTag): QueuedExplosion {
-			return QueuedExplosion(tag.getDouble("x"), tag.getDouble("y"), tag.getDouble("z"), tag.getFloat("radius"))
-		}
-	}
-}
-
-class ExplosionQueue(private val level: ServerLevel) : SavedData() {
-	private var indexExplosion = 0
-	private var indexRing = 0
-	private val explosions = ArrayList<QueuedExplosion>()
-	private val rings = ArrayList<RingExplosion>()
-	private val hives = ArrayList<ExplosionRayHive>()
-
-	override fun save(tag: CompoundTag): CompoundTag {
-		val listExplosions = ListTag()
-		val listRings = ListTag()
-
-		for (i in indexExplosion until explosions.size)
-			listExplosions.add(explosions[i].serializeNBT())
-
-		for (i in indexRing until rings.size)
-			listRings.add(rings[i].serializeNBT())
-
-		tag["explosions"] = listExplosions
-		tag["rings"] = listRings
-		// tag["hives"] = ListTag().also {
-		// 	for (hive in hives) {
-		// 		it.add(hive.serializeNbt())
-		// 	}
-		// }
-
-		return tag
-	}
-
-	fun load(tag: CompoundTag) {
-		explosions.clear()
-		rings.clear()
-		hives.clear()
-
-		indexExplosion = 0
-		indexRing = 0
-
-		for (explosion in tag.getList("explosions", Tag.TAG_COMPOUND.toInt()))
-			explosions.add(QueuedExplosion.deserializeNBT(explosion as CompoundTag))
-
-		for (ring in tag.getList("rings", Tag.TAG_COMPOUND.toInt()))
-			rings.add(RingExplosion.deserializeNBT(ring as CompoundTag))
-
-		for (hive in tag.getList("hives", Tag.TAG_COMPOUND.toInt()))
-			hives.add(ExplosionRayHive(level).also { it.deserializeNbt(hive as CompoundTag) })
-	}
-
-	fun explode(x: Double, y: Double, z: Double, radius: Float) {
-		if (level.isOutsideBuildHeight(BlockPos(x.toInt(), y.toInt() + 24, z.toInt())) || level.isOutsideBuildHeight(BlockPos(x.toInt(), y.toInt() - 24, z.toInt())))
-			return
-
-		explosions.add(QueuedExplosion(x, y, z, radius))
-		isDirty = true
-	}
-
-	fun explodeRing(x: Double, y: Double, z: Double, radius: Double, strength: Float) {
-		rings.add(RingExplosion(x, y, z, radius, strength))
-		isDirty = true
-	}
-
-	fun explodeRays(tpos: Vector, force: Double) {
-		val hive = ExplosionRayHive(level)
-		hives.add(hive)
-
-		for (normal in ExplosionRayHive.evenlyDistributedPoints(1000)) {
-			hive.addRay(normal + tpos, normal, force)
-		}
-
-		isDirty = true
-	}
-
-	fun tick() {
-		if (explosions.size != 0) {
-			isDirty = true
-			var iterations = 0
-
-			for (i in indexExplosion until explosions.size) {
-				explosions[i].explode(level)
-				indexExplosion++
-
-				if (iterations++ == 4) {
-					break
-				}
-			}
-
-			if (indexExplosion >= explosions.size) {
-				indexExplosion = 0
-				explosions.clear()
-			}
-		} else if (rings.size != 0) {
-			if (indexRing >= rings.size) {
-				indexRing = 0
-				rings.clear()
-			} else {
-				rings[indexRing++].explode(this)
-			}
-		}
-
-		for (i in hives.size - 1 downTo 0) {
-			isDirty = true
-			hives[i].step()
-
-			if (hives[i].isEmpty()) {
-				hives.removeAt(i)
-			}
-		}
-	}
-
-	companion object {
-		@JvmStatic
-		fun queueForLevel(level: ServerLevel): ExplosionQueue {
-			return level.dataStorage.computeIfAbsent(
-				Factory({ ExplosionQueue(level) }, {
-					val factory = ExplosionQueue(level)
-					factory.load(it)
-					factory
-				}, DataFixTypes.LEVEL),
-				"otm_blackhole_explosion_queue"
-			)
-		}
-
-		@SubscribeEvent
-		fun onWorldTick(event: TickEvent.LevelTickEvent) {
-			if (event.phase == TickEvent.Phase.START && event.level is ServerLevel) {
-				queueForLevel(event.level as ServerLevel).tick()
-			}
-		}
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt
index 8ab790400..27f03ce86 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt
@@ -6,7 +6,7 @@ import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.Block
 import net.minecraft.world.level.block.entity.BlockEntityType
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
 import ru.dbotthepony.mc.otm.block.CableBlock
 import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
@@ -43,23 +43,23 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
 		init {
 			check(side !in energySidesInternal)
 			energySidesInternal[side] = this
-			sides[side]!!.Cap(ForgeCapabilities.ENERGY, this)
+			exposeSided(side, Capabilities.EnergyStorage.BLOCK, this)
 		}
 
-		val neighbour = sides[side]!!.trackEnergy()
+		val neighbour = CapabilityCache(side, Capabilities.EnergyStorage.BLOCK)
 
 		init {
 			waitForServerLevel {
 				neighbour.addListener(Consumer {
 					if (isEnabled) {
-						if (it.isPresent) {
-							if (it.resolve().get() !is CableSide) {
+						if (neighbour.isPresent) {
+							if (neighbour.get() !is CableSide) {
 								node.graph.livelyNodes.add(node)
 							}
 						}
 
 						onceServer {
-							updateBlockState(blockRotation.side2Dir(side), it.isPresent || node.neighboursView[GraphNode.link(blockRotation.side2Dir(side))] != null)
+							updateBlockState(blockRotation.side2Dir(side), neighbour.isPresent || node.neighboursView[GraphNode.link(blockRotation.side2Dir(side))] != null)
 						}
 					}
 				})
@@ -144,8 +144,8 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
 	}
 
 	init {
-		sides.keys.forEach { CableSide(it) }
-		exposeGlobally(MatteryCapability.ENERGY_CABLE_NODE, node)
+		RelativeSide.entries.forEach { CableSide(it) }
+		exposeSideless(MatteryCapability.ENERGY_CABLE_NODE, node)
 	}
 }
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt
index c6f435c08..7e391ec77 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt
@@ -1,14 +1,10 @@
 package ru.dbotthepony.mc.otm.block.entity.cable
 
-import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
-import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
-import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
 import ru.dbotthepony.mc.otm.capability.receiveEnergy
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.math.RelativeSide
 import ru.dbotthepony.mc.otm.graph.GraphNodeList
-import java.util.PriorityQueue
+import java.util.*
 import kotlin.math.ln
 
 class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableGraph>() {
@@ -102,7 +98,7 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
 						continue
 					}
 
-					side.neighbour.get().ifPresentK {
+					side.neighbour.get()?.let {
 						if (it !is EnergyCableBlockEntity.CableSide) {
 							val limit = getPath(fromNode, node)
 							hit = true
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt
index 285a6c7ca..023eeac19 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt
@@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.block.entity.decorative
 
 import net.minecraft.advancements.CriteriaTriggers
 import net.minecraft.core.BlockPos
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.LongTag
 import net.minecraft.nbt.StringTag
@@ -23,7 +24,7 @@ import net.minecraft.world.level.storage.loot.LootParams
 import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets
 import net.minecraft.world.level.storage.loot.parameters.LootContextParams
 import net.minecraft.world.phys.Vec3
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
 import ru.dbotthepony.mc.otm.container.MatteryContainer
@@ -84,18 +85,18 @@ class CargoCrateBlockEntity(
 	}
 
 	init {
-		exposeGlobally(ForgeCapabilities.ITEM_HANDLER, handler)
+		exposeGlobally(Capabilities.ItemHandler.BLOCK, handler)
 		savetablesLevel.stateful(::container, INVENTORY_KEY)
 	}
 
-	override fun saveLevel(nbt: CompoundTag) {
-		super.saveLevel(nbt)
+	override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.saveLevel(nbt, registry)
 		lootTable?.let { nbt[LOOT_TABLE_KEY] = it.toString() }
 		lootTableSeed?.let { nbt[LOOT_TABLE_SEED_KEY] = it }
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
 		lootTable = nbt.map(LOOT_TABLE_KEY) { it: StringTag -> ResourceLocation.tryParse(it.asString) }
 		lootTableSeed = (nbt[LOOT_TABLE_SEED_KEY] as LongTag?)?.asLong
 	}
@@ -105,7 +106,7 @@ class CargoCrateBlockEntity(
 		val lootTableSeed = lootTableSeed ?: 0L
 		val server = level?.server ?: return
 
-		val loot = server.lootData.getLootTable(lootTable)
+		val loot = server.loot.getLootTable(lootTable)
 
 		if (ply is ServerPlayer) {
 			CriteriaTriggers.GENERATE_LOOT.trigger(ply, lootTable)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/DevChestBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/DevChestBlockEntity.kt
index 16b68cb47..d3fb15df3 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/DevChestBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/DevChestBlockEntity.kt
@@ -2,14 +2,13 @@ package ru.dbotthepony.mc.otm.block.entity.decorative
 
 import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
 import net.minecraft.core.BlockPos
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.items.IItemHandler
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.registries.IdMappingEvent
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.items.IItemHandler
+import net.neoforged.neoforge.registries.IdMappingEvent
 import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
-import ru.dbotthepony.mc.otm.core.getID
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
 
 class DevChestBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(MBlockEntities.DEV_CHEST, blockPos, blockState), IItemHandler {
@@ -38,7 +37,7 @@ class DevChestBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryB
 	}
 
 	init {
-		exposeGlobally(ForgeCapabilities.ITEM_HANDLER, this)
+		exposeGlobally(Capabilities.ItemHandler.BLOCK, this)
 	}
 
 	companion object {
@@ -52,8 +51,8 @@ class DevChestBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryB
 
 			val sorted = Int2ObjectAVLTreeMap<ItemStack>()
 
-			for (item in ForgeRegistries.ITEMS.values) {
-				check(sorted.put(ForgeRegistries.ITEMS.getID(item), ItemStack(item, 1).also { it.count = item.getMaxStackSize(it) }) == null)
+			for (item in BuiltInRegistries.ITEM) {
+				check(sorted.put(BuiltInRegistries.ITEM.getId(item), ItemStack(item, 1).also { it.count = item.getMaxStackSize(it) }) == null)
 			}
 
 			cache.addAll(sorted.values)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt
index 110353896..3ea66d026 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt
@@ -8,9 +8,9 @@ import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.kommons.util.ListenableDelegate
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
@@ -22,7 +22,6 @@ import ru.dbotthepony.mc.otm.container.HandlerFilter
 import ru.dbotthepony.mc.otm.container.MatteryContainer
 import ru.dbotthepony.mc.otm.container.get
 import ru.dbotthepony.mc.otm.core.isNotEmpty
-import ru.dbotthepony.mc.otm.core.orNull
 import ru.dbotthepony.mc.otm.core.util.FluidStackValueCodec
 import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
@@ -47,10 +46,10 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery
 			fillInput.handler(object : HandlerFilter {
 				override fun canInsert(slot: Int, stack: ItemStack): Boolean {
 					if (fluid.isEmpty) {
-						return stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).map { it.tanks > 0 }.orElse(false)
+						return stack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.tanks > 0 } ?: false
 					}
 
-					return stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).map { it.fill(fluid[0], IFluidHandler.FluidAction.SIMULATE) > 0 }.orElse(false)
+					return stack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.fill(fluid[0], IFluidHandler.FluidAction.SIMULATE) > 0 } ?: false
 				}
 
 				override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
@@ -80,7 +79,7 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery
 		val item = drainInput[0]
 
 		if (item.isNotEmpty) {
-			val cap = (if (item.count == 1) item else item.copyWithCount(1)).getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull()
+			val cap = (if (item.count == 1) item else item.copyWithCount(1)).getCapability(Capabilities.FluidHandler.ITEM)
 
 			if (cap == null) {
 				if (output.consumeItem(item, simulate = false)) {
@@ -106,7 +105,7 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery
 
 					if (moved0.isNotEmpty) {
 						if (output.consumeItem(cap.container, simulate = true)) {
-							val cap1 = item.copyWithCount(1).getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: throw ConcurrentModificationException()
+							val cap1 = item.copyWithCount(1).getCapability(Capabilities.FluidHandler.ITEM) ?: throw ConcurrentModificationException()
 
 							val moved1 = moveFluid(source = cap1, destination = fluid)
 
@@ -132,7 +131,7 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery
 		val item = fillInput[0]
 
 		if (item.isNotEmpty) {
-			val cap = (if (item.count == 1) item else item.copyWithCount(1)).getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull()
+			val cap = (if (item.count == 1) item else item.copyWithCount(1)).getCapability(Capabilities.FluidHandler.ITEM)
 
 			if (cap == null) {
 				if (output.consumeItem(item, simulate = false)) {
@@ -158,7 +157,7 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery
 
 					if (moved0.isNotEmpty) {
 						if (output.consumeItem(cap.container, simulate = true)) {
-							val cap1 = item.copyWithCount(1).getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: throw ConcurrentModificationException()
+							val cap1 = item.copyWithCount(1).getCapability(Capabilities.FluidHandler.ITEM) ?: throw ConcurrentModificationException()
 
 							val moved1 = moveFluid(source = fluid, destination = cap1)
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt
index 420932af9..34a356892 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt
@@ -1,6 +1,7 @@
 package ru.dbotthepony.mc.otm.block.entity.decorative
 
 import net.minecraft.core.BlockPos
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.network.chat.Component
 import net.minecraft.server.level.ServerLevel
@@ -86,8 +87,8 @@ class HoloSignBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryB
 		}
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
 
 		if (!isLocked)
 			signText = truncate(signText)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/InfiniteWaterSourceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/InfiniteWaterSourceBlockEntity.kt
index 57892f006..e6d2969fc 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/InfiniteWaterSourceBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/InfiniteWaterSourceBlockEntity.kt
@@ -3,11 +3,11 @@ package ru.dbotthepony.mc.otm.block.entity.decorative
 import net.minecraft.core.BlockPos
 import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.level.material.Fluids
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
-import ru.dbotthepony.mc.otm.core.ifPresentK
+import ru.dbotthepony.mc.otm.core.math.RelativeSide
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
 import java.util.function.Consumer
 
@@ -45,18 +45,16 @@ class InfiniteWaterSourceBlockEntity(blockPos: BlockPos, blockState: BlockState)
 	}
 
 	init {
-		exposeGlobally(ForgeCapabilities.FLUID_HANDLER, this)
+		exposeGlobally(Capabilities.FluidHandler.BLOCK, this)
 
-		for (side in sides.values) {
-			val tracker = side.track(ForgeCapabilities.FLUID_HANDLER)
+		for (side in RelativeSide.entries) {
+			val tracker = CapabilityCache(side, Capabilities.FluidHandler.BLOCK)
 
 			val ticker = tickList.Ticker {
-				tracker.get().ifPresentK {
-					it.fill(FluidStack(Fluids.WATER, Int.MAX_VALUE), IFluidHandler.FluidAction.EXECUTE)
-				}
+				tracker.get()?.fill(FluidStack(Fluids.WATER, Int.MAX_VALUE), IFluidHandler.FluidAction.EXECUTE)
 			}
 
-			tracker.addListener(Consumer { ticker.isEnabled = it.isPresent })
+			tracker.addListener(Consumer { ticker.isEnabled = tracker.isPresent })
 		}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt
index 5b594f8de..1c7542d3d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt
@@ -1,12 +1,11 @@
 package ru.dbotthepony.mc.otm.block.entity.decorative
 
 import com.google.common.collect.ImmutableList
-import com.mojang.datafixers.util.Either
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap
 import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
 import it.unimi.dsi.fastutil.objects.Object2IntMap
-import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
 import net.minecraft.core.BlockPos
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
@@ -15,13 +14,12 @@ import net.minecraft.world.item.DyeColor
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.level.material.Fluids
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
 import ru.dbotthepony.mc.otm.container.HandlerFilter
 import ru.dbotthepony.mc.otm.container.MatteryContainer
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.immutableList
 import ru.dbotthepony.mc.otm.core.immutableMap
 import ru.dbotthepony.mc.otm.core.isNotEmpty
@@ -85,7 +83,7 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
 		addDroppableContainer(dyeInput)
 		savetables.stateful(dyeInput, INVENTORY_KEY)
 		savetables.bool(::isBulk)
-		exposeGlobally(ForgeCapabilities.FLUID_HANDLER, this)
+		exposeGlobally(Capabilities.FluidHandler.BLOCK, this)
 	}
 
 	fun takeDyes(dyes: Map<out DyeColor?, Int>) {
@@ -113,7 +111,7 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
 	val config = ConfigurableItemHandler(input = dyeInput.handler(object : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
 			if (waterStored() < MAX_WATER_STORAGE) {
-				stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
+				stack.getCapability(Capabilities.FluidHandler.ITEM)?.let {
 					val drain = it.drain(FluidStack(Fluids.WATER, MAX_WATER_STORAGE - waterStored()), IFluidHandler.FluidAction.SIMULATE)
 
 					if (drain.isNotEmpty) {
@@ -127,7 +125,7 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
 		}
 
 		override fun modifyInsertCount(slot: Int, stack: ItemStack, existing: ItemStack, simulate: Boolean): Int {
-			if (!stack.equals(existing, false))
+			if (!ItemStack.isSameItemSameComponents(stack, existing))
 				return super.modifyInsertCount(slot, stack, existing, simulate)
 
 			val dye = DyeColor.entries.firstOrNull { stack.`is`(it.tag) } ?: return 0
@@ -147,8 +145,8 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
 		return dyeStored.getInt(dye)
 	}
 
-	override fun saveShared(nbt: CompoundTag) {
-		super.saveShared(nbt)
+	override fun saveShared(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.saveShared(nbt, registry)
 
 		nbt["dyes"] = CompoundTag().also {
 			for ((k, v) in dyeStored) {
@@ -157,8 +155,8 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
 		}
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
 
 		dyeStored.clear()
 
@@ -177,7 +175,7 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
 
 		for (slot in dyeInput.slotIterator()) {
 			if (waterStored() < MAX_WATER_STORAGE) {
-				slot.item.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
+				slot.item.getCapability(Capabilities.FluidHandler.ITEM)?.let {
 					val drain = it.drain(FluidStack(Fluids.WATER, MAX_WATER_STORAGE - waterStored()), IFluidHandler.FluidAction.EXECUTE)
 
 					if (drain.isNotEmpty) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt
index 4f1e6a8fd..04843effc 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt
@@ -81,13 +81,13 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) :
 
 	val bottlingHandler = bottling.handler(object : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return isBottling && stack.getCapability(MatteryCapability.MATTER).map { it.matterFlow.input && it.missingMatter.isPositive }.orElse(false)
+			return isBottling && stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { it.matterFlow.input && it.missingMatter.isPositive } ?: false
 		}
 	})
 
 	val unbottlingHandler = unbottling.handler(object : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return !isBottling && stack.getCapability(MatteryCapability.MATTER).map { it.matterFlow.output && it.storedMatter.isPositive }.orElse(false)
+			return !isBottling && stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { it.matterFlow.output && it.storedMatter.isPositive } ?: false
 		}
 	})
 
@@ -109,7 +109,7 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) :
 	val matterNode = SimpleMatterNode(matter = matter)
 
 	init {
-		exposeGlobally(MatteryCapability.MATTER, matter)
+		exposeGlobally(MatteryCapability.MATTER_BLOCK, matter)
 		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
 
 		savetables.bool(::isBottling)
@@ -145,7 +145,7 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) :
 		var state = blockState
 
 		for (i in 0 .. 2) {
-			val desired = !container.getItem(i).isEmpty && container.getItem(i).getCapability(MatteryCapability.MATTER).isPresent
+			val desired = !container.getItem(i).isEmpty && container.getItem(i).getCapability(MatteryCapability.MATTER_ITEM) != null
 
 			if (state.getValue(MatterBottlerBlock.SLOT_PROPERTIES[i]) != desired) {
 				state = state.setValue(MatterBottlerBlock.SLOT_PROPERTIES[i], desired)
@@ -190,7 +190,7 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) :
 
 			for (slot in bottling.slotIterator()) {
 				val item = slot.item
-				item.getCapability(MatteryCapability.MATTER).ifPresentK {
+				item.getCapability(MatteryCapability.MATTER_ITEM)?.let {
 					if (!it.missingMatter.isPositive) {
 						unbottling.consumeItem(item, false)
 						slot.setChanged()
@@ -256,7 +256,7 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) :
 			for (slot in unbottling.slotIterator()) {
 				val item = slot.item
 
-				item.getCapability(MatteryCapability.MATTER).ifPresentK {
+				item.getCapability(MatteryCapability.MATTER_ITEM)?.let {
 					if (!it.storedMatter.isPositive) {
 						bottling.consumeItem(item, false)
 						slot.setChanged()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt
index a8e6df930..34598cccf 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt
@@ -19,7 +19,6 @@ import ru.dbotthepony.mc.otm.container.HandlerFilter
 import ru.dbotthepony.mc.otm.container.MatteryContainer
 import ru.dbotthepony.mc.otm.core.immutableList
 import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode
 import ru.dbotthepony.mc.otm.menu.matter.MatterCapacitorBankMenu
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
@@ -39,7 +38,7 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState)
 
 			for (stack in container)
 				if (!stack.isEmpty)
-					stack.getCapability(MatteryCapability.MATTER).ifPresentK {
+					stack.getCapability(MatteryCapability.MATTER_ITEM)?.let {
 						summ += it.storedMatter
 					}
 
@@ -55,7 +54,7 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState)
 
 		for (stack in container)
 			if (!stack.isEmpty)
-				stack.getCapability(MatteryCapability.MATTER).ifPresentK {
+				stack.getCapability(MatteryCapability.MATTER_ITEM)?.let {
 					summ += it.maxStoredMatter
 				}
 
@@ -72,7 +71,7 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState)
 
 		for (stack in container) {
 			if (!stack.isEmpty) {
-				stack.getCapability(MatteryCapability.MATTER).ifPresent {
+				stack.getCapability(MatteryCapability.MATTER_ITEM)?.let {
 					val diff = it.receiveMatterChecked(howMuch, simulate)
 					summ += diff
 					howMuch -= diff
@@ -101,7 +100,7 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState)
 
 		for (stack in container) {
 			if (!stack.isEmpty) {
-				stack.getCapability(MatteryCapability.MATTER).ifPresent {
+				stack.getCapability(MatteryCapability.MATTER_ITEM)?.let {
 					val diff = it.extractMatterChecked(howMuch, simulate)
 					summ += diff
 					howMuch -= diff
@@ -123,10 +122,10 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState)
 	override val matterFlow: FlowDirection
 		get() = FlowDirection.BI_DIRECTIONAL
 
-	val container = object : MatteryContainer(this::markDirtyFast, BatteryBankBlockEntity.CAPACITY) {
+	val container = object : MatteryContainer(::markDirtyFast, BatteryBankBlockEntity.CAPACITY) {
 		override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
 			super.setChanged(slot, new, old)
-			capacitorStatus[slot].value = new.getCapability(MatteryCapability.MATTER).isPresent
+			capacitorStatus[slot].value = new.getCapability(MatteryCapability.MATTER_ITEM) != null
 			gaugeLevel = storedMatter.percentage(maxStoredMatter)
 		}
 
@@ -135,11 +134,11 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState)
 
 	val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(object : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(MatteryCapability.MATTER).isPresent
+			return stack.getCapability(MatteryCapability.MATTER_ITEM) != null
 		}
 
 		override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
-			stack.getCapability(MatteryCapability.MATTER).ifPresentK {
+			stack.getCapability(MatteryCapability.MATTER_ITEM)?.let {
 				if (it.storedMatter.isPositive) {
 					return false
 				}
@@ -155,7 +154,7 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState)
 
 	init {
 		savetables.stateful(::container, INVENTORY_KEY)
-		exposeGlobally(MatteryCapability.MATTER, this)
+		exposeGlobally(MatteryCapability.MATTER_BLOCK, this)
 		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt
index d454e94b3..f3f5fc62f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt
@@ -67,7 +67,7 @@ class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState)
 	val matterNode = SimpleMatterNode(matter = matter)
 
 	init {
-		exposeGlobally(MatteryCapability.MATTER, matter)
+		exposeGlobally(MatteryCapability.MATTER_BLOCK, matter)
 		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
 		savetables.stateful(::matter, MATTER_STORAGE_KEY)
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt
index 40c2212b5..6860f13b8 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt
@@ -7,9 +7,10 @@ import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.crafting.CraftingInput
 import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage
 import ru.dbotthepony.mc.otm.block.entity.ItemJob
 import ru.dbotthepony.mc.otm.block.entity.JobContainer
@@ -26,7 +27,6 @@ import ru.dbotthepony.mc.otm.config.MachinesConfig
 import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer
 import ru.dbotthepony.mc.otm.container.HandlerFilter
 import ru.dbotthepony.mc.otm.container.MatteryContainer
-import ru.dbotthepony.mc.otm.container.ShadowCraftingContainer
 import ru.dbotthepony.mc.otm.container.UpgradeContainer
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.data.DecimalCodec
@@ -75,12 +75,13 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
 	val itemConfig = ConfigurableItemHandler(
 		input = inputs.handler(object : HandlerFilter {
 			override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-				val shadow = ShadowCraftingContainer.shadow(inputs, slot, stack)
+				val list = inputs.toList()
+				list[slot] = stack
+				val shadow = CraftingInput.of(3, 3, list)
 
 				return (level ?: return false)
 					.recipeManager
 					.byType(MRecipes.MATTER_ENTANGLER)
-					.values
 					.any { it.value.preemptivelyMatches(shadow, level!!) }
 			}
 
@@ -92,7 +93,7 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
 	)
 
 	init {
-		exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience)
+		exposeGlobally(Capabilities.FluidHandler.BLOCK, experience)
 
 		savetables.stateful(::energy, ENERGY_KEY)
 		savetables.stateful(::matter, MATTER_STORAGE_KEY)
@@ -102,7 +103,7 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
 		savetables.stateful(::experience)
 
 		exposeGlobally(MatteryCapability.MATTER_NODE, node)
-		exposeGlobally(MatteryCapability.MATTER, matter)
+		exposeGlobally(MatteryCapability.MATTER_BLOCK, matter)
 	}
 
 	override fun setLevel(level: Level) {
@@ -144,16 +145,17 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
 		if (!energy.batteryLevel.isPositive)
 			return JobContainer.noEnergy()
 
+		val inputs = CraftingInput.of(3, 3, inputs.toList())
+
 		val recipe = (level ?: return JobContainer.failure())
 			.recipeManager
 			.byType(MRecipes.MATTER_ENTANGLER)
-			.values
 			.firstOrNull { it.value.matches(inputs, level!!) } ?: return JobContainer.noItem()
 
 		val result = recipe.value.assemble(inputs, level!!.registryAccess())
 
-		inputs.forEach { it.shrink(1) }
-		inputs.setChanged()
+		this.inputs.forEach { it.shrink(1) }
+		this.inputs.setChanged()
 
 		return JobContainer.success(
 			Job(
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt
index ae73612fd..e9a165d62 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt
@@ -3,27 +3,28 @@ package ru.dbotthepony.mc.otm.block.entity.matter
 import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
 import net.minecraft.core.BlockPos
-import net.minecraft.world.level.block.state.BlockState
-import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu
+import net.minecraft.core.HolderLookup
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.ListTag
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
-import ru.dbotthepony.mc.otm.capability.MatteryCapability
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.nbt.ListTag
 import net.minecraft.world.level.Level
-import net.minecraftforge.common.util.INBTSerializable
+import net.minecraft.world.level.block.state.BlockState
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
-import ru.dbotthepony.mc.otm.capability.matter.*
+import ru.dbotthepony.mc.otm.capability.MatteryCapability
+import ru.dbotthepony.mc.otm.capability.matter.IReplicationTaskProvider
+import ru.dbotthepony.mc.otm.capability.matter.PatternState
+import ru.dbotthepony.mc.otm.capability.matter.ReplicationTask
+import ru.dbotthepony.mc.otm.capability.matter.ReplicationTaskAllocation
 import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
-import ru.dbotthepony.mc.otm.core.nbt.getBoolean
 import ru.dbotthepony.mc.otm.core.nbt.getCompoundList
 import ru.dbotthepony.mc.otm.core.nbt.map
-import ru.dbotthepony.mc.otm.core.nbt.mapString
 import ru.dbotthepony.mc.otm.core.nbt.set
 import ru.dbotthepony.mc.otm.core.util.ItemSorter
 import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode
 import ru.dbotthepony.mc.otm.menu.IItemSortingSettings
+import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
 import java.util.*
 import java.util.stream.Stream
@@ -81,7 +82,7 @@ class MatterPanelBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 
 	init {
 		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
-		exposeGlobally(MatteryCapability.TASK, this)
+		exposeGlobally(MatteryCapability.REPLICATION_TASK, this)
 
 		savetables.bool(::isProvidingTasks)
 	}
@@ -149,8 +150,8 @@ class MatterPanelBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 		return true
 	}
 
-	override fun saveShared(nbt: CompoundTag) {
-		super.saveShared(nbt)
+	override fun saveShared(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.saveShared(nbt, registry)
 
 		val list = ListTag()
 
@@ -163,14 +164,14 @@ class MatterPanelBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 		val settings = CompoundTag()
 
 		for ((uuid, value) in playerSettings) {
-			settings[uuid.toString()] = value.serializeNBT()
+			settings[uuid.toString()] = value.serializeNBT(registry)
 		}
 
 		nbt["settings"] = settings
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
 		_tasks.clear()
 		val list = nbt.getCompoundList("tasks")
 
@@ -186,8 +187,9 @@ class MatterPanelBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 
 		nbt.map("settings") { it: CompoundTag ->
 			for (k in it.allKeys) {
-				playerSettings.computeIfAbsent(UUID.fromString(k), Object2ObjectFunction { PlayerSettings() })
-					.deserializeNBT(it.getCompound(k))
+				playerSettings
+					.computeIfAbsent(UUID.fromString(k), Object2ObjectFunction { PlayerSettings() })
+					.deserializeNBT(registry, it.getCompound(k))
 			}
 		}
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt
index 2245711ed..9006e64d9 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt
@@ -1,6 +1,7 @@
 package ru.dbotthepony.mc.otm.block.entity.matter
 
 import net.minecraft.core.BlockPos
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.nbt.StringTag
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.world.entity.player.Inventory
@@ -10,7 +11,6 @@ import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.registries.ForgeRegistries
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
@@ -78,7 +78,7 @@ class MatterReconstructorBlockEntity(blockPos: BlockPos, blockState: BlockState)
 	}
 
 	init {
-		exposeGlobally(MatteryCapability.MATTER, matter)
+		exposeGlobally(MatteryCapability.MATTER_BLOCK, matter)
 		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
 
 		savetables.stateful(::repairContainer)
@@ -92,7 +92,7 @@ class MatterReconstructorBlockEntity(blockPos: BlockPos, blockState: BlockState)
 
 		savetables.Stateless(::lastItem, type = StringTag::class.java)
 			.withSerializer { it?.registryName?.toString()?.let(StringTag::valueOf) }
-			.withDeserializer { ResourceLocation.tryParse(it.asString)?.let { ForgeRegistries.ITEMS.getValue(it) } }
+			.withDeserializer { ResourceLocation.tryParse(it.asString)?.let { BuiltInRegistries.ITEM.get(it) } }
 	}
 
 	val energyConfig = ConfigurableEnergy(energy)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt
index 1602236fb..e1a4d9e3d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt
@@ -66,7 +66,7 @@ class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState)
 	val energyConfig = ConfigurableEnergy(energy)
 
 	init {
-		exposeGlobally(MatteryCapability.MATTER, matter)
+		exposeGlobally(MatteryCapability.MATTER_BLOCK, matter)
 		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
 
 		savetables.stateful(::energy, ENERGY_KEY)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt
index bcc8d307e..5130d082a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt
@@ -30,7 +30,6 @@ import ru.dbotthepony.mc.otm.container.UpgradeContainer
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.util.item
 import ru.dbotthepony.mc.otm.data.DecimalCodec
-import ru.dbotthepony.mc.otm.data.UUIDCodec
 import ru.dbotthepony.mc.otm.data.minRange
 import ru.dbotthepony.mc.otm.graph.matter.MatterNode
 import ru.dbotthepony.mc.otm.matter.MatterManager
@@ -96,7 +95,7 @@ class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 	}
 
 	init {
-		exposeGlobally(MatteryCapability.MATTER, matter)
+		exposeGlobally(MatteryCapability.MATTER_BLOCK, matter)
 		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
 
 		savetables.stateful(::energy, ENERGY_KEY)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt
index baeadaa50..8af075a13 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt
@@ -72,11 +72,6 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 		savetables.stateful(::upgrades)
 	}
 
-	override fun invalidateCaps() {
-		super.invalidateCaps()
-		matterNode.isValid = false
-	}
-
 	override fun setRemoved() {
 		super.setRemoved()
 		matterNode.isValid = false
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt
index 6048a42d5..2ee1873c1 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt
@@ -19,7 +19,6 @@ import ru.dbotthepony.mc.otm.core.collect.filterNotNull
 import ru.dbotthepony.mc.otm.core.collect.map
 import ru.dbotthepony.mc.otm.core.filterNotNull
 import ru.dbotthepony.mc.otm.core.isNotEmpty
-import ru.dbotthepony.mc.otm.core.orNull
 import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
 import java.util.stream.Stream
@@ -29,17 +28,17 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 
 	val matterNode = SimpleMatterNode(patterns = this)
 
-	val container: MatteryContainer = object : MatteryContainer(this::setChanged, 8) {
+	val container: MatteryContainer = object : MatteryContainer(::markDirtyFast, 8) {
 		override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
-			if (!ItemStack.isSameItemSameTags(new, old)) {
+			if (!ItemStack.isSameItemSameComponents(new, old)) {
 				if (!old.isEmpty) {
-					old.getCapability(MatteryCapability.PATTERN).ifPresent { cap: IPatternStorage ->
+					old.getCapability(MatteryCapability.PATTERN_ITEM)?.let { cap: IPatternStorage ->
 						cap.patterns.forEach { matterNode.graph.onPatternRemoved(it) }
 					}
 				}
 
 				if (!new.isEmpty) {
-					new.getCapability(MatteryCapability.PATTERN).ifPresent { cap: IPatternStorage ->
+					new.getCapability(MatteryCapability.PATTERN_ITEM)?.let { cap: IPatternStorage ->
 						cap.patterns.forEach { matterNode.graph.onPatternAdded(it) }
 					}
 				}
@@ -61,7 +60,7 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 		for (i in 0..7) {
 			state = state.setValue(
 				PatternStorageBlock.PATTERN_STORAGE_DISKS_PROPS[i],
-				this.container.getItem(i).getCapability(MatteryCapability.PATTERN).isPresent
+				this.container.getItem(i).getCapability(MatteryCapability.PATTERN_ITEM) != null
 			)
 		}
 
@@ -77,13 +76,8 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 		matterNode.discover(this)
 	}
 
-	override fun invalidateCaps() {
-		super.invalidateCaps()
-		matterNode.isValid = false
-	}
-
 	init {
-		exposeGlobally(MatteryCapability.PATTERN, this)
+		exposeGlobally(MatteryCapability.PATTERN_BLOCK, this)
 		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
 
 		savetables.stateful(::container, INVENTORY_KEY)
@@ -96,7 +90,7 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 	override val patterns: Stream<PatternState> get() {
 		return container.stream()
 			.filter { it.isNotEmpty }
-			.map { it.getCapability(MatteryCapability.PATTERN).orNull() }
+			.map { it.getCapability(MatteryCapability.PATTERN_ITEM) }
 			.filterNotNull()
 			.flatMap { it.patterns }
 	}
@@ -104,7 +98,7 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 	override val patternCapacity: Int get() {
 		var stored = 0L
 
-		for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN).orNull() }.filterNotNull())
+		for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN_ITEM) }.filterNotNull())
 			stored += pattern.patternCapacity.toLong()
 
 		return if (stored > Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt()
@@ -113,7 +107,7 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 	override val storedPatterns: Int get() {
 		var stored = 0L
 
-		for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN).orNull() }.filterNotNull())
+		for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN_ITEM) }.filterNotNull())
 			stored += pattern.storedPatterns.toLong()
 
 		return if (stored > Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt()
@@ -125,7 +119,7 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 	}
 
 	override fun insertPattern(pattern: PatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus {
-		for (spattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN).orNull() }.filterNotNull()) {
+		for (spattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN_ITEM) }.filterNotNull()) {
 			val status = spattern.insertPattern(pattern, onlyUpdate, simulate)
 
 			if (!status.isFailed) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt
index 43d369993..dbd81872b 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt
@@ -15,7 +15,6 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
 import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
 import ru.dbotthepony.mc.otm.config.MachinesConfig
 import ru.dbotthepony.mc.otm.container.MatteryContainer
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.menu.storage.DriveRackMenu
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
 import ru.dbotthepony.mc.otm.storage.optics.priority
@@ -46,15 +45,15 @@ class DriveRackBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery
 			markDirtyFast()
 		}
 
-	val container: MatteryContainer = object : MatteryContainer(this::setChanged, 4) {
+	val container: MatteryContainer = object : MatteryContainer(::markDirtyFast, 4) {
 		override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
 			super.setChanged(slot, new, old)
 
-			old.getCapability(MatteryCapability.DRIVE).ifPresentK {
+			old.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let {
 				cell.removeStorageComponent(it.priority(::insertPriority, ::extractPriority).powered(energy).flow(::mode))
 			}
 
-			new.getCapability(MatteryCapability.DRIVE).ifPresentK {
+			new.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let {
 				cell.addStorageComponent(it.priority(::insertPriority, ::extractPriority).powered(energy).flow(::mode))
 			}
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt
index d6dee99b5..3d6bf153f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt
@@ -29,7 +29,7 @@ class DriveViewerBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 	override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyUpdated, MachinesConfig.DRIVE_VIEWER))
 	val energyConfig = ConfigurableEnergy(energy)
 
-	val container: MatteryContainer = object : MatteryContainer(this::markDirtyFast, 1) {
+	val container: MatteryContainer = object : MatteryContainer(::markDirtyFast, 1) {
 		override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
 			super.setChanged(slot, new, old)
 
@@ -37,7 +37,7 @@ class DriveViewerBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 
 			if (level is ServerLevel) {
 				tickList.once {
-					val isPresent = new.getCapability(MatteryCapability.DRIVE).isPresent
+					val isPresent = new.getCapability(MatteryCapability.CONDENSATION_DRIVE) != null
 					var state = this@DriveViewerBlockEntity.blockState.setValue(DriveViewerBlock.DRIVE_PRESENT, isPresent)
 
 					if (!isPresent) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt
index 970a12ba2..19b7836b2 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt
@@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.block.entity.storage
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
 import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
 import net.minecraft.core.BlockPos
+import net.minecraft.core.HolderLookup
 import net.minecraft.core.NonNullList
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.network.chat.Component
@@ -11,15 +12,13 @@ import net.minecraft.world.Container
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
-import net.minecraft.world.inventory.TransientCraftingContainer
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.crafting.CraftingRecipe
 import net.minecraft.world.item.crafting.RecipeType
 import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.ForgeHooks
-import net.minecraftforge.common.util.INBTSerializable
-import ru.dbotthepony.mc.otm.core.TranslatableComponent
+import net.neoforged.neoforge.common.CommonHooks
+import net.neoforged.neoforge.common.util.INBTSerializable
 import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
@@ -27,28 +26,35 @@ import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
 import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
+import ru.dbotthepony.mc.otm.client.render.Widgets8
 import ru.dbotthepony.mc.otm.config.MachinesConfig
-import ru.dbotthepony.mc.otm.container.MatteryContainer
-import ru.dbotthepony.mc.otm.graph.storage.StorageNode
-import ru.dbotthepony.mc.otm.graph.storage.StorageGraph
-import ru.dbotthepony.mc.otm.registry.MBlockEntities
-import ru.dbotthepony.mc.otm.container.set
+import ru.dbotthepony.mc.otm.container.CombinedContainer
+import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer
+import ru.dbotthepony.mc.otm.container.util.slotIterator
+import ru.dbotthepony.mc.otm.core.TranslatableComponent
+import ru.dbotthepony.mc.otm.core.collect.map
+import ru.dbotthepony.mc.otm.core.collect.toList
+import ru.dbotthepony.mc.otm.core.isNotEmpty
 import ru.dbotthepony.mc.otm.core.nbt.mapString
 import ru.dbotthepony.mc.otm.core.nbt.set
+import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
+import ru.dbotthepony.mc.otm.graph.storage.StorageGraph
+import ru.dbotthepony.mc.otm.graph.storage.StorageNode
 import ru.dbotthepony.mc.otm.menu.storage.ItemMonitorMenu
-import ru.dbotthepony.mc.otm.storage.*
+import ru.dbotthepony.mc.otm.registry.MBlockEntities
+import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer
+import ru.dbotthepony.mc.otm.storage.IStorageProvider
+import ru.dbotthepony.mc.otm.storage.ItemStorageStack
+import ru.dbotthepony.mc.otm.storage.StorageStack
 import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent
 import java.math.BigInteger
 import java.util.*
 import kotlin.collections.HashMap
-import ru.dbotthepony.mc.otm.client.render.Widgets8
-import ru.dbotthepony.mc.otm.container.CombinedContainer
-import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer
-import ru.dbotthepony.mc.otm.container.util.slotIterator
-import ru.dbotthepony.mc.otm.core.collect.map
-import ru.dbotthepony.mc.otm.core.collect.toList
-import ru.dbotthepony.mc.otm.core.isNotEmpty
-import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
+import kotlin.collections.component1
+import kotlin.collections.component2
+import kotlin.collections.indices
+import kotlin.collections.iterator
+import kotlin.collections.set
 
 interface IItemMonitorPlayerSettings {
 	var ingredientPriority: ItemMonitorPlayerSettings.IngredientPriority
@@ -64,7 +70,7 @@ private fun takeOne(inventory: Container?, item: ItemStack): Boolean {
 	for (slot in iterator) {
 		val stack = slot.item
 
-		if (stack.equals(item, false)) {
+		if (ItemStack.isSameItemSameComponents(stack, item)) {
 			stack.shrink(1)
 			slot.setChanged()
 			return true
@@ -154,7 +160,7 @@ class ItemMonitorPlayerSettings : INBTSerializable<CompoundTag>, IItemMonitorPla
 	override var sorting: ItemStorageStackSorter = ItemStorageStackSorter.DEFAULT
 	override var ascendingSort: Boolean = false
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		return CompoundTag().also {
 			it["ingredientPriority"] = ingredientPriority.name
 			it["resultTarget"] = resultTarget.name
@@ -164,7 +170,7 @@ class ItemMonitorPlayerSettings : INBTSerializable<CompoundTag>, IItemMonitorPla
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag) {
 		ingredientPriority = nbt.mapString("ingredientPriority", IngredientPriority::valueOf, IngredientPriority.SYSTEM)
 		resultTarget = nbt.mapString("resultTarget", ResultTarget::valueOf, ResultTarget.MIXED)
 		craftingAmount = nbt.mapString("quickCraftAmount", Amount::valueOf, Amount.STACK)
@@ -227,10 +233,10 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 		val server = level.server ?: return false
 
 		var craftingRecipe = craftingRecipe
-		if (craftingRecipe != null && craftingRecipe.matches(craftingGrid, level)) return true
+		if (craftingRecipe != null && craftingRecipe.matches(craftingGrid.asCraftInput(), level)) return true
 		if (justCheckForRecipeChange) return false
 
-		craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGrid, level).orElse(null)?.value
+		craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGrid.asCraftInput(), level).orElse(null)?.value
 		Arrays.fill(craftingGridTuples, null)
 		val poweredView = poweredView
 
@@ -303,13 +309,14 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 			val craftingPlayer = craftingPlayer ?: return ItemStack.EMPTY
 
 			try {
-				ForgeHooks.setCraftingPlayer(craftingPlayer)
+				CommonHooks.setCraftingPlayer(craftingPlayer)
 
 				if (craftingRecipe.getResultItem(level.registryAccess()).count != amount) {
 					return ItemStack.EMPTY
 				}
 			} finally {
-				ForgeHooks.setCraftingPlayer(null)
+				@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
+				CommonHooks.setCraftingPlayer(null)
 			}
 
 			craftingAmount[craftingPlayer] = craftingAmount.getInt(craftingPlayer) + 1
@@ -325,15 +332,16 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 			inProcessOfCraft = true
 
 			try {
-				ForgeHooks.setCraftingPlayer(craftingPlayer)
+				CommonHooks.setCraftingPlayer(craftingPlayer)
 				val residue: NonNullList<ItemStack>
 				val result: ItemStack
 
 				try {
-					residue = craftingRecipe.getRemainingItems(craftingGrid)
+					residue = craftingRecipe.getRemainingItems(craftingGrid.asCraftInput())
 					result = craftingRecipe.getResultItem(level.registryAccess())
 				} finally {
-					ForgeHooks.setCraftingPlayer(null)
+					@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
+					CommonHooks.setCraftingPlayer(null)
 				}
 
 				check(residue.size == craftingGrid.containerSize) { "Container and residue list sizes mismatch: ${residue.size} != ${craftingGrid.containerSize}" }
@@ -408,29 +416,29 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
 
 	val craftingResultContainer = CraftingResultContainer()
 
-	override fun saveLevel(nbt: CompoundTag) {
-		super.saveLevel(nbt)
+	override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.saveLevel(nbt, registry)
 
 		nbt.put("player_settings", CompoundTag().also {
 			for ((key, value) in this.settings) {
-				it[key.toString()] = value.serializeNBT()
+				it[key.toString()] = value.serializeNBT(registry)
 			}
 		})
 
-		nbt["crafting_grid"] = craftingGrid.serializeNBT()
+		nbt["crafting_grid"] = craftingGrid.serializeNBT(registry)
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
 		this.settings.clear()
 
 		val settings = nbt.getCompound("player_settings")
 
 		for (key in settings.allKeys) {
-			check(this.settings.put(UUID.fromString(key), ItemMonitorPlayerSettings().also { it.deserializeNBT(settings.getCompound(key)) }) == null)
+			check(this.settings.put(UUID.fromString(key), ItemMonitorPlayerSettings().also { it.deserializeNBT(registry, settings.getCompound(key)) }) == null)
 		}
 
-		craftingGrid.deserializeNBT(nbt["crafting_grid"])
+		craftingGrid.deserializeNBT(registry, nbt["crafting_grid"])
 	}
 
 	fun getSettings(ply: ServerPlayer): ItemMonitorPlayerSettings {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt
index 03a2ea81e..cc1c9610b 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt
@@ -12,8 +12,8 @@ import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.Block
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.items.IItemHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.items.IItemHandler
 import ru.dbotthepony.mc.otm.*
 import ru.dbotthepony.mc.otm.block.CableBlock
 import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
@@ -102,10 +102,10 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
 		savetables.int(::extractPriority)
 		exposeGlobally(MatteryCapability.STORAGE_NODE, cell) { it != RelativeSide.FRONT }
 
-		side(RelativeSide.FRONT).track(ForgeCapabilities.ITEM_HANDLER).addListener(Consumer {
+		CapabilityCache(RelativeSide.FRONT, Capabilities.ItemHandler.BLOCK).addListener(Consumer {
 			component?.let(cell::removeStorageComponent)
-			component = if (it.isPresent) {
-				ItemHandlerComponent(it.orThrow()).also { if (!redstoneControl.isBlockedByRedstone) cell.addStorageComponent(it) }
+			component = if (it != null) {
+				ItemHandlerComponent(it).also { if (!redstoneControl.isBlockedByRedstone) cell.addStorageComponent(it) }
 			} else {
 				null
 			}
@@ -353,7 +353,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
 			} else if (current != null && last == null) {
 				addTracked(slot, current)
 			} else if (current != null && last != null) {
-				if (!ItemStack.isSameItemSameTags(current, last)) {
+				if (!ItemStack.isSameItemSameComponents(current, last)) {
 					removeTracked(slot)
 					addTracked(slot, current)
 				} else if (current.count != last.count) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt
index e03e29597..e5c2c9c3d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt
@@ -11,8 +11,8 @@ import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.Block
 import net.minecraft.world.level.block.entity.BlockEntityType
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.items.IItemHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.items.IItemHandler
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
 import ru.dbotthepony.mc.otm.block.CableBlock
@@ -27,7 +27,6 @@ import ru.dbotthepony.mc.otm.container.ItemFilter
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.isNotEmpty
 import ru.dbotthepony.mc.otm.core.math.RelativeSide
-import ru.dbotthepony.mc.otm.core.orNull
 import ru.dbotthepony.mc.otm.graph.storage.StorageNode
 import ru.dbotthepony.mc.otm.menu.storage.StorageImporterExporterMenu
 import ru.dbotthepony.mc.otm.once
@@ -39,7 +38,6 @@ import ru.dbotthepony.mc.otm.storage.ItemStorageStack
 import ru.dbotthepony.mc.otm.storage.StorageStack
 import java.math.BigInteger
 import java.util.*
-import kotlin.collections.ArrayList
 
 abstract class AbstractStorageImportExport(
 	blockType: BlockEntityType<*>,
@@ -97,7 +95,7 @@ abstract class AbstractStorageImportExport(
 		cell.discover(this)
 	}
 
-	protected val target = front.track(ForgeCapabilities.ITEM_HANDLER)
+	protected val target = CapabilityCache(RelativeSide.FRONT, Capabilities.ItemHandler.BLOCK)
 
 	abstract val filter: ItemFilter
 
@@ -122,7 +120,7 @@ class StorageImporterBlockEntity(
 	private var nextTick = INTERVAL
 
 	init {
-		front.Cap(ForgeCapabilities.ITEM_HANDLER, this)
+		exposeSided(RelativeSide.FRONT, Capabilities.ItemHandler.BLOCK, this)
 	}
 
 	override fun getSlots(): Int {
@@ -190,7 +188,7 @@ class StorageImporterBlockEntity(
 
 		nextTick--
 
-		val target = target.get().orNull()
+		val target = target.get()
 
 		if (nextTick <= 0 && target != null) {
 			if (lastSlot >= target.slots) {
@@ -280,7 +278,7 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) :
 
 		nextTick--
 
-		val target = target.get().orNull()
+		val target = target.get()
 
 		if (nextTick <= 0 && target != null) {
 			val items = cell.graph.getVirtualComponent(StorageStack.ITEMS)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt
index b2b69e2b1..94d8fd791 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt
@@ -1,5 +1,6 @@
 package ru.dbotthepony.mc.otm.block.entity.tech
 
+import it.unimi.dsi.fastutil.ints.IntList
 import net.minecraft.core.BlockPos
 import net.minecraft.server.level.ServerLevel
 import net.minecraft.world.entity.player.Inventory
@@ -9,19 +10,19 @@ import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.crafting.AbstractCookingRecipe
 import net.minecraft.world.item.crafting.BlastingRecipe
 import net.minecraft.world.item.crafting.RecipeType
+import net.minecraft.world.item.crafting.SingleRecipeInput
 import net.minecraft.world.item.crafting.SmeltingRecipe
 import net.minecraft.world.item.crafting.SmokingRecipe
 import net.minecraft.world.level.block.entity.BlockEntityType
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage
+import ru.dbotthepony.mc.otm.block.entity.ItemJob
 import ru.dbotthepony.mc.otm.block.entity.JobContainer
 import ru.dbotthepony.mc.otm.block.entity.JobStatus
-import ru.dbotthepony.mc.otm.block.entity.ItemJob
 import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
-import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler
 import ru.dbotthepony.mc.otm.capability.UpgradeType
 import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
 import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
@@ -34,7 +35,6 @@ import ru.dbotthepony.mc.otm.container.UpgradeContainer
 import ru.dbotthepony.mc.otm.container.balance
 import ru.dbotthepony.mc.otm.core.collect.filter
 import ru.dbotthepony.mc.otm.core.collect.maybe
-import ru.dbotthepony.mc.otm.core.getValue
 import ru.dbotthepony.mc.otm.core.immutableList
 import ru.dbotthepony.mc.otm.core.util.item
 import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu
@@ -55,12 +55,12 @@ sealed class AbstractPoweredFurnaceBlockEntity<P : AbstractCookingRecipe, S : Ma
 	final override val upgrades = UpgradeContainer(this::markDirtyFast, 2, UpgradeType.BASIC_PROCESSING)
 	final override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(config)))
 
-	val inputs = immutableList(maxJobs) { MatteryContainer(this::itemContainerUpdated, 1) }
-	val outputs = immutableList(maxJobs) { MatteryContainer(this::itemContainerUpdated, 1) }
+	val inputs = MatteryContainer(this::itemContainerUpdated, maxJobs)
+	val outputs = MatteryContainer(this::itemContainerUpdated, maxJobs)
 
 	init {
-		inputs.forEach { addDroppableContainer(it) }
-		outputs.forEach { addDroppableContainer(it) }
+		addDroppableContainer(inputs)
+		addDroppableContainer(outputs)
 	}
 
 	inner class SyncSlot {
@@ -77,13 +77,13 @@ sealed class AbstractPoweredFurnaceBlockEntity<P : AbstractCookingRecipe, S : Ma
 	val experience = ExperienceStorage(config::maxExperienceStored).also(::addNeighbourListener)
 	val energyConfig = ConfigurableEnergy(energy)
 	val itemConfig = ConfigurableItemHandler(
-		input = CombinedItemHandler(inputs.map { it.handler(HandlerFilter.OnlyIn) }),
-		output = CombinedItemHandler(outputs.map { it.handler(HandlerFilter.OnlyOut) }),
+		input = inputs.handler(HandlerFilter.OnlyIn),
+		output = outputs.handler(HandlerFilter.OnlyOut),
 		battery = batteryItemHandler
 	)
 
 	init {
-		exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience)
+		exposeGlobally(Capabilities.FluidHandler.BLOCK, experience)
 
 		savetables.stateful(::upgrades)
 		savetables.stateful(::energy)
@@ -105,7 +105,7 @@ sealed class AbstractPoweredFurnaceBlockEntity<P : AbstractCookingRecipe, S : Ma
 	}
 
 	override fun onJobFinish(status: JobStatus<ItemJob>, id: Int) {
-		if (outputs[id].fullyAddItem(status.job.itemStack)) {
+		if (outputs.fullyAddItem(status.job.itemStack, slots = IntList.of(id))) {
 			experience.storeExperience(status.experience, this)
 			syncSlots[id].inputItem = ItemStack.EMPTY
 			syncSlots[id].outputItem = ItemStack.EMPTY
@@ -132,16 +132,22 @@ sealed class AbstractPoweredFurnaceBlockEntity<P : AbstractCookingRecipe, S : Ma
 		if (secondaryRecipeType != null) {
 			val recipe = level.recipeManager
 				.byType(secondaryRecipeType)
-				.values
 				.iterator()
-				.filter { it.value.matches(inputs[id], 0) }
+				.filter { it.value.matches(SingleRecipeInput(inputs[id]), level) }
 				.maybe()?.value
 
 			if (recipe != null) {
-				val toProcess = inputs[id][0].count.coerceAtMost(1 + upgrades.processingItems)
+				val toProcess = inputs[id].count.coerceAtMost(1 + upgrades.processingItems)
 
-				inputs[id][0].shrink(toProcess)
-				inputs[id].setChanged(id)
+				val output = recipe.getResultItem(level.registryAccess()).copyWithCount(toProcess)
+				val inputItem = inputs[id].copyWithCount(1)
+
+				inputs[id].shrink(toProcess)
+				inputs.setChanged(id)
+
+				syncSlots[id].inputItem = inputItem
+				syncSlots[id].outputItem = output.copy()
+				syncSlots[id].progress = 0f
 
 				return JobContainer.success(
 					ItemJob(
@@ -152,11 +158,11 @@ sealed class AbstractPoweredFurnaceBlockEntity<P : AbstractCookingRecipe, S : Ma
 			}
 		}
 
-		return level.recipeManager.getRecipeFor(recipeType, inputs[id], level).map {
-			val output = it.value.assemble(inputs[id], level.registryAccess())
-			val inputItem = inputs[id][0].copyWithCount(1)
-			val toProcess = inputs[id][0].count.coerceAtMost(upgrades.processingItems + 1)
-			inputs[id][0].shrink(toProcess)
+		return level.recipeManager.getRecipeFor(recipeType, SingleRecipeInput(inputs[id]), level).map {
+			val output = it.value.assemble(SingleRecipeInput(inputs[id]), level.registryAccess())
+			val inputItem = inputs[id].copyWithCount(1)
+			val toProcess = inputs[id].count.coerceAtMost(upgrades.processingItems + 1)
+			inputs[id].shrink(toProcess)
 
 			syncSlots[id].inputItem = inputItem
 			syncSlots[id].outputItem = output.copy()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidChargerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidChargerBlockEntity.kt
index 83946a3ab..670f0c52e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidChargerBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidChargerBlockEntity.kt
@@ -44,7 +44,7 @@ class AndroidChargerBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
 		ents.sort()
 
 		for ((ent) in ents) {
-			val ply = ent.matteryPlayer ?: continue
+			val ply = (ent as Player).matteryPlayer
 
 			if (ply.isAndroid) {
 				val received = ply.androidEnergy.receiveEnergyChecked(available, false)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt
index 95c323e12..44ac77d69 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt
@@ -11,13 +11,13 @@ import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.phys.AABB
 import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
 import ru.dbotthepony.mc.otm.block.entity.WorkerState
-import ru.dbotthepony.mc.otm.capability.MatteryCapability
+import ru.dbotthepony.mc.otm.capability.IMatteryPlayer
 import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
 import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
+import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.capability.moveEnergy
 import ru.dbotthepony.mc.otm.config.MachinesConfig
 import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
 
@@ -81,10 +81,8 @@ class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
 		val z = blockPos.z.toDouble()
 
 		for (ent in level.getEntitiesOfClass(ServerPlayer::class.java, AABB(x, y, z, x + 1.0, y + 2.0, z + 1.0))) {
-			ent.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK {
-				if (it.isAndroid)
-					moveEnergy(energy, it.androidEnergy, amount = energy.batteryLevel, simulate = false, ignoreFlowRestrictions = true)
-			}
+			if (ent.matteryPlayer.isAndroid)
+				moveEnergy(energy, ent.matteryPlayer.androidEnergy, amount = energy.batteryLevel, simulate = false, ignoreFlowRestrictions = true)
 		}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt
index fffbc30f9..2e1fefc45 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt
@@ -6,10 +6,14 @@ import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.bus.api.SubscribeEvent
+import net.neoforged.fml.common.EventBusSubscriber
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.kommons.util.value
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
 import ru.dbotthepony.mc.otm.capability.FlowDirection
 import ru.dbotthepony.mc.otm.capability.energy
@@ -35,7 +39,7 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte
 	val container: MatteryContainer = object : MatteryContainer(::setChanged, CAPACITY) {
 		override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
 			super.setChanged(slot, new, old)
-			batteryStatus[slot].value = new.getCapability(ForgeCapabilities.ENERGY).isPresent
+			batteryStatus[slot].value = new.getCapability(Capabilities.EnergyStorage.ITEM) != null
 			gaugeLevel = batteryLevel.percentage(maxBatteryLevel)
 		}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt
index 38597c699..94fa6f4f5 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt
@@ -6,7 +6,6 @@ import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.level.block.Block
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.ForgeHooks
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
 import ru.dbotthepony.mc.otm.block.entity.WorkerState
 import ru.dbotthepony.mc.otm.capability.*
@@ -89,7 +88,7 @@ class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDe
 
 		if (workTicks == 0 && !redstoneControl.isBlockedByRedstone && checkFuelSlot) {
 			if (!fuelContainer[0].isEmpty) {
-				val ticks = ForgeHooks.getBurnTime(fuelContainer[0], null) / MachinesConfig.ChemicalGenerator.RATIO
+				val ticks = fuelContainer[0].getBurnTime(null) / MachinesConfig.ChemicalGenerator.RATIO
 
 				if (
 					ticks > 0 &&
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt
index 1bdb492ea..5fe62e3c6 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt
@@ -1,6 +1,7 @@
 package ru.dbotthepony.mc.otm.block.entity.tech
 
 import net.minecraft.core.BlockPos
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.IntTag
 import net.minecraft.nbt.ListTag
@@ -9,7 +10,7 @@ import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock
@@ -19,6 +20,7 @@ import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
 import ru.dbotthepony.mc.otm.core.*
 import ru.dbotthepony.mc.otm.core.math.BlockRotation
 import ru.dbotthepony.mc.otm.core.math.Decimal
+import ru.dbotthepony.mc.otm.core.math.RelativeSide
 import ru.dbotthepony.mc.otm.core.math.getDecimal
 import ru.dbotthepony.mc.otm.core.nbt.getByteArrayList
 import ru.dbotthepony.mc.otm.core.nbt.map
@@ -84,14 +86,14 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 		return value
 	}
 
-	override fun saveShared(nbt: CompoundTag) {
-		super.saveShared(nbt)
+	override fun saveShared(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.saveShared(nbt, registry)
 		nbt[PASSED_ENERGY_KEY] = passed.serializeNBT()
 		ioLimit?.let { nbt[IO_LIMIT_KEY] = it.serializeNBT() }
 	}
 
-	override fun saveLevel(nbt: CompoundTag) {
-		super.saveLevel(nbt)
+	override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.saveLevel(nbt, registry)
 		val list = ListTag()
 		nbt[POWER_HISTORY_KEY] = list
 		nbt[POWER_HISTORY_POINTER_KEY] = historyTick
@@ -100,8 +102,8 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 			list.add(num.serializeNBT())
 	}
 
-	override fun load(nbt: CompoundTag) {
-		super.load(nbt)
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
 
 		passed = nbt.getDecimal(PASSED_ENERGY_KEY)
 		ioLimit = nbt.mapPresent(IO_LIMIT_KEY, Decimal.Companion::deserializeNBT)
@@ -127,8 +129,8 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 	private val energyInput = EnergyCounterCap(true)
 	private val energyOutput = EnergyCounterCap(false)
 
-	private val inputCapability by front.trackEnergy()
-	private val outputCapability by back.trackEnergy()
+	private val inputCapability by CapabilityCache(RelativeSide.FRONT, Capabilities.EnergyStorage.BLOCK)
+	private val outputCapability by CapabilityCache(RelativeSide.BACK, Capabilities.EnergyStorage.BLOCK)
 
 	private inner class EnergyCounterCap(isInput: Boolean) : IMatteryEnergyStorage {
 		override val energyFlow = FlowDirection.input(isInput)
@@ -137,7 +139,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 			if (redstoneControl.isBlockedByRedstone)
 				return Decimal.ZERO
 
-			val it = inputCapability.orNull()
+			val it = inputCapability
 
 			if (it != null) {
 				val diff: Decimal
@@ -165,7 +167,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 			if (redstoneControl.isBlockedByRedstone)
 				return Decimal.ZERO
 
-			val it = outputCapability.orNull()
+			val it = outputCapability
 
 			if (it != null) {
 				val diff: Decimal
@@ -195,7 +197,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 		override var batteryLevel: Decimal
 			get() {
 				if (energyFlow.input) {
-					val it = outputCapability.orNull()
+					val it = outputCapability
 
 					if (it != null) {
 						if (it is IMatteryEnergyStorage) {
@@ -205,7 +207,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 						return Decimal(it.energyStored)
 					}
 				} else {
-					val it = inputCapability.orNull()
+					val it = inputCapability
 
 					if (it != null) {
 						if (it is IMatteryEnergyStorage) {
@@ -225,7 +227,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 		override val maxBatteryLevel: Decimal
 			get() {
 				if (energyFlow.input) {
-					val it = outputCapability.orNull()
+					val it = outputCapability
 
 					if (it != null) {
 						if (it is IMatteryEnergyStorage) {
@@ -235,7 +237,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 						return Decimal(it.maxEnergyStored)
 					}
 				} else {
-					val it = inputCapability.orNull()
+					val it = inputCapability
 
 					if (it != null) {
 						if (it is IMatteryEnergyStorage) {
@@ -252,7 +254,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 		override val missingPower: Decimal
 			get() {
 				if (energyFlow.input) {
-					val it = outputCapability.orNull()
+					val it = outputCapability
 
 					if (it != null) {
 						if (it is IMatteryEnergyStorage) {
@@ -262,7 +264,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 						return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0))
 					}
 				} else {
-					val it = inputCapability.orNull()
+					val it = inputCapability
 
 					if (it != null) {
 						if (it is IMatteryEnergyStorage) {
@@ -278,11 +280,8 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
 	}
 
 	init {
-		front.Cap(ForgeCapabilities.ENERGY, energyInput)
-		front.Cap(MatteryCapability.ENERGY, energyInput)
-
-		back.Cap(ForgeCapabilities.ENERGY, energyOutput)
-		back.Cap(MatteryCapability.ENERGY, energyOutput)
+		exposeEnergySided(RelativeSide.FRONT, energyInput)
+		exposeEnergySided(RelativeSide.BACK, energyOutput)
 	}
 
 	override fun tick() {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt
index af76a7e9d..b42dfc868 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt
@@ -7,10 +7,9 @@ import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.block.entity.BlockEntityType
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
 import ru.dbotthepony.mc.otm.capability.FlowDirection
-import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.energy.BlockEnergyStorageImpl
 import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
 import ru.dbotthepony.mc.otm.capability.moveEnergy
@@ -19,7 +18,6 @@ import ru.dbotthepony.mc.otm.config.MachinesConfig
 import ru.dbotthepony.mc.otm.container.HandlerFilter
 import ru.dbotthepony.mc.otm.container.MatteryContainer
 import ru.dbotthepony.mc.otm.core.MultiblockBuilder
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.menu.tech.EnergyHatchMenu
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
 
@@ -32,7 +30,7 @@ class EnergyHatchBlockEntity(
 ) : MatteryDeviceBlockEntity(type, blockPos, blockState) {
 	val energy = ProfiledEnergyStorage(BlockEnergyStorageImpl(this::markDirtyFast, FlowDirection.input(isInput), capacity))
 
-	val container = object : MatteryContainer(this::markDirtyFast, CAPACITY) {
+	val container = object : MatteryContainer(::markDirtyFast, CAPACITY) {
 		override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int {
 			return 1
 		}
@@ -45,9 +43,8 @@ class EnergyHatchBlockEntity(
 		savetables.stateful(::container, BATTERY_KEY)
 
 		// it would cause a lot of frustration if hatches accept stuff only though one face
-		exposeGlobally(ForgeCapabilities.ENERGY, energy)
-		exposeGlobally(MatteryCapability.ENERGY, energy)
-		exposeGlobally(ForgeCapabilities.ITEM_HANDLER, itemHandler)
+		exposeGlobally(Capabilities.EnergyStorage.BLOCK, energy)
+		exposeGlobally(Capabilities.ItemHandler.BLOCK, itemHandler)
 	}
 
 	override fun tick() {
@@ -55,7 +52,7 @@ class EnergyHatchBlockEntity(
 
 		if (!redstoneControl.isBlockedByRedstone) {
 			container.forEach {
-				it.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
+				it.getCapability(Capabilities.EnergyStorage.ITEM)?.let {
 					if (isInput) {
 						moveEnergy(it, energy, simulate = false)
 					} else {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt
index 80c28c98e..4a7722a5d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt
@@ -1,6 +1,9 @@
 package ru.dbotthepony.mc.otm.block.entity.tech
 
 import net.minecraft.core.BlockPos
+import net.minecraft.core.Holder
+import net.minecraft.core.HolderLookup
+import net.minecraft.nbt.CompoundTag
 import net.minecraft.server.level.ServerLevel
 import net.minecraft.sounds.SoundEvents
 import net.minecraft.sounds.SoundSource
@@ -9,11 +12,12 @@ import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.enchantment.Enchantment
 import net.minecraft.world.item.enchantment.Enchantments
 import net.minecraft.world.level.block.Block
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage.Companion.XP_TO_LIQUID_RATIO
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
 import ru.dbotthepony.mc.otm.block.tech.EssenceStorageBlock
@@ -22,6 +26,7 @@ import ru.dbotthepony.mc.otm.config.MachinesConfig
 import ru.dbotthepony.mc.otm.container.HandlerFilter
 import ru.dbotthepony.mc.otm.container.MatteryContainer
 import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid
+import ru.dbotthepony.mc.otm.core.lookupOrThrow
 import ru.dbotthepony.mc.otm.core.math.Vector
 import ru.dbotthepony.mc.otm.core.util.countingLazy
 import ru.dbotthepony.mc.otm.item.EssenceCapsuleItem
@@ -42,6 +47,8 @@ class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
 	val servoContainer = MatteryContainer(::markDirtyFast, 1)
 	val mendingContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer)
 
+	private var mending: Holder<Enchantment>? = null
+
 	init {
 		savetables.long(::experienceStored)
 		savetables.stateful(::capsuleContainer)
@@ -62,7 +69,7 @@ class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
 			})),
 			mendingContainer.handler(object : HandlerFilter {
 				override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-					return stack.isDamaged && stack.getEnchantmentLevel(Enchantments.MENDING) > 0
+					return stack.isDamaged && stack.getEnchantmentLevel(mending!!) > 0
 				}
 
 				override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
@@ -72,6 +79,12 @@ class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
 		)
 	)
 
+	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
+		super.loadAdditional(nbt, registry)
+
+		mending = registry.lookupOrThrow(Enchantments.MENDING)
+	}
+
 	val fluidConfig = ConfigurableFluidHandler(this)
 
 	override fun getTanks(): Int {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/GravitationStabilizerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/GravitationStabilizerBlockEntity.kt
index 5e119bf06..be89d3322 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/GravitationStabilizerBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/GravitationStabilizerBlockEntity.kt
@@ -70,10 +70,6 @@ class GravitationStabilizerBlockEntity(p_155229_: BlockPos, p_155230_: BlockStat
 		blackHole?.stabilizerDetached(this)
 	}
 
-	override fun getRenderBoundingBox(): AABB {
-		return AABB(blockPos.offset(-RANGE, -RANGE, -RANGE), blockPos.offset(RANGE, RANGE, RANGE))
-	}
-
 	companion object {
 		const val RANGE = 64
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt
index 2e00b1113..1fea51d5e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt
@@ -6,7 +6,7 @@ import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.level.block.entity.BlockEntityType
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
 import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity
 import ru.dbotthepony.mc.otm.container.HandlerFilter
@@ -26,7 +26,7 @@ class ItemHatchBlockEntity(
 
 	init {
 		savetables.stateful(::container, INVENTORY_KEY)
-		exposeGlobally(ForgeCapabilities.ITEM_HANDLER, itemHandler)
+		exposeGlobally(Capabilities.ItemHandler.BLOCK, itemHandler)
 	}
 
 	override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt
index 5e0f7b7a0..a234a34f0 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt
@@ -7,7 +7,7 @@ import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.block.entity.BlockEntityType
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
 import ru.dbotthepony.mc.otm.capability.FlowDirection
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
@@ -18,7 +18,6 @@ import ru.dbotthepony.mc.otm.config.MachinesConfig
 import ru.dbotthepony.mc.otm.container.HandlerFilter
 import ru.dbotthepony.mc.otm.container.MatteryContainer
 import ru.dbotthepony.mc.otm.core.MultiblockBuilder
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
@@ -29,7 +28,7 @@ class MatterHatchBlockEntity(
 	blockPos: BlockPos,
 	blockState: BlockState
 ) : MatteryDeviceBlockEntity(type, blockPos, blockState) {
-	val container = object : MatteryContainer(this::markDirtyFast, CAPACITY) {
+	val container = object : MatteryContainer(::markDirtyFast, CAPACITY) {
 		override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int {
 			return 1
 		}
@@ -48,8 +47,8 @@ class MatterHatchBlockEntity(
 		savetables.stateful(::matter, MATTER_STORAGE_KEY)
 
 		// it would cause a lot of frustration if hatches accept stuff only though one face
-		exposeGlobally(ForgeCapabilities.ITEM_HANDLER, itemHandler)
-		exposeGlobally(MatteryCapability.MATTER, matter)
+		exposeGlobally(Capabilities.ItemHandler.BLOCK, itemHandler)
+		exposeGlobally(MatteryCapability.MATTER_BLOCK, matter)
 	}
 
 	override fun tick() {
@@ -57,7 +56,7 @@ class MatterHatchBlockEntity(
 
 		if (!redstoneControl.isBlockedByRedstone && matter.missingMatter > Decimal.ZERO) {
 			container.forEach {
-				it.getCapability(MatteryCapability.MATTER).ifPresentK {
+				it.getCapability(MatteryCapability.MATTER_ITEM)?.let {
 					if (isInput) {
 						moveMatter(it, matter, simulate = false)
 					} else {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt
index b074929fc..74e72acef 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt
@@ -6,7 +6,7 @@ import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage
 import ru.dbotthepony.mc.otm.block.entity.JobContainer
 import ru.dbotthepony.mc.otm.block.entity.JobStatus
@@ -44,7 +44,7 @@ class PlatePressBlockEntity(
 	)
 
 	init {
-		exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience)
+		exposeGlobally(Capabilities.FluidHandler.BLOCK, experience)
 
 		savetables.stateful(::energy, ENERGY_KEY)
 		savetables.stateful(::inputContainer)
@@ -76,7 +76,6 @@ class PlatePressBlockEntity(
 
 		val recipe = level.recipeManager
 			.byType(MRecipes.PLATE_PRESS)
-			.values
 			.iterator()
 			.filter { it.value.matches(inputContainer, id) }
 			.maybe()?.value ?: return JobContainer.noItem()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidStationBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidStationBlock.kt
index 2659dea5d..767d9981f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidStationBlock.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidStationBlock.kt
@@ -22,7 +22,7 @@ import ru.dbotthepony.mc.otm.block.addSimpleDescription
 import ru.dbotthepony.mc.otm.block.entity.tech.AndroidStationBlockEntity
 import ru.dbotthepony.mc.otm.block.entity.WorkerState
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
-import ru.dbotthepony.mc.otm.core.orNull
+import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.registry.MBlockEntities
 import ru.dbotthepony.mc.otm.shapes.BlockShapes
 
@@ -41,9 +41,7 @@ class AndroidStationBlock(val color: DyeColor?) : MatteryBlock(DEFAULT_MACHINE_P
 		hand: InteractionHand,
 		blockHitResult: BlockHitResult
 	): InteractionResult {
-		val cap = ply.getCapability(MatteryCapability.MATTERY_PLAYER).orNull() ?: return InteractionResult.FAIL
-
-		if (!cap.isAndroid)
+		if (!ply.matteryPlayer.isAndroid)
 			return InteractionResult.FAIL
 
 		return super.use(blockState, level, blockPos, ply, hand, blockHitResult)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt
index 1628451d7..ed7d56301 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt
@@ -40,7 +40,7 @@ class EssenceStorageBlock(val color: DyeColor?) : RotatableMatteryBlock(Properti
 
 		addSimpleDescription()
 
-		tooltips.blockEntityData<LongTag>("experienceStored") { _, l, acceptor ->
+		tooltips.blockEntityData<LongTag>("experienceStored") { _, _, l, acceptor ->
 			if (minecraft.window.isShiftDown) {
 				acceptor(TranslatableComponent("otm.gui.experience", l.asLong).withStyle(ChatFormatting.GRAY))
 			} else {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt
index 29aacc60d..1bc882e33 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt
@@ -2,10 +2,11 @@ package ru.dbotthepony.mc.otm.capability
 
 import com.google.common.collect.ImmutableList
 import it.unimi.dsi.fastutil.objects.ObjectArrayList
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.ListTag
 import net.minecraft.nbt.NumericTag
-import net.minecraftforge.common.util.INBTSerializable
+import net.neoforged.neoforge.common.util.INBTSerializable
 import ru.dbotthepony.mc.otm.core.forValidRefs
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.nbt.map
@@ -81,7 +82,7 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
 	}
 
 	val savedata: INBTSerializable<CompoundTag?> = object : INBTSerializable<CompoundTag?> {
-		override fun serializeNBT(): CompoundTag {
+		override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 			return CompoundTag().also { tag ->
 				tag["historyReceive"] = ListTag().also {
 					for (value in historyReceiveInternal) {
@@ -99,7 +100,7 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
 			}
 		}
 
-		override fun deserializeNBT(nbt: CompoundTag?) {
+		override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
 			tick = 0
 			thisTickReceive = Decimal.ZERO
 			thisTickTransfer = Decimal.ZERO
@@ -131,16 +132,16 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
 		}
 	}
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		val tag: CompoundTag
 
 		if (parent is INBTSerializable<*>) {
-			tag = (parent as INBTSerializable<CompoundTag?>).serializeNBT() ?: CompoundTag()
+			tag = (parent as INBTSerializable<CompoundTag?>).serializeNBT(registry) ?: CompoundTag()
 		} else {
 			tag = CompoundTag()
 		}
 
-		val tag2 = savedata.serializeNBT()!!
+		val tag2 = savedata.serializeNBT(registry)!!
 
 		for (k in tag2.allKeys) {
 			tag[k] = tag2[k]!!
@@ -157,12 +158,12 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
 		return calcWeightedAverage(historyReceive, tick)
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
 		if (parent is INBTSerializable<*>) {
-			(parent as INBTSerializable<CompoundTag?>).deserializeNBT(nbt)
+			(parent as INBTSerializable<CompoundTag?>).deserializeNBT(registry, nbt)
 		}
 
-		savedata.deserializeNBT(nbt)
+		savedata.deserializeNBT(registry, nbt)
 	}
 
 	companion object {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt
index bc93e7e4a..d2ac47101 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt
@@ -3,14 +3,14 @@ package ru.dbotthepony.mc.otm.capability
 import com.google.common.collect.Streams
 import net.minecraft.ChatFormatting
 import net.minecraft.network.chat.Component
+import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.energy.IEnergyStorage
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
-import net.minecraftforge.items.IItemHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.energy.IEnergyStorage
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.items.IItemHandler
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
 import ru.dbotthepony.mc.otm.capability.fluid.iterator
@@ -33,13 +33,13 @@ import ru.dbotthepony.mc.otm.core.collect.filter
 import ru.dbotthepony.mc.otm.core.collect.map
 import ru.dbotthepony.mc.otm.core.isNotEmpty
 import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.mc.otm.core.orNull
 import ru.dbotthepony.mc.otm.core.util.formatFluidLevel
 import java.util.stream.Stream
 
 private val LOGGER = LogManager.getLogger()
 
-val ICapabilityProvider.matteryPlayer: MatteryPlayerCapability? get() = getCapability(MatteryCapability.MATTERY_PLAYER).orNull()
+val Player.matteryPlayer: MatteryPlayer get() = (this as IMatteryPlayer).otmPlayer
+val LivingEntity.matteryPlayer: MatteryPlayer? get() = (this as? IMatteryPlayer)?.otmPlayer
 
 /**
  * Does a checked energy receive, calls [IMatteryEnergyStorage.receiveEnergyChecked] if possible
@@ -131,21 +131,21 @@ val IEnergyStorage.chargeRatio: Float get() {
 /**
  * Shortcut for getting [IEnergyStorage], including wrappers for it
  */
-val ICapabilityProvider.energy: IEnergyStorage? get() {
-	val mattery = getCapability(MatteryCapability.ENERGY)
+val ItemStack.energy: IEnergyStorage? get() {
+	//val mattery = getCapability(MatteryCapability.ITEM_ENERGY)
 
-	if (mattery.isPresent) {
-		return mattery.orNull()
-	}
+	//if (mattery != null) {
+	//	return mattery
+	//}
 
-	return getCapability(ForgeCapabilities.ENERGY).orNull()
+	return getCapability(Capabilities.EnergyStorage.ITEM)
 }
 
 /**
  * Shortcut for getting sideless [IMatteryEnergyStorage], including wrappers for it
  */
-val ICapabilityProvider.matteryEnergy: IMatteryEnergyStorage? get() {
-	return getCapability(MatteryCapability.ENERGY).orNull()
+val ItemStack.matteryEnergy: IMatteryEnergyStorage? get() {
+	return getCapability(MatteryCapability.ITEM_ENERGY)
 }
 
 fun Player.items(includeCosmetics: Boolean = true): Iterator<ItemStack> {
@@ -320,7 +320,7 @@ internal fun IFluidHandler.fluidLevel(tooltips: (Component) -> Unit) {
 	if (fluid.isEmpty) {
 		tooltips(formatFluidLevel(0, getTankCapacity(0), formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY))
 	} else {
-		tooltips(formatFluidLevel(fluid.amount, getTankCapacity(0), fluid.displayName, formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY))
+		tooltips(formatFluidLevel(fluid.amount, getTankCapacity(0), fluid.hoverName, formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY))
 	}
 }
 
@@ -330,18 +330,18 @@ private fun actuallyMoveFluid(drained: FluidStack, source: IFluidHandler, destin
 	val filled = destination.fill(drained, IFluidHandler.FluidAction.SIMULATE)
 	if (filled == 0) return FluidStack.EMPTY
 
-	val drained2 = source.drain(FluidStack(drained, filled), IFluidHandler.FluidAction.SIMULATE)
+	val drained2 = source.drain(drained.copyWithAmount(filled), IFluidHandler.FluidAction.SIMULATE)
 	if (drained2.amount != filled) return FluidStack.EMPTY
 
 	val filled2 = destination.fill(drained2, IFluidHandler.FluidAction.SIMULATE)
 	if (filled2 != drained2.amount) return FluidStack.EMPTY
 
-	if (!actuallyDrain && !actuallyFill) return FluidStack(drained2, filled2)
+	if (!actuallyDrain && !actuallyFill) return drained2.copyWithAmount(filled2)
 
 	val drained3: FluidStack
 
 	if (actuallyDrain) {
-		drained3 = source.drain(FluidStack(drained2, filled2), IFluidHandler.FluidAction.EXECUTE)
+		drained3 = source.drain(drained2.copyWithAmount(filled2), IFluidHandler.FluidAction.EXECUTE)
 
 		if (drained3.amount != filled2) {
 			LOGGER.warn("Inconsistency of fluid extraction from $source between simulate and execute modes (simulated $drained2; extracted $drained3); This can lead to duping!!!")
@@ -362,7 +362,7 @@ private fun actuallyMoveFluid(drained: FluidStack, source: IFluidHandler, destin
 		filled3 = filled2
 	}
 
-	return FluidStack(drained3, filled3)
+	return drained3.copyWithAmount(filled3)
 }
 
 fun moveFluid(source: IFluidHandler, sourceTank: Int? = null, destination: IFluidHandler, limit: Int = Int.MAX_VALUE, actuallyDrain: Boolean = true, actuallyFill: Boolean = true): FluidStack {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IMatteryPlayer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IMatteryPlayer.kt
new file mode 100644
index 000000000..890f6385f
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IMatteryPlayer.kt
@@ -0,0 +1,8 @@
+package ru.dbotthepony.mc.otm.capability
+
+interface IMatteryPlayer {
+	// since new capabilities dont get to live through getCapability calls by design
+	// and data attachments are.... limited.
+	// We gonna do it Fabric way
+	val otmPlayer: MatteryPlayer
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayer.kt
similarity index 85%
rename from src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt
rename to src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayer.kt
index 52cde2994..c529aa144 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayer.kt
@@ -10,12 +10,14 @@ import net.minecraft.client.model.PlayerModel
 import net.minecraft.client.player.AbstractClientPlayer
 import net.minecraft.commands.Commands
 import net.minecraft.commands.arguments.EntityArgument
-import net.minecraft.core.Direction
+import net.minecraft.core.HolderLookup
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.IntTag
 import net.minecraft.nbt.ListTag
 import net.minecraft.nbt.StringTag
 import net.minecraft.network.chat.Component
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.level.ServerLevel
 import net.minecraft.server.level.ServerPlayer
@@ -26,40 +28,31 @@ import net.minecraft.world.damagesource.DamageSource
 import net.minecraft.world.effect.MobEffect
 import net.minecraft.world.effect.MobEffectInstance
 import net.minecraft.world.effect.MobEffects
-import net.minecraft.world.entity.Entity
 import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.entity.boss.wither.WitherBoss
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.Items
 import net.minecraft.world.item.ProjectileWeaponItem
 import net.minecraft.world.item.crafting.RecipeManager
 import net.minecraft.world.item.crafting.RecipeType
+import net.minecraft.world.item.crafting.SingleRecipeInput
 import net.minecraft.world.level.GameRules
 import net.minecraft.world.level.Level
 import net.minecraft.world.phys.Vec3
-import net.minecraftforge.common.ForgeHooks
-import net.minecraftforge.common.MinecraftForge
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.common.util.INBTSerializable
-import net.minecraftforge.common.util.LazyOptional
-import net.minecraftforge.event.AttachCapabilitiesEvent
-import net.minecraftforge.event.RegisterCommandsEvent
-import net.minecraftforge.event.TickEvent
-import net.minecraftforge.event.TickEvent.PlayerTickEvent
-import net.minecraftforge.event.entity.living.LivingAttackEvent
-import net.minecraftforge.event.entity.living.LivingDeathEvent
-import net.minecraftforge.event.entity.living.LivingHurtEvent
-import net.minecraftforge.event.entity.living.MobEffectEvent
-import net.minecraftforge.event.entity.player.PlayerEvent
-import net.minecraftforge.eventbus.api.Cancelable
-import net.minecraftforge.eventbus.api.Event
-import net.minecraftforge.network.PacketDistributor
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.server.command.EnumArgument
+import net.neoforged.bus.api.Event
+import net.neoforged.bus.api.ICancellableEvent
+import net.neoforged.neoforge.common.CommonHooks
+import net.neoforged.neoforge.common.NeoForge
+import net.neoforged.neoforge.event.RegisterCommandsEvent
+import net.neoforged.neoforge.event.entity.living.LivingDeathEvent
+import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent
+import net.neoforged.neoforge.event.entity.living.MobEffectEvent
+import net.neoforged.neoforge.event.entity.player.PlayerEvent
+import net.neoforged.neoforge.event.tick.PlayerTickEvent
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.server.command.EnumArgument
 import org.apache.logging.log4j.LogManager
 import org.joml.Vector4f
 import ru.dbotthepony.kommons.collect.ListenableMap
@@ -93,18 +86,18 @@ import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.config.AndroidConfig
 import ru.dbotthepony.mc.otm.config.ExopackConfig
 import ru.dbotthepony.mc.otm.container.CombinedContainer
-import ru.dbotthepony.mc.otm.container.MatteryContainer
-import ru.dbotthepony.mc.otm.container.get
 import ru.dbotthepony.mc.otm.container.DynamicallyProxiedContainer
 import ru.dbotthepony.mc.otm.container.IContainer
 import ru.dbotthepony.mc.otm.container.IMatteryContainer
+import ru.dbotthepony.mc.otm.container.MatteryContainer
+import ru.dbotthepony.mc.otm.container.get
 import ru.dbotthepony.mc.otm.container.util.slotIterator
 import ru.dbotthepony.mc.otm.container.vanishCursedItems
 import ru.dbotthepony.mc.otm.core.*
 import ru.dbotthepony.mc.otm.core.collect.UUIDIntModifiersMap
 import ru.dbotthepony.mc.otm.core.collect.filter
-import ru.dbotthepony.mc.otm.core.math.RGBColorDFUCodec
 import ru.dbotthepony.mc.otm.core.math.Decimal
+import ru.dbotthepony.mc.otm.core.math.RGBColorDFUCodec
 import ru.dbotthepony.mc.otm.core.math.minus
 import ru.dbotthepony.mc.otm.core.nbt.getCompoundList
 import ru.dbotthepony.mc.otm.core.nbt.getIntList
@@ -147,45 +140,32 @@ private fun Player.dropContainer(container: Container, spill: Boolean = true, se
 }
 
 @Suppress("unused")
-class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerializable<CompoundTag> {
+class MatteryPlayer(val ply: Player) {
 	/**
 	 * This event is fired on main event bus before ticking logic takes place.
 	 *
 	 * Cancelling it is probably not a good idea, but you can do it anyway.
 	 */
-	@Cancelable
-	data class PreTick(val capability: MatteryPlayerCapability) : Event() {
+	data class PreTick(val capability: MatteryPlayer) : Event(), ICancellableEvent {
 		val player get() = capability.ply
 		val level: Level get() = capability.ply.level()
-
-		override fun isCancelable(): Boolean {
-			return true
-		}
 	}
 
 	/**
 	 * This event is fired on main event bus after ticking logic took place.
 	 */
-	data class PostTick(val capability: MatteryPlayerCapability) : Event() {
+	data class PostTick(val capability: MatteryPlayer) : Event() {
 		val player get() = capability.ply
 		val level: Level get() = capability.ply.level()
-
-		override fun isCancelable(): Boolean {
-			return false
-		}
 	}
 
 	/**
 	 * This event is fired on main event bus when [Inventory.add] was called and entire [ItemStack] can not be stored
-	 * (both in [Inventory] and [MatteryPlayerCapability] exopack due to inventory filters or no free space).
+	 * (both in [Inventory] and [MatteryPlayer] exopack due to inventory filters or no free space).
 	 */
-	data class ItemStackLeftoverEvent(val stack: ItemStack, val capability: MatteryPlayerCapability) : Event() {
+	data class ItemStackLeftoverEvent(val stack: ItemStack, val capability: MatteryPlayer) : Event() {
 		val player get() = capability.ply
 		val level: Level get() = capability.ply.level()
-
-		override fun isCancelable(): Boolean {
-			return false
-		}
 	}
 
 	private inner class PlayerMatteryContainer(size: Int) : MatteryContainer(size) {
@@ -327,7 +307,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 				field[i] = ItemStack.EMPTY
 			}
 
-			value.deserializeNBT(field.serializeNBT())
+			value.deserializeNBT(ply.level().registryAccess(), field.serializeNBT(ply.level().registryAccess()))
 			value.addFilterSynchronizer(syncher)
 			field = value
 
@@ -449,7 +429,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 	private val deathLog = ArrayDeque<Pair<Int, Component>>()
 
 	private val featureMap = IdentityHashMap<AndroidFeatureType<*>, AndroidFeature>()
-	private val networkQueue = ArrayList<Any>()
+	private val networkQueue = ArrayList<CustomPacketPayload>()
 	private val queuedTicks = ArrayList<Runnable>()
 	private var tickedOnce = false
 
@@ -474,7 +454,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 	private var lastLiquidPosition = Vec3(0.0, 0.0, 0.0)
 	private var liquidDistanceTravelled = 0.0
 	private var wasInLiquid = false
-	private var lastDimension = ResourceLocation("overworld")
+	private var lastDimension = ResourceLocation("minecraft", "overworld")
 
 	// clientside only flag for render hook
 	// TODO: actual code, currently particles spawn just behind player on ExopackSmokePacket
@@ -539,13 +519,13 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		override fun computeNextJob(): JobContainer<ItemJob> {
 			if (!exopackEnergy.batteryLevel.isPositive) return JobContainer.noEnergy()
 			val level = ply.level() as? ServerLevel ?: return JobContainer.failure()
-			val recipe = cache.getRecipeFor(input, level)
+			val recipe = cache.getRecipeFor(SingleRecipeInput(input[0]), level)
 
 			if (recipe.isEmpty) {
 				return JobContainer.noItem()
 			} else {
 				val actual = recipe.get()
-				val item = actual.value.assemble(input, level.registryAccess())
+				val item = actual.value.assemble(SingleRecipeInput(input[0]), level.registryAccess())
 				input[0].shrink(1)
 				input.setChanged(0)
 				return JobContainer.success(ItemJob(item, actual.value.cookingTime.toDouble(), ExopackConfig.FURNACE_POWER_CONSUMPTION, actual.value.experience))
@@ -556,7 +536,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 			super.onJobTick(status)
 
 			if (isExopackVisible && ply.level().random.nextFloat() <= 0.05f) {
-				MatteryPlayerNetworkChannel.send(PacketDistributor.TRACKING_ENTITY_AND_SELF.with(ply as ServerPlayer), ExopackSmokePacket(ply.uuid))
+				PacketDistributor.sendToPlayersTrackingEntityAndSelf(ply, ExopackSmokePacket(ply.uuid))
 			}
 		}
 
@@ -912,7 +892,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		return featureMap[feature] as T?
 	}
 
-	private fun onHurt(event: LivingHurtEvent) {
+	private fun onHurt(event: LivingIncomingDamageEvent) {
 		if (isAndroid) {
 			for (feature in featureMap.values) {
 				feature.onHurt(event)
@@ -924,14 +904,6 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		}
 	}
 
-	private fun onAttack(event: LivingAttackEvent) {
-		if (isAndroid) {
-			for (feature in featureMap.values) {
-				feature.onAttack(event)
-			}
-		}
-	}
-
 	internal fun <T : AndroidFeature> computeIfAbsent(feature: AndroidFeatureType<T>): T {
 		return getFeature(feature) ?: addFeature(feature)
 	}
@@ -947,22 +919,22 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		}
 	}
 
-	override fun serializeNBT(): CompoundTag {
-		val tag = savetables.serializeNBT()
+	fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
+		val tag = savetables.serializeNBT(registry)
 
 		// iteration
 		tag["deathLog"] = ListTag().also {
 			for ((ticks, component) in deathLog) {
 				it.add(CompoundTag().also {
 					it["ticks"] = ticks
-					it["component"] = StringTag.valueOf(Component.Serializer.toJson(component))
+					it["component"] = StringTag.valueOf(Component.Serializer.toJson(component, registry))
 				})
 			}
 		}
 
 		tag["features"] = ListTag().also {
 			for (feature in featureMap.values) {
-				it.add(feature.serializeNBT().also {
+				it.add(feature.serializeNBT(registry).also {
 					it["id"] = feature.type.registryName!!.toString()
 				})
 			}
@@ -991,8 +963,8 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		return tag
 	}
 
-	override fun deserializeNBT(tag: CompoundTag) {
-		savetables.deserializeNBT(tag)
+	fun deserializeNBT(tag: CompoundTag, registry: HolderLookup.Provider) {
+		savetables.deserializeNBT(registry, tag)
 
 		if (MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON.uuid(ItemStack.EMPTY) in exopackSlotModifierMap.delegate) {
 			isExopackEnderAccessInstalled = true
@@ -1014,7 +986,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		for (i in 0 until regularSlotFilters.size.coerceAtMost(this.regularSlotFilters.size)) {
 			val path = regularSlotFilters[i].asString
 			if (path == "") continue
-			this.regularSlotFilters[i].value = ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(path) ?: continue) ?: Items.AIR
+			this.regularSlotFilters[i].value = BuiltInRegistries.ITEM.get(ResourceLocation.tryParse(path) ?: continue)
 		}
 
 		if ("slotsChargeFlag" in tag) {
@@ -1029,7 +1001,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		deathLog.clear()
 
 		for (value in tag.getCompoundList("deathLog")) {
-			val component = Component.Serializer.fromJson(value.getString("component"))
+			val component = Component.Serializer.fromJson(value.getString("component"), registry)
 
 			if (component != null) {
 				deathLog.add(value.getInt("ticks") to component)
@@ -1040,12 +1012,12 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		research.clear()
 
 		for (featureTag in tag.getCompoundList("features")) {
-			val feature = MRegistry.ANDROID_FEATURES.getValue(ResourceLocation(featureTag.getString("id")))
+			val feature = MRegistry.ANDROID_FEATURES.get(ResourceLocation.parse(featureTag.getString("id")))
 
 			if (feature?.isApplicable(this) == true) {
 				val instance = feature.create(this)
 
-				instance.deserializeNBT(featureTag)
+				instance.deserializeNBT(registry, featureTag)
 				addFeature(instance)
 
 				if (!ply.level().isClientSide) {
@@ -1055,7 +1027,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		}
 
 		for (researchTag in tag.getCompoundList("research")) {
-			val research = AndroidResearchManager[ResourceLocation(researchTag.getString("id"))]
+			val research = AndroidResearchManager[ResourceLocation.parse(researchTag.getString("id"))]
 
 			if (research != null) {
 				val instance = AndroidResearch(research, this)
@@ -1088,10 +1060,10 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		androidEnergy.item = ItemStack.EMPTY
 	}
 
-	private fun sendNetwork(packet: Any) {
+	private fun sendNetwork(packet: CustomPacketPayload) {
 		if (ply is ServerPlayer) {
 			if (tickedOnce) {
-				MatteryPlayerNetworkChannel.send(ply, packet)
+				PacketDistributor.sendToPlayer(ply, packet)
 			} else {
 				networkQueue.add(packet)
 			}
@@ -1128,7 +1100,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		if (!ply.isAlive) return
 
 		if (isAndroid) {
-			ForgeRegistries.MOB_EFFECTS.tags()?.getTag(ANDROID_IMMUNE_EFFECTS)?.forEach {
+			BuiltInRegistries.MOB_EFFECT.getOrCreateTag(ANDROID_IMMUNE_EFFECTS).forEach {
 				if (ply.hasEffect(it))
 					ply.removeEffect(it)
 			}
@@ -1138,10 +1110,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 	private fun tick() {
 		if (!ply.isAlive) return
 
-		PreTick(this).also {
-			MinecraftForge.EVENT_BUS.post(it)
-			if (it.isCanceled) return
-		}
+		if (NeoForge.EVENT_BUS.post(PreTick(this)).isCanceled) return
 
 		ticksIExist++
 
@@ -1335,7 +1304,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		val payload = privateSyncherRemote.write()
 
 		if (payload != null) {
-			MatteryPlayerNetworkChannel.send(ply, MatteryPlayerFieldPacket(payload, false))
+			PacketDistributor.sendToPlayer(ply as ServerPlayer, MatteryPlayerDataPacket(payload, false))
 		}
 
 		val trackingIterator = remoteSynchers.entries.iterator()
@@ -1349,19 +1318,19 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 			val payload2 = remote.write()
 
 			if (payload2 != null) {
-				MatteryPlayerNetworkChannel.send(ply, MatteryPlayerFieldPacket(payload2, true, this.ply.uuid))
+				PacketDistributor.sendToPlayer(ply as ServerPlayer, MatteryPlayerDataPacket(payload2, true, this.ply.uuid))
 			}
 		}
 
 		val payload3 = publicSyncherRemote.write()
 
 		if (payload3 != null) {
-			MatteryPlayerNetworkChannel.send(ply, MatteryPlayerFieldPacket(payload3, true))
+			PacketDistributor.sendToPlayer(ply as ServerPlayer, MatteryPlayerDataPacket(payload3, true))
 		}
 
 		if (networkQueue.size != 0) {
 			for (packet in networkQueue) {
-				MatteryPlayerNetworkChannel.send(ply, packet)
+				PacketDistributor.sendToPlayer(ply as ServerPlayer, packet)
 			}
 
 			networkQueue.clear()
@@ -1395,18 +1364,10 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		tickInventory()
 
 		PostTick(this).also {
-			MinecraftForge.EVENT_BUS.post(it)
+			NeoForge.EVENT_BUS.post(it)
 		}
 	}
 
-	private val resolver = LazyOptional.of { this }
-
-	override fun <T> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
-		return if (cap == MatteryCapability.MATTERY_PLAYER) {
-			resolver.cast()
-		} else LazyOptional.empty()
-	}
-
 	/**
 	 * This re-implement [Inventory.add] logic (original method is redirected to this)
 	 */
@@ -1421,7 +1382,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 			wrappedItemInventory.consumeItem(stack, false, onlyIntoExisting = false, popTime = 5)
 		}
 
-		MinecraftForge.EVENT_BUS.post(ItemStackLeftoverEvent(stack, this))
+		NeoForge.EVENT_BUS.post(ItemStackLeftoverEvent(stack, this))
 
 		if (ply.abilities.instabuild) {
 			stack.count = 0
@@ -1445,10 +1406,10 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		}
 	}
 
-	enum class UpgradeType(val prop: KMutableProperty1<MatteryPlayerCapability, Boolean>) {
-		CRAFTING(MatteryPlayerCapability::isExopackCraftingUpgraded),
-		ENDER_ACCESS(MatteryPlayerCapability::isExopackEnderAccessInstalled),
-		SMELTING(MatteryPlayerCapability::isExopackSmeltingInstalled);
+	enum class UpgradeType(val prop: KMutableProperty1<MatteryPlayer, Boolean>) {
+		CRAFTING(MatteryPlayer::isExopackCraftingUpgraded),
+		ENDER_ACCESS(MatteryPlayer::isExopackEnderAccessInstalled),
+		SMELTING(MatteryPlayer::isExopackSmeltingInstalled);
 	}
 
 	@Suppress("unused")
@@ -1457,8 +1418,8 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 
 		private fun setExoPack(players: Collection<Player>, hasExoPack: Boolean): Int {
 			for (player in players) {
-				player.matteryPlayer?.hasExopack = hasExoPack
-				player.matteryPlayer?._exoPackMenu = null
+				player.matteryPlayer.hasExopack = hasExoPack
+				player.matteryPlayer._exoPackMenu = null
 			}
 
 			return players.size
@@ -1466,7 +1427,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 
 		private fun makeAndroid(players: Collection<Player>): Int {
 			for (player in players) {
-				player.matteryPlayer?.becomeAndroid()
+				player.matteryPlayer.becomeAndroid()
 			}
 
 			return players.size
@@ -1474,7 +1435,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 
 		private fun makeHuman(players: Collection<Player>): Int {
 			for (player in players) {
-				player.matteryPlayer?.becomeHumane()
+				player.matteryPlayer.becomeHumane()
 			}
 
 			return players.size
@@ -1482,7 +1443,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 
 		private fun setUpgrade(players: Collection<Player>, type: UpgradeType, state: Boolean): Int {
 			for (player in players) {
-				player.matteryPlayer?.let { type.prop.set(it, state) }
+				player.matteryPlayer.let { type.prop.set(it, state) }
 			}
 
 			return players.size
@@ -1530,104 +1491,73 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 			WitherBoss.LIVING_ENTITY_SELECTOR = WitherBoss.LIVING_ENTITY_SELECTOR.and { it.matteryPlayer?.isAndroid != true }
 		}
 
-		val ANDROID_IMMUNE_EFFECTS: TagKey<MobEffect> = TagKey.create(ForgeRegistries.MOB_EFFECTS.registryKey, ResourceLocation(OverdriveThatMatters.MOD_ID, "android_immune_effects"))
+		val ANDROID_IMMUNE_EFFECTS: TagKey<MobEffect> = TagKey.create(BuiltInRegistries.MOB_EFFECT.key(), ResourceLocation(OverdriveThatMatters.MOD_ID, "android_immune_effects"))
 
-		fun onPlayerTick(event: PlayerTickEvent) {
-			val ent = event.player
+		fun onPlayerTickPre(event: PlayerTickEvent.Pre) {
+			val ent = event.entity
 
-			if (event.phase == TickEvent.Phase.START) {
-				if (!ent.level().isClientSide) {
-					ent.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK {
-						it.preTick()
-					}
-				}
+			if (!ent.level().isClientSide) {
+				ent.matteryPlayer.preTick()
+			}
+		}
+
+		fun onPlayerTickPost(event: PlayerTickEvent.Pre) {
+			val ent = event.entity
+
+			if (ent.level().isClientSide) {
+				ent.matteryPlayer.tickClient()
 			} else {
-				if (ent.level().isClientSide) {
-					ent.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK {
-						it.tickClient()
-					}
-				} else {
-					ent.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK {
-						it.tick()
-					}
-				}
+				ent.matteryPlayer.tick()
 			}
 		}
 
 		fun isMobEffectApplicable(event: MobEffectEvent.Applicable) {
-			event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK {
-				if (it.isAndroid && ForgeRegistries.MOB_EFFECTS.tags()?.getTag(ANDROID_IMMUNE_EFFECTS)?.stream()?.anyMatch { it == event.effectInstance.effect } == true) {
-					event.result = Event.Result.DENY
+			event.entity.matteryPlayer?.let {
+				if (it.isAndroid && BuiltInRegistries.MOB_EFFECT.getOrCreateTag(ANDROID_IMMUNE_EFFECTS).any { it == event.effectInstance?.effect }) {
+					event.result = MobEffectEvent.Applicable.Result.DO_NOT_APPLY
 				}
 			}
 		}
 
-		fun onHurtEvent(event: LivingHurtEvent) {
-			event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK { it.onHurt(event) }
-		}
-
-		fun onAttackEvent(event: LivingAttackEvent) {
-			event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK { it.onAttack(event) }
-		}
-
-		val CAPABILITY_LOCATION = ResourceLocation(OverdriveThatMatters.MOD_ID, "player")
-
-		fun onAttachCapabilityEvent(event: AttachCapabilitiesEvent<Entity>) {
-			val ent = event.`object`
-
-			if (ent is Player) {
-				event.addCapability(CAPABILITY_LOCATION, MatteryPlayerCapability(ent))
-			}
+		fun onHurtEvent(event: LivingIncomingDamageEvent) {
+			event.entity.matteryPlayer?.onHurt(event)
 		}
 
 		fun onPlayerChangeDimensionEvent(event: PlayerEvent.PlayerChangedDimensionEvent) {
 			onceServer {
-				event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK {
-					it.invalidateNetworkState()
-					it.recreateExoPackMenu()
-				}
+				event.entity.matteryPlayer.invalidateNetworkState()
+				event.entity.matteryPlayer.recreateExoPackMenu()
 			}
 		}
 
 		fun onPlayerDeath(event: LivingDeathEvent) {
 			val ply = event.entity as? Player ?: return
-			val mattery = ply.matteryPlayer ?: return
 
-			if (mattery.lastDeathTick != ply.tickCount) {
-				mattery.lastDeathTick = ply.tickCount
+			if (ply.matteryPlayer.lastDeathTick != ply.tickCount) {
+				ply.matteryPlayer.lastDeathTick = ply.tickCount
 			} else {
 				return
 			}
 
-			if (!mattery.isAndroid) {
+			if (!ply.matteryPlayer.isAndroid) {
 				return
 			}
 
-			mattery.iteration++
-			mattery.shouldSendIteration = true
-			mattery.deathLog.addLast(ply.tickCount to ply.combatTracker.deathMessage)
+			ply.matteryPlayer.iteration++
+			ply.matteryPlayer.shouldSendIteration = true
+			ply.matteryPlayer.deathLog.addLast(ply.tickCount to ply.combatTracker.deathMessage)
 
-			if (mattery.androidEnergy.batteryLevel < AndroidConfig.ANDROID_MAX_ENERGY * Decimal("0.2"))
-				mattery.androidEnergy.batteryLevel = AndroidConfig.ANDROID_MAX_ENERGY * Decimal("0.2") // если смерть была от разряда батареи, то предотвращаем софтлок
+			if (ply.matteryPlayer.androidEnergy.batteryLevel < AndroidConfig.ANDROID_MAX_ENERGY * Decimal("0.2"))
+				ply.matteryPlayer.androidEnergy.batteryLevel = AndroidConfig.ANDROID_MAX_ENERGY * Decimal("0.2") // если смерть была от разряда батареи, то предотвращаем софтлок
 
-			while (mattery.deathLog.size > 6) {
-				mattery.deathLog.removeFirst()
+			while (ply.matteryPlayer.deathLog.size > 6) {
+				ply.matteryPlayer.deathLog.removeFirst()
 			}
 		}
 
 		fun onPlayerCloneEvent(event: PlayerEvent.Clone) {
-			val it = event.entity.matteryPlayer ?: return
-			var original = event.original.matteryPlayer
-
-			if (original == null) {
-				event.original.reviveCaps()
-				original = event.original.matteryPlayer
-			}
-
-			if (original == null) {
-				event.original.invalidateCaps()
-				return
-			}
+			val it = event.entity.matteryPlayer
+			val original = event.original.matteryPlayer
 
 			if (original.willBecomeAndroid && event.isWasDeath) {
 				original.becomeAndroid()
@@ -1640,15 +1570,12 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 				}
 			}
 
-			it.deserializeNBT(original.serializeNBT())
+			it.deserializeNBT(original.serializeNBT(it.ply.registryAccess()), it.ply.registryAccess())
 			it.invalidateNetworkState()
-			event.original.invalidateCaps()
 
 			onceServer {
-				event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK {
-					it.invalidateNetworkState()
-					it.recreateExoPackMenu()
-				}
+				event.entity.matteryPlayer.invalidateNetworkState()
+				event.entity.matteryPlayer.recreateExoPackMenu()
 			}
 		}
 
@@ -1661,7 +1588,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 		 */
 		@JvmStatic
 		fun phantomSpawnHook(iterator: Iterator<Player>): Iterator<Player> {
-			return iterator.filter { it.matteryPlayer?.isAndroid != true }
+			return iterator.filter { it.matteryPlayer.isAndroid != true }
 		}
 
 		/**
@@ -1673,9 +1600,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 				return iterator
 			}
 
-			val mattery = entity.matteryPlayer ?: return iterator
-
-			if (mattery.isAndroid) {
+			if (entity.matteryPlayer.isAndroid) {
 				return iterator.filter {
 					it.first.effect != MobEffects.HUNGER
 				}
@@ -1686,7 +1611,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 
 		@JvmStatic
 		fun inventoryDropAll(inventory: Inventory) {
-			val mattery = inventory.player.matteryPlayer ?: return
+			val mattery = inventory.player.matteryPlayer
 
 			if (!mattery.hasExopack) {
 				return
@@ -1724,16 +1649,14 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 
 		@JvmStatic
 		fun playerDestroyVanishingCursedItems(player: Player) {
-			player.matteryPlayer?.let {
-				if (it.hasExopack) {
-					it.exopackContainer.vanishCursedItems()
-					it.exopackChargeSlots.vanishCursedItems()
-					// it.exoPackEnergy.parent.vanishCursedItems()
+			if (player.matteryPlayer.hasExopack) {
+				player.matteryPlayer.exopackContainer.vanishCursedItems()
+				player.matteryPlayer.exopackChargeSlots.vanishCursedItems()
+				// it.exoPackEnergy.parent.vanishCursedItems()
 
-					for (smelter in it.smelters) {
-						smelter.input.vanishCursedItems()
-						smelter.output.vanishCursedItems()
-					}
+				for (smelter in player.matteryPlayer.smelters) {
+					smelter.input.vanishCursedItems()
+					smelter.output.vanishCursedItems()
 				}
 			}
 		}
@@ -1746,25 +1669,25 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 				return
 			}
 
-			val matteryPlayer = player.matteryPlayer ?: return
+			val matteryPlayer = player.matteryPlayer
 
 			if (!matteryPlayer.hasExopack) {
 				return
 			}
 
 			val targetSlot = player.inventory.suitableHotbarSlot
-			val itemSlot = matteryPlayer.exopackContainer.indexOfFirst { !it.isEmpty && ItemStack.isSameItemSameTags(itemStack, it) }
+			val itemSlot = matteryPlayer.exopackContainer.indexOfFirst { !it.isEmpty && ItemStack.isSameItemSameComponents(itemStack, it) }
 
 			if (itemSlot == -1) {
 				return
 			}
 
-			MatteryPlayerNetworkChannel.sendToServer(PickItemFromInventoryPacket(targetSlot, itemSlot))
+			PacketDistributor.sendToServer(PickItemFromInventoryPacket(targetSlot, itemSlot))
 		}
 
 		fun onStartTracking(event: PlayerEvent.StartTracking) {
 			if (event.target is ServerPlayer) {
-				event.target.matteryPlayer?.let {
+				(event.target as ServerPlayer).matteryPlayer.let {
 					it.remoteSynchers[event.entity as ServerPlayer] = it.publicSyncher.Remote()
 				}
 			}
@@ -1772,21 +1695,21 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
 
 		fun onStopTracking(event: PlayerEvent.StopTracking) {
 			if (event.target is ServerPlayer) {
-				event.target.matteryPlayer?.remoteSynchers?.remove(event.entity as ServerPlayer)
+				(event.target as ServerPlayer).matteryPlayer.remoteSynchers.remove(event.entity as ServerPlayer)
 			}
 		}
 
 		@JvmStatic
 		fun getProjectileHook(player: Player, weaponItem: ItemStack): ItemStack? {
 			val weapon = weaponItem.item as? ProjectileWeaponItem ?: return null
-			val matteryPlayer = player.matteryPlayer ?: return null
+			val matteryPlayer = player.matteryPlayer
 
 			if (!matteryPlayer.hasExopack) {
 				return null
 			}
 
 			val item = matteryPlayer.exopackContainer.stream().filter(weapon.allSupportedProjectiles).findFirst().orElse(null) ?: return null
-			return ForgeHooks.getProjectile(player, weaponItem, item)
+			return CommonHooks.getProjectile(player, weaponItem, item)
 		}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/API.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/API.kt
index 689056cb4..e716d7438 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/API.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/API.kt
@@ -1,5 +1,6 @@
 package ru.dbotthepony.mc.otm.capability.drive
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
@@ -33,6 +34,6 @@ interface IMatteryDrive<T : StorageStack<T>> : IStorageComponent<T> {
 	val driveCapacity: BigInteger
 
 	// not extending INBTSerializable to avoid serializing it as forgecaps
-	fun serializeNBT(): CompoundTag
-	fun deserializeNBT(nbt: CompoundTag)
+	fun serializeNBT(registry: HolderLookup.Provider): CompoundTag
+	fun deserializeNBT(nbt: CompoundTag, registry: HolderLookup.Provider)
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt
index 4d506a470..29a0f5855 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt
@@ -4,6 +4,8 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
 import it.unimi.dsi.fastutil.objects.ObjectArraySet
 import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
+import net.minecraft.core.HolderLookup
+import net.minecraft.core.HolderLookup.Provider
 import kotlin.jvm.JvmOverloads
 import java.util.UUID
 import net.minecraft.nbt.CompoundTag
@@ -147,10 +149,10 @@ abstract class AbstractMatteryDrive<T : StorageStack<T>>(
 		return copy
 	}
 
-	protected abstract fun serializeStack(item: T): CompoundTag?
-	protected abstract fun deserializeStack(tag: CompoundTag): T?
+	protected abstract fun serializeStack(item: T, registry: Provider): CompoundTag?
+	protected abstract fun deserializeStack(tag: CompoundTag, registry: Provider): T?
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: Provider): CompoundTag {
 		val compound = CompoundTag()
 
 		compound["capacity"] = driveCapacity.serializeNBT()
@@ -160,7 +162,7 @@ abstract class AbstractMatteryDrive<T : StorageStack<T>>(
 		compound["items"] = list
 
 		for (tuple in stack2tuples.values) {
-			val serialized = serializeStack(tuple.stack)
+			val serialized = serializeStack(tuple.stack, registry)
 
 			if (serialized != null) {
 				list.add(serialized)
@@ -170,7 +172,7 @@ abstract class AbstractMatteryDrive<T : StorageStack<T>>(
 		return compound
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag) {
+	override fun deserializeNBT(nbt: CompoundTag, registry: HolderLookup.Provider) {
 		for (listener in listeners) {
 			for (get in stack2tuples.values) {
 				listener.onStackRemoved(get.id)
@@ -183,12 +185,12 @@ abstract class AbstractMatteryDrive<T : StorageStack<T>>(
 		storedDifferentStacks = 0
 		// nextID = 0L
 
-		driveCapacity = nbt.map("capacity", ::BigInteger) ?: driveCapacity
+		driveCapacity = nbt.get("capacity")?.let { BigInteger(it) } ?: driveCapacity
 		maxDifferentStacks = nbt.getInt("max_different_stacks")
 
 		for (entry in nbt.getList("items", Tag.TAG_COMPOUND.toInt())) {
 			if (entry is CompoundTag) {
-				val stack = deserializeStack(entry)
+				val stack = deserializeStack(entry, registry)
 
 				if (stack != null && stack.isNotEmpty) {
 					storedCount += stack.count
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt
index 9794484b9..6b18ec9dd 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt
@@ -1,25 +1,27 @@
 package ru.dbotthepony.mc.otm.capability.drive
 
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
-import java.util.UUID
-import net.minecraft.ReportedException
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.nbt.NbtIo
 import net.minecraft.CrashReport
+import net.minecraft.ReportedException
 import net.minecraft.Util
+import net.minecraft.core.HolderLookup
+import net.minecraft.core.HolderLookup.Provider
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.NbtAccounter
+import net.minecraft.nbt.NbtIo
 import net.minecraft.world.level.storage.LevelResource
-import java.util.concurrent.locks.LockSupport
-import net.minecraftforge.event.server.ServerAboutToStartEvent
-import net.minecraftforge.event.server.ServerStoppingEvent
-import net.minecraftforge.event.level.LevelEvent
+import net.neoforged.neoforge.event.level.LevelEvent
 import org.apache.logging.log4j.LogManager
-import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
+import ru.dbotthepony.mc.otm.MINECRAFT_SERVER
+import ru.dbotthepony.mc.otm.Registries
 import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
 import ru.dbotthepony.mc.otm.isServerThread
-import java.io.File
 import java.lang.ref.WeakReference
-import java.util.ArrayList
+import java.nio.file.Files
+import java.nio.file.Path
+import java.util.*
 import java.util.concurrent.ConcurrentLinkedQueue
+import kotlin.io.path.exists
 
 private class WeakDriveReference(drive: IMatteryDrive<*>) {
 	private var drive: IMatteryDrive<*>? = drive
@@ -33,7 +35,7 @@ private class WeakDriveReference(drive: IMatteryDrive<*>) {
 		return drive ?: weak.get()
 	}
 
-	fun sync(): CompoundTag? {
+	fun sync(registry: HolderLookup.Provider): CompoundTag? {
 		val drive = drive() ?: return null
 
 		if (!drive.isDirty) {
@@ -41,7 +43,7 @@ private class WeakDriveReference(drive: IMatteryDrive<*>) {
 			return null
 		}
 
-		val tag = drive.serializeNBT()
+		val tag = drive.serializeNBT(registry)
 		drive.isDirty = false
 		this.drive = null
 
@@ -65,22 +67,20 @@ object DrivePool {
 	private val pool = Object2ObjectOpenHashMap<UUID, WeakDriveReference>()
 
 	private val backlog = ConcurrentLinkedQueue<BacklogLine>()
-	private var knownBaseDirectory: File? = null
+	private var knownBaseDirectory: Path? = null
 
-	private val baseDirectory: File? get() {
-		val server = NULLABLE_MINECRAFT_SERVER ?: return null
-
-		val baseDirectory = server.storageSource.getLevelPath(resource).toFile()
+	private val baseDirectory: Path get() {
+		val baseDirectory = MINECRAFT_SERVER.storageSource.getLevelPath(resource)
 
 		if (knownBaseDirectory != baseDirectory) {
-			baseDirectory.mkdirs()
+			Files.createDirectories(baseDirectory)
 			knownBaseDirectory = baseDirectory
 		}
 
 		return baseDirectory
 	}
 
-	operator fun <T : IMatteryDrive<*>> get(id: UUID, deserializer: (CompoundTag) -> T, factory: () -> T): T {
+	fun <T : IMatteryDrive<*>> get(id: UUID, deserializer: (CompoundTag, HolderLookup.Provider) -> T, factory: () -> T): T {
 		if (!isServerThread())
 			throw IllegalAccessException("Can not access drive pool from outside of server thread.")
 
@@ -93,11 +93,11 @@ object DrivePool {
 			return get as T
 		}
 
-		val file = File(baseDirectory, "$id.dat")
+		val file = baseDirectory.resolve("$id.dat")
 
 		if (file.exists()) {
 			try {
-				val factorize = deserializer(NbtIo.readCompressed(file))
+				val factorize = deserializer(NbtIo.readCompressed(file, NbtAccounter.unlimitedHeap()), MINECRAFT_SERVER.registryAccess())
 				pool[id] = WeakDriveReference(factorize)
 				return factorize
 			} catch (error: Throwable) {
@@ -119,16 +119,16 @@ object DrivePool {
 	}
 
 	fun onWorldSave(event: LevelEvent.Save) {
-		writeBacklog()
+		writeBacklog(event.level.server!!.registryAccess())
 	}
 
-	private fun writeBacklog() {
+	private fun writeBacklog(registry: Provider) {
 		var needsSync = false
 		val removeKeys = ArrayList<UUID>()
 
 		for ((key, value) in pool) {
 			try {
-				val tag = value.sync()
+				val tag = value.sync(registry)
 
 				if (tag != null) {
 					LOGGER.debug("Serializing OTM Drive {}", key)
@@ -147,11 +147,11 @@ object DrivePool {
 		}
 
 		if (needsSync) {
-			Util.backgroundExecutor().execute(::sync)
+			Util.backgroundExecutor().execute { sync(registry) }
 		}
 	}
 
-	private fun sync() {
+	private fun sync(registry: HolderLookup.Provider) {
 		while (true) {
 			val next = backlog.poll() ?: return
 			val (uuid, tag) = next
@@ -159,11 +159,11 @@ object DrivePool {
 			try {
 				LOGGER.debug("Syncing OTM Drive {}", uuid)
 
-				val oldFile = File(baseDirectory, "$uuid.dat_old")
-				val newFile = File(baseDirectory, "$uuid.dat")
+				val oldFile = baseDirectory.resolve("$uuid.dat_old")
+				val newFile = baseDirectory.resolve("$uuid.dat")
 
-				if (oldFile.exists()) check(oldFile.delete()) { "Unable to delete old dat file" }
-				if (newFile.exists()) check(newFile.renameTo(oldFile)) { "Unable to move old dat file" }
+				if (oldFile.exists()) Files.delete(oldFile)
+				if (newFile.exists()) Files.move(newFile, oldFile)
 
 				NbtIo.writeCompressed(tag, newFile)
 			} catch (error: Throwable) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt
index a5fb59159..f38073b4c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt
@@ -1,5 +1,6 @@
 package ru.dbotthepony.mc.otm.capability.drive
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
@@ -24,18 +25,18 @@ class ItemMatteryDrive : AbstractMatteryDrive<ItemStorageStack>, IItemMatteryDri
 		return insertStack(ItemStorageStack(item), simulate).toItemStack()
 	}
 
-	override fun serializeStack(item: ItemStorageStack): CompoundTag {
+	override fun serializeStack(item: ItemStorageStack, registry: HolderLookup.Provider): CompoundTag {
 		val tag = CompoundTag()
-		tag["item"] = item.toItemStack(1).serializeNBT()
+		tag["item"] = item.toItemStack(1).save(registry)
 		tag["count"] = item.count.serializeNBT()
 		return tag
 	}
 
-	override fun deserializeStack(tag: CompoundTag): ItemStorageStack? {
+	override fun deserializeStack(tag: CompoundTag, registry: HolderLookup.Provider): ItemStorageStack? {
 		if ("item" in tag && "count" in tag) {
 			val item = tag["item"] as? CompoundTag ?: return null
 			val count = BigInteger(tag["count"])
-			return ItemStorageStack.unsafe(ItemStack.of(item), count)
+			return ItemStorageStack.unsafe(ItemStack.parseOptional(registry, item), count)
 		}
 
 		return null
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt
index f43df3712..9bb35f2f7 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt
@@ -1,12 +1,13 @@
 package ru.dbotthepony.mc.otm.capability.energy
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.ticks.ContainerSingleItem
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.util.INBTSerializable
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.common.util.INBTSerializable
 import ru.dbotthepony.kommons.io.DelegateSyncher
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
@@ -15,8 +16,6 @@ import ru.dbotthepony.mc.otm.capability.extractEnergy
 import ru.dbotthepony.mc.otm.capability.receiveEnergy
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.math.getDecimal
-import ru.dbotthepony.mc.otm.core.nbt.getItemStack
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.nbt.set
 import ru.dbotthepony.mc.otm.core.util.decimal
 import ru.dbotthepony.mc.otm.core.util.observedItem
@@ -55,6 +54,14 @@ class BatteryBackedEnergyStorage(
 		return item
 	}
 
+	override fun getTheItem(): ItemStack {
+		return item
+	}
+
+	override fun setTheItem(p_304718_: ItemStack) {
+		item = p_304718_
+	}
+
 	override fun removeItem(slot: Int, count: Int): ItemStack {
 		require(slot == 0) { "Invalid slot $slot" }
 		return item.split(count)
@@ -73,24 +80,24 @@ class BatteryBackedEnergyStorage(
 		return true
 	}
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		return CompoundTag().also {
 			it["battery"] = battery.serializeNBT()
 			it["maxBattery"] = maxBattery.serializeNBT()
-			it["item"] = item.serializeNBT()
+			it["item"] = item.saveOptional(registry)
 		}
 	}
 
-	override fun deserializeNBT(tag: CompoundTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, tag: CompoundTag?) {
 		tag ?: return
 		battery = tag.getDecimal("battery")
 		maxBattery = tag.getDecimal("maxBattery")
-		item = tag.getItemStack("item")
+		item = ItemStack.parseOptional(registry, tag.getCompound("item"))
 	}
 
 	fun tick() {
 		if (!item.isEmpty && battery < maxBattery) {
-			item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
+			item.getCapability(Capabilities.EnergyStorage.ITEM)?.let {
 				battery += it.extractEnergy(maxBattery - battery, false)
 			}
 		}
@@ -102,7 +109,7 @@ class BatteryBackedEnergyStorage(
 		var drained = Decimal.ZERO
 
 		if (!item.isEmpty) {
-			item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
+			item.getCapability(Capabilities.EnergyStorage.ITEM)?.let {
 				val extracted = it.extractEnergy(howMuch, simulate)
 				drained += extracted
 				howMuch -= extracted
@@ -140,7 +147,7 @@ class BatteryBackedEnergyStorage(
 		var received = Decimal.ZERO
 
 		if (!item.isEmpty) {
-			item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
+			item.getCapability(Capabilities.EnergyStorage.ITEM)?.let {
 				val extracted = it.receiveEnergy(howMuch, simulate)
 				received += extracted
 				howMuch -= extracted
@@ -166,7 +173,7 @@ class BatteryBackedEnergyStorage(
 	override var batteryLevel: Decimal
 		get() {
 			if (!item.isEmpty) {
-				item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
+				item.getCapability(Capabilities.EnergyStorage.ITEM)?.let {
 					if (it is IMatteryEnergyStorage) {
 						return battery + it.batteryLevel
 					} else {
@@ -185,7 +192,7 @@ class BatteryBackedEnergyStorage(
 	override var maxBatteryLevel: Decimal
 		get() {
 			if (item != ItemStack.EMPTY) {
-				item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
+				item.getCapability(Capabilities.EnergyStorage.ITEM)?.let {
 					if (it is IMatteryEnergyStorage) {
 						return maxBattery + it.maxBatteryLevel
 					} else {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt
index a798bb3f6..ded418754 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt
@@ -3,9 +3,9 @@
 
 package ru.dbotthepony.mc.otm.capability.energy
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
-import net.minecraftforge.common.util.INBTSerializable
-import net.minecraftforge.common.util.LazyOptional
+import net.neoforged.neoforge.common.util.INBTSerializable
 import ru.dbotthepony.mc.otm.capability.FlowDirection
 import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
 import ru.dbotthepony.mc.otm.config.VerboseEnergyBalanceValues
@@ -105,7 +105,7 @@ open class BlockEnergyStorageImpl(
 		return diff
 	}
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		val tag = CompoundTag()
 		tag[ENERGY_STORED_KEY] = batteryLevel.serializeNBT()
 
@@ -116,7 +116,7 @@ open class BlockEnergyStorageImpl(
 		return tag
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
 		if (nbt == null) return
 		batteryLevel = nbt.mapPresent(ENERGY_STORED_KEY, Decimal.Companion::deserializeNBT) ?: Decimal.ZERO
 		maxBatteryLevelStorage = nbt.mapPresent(ENERGY_STORED_MAX_KEY, Decimal.Companion::deserializeNBT)
@@ -124,17 +124,6 @@ open class BlockEnergyStorageImpl(
 		maxOutputStorage = nbt.mapPresent(MAX_OUTPUT_KEY, Decimal.Companion::deserializeNBT)
 	}
 
-	var resolver: LazyOptional<IMatteryEnergyStorage> = LazyOptional.of { this }
-		private set
-
-	fun invalidate() {
-		resolver.invalidate()
-	}
-
-	fun revive() {
-		resolver = LazyOptional.of { this }
-	}
-
 	companion object {
 		val DEFAULT_MAX_IO = Decimal(400)
 		val DEFAULT_MAX_CAPACITY = Decimal(40_000)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IEnergyStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IEnergyStorageImpl.kt
index 9cbf683af..28fa9e8cc 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IEnergyStorageImpl.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IEnergyStorageImpl.kt
@@ -2,7 +2,7 @@ package ru.dbotthepony.mc.otm.capability.energy
 
 import net.minecraft.ChatFormatting
 import net.minecraft.network.chat.Component
-import net.minecraftforge.energy.IEnergyStorage
+import net.neoforged.neoforge.energy.IEnergyStorage
 import ru.dbotthepony.mc.otm.capability.FlowDirection
 import ru.dbotthepony.mc.otm.client.ShiftPressedCond
 import ru.dbotthepony.mc.otm.core.math.Decimal
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt
index 4dcf2b376..ce8a73ea1 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt
@@ -1,6 +1,6 @@
 package ru.dbotthepony.mc.otm.capability.energy
 
-import net.minecraftforge.energy.IEnergyStorage
+import net.neoforged.neoforge.energy.IEnergyStorage
 import ru.dbotthepony.mc.otm.capability.FlowDirection
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.kommons.math.RGBAColor
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ItemEnergyStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ItemEnergyStorageImpl.kt
index 44268e9ab..111bbf8d4 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ItemEnergyStorageImpl.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ItemEnergyStorageImpl.kt
@@ -1,39 +1,16 @@
 package ru.dbotthepony.mc.otm.capability.energy
 
-import net.minecraft.core.Direction
-import net.minecraft.network.chat.Component
 import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.TooltipFlag
-import net.minecraft.world.level.Level
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.common.util.LazyOptional
 import ru.dbotthepony.mc.otm.capability.FlowDirection
-import ru.dbotthepony.mc.otm.capability.MatteryCapability
-import ru.dbotthepony.mc.otm.capability.energy
-import ru.dbotthepony.mc.otm.core.TooltipList
 import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.mc.otm.core.nbt.map
-import ru.dbotthepony.mc.otm.core.nbt.set
-import ru.dbotthepony.mc.otm.core.tagNotNull
-
-abstract class ItemEnergyStorageImpl(val itemStack: ItemStack) : IMatteryEnergyStorage, ICapabilityProvider, IEnergyStorageImpl {
-	private val resolver = LazyOptional.of { this }
-
-	override fun <T : Any> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
-		if (cap === ForgeCapabilities.ENERGY || cap === MatteryCapability.ENERGY) {
-			return resolver.cast()
-		}
-
-		return LazyOptional.empty()
-	}
+import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
 
+abstract class ItemEnergyStorageImpl(val itemStack: ItemStack) : IMatteryEnergyStorage, IEnergyStorageImpl {
 	abstract val initialBatteryLevel: Decimal
 
 	override var batteryLevel: Decimal
-		get() = itemStack.tag?.map(ENERGY_KEY, Decimal.Companion::deserializeNBT) ?: initialBatteryLevel
-		set(value) { itemStack.tagNotNull[ENERGY_KEY] = value.serializeNBT() }
+		get() = itemStack[MDataComponentTypes.BATTERY_LEVEL] ?: initialBatteryLevel
+		set(value) { itemStack[MDataComponentTypes.BATTERY_LEVEL] = value }
 
 	override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
 		if (!howMuch.isPositive || itemStack.count != 1)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt
index ded8ab5a4..7799e9397 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt
@@ -1,7 +1,7 @@
 package ru.dbotthepony.mc.otm.capability.fluid
 
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.capability.FlowDirection
 import ru.dbotthepony.mc.otm.core.isNotEmpty
 
@@ -49,12 +49,12 @@ abstract class AbstractMatteryFluidHandler : IFluidHandler {
 
 		val fluid = fluid
 
-		if (fluid.isEmpty || fluid.isFluidEqual(resource)) {
+		if (fluid.isEmpty || FluidStack.isSameFluidSameComponents(fluid, resource)) {
 			val new = (fluid.amount.toLong() + resource.amount.toLong()).coerceAtMost(capacity.toLong()).toInt()
 			if (new <= fluid.amount) return 0
 
 			if (action.execute()) {
-				this.fluid = FluidStack(resource, new)
+				this.fluid = resource.copyWithAmount(new)
 			}
 
 			return new - fluid.amount
@@ -70,7 +70,7 @@ abstract class AbstractMatteryFluidHandler : IFluidHandler {
 
 		val fluid = fluid
 
-		if (!fluid.isEmpty && fluid.isFluidEqual(resource)) {
+		if (!fluid.isEmpty && FluidStack.isSameFluidSameComponents(fluid, resource)) {
 			return drain(resource.amount, action)
 		} else {
 			return FluidStack.EMPTY
@@ -92,11 +92,11 @@ abstract class AbstractMatteryFluidHandler : IFluidHandler {
 				if (new == 0) {
 					this.fluid = FluidStack.EMPTY
 				} else {
-					this.fluid = FluidStack(fluid, new)
+					this.fluid = fluid.copyWithAmount(new)
 				}
 			}
 
-			return FluidStack(fluid, fluid.amount - new)
+			return fluid.copyWithAmount(fluid.amount - new)
 		}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt
index 0851929d6..f2ac59c5a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt
@@ -1,32 +1,39 @@
 package ru.dbotthepony.mc.otm.capability.fluid
 
+import net.minecraft.core.HolderLookup
+import net.minecraft.core.component.DataComponents
 import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.Tag
 import net.minecraft.world.item.BlockItem
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.util.INBTSerializable
-import net.minecraftforge.fluids.FluidStack
+import net.minecraft.world.item.component.CustomData
+import net.neoforged.neoforge.common.util.INBTSerializable
+import net.neoforged.neoforge.fluids.FluidStack
 import ru.dbotthepony.kommons.util.Delegate
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
+import ru.dbotthepony.mc.otm.Registries
 import ru.dbotthepony.mc.otm.core.nbt.set
-import ru.dbotthepony.mc.otm.core.tagNotNull
 import java.util.function.IntSupplier
 
 /**
  * Fluid handler for blocks
  */
-open class BlockMatteryFluidHandler(private val _capacity: IntSupplier, field: Delegate<FluidStack>) : AbstractMatteryFluidHandler(), INBTSerializable<CompoundTag?> {
+open class BlockMatteryFluidHandler(private val _capacity: IntSupplier, field: Delegate<FluidStack>) : AbstractMatteryFluidHandler(), INBTSerializable<Tag?> {
 	override var fluid by field
 
 	override val capacity: Int
 		get() = _capacity.asInt
 
-	override fun serializeNBT(): CompoundTag {
-		return fluid.writeToNBT(CompoundTag())
+	override fun serializeNBT(registry: HolderLookup.Provider): Tag {
+		return fluid.save(registry)
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag?) {
-		fluid = FluidStack.loadFluidStackFromNBT(nbt)
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: Tag?) {
+		if (nbt !is CompoundTag)
+			fluid = FluidStack.EMPTY
+		else
+			fluid = FluidStack.parseOptional(registry, nbt)
 	}
 
 	/**
@@ -35,39 +42,16 @@ open class BlockMatteryFluidHandler(private val _capacity: IntSupplier, field: D
 	open class Item(itemStack: ItemStack, capacity: IntSupplier, private val nbtName: String) : ItemMatteryFluidHandler(itemStack, capacity) {
 		override var fluid: FluidStack
 			get() {
-				val sub = itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag ?: return FluidStack.EMPTY
-				return FluidStack.loadFluidStackFromNBT(sub[nbtName] as? CompoundTag ?: return FluidStack.EMPTY)
+				val custom = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY)
+				val sub = custom.unsafe[nbtName] as? CompoundTag ?: return FluidStack.EMPTY
+				return FluidStack.parseOptional(Registries, sub)
 			}
 			set(value) {
-				if (value.isEmpty) {
-					val tag = itemStack.tag ?: return
-					val subTag = tag.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag
+				val data = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY)
 
-					if (subTag == null) {
-						if (tag.isEmpty) {
-							itemStack.tag = null
-						}
-					} else {
-						subTag.remove(nbtName)
-
-						if (subTag.isEmpty) {
-							tag.remove(BlockItem.BLOCK_ENTITY_TAG)
-
-							if (tag.isEmpty) {
-								itemStack.tag = null
-							}
-						}
-					}
-				} else {
-					var sub = itemStack.tagNotNull.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag
-
-					if (sub == null) {
-						sub = CompoundTag()
-						itemStack.tagNotNull[BlockItem.BLOCK_ENTITY_TAG] = sub
-					}
-
-					sub[nbtName] = value.writeToNBT(CompoundTag())
-				}
+				itemStack[DataComponents.BLOCK_ENTITY_DATA] = CustomData.of(data.copyTag().also {
+					it[nbtName] = value.saveOptional(Registries)
+				})
 			}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerIterator.kt
index 6e81f7cfb..a89e628ed 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerIterator.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerIterator.kt
@@ -1,8 +1,8 @@
 package ru.dbotthepony.mc.otm.capability.fluid
 
 import it.unimi.dsi.fastutil.objects.ObjectIterators.AbstractIndexBasedIterator
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.container.get
 
 class FluidHandlerIterator(private val handler: IFluidHandler, initialPosition: Int = 0) : AbstractIndexBasedIterator<FluidStack>(0, initialPosition) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerSpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerSpliterator.kt
index 4d7f874d8..9afa4881b 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerSpliterator.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerSpliterator.kt
@@ -2,8 +2,8 @@ package ru.dbotthepony.mc.otm.capability.fluid
 
 import it.unimi.dsi.fastutil.objects.ObjectSpliterator
 import it.unimi.dsi.fastutil.objects.ObjectSpliterators.AbstractIndexBasedSpliterator
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.container.get
 import java.util.Spliterator
 import java.util.stream.Stream
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt
index fcde61b2b..4a9fc3428 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt
@@ -1,43 +1,19 @@
 package ru.dbotthepony.mc.otm.capability.fluid
 
-import net.minecraft.core.Direction
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.world.item.BlockItem
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.common.util.LazyOptional
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
-import net.minecraftforge.fluids.capability.IFluidHandlerItem
-import ru.dbotthepony.mc.otm.core.nbt.set
-import ru.dbotthepony.mc.otm.core.tagNotNull
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.fluids.capability.IFluidHandlerItem
+import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
 import java.util.function.IntSupplier
 
 /**
  * Fluid handler for standalone items
  */
-open class ItemMatteryFluidHandler(val itemStack: ItemStack, private val _capacity: IntSupplier) : AbstractMatteryFluidHandler(), IFluidHandlerItem, ICapabilityProvider {
-	private val resolver = LazyOptional.of { this }
-
+open class ItemMatteryFluidHandler(val itemStack: ItemStack, private val _capacity: IntSupplier) : AbstractMatteryFluidHandler(), IFluidHandlerItem {
 	override var fluid: FluidStack
-		get() { return FluidStack.loadFluidStackFromNBT(itemStack.tag?.get("fluid") as? CompoundTag ?: return FluidStack.EMPTY) }
-		set(value) {
-			if (value.isEmpty) {
-				val tag = itemStack.tag
-
-				if (tag != null) {
-					tag.remove("fluid")
-
-					if (tag.isEmpty) {
-						itemStack.tag = null
-					}
-				}
-			} else {
-				itemStack.tagNotNull["fluid"] = value.writeToNBT(CompoundTag())
-			}
-		}
+		get() = itemStack[MDataComponentTypes.FLUID_STACK] ?: FluidStack.EMPTY
+		set(value) { itemStack[MDataComponentTypes.FLUID_STACK] = value }
 
 	final override val capacity: Int
 		get() = _capacity.asInt
@@ -46,14 +22,6 @@ open class ItemMatteryFluidHandler(val itemStack: ItemStack, private val _capaci
 		return itemStack
 	}
 
-	override fun <T : Any?> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
-		if (cap === ForgeCapabilities.FLUID_HANDLER_ITEM || cap === ForgeCapabilities.FLUID_HANDLER) {
-			return resolver.cast()
-		}
-
-		return LazyOptional.empty()
-	}
-
 	override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int {
 		if (itemStack.count != 1)
 			return 0
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt
index 518d43e3e..83c426d0f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt
@@ -2,7 +2,7 @@ package ru.dbotthepony.mc.otm.capability.item
 
 import com.google.common.collect.ImmutableList
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.items.IItemHandler
+import net.neoforged.neoforge.items.IItemHandler
 import ru.dbotthepony.mc.otm.container.ContainerHandler
 import java.util.stream.Stream
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/EmptyItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/EmptyItemHandler.kt
index 5fb9e1ff3..598bdd942 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/EmptyItemHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/EmptyItemHandler.kt
@@ -1,7 +1,7 @@
 package ru.dbotthepony.mc.otm.capability.item
 
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.items.IItemHandler
+import net.neoforged.neoforge.items.IItemHandler
 
 object EmptyItemHandler : IItemHandler {
 	override fun getSlots(): Int {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/ProxiedItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/ProxiedItemHandler.kt
index 81533ebd0..4d440d3c3 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/ProxiedItemHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/ProxiedItemHandler.kt
@@ -1,7 +1,7 @@
 package ru.dbotthepony.mc.otm.capability.item
 
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.items.IItemHandler
+import net.neoforged.neoforge.items.IItemHandler
 
 class ProxiedItemHandler<T : IItemHandler>(var parent: T? = null) : IItemHandler {
 	override fun getSlots(): Int {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/UnmodifiableItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/UnmodifiableItemHandler.kt
index 7895a417b..3beaf5ed3 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/UnmodifiableItemHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/UnmodifiableItemHandler.kt
@@ -1,7 +1,7 @@
 package ru.dbotthepony.mc.otm.capability.item
 
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.items.IItemHandler
+import net.neoforged.neoforge.items.IItemHandler
 
 class UnmodifiableItemHandler(private val parent: IItemHandler) : IItemHandler by parent {
 	override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt
index 9ef928958..dfaa1e3ec 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt
@@ -1,11 +1,8 @@
 package ru.dbotthepony.mc.otm.capability.matter
 
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.FlowDirection
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.kommons.math.RGBAColor
-import ru.dbotthepony.mc.otm.core.orNull
 import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
 import kotlin.math.roundToInt
 
@@ -125,5 +122,3 @@ fun IMatterStorage.getBarWidth(): Int {
 fun IMatterStorage.getBarColor(): Int {
 	return RGBAColor.LOW_MATTER.linearInterpolation((storedMatter / maxStoredMatter).toFloat(), RGBAColor.FULL_MATTER).toBGR()
 }
-
-val ICapabilityProvider.matter: IMatterStorage? get() = getCapability(MatteryCapability.MATTER).orNull()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IPatternStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IPatternStorage.kt
index 8b3886e1d..70238b168 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IPatternStorage.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IPatternStorage.kt
@@ -1,10 +1,7 @@
 package ru.dbotthepony.mc.otm.capability.matter
 
 import net.minecraft.world.item.Item
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.kommons.math.RGBAColor
-import ru.dbotthepony.mc.otm.core.orNull
 import java.util.*
 import java.util.function.Predicate
 import java.util.stream.Collectors
@@ -75,5 +72,3 @@ fun IPatternStorage.getBarWidth(): Int {
 fun IPatternStorage.getBarColor(): Int {
 	return RGBAColor.LOW_PATTERNS.linearInterpolation((storedPatterns / patternCapacity).toFloat(), RGBAColor.FULL_PATTERNS).toBGR()
 }
-
-val ICapabilityProvider.patterns: IPatternStorage? get() = getCapability(MatteryCapability.PATTERN).orNull()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterStorageImpl.kt
index dbcb66cee..9e361793c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterStorageImpl.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterStorageImpl.kt
@@ -1,7 +1,8 @@
 package ru.dbotthepony.mc.otm.capability.matter
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
-import net.minecraftforge.common.util.INBTSerializable
+import net.neoforged.neoforge.common.util.INBTSerializable
 import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
 import ru.dbotthepony.mc.otm.config.VerboseEnergyBalanceValues
 import ru.dbotthepony.mc.otm.capability.FlowDirection
@@ -91,7 +92,7 @@ open class MatterStorageImpl(
 		return diff
 	}
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		return CompoundTag().also {
 			it[MATTER_STORED_KEY] = storedMatter.serializeNBT()
 			//it["max_storage"] = maxStoredMatter.serializeNBT()
@@ -100,7 +101,7 @@ open class MatterStorageImpl(
 		}
 	}
 
-	override fun deserializeNBT(tag: CompoundTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, tag: CompoundTag?) {
 		if (tag == null) return
 		storedMatter = Decimal.deserializeNBT(tag[MATTER_STORED_KEY])
 		//maxStoredMatter = ImpreciseFraction.deserializeNBT(tag["max_storage"])
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt
index d3ae04379..82315afaf 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt
@@ -2,16 +2,12 @@ package ru.dbotthepony.mc.otm.capability.matter
 
 import com.mojang.serialization.Codec
 import com.mojang.serialization.codecs.RecordCodecBuilder
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.nbt.NbtOps
-import net.minecraft.nbt.Tag
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.network.FriendlyByteBuf
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.registries.ForgeRegistries
 import ru.dbotthepony.mc.otm.core.util.readBinaryJsonWithCodec
 import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec
-import ru.dbotthepony.mc.otm.data.UUIDCodec
 import java.util.*
 
 data class PatternState(
@@ -31,17 +27,7 @@ data class PatternState(
 		buff.writeBinaryJsonWithCodec(CODEC, this)
 	}
 
-	fun serializeNBT(): CompoundTag {
-		return CODEC.encode(this, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get()
-			.map({ it as CompoundTag }, { throw RuntimeException("Failed to serialize PatternState: ${it.message()}") })
-	}
-
 	companion object {
-		fun deserializeNBT(tag: Tag?): PatternState? {
-			tag ?: return null
-			return CODEC.decode(NbtOps.INSTANCE, tag).result().map { it.first }.orElse(null)
-		}
-
 		fun read(buff: FriendlyByteBuf): PatternState {
 			return buff.readBinaryJsonWithCodec(CODEC)
 		}
@@ -50,7 +36,7 @@ data class PatternState(
 			RecordCodecBuilder.create {
 				it.group(
 					UUIDCodec.fieldOf("id").forGetter(PatternState::id),
-					ForgeRegistries.ITEMS.codec.fieldOf("item").forGetter(PatternState::item),
+					BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter(PatternState::item),
 					Codec.doubleRange(0.0, 1.0).fieldOf("researchPercent").forGetter(PatternState::researchPercent)
 				).apply(it, ::PatternState)
 			}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt
index feb080e75..2b8f52a07 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt
@@ -2,16 +2,15 @@ package ru.dbotthepony.mc.otm.capability.matter
 
 import com.mojang.serialization.Codec
 import com.mojang.serialization.codecs.RecordCodecBuilder
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.NbtOps
 import net.minecraft.nbt.Tag
 import net.minecraft.network.FriendlyByteBuf
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.registries.ForgeRegistries
 import ru.dbotthepony.mc.otm.core.util.readBinaryJsonWithCodec
 import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec
-import ru.dbotthepony.mc.otm.data.UUIDCodec
 import java.util.Optional
 import java.util.UUID
 
@@ -48,7 +47,7 @@ data class ReplicationTask(
 	}
 
 	fun serializeNBT(): CompoundTag {
-		return CODEC.encode(this, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get().map({ it as CompoundTag }, { throw RuntimeException("Failed to serialize ReplicationTask: ${it.message()}") })
+		return CODEC.encode(this, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).mapOrElse({ it as CompoundTag }, { throw RuntimeException("Failed to serialize ReplicationTask: ${it.message()}") })
 	}
 
 	fun write(buff: FriendlyByteBuf) {
@@ -69,7 +68,7 @@ data class ReplicationTask(
 				it.group(
 					UUIDCodec.fieldOf("id").forGetter(ReplicationTask::id),
 					UUIDCodec.optionalFieldOf("patternId").forGetter(ReplicationTask::patternId),
-					ForgeRegistries.ITEMS.codec.fieldOf("item").forGetter(ReplicationTask::item),
+					BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter(ReplicationTask::item),
 					Codec.intRange(0, Int.MAX_VALUE).fieldOf("inProgress").forGetter(ReplicationTask::inProgress),
 					Codec.intRange(0, Int.MAX_VALUE).fieldOf("finished").forGetter(ReplicationTask::finished),
 					Codec.intRange(0, Int.MAX_VALUE).fieldOf("required").forGetter(ReplicationTask::required),
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidAbilityKeyMapping.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidAbilityKeyMapping.kt
index 3fd14d90f..0d65f5591 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidAbilityKeyMapping.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidAbilityKeyMapping.kt
@@ -2,10 +2,11 @@ package ru.dbotthepony.mc.otm.client
 
 import com.mojang.blaze3d.platform.InputConstants
 import net.minecraft.client.KeyMapping
-import net.minecraftforge.client.event.RegisterKeyMappingsEvent
-import net.minecraftforge.client.event.RenderGuiEvent
-import net.minecraftforge.client.event.RenderLevelStageEvent
-import net.minecraftforge.client.settings.KeyConflictContext
+import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent
+import net.neoforged.neoforge.client.event.RenderGuiEvent
+import net.neoforged.neoforge.client.event.RenderLevelStageEvent
+import net.neoforged.neoforge.client.settings.KeyConflictContext
+import net.neoforged.neoforge.network.PacketDistributor
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.android.AndroidActiveFeature
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
@@ -14,7 +15,6 @@ import ru.dbotthepony.mc.otm.client.render.Widgets18
 import ru.dbotthepony.mc.otm.client.render.is3DContext
 import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.network.ActivateAndroidFeaturePacket
-import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
 import kotlin.math.roundToInt
 
 object AndroidAbilityKeyMapping : KeyMapping("key.otm.android_ability", KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM.getOrCreate(InputConstants.KEY_V), OverdriveThatMatters.MOD_ID) {
@@ -37,7 +37,7 @@ object AndroidAbilityKeyMapping : KeyMapping("key.otm.android_ability", KeyConfl
 				val feature = feature
 
 				if (feature != null && feature.activate(true) && minecraft.player != null && feature.android.isAndroid) {
-					MatteryPlayerNetworkChannel.sendToServer(ActivateAndroidFeaturePacket(feature.type))
+					PacketDistributor.sendToServer(ActivateAndroidFeaturePacket(feature.type))
 				}
 
 				this.feature = null
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt
index 0ac414113..6deaaeba9 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt
@@ -5,10 +5,11 @@ import com.mojang.blaze3d.systems.RenderSystem
 import it.unimi.dsi.fastutil.objects.Object2FloatArrayMap
 import it.unimi.dsi.fastutil.objects.Object2FloatFunction
 import net.minecraft.client.KeyMapping
-import net.minecraftforge.client.event.InputEvent
-import net.minecraftforge.client.event.RegisterKeyMappingsEvent
-import net.minecraftforge.client.event.RenderGuiEvent
-import net.minecraftforge.client.settings.KeyConflictContext
+import net.neoforged.neoforge.client.event.InputEvent
+import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent
+import net.neoforged.neoforge.client.event.RenderGuiEvent
+import net.neoforged.neoforge.client.settings.KeyConflictContext
+import net.neoforged.neoforge.network.PacketDistributor
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.android.AndroidFeature
 import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature
@@ -21,7 +22,6 @@ import ru.dbotthepony.mc.otm.core.math.angleDifference
 import ru.dbotthepony.mc.otm.core.math.normalizeAngle
 import ru.dbotthepony.mc.otm.core.util.formatTickDuration
 import ru.dbotthepony.mc.otm.milliTimeD
-import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
 import ru.dbotthepony.mc.otm.network.SwitchAndroidFeaturePacket
 import java.util.stream.Collectors
 import kotlin.math.PI
@@ -84,7 +84,7 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon
 						val selectedFeature = selectedFeature
 
 						if (selectedFeature != null) {
-							MatteryPlayerNetworkChannel.sendToServer(SwitchAndroidFeaturePacket(selectedFeature.type, !selectedFeature.isActive))
+							PacketDistributor.sendToServer(SwitchAndroidFeaturePacket(selectedFeature.type, !selectedFeature.isActive))
 							playGuiClickSound()
 						}
 					}
@@ -122,7 +122,7 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon
 				val selectedFeature = selectedFeature
 
 				if (selectedFeature != null) {
-					MatteryPlayerNetworkChannel.sendToServer(SwitchAndroidFeaturePacket(selectedFeature.type, !selectedFeature.isActive))
+					PacketDistributor.sendToServer(SwitchAndroidFeaturePacket(selectedFeature.type, !selectedFeature.isActive))
 				}
 			}
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt
index 66b76fa4a..0621d3e0a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt
@@ -13,14 +13,13 @@ import net.minecraft.world.inventory.Slot
 import net.minecraft.world.item.BlockItem
 import net.minecraft.world.item.Item
 import net.minecraft.world.level.block.Block
-import net.minecraftforge.client.event.MovementInputUpdateEvent
-import net.minecraftforge.client.event.ScreenEvent
-import net.minecraftforge.client.event.ScreenEvent.MouseDragged
-import net.minecraftforge.client.event.ScreenEvent.MouseScrolled
-import net.minecraftforge.event.entity.player.ItemTooltipEvent
+import net.neoforged.neoforge.client.event.MovementInputUpdateEvent
+import net.neoforged.neoforge.client.event.ScreenEvent
+import net.neoforged.neoforge.event.entity.player.ItemTooltipEvent
+import net.neoforged.neoforge.network.PacketDistributor
 import ru.dbotthepony.mc.otm.config.ClientConfig
 import ru.dbotthepony.mc.otm.android.feature.JumpBoostFeature
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
 import ru.dbotthepony.mc.otm.client.render.Widgets18
@@ -36,7 +35,6 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.addAll
 import ru.dbotthepony.mc.otm.core.math.integerDivisionUp
 import ru.dbotthepony.mc.otm.menu.MatteryMenu
-import ru.dbotthepony.mc.otm.network.MenuNetworkChannel
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
 import java.util.WeakHashMap
 
@@ -115,10 +113,10 @@ private fun inventoryLogic(event: ScreenEvent.Init.Post) {
 	}
 }
 
-private class InventoryScrollbarPanel<S : Screen>(screen: S, matteryPlayer: MatteryPlayerCapability) : DiscreteScrollBarPanel<S>(
+private class InventoryScrollbarPanel<S : Screen>(screen: S, matteryPlayer: MatteryPlayer) : DiscreteScrollBarPanel<S>(
 	screen, null, { integerDivisionUp(matteryPlayer.exopackContainer.containerSize, 9) }, { _, _, newScroll ->
 		inventoryScroll = newScroll
-		MenuNetworkChannel.sendToServer(InventoryScrollPacket(newScroll).also { it.play(matteryPlayer.ply) })
+		PacketDistributor.sendToServer(InventoryScrollPacket(newScroll).also { it.play(matteryPlayer.ply) })
 	}, isSlim = true
 )
 
@@ -200,7 +198,7 @@ private fun exosuitInventoryLogic(screen: Screen, addListener: (GuiEventListener
 	scrollbar.scroll = inventoryScroll
 }
 
-fun onMouseDragged(event: MouseDragged.Pre) {
+fun onMouseDragged(event: ScreenEvent.MouseDragged.Pre) {
 	val screen = minecraft.screen as? AbstractContainerScreen<*> ?: return
 
 	if (screen is MatteryScreen<*>)
@@ -216,7 +214,7 @@ fun onMouseDragged(event: MouseDragged.Pre) {
 	}
 }
 
-fun onMouseScrolled(event: MouseScrolled.Pre) {
+fun onMouseScrolled(event: ScreenEvent.MouseScrolled.Pre) {
 	val screen = minecraft.screen as? AbstractContainerScreen<*> ?: return
 
 	if (screen is MatteryScreen<*>)
@@ -224,7 +222,7 @@ fun onMouseScrolled(event: MouseScrolled.Pre) {
 
 	for (widget in screen.renderables) {
 		if (widget is Panel2Widget<*, *>) {
-			if (widget.panel.mouseScrolledChecked(event.mouseX, event.mouseY, event.deltaX)) {
+			if (widget.panel.mouseScrolledChecked(event.mouseX, event.mouseY, event.scrollDeltaX)) {
 				event.isCanceled = true
 				return
 			}
@@ -233,7 +231,7 @@ fun onMouseScrolled(event: MouseScrolled.Pre) {
 				val slot = screen.slotUnderMouse
 
 				if (slot != null && (slot.container == minecraft.player?.inventory && slot.containerSlot in 9 .. 35 || slot.container == minecraft.player?.matteryPlayer?.exopackContainer)) {
-					widget.panel.mouseScrolledInner(event.mouseX, event.mouseY, event.deltaX)
+					widget.panel.mouseScrolledInner(event.mouseX, event.mouseY, event.scrollDeltaX)
 					event.isCanceled = true
 					return
 				}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientTickHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientTickHandler.kt
index 4b456c734..d9bd0339d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientTickHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientTickHandler.kt
@@ -1,8 +1,7 @@
 package ru.dbotthepony.mc.otm.client
 
-import net.minecraftforge.client.event.ClientPlayerNetworkEvent
-import net.minecraftforge.event.TickEvent
-import net.minecraftforge.event.TickEvent.RenderTickEvent
+import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent
+import net.neoforged.neoforge.client.event.ClientTickEvent
 import org.lwjgl.glfw.GLFW
 import ru.dbotthepony.mc.otm.core.util.IConditionalTickable
 import ru.dbotthepony.mc.otm.core.util.ITickable
@@ -107,12 +106,12 @@ fun tickWhileClientPre(condition: () -> Boolean, ticker: () -> Unit) {
 	tickClientPre(IConditionalTickable.wrap(condition, ticker))
 }
 
-fun onClientTick(event: TickEvent.ClientTickEvent) {
-	if (event.phase == TickEvent.Phase.START) {
-		preTickList.tick()
-	} else {
-		postTickList.tick()
-	}
+fun onClientTickPre(event: ClientTickEvent.Pre) {
+	preTickList.tick()
+}
+
+fun onClientTickPost(event: ClientTickEvent.Post) {
+	postTickList.tick()
 }
 
 fun onClientDisconnected(event: ClientPlayerNetworkEvent.LoggingOut) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt
index 5cb58196d..8b0628872 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt
@@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.client
 
 import com.mojang.blaze3d.systems.RenderSystem
 import net.minecraft.client.gui.Font
+import net.minecraft.client.gui.Gui
 import net.minecraft.client.gui.GuiGraphics
 import net.minecraft.client.gui.components.Button
 import net.minecraft.client.gui.screens.DeathScreen
@@ -14,17 +15,17 @@ import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.ShieldItem
-import net.minecraftforge.client.event.RenderGuiEvent
-import net.minecraftforge.client.event.RenderGuiOverlayEvent
-import net.minecraftforge.client.event.ScreenEvent
-import net.minecraftforge.client.gui.overlay.ForgeGui
-import net.minecraftforge.client.gui.overlay.GuiOverlayManager
-import net.minecraftforge.common.ToolActions
+import net.neoforged.neoforge.client.event.RenderGuiEvent
+import net.neoforged.neoforge.client.event.RenderGuiLayerEvent
+import net.neoforged.neoforge.client.event.ScreenEvent
+import net.neoforged.neoforge.client.gui.VanillaGuiLayers
+import net.neoforged.neoforge.common.ItemAbilities
+import net.neoforged.neoforge.common.ItemAbility
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.client.render.*
 import ru.dbotthepony.mc.otm.client.render.sprites.MatteryAtlas
@@ -32,8 +33,8 @@ import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite
 import ru.dbotthepony.mc.otm.config.ClientConfig
 import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.util.formatPower
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.registry.AndroidFeatures
 import java.util.*
 import kotlin.math.PI
@@ -61,8 +62,6 @@ object MatteryGUI {
 	private var originalBedButtonX = -1
 	private var originalBedButtonY = -1
 
-	private var lastState: MatteryPlayerCapability? = null
-
 	private val buttonShaker = Random()
 
 	fun onScreenRender(event: ScreenEvent.Render.Pre) {
@@ -79,16 +78,16 @@ object MatteryGUI {
 			originalBedButtonY = screen.leaveBedButton.y
 		}
 
-		minecraft.player?.getCapability(MatteryCapability.MATTERY_PLAYER)?.ifPresentK {
+		minecraft.player?.matteryPlayer?.let {
 			if (!it.willBecomeAndroid) {
 				screen.leaveBedButton.x = originalBedButtonX
 				screen.leaveBedButton.y = originalBedButtonY
 				originalBedButtonX = -1
 				originalBedButtonY = -1
-				return@ifPresentK
+				return@let
 			}
 
-			val dispersion = (10.0 * Math.max(0, it.ply.sleepTimer - 20) / (MatteryPlayerCapability.SLEEP_TICKS_LIMIT - 20)).toInt()
+			val dispersion = (10.0 * Math.max(0, it.ply.sleepTimer - 20) / (MatteryPlayer.SLEEP_TICKS_LIMIT - 20)).toInt()
 
 			screen.leaveBedButton.x =
 				originalBedButtonX - dispersion / 2 + (buttonShaker.nextDouble() * dispersion).toInt()
@@ -106,24 +105,12 @@ object MatteryGUI {
 		val screen = event.screen
 
 		if (screen is DeathScreen) {
-			minecraft.player?.getCapability(MatteryCapability.MATTERY_PLAYER)?.ifPresentK {
+			minecraft.player?.matteryPlayer?.let {
 				if (it.isAndroid) screen.title = TranslatableComponent("otm.death_reason")
 			}
 		}
 	}
 
-	private val FOOD_LEVEL_ELEMENT by lazy {
-		GuiOverlayManager.findOverlay(ResourceLocation("minecraft", "food_level"))
-	}
-
-	private val AIR_LEVEL_ELEMENT by lazy {
-		GuiOverlayManager.findOverlay(ResourceLocation("minecraft", "air_level"))
-	}
-
-	private val PLAYER_HEALTH_ELEMENT by lazy {
-		GuiOverlayManager.findOverlay(ResourceLocation("minecraft", "player_health"))
-	}
-
 	var iteration = 0
 	var showIterationUntil = 0L
 	var showIterationUntilFade = 0L
@@ -143,7 +130,7 @@ object MatteryGUI {
 
 		val guiGraphics = MGUIGraphics(event.guiGraphics)
 		val stack = guiGraphics.pose
-		val window = event.window
+		val window = minecraft.window
 
 		stack.pushPose()
 
@@ -207,29 +194,19 @@ object MatteryGUI {
 		showIteration(event)
 	}
 
-	private fun renderFoodAndAir(event: RenderGuiOverlayEvent.Pre, gui: ForgeGui) {
+	private fun renderFoodAndAir(event: RenderGuiLayerEvent.Pre, gui: Gui) {
 		val ply: LocalPlayer = minecraft.player ?: return
 
-		if (ply.vehicle is LivingEntity) {
+		if (ply.vehicle is LivingEntity)
 			return
-		}
 
-		var mattery = ply.matteryPlayer
+		val mattery = ply.matteryPlayer
 
-		if (!ply.isAlive && mattery == null) {
-			mattery = lastState
-		} else if (ply.isAlive && mattery != null) {
-			lastState = mattery
-		}
-
-		if (mattery != null && mattery.isAndroid) {
+		if (mattery.isAndroid) {
 			event.isCanceled = true
 
-			if (event.overlay === AIR_LEVEL_ELEMENT) {
+			if (event.name === VanillaGuiLayers.AIR_LEVEL)
 				return
-			}
-
-			if (!gui.shouldDrawSurvivalElements()) return
 
 			var level: Float
 
@@ -242,11 +219,13 @@ object MatteryGUI {
 					level = 1f
 			}
 
-			gui.setupOverlayRenderState(true, false)
+			RenderSystem.enableBlend()
+			RenderSystem.defaultBlendFunc()
+			RenderSystem.disableDepthTest()
 			RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
 
-			val width = event.window.guiScaledWidth
-			val height = event.window.guiScaledHeight
+			val width = minecraft.window.guiScaledWidth
+			val height = minecraft.window.guiScaledHeight
 			val left = width / 2 + 10
 			val top: Int = height - gui.rightHeight
 			gui.rightHeight += 10
@@ -267,6 +246,9 @@ object MatteryGUI {
 
 			val scale = ClientConfig.HUD.BAR_TEXT_SCALE.toFloat()
 			guiGraphics.draw(formattedPower, left + CHARGE_BG.width + 2f + scale, top + CHARGE_BG.height / 2f + scale, font = gui.font, scale = scale, gravity = RenderGravity.CENTER_LEFT, color = RGBAColor.YELLOW, drawOutline = true)
+
+			RenderSystem.disableBlend()
+			RenderSystem.enableDepthTest()
 		}
 	}
 
@@ -294,31 +276,25 @@ object MatteryGUI {
 		return RGBAColor.RED
 	} // можно вынести в конфиг, но для этого нужен селектор цвета
 
-	private fun renderPlayerHealth(event: RenderGuiOverlayEvent.Pre, gui: ForgeGui) {
+	private fun renderPlayerHealth(event: RenderGuiLayerEvent.Pre, gui: Gui) {
 		if (!ClientConfig.HUD.ANDROID_HEALTH_BAR) return
 
 		val ply: LocalPlayer = minecraft.player ?: return
-		var mattery = ply.matteryPlayer
+		val mattery = ply.matteryPlayer
 
-		if (!ply.isAlive && mattery == null) {
-			mattery = lastState
-		} else if (ply.isAlive && mattery != null) {
-			lastState = mattery
-		}
-
-		if (mattery != null && mattery.isAndroid) {
+		if (mattery.isAndroid) {
 			event.isCanceled = true
 
-			if (!gui.shouldDrawSurvivalElements()) return
-
 			val level: Float = (ply.health / ply.maxHealth).coerceIn(0.0f, 1.0f)
 			val levelAbsorb: Float = (ply.absorptionAmount / ply.maxHealth).coerceIn(0.0f, 1.0f)
 
-			gui.setupOverlayRenderState(true, false)
+			RenderSystem.enableBlend()
+			RenderSystem.defaultBlendFunc()
+			RenderSystem.disableDepthTest()
 			RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
 
-			val width = event.window.guiScaledWidth
-			val height = event.window.guiScaledHeight
+			val width = minecraft.window.guiScaledWidth
+			val height = minecraft.window.guiScaledHeight
 			val left = width / 2 - 10 - 81
 			val top: Int = height - gui.leftHeight
 			gui.leftHeight += 10
@@ -346,28 +322,30 @@ object MatteryGUI {
 
 			val scale = ClientConfig.HUD.BAR_TEXT_SCALE.toFloat()
 			guiGraphics.draw(formattedHealth, left - 2f, top + HEALTH_BG.height / 2f + 1f * scale, scale = scale, gravity = RenderGravity.CENTER_RIGHT, color = getHealthColorForPlayer(ply), drawOutline = true)
+
+			RenderSystem.disableBlend()
+			RenderSystem.enableDepthTest()
 		}
 	}
 
-	fun onLayerRenderEvent(event: RenderGuiOverlayEvent.Pre) {
-		val gui = minecraft.gui as? ForgeGui ?: return
-		if (minecraft.options.hideGui || !gui.shouldDrawSurvivalElements()) return
+	fun onLayerRenderEvent(event: RenderGuiLayerEvent.Pre) {
+		val gui = minecraft.gui
 
-		if (event.overlay == FOOD_LEVEL_ELEMENT || event.overlay == AIR_LEVEL_ELEMENT) {
+		if (event.name == VanillaGuiLayers.FOOD_LEVEL || event.name == VanillaGuiLayers.AIR_LEVEL) {
 			renderFoodAndAir(event, gui)
-		} else if (event.overlay == PLAYER_HEALTH_ELEMENT)  {
+		} else if (event.name == VanillaGuiLayers.PLAYER_HEALTH)  {
 			renderPlayerHealth(event, gui)
 		}
 	}
 
 	fun renderShieldCooldownOverlay(graphics: GuiGraphics, font: Font, stack: ItemStack, x: Int, y: Int): Boolean {
 		if (!stack.isEmpty && stack.item is ShieldItem) {
-			if (!stack.canPerformAction(ToolActions.SHIELD_BLOCK)) return false
+			if (!stack.canPerformAction(ItemAbilities.SHIELD_BLOCK)) return false
 
 			val ply = minecraft.player ?: return false
 			if (!ply.isUsingItem || stack != ply.useItem || ply.isBlocking) return false
 
-			val percent = ((stack.item.getUseDuration(stack) - ply.useItemRemainingTicks + minecraft.partialTick) / 5f).coerceIn(0f, 1f)
+			val percent = (((stack.item as ShieldItem).getUseDuration(stack, ply) - ply.useItemRemainingTicks + minecraft.gameRenderer.mainCamera.partialTickTime) / 5f).coerceIn(0f, 1f)
 			RenderSystem.setShaderColor(1f, 1f, 1f, 0.5f)
 			drawArc(graphics.pose(), x + 8f, y + 8f, 8f, 0f, PI / 2.0, PI / 2.0 + PI * 2.0 * percent, alignAtCenter = true)
 			RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GlitchRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GlitchRenderer.kt
index 9a3fcf2cb..14f601f8e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GlitchRenderer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GlitchRenderer.kt
@@ -11,7 +11,7 @@ import net.minecraft.client.renderer.GameRenderer
 import net.minecraft.world.level.levelgen.XoroshiroRandomSource
 import net.minecraft.world.level.material.FogType
 import org.joml.Matrix4f
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.core.math.linearInterpolation
@@ -23,17 +23,17 @@ import kotlin.math.absoluteValue
 object GlitchRenderer {
 	private val random = XoroshiroRandomSource(System.nanoTime(), System.currentTimeMillis())
 
-	var redShiftX = 0.0
+	var redShiftX = 0.0f
 		private set
-	var redShiftY = 0.0
+	var redShiftY = 0.0f
 		private set
-	var greenShiftX = 0.0
+	var greenShiftX = 0.0f
 		private set
-	var greenShiftY = 0.0
+	var greenShiftY = 0.0f
 		private set
-	var blueShiftX = 0.0
+	var blueShiftX = 0.0f
 		private set
-	var blueShiftY = 0.0
+	var blueShiftY = 0.0f
 		private set
 
 	var lastGlitch = System.nanoTime()
@@ -46,79 +46,76 @@ object GlitchRenderer {
 		MainTarget(minecraft.window.width, minecraft.window.height)
 	}
 
-	private fun upload(builder: BufferBuilder, offsetX: Double, offsetY: Double, u0: Float, v0: Float, u1: Float, v1: Float) {
-		builder.vertex(offsetX - 1.0, offsetY + 1.0, 0.0).uv(u0, v1).endVertex()
-		builder.vertex(offsetX + 1.0, offsetY + 1.0, 0.0).uv(u1, v1).endVertex()
-		builder.vertex(offsetX + 1.0, offsetY - 1.0, 0.0).uv(u1, v0).endVertex()
-		builder.vertex(offsetX - 1.0, offsetY - 1.0, 0.0).uv(u0, v0).endVertex()
+	private fun upload(builder: BufferBuilder, offsetX: Float, offsetY: Float, u0: Float, v0: Float, u1: Float, v1: Float) {
+		builder.vertex(offsetX - 1.0f, offsetY + 1.0f, 0.0f).uv(u0, v1)
+		builder.vertex(offsetX + 1.0f, offsetY + 1.0f, 0.0f).uv(u1, v1)
+		builder.vertex(offsetX + 1.0f, offsetY - 1.0f, 0.0f).uv(u1, v0)
+		builder.vertex(offsetX - 1.0f, offsetY - 1.0f, 0.0f).uv(u0, v0)
 	}
 
-	private fun uploadEmpty(builder: BufferBuilder, offsetX: Double, offsetY: Double) {
-		builder.vertex(offsetX - 1.0, offsetY + 1.0, 0.0).endVertex()
-		builder.vertex(offsetX + 1.0, offsetY + 1.0, 0.0).endVertex()
-		builder.vertex(offsetX + 1.0, offsetY - 1.0, 0.0).endVertex()
-		builder.vertex(offsetX - 1.0, offsetY - 1.0, 0.0).endVertex()
+	private fun uploadEmpty(builder: BufferBuilder, offsetX: Float, offsetY: Float) {
+		builder.vertex(offsetX - 1.0f, offsetY + 1.0f, 0.0f)
+		builder.vertex(offsetX + 1.0f, offsetY + 1.0f, 0.0f)
+		builder.vertex(offsetX + 1.0f, offsetY - 1.0f, 0.0f)
+		builder.vertex(offsetX - 1.0f, offsetY - 1.0f, 0.0f)
 	}
 
-	private fun uploadLine(builder: BufferBuilder, offsetX: Double, offsetY: Double, height: Double, u0: Float, v0: Float, u1: Float, v1: Float) {
-		builder.vertex(offsetX - 1.0, offsetY - height, 0.0).uv(u0, v1).endVertex()
-		builder.vertex(offsetX + 1.0, offsetY - height, 0.0).uv(u1, v1).endVertex()
-		builder.vertex(offsetX + 1.0, offsetY, 0.0).uv(u1, v0).endVertex()
-		builder.vertex(offsetX - 1.0, offsetY, 0.0).uv(u0, v0).endVertex()
+	private fun uploadLine(builder: BufferBuilder, offsetX: Float, offsetY: Float, height: Float, u0: Float, v0: Float, u1: Float, v1: Float) {
+		builder.vertex(offsetX - 1.0f, offsetY - height, 0.0f).uv(u0, v1)
+		builder.vertex(offsetX + 1.0f, offsetY - height, 0.0f).uv(u1, v1)
+		builder.vertex(offsetX + 1.0f, offsetY, 0.0f).uv(u1, v0)
+		builder.vertex(offsetX - 1.0f, offsetY, 0.0f).uv(u0, v0)
 	}
 
-	private inline fun makeMirrors(handler: (x: Double, y: Double, u0: Float, v0: Float, u1: Float, v1: Float) -> Unit) {
-		handler(0.0 - 2f, 0.0, 1f, 0f, 0f, 1f)
-		handler(0.0 + 2f, 0.0, 1f, 0f, 0f, 1f)
+	private inline fun makeMirrors(handler: (x: Float, y: Float, u0: Float, v0: Float, u1: Float, v1: Float) -> Unit) {
+		handler(0.0f - 2f, 0.0f, 1f, 0f, 0f, 1f)
+		handler(0.0f + 2f, 0.0f, 1f, 0f, 0f, 1f)
 
-		handler(0.0, 2.0, 0f, 1f, 1f, 0f)
-		handler(0.0, -2.0, 0f, 1f, 1f, 0f)
+		handler(0.0f, 2.0f, 0f, 1f, 1f, 0f)
+		handler(0.0f, -2.0f, 0f, 1f, 1f, 0f)
 
-		handler(0.0 - 2f, -2.0, 1f, 1f, 0f, 0f)
-		handler(0.0 + 2f, -2.0, 1f, 1f, 0f, 0f)
+		handler(0.0f - 2f, -2.0f, 1f, 1f, 0f, 0f)
+		handler(0.0f + 2f, -2.0f, 1f, 1f, 0f, 0f)
 
-		handler(0.0 - 2f, +2.0, 1f, 1f, 0f, 0f)
-		handler(0.0 + 2f, +2.0, 1f, 1f, 0f, 0f)
+		handler(0.0f - 2f, +2.0f, 1f, 1f, 0f, 0f)
+		handler(0.0f + 2f, +2.0f, 1f, 1f, 0f, 0f)
 	}
 
-	private fun draw(offsetX: Double, offsetY: Double) {
-		val builder = tesselator.builder
-
-		builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
+	private fun draw(offsetX: Float, offsetY: Float) {
+		val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
 
 		upload(builder, offsetX, offsetY, 0f, 0f, 1f, 1f)
 
-		if (offsetX != 0.0 || offsetY != 0.0) {
-			makeMirrors { x: Double, y: Double, u0: Float, v0: Float, u1: Float, v1: Float ->
+		if (offsetX != 0.0f || offsetY != 0.0f) {
+			makeMirrors { x: Float, y: Float, u0: Float, v0: Float, u1: Float, v1: Float ->
 				upload(builder, offsetX + x, offsetY + y, u0, v0, u1, v1)
 			}
 		}
 
-		BufferUploader.drawWithShader(builder.end())
+		BufferUploader.drawWithShader(builder.buildOrThrow())
 	}
 
-	private fun pixel2ViewX(value: Double) = linearInterpolation(value / glitchBuffer.width, 1.0, -1.0)
-	private fun pixel2ViewY(value: Double) = linearInterpolation(value / glitchBuffer.height, 1.0, -1.0)
-	private fun pixel2TextureX(value: Double) = (value / glitchBuffer.width).toFloat()
-	private fun pixel2TextureY(value: Double) = 1f - (value / glitchBuffer.height).toFloat()
+	private fun pixel2ViewX(value: Float) = linearInterpolation(value / glitchBuffer.width, 1.0f, -1.0f)
+	private fun pixel2ViewY(value: Float) = linearInterpolation(value / glitchBuffer.height, 1.0f, -1.0f)
+	private fun pixel2TextureX(value: Float) = (value / glitchBuffer.width).toFloat()
+	private fun pixel2TextureY(value: Float) = 1f - (value / glitchBuffer.height).toFloat()
 
-	private fun drawVHSLineGap(y: Double, height: Double) {
-		val builder = tesselator.builder
+	private fun drawVHSLineGap(y: Float, height: Float) {
+		val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
 
 		val v = pixel2TextureY(y)
 
-		builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
-		uploadLine(builder, 0.0, pixel2ViewY(y), (height / glitchBuffer.height) * 2.0, 0f, v, 1f, v)
-		BufferUploader.drawWithShader(builder.end())
+		uploadLine(builder, 0.0f, pixel2ViewY(y), (height / glitchBuffer.height) * 2.0f, 0f, v, 1f, v)
+		BufferUploader.drawWithShader(builder.buildOrThrow())
 	}
 
 	private fun makeGlitch() {
-		redShiftX = random.nextDouble() * 0.05 - 0.025
-		redShiftY = random.nextDouble() * 0.05 - 0.025
-		greenShiftX = random.nextDouble() * 0.05 - 0.025
-		greenShiftY = random.nextDouble() * 0.05 - 0.025
-		blueShiftX = random.nextDouble() * 0.05 - 0.025
-		blueShiftY = random.nextDouble() * 0.05 - 0.025
+		redShiftX = random.nextFloat() * 0.05f - 0.025f
+		redShiftY = random.nextFloat() * 0.05f - 0.025f
+		greenShiftX = random.nextFloat() * 0.05f - 0.025f
+		greenShiftY = random.nextFloat() * 0.05f - 0.025f
+		blueShiftX = random.nextFloat() * 0.05f - 0.025f
+		blueShiftY = random.nextFloat() * 0.05f - 0.025f
 		lastGlitch = System.nanoTime()
 		nextGlitch = random.nextIntBetweenInclusive(75_000_000, 400_000_000).toLong()
 	}
@@ -135,7 +132,7 @@ object GlitchRenderer {
 		}
 	}
 
-	private var lastAndroid: WeakReference<MatteryPlayerCapability>? = null
+	private var lastAndroid: WeakReference<MatteryPlayer>? = null
 
 	@JvmStatic
 	fun render() {
@@ -151,8 +148,8 @@ object GlitchRenderer {
 		RenderSystem.setProjectionMatrix(Matrix4f(), VertexSorting.ORTHOGRAPHIC_Z)
 
 		RenderSystem.getModelViewStack().also {
-			it.pushPose()
-			it.setIdentity()
+			it.pushMatrix()
+			it.identity()
 		}
 
 		RenderSystem.applyModelViewMatrix()
@@ -186,7 +183,7 @@ object GlitchRenderer {
 			RenderSystem.setShader(GameRenderer::getPositionTexShader)
 
 			RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
-			draw(0.0, 0.0)
+			draw(0.0f, 0.0f)
 			minecraft.mainRenderTarget.bindWrite(true)
 
 			RenderSystem.setShader(GameRenderer::getPositionTexShader)
@@ -194,7 +191,7 @@ object GlitchRenderer {
 			RenderSystem.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO)
 			RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
 			RenderSystem.setShaderTexture(0, glitchBuffer.colorTextureId)
-			draw(0.0, 0.0)
+			draw(0.0f, 0.0f)
 		}
 
 		glitchBuffer.bindWrite(true)
@@ -222,17 +219,17 @@ object GlitchRenderer {
 		RenderSystem.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO)
 		RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
 		RenderSystem.setShaderTexture(0, glitchBuffer.colorTextureId)
-		draw(0.0, 0.0)
+		draw(0.0f, 0.0f)
 
 		// return to our frame buffer (post process stage)
 		glitchBuffer.bindWrite(true)
 		RenderSystem.setShaderTexture(0, minecraft.mainRenderTarget.colorTextureId)
 
 		// color perception errors (eye-camera glitch)
-		drawVHSLineGap((milliTime % glitchBuffer.height).toDouble(), glitchBuffer.height * 0.025)
-		drawVHSLineGap(((milliTime + glitchBuffer.height / 2) % glitchBuffer.height).toDouble(), glitchBuffer.height * 0.075)
-		drawVHSLineGap(((milliTime + glitchBuffer.height / 3) % glitchBuffer.height).toDouble(), glitchBuffer.height * 0.04)
-		drawVHSLineGap(((-milliTime - glitchBuffer.height / 3) % glitchBuffer.height).toDouble().absoluteValue, glitchBuffer.height * 0.07)
+		drawVHSLineGap((milliTime % glitchBuffer.height).toFloat(), glitchBuffer.height * 0.025f)
+		drawVHSLineGap(((milliTime + glitchBuffer.height / 2) % glitchBuffer.height).toFloat(), glitchBuffer.height * 0.075f)
+		drawVHSLineGap(((milliTime + glitchBuffer.height / 3) % glitchBuffer.height).toFloat(), glitchBuffer.height * 0.04f)
+		drawVHSLineGap(((-milliTime - glitchBuffer.height / 3) % glitchBuffer.height).toFloat().absoluteValue, glitchBuffer.height * 0.07f)
 
 		// upload final result to main frame buffer
 		minecraft.mainRenderTarget.bindWrite(true)
@@ -242,10 +239,10 @@ object GlitchRenderer {
 		RenderSystem.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO)
 		RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
 		RenderSystem.setShaderTexture(0, glitchBuffer.colorTextureId)
-		draw(0.0, 0.0)
+		draw(0.0f, 0.0f)
 
 		RenderSystem.setProjectionMatrix(projection, VertexSorting.DISTANCE_TO_ORIGIN)
-		RenderSystem.getModelViewStack().popPose()
+		RenderSystem.getModelViewStack().popMatrix()
 		RenderSystem.applyModelViewMatrix()
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderExtensions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderExtensions.kt
index 98d6442e5..1c7ad7433 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderExtensions.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderExtensions.kt
@@ -4,6 +4,7 @@ import com.mojang.blaze3d.vertex.PoseStack
 import com.mojang.blaze3d.vertex.Tesselator
 import com.mojang.blaze3d.vertex.VertexConsumer
 import net.minecraft.core.Vec3i
+import org.joml.Matrix3f
 import org.joml.Matrix4f
 import org.joml.Vector3f
 import ru.dbotthepony.kommons.math.RGBAColor
@@ -15,15 +16,29 @@ import ru.dbotthepony.mc.otm.core.math.translation
 
 val tesselator: Tesselator get() = Tesselator.getInstance()
 
-fun VertexConsumer.normal(vector: Vector): VertexConsumer = normal(vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat())
+// what meth have you been smoking, mojang, with new names
+fun VertexConsumer.vertex(matrix4f: Matrix4f, x: Float, y: Float, z: Float): VertexConsumer = addVertex(matrix4f, x, y, z)
+fun VertexConsumer.vertex(x: Float, y: Float, z: Float): VertexConsumer = addVertex(x, y, z)
+fun VertexConsumer.uv(u: Float, v: Float): VertexConsumer = setUv(u, v)
+fun VertexConsumer.overlayCoords(coords: Int): VertexConsumer = setOverlay(coords)
+fun VertexConsumer.light(coords: Int): VertexConsumer = setLight(coords)
+
+fun VertexConsumer.normal(vector: Vector): VertexConsumer = setNormal(vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat())
+fun VertexConsumer.normal(pose: PoseStack.Pose, vector: Vector): VertexConsumer = setNormal(pose, vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat())
+fun VertexConsumer.normal(x: Float, y: Float, z: Float): VertexConsumer = setNormal(x, y, z)
+fun VertexConsumer.normal(pose: PoseStack.Pose, x: Float, y: Float, z: Float): VertexConsumer = setNormal(pose, x, y, z)
 fun VertexConsumer.vertex(matrix4f: Matrix4f, vector: Vector): VertexConsumer = vertex(matrix4f, vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat())
 fun VertexConsumer.color(color: RGBAColor?): VertexConsumer {
 	if (color != null)
-		color(color.redInt, color.greenInt, color.blueInt, color.alphaInt)
+		setColor(color.redInt, color.greenInt, color.blueInt, color.alphaInt)
 
 	return this
 }
 
+fun VertexConsumer.color(r: Float, g: Float, b: Float, a: Float): VertexConsumer {
+	return setColor(r, g, b, a)
+}
+
 fun PoseStack.translate(vector: Vector) = translate(vector.x, vector.y, vector.z)
 fun PoseStack.translate(vector: Vec3i) = translate(vector.x.toDouble(), vector.y.toDouble(), vector.z.toDouble())
 fun PoseStack.translate(vector: Vector3f) = translate(vector.x(), vector.y(), vector.z())
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt
index f5f3b0a83..b2808cb34 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt
@@ -17,6 +17,7 @@ import org.lwjgl.opengl.GL11.GL_LESS
 import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.client.minecraft
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import java.util.*
 import kotlin.collections.ArrayDeque
 import kotlin.math.PI
@@ -70,26 +71,25 @@ fun renderRect(
 	if (!is3DContext)
 		RenderSystem.depthFunc(GL_ALWAYS)
 
-	val tess = tesselator
-	val builder = tess.builder
-
 	if (color.isWhite) {
-		builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
+		val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
 
-		builder.vertex(matrix, x, y + height, z).endVertex()
-		builder.vertex(matrix, x + width, y + height, z).endVertex()
-		builder.vertex(matrix, x + width, y, z).endVertex()
-		builder.vertex(matrix, x, y, z).endVertex()
+		builder.vertex(matrix, x, y + height, z)
+		builder.vertex(matrix, x + width, y + height, z)
+		builder.vertex(matrix, x + width, y, z)
+		builder.vertex(matrix, x, y, z)
+
+		BufferUploader.drawWithShader(builder.buildOrThrow())
 	} else {
-		builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
+		val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
 
-		builder.vertex(matrix, x, y + height, z).color(color).endVertex()
-		builder.vertex(matrix, x + width, y + height, z).color(color).endVertex()
-		builder.vertex(matrix, x + width, y, z).color(color).endVertex()
-		builder.vertex(matrix, x, y, z).color(color).endVertex()
+		builder.vertex(matrix, x, y + height, z).color(color)
+		builder.vertex(matrix, x + width, y + height, z).color(color)
+		builder.vertex(matrix, x + width, y, z).color(color)
+		builder.vertex(matrix, x, y, z).color(color)
+
+		BufferUploader.drawWithShader(builder.buildOrThrow())
 	}
-
-	tess.end()
 }
 
 @Suppress("NAME_SHADOWING")
@@ -110,10 +110,7 @@ fun renderCheckerboard(
 	if (!is3DContext)
 		RenderSystem.depthFunc(GL_ALWAYS)
 
-	val tess = tesselator
-	val builder = tess.builder
-
-	builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
+	val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
 
 	for (i in 0 ..< width.toInt()) {
 		val x = x + i.toFloat()
@@ -122,15 +119,15 @@ fun renderCheckerboard(
 			val y = y + j.toFloat()
 
 			if ((i + j) % 2 == 0) {
-				builder.vertex(matrix, x, y + 1f, z).color(color).endVertex()
-				builder.vertex(matrix, x + 1f, y + 1f, z).color(color).endVertex()
-				builder.vertex(matrix, x + 1f, y, z).color(color).endVertex()
-				builder.vertex(matrix, x, y, z).color(color).endVertex()
+				builder.vertex(matrix, x, y + 1f, z).color(color)
+				builder.vertex(matrix, x + 1f, y + 1f, z).color(color)
+				builder.vertex(matrix, x + 1f, y, z).color(color)
+				builder.vertex(matrix, x, y, z).color(color)
 			}
 		}
 	}
 
-	tess.end()
+	BufferUploader.drawWithShader(builder.buildOrThrow())
 }
 
 fun renderTexturedRect(
@@ -164,25 +161,25 @@ fun renderTexturedRect(
 	if (!is3DContext)
 		RenderSystem.depthFunc(GL_ALWAYS)
 
-	val builder = tesselator.builder
-
 	if (color.isWhite) {
-		builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
+		val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
 
-		builder.vertex(matrix, x, y + height, z).uv(u0, v1).endVertex()
-		builder.vertex(matrix, x + width, y + height, z).uv(u1, v1).endVertex()
-		builder.vertex(matrix, x + width, y, z).uv(u1, v0).endVertex()
-		builder.vertex(matrix, x, y, z).uv(u0, v0).endVertex()
+		builder.vertex(matrix, x, y + height, z).uv(u0, v1)
+		builder.vertex(matrix, x + width, y + height, z).uv(u1, v1)
+		builder.vertex(matrix, x + width, y, z).uv(u1, v0)
+		builder.vertex(matrix, x, y, z).uv(u0, v0)
+
+		BufferUploader.drawWithShader(builder.buildOrThrow())
 	} else {
-		builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR)
+		val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR)
 
-		builder.vertex(matrix, x, y + height, z).uv(u0, v1).color(color).endVertex()
-		builder.vertex(matrix, x + width, y + height, z).uv(u1, v1).color(color).endVertex()
-		builder.vertex(matrix, x + width, y, z).uv(u1, v0).color(color).endVertex()
-		builder.vertex(matrix, x, y, z).uv(u0, v0).color(color).endVertex()
+		builder.vertex(matrix, x, y + height, z).uv(u0, v1).color(color)
+		builder.vertex(matrix, x + width, y + height, z).uv(u1, v1).color(color)
+		builder.vertex(matrix, x + width, y, z).uv(u1, v0).color(color)
+		builder.vertex(matrix, x, y, z).uv(u0, v0).color(color)
+
+		BufferUploader.drawWithShader(builder.buildOrThrow())
 	}
-
-	BufferUploader.drawWithShader(builder.end())
 }
 
 fun renderColoredSphere(pose: PoseStack, radius: Float, color: RGBAColor = RGBAColor.WHITE) {
@@ -190,8 +187,7 @@ fun renderColoredSphere(pose: PoseStack, radius: Float, color: RGBAColor = RGBAC
 	RenderSystem.enableBlend()
 	RenderSystem.defaultBlendFunc()
 
-	val builder = tesselator.builder
-	builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
+	val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
 
 	val turnStep = Math.PI * (1 / fragments.toDouble()) * 2
 	val stripeStep = Math.PI * (1 / fragments.toDouble())
@@ -212,23 +208,19 @@ fun renderColoredSphere(pose: PoseStack, radius: Float, color: RGBAColor = RGBAC
 
 			builder.vertex(pose.last().pose(), xPre * cos(tiltPost).toFloat(), yPost, zPre * cos(tiltPost).toFloat())
 				.color(color)
-				.endVertex()
 
 			builder.vertex(pose.last().pose(), xPost * cos(tiltPost).toFloat(), yPost, zPost * cos(tiltPost).toFloat())
 				.color(color)
-				.endVertex()
 
 			builder.vertex(pose.last().pose(), xPost * cos(tiltPre).toFloat(), yPre, zPost * cos(tiltPre).toFloat())
 				.color(color)
-				.endVertex()
 
 			builder.vertex(pose.last().pose(), xPre * cos(tiltPre).toFloat(), yPre, zPre * cos(tiltPre).toFloat())
 				.color(color)
-				.endVertex()
 		}
 	}
 
-	BufferUploader.drawWithShader(builder.end())
+	BufferUploader.drawWithShader(builder.buildOrThrow())
 }
 
 fun drawLine(
@@ -248,10 +240,7 @@ fun drawLine(
 	if (!is3DContext)
 		RenderSystem.depthFunc(GL_ALWAYS)
 
-	val tess = Tesselator.getInstance()
-	val builder = tess.builder
-
-	builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
+	val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
 
 	val length = ((startX - endX).pow(2f) + (startY - endY).pow(2f)).pow(0.5f)
 	val angle = acos((endX - startX) / length)
@@ -272,24 +261,24 @@ fun drawLine(
 	builder.vertex(matrix,
 		startX - y0 * sin,
 		startY + y0 * cos,
-		z).color(color).endVertex()
+		z).color(color)
 
 	builder.vertex(matrix,
 		startX - y1 * sin,
 		startY + y1 * cos,
-		z).color(color).endVertex()
+		z).color(color)
 
 	builder.vertex(matrix,
 		startX + x2 * cos - y2 * sin,
 		startY + x2 * sin + y2 * cos,
-		z).color(color).endVertex()
+		z).color(color)
 
 	builder.vertex(matrix,
 		startX + x3 * cos - y3 * sin,
 		startY + x3 * sin + y3 * cos,
-		z).color(color).endVertex()
+		z).color(color)
 
-	tess.end()
+	BufferUploader.drawWithShader(builder.buildOrThrow())
 }
 
 data class ScissorRect(val xStart: Int, val yStart: Int, val xEnd: Int, val yEnd: Int, val lock: Boolean = false) {
@@ -421,17 +410,15 @@ fun clearDepth(stack: PoseStack, x: Float, y: Float, width: Float, height: Float
 	RenderSystem.enableDepthTest()
 	RenderSystem.depthFunc(GL_ALWAYS)
 
-	val builder = tesselator.builder
-
 	val matrix = stack.last().pose()
 
-	builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
-	builder.vertex(matrix, x, y + height, depth).endVertex()
-	builder.vertex(matrix, x + width, y + height, depth).endVertex()
-	builder.vertex(matrix, x + width, y, depth).endVertex()
-	builder.vertex(matrix, x, y, depth).endVertex()
+	val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
+	builder.vertex(matrix, x, y + height, depth)
+	builder.vertex(matrix, x + width, y + height, depth)
+	builder.vertex(matrix, x + width, y, depth)
+	builder.vertex(matrix, x, y, depth)
 
-	BufferUploader.drawWithShader(builder.end())
+	BufferUploader.drawWithShader(builder.buildOrThrow())
 
 	RenderSystem.defaultBlendFunc()
 	RenderSystem.depthFunc(GL_LESS)
@@ -498,13 +485,13 @@ fun uploadArc(
 	if (triangleFan) {
 		val singleStep = (endDegree - startDegree) / steps
 
-		builder.vertex(matrix, x, y, z).endVertex()
+		builder.vertex(matrix, x, y, z)
 
 		for (i in 0 .. steps) {
 			val sin = sin(startDegree + i * singleStep).toFloat()
 			val cos = cos(startDegree + i * singleStep).toFloat()
 
-			builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, z).endVertex()
+			builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, z)
 		}
 	} else {
 		val singleStep = (endDegree - startDegree) / (steps + 1)
@@ -516,10 +503,10 @@ fun uploadArc(
 			val sin2 = sin(startDegree + (i + 1) * singleStep).toFloat()
 			val cos2 = cos(startDegree + (i + 1) * singleStep).toFloat()
 
-			builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, z).endVertex()
-			builder.vertex(matrix, x + outerRadius * sin2, y + cos2 * outerRadius, z).endVertex()
-			builder.vertex(matrix, x + innerRadius * sin2, y + cos2 * innerRadius, z).endVertex()
-			builder.vertex(matrix, x + innerRadius * sin, y + cos * innerRadius, z).endVertex()
+			builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, z)
+			builder.vertex(matrix, x + outerRadius * sin2, y + cos2 * outerRadius, z)
+			builder.vertex(matrix, x + innerRadius * sin2, y + cos2 * innerRadius, z)
+			builder.vertex(matrix, x + innerRadius * sin, y + cos * innerRadius, z)
 		}
 	}
 }
@@ -545,18 +532,16 @@ fun drawArc(
 	RenderSystem.defaultBlendFunc()
 	RenderSystem.depthFunc(GL_ALWAYS)
 
-	val builder = tesselator.builder
-
 	if (innerRadius == 0f) {
 		if (steps >= 1) {
-			builder.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION)
+			val builder = tesselator.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION)
 			uploadArc(matrix, builder, x, y, outerRadius, innerRadius, startDegree, endDegree, steps, alignAtCenter, triangleFan = true, z = z)
-			BufferUploader.drawWithShader(builder.end())
+			BufferUploader.drawWithShader(builder.buildOrThrow())
 		}
 	} else {
-		builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
+		val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
 		uploadArc(matrix, builder, x, y, outerRadius, innerRadius, startDegree, endDegree, steps, alignAtCenter, triangleFan = false, z = z)
-		BufferUploader.drawWithShader(builder.end())
+		BufferUploader.drawWithShader(builder.buildOrThrow())
 	}
 }
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt
index 1124d674f..05d050374 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt
@@ -5,6 +5,7 @@ import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite
 import ru.dbotthepony.mc.otm.client.render.sprites.GridAtlas
 import ru.dbotthepony.mc.otm.client.render.sprites.sprite
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 object ResearchIcons {
 	val ICON_TRANSFER: AbstractMatterySprite
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ShockwaveRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ShockwaveRenderer.kt
index c408b8245..1c4515def 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ShockwaveRenderer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ShockwaveRenderer.kt
@@ -5,10 +5,9 @@ import com.mojang.blaze3d.vertex.BufferUploader
 import com.mojang.blaze3d.vertex.DefaultVertexFormat
 import com.mojang.blaze3d.vertex.VertexFormat
 import net.minecraft.client.renderer.GameRenderer
-import net.minecraftforge.client.event.RenderLevelStageEvent
+import net.neoforged.neoforge.client.event.RenderLevelStageEvent
 import org.lwjgl.opengl.GL11.GL_LESS
 import ru.dbotthepony.mc.otm.config.AndroidConfig
-import ru.dbotthepony.mc.otm.config.ServerConfig
 import ru.dbotthepony.mc.otm.core.math.Vector
 import ru.dbotthepony.mc.otm.core.math.component1
 import ru.dbotthepony.mc.otm.core.math.component2
@@ -39,8 +38,6 @@ object ShockwaveRenderer {
 			lastRender = secondTimeD
 
 			if (radius <= finalRadius) {
-				val builder = tesselator.builder
-
 				RenderSystem.setShader(GameRenderer::getPositionShader)
 				RenderSystem.setShaderColor(1f, 1f, 1f, linearInterpolation(radius / finalRadius, 0.5f, 0f))
 				RenderSystem.defaultBlendFunc()
@@ -48,7 +45,7 @@ object ShockwaveRenderer {
 				RenderSystem.enableDepthTest()
 				RenderSystem.depthFunc(GL_LESS)
 
-				builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
+				val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
 
 				event.poseStack.pushPose()
 				val (x, y, z) = event.camera.position
@@ -57,7 +54,7 @@ object ShockwaveRenderer {
 				uploadArc(event.poseStack.last.pose, builder, x = 0f, y = 0f, innerRadius = (radius - 1f).coerceAtLeast(0f), outerRadius = radius, triangleFan = false)
 				event.poseStack.popPose()
 
-				BufferUploader.drawWithShader(builder.end())
+				BufferUploader.drawWithShader(builder.buildOrThrow())
 			}
 
 			return radius <= finalRadius
@@ -72,13 +69,7 @@ object ShockwaveRenderer {
 		}
 
 		synchronized(activeShockwaves) {
-			val iterator = activeShockwaves.listIterator()
-
-			for (value in iterator) {
-				if (!value.render(event)) {
-					iterator.remove()
-				}
-			}
+			activeShockwaves.removeIf { !it.render(event) }
 		}
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt
index 7929fc86c..17a21e0be 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt
@@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.client.render
 import net.minecraft.resources.ResourceLocation
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.client.render.sprites.MatteryAtlas
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 object WidgetLocation {
 	val LARGE_BUTTON = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/large_button.png"), 72f, 18f)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets8.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets8.kt
index 44af60e19..d53587cc0 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets8.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets8.kt
@@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.client.render
 import net.minecraft.resources.ResourceLocation
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.client.render.sprites.GridAtlas
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 object Widgets8 {
 	val GRID = GridAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets_8.png"), 8f, 8f, columns = 64 / 8, rows = 32 / 8)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleRenderer.kt
index 770ef3d96..f6b3174da 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleRenderer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleRenderer.kt
@@ -8,6 +8,7 @@ import net.minecraft.client.renderer.MultiBufferSource
 import net.minecraft.client.renderer.blockentity.BeaconRenderer
 import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
 import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
+import net.minecraft.world.phys.AABB
 import net.minecraft.world.phys.Vec3
 import org.joml.Matrix4f
 import org.lwjgl.opengl.GL30
@@ -19,6 +20,7 @@ import ru.dbotthepony.mc.otm.client.ShiftPressedCond
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.client.render.*
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.core.AABB
 import ru.dbotthepony.mc.otm.core.math.VECTOR_FORWARD
 import ru.dbotthepony.mc.otm.core.math.VECTOR_RIGHT
 import ru.dbotthepony.mc.otm.core.math.VECTOR_UP
@@ -60,10 +62,10 @@ private fun pushQuad(matrix4f: Matrix4f, builder: BufferBuilder, quad: Array<Vec
 		d = d.rotateAroundAxis(VECTOR_UP, topRotation)
 	}
 
-	builder.vertex(matrix4f, b.x.toFloat(), b.y.toFloat(), b.z.toFloat()).uv(0f, anim).endVertex()
-	builder.vertex(matrix4f, c.x.toFloat(), c.y.toFloat(), c.z.toFloat()).uv(1f, anim).endVertex()
-	builder.vertex(matrix4f, d.x.toFloat(), d.y.toFloat(), d.z.toFloat()).uv(1f, anim + 64f).endVertex()
-	builder.vertex(matrix4f, a.x.toFloat(), a.y.toFloat(), a.z.toFloat()).uv(0f, anim + 64f).endVertex()
+	builder.vertex(matrix4f, b.x.toFloat(), b.y.toFloat(), b.z.toFloat()).uv(0f, anim)
+	builder.vertex(matrix4f, c.x.toFloat(), c.y.toFloat(), c.z.toFloat()).uv(1f, anim)
+	builder.vertex(matrix4f, d.x.toFloat(), d.y.toFloat(), d.z.toFloat()).uv(1f, anim + 64f)
+	builder.vertex(matrix4f, a.x.toFloat(), a.y.toFloat(), a.z.toFloat()).uv(0f, anim + 64f)
 }
 
 private val quadA = quadY(-BEAM_WIDTH)
@@ -129,8 +131,7 @@ class BlackHoleRenderer(private val context: BlockEntityRendererProvider.Context
 				RenderSystem.setShaderTexture(0, BeaconRenderer.BEAM_LOCATION)
 				RenderSystem.disableCull()
 
-				val builder = Tesselator.getInstance().builder
-				builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
+				val builder = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
 
 				val anim = (((System.currentTimeMillis().toDouble() / 100.0) % 100.0).toFloat() / 100f) * 64f
 				val rotation = System.currentTimeMillis().toDouble() / 1000.0
@@ -151,7 +152,7 @@ class BlackHoleRenderer(private val context: BlockEntityRendererProvider.Context
 
 				// builder.end()
 				// BufferUploader.end(builder)
-				BufferUploader.drawWithShader(builder.end())
+				BufferUploader.drawWithShader(builder.buildOrThrow())
 				RenderSystem.enableCull()
 			}
 
@@ -192,6 +193,13 @@ class BlackHoleRenderer(private val context: BlockEntityRendererProvider.Context
 		}
 	}
 
+	override fun getRenderBoundingBox(entity: BlackHoleBlockEntity): AABB {
+		with(entity) {
+			return AABB(blockPos.offset(-GravitationStabilizerBlockEntity.RANGE, -GravitationStabilizerBlockEntity.RANGE, -GravitationStabilizerBlockEntity.RANGE), blockPos.offset(
+				GravitationStabilizerBlockEntity.RANGE, GravitationStabilizerBlockEntity.RANGE, GravitationStabilizerBlockEntity.RANGE))
+		}
+	}
+
 	override fun shouldRenderOffScreen(p_112306_: BlackHoleBlockEntity): Boolean {
 		return true
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/FluidTankRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/FluidTankRenderer.kt
index 3328df276..01e8c6a9e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/FluidTankRenderer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/FluidTankRenderer.kt
@@ -1,8 +1,10 @@
 package ru.dbotthepony.mc.otm.client.render.blockentity
 
-import com.mojang.blaze3d.vertex.*
+import com.mojang.blaze3d.vertex.PoseStack
 import com.mojang.math.Axis
-import net.minecraft.client.renderer.*
+import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer
+import net.minecraft.client.renderer.MultiBufferSource
+import net.minecraft.client.renderer.Sheets
 import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
 import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
 import net.minecraft.client.renderer.entity.ItemRenderer
@@ -12,14 +14,19 @@ import net.minecraft.world.item.ItemDisplayContext
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.block.HalfTransparentBlock
 import net.minecraft.world.level.block.StainedGlassPaneBlock
-import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
+import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
 import ru.dbotthepony.mc.otm.client.minecraft
-import ru.dbotthepony.mc.otm.core.ifPresentK
-import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.client.render.color
+import ru.dbotthepony.mc.otm.client.render.light
+import ru.dbotthepony.mc.otm.client.render.normal
+import ru.dbotthepony.mc.otm.client.render.overlayCoords
+import ru.dbotthepony.mc.otm.client.render.uv
+import ru.dbotthepony.mc.otm.client.render.vertex
 import ru.dbotthepony.mc.otm.core.math.linearInterpolation
 import ru.dbotthepony.mc.otm.registry.MBlocks
 
@@ -46,7 +53,7 @@ class FluidTankRenderer(private val context: BlockEntityRendererProvider.Context
 		) {
 			val model = minecraft.modelManager.blockModelShaper.getBlockModel(MBlocks.FLUID_TANK.defaultBlockState())
 
-			stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
+			stack.getCapability(Capabilities.FluidHandler.ITEM)?.let {
 				renderFluidInTank(it, it.getFluidInTank(0), poseStack, bufferSource, packedLight, packedOverlay)
 			}
 
@@ -111,10 +118,10 @@ class FluidTankRenderer(private val context: BlockEntityRendererProvider.Context
 
 				val matrix = poseStack.last().pose()
 
-				builder.vertex(matrix, fluidPadding, 0f, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
-				builder.vertex(matrix, fluidPadding + fluidWidth, 0f, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
-				builder.vertex(matrix, fluidPadding + fluidWidth, fluidLevel * fluidHeight, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, interp).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
-				builder.vertex(matrix, fluidPadding, fluidLevel * fluidHeight, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, interp).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
+				builder.vertex(matrix, fluidPadding, 0f, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, v1).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
+				builder.vertex(matrix, fluidPadding + fluidWidth, 0f, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, v1).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
+				builder.vertex(matrix, fluidPadding + fluidWidth, fluidLevel * fluidHeight, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, interp).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
+				builder.vertex(matrix, fluidPadding, fluidLevel * fluidHeight, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, interp).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
 
 				poseStack.popPose()
 			}
@@ -122,17 +129,17 @@ class FluidTankRenderer(private val context: BlockEntityRendererProvider.Context
 			val matrix = poseStack.last().pose()
 
 			if (!gas && fluidLevel < 1f) { // top
-				builder.vertex(matrix, fluidPadding, fluidLevel * fluidHeight, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
-				builder.vertex(matrix, fluidPadding + fluidWidth, fluidLevel * fluidHeight, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
-				builder.vertex(matrix, fluidPadding + fluidWidth, fluidLevel * fluidHeight, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v0).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
-				builder.vertex(matrix, fluidPadding, fluidLevel * fluidHeight, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v0).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
+				builder.vertex(matrix, fluidPadding, fluidLevel * fluidHeight, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v1).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
+				builder.vertex(matrix, fluidPadding + fluidWidth, fluidLevel * fluidHeight, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v1).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
+				builder.vertex(matrix, fluidPadding + fluidWidth, fluidLevel * fluidHeight, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v0).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
+				builder.vertex(matrix, fluidPadding, fluidLevel * fluidHeight, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v0).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
 			}
 
 			if (gas && fluidLevel < 1f) { // bottom
-				builder.vertex(matrix, fluidPadding, 0f, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v0).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
-				builder.vertex(matrix, fluidPadding + fluidWidth, 0f, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v0).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
-				builder.vertex(matrix, fluidPadding + fluidWidth, 0f, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
-				builder.vertex(matrix, fluidPadding, 0f, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex()
+				builder.vertex(matrix, fluidPadding, 0f, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v0).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
+				builder.vertex(matrix, fluidPadding + fluidWidth, 0f, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v0).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
+				builder.vertex(matrix, fluidPadding + fluidWidth, 0f, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v1).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
+				builder.vertex(matrix, fluidPadding, 0f, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v1).overlayCoords(packedOverlay).light(packedLight).normal(0.0f, 1.0f, 0.0f)
 			}
 
 			poseStack.popPose()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/GravitationStabilizerRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/GravitationStabilizerRenderer.kt
index 2b6136717..0e683fe2d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/GravitationStabilizerRenderer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/GravitationStabilizerRenderer.kt
@@ -9,6 +9,7 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
 import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
 import net.minecraft.client.renderer.texture.OverlayTexture
 import net.minecraft.core.Direction
+import net.minecraft.world.phys.AABB
 import org.joml.Matrix4f
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.block.BlackHoleBlock
@@ -21,6 +22,7 @@ import ru.dbotthepony.mc.otm.client.render.*
 import ru.dbotthepony.mc.otm.core.*
 import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity.Companion.RANGE
 import ru.dbotthepony.mc.otm.core.math.VECTOR_DOWN
 import ru.dbotthepony.mc.otm.core.math.VECTOR_FORWARD
 import ru.dbotthepony.mc.otm.core.math.VECTOR_RIGHT
@@ -174,6 +176,10 @@ class GravitationStabilizerRenderer(private val context: BlockEntityRendererProv
 		}
 	}
 
+	override fun getRenderBoundingBox(blockEntity: GravitationStabilizerBlockEntity): AABB {
+		return AABB(blockEntity.blockPos.offset(-RANGE, -RANGE, -RANGE), blockEntity.blockPos.offset(RANGE, RANGE, RANGE))
+	}
+
 	override fun shouldRenderOffScreen(p_112306_: GravitationStabilizerBlockEntity) = true
 	override fun getViewDistance() = 96
 
@@ -245,10 +251,10 @@ class GravitationStabilizerRenderer(private val context: BlockEntityRendererProv
 				normal = normal.rotateAroundAxis(VECTOR_UP, topRotation)
 			}
 
-			builder.vertex(matrix4f, b).color(color).uv(0f, anim).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(15728880).normal(normal).endVertex()
-			builder.vertex(matrix4f, c).color(color).uv(1f, anim).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(15728880).normal(normal).endVertex()
-			builder.vertex(matrix4f, d).color(color).uv(1f, anim + len.toFloat()).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(15728880).normal(normal).endVertex()
-			builder.vertex(matrix4f, a).color(color).uv(0f, anim + len.toFloat()).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(15728880).normal(normal).endVertex()
+			builder.vertex(matrix4f, b).color(color).uv(0f, anim).overlayCoords(OverlayTexture.NO_OVERLAY).light(15728880).normal(normal)
+			builder.vertex(matrix4f, c).color(color).uv(1f, anim).overlayCoords(OverlayTexture.NO_OVERLAY).light(15728880).normal(normal)
+			builder.vertex(matrix4f, d).color(color).uv(1f, anim + len.toFloat()).overlayCoords(OverlayTexture.NO_OVERLAY).light(15728880).normal(normal)
+			builder.vertex(matrix4f, a).color(color).uv(0f, anim + len.toFloat()).overlayCoords(OverlayTexture.NO_OVERLAY).light(15728880).normal(normal)
 		}
 
 		private fun ray(
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/entity/PlasmaProjectileRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/entity/PlasmaProjectileRenderer.kt
index 926a04f65..5bfffd6dd 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/entity/PlasmaProjectileRenderer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/entity/PlasmaProjectileRenderer.kt
@@ -9,6 +9,12 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider
 import net.minecraft.client.renderer.texture.OverlayTexture
 import net.minecraft.resources.ResourceLocation
 import ru.dbotthepony.mc.otm.OverdriveThatMatters.loc
+import ru.dbotthepony.mc.otm.client.render.color
+import ru.dbotthepony.mc.otm.client.render.light
+import ru.dbotthepony.mc.otm.client.render.normal
+import ru.dbotthepony.mc.otm.client.render.overlayCoords
+import ru.dbotthepony.mc.otm.client.render.uv
+import ru.dbotthepony.mc.otm.client.render.vertex
 import ru.dbotthepony.mc.otm.entity.PlasmaProjectile
 
 class PlasmaProjectileRenderer(context: EntityRendererProvider.Context) : EntityRenderer<PlasmaProjectile>(context) {
@@ -33,10 +39,10 @@ class PlasmaProjectileRenderer(context: EntityRendererProvider.Context) : Entity
 		val matrix = poseStack.last().pose()
 		val normal = poseStack.last().normal()
 
-		consumer.vertex(matrix, -.5f, -.5f, 0f).color(1f, 1f, 1f, 1f).uv(0f, 1f).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(normal, 0f, 1f, 0f).endVertex()
-		consumer.vertex(matrix, .5f, -.5f, 0f).color(1f, 1f, 1f, 1f).uv(1f, 1f).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(normal, 0f, 1f, 0f).endVertex()
-		consumer.vertex(matrix, .5f, .5f, 0f).color(1f, 1f, 1f, 1f).uv(1f, 0f).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(normal, 0f, 1f, 0f).endVertex()
-		consumer.vertex(matrix, -.5f, .5f, 0f).color(1f, 1f, 1f, 1f).uv(0f, 0f).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(normal, 0f, 1f, 0f).endVertex()
+		consumer.vertex(matrix, -.5f, -.5f, 0f).color(1f, 1f, 1f, 1f).uv(0f, 1f).overlayCoords(OverlayTexture.NO_OVERLAY).light(packedLight).normal(poseStack.last(), 0f, 1f, 0f)
+		consumer.vertex(matrix, .5f, -.5f, 0f).color(1f, 1f, 1f, 1f).uv(1f, 1f).overlayCoords(OverlayTexture.NO_OVERLAY).light(packedLight).normal(poseStack.last(), 0f, 1f, 0f)
+		consumer.vertex(matrix, .5f, .5f, 0f).color(1f, 1f, 1f, 1f).uv(1f, 0f).overlayCoords(OverlayTexture.NO_OVERLAY).light(packedLight).normal(poseStack.last(), 0f, 1f, 0f)
+		consumer.vertex(matrix, -.5f, .5f, 0f).color(1f, 1f, 1f, 1f).uv(0f, 0f).overlayCoords(OverlayTexture.NO_OVERLAY).light(packedLight).normal(poseStack.last(), 0f, 1f, 0f)
 
 		poseStack.popPose()
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/AbstractMatterySprite.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/AbstractMatterySprite.kt
index a65fc4dce..faeb8d4cf 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/AbstractMatterySprite.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/AbstractMatterySprite.kt
@@ -19,6 +19,8 @@ import ru.dbotthepony.mc.otm.client.render.IUVCoords
 import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
 import ru.dbotthepony.mc.otm.client.render.color
 import ru.dbotthepony.mc.otm.client.render.renderTexturedRect
+import ru.dbotthepony.mc.otm.client.render.uv
+import ru.dbotthepony.mc.otm.client.render.vertex
 import ru.dbotthepony.mc.otm.core.math.linearInterpolation
 import java.util.concurrent.ConcurrentHashMap
 
@@ -129,10 +131,10 @@ sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords {
 		v1: Float,
 	) {
 		val matrix = pose.last().pose()
-		builder.vertex(matrix, x, y + height, z).color(color).uv(u0, v1).endVertex()
-		builder.vertex(matrix, x + width, y + height, z).color(color).uv(u1, v1).endVertex()
-		builder.vertex(matrix, x + width, y, z).color(color).uv(u1, v0).endVertex()
-		builder.vertex(matrix, x, y, z).color(color).uv(u0, v0).endVertex()
+		builder.vertex(matrix, x, y + height, z).color(color).uv(u0, v1)
+		builder.vertex(matrix, x + width, y + height, z).color(color).uv(u1, v1)
+		builder.vertex(matrix, x + width, y, z).color(color).uv(u1, v0)
+		builder.vertex(matrix, x, y, z).color(color).uv(u0, v0)
 	}
 
 	fun uploadOnto(
@@ -185,7 +187,7 @@ sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords {
 			val builder = RenderType.CompositeState.builder()
 
 			builder.setTextureState(RenderStateShard.TextureStateShard(it, false, false))
-			builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader))
+			builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionTexColorShader))
 			builder.setDepthTestState(RenderStateShard.DepthTestStateShard("always", GL_ALWAYS))
 			builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", {
 				RenderSystem.enableBlend()
@@ -196,7 +198,7 @@ sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords {
 
 			@Suppress("INACCESSIBLE_TYPE")
 			RenderType.create("otm_gui_element_no_depth",
-				DefaultVertexFormat.POSITION_COLOR_TEX,
+				DefaultVertexFormat.POSITION_TEX_COLOR,
 				VertexFormat.Mode.QUADS,
 				2048,
 				false,
@@ -210,7 +212,7 @@ sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords {
 			val builder = RenderType.CompositeState.builder()
 
 			builder.setTextureState(RenderStateShard.TextureStateShard(it, false, false))
-			builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader))
+			builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionTexColorShader))
 			builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", {
 				RenderSystem.enableBlend()
 				RenderSystem.defaultBlendFunc()
@@ -220,7 +222,7 @@ sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords {
 
 			@Suppress("INACCESSIBLE_TYPE")
 			RenderType.create("otm_gui_element_depth",
-				DefaultVertexFormat.POSITION_COLOR_TEX,
+				DefaultVertexFormat.POSITION_TEX_COLOR,
 				VertexFormat.Mode.QUADS,
 				2048,
 				false,
@@ -234,7 +236,7 @@ sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords {
 			val builder = RenderType.CompositeState.builder()
 
 			builder.setTextureState(RenderStateShard.TextureStateShard(it, false, false))
-			builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader))
+			builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionTexColorShader))
 			builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", {
 				RenderSystem.enableBlend()
 				RenderSystem.defaultBlendFunc()
@@ -244,7 +246,7 @@ sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords {
 
 			@Suppress("INACCESSIBLE_TYPE")
 			RenderType.create("otm_gui_element_world",
-				DefaultVertexFormat.POSITION_COLOR_TEX,
+				DefaultVertexFormat.POSITION_TEX_COLOR,
 				VertexFormat.Mode.QUADS,
 				8192,
 				false,
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SpriteType.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SpriteType.kt
index 9fd4b7efd..c81bfb36f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SpriteType.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SpriteType.kt
@@ -39,7 +39,7 @@ enum class SpriteType {
 		}
 
 		override fun fromNetwork(buff: FriendlyByteBuf): AbstractMatterySprite {
-			val texture = ResourceLocation(buff.readUtf())
+			val texture = ResourceLocation.parse(buff.readUtf())
 			val x = buff.readFloat()
 			val y = buff.readFloat()
 			val width = buff.readFloat()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt
index ee8b3b2bf..3f627a499 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt
@@ -4,6 +4,7 @@ import mezz.jei.api.constants.RecipeTypes
 import net.minecraft.client.gui.screens.inventory.InventoryScreen
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Items
+import net.neoforged.neoforge.network.PacketDistributor
 import ru.dbotthepony.mc.otm.client.mousePos
 import ru.dbotthepony.mc.otm.client.moveMousePosScaled
 import ru.dbotthepony.mc.otm.client.render.ItemStackIcon
@@ -27,7 +28,6 @@ import ru.dbotthepony.mc.otm.compat.curios.openCuriosScreen
 import ru.dbotthepony.mc.otm.core.math.integerDivisionDown
 import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu
 import ru.dbotthepony.mc.otm.network.ExopackMenuOpen
-import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
 import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak
 
 @MouseTweaksDisableWheelTweak
@@ -295,7 +295,7 @@ class ExopackInventoryScreen(menu: ExopackInventoryMenu) : MatteryScreen<Exopack
 	}
 
 	init {
-		MatteryPlayerNetworkChannel.sendToServer(ExopackMenuOpen)
+		PacketDistributor.sendToServer(ExopackMenuOpen)
 		ru.dbotthepony.mc.otm.client.minecraft.player?.containerMenu = menu
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt
index 4b45c508e..59f1bcbbb 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt
@@ -12,9 +12,8 @@ import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.inventory.Slot
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.client.event.ContainerScreenEvent.Render.Background
-import net.minecraftforge.client.event.ContainerScreenEvent.Render.Foreground
-import net.minecraftforge.common.MinecraftForge
+import net.neoforged.neoforge.client.event.ContainerScreenEvent
+import net.neoforged.neoforge.common.NeoForge
 import org.lwjgl.opengl.GL11
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
@@ -680,7 +679,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
 			panels.asReversed().any { it.updateCursor1() }
 
 		RenderSystem.depthFunc(GL11.GL_LESS)
-		MinecraftForge.EVENT_BUS.post(Background(this, graphics, mouseX, mouseY))
+		NeoForge.EVENT_BUS.post(ContainerScreenEvent.Render.Background(this, graphics, mouseX, mouseY))
 		RenderSystem.disableDepthTest()
 
 		// Screen.super.render
@@ -690,7 +689,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
 		// /Screen.super.render
 
 		RenderSystem.disableDepthTest()
-		MinecraftForge.EVENT_BUS.post(Foreground(this, graphics, mouseX, mouseY))
+		NeoForge.EVENT_BUS.post(ContainerScreenEvent.Render.Foreground(this, graphics, mouseX, mouseY))
 
 		var itemstack = if (draggingItem.isEmpty) menu.carried else draggingItem
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt
index dbec4100b..1c0dc1c2d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt
@@ -1,14 +1,14 @@
 package ru.dbotthepony.mc.otm.client.screen.decorative
 
 import net.minecraft.ChatFormatting
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.network.chat.Component
 import net.minecraft.util.RandomSource
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.item.DyeColor
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Items
-import net.minecraftforge.common.Tags
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.neoforge.common.Tags
 import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.FlatRectangleIcon
@@ -105,7 +105,7 @@ class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) :
 			it.dock = Dock.FILL
 			it.dockResize = DockResizeMode.NONE
 			it.slotBackgroundEmpty =
-				ItemStackIcon(ItemStack(ForgeRegistries.ITEMS.tags()!!.getTag(Tags.Items.DYES).getRandomElement(RandomSource.create()).orElse(Items.AIR)), 16f, 16f)
+				ItemStackIcon(ItemStack(BuiltInRegistries.ITEM.getOrCreateTag(Tags.Items.DYES).getRandomElement(menu.player.level().random).map { it.value() }.orElse(Items.AIR)), 16f, 16f)
 				.fixed()
 				.composeBefore(FlatRectangleIcon(16f, 16f, RGBAColor.rgb(0x8b8b8b).copy(alpha = 0.6f)))
 				.fixed()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt
index 10a39519d..21f37ce0f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt
@@ -4,6 +4,7 @@ import net.minecraft.ChatFormatting
 import net.minecraft.network.chat.Component
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.item.ItemStack
+import net.neoforged.neoforge.network.PacketDistributor
 import ru.dbotthepony.mc.otm.capability.matter.PatternState
 import ru.dbotthepony.mc.otm.capability.matter.ReplicationTask
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
@@ -30,7 +31,6 @@ import ru.dbotthepony.mc.otm.core.util.formatTickDuration
 import ru.dbotthepony.mc.otm.matter.MatterManager
 import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu
 import ru.dbotthepony.mc.otm.menu.matter.ReplicationRequestPacket
-import ru.dbotthepony.mc.otm.network.MenuNetworkChannel
 import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak
 import java.util.function.Predicate
 import kotlin.math.ceil
@@ -394,7 +394,7 @@ class MatterPanelScreen(
 				} catch (_: NumberFormatException) {
 				}
 
-				MenuNetworkChannel.sendToServer(ReplicationRequestPacket(pattern.id, count))
+				PacketDistributor.sendToServer(ReplicationRequestPacket(pattern.id, count))
 				frame.remove()
 			}
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt
index 63083b69e..76568231c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt
@@ -21,6 +21,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.input.TextInputPanel
 import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import java.util.function.Consumer
 import java.util.function.Supplier
 import kotlin.math.roundToInt
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt
index 40ae1f12d..4f4680962 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt
@@ -4,6 +4,7 @@ import com.mojang.blaze3d.systems.RenderSystem
 import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
 import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
 import net.minecraft.client.gui.screens.Screen
+import net.minecraft.core.Holder
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.world.effect.MobEffect
 import net.minecraft.world.effect.MobEffectInstance
@@ -15,6 +16,7 @@ import ru.dbotthepony.mc.otm.client.render.determineTooltipPosition
 import ru.dbotthepony.mc.otm.client.render.sprites.sprite
 import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.util.formatTickDuration
 import ru.dbotthepony.mc.otm.core.math.integerDivisionDown
@@ -73,7 +75,7 @@ open class EffectListPanel<out S : Screen> @JvmOverloads constructor(
 	) : EditablePanel<S>(screen, this@EffectListPanel.canvas, x, y, width, height) {
 		protected var lastInstance: MobEffectInstance = effect
 
-		val effectType: MobEffect = effect.effect
+		val effectType = effect.effect
 		val effect: MobEffectInstance get() {
 			val get = entity.activeEffectsMap[effectType]
 
@@ -134,7 +136,7 @@ open class EffectListPanel<out S : Screen> @JvmOverloads constructor(
 					width = renderWidth,
 					height = renderHeight)
 
-				val name = effect.effect.displayName.copy()
+				val name = effect.effect.value().displayName.copy()
 
 				if (effect.amplifier in 1 .. 9) {
 					name.append(" ").append(TranslatableComponent("enchantment.level.${effect.amplifier + 1}"))
@@ -150,7 +152,7 @@ open class EffectListPanel<out S : Screen> @JvmOverloads constructor(
 		}
 	}
 
-	protected val effectButtons: MutableMap<MobEffect, EffectSquare> = Object2ObjectArrayMap()
+	protected val effectButtons: MutableMap<Holder<MobEffect>, EffectSquare> = Object2ObjectArrayMap()
 
 	fun calculateMaxScroll(scrollBarPanel: DiscreteScrollBarPanel<*>): Int {
 		return integerDivisionDown(entity.activeEffects.size, gridWidth)
@@ -264,7 +266,7 @@ open class EffectListPanel<out S : Screen> @JvmOverloads constructor(
 	}
 
 	companion object {
-		val BAR = ResourceLocation("textures/gui/sprites/container/inventory/effect_background_large.png").sprite(0f, 0f, 120f, 32f, 120f, 32f)
-		val SQUARE_THIN = ResourceLocation("textures/gui/sprites/hud/effect_background.png").sprite(0f, 0f, 24f, 24f, 24f, 24f)
+		val BAR = ResourceLocation("minecraft", "textures/gui/sprites/container/inventory/effect_background_large.png").sprite(0f, 0f, 120f, 32f, 120f, 32f)
+		val SQUARE_THIN = ResourceLocation("minecraft", "textures/gui/sprites/hud/effect_background.png").sprite(0f, 0f, 24f, 24f, 24f, 24f)
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt
index 86dac7021..9811a3056 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt
@@ -5,7 +5,8 @@ import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
 import net.minecraft.client.gui.screens.inventory.InventoryScreen
 import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.entity.player.Player
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import net.neoforged.neoforge.network.PacketDistributor
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.Widgets8
@@ -25,7 +26,6 @@ import ru.dbotthepony.mc.otm.network.DisableExopackGlowPacket
 import ru.dbotthepony.mc.otm.network.DisplayExopackPacket
 import ru.dbotthepony.mc.otm.network.EnableExopackGlowPacket
 import ru.dbotthepony.mc.otm.network.HideExopackPacket
-import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
 import ru.dbotthepony.mc.otm.network.ResetExopackColorPacket
 import ru.dbotthepony.mc.otm.network.SetExopackColorPacket
 import java.util.function.IntConsumer
@@ -44,7 +44,7 @@ private fun calculateScale(width: Float, height: Float): Int {
 	}
 }
 
-private fun createExopackAppearanceWindow(screen: MatteryScreen<*>, matteryPlayer: MatteryPlayerCapability): FramePanel<*> {
+private fun createExopackAppearanceWindow(screen: MatteryScreen<*>, matteryPlayer: MatteryPlayer): FramePanel<*> {
 	val frame = FramePanel.padded(screen, width = 200f, height = 90f, title = TranslatableComponent("otm.gui.exopack.customization"))
 
 	screen.addPanel(frame)
@@ -63,9 +63,9 @@ private fun createExopackAppearanceWindow(screen: MatteryScreen<*>, matteryPlaye
 			},
 			{
 				if (it) {
-					MatteryPlayerNetworkChannel.sendToServer(DisplayExopackPacket)
+					PacketDistributor.sendToServer(DisplayExopackPacket)
 				} else {
-					MatteryPlayerNetworkChannel.sendToServer(HideExopackPacket)
+					PacketDistributor.sendToServer(HideExopackPacket)
 				}
 			}
 		)
@@ -84,9 +84,9 @@ private fun createExopackAppearanceWindow(screen: MatteryScreen<*>, matteryPlaye
 			},
 			{
 				if (it) {
-					MatteryPlayerNetworkChannel.sendToServer(EnableExopackGlowPacket)
+					PacketDistributor.sendToServer(EnableExopackGlowPacket)
 				} else {
-					MatteryPlayerNetworkChannel.sendToServer(DisableExopackGlowPacket)
+					PacketDistributor.sendToServer(DisableExopackGlowPacket)
 				}
 			}
 		)
@@ -98,7 +98,7 @@ private fun createExopackAppearanceWindow(screen: MatteryScreen<*>, matteryPlaye
 	ButtonPanel(screen, frame, label = TranslatableComponent("otm.gui.exopack.change_color"), onPress = IntConsumer {
 		frame.blockingWindow = ColorPickerPanel.frame(
 			screen,
-			callback = { MatteryPlayerNetworkChannel.sendToServer(SetExopackColorPacket(it)) },
+			callback = { PacketDistributor.sendToServer(SetExopackColorPacket(it)) },
 			color = matteryPlayer.exopackColor ?: RGBAColor.WHITE,
 			title = TranslatableComponent("otm.gui.exopack.change_color"))
 	}).also {
@@ -106,7 +106,7 @@ private fun createExopackAppearanceWindow(screen: MatteryScreen<*>, matteryPlaye
 		it.dockTop = 2f
 	}
 
-	ButtonPanel(screen, frame, label = TranslatableComponent("otm.gui.exopack.change_color2"), onPress = IntConsumer { MatteryPlayerNetworkChannel.sendToServer(ResetExopackColorPacket) }).also {
+	ButtonPanel(screen, frame, label = TranslatableComponent("otm.gui.exopack.change_color2"), onPress = IntConsumer { PacketDistributor.sendToServer(ResetExopackColorPacket) }).also {
 		it.dock = Dock.TOP
 		it.dockTop = 2f
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt
index 929ce3368..474c14dbd 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt
@@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.client.screen.panels.input
 
 import com.mojang.blaze3d.platform.InputConstants
 import com.mojang.blaze3d.systems.RenderSystem
+import com.mojang.blaze3d.vertex.BufferUploader
 import com.mojang.blaze3d.vertex.DefaultVertexFormat
 import com.mojang.blaze3d.vertex.VertexFormat
 import it.unimi.dsi.fastutil.chars.Char2IntFunction
@@ -29,6 +30,7 @@ import ru.dbotthepony.mc.otm.core.addAll
 import ru.dbotthepony.mc.otm.core.collect.map
 import ru.dbotthepony.mc.otm.core.collect.reduce
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.client.render.vertex
 import ru.dbotthepony.mc.otm.milliTime
 import java.util.function.Predicate
 import kotlin.math.roundToInt
@@ -1204,16 +1206,14 @@ open class TextInputPanel<out S : Screen>(
 				RenderSystem.defaultBlendFunc()
 				RenderSystem.enableBlend()
 
-				val builder = tesselator.builder
+				val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
 
-				builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
+				builder.vertex(stack.last().pose(), x, y + font.lineHeight + rowSpacing, 0f)
+				builder.vertex(stack.last().pose(), x + width, y + font.lineHeight + rowSpacing, 0f)
+				builder.vertex(stack.last().pose(), x + width, y, 0f)
+				builder.vertex(stack.last().pose(), x, y, 0f)
 
-				builder.vertex(stack.last().pose(), x, y + font.lineHeight + rowSpacing, 0f).endVertex()
-				builder.vertex(stack.last().pose(), x + width, y + font.lineHeight + rowSpacing, 0f).endVertex()
-				builder.vertex(stack.last().pose(), x + width, y, 0f).endVertex()
-				builder.vertex(stack.last().pose(), x, y, 0f).endVertex()
-
-				tesselator.end()
+				BufferUploader.drawWithShader(builder.buildOrThrow())
 
 				RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
 				//RenderSystem.disableColorLogicOp()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt
index e31357af2..ce1f04d26 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt
@@ -5,7 +5,7 @@ import net.minecraft.client.gui.screens.Screen.getTooltipFromItem
 import net.minecraft.client.renderer.GameRenderer
 import net.minecraft.network.chat.Component
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.client.extensions.common.IClientItemExtensions
+import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions
 import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.WidgetLocation
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt
index d36d53db7..9130dd5f5 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt
@@ -7,7 +7,7 @@ import net.minecraft.world.inventory.Slot
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Items
-import net.minecraftforge.client.extensions.common.IClientItemExtensions
+import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.isCtrlDown
 import ru.dbotthepony.mc.otm.client.minecraft
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollBarConstants.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollBarConstants.kt
index 5309d2144..b079bdbc1 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollBarConstants.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollBarConstants.kt
@@ -4,6 +4,7 @@ import net.minecraft.resources.ResourceLocation
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.client.render.sprites.MatteryAtlas
 import ru.dbotthepony.mc.otm.client.render.sprites.sprite
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 object ScrollBarConstants {
 	const val WIDTH = 14f
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt
index 387ef43af..7adcf7fd6 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt
@@ -9,11 +9,12 @@ import net.minecraft.client.Minecraft
 import net.minecraft.network.chat.Component
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.item.ItemStack
+import net.neoforged.neoforge.network.PacketDistributor
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.android.AndroidResearch
 import ru.dbotthepony.mc.otm.android.AndroidResearchManager
 import ru.dbotthepony.mc.otm.android.AndroidResearchType
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.client.CursorType
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
@@ -32,9 +33,8 @@ import ru.dbotthepony.mc.otm.client.screen.panels.util.DraggableCanvasPanel
 import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel
 import ru.dbotthepony.mc.otm.config.MachinesConfig
 import ru.dbotthepony.kommons.math.RGBAColor
-import ru.dbotthepony.mc.otm.core.ifPresentK
+import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
-import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
 import ru.dbotthepony.mc.otm.network.AndroidResearchRequestPacket
 import java.util.*
 import kotlin.collections.ArrayList
@@ -163,7 +163,7 @@ private class Tree(val node: AndroidResearchType) : Iterable<Tree> {
 	fun put(
 		rows: Int2ObjectFunction<EditablePanel<AndroidStationScreen>>,
 		left: Float,
-		capability: MatteryPlayerCapability
+		capability: MatteryPlayer
 	): Pair<AndroidResearchButton, Float> {
 		val totalWidth = width * 24f
 
@@ -395,13 +395,13 @@ private class AndroidResearchButton(
 							*node.type.flatBlocking.map { it.displayName }.toTypedArray()
 						),
 						onConfirm = {
-							MatteryPlayerNetworkChannel.sendToServer(AndroidResearchRequestPacket(node.type))
+							PacketDistributor.sendToServer(AndroidResearchRequestPacket(node.type))
 						}
 					)
 
 					playGuiClickSound()
 				} else {
-					MatteryPlayerNetworkChannel.sendToServer(AndroidResearchRequestPacket(node.type))
+					PacketDistributor.sendToServer(AndroidResearchRequestPacket(node.type))
 					playGuiClickSound()
 				}
 			}
@@ -535,7 +535,7 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I
 			}
 		}
 
-		minecraft?.player?.getCapability(MatteryCapability.MATTERY_PLAYER)?.ifPresentK {
+		minecraft?.player?.matteryPlayer?.let {
 			var totalWidth = 0f
 
 			for (graph in findGraphs()) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt
index 970f19148..3f92975fd 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt
@@ -23,6 +23,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls
 import ru.dbotthepony.mc.otm.client.screen.panels.input.TextInputPanel
 import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
 import ru.dbotthepony.mc.otm.client.screen.panels.util.HorizontalStripPanel
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.util.getLevelFromXp
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt
index 0a3f10eee..12cf5c213 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt
@@ -1,28 +1,31 @@
 package ru.dbotthepony.mc.otm.client.screen.widget
 
 import com.mojang.blaze3d.systems.RenderSystem
+import com.mojang.blaze3d.vertex.BufferUploader
 import com.mojang.blaze3d.vertex.DefaultVertexFormat
 import com.mojang.blaze3d.vertex.VertexFormat
 import net.minecraft.ChatFormatting
 import net.minecraft.client.gui.screens.Screen
 import net.minecraft.client.renderer.GameRenderer
 import net.minecraft.network.chat.Component
-import net.minecraft.resources.ResourceLocation
 import net.minecraft.world.inventory.InventoryMenu
-import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions
-import net.minecraftforge.fml.ModList
+import net.neoforged.fml.ModList
+import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions
 import org.lwjgl.opengl.GL11
+import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.client.ShiftPressedCond
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite
 import ru.dbotthepony.mc.otm.client.render.tesselator
+import ru.dbotthepony.mc.otm.client.render.uv
+import ru.dbotthepony.mc.otm.client.render.vertex
 import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.isNotEmpty
-import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.core.math.linearInterpolation
 import ru.dbotthepony.mc.otm.core.registryName
 import ru.dbotthepony.mc.otm.core.util.formatFluidLevel
@@ -71,9 +74,7 @@ open class FluidGaugePanel<out S : Screen>(
 			var bottom = this.height
 
 			val matrix = graphics.pose.last().pose()
-			val builder = tesselator.builder
-
-			builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
+			val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
 
 			while (height > 0.01f) {
 				val thisHeight = height.coerceAtMost(1f)
@@ -82,10 +83,10 @@ open class FluidGaugePanel<out S : Screen>(
 
 				val interp = linearInterpolation(thisHeight, sprite.v1, sprite.v0)
 
-				builder.vertex(matrix, 0f, bottom, 0f).uv(sprite.u0, sprite.v1).endVertex()
-				builder.vertex(matrix, width, bottom, 0f).uv(sprite.u1, sprite.v1).endVertex()
-				builder.vertex(matrix, width, bottom - actualHeight, 0f).uv(sprite.u1, interp).endVertex()
-				builder.vertex(matrix, 0f, bottom - actualHeight, 0f).uv(sprite.u0, interp).endVertex()
+				builder.vertex(matrix, 0f, bottom, 0f).uv(sprite.u0, sprite.v1)
+				builder.vertex(matrix, width, bottom, 0f).uv(sprite.u1, sprite.v1)
+				builder.vertex(matrix, width, bottom - actualHeight, 0f).uv(sprite.u1, interp)
+				builder.vertex(matrix, 0f, bottom - actualHeight, 0f).uv(sprite.u0, interp)
 
 				bottom -= actualHeight
 			}
@@ -98,7 +99,7 @@ open class FluidGaugePanel<out S : Screen>(
 			RenderSystem.setShaderColor(tint.red, tint.green, tint.blue, tint.alpha)
 			RenderSystem.setShaderTexture(0, sprite.atlasLocation())
 
-			tesselator.end()
+			BufferUploader.drawWithShader(builder.buildOrThrow())
 
 			RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
 			RenderSystem.depthFunc(GL11.GL_LESS)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt
index cd05ee603..973ef7d45 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt
@@ -18,6 +18,8 @@ import ru.dbotthepony.mc.otm.client.isShiftDown
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.client.render.WidgetLocation
 import ru.dbotthepony.mc.otm.client.render.tesselator
+import ru.dbotthepony.mc.otm.client.render.uv
+import ru.dbotthepony.mc.otm.client.render.vertex
 import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
 import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
@@ -111,12 +113,10 @@ open class MatterGaugePanel<out S : Screen> @JvmOverloads constructor(
 			val v1 = GAUGE_FOREGROUND.v1
 
 			val matrix = graphics.pose.last().pose()
-			val builder = tesselator.builder
+			val builder = tesselator.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_TEX)
 
-			builder.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_TEX)
-
-			builder.vertex(matrix, 0f, height, 0f).uv(u0, v1).endVertex()
-			builder.vertex(matrix, width, height, 0f).uv(u1, v1).endVertex()
+			builder.vertex(matrix, 0f, height, 0f).uv(u0, v1)
+			builder.vertex(matrix, width, height, 0f).uv(u1, v1)
 
 			for (i in 4 downTo 0) {
 				val sin = sin((System.currentTimeMillis() / 50L + i * 2L).toDouble() / 4.0).toFloat() * wavesStrength.coerceAtMost(4f)
@@ -128,10 +128,10 @@ open class MatterGaugePanel<out S : Screen> @JvmOverloads constructor(
 				builder.vertex(matrix, thisX, thisY, 0f).uv(
 					GAUGE_FOREGROUND.partialU((i / 4f) * GAUGE_FOREGROUND.width),
 					GAUGE_FOREGROUND.partialV(thisY),
-				).endVertex()
+				)
 			}
 
-			BufferUploader.drawWithShader(builder.end())
+			BufferUploader.drawWithShader(builder.buildOrThrow())
 			graphics.pose.popPose()
 		}
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/adastra/AdAstraCompat.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/adastra/AdAstraCompat.kt
deleted file mode 100644
index b29c3359e..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/adastra/AdAstraCompat.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package ru.dbotthepony.mc.otm.compat.adastra
-
-import earth.terrarium.ad_astra.AdAstra
-import earth.terrarium.ad_astra.common.data.Planet
-import earth.terrarium.ad_astra.common.data.PlanetData
-import earth.terrarium.ad_astra.common.item.armor.SpaceSuit
-import earth.terrarium.ad_astra.common.registry.ModDamageSources
-import net.minecraft.world.entity.player.Player
-import net.minecraftforge.event.entity.living.LivingHurtEvent
-import net.minecraftforge.fml.ModList
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
-import ru.dbotthepony.mc.otm.capability.matteryPlayer
-import ru.dbotthepony.mc.otm.config.ServerCompatConfig
-import ru.dbotthepony.mc.otm.core.damageType
-import ru.dbotthepony.mc.otm.registry.MDamageTypes
-import ru.dbotthepony.mc.otm.registry.MItems
-import ru.dbotthepony.mc.otm.registry.MatteryDamageSource
-
-val isAdAstraLoaded by lazy {
-	ModList.get().isLoaded(AdAstra.MOD_ID)
-}
-
-// для надёжности
-fun onDamageEvent(event: LivingHurtEvent) {
-	check(isAdAstraLoaded) { "Ad Astra is not loaded!" }
-	val ply = event.entity as? Player ?: return
-
-	if (ServerCompatConfig.AdAstra.ANDROIDS_DO_NOT_NEED_OXYGEN) {
-		if (ply.matteryPlayer?.isAndroid != true) return
-
-		if (event.source.`is`(ModDamageSources.OXYGEN)) {
-			event.amount = 0f
-			event.isCanceled = true
-		}
-	}
-}
-
-fun onMatteryTick(event: MatteryPlayerCapability.PostTick) {
-	check(isAdAstraLoaded) { "Ad Astra is not loaded!" }
-
-	if (
-		ServerCompatConfig.AdAstra.ANDROID_COSMIC_RAYS &&
-		!event.player.abilities.invulnerable &&
-		event.capability.isAndroid &&
-		!PlanetData.getPlanetFromLevel(event.level.dimension()).map(Planet::hasAtmosphere).orElse(true)
-	) {
-		val rand = event.level.random
-
-		val noSpacesuits = event.player.armorSlots.count { it.item !is SpaceSuit }
-		val yesTritanium0 = if (!ServerCompatConfig.AdAstra.TRITANIUM_ARMOR_PROTECTS_AGAINST_COSMIC_RAYS) 0.0 else event.player.armorSlots.count { it.item in MItems.TRITANIUM_ARMOR } * 0.75
-		val yesTritanium1 = if (!ServerCompatConfig.AdAstra.TRITANIUM_ARMOR_PROTECTS_AGAINST_COSMIC_RAYS) 0.0 else event.player.armorSlots.count { it.item in MItems.SIMPLE_TRITANIUM_ARMOR } * 0.2
-		val yesTritanium = yesTritanium0 + yesTritanium1
-
-		if (rand.nextDouble() <= (noSpacesuits - yesTritanium) * ServerCompatConfig.AdAstra.ANDROID_COSMIC_RAYS_CHANCE) {
-			event.player.hurt(MatteryDamageSource(event.level.registryAccess().damageType(MDamageTypes.COSMIC_RAYS)), 1f)
-		}
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt
index 9ad73c8cd..40146f476 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt
@@ -15,6 +15,7 @@ import net.minecraft.world.inventory.InventoryMenu
 import net.minecraft.world.inventory.Slot
 import net.minecraft.world.item.ItemStack
 import net.minecraftforge.fml.ModList
+import net.neoforged.fml.ModList
 import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt
index e56319174..21c6c37ea 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt
@@ -10,7 +10,6 @@ import ru.dbotthepony.mc.otm.compat.jade.JadeColors
 import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys
 import ru.dbotthepony.mc.otm.compat.jade.JadeUids
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.core.math.getDecimal
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt
index b13359070..41b80264f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt
@@ -9,7 +9,6 @@ import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys
 import ru.dbotthepony.mc.otm.compat.jade.JadeUids
 import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.core.nbt.getCompoundList
-import ru.dbotthepony.mc.otm.core.nbt.getItemStack
 import ru.dbotthepony.mc.otm.core.nbt.set
 import snownee.jade.api.BlockAccessor
 import snownee.jade.api.IBlockComponentProvider
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MicrowaveRecipeCategory.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MicrowaveRecipeCategory.kt
index 74e5f6872..6cd4b88c7 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MicrowaveRecipeCategory.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MicrowaveRecipeCategory.kt
@@ -19,6 +19,7 @@ import ru.dbotthepony.mc.otm.client.render.RenderGravity
 import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
 import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.registryName
 import ru.dbotthepony.mc.otm.recipe.MicrowaveRecipe
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt
index 50bb55cea..98213c09a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt
@@ -18,6 +18,7 @@ import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
 import ru.dbotthepony.mc.otm.client.render.ItemStackIcon
 import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
 import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.recipe.PainterRecipe
 import ru.dbotthepony.mc.otm.registry.MItems
 import ru.dbotthepony.mc.otm.registry.MNames
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PlatePressRecipeCategory.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PlatePressRecipeCategory.kt
index 5b6759e34..32613840e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PlatePressRecipeCategory.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PlatePressRecipeCategory.kt
@@ -19,6 +19,7 @@ import ru.dbotthepony.mc.otm.client.render.RenderGravity
 import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
 import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
 import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.registryName
 import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt
index 910a6933f..e5bb727c2 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt
@@ -6,6 +6,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
 import it.unimi.dsi.fastutil.objects.ReferenceArraySet
 import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.world.SimpleContainer
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.AbstractContainerMenu
@@ -13,13 +15,14 @@ import net.minecraft.world.inventory.InventoryMenu
 import net.minecraft.world.inventory.Slot
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Items
+import net.neoforged.neoforge.network.handling.IPayloadContext
 import org.apache.logging.log4j.LogManager
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.menu.MatteryMenu
 import ru.dbotthepony.mc.otm.menu.MatterySlot
-import ru.dbotthepony.mc.otm.network.MNetworkContext
-import ru.dbotthepony.mc.otm.network.MatteryPacket
 import java.util.*
 
 private val menuConfigurations = WeakHashMap<AbstractContainerMenu, MenuConfiguration>()
@@ -38,7 +41,7 @@ private class MenuConfiguration(
 	private val rows = Int2ObjectOpenHashMap<List<Slot>>()
 	val isIncomplete: Boolean
 
-	private fun getRow(index: Int, matteryPlayer: MatteryPlayerCapability) = rows.computeIfAbsent(index, Int2ObjectFunction {
+	private fun getRow(index: Int, matteryPlayer: MatteryPlayer) = rows.computeIfAbsent(index, Int2ObjectFunction {
 		val row = ArrayList<Slot>(9)
 		val offset = (it - 3) * 9
 
@@ -197,8 +200,8 @@ private class FakeSlot : MatterySlot(SimpleContainer(1), 0, 0, 0) {
 	}
 }
 
-class InventoryScrollPacket(val scroll: Int) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
+class InventoryScrollPacket(val scroll: Int) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeVarInt(scroll)
 	}
 
@@ -223,11 +226,25 @@ class InventoryScrollPacket(val scroll: Int) : MatteryPacket {
 		}.scroll = scroll
 	}
 
-	override fun play(context: MNetworkContext) {
-		play(context.sender ?: throw IllegalStateException("Illegal side"))
+	fun play(context: IPayloadContext) {
+		play(context.player())
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
 	}
 
 	companion object {
+		val TYPE = CustomPacketPayload.Type<InventoryScrollPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"inventory_scroll"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, InventoryScrollPacket> =
+			StreamCodec.ofMember(InventoryScrollPacket::write, ::read)
+
 		fun read(buff: FriendlyByteBuf): InventoryScrollPacket {
 			return InventoryScrollPacket(buff.readVarInt())
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt
index 446cb3fb3..d5ef52363 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt
@@ -1,6 +1,5 @@
 package ru.dbotthepony.mc.otm.compat.vanilla
 
-import net.minecraft.client.gui.screens.MenuScreens
 import net.minecraft.core.registries.Registries
 import net.minecraft.world.Container
 import net.minecraft.world.SimpleContainer
@@ -8,15 +7,13 @@ import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.flag.FeatureFlags
 import net.minecraft.world.inventory.MenuType
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
-import net.minecraftforge.registries.DeferredRegister
-import ru.dbotthepony.kommons.util.getValue
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.core.getValue
 import ru.dbotthepony.mc.otm.menu.MatteryMenu
 import ru.dbotthepony.mc.otm.menu.MatterySlot
 import ru.dbotthepony.mc.otm.menu.makeSlots
+import ru.dbotthepony.mc.otm.registry.MDeferredRegister
 
 class MatteryChestMenu(
 	type: MenuType<*>, containerId: Int,
@@ -43,7 +40,7 @@ class MatteryChestMenu(
 	}
 
 	companion object {
-		private val registrar = DeferredRegister.create(Registries.MENU, OverdriveThatMatters.MOD_ID)
+		private val registrar = MDeferredRegister(Registries.MENU, OverdriveThatMatters.MOD_ID)
 
 		private val GENERIC_9x1 by registrar.register("generic_9x1") { MenuType(::c9x1, FeatureFlags.VANILLA_SET) }
 		private val GENERIC_9x2 by registrar.register("generic_9x2") { MenuType(::c9x2, FeatureFlags.VANILLA_SET) }
@@ -102,22 +99,20 @@ class MatteryChestMenu(
 			return MatteryChestMenu(HOPPER, containerId, inventory, 1, 5, container)
 		}
 
-		internal fun register(bus: IEventBus) {
+		fun register(bus: IEventBus) {
 			registrar.register(bus)
-			bus.addListener(this::registerClient)
+			bus.addListener(this::registerScreens)
 		}
 
-		private fun registerClient(event: FMLClientSetupEvent) {
-			event.enqueueWork {
-				MenuScreens.register(GENERIC_9x1, ::MatteryChestScreen)
-				MenuScreens.register(GENERIC_9x2, ::MatteryChestScreen)
-				MenuScreens.register(GENERIC_9x3, ::MatteryChestScreen)
-				MenuScreens.register(GENERIC_9x4, ::MatteryChestScreen)
-				MenuScreens.register(GENERIC_9x5, ::MatteryChestScreen)
-				MenuScreens.register(GENERIC_9x6, ::MatteryChestScreen)
-				MenuScreens.register(GENERIC_3x3, ::MatteryChestScreen)
-				MenuScreens.register(HOPPER, ::MatteryChestScreen)
-			}
+		private fun registerScreens(event: RegisterMenuScreensEvent) {
+			event.register(GENERIC_9x1, ::MatteryChestScreen)
+			event.register(GENERIC_9x2, ::MatteryChestScreen)
+			event.register(GENERIC_9x3, ::MatteryChestScreen)
+			event.register(GENERIC_9x4, ::MatteryChestScreen)
+			event.register(GENERIC_9x5, ::MatteryChestScreen)
+			event.register(GENERIC_9x6, ::MatteryChestScreen)
+			event.register(GENERIC_3x3, ::MatteryChestScreen)
+			event.register(HOPPER, ::MatteryChestScreen)
 		}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt
index d56270c19..98e7277eb 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt
@@ -1,19 +1,18 @@
 package ru.dbotthepony.mc.otm.config
 
-import net.minecraftforge.common.ForgeConfigSpec
-import net.minecraftforge.fml.ModLoadingContext
-import net.minecraftforge.fml.config.ModConfig
+import net.neoforged.fml.ModContainer
+import net.neoforged.fml.config.ModConfig
+import net.neoforged.neoforge.common.ModConfigSpec
 import ru.dbotthepony.kommons.util.Delegate
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.core.getValue
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.math.defineDecimal
 import ru.dbotthepony.mc.otm.core.util.WriteOnce
 
 abstract class AbstractConfig(private val configName: String, private val type: ModConfig.Type = ModConfig.Type.SERVER) {
-	private var spec: ForgeConfigSpec by WriteOnce()
-	protected val builder = ForgeConfigSpec.Builder()
+	private var spec: ModConfigSpec by WriteOnce()
+	protected val builder = ModConfigSpec.Builder()
 	private var registered = false
 
 	fun batteryValues(name: String, storage: Decimal, receive: Decimal, extract: Decimal = receive, initialBatteryLevel: Decimal = Decimal.ZERO): BatteryBalanceValues {
@@ -31,7 +30,7 @@ abstract class AbstractConfig(private val configName: String, private val type:
 		return obj
 	}
 
-	fun conciseValues(name: String, storage: Decimal, throughput: Decimal, configurator: ForgeConfigSpec.Builder.() -> Unit = {}): EnergyBalanceValues {
+	fun conciseValues(name: String, storage: Decimal, throughput: Decimal, configurator: ModConfigSpec.Builder.() -> Unit = {}): EnergyBalanceValues {
 		builder.push(name)
 
 		val obj = object : EnergyBalanceValues {
@@ -53,7 +52,7 @@ abstract class AbstractConfig(private val configName: String, private val type:
 		energyConsumption: Decimal?,
 		matterCapacity: Decimal? = null,
 		maxExperience: Double? = null,
-		configurator: ForgeConfigSpec.Builder.() -> Unit = {}
+		configurator: ModConfigSpec.Builder.() -> Unit = {}
 	): WorkerBalanceValues {
 		builder.push(name)
 
@@ -72,10 +71,10 @@ abstract class AbstractConfig(private val configName: String, private val type:
 		return obj
 	}
 
-	fun register() {
+	fun register(container: ModContainer) {
 		check(!registered) { "Already registered config" }
 		registered = true
 		spec = builder.build()
-		ModLoadingContext.get().registerConfig(type, spec, "${OverdriveThatMatters.MOD_ID}-$configName.toml")
+		container.registerConfig(type, spec, "${OverdriveThatMatters.MOD_ID}-$configName.toml")
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ConfigExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ConfigExt.kt
index 9febd526a..245d92661 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ConfigExt.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ConfigExt.kt
@@ -1,20 +1,20 @@
 package ru.dbotthepony.mc.otm.config
 
-import net.minecraftforge.common.ForgeConfigSpec
+import net.neoforged.neoforge.common.ModConfigSpec
 import kotlin.reflect.KProperty
 
-operator fun <T> ForgeConfigSpec.ConfigValue<T>.setValue(config: Any, property: KProperty<*>, value: T) {
+operator fun <T : Any> ModConfigSpec.ConfigValue<T>.setValue(config: Any, property: KProperty<*>, value: T) {
 	set(value)
 }
 
-operator fun <T : Any> ForgeConfigSpec.ConfigValue<T>.getValue(config: Any, property: KProperty<*>): T {
+operator fun <T : Any> ModConfigSpec.ConfigValue<T>.getValue(config: Any, property: KProperty<*>): T {
 	return get()
 }
 
-fun ForgeConfigSpec.Builder.defineInRange(path: String, value: Int, minValue: Int): ForgeConfigSpec.IntValue {
+fun ModConfigSpec.Builder.defineInRange(path: String, value: Int, minValue: Int): ModConfigSpec.IntValue {
 	return defineInRange(path, value, minValue, Int.MAX_VALUE)
 }
 
-fun ForgeConfigSpec.Builder.defineInRange(path: String, value: Double, minValue: Double): ForgeConfigSpec.DoubleValue {
+fun ModConfigSpec.Builder.defineInRange(path: String, value: Double, minValue: Double): ModConfigSpec.DoubleValue {
 	return defineInRange(path, value, minValue, Double.MAX_VALUE)
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ObservedConfigValue.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ObservedConfigValue.kt
index ca69d1fba..e9c1efed6 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ObservedConfigValue.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ObservedConfigValue.kt
@@ -1,10 +1,10 @@
 package ru.dbotthepony.mc.otm.config
 
-import net.minecraftforge.common.ForgeConfigSpec.ConfigValue
+import net.neoforged.neoforge.common.ModConfigSpec
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.kommons.util.Delegate
 
-abstract class ObservedConfigValue<V : Any>(val parent: ConfigValue<String>) : Delegate<V> {
+abstract class ObservedConfigValue<V : Any>(val parent: ModConfigSpec.ConfigValue<String>) : Delegate<V> {
 	var rawValue: String by parent
 	private var observedValue: String? = null
 	private var cachedValue: V? = null
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt
index eefa76b79..f3c21eb2d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt
@@ -1,7 +1,7 @@
 package ru.dbotthepony.mc.otm.container
 
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.items.IItemHandler
+import net.neoforged.neoforge.items.IItemHandler
 
 class ContainerHandler(
 	private val container: IMatteryContainer,
@@ -34,7 +34,7 @@ class ContainerHandler(
 			} else {
 				return stack.copyWithCount(stack.count - amount)
 			}
-		} else if (localStack.isStackable && container.getMaxStackSize(slot, localStack) > localStack.count && ItemStack.isSameItemSameTags(localStack, stack)) {
+		} else if (localStack.isStackable && container.getMaxStackSize(slot, localStack) > localStack.count && ItemStack.isSameItemSameComponents(localStack, stack)) {
 			val newCount = container.getMaxStackSize(slot, localStack).coerceAtMost(localStack.count + stack.count.coerceAtMost(amount))
 			val diff = newCount - localStack.count
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt
index c50cd7b5c..5750aa75d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt
@@ -15,8 +15,9 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList
 import net.minecraft.world.Container
 import net.minecraft.world.inventory.CraftingContainer
 import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.enchantment.EnchantmentHelper.hasVanishingCurse
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.minecraft.world.item.enchantment.EnchantmentEffectComponents
+import net.minecraft.world.item.enchantment.EnchantmentHelper
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.container.util.IContainerSlot
 import ru.dbotthepony.mc.otm.container.util.ItemStackHashStrategy
 import ru.dbotthepony.mc.otm.container.util.containerSlot
@@ -74,7 +75,7 @@ fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable =
 	while (i.hasNext()) {
 		val slot = i.nextInt()
 
-		if (ItemStack.isSameItemSameTags(this[slot], copy)) {
+		if (ItemStack.isSameItemSameComponents(this[slot], copy)) {
 			val slotStack = this[slot]
 			val slotLimit = maxStackSize.coerceAtMost(slotStack.maxStackSize)
 
@@ -125,7 +126,7 @@ fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable =
 
 fun Container.vanishCursedItems() {
 	for (slot in slotIterator()) {
-		if (hasVanishingCurse(slot.item)) {
+		if (EnchantmentHelper.has(slot.item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
 			slot.remove()
 		}
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt
index eadb8a8e0..dbe17de88 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt
@@ -1,8 +1,7 @@
 package ru.dbotthepony.mc.otm.container
 
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.ForgeHooks
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.fluid.stream
 import ru.dbotthepony.mc.otm.core.isNotEmpty
@@ -52,13 +51,13 @@ interface HandlerFilter {
 
 	object FluidContainers : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).map { it.tanks > 0 }.orElse(false)
+			return stack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.tanks > 0 } ?: false
 		}
 	}
 
 	object DrainableFluidContainers : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).map { it.stream().anyMatch { it.isNotEmpty } }.orElse(false)
+			return stack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.stream().anyMatch { it.isNotEmpty } } ?: false
 		}
 
 		override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
@@ -90,65 +89,65 @@ interface HandlerFilter {
 
 	object Dischargeable : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(ForgeCapabilities.ENERGY).map { it.canExtract() && it.extractEnergy(Int.MAX_VALUE, true) > 0 }.orElse(false)
+			return stack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { it.canExtract() && it.extractEnergy(Int.MAX_VALUE, true) > 0 } ?: false
 		}
 
 		override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(ForgeCapabilities.ENERGY).map { !it.canExtract() || it.extractEnergy(Int.MAX_VALUE, true) <= 0 }.orElse(true)
+			return stack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { !it.canExtract() || it.extractEnergy(Int.MAX_VALUE, true) <= 0 } ?: false
 		}
 	}
 
 	object Chargeable : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(ForgeCapabilities.ENERGY).map { it.canReceive() && it.receiveEnergy(Int.MAX_VALUE, true) > 0 }.orElse(false)
+			return stack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { it.canReceive() && it.receiveEnergy(Int.MAX_VALUE, true) > 0 } ?: false
 		}
 
 		override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(ForgeCapabilities.ENERGY).map { !it.canReceive() || it.receiveEnergy(Int.MAX_VALUE, true) <= 0 }.orElse(true)
+			return stack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { !it.canReceive() || it.receiveEnergy(Int.MAX_VALUE, true) <= 0 } ?: false
 		}
 	}
 
 	object ChemicalFuel : HandlerFilter {
 		override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
-			return ForgeHooks.getBurnTime(stack, null) <= 0
+			return stack.getBurnTime(null) <= 0
 		}
 
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return ForgeHooks.getBurnTime(stack, null) > 0
+			return stack.getBurnTime(null) > 0
 		}
 	}
 
 	object IsPattern : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(MatteryCapability.PATTERN).isPresent
+			return stack.getCapability(MatteryCapability.PATTERN_ITEM) != null
 		}
 	}
 
 	object MatterProviders : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(MatteryCapability.MATTER)
-				.map { it.matterFlow.output && it.extractMatterChecked(Decimal.POSITIVE_INFINITY, true) > Decimal.ZERO }
-				.orElse(false)
+			return stack.getCapability(MatteryCapability.MATTER_ITEM)
+				?.let { it.matterFlow.output && it.extractMatterChecked(Decimal.POSITIVE_INFINITY, true) > Decimal.ZERO }
+				?: false
 		}
 
 		override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(MatteryCapability.MATTER)
-				.map { !it.matterFlow.output || it.extractMatterChecked(Decimal.POSITIVE_INFINITY, true) <= Decimal.ZERO }
-				.orElse(true)
+			return stack.getCapability(MatteryCapability.MATTER_ITEM)
+				?.let { !it.matterFlow.output || it.extractMatterChecked(Decimal.POSITIVE_INFINITY, true) <= Decimal.ZERO }
+				?: false
 		}
 	}
 
 	object MatterConsumers : HandlerFilter {
 		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(MatteryCapability.MATTER)
-				.map { it.matterFlow.input && it.receiveMatterChecked(Decimal.POSITIVE_INFINITY, true) > Decimal.ZERO }
-				.orElse(false)
+			return stack.getCapability(MatteryCapability.MATTER_ITEM)
+				?.let { it.matterFlow.input && it.receiveMatterChecked(Decimal.POSITIVE_INFINITY, true) > Decimal.ZERO }
+				?: false
 		}
 
 		override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
-			return stack.getCapability(MatteryCapability.MATTER)
-				.map { !it.matterFlow.input || it.receiveMatterChecked(Decimal.POSITIVE_INFINITY, true) <= Decimal.ZERO }
-				.orElse(true)
+			return stack.getCapability(MatteryCapability.MATTER_ITEM)
+				?.let { !it.matterFlow.input || it.receiveMatterChecked(Decimal.POSITIVE_INFINITY, true) <= Decimal.ZERO }
+				?: false
 		}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt
index fcc12e259..7010e7038 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt
@@ -220,4 +220,14 @@ interface IMatteryContainer : IContainer, RecipeInput, Iterable<ItemStack> {
 
 		return addItem(stack, false, slots, ignoreFilters).isEmpty
 	}
+
+	fun toList(): MutableList<ItemStack> {
+		val list = ArrayList<ItemStack>(size())
+
+		for (i in 0 until size()) {
+			list[i] = this[i]
+		}
+
+		return list
+	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt
index 5bd791cb2..58be92692 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt
@@ -1,97 +1,72 @@
 package ru.dbotthepony.mc.otm.container
 
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.nbt.ListTag
+import com.google.common.collect.ImmutableList
+import com.mojang.serialization.Codec
+import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.tags.TagKey
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.util.INBTSerializable
-import ru.dbotthepony.mc.otm.core.nbt.map
-import ru.dbotthepony.mc.otm.core.nbt.set
-import java.util.*
-import java.util.function.Consumer
 
-class ItemFilter(
-	val size: Int,
-	private val modified: Consumer<ItemFilter>? = null
-) : INBTSerializable<CompoundTag?> {
-	private val filter = Array<ItemStack>(size) { ItemStack.EMPTY }
-	private val linkedFilter = LinkedList<ItemStack>()
+data class ItemFilter(val filter: List<ItemStack>, val isWhitelist: Boolean = false, val matchTag: Boolean = false, val matchComponents: Boolean = false) {
+	fun add(item: ItemStack): ItemFilter {
+		if (filter.any { ItemStack.isSameItemSameComponents(it, item) })
+			return this
 
-	var isLocked = false
-
-	var isWhitelist = false
-		set(value) {
-			if (value != field) {
-				field = value
-				modified?.accept(this)
-			}
-		}
-
-	var matchTag = false
-		set(value) {
-			if (value != field) {
-				field = value
-				modified?.accept(this)
-			}
-		}
-
-	var matchNBT = false
-		set(value) {
-			if (value != field) {
-				field = value
-				modified?.accept(this)
-			}
-		}
-
-	fun clear() {
-		isWhitelist = false
-		matchTag = false
-		matchNBT = false
-
-		Arrays.fill(filter, ItemStack.EMPTY)
-		linkedFilter.clear()
-
-		modified?.accept(this)
+		return copy(
+			filter = ArrayList(filter).also { it.add(item) }
+		)
 	}
 
-	operator fun set(index: Int, value: ItemStack) {
-		if (value.isEmpty && filter[index].isEmpty) {
-			return
-		}
+	fun remove(item: ItemStack): ItemFilter {
+		val indexOf = filter.indexOfFirst { ItemStack.isSameItemSameComponents(it, item) }
 
-		val old = filter[index]
-		filter[index] = value.let { if (!it.isEmpty) it.copy().also { it.count = 1 } else it }
+		if (indexOf == -1)
+			return this
 
-		if (!filter[index].isEmpty && filter[index].tag != null && filter[index].tag!!.isEmpty) {
-			filter[index].tag = null
-		}
-
-		if (!old.isEmpty)
-			linkedFilter.remove(old)
-
-		if (!filter[index].isEmpty)
-			linkedFilter.add(filter[index])
-
-		modified?.accept(this)
+		return copy(
+			filter = ArrayList(filter).also { it.removeAt(indexOf) }
+		)
 	}
 
-	operator fun get(index: Int): ItemStack = filter[index].copy()
+	fun get(index: Int): ItemStack {
+		return filter.getOrElse(index) { ItemStack.EMPTY }
+	}
+
+	fun isWhitelist(flag: Boolean): ItemFilter {
+		if (flag == isWhitelist)
+			return this
+		else
+			return copy(isWhitelist = flag)
+	}
+
+	fun matchTag(flag: Boolean): ItemFilter {
+		if (flag == matchTag)
+			return this
+		else
+			return copy(matchTag = flag)
+	}
+
+	fun matchComponents(flag: Boolean): ItemFilter {
+		if (flag == matchComponents)
+			return this
+		else
+			return copy(matchComponents = flag)
+	}
 
 	fun match(value: ItemStack): Boolean {
 		if (value.isEmpty) {
 			return false
 		}
 
-		if (linkedFilter.isEmpty()) {
+		if (filter.isEmpty()) {
 			return !isWhitelist
 		}
 
-		for (item in linkedFilter) {
-			var matched = item.`is`(value.item)
+		for (item in filter) {
+			var matches = item.`is`(value.item)
 
-			if (matched && matchTag) {
-				matched = false
+			if (matches && matchTag) {
+				matches = false
 
 				val thisTags = item.tags
 				val stackTags = HashSet<TagKey<Item>>()
@@ -102,28 +77,17 @@ class ItemFilter(
 
 				for (tag1 in thisTags) {
 					if (stackTags.contains(tag1)) {
-						matched = true
+						matches = true
 						break
 					}
 				}
 			}
 
-			if (matched && matchNBT) {
-				val a = item.tag
-				val b = value.tag
-
-				if (a == null && b == null) {
-					// nothing
-				} else if (a != null && b != null) {
-					matched = a == b
-				} else if (a != null) {
-					matched = a.isEmpty
-				} else {
-					matched = false
-				}
+			if (matches && matchComponents) {
+				matches = item.components == value.components
 			}
 
-			if (matched) {
+			if (matches) {
 				return isWhitelist
 			}
 		}
@@ -131,41 +95,18 @@ class ItemFilter(
 		return !isWhitelist
 	}
 
-	override fun serializeNBT(): CompoundTag {
-		return CompoundTag().also {
-			it["items"] = ListTag().also {
-				for (value in filter) {
-					it.add(value.serializeNBT())
-				}
-			}
+	companion object {
+		val EMPTY = ItemFilter(ImmutableList.of())
 
-			it["is_whitelist"] = isWhitelist
-			it["match_tag"] = matchTag
-			it["match_nbt"] = matchNBT
-		}
-	}
-
-	override fun deserializeNBT(nbt: CompoundTag?) {
-		for (i in filter.indices)
-			filter[i] = ItemStack.EMPTY
-
-		if (nbt == null)
-			return
-
-		nbt.map("items") { it: ListTag ->
-			for ((i, value) in it.withIndex()) {
-				if (value is CompoundTag) {
-					filter[i] = ItemStack.of(value)
-
-					if (!filter[i].isEmpty) {
-						linkedFilter.add(filter[i])
-					}
-				}
+		val CODEC: Codec<ItemFilter> by lazy {
+			RecordCodecBuilder.create<ItemFilter> {
+				it.group(
+					Codec.list(ItemStack.CODEC, 0, 40).fieldOf("filter").forGetter { it.filter },
+					Codec.BOOL.optionalFieldOf("isWhitelist", false).forGetter { it.isWhitelist },
+					Codec.BOOL.optionalFieldOf("matchTag", false).forGetter { it.matchTag },
+					Codec.BOOL.optionalFieldOf("matchComponents", false).forGetter { it.matchComponents },
+				).apply(it, ::ItemFilter)
 			}
 		}
-
-		isWhitelist = nbt.getBoolean("is_whitelist")
-		matchTag = nbt.getBoolean("match_tag")
-		matchNBT = nbt.getBoolean("match_nbt")
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt
index 7c9a607a0..8984bcf4f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt
@@ -9,20 +9,19 @@ import it.unimi.dsi.fastutil.ints.IntComparators
 import it.unimi.dsi.fastutil.ints.IntSpliterator
 import it.unimi.dsi.fastutil.objects.ObjectSpliterators
 import net.minecraft.core.HolderLookup
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.world.item.ItemStack
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.ListTag
 import net.minecraft.nbt.NbtOps
 import net.minecraft.nbt.Tag
-import net.minecraft.resources.ResourceLocation
 import net.minecraft.world.Container
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.entity.player.StackedContents
 import net.minecraft.world.inventory.StackedContentsCompatible
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.Items
-import net.minecraftforge.common.util.INBTSerializable
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.neoforge.common.util.INBTSerializable
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.kommons.collect.ListenableMap
 import ru.dbotthepony.kommons.io.DelegateSyncher
@@ -35,7 +34,6 @@ import ru.dbotthepony.mc.otm.core.collect.emptyIterator
 import ru.dbotthepony.mc.otm.core.collect.filter
 import ru.dbotthepony.mc.otm.core.collect.map
 import ru.dbotthepony.mc.otm.core.isNotEmpty
-import ru.dbotthepony.mc.otm.core.nbt.map
 import ru.dbotthepony.mc.otm.core.nbt.set
 import ru.dbotthepony.mc.otm.core.registryName
 import ru.dbotthepony.mc.otm.core.util.ItemValueCodec
@@ -205,7 +203,7 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I
 		companion object {
 			val CODEC: Codec<SerializedFilter> = RecordCodecBuilder.create {
 				it.group(
-					ForgeRegistries.ITEMS.codec.fieldOf("item").forGetter { it.item },
+					BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter { it.item },
 					Codec.INT.minRange(0).fieldOf("slot").forGetter { it.slot },
 				).apply(it, ::SerializedFilter)
 			}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt
index 65831e744..036e7bc27 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt
@@ -16,9 +16,6 @@ open class MatteryCraftingContainer(listener: ContainerListener, private val wid
 	}
 
 	final override fun getItems(): MutableList<ItemStack> {
-		val i = spliterator()
-		val result = ArrayList<ItemStack>(i.estimateSize().toInt())
-		i.forEachRemaining { result.add(it) }
-		return result
+		return toList()
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt
index 9b178fa01..df8109ef5 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt
@@ -18,20 +18,20 @@ open class UpgradeContainer(slotCount: Int, open val allowedUpgrades: Set<Upgrad
 
 	protected fun positiveDecimals(fn: (IMatteryUpgrade) -> Decimal, reducer: (Decimal, Decimal) -> Decimal): Decimal {
 		return iterator()
-			.map { it.getCapability(MatteryCapability.UPGRADE).map(fn).orElse(Decimal.ZERO).moreThanZero() * it.count }
+			.map { (it.getCapability(MatteryCapability.UPGRADE)?.let(fn) ?: Decimal.ZERO).moreThanZero() * it.count }
 			.reduce(Decimal.ZERO, reducer)
 	}
 
 	protected fun anyDecimals(fn: (IMatteryUpgrade) -> Decimal, reducer: (Decimal, Decimal) -> Decimal): Decimal {
 		return iterator()
-			.map { it.getCapability(MatteryCapability.UPGRADE).map(fn).orElse(Decimal.ZERO) * it.count }
+			.map { (it.getCapability(MatteryCapability.UPGRADE)?.let(fn) ?: Decimal.ZERO) * it.count }
 			.reduce(Decimal.ZERO, reducer)
 	}
 
 	override val speedBonus: Double
-		get() = iterator().map { it.getCapability(MatteryCapability.UPGRADE).map { it.speedBonus }.orElse(0.0) * it.count }.reduce(0.0) { a, b -> a + b }
+		get() = iterator().map { (it.getCapability(MatteryCapability.UPGRADE)?.speedBonus ?: 0.0) * it.count }.reduce(0.0) { a, b -> a + b }
 	override val processingItems: Int
-		get() = iterator().map { it.getCapability(MatteryCapability.UPGRADE).map { it.processingItems }.orElse(0).coerceAtLeast(0) * it.count }.reduce(0) { a, b -> a + b }
+		get() = iterator().map { (it.getCapability(MatteryCapability.UPGRADE)?.processingItems ?: 0).coerceAtLeast(0) * it.count }.reduce(0) { a, b -> a + b }
 	override val energyStorageFlat: Decimal
 		get() = positiveDecimals(IMatteryUpgrade::energyStorageFlat, Decimal::plus)
 	override val energyStorage: Decimal
@@ -43,7 +43,7 @@ open class UpgradeContainer(slotCount: Int, open val allowedUpgrades: Set<Upgrad
 	override val energyConsumed: Decimal
 		get() = anyDecimals(IMatteryUpgrade::energyConsumed, Decimal::plus)
 	override val failureMultiplier: Double
-		get() = iterator().map { it.getCapability(MatteryCapability.UPGRADE).map { it.failureMultiplier }.orElse(1.0).coerceAtLeast(0.0).pow(it.count.toDouble()) }.reduce(1.0) { a, b -> a * b }
+		get() = iterator().map { (it.getCapability(MatteryCapability.UPGRADE)?.failureMultiplier ?: 1.0).coerceAtLeast(0.0).pow(it.count.toDouble()) }.reduce(1.0) { a, b -> a * b }
 	override val energyThroughputFlat: Decimal
 		get() = positiveDecimals(IMatteryUpgrade::energyThroughputFlat, Decimal::plus)
 	override val energyThroughput: Decimal
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemHandlerSpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemHandlerSpliterator.kt
index 9d7c27669..9227b3b39 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemHandlerSpliterator.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemHandlerSpliterator.kt
@@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.container.util
 import it.unimi.dsi.fastutil.objects.ObjectSpliterator
 import it.unimi.dsi.fastutil.objects.ObjectSpliterators
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.items.IItemHandler
+import net.neoforged.neoforge.items.IItemHandler
 import ru.dbotthepony.mc.otm.core.collect.AwareItemStack
 import ru.dbotthepony.mc.otm.core.collect.ItemHandlerItemStackEntry
 import ru.dbotthepony.mc.otm.core.collect.filter
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemStackHashStrategy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemStackHashStrategy.kt
index 5d635e5af..9ff9bae12 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemStackHashStrategy.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemStackHashStrategy.kt
@@ -5,11 +5,11 @@ import net.minecraft.world.item.ItemStack
 
 object ItemStackHashStrategy : Hash.Strategy<ItemStack> {
 	override fun equals(a: ItemStack?, b: ItemStack?): Boolean {
-		return a === b || a != null && b != null && ItemStack.isSameItemSameTags(a, b)
+		return a === b || a != null && b != null && ItemStack.isSameItemSameComponents(a, b)
 	}
 
 	override fun hashCode(o: ItemStack?): Int {
 		o ?: return 0
-		return o.item.hashCode().xor(o.tag.hashCode())
+		return o.item.hashCode().xor(o.components.hashCode())
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt
index 18a9e39d6..cb8641ab3 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt
@@ -13,19 +13,25 @@ import com.google.gson.JsonPrimitive
 import it.unimi.dsi.fastutil.objects.ObjectComparators
 import net.minecraft.Util
 import net.minecraft.core.BlockPos
+import net.minecraft.core.Holder
+import net.minecraft.core.HolderLookup
+import net.minecraft.core.HolderSet
+import net.minecraft.core.Registry
 import net.minecraft.core.SectionPos
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.nbt.NbtAccounter
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.RegistryFriendlyByteBuf
+import net.minecraft.network.chat.Component
 import net.minecraft.network.chat.ComponentContents
+import net.minecraft.network.chat.ComponentSerialization
 import net.minecraft.network.chat.contents.TranslatableContents
+import net.minecraft.resources.ResourceKey
 import net.minecraft.resources.ResourceLocation
+import net.minecraft.tags.TagKey
 import net.minecraft.world.entity.Entity
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.Items
 import net.minecraft.world.item.crafting.CraftingInput
-import net.minecraft.world.item.crafting.Ingredient
 import net.minecraft.world.item.crafting.RecipeInput
 import net.minecraft.world.level.BlockGetter
 import net.minecraft.world.level.Level
@@ -36,42 +42,25 @@ import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.level.block.state.StateHolder
 import net.minecraft.world.level.block.state.properties.Property
 import net.minecraft.world.phys.Vec3
-import net.minecraftforge.common.ForgeHooks
-import net.minecraftforge.common.util.LazyOptional
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.items.IItemHandler
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.registries.ForgeRegistry
-import net.minecraftforge.registries.IForgeRegistry
-import ru.dbotthepony.kommons.io.DelegateSyncher
-import ru.dbotthepony.kommons.io.StreamCodec
-import ru.dbotthepony.kommons.util.DelegateGetter
-import ru.dbotthepony.kommons.util.DelegateSetter
-import ru.dbotthepony.kommons.util.ListenableDelegate
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.items.IItemHandler
 import ru.dbotthepony.mc.otm.core.math.BlockRotation
 import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom
-import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.math.Vector
-import ru.dbotthepony.mc.otm.core.math.readDecimal
-import ru.dbotthepony.mc.otm.core.math.writeDecimal
-import ru.dbotthepony.mc.otm.core.util.DecimalValueCodec
-import ru.dbotthepony.mc.otm.core.util.readInt
 import ru.dbotthepony.mc.otm.core.util.readVarIntLE
-import ru.dbotthepony.mc.otm.core.util.writeInt
 import ru.dbotthepony.mc.otm.core.util.writeVarIntLE
 import java.io.InputStream
 import java.io.OutputStream
 import java.lang.ref.Reference
 import java.math.BigInteger
-import java.util.Arrays
-import java.util.Spliterators
-import java.util.UUID
+import java.util.*
 import java.util.concurrent.Callable
 import java.util.concurrent.Future
 import java.util.function.Consumer
 import java.util.function.Supplier
 import java.util.stream.Stream
 import java.util.stream.StreamSupport
+import kotlin.jvm.optionals.getOrNull
 import kotlin.reflect.KProperty
 
 operator fun RecipeInput.get(index: Int): ItemStack = getItem(index)
@@ -125,29 +114,6 @@ fun LevelAccessor.getBlockStateNow(pos: BlockPos): BlockState {
 	return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockState(pos) ?: Blocks.AIR.defaultBlockState()
 }
 
-fun <T> LazyOptional<T>.orNull(): T? {
-	if (!isPresent) {
-		return null
-	}
-
-	return resolve().orElse(null)
-}
-
-fun <T> LazyOptional<T>.orThrow(): T {
-	if (!isPresent) {
-		throw IllegalStateException("Capability was expected to be not null")
-	}
-
-	return resolve().get() ?: throw IllegalStateException("Capability was expected to be not null")
-}
-
-inline fun <T> LazyOptional<T>.ifPresentK(lambda: (T) -> Unit) {
-	if (isPresent) {
-		val value = resolve().orElse(null) ?: throw IllegalStateException("Capability was expected to be not null")
-		lambda.invoke(value)
-	}
-}
-
 inline val FluidStack.isNotEmpty get() = !isEmpty
 inline val ItemStack.isNotEmpty get() = !isEmpty
 
@@ -234,32 +200,36 @@ fun <V : Any> immutableList(a: V, vararg values: V): ImmutableList<V> {
 	return builder.build()
 }
 
-fun <T> IForgeRegistry<T>.getID(value: T): Int {
-	return (this as ForgeRegistry<T>).getID(value)
-}
-
-fun <T> IForgeRegistry<T>.getValue(index: Int): T? {
-	return (this as ForgeRegistry<T>).getValue(index)
-}
-
-fun IForgeRegistry<*>.getID(value: ResourceLocation): Int {
-	return (this as ForgeRegistry<*>).getID(value)
-}
-
 fun FriendlyByteBuf.writeItemType(value: Item) {
-	writeInt(ForgeRegistries.ITEMS.getID(value))
+	writeInt(BuiltInRegistries.ITEM.getId(value))
+}
+
+fun RegistryFriendlyByteBuf.writeItem(value: ItemStack) {
+	ItemStack.STREAM_CODEC.encode(this, value)
+}
+
+fun RegistryFriendlyByteBuf.writeComponent(value: Component) {
+	ComponentSerialization.STREAM_CODEC.encode(this, value)
 }
 
 fun OutputStream.writeItemType(value: Item) {
-	writeVarIntLE(ForgeRegistries.ITEMS.getID(value))
+	writeVarIntLE(BuiltInRegistries.ITEM.getId(value))
 }
 
 fun FriendlyByteBuf.readItemType(): Item {
-	return ForgeRegistries.ITEMS.getValue(readInt()) ?: Items.AIR
+	return BuiltInRegistries.ITEM.byId(readInt())
+}
+
+fun RegistryFriendlyByteBuf.readItem(): ItemStack {
+	return ItemStack.STREAM_CODEC.decode(this)
+}
+
+fun RegistryFriendlyByteBuf.readComponent(): Component {
+	return ComponentSerialization.STREAM_CODEC.decode(this)
 }
 
 fun InputStream.readItemType(): Item {
-	return ForgeRegistries.ITEMS.getValue(readVarIntLE()) ?: Items.AIR
+	return BuiltInRegistries.ITEM.byId(readVarIntLE())
 }
 
 operator fun <T : Comparable<T>> StateHolder<*, *>.get(property: Property<T>): T {
@@ -326,8 +296,6 @@ fun <E> Iterator<E>.stream(): Stream<out E> {
 	return StreamSupport.stream(Spliterators.spliteratorUnknownSize(this, 0), false)
 }
 
-val Ingredient.isActuallyEmpty: Boolean get() = ForgeHooks.hasNoElements(this)
-
 fun Entity.genericPositions(): Collection<Vector> {
 	return listOf(
 		position,
@@ -520,3 +488,21 @@ fun <A, B> lazy2(a: () -> A, b: A.() -> B): Supplier<B> {
 	return Supplier { b.invoke(first.value) }
 }
 
+fun <T : Any> HolderLookup.Provider.lookupOrThrow(key: ResourceKey<T>): Holder<T> {
+	return lookupOrThrow(key.registryKey()).getOrThrow(key)
+}
+
+fun <T : Any> Registry<T>.getHolder(value: T): Holder<T>? {
+	// this is so stupid
+	return getHolder(getResourceKey(value).getOrNull() ?: return null).getOrNull()
+}
+
+fun <T : Any> Registry<T>.getHolderOrThrow(value: T): Holder<T> {
+	// this is so stupid
+	return getHolder(getResourceKey(value).orElseThrow()).orElseThrow()
+}
+
+// forge registry functionality emulation on vanilla registry
+fun <T : Any> Registry<T>.getReverseTag(value: T): Stream<TagKey<T>> {
+	return getHolder(value)?.tags() ?: Stream.empty()
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TooltipList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TooltipList.kt
index cff6c59ac..dfb56003e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TooltipList.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TooltipList.kt
@@ -1,21 +1,21 @@
 package ru.dbotthepony.mc.otm.core
 
 import net.minecraft.ChatFormatting
+import net.minecraft.core.HolderLookup
+import net.minecraft.core.component.DataComponents
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.Tag
 import net.minecraft.network.chat.Component
 import net.minecraft.world.item.BlockItem
 import net.minecraft.world.item.DyeColor
+import net.minecraft.world.item.Item.TooltipContext
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.minecraft.world.item.component.CustomData
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
 import ru.dbotthepony.mc.otm.capability.energy
 import ru.dbotthepony.mc.otm.capability.energy.BlockEnergyStorageImpl
-import ru.dbotthepony.mc.otm.capability.energy.CapacitorEnergyStorage
-import ru.dbotthepony.mc.otm.capability.energy.GeneratorEnergyStorage
 import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
-import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl
-import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
 import ru.dbotthepony.mc.otm.capability.energy.batteryLevel
 import ru.dbotthepony.mc.otm.capability.fluidLevel
 import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl
@@ -31,36 +31,40 @@ import ru.dbotthepony.mc.otm.core.util.formatMatter
 import ru.dbotthepony.mc.otm.core.util.formatPower
 
 class TooltipList {
-	private val descriptionLines = ArrayList<(itemStack: ItemStack, acceptor: (text: Component) -> Unit) -> Unit>()
+	fun interface TooltipProvider {
+		fun invoke(itemStack: ItemStack, context: TooltipContext, acceptor: (text: Component) -> Unit)
+	}
 
-	fun assemble(itemStack: ItemStack, into: (Component) -> Unit) {
+	private val descriptionLines = ArrayList<TooltipProvider>()
+
+	fun assemble(itemStack: ItemStack, context: TooltipContext, into: (Component) -> Unit) {
 		if (descriptionLines.isNotEmpty()) {
-			if (ClientConfig.HIDE_DESCRIPTION && !minecraft.window.isShiftDown && descriptionLines.any { var flag = false; it.invoke(itemStack) { flag = true }; flag }) {
+			if (ClientConfig.HIDE_DESCRIPTION && !minecraft.window.isShiftDown && descriptionLines.any { var flag = false; it.invoke(itemStack, context) { flag = true }; flag }) {
 				into.invoke(TranslatableComponent("otm.gui.shift_for_more_info").withStyle(ChatFormatting.GRAY).withStyle(ChatFormatting.ITALIC))
 			} else {
 				for (lines in descriptionLines) {
-					lines.invoke(itemStack, into)
+					lines.invoke(itemStack, context, into)
 				}
 			}
 		}
 	}
 
-	fun assemble(itemStack: ItemStack, into: MutableCollection<Component>) {
-		assemble(itemStack, into::add)
+	fun assemble(itemStack: ItemStack, context: TooltipContext, into: MutableCollection<Component>) {
+		assemble(itemStack, context, into::add)
 	}
 
-	fun addNormal(function: (itemStack: ItemStack, acceptor: (text: Component) -> Unit) -> Unit) {
+	fun addNormal(function: TooltipProvider) {
 		descriptionLines.add(function)
 	}
 
 	@JvmName("addPlain")
 	fun add(component: Component) {
-		descriptionLines.add { _, result -> result.invoke(component.copy()) }
+		descriptionLines.add { _, _, result -> result.invoke(component.copy()) }
 	}
 
 	@JvmName("addFunction2Component")
 	fun add(component: () -> Component) {
-		descriptionLines.add { _, result -> result.invoke(component.invoke()) }
+		descriptionLines.add { _, _, result -> result.invoke(component.invoke()) }
 	}
 
 	fun needsNoPower(formatting: ChatFormatting = ChatFormatting.GRAY) {
@@ -75,15 +79,15 @@ class TooltipList {
 
 	fun colored(color: DyeColor?) = painted(color)
 
-	inline fun <reified T : Tag> blockEntityData(key: String, noinline callback: (itemStack: ItemStack, data: T, acceptor: (line: Component) -> Unit) -> Unit) {
-		addNormal { itemStack, acceptor ->
-			val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(key) as? T ?: return@addNormal
-			callback(itemStack, tag, acceptor)
+	inline fun <reified T : Tag> blockEntityData(key: String, noinline callback: (itemStack: ItemStack, context: TooltipContext, data: T, acceptor: (line: Component) -> Unit) -> Unit) {
+		addNormal { itemStack, context, acceptor ->
+			val data = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY)
+			callback(itemStack, context, data.unsafe.get(key) as? T ?: return@addNormal, acceptor)
 		}
 	}
 
 	fun blockEntityEnergy(energyKey: String = MatteryBlockEntity.ENERGY_KEY, batteryKey: String = MatteryBlockEntity.BATTERY_KEY) {
-		blockEntityData<CompoundTag>(energyKey) { _, tag, acceptor ->
+		blockEntityData<CompoundTag>(energyKey) { _, context, tag, acceptor ->
 			val stored = tag.mapPresent(BlockEnergyStorageImpl.ENERGY_STORED_KEY, Decimal::deserializeNBT)
 
 			if (stored != null) {
@@ -94,25 +98,29 @@ class TooltipList {
 					).withStyle(ChatFormatting.GRAY))
 			}
 
-			val container = MatteryContainer(1)
-			tag.map(batteryKey, container::deserializeNBT)
+			val registry = context.registries()
 
-			if (!container[0].isEmpty) {
-				acceptor(TranslatableComponent("otm.item.block.stored_battery", container[0].displayName).withStyle(ChatFormatting.GRAY))
+			if (registry != null) {
+				val container = MatteryContainer(1)
+				tag.map(batteryKey) { it: Tag -> container.deserializeNBT(registry, it) }
 
-				val energy = container[0].energy ?: return@blockEntityData
+				if (!container[0].isEmpty) {
+					acceptor(TranslatableComponent("otm.item.block.stored_battery", container[0].displayName).withStyle(ChatFormatting.GRAY))
 
-				if (energy is IMatteryEnergyStorage) {
-					batteryLevel(energy, acceptor)
-				} else {
-					batteryLevel(energy, acceptor)
+					val energy = container[0].energy ?: return@blockEntityData
+
+					if (energy is IMatteryEnergyStorage) {
+						batteryLevel(energy, acceptor)
+					} else {
+						batteryLevel(energy, acceptor)
+					}
 				}
 			}
 		}
 	}
 
 	fun blockEntityMatter(matterKey: String = MatteryBlockEntity.MATTER_STORAGE_KEY) {
-		blockEntityData<CompoundTag>(matterKey) { _, tag, acceptor ->
+		blockEntityData<CompoundTag>(matterKey) { _, _, tag, acceptor ->
 			val stored = tag.mapPresent(MatterStorageImpl.MATTER_STORED_KEY, Decimal::deserializeNBT)
 
 			if (stored != null) {
@@ -126,7 +134,7 @@ class TooltipList {
 	}
 
 	fun itemEnergy() {
-		addNormal { itemStack, acceptor ->
+		addNormal { itemStack, _, acceptor ->
 			val energy = itemStack.energy ?: return@addNormal
 
 			if (energy is IMatteryEnergyStorage) {
@@ -142,10 +150,8 @@ class TooltipList {
 	}
 
 	fun itemFluid() {
-		addNormal { itemStack, acceptor ->
-			itemStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
-				it.fluidLevel(acceptor)
-			}
+		addNormal { itemStack, _, acceptor ->
+			itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.fluidLevel(acceptor)
 		}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/UnOverengineering.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/UnOverengineering.kt
index 428f34283..1c9e701f5 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/UnOverengineering.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/UnOverengineering.kt
@@ -2,12 +2,14 @@ package ru.dbotthepony.mc.otm.core
 
 import com.google.gson.JsonElement
 import com.google.gson.JsonSyntaxException
-import com.mojang.datafixers.util.Either
 import com.mojang.serialization.Codec
 import com.mojang.serialization.DataResult
 import com.mojang.serialization.JsonOps
 import net.minecraft.core.Holder
+import net.minecraft.core.Registry
 import net.minecraft.core.RegistryAccess
+import net.minecraft.core.Vec3i
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.core.registries.Registries
 import net.minecraft.nbt.NbtOps
 import net.minecraft.nbt.Tag
@@ -24,8 +26,8 @@ import net.minecraft.world.damagesource.DamageType
 import net.minecraft.world.item.Item
 import net.minecraft.world.level.block.Block
 import net.minecraft.world.level.material.Fluid
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.registries.IForgeRegistry
+import net.minecraft.world.phys.AABB
+import net.minecraft.world.phys.Vec3
 import ru.dbotthepony.mc.otm.core.util.readBinaryJson
 import ru.dbotthepony.mc.otm.core.util.writeBinaryJson
 import kotlin.jvm.optionals.getOrNull
@@ -36,31 +38,31 @@ fun <V : Any> Codec<V>.fromJson(value: JsonElement): V? {
 }
 
 fun <V : Any> Codec<V>.fromJsonStrict(value: JsonElement): V {
-	return decode(JsonOps.INSTANCE, value).get({ left -> left.first }, { throw JsonSyntaxException("Error decoding element: ${it.message()}") })
+	return decode(JsonOps.INSTANCE, value).mapOrElse({ left -> left.first }, { throw JsonSyntaxException("Error decoding element: ${it.message()}") })
 }
 
 fun <V : Any> Codec<V>.toJson(value: V, prefix: JsonElement = JsonOps.INSTANCE.empty()): JsonElement? {
-	return encode(value, JsonOps.INSTANCE, prefix).getOrNull { it }
+	return encode(value, JsonOps.INSTANCE, prefix).mapOrNull { it }
 }
 
 fun <V : Any> Codec<V>.toJsonStrict(value: V, prefix: JsonElement = JsonOps.INSTANCE.empty()): JsonElement {
-	return encode(value, JsonOps.INSTANCE, prefix).get({ it }, { throw RuntimeException("Error encoding element: ${it.message()}") })
+	return encode(value, JsonOps.INSTANCE, prefix).mapOrElse({ it }, { throw RuntimeException("Error encoding element: ${it.message()}") })
 }
 
 fun <V : Any> Codec<V>.fromNbt(value: Tag): V? {
-	return decode(NbtOps.INSTANCE, value).getOrNull { left -> left.first }
+	return decode(NbtOps.INSTANCE, value).mapOrNull { left -> left.first }
 }
 
 fun <V : Any> Codec<V>.fromNbtStrict(value: Tag): V {
-	return decode(NbtOps.INSTANCE, value).get({ left -> left.first }, { throw RuntimeException("Error decoding element: ${it.message()}") })
+	return decode(NbtOps.INSTANCE, value).mapOrElse({ left -> left.first }, { throw RuntimeException("Error decoding element: ${it.message()}") })
 }
 
 fun <V : Any> Codec<V>.toNbt(value: V, prefix: Tag = NbtOps.INSTANCE.empty()): Tag? {
-	return encode(value, NbtOps.INSTANCE, prefix).getOrNull { it }
+	return encode(value, NbtOps.INSTANCE, prefix).mapOrNull { it }
 }
 
 fun <V : Any> Codec<V>.toNbtStrict(value: V, prefix: Tag = NbtOps.INSTANCE.empty()): Tag {
-	return encode(value, NbtOps.INSTANCE, prefix).get({ it }, { throw RuntimeException("Error encoding element: ${it.message()}") })
+	return encode(value, NbtOps.INSTANCE, prefix).mapOrElse({ it }, { throw RuntimeException("Error encoding element: ${it.message()}") })
 }
 
 fun <V : Any> Codec<V>.toNetwork(buff: FriendlyByteBuf, value: V) {
@@ -75,7 +77,7 @@ fun <V : Any> Codec<V>.fromNetwork(buff: FriendlyByteBuf): V {
 fun TranslatableComponent(key: String, vararg values: Any): MutableComponent = MutableComponent.create(TranslatableContents(key, null, values))
 fun TextComponent(value: String): MutableComponent = MutableComponent.create(PlainTextContents.create(value))
 
-fun <T> IForgeRegistry<T>.getKeyNullable(value: T): ResourceLocation? {
+fun <T : Any> Registry<T>.getKeyNullable(value: T): ResourceLocation? {
 	val key = getResourceKey(value)
 
 	if (key.isPresent) {
@@ -85,14 +87,12 @@ fun <T> IForgeRegistry<T>.getKeyNullable(value: T): ResourceLocation? {
 	return null
 }
 
-val Item.registryName get() = ForgeRegistries.ITEMS.getKeyNullable(this)
-val Fluid.registryName get() = ForgeRegistries.FLUIDS.getKeyNullable(this)
-val Block.registryName get() = ForgeRegistries.BLOCKS.getKeyNullable(this)
-
-fun FriendlyByteBuf.writeRegistryId(value: Item) = writeRegistryId(ForgeRegistries.ITEMS, value)
+val Item.registryName get() = BuiltInRegistries.ITEM.getKeyNullable(this)
+val Fluid.registryName get() = BuiltInRegistries.FLUID.getKeyNullable(this)
+val Block.registryName get() = BuiltInRegistries.BLOCK.getKeyNullable(this)
 
 // 1.19.3 lol
-inline val SoundEvent.holder get() = ForgeRegistries.SOUND_EVENTS.getHolder(this).orElse(null) ?: throw NoSuchElementException("$this is missing from ${ForgeRegistries.SOUND_EVENTS}")
+inline val SoundEvent.holder: Holder<SoundEvent> get() = BuiltInRegistries.SOUND_EVENT.wrapAsHolder(this)
 
 // 1.19.4 :thonkang:
 inline val DamageSource.isFall get() = `is`(DamageTypeTags.IS_FALL)
@@ -104,14 +104,13 @@ fun RegistryAccess.damageType(key: ResourceKey<DamageType>): Holder<DamageType>
 	return registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(key)
 }
 
-// 1.21
-fun ResourceLocation(namespace: String, path: String) = ResourceLocation.fromNamespaceAndPath(namespace, path)
+// 1.21 :help_me:
+fun ResourceLocation(namespace: String, path: String): ResourceLocation = ResourceLocation.fromNamespaceAndPath(namespace, path)
 
-// mojang hello?
-fun <IN, OUT> DataResult<IN>.get(map: (IN) -> OUT, orThrow: (DataResult.Error<IN>) -> Nothing): OUT {
-	return result().map(map).orElseGet { orThrow(error().get()) }
-}
-
-fun <IN, OUT> DataResult<IN>.getOrNull(map: (IN) -> OUT): OUT? {
+fun <IN, OUT> DataResult<IN>.mapOrNull(map: (IN) -> OUT): OUT? {
 	return result().map(map).orElse(null)
 }
+
+fun AABB(mins: Vec3i, maxs: Vec3i): AABB {
+	return AABB(Vec3.atLowerCornerOf(mins), Vec3.atLowerCornerOf(maxs))
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/AwareItemStack.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/AwareItemStack.kt
index cc60cfe9d..a29c78778 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/AwareItemStack.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/AwareItemStack.kt
@@ -3,8 +3,9 @@ package ru.dbotthepony.mc.otm.core.collect
 import net.minecraft.world.Container
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.enchantment.EnchantmentHelper.hasBindingCurse
-import net.minecraftforge.items.IItemHandler
+import net.minecraft.world.item.enchantment.EnchantmentEffectComponents
+import net.minecraft.world.item.enchantment.EnchantmentHelper
+import net.neoforged.neoforge.items.IItemHandler
 import ru.dbotthepony.mc.otm.container.get
 import ru.dbotthepony.mc.otm.container.set
 import ru.dbotthepony.mc.otm.core.get
@@ -28,7 +29,7 @@ data class ContainerItemStackEntry(val index: Int, val container: Container) : A
 	}
 
 	override fun extract(amount: Int, simulate: Boolean): ItemStack {
-		if (container is Inventory && index in 36 .. 39 && hasBindingCurse(container[index])) {
+		if (container is Inventory && index in 36 .. 39 && EnchantmentHelper.has(container[index], EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)) {
 			return ItemStack.EMPTY
 		}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/UUIDIntModifiersMap.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/UUIDIntModifiersMap.kt
index 601913052..a2f86c992 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/UUIDIntModifiersMap.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/UUIDIntModifiersMap.kt
@@ -1,8 +1,9 @@
 package ru.dbotthepony.mc.otm.core.collect
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.ListTag
-import net.minecraftforge.common.util.INBTSerializable
+import net.neoforged.neoforge.common.util.INBTSerializable
 import ru.dbotthepony.mc.otm.core.nbt.contains
 import java.util.UUID
 
@@ -88,7 +89,7 @@ class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backi
 		}
 	}
 
-	override fun serializeNBT(): ListTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): ListTag {
 		return ListTag().also {
 			for ((key, value) in backingMap) {
 				it.add(CompoundTag().also {
@@ -99,7 +100,7 @@ class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backi
 		}
 	}
 
-	override fun deserializeNBT(nbt: ListTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: ListTag?) {
 		backingMap.clear()
 		nbt ?: return
 		val old = this.value
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotation.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotation.kt
index fee43c2be..e7446b2f2 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotation.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotation.kt
@@ -5,19 +5,12 @@ import net.minecraft.core.Direction
 import net.minecraft.core.Vec3i
 import net.minecraft.util.StringRepresentable
 import net.minecraft.world.level.block.Rotation
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.common.util.LazyOptional
 import java.util.Collections
 import java.util.EnumMap
 
 internal inline val Direction.blockRotation
 	get() = BlockRotation.of(this)
 
-fun <T> ICapabilityProvider.getCapability(capability: Capability<T>, side: BlockRotation?): LazyOptional<T> {
-	return getCapability(capability, side?.front)
-}
-
 operator fun Vec3i.plus(other: BlockRotation): Vec3i {
 	return this + other.normal
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt
index 9011af8c0..7c7186da3 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt
@@ -1,14 +1,12 @@
 package ru.dbotthepony.mc.otm.core.math
 
-import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
 import net.minecraft.nbt.ByteArrayTag
 import net.minecraft.nbt.CompoundTag
-import net.minecraft.nbt.NbtAccounter
 import net.minecraft.nbt.StringTag
 import net.minecraft.nbt.Tag
 import net.minecraft.network.FriendlyByteBuf
 import net.minecraft.util.RandomSource
-import net.minecraftforge.common.ForgeConfigSpec
+import net.neoforged.neoforge.common.ModConfigSpec
 import ru.dbotthepony.mc.otm.config.ObservedConfigValue
 import ru.dbotthepony.mc.otm.core.util.readVarIntLE
 import ru.dbotthepony.mc.otm.core.util.writeVarIntLE
@@ -1595,7 +1593,7 @@ fun Long.toDecimal() = Decimal(this)
 fun Decimal.toDecimal() = this
 
 class DecimalConfigValue(
-	parent: ForgeConfigSpec.ConfigValue<String>,
+	parent: ModConfigSpec.ConfigValue<String>,
 	val minimum: Decimal? = null,
 	val maximum: Decimal? = null,
 ) : ObservedConfigValue<Decimal>(parent) {
@@ -1626,7 +1624,7 @@ class DecimalConfigValue(
 	}
 }
 
-private fun ForgeConfigSpec.Builder.commentRange(minimum: Decimal?, maximum: Decimal?) {
+private fun ModConfigSpec.Builder.commentRange(minimum: Decimal?, maximum: Decimal?) {
 	if (minimum != null && maximum != null) {
 		comment("Range: $minimum ~ $maximum")
 	} else if (minimum != null) {
@@ -1636,13 +1634,13 @@ private fun ForgeConfigSpec.Builder.commentRange(minimum: Decimal?, maximum: Dec
 	}
 }
 
-fun ForgeConfigSpec.Builder.defineDecimal(path: String, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue {
+fun ModConfigSpec.Builder.defineDecimal(path: String, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue {
 	commentRange(minimum, maximum)
 	comment("Default: $defaultValue")
 	return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum)
 }
 
-fun ForgeConfigSpec.Builder.defineDecimal(path: List<String>, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue {
+fun ModConfigSpec.Builder.defineDecimal(path: List<String>, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue {
 	commentRange(minimum, maximum)
 	comment("Default: $defaultValue")
 	return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt
index f3ff05318..22230c67e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt
@@ -79,8 +79,6 @@ fun <T> CompoundTag.mapString(index: String, mapper: (String) -> T, orElse: T):
 	}
 }
 
-fun CompoundTag.getItemStack(key: String): ItemStack = map(key, ItemStack::of) ?: ItemStack.EMPTY
-
 @Suppress("unchecked_cast") // type is checked inside getList
 fun CompoundTag.getByteList(key: String): MutableList<ByteTag> = getList(key, Tag.TAG_BYTE.toInt()) as MutableList<ByteTag>
 @Suppress("unchecked_cast") // type is checked inside getList
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt
index 3949f9121..de70fb81c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt
@@ -10,6 +10,7 @@ import io.netty.handler.codec.DecoderException
 import io.netty.handler.codec.EncoderException
 import net.minecraft.nbt.NbtAccounter
 import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.RegistryFriendlyByteBuf
 import net.minecraft.network.chat.Component
 
 fun FriendlyByteBuf.readBinaryJson(): JsonElement {
@@ -21,27 +22,18 @@ fun FriendlyByteBuf.writeBinaryJson(value: JsonElement) {
 }
 
 fun <S> FriendlyByteBuf.writeBinaryJsonWithCodec(codec: Codec<S>, value: S) {
-	writeBinaryJson(codec.encode(value, JsonOps.INSTANCE, JsonOps.INSTANCE.empty())
-						.get().map({ it }, { throw EncoderException("Failed to encode input data: ${it.message()}") }))
+	writeBinaryJson(codec.encode(value, JsonOps.INSTANCE, JsonOps.INSTANCE.empty()).mapOrElse({ it }, { throw EncoderException("Failed to encode input data: ${it.message()}") }))
 }
 
 fun <S> FriendlyByteBuf.readBinaryJsonWithCodec(codec: Codec<S>): S {
 	return codec.decode(JsonOps.INSTANCE, readBinaryJson())
-		.get().map({ it.first }, { throw DecoderException("Failed to decode data from network: ${it.message()}") })
+		.mapOrElse({ it.first }, { throw DecoderException("Failed to decode data from network: ${it.message()}") })
 }
 
 fun <S> FriendlyByteBuf.readBinaryJsonWithCodecIndirect(codec: Codec<S>): DataResult<S> {
 	return codec.decode(JsonOps.INSTANCE, readBinaryJson()).map { it.first }
 }
 
-fun FriendlyByteBuf.readBinaryComponent(): Component {
-	return Component.Serializer.fromJson(readBinaryJson()) ?: throw NullPointerException("Received null component")
-}
-
-fun FriendlyByteBuf.writeBinaryComponent(component: Component) {
-	writeBinaryJson(Component.Serializer.toJsonTree(component))
-}
-
 // обратный порядок аргументов у лямбда выражения
 fun <T> FriendlyByteBuf.writeCollection(collection: Collection<T>, writer: (T, FriendlyByteBuf) -> Unit) {
 	writeCollection(collection) { a, b -> writer.invoke(b, a) }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt
index 2327a2abf..855ab7541 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt
@@ -1,6 +1,7 @@
 package ru.dbotthepony.mc.otm.core.util
 
 import io.netty.handler.codec.EncoderException
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.NbtAccounter
 import net.minecraft.nbt.NbtIo
@@ -9,9 +10,7 @@ import net.minecraft.resources.ResourceLocation
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.material.Fluid
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.registries.ForgeRegistry
+import net.neoforged.neoforge.fluids.FluidStack
 import java.io.*
 import java.math.BigDecimal
 import java.math.BigInteger
@@ -43,14 +42,14 @@ fun OutputStream.writeItem(itemStack: ItemStack, limitedTag: Boolean = true) {
 		write(0)
 	} else {
 		write(1)
-		val id = (ForgeRegistries.ITEMS as ForgeRegistry<Item>).getID(itemStack.item)
+		val id = BuiltInRegistries.ITEM.getId(itemStack.item)
 
 		writeInt(id)
 		writeInt(itemStack.count)
 
 		var compoundtag: CompoundTag? = null
 
-		if (itemStack.item.isDamageable(itemStack) || itemStack.item.shouldOverrideMultiplayerNbt()) {
+		if (itemStack.item.isDamageable(itemStack)) {
 			compoundtag = if (limitedTag) itemStack.shareTag else itemStack.tag
 		}
 
@@ -67,7 +66,7 @@ fun InputStream.readItem(): ItemStack {
 		return ItemStack.EMPTY
 	}
 
-	val item = (ForgeRegistries.ITEMS as ForgeRegistry<Item>).getValue(readInt())
+	val item = BuiltInRegistries.ITEM.byId(readInt())
 	val itemStack = ItemStack(item, readInt())
 
 	if (read() != 0) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/InvalidableLazy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/InvalidableLazy.kt
index f6c16c449..4e93f08b0 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/InvalidableLazy.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/InvalidableLazy.kt
@@ -1,5 +1,31 @@
 package ru.dbotthepony.mc.otm.core.util
 
+import ru.dbotthepony.kommons.util.KOptional
+
 interface InvalidableLazy<V> : Lazy<V> {
 	fun invalidate()
+
+	class Impl<V>(private val supplier: () -> V) : InvalidableLazy<V> {
+		private var _value: KOptional<V> = KOptional.empty()
+
+		override val value: V get() {
+			_value.ifPresent {
+				return it
+			}.ifNotPresent {
+				val v = supplier.invoke()
+				_value = KOptional(v)
+				return v
+			}
+
+			throw RuntimeException()
+		}
+
+		override fun invalidate() {
+			_value = KOptional()
+		}
+
+		override fun isInitialized(): Boolean {
+			return _value.isPresent
+		}
+	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt
index 6319572f0..a0e9ca6dd 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt
@@ -7,14 +7,13 @@ import net.minecraft.network.chat.MutableComponent
 import net.minecraft.world.item.CreativeModeTabs
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.CreativeModeTabRegistry
+import net.neoforged.neoforge.common.CreativeModeTabRegistry
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.nullsFirst
 import ru.dbotthepony.mc.otm.core.nullsLast
 import ru.dbotthepony.mc.otm.core.registryName
-import ru.dbotthepony.mc.otm.core.suppliers
 import ru.dbotthepony.mc.otm.matter.MatterManager
 import ru.dbotthepony.mc.otm.storage.ItemStorageStack
 import ru.dbotthepony.mc.otm.client.render.Widgets18
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LOHolder.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LOHolder.kt
deleted file mode 100644
index 289c1785f..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LOHolder.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package ru.dbotthepony.mc.otm.core.util
-
-import net.minecraftforge.common.util.LazyOptional
-
-class LOHolder<T : Any>(val value: T) {
-	private var lazyOptional: LazyOptional<T> = LazyOptional.of { value }
-
-	fun invalidate() {
-		lazyOptional.invalidate()
-	}
-
-	fun revive() {
-		lazyOptional.invalidate()
-		lazyOptional = LazyOptional.of { value }
-	}
-
-	fun <T> get(): LazyOptional<T> {
-		return lazyOptional.cast()
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt
index d422d6851..9d7c2583c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt
@@ -2,21 +2,23 @@ package ru.dbotthepony.mc.otm.core.util
 
 import com.google.common.collect.ImmutableList
 import com.mojang.serialization.Codec
+import net.minecraft.core.HolderLookup
+import net.minecraft.core.HolderLookup.Provider
 import net.minecraft.nbt.ByteTag
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.DoubleTag
 import net.minecraft.nbt.FloatTag
 import net.minecraft.nbt.IntTag
-import net.minecraft.nbt.ListTag
 import net.minecraft.nbt.LongTag
 import net.minecraft.nbt.NbtOps
 import net.minecraft.nbt.NumericTag
 import net.minecraft.nbt.StringTag
 import net.minecraft.nbt.Tag
 import net.minecraft.resources.ResourceLocation
-import net.minecraftforge.common.util.INBTSerializable
+import net.neoforged.neoforge.common.util.INBTSerializable
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.kommons.util.Delegate
+import ru.dbotthepony.mc.otm.core.get
 import ru.dbotthepony.mc.otm.core.immutableList
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.math.Vector
@@ -59,20 +61,20 @@ class Savetables : INBTSerializable<CompoundTag?> {
 
 	fun <V : INBTSerializable<T?>, T : Tag> stateful(getter: Supplier<V>, name: String, type: Class<T>): Stateful<V, T> {
 		return Stateful(getter, name, type)
-			.withSerializer { it.serializeNBT() }
-			.withDeserializer { v, t -> v.deserializeNBT(t) }
+			.withSerializer { it, l -> it.serializeNBT(l) }
+			.withDeserializer { v, t, l -> v.deserializeNBT(l, t) }
 	}
 
 	fun <V : INBTSerializable<T?>, T : Tag> stateful(getter: V, name: String, type: Class<T>): Stateful<V, T> {
 		return Stateful({ getter }, name, type)
-			.withSerializer { it.serializeNBT() }
-			.withDeserializer { v, t -> v.deserializeNBT(t) }
+			.withSerializer { it, l -> it.serializeNBT(l) }
+			.withDeserializer { v, t, l -> v.deserializeNBT(l, t) }
 	}
 
 	fun <V : INBTSerializable<T?>, T : Tag> stateful(getter: KProperty0<V>, name: String = getter.name, type: Class<T>): Stateful<V, T> {
 		return Stateful(getter, name, type)
-			.withSerializer { it.serializeNBT() }
-			.withDeserializer { v, t -> v.deserializeNBT(t) }
+			.withSerializer { it, l -> it.serializeNBT(l) }
+			.withDeserializer { v, t, l -> v.deserializeNBT(l, t) }
 	}
 
 	fun decimal(prop: Delegate<Decimal>, name: String, default: Decimal = Decimal.ZERO): Stateless<Decimal, Tag> {
@@ -172,8 +174,8 @@ class Savetables : INBTSerializable<CompoundTag?> {
 
 	fun <T : Any> codecNullable(prop: Delegate<T?>, codec: Codec<T>, name: String): Stateless<T?, Tag> {
 		return Stateless(prop, name, Tag::class.java)
-			.withSerializer { prop.get()?.let { codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow(false) { throw IllegalStateException("Failed to write NBT data for $name: $it") } } }
-			.withDeserializer { codec.decode(NbtOps.INSTANCE, it).getOrThrow(false) { throw IllegalStateException("Failed to read NBT data for $name: $it") }.first }
+			.withSerializer { prop.get()?.let { codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow() { throw IllegalStateException("Failed to write NBT data for $name: $it") } } }
+			.withDeserializer { codec.decode(NbtOps.INSTANCE, it).getOrThrow() { throw IllegalStateException("Failed to read NBT data for $name: $it") }.first }
 	}
 
 	fun <T : Any> codecNullable(prop: KMutableProperty0<T?>, codec: Codec<T>, name: String = prop.name): Stateless<T?, Tag> {
@@ -182,8 +184,8 @@ class Savetables : INBTSerializable<CompoundTag?> {
 
 	fun <T : Any> codecNullable(prop: Delegate<T?>, codec: Codec<T>, name: String, default: T?): Stateless<T?, Tag> {
 		return Stateless(prop, name, Tag::class.java)
-			.withSerializer { prop.get()?.let { codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow(false) { throw IllegalStateException("Failed to write NBT data for $name: $it") } } }
-			.withDeserializer { codec.decode(NbtOps.INSTANCE, it).get().map({ it.first }, { LOGGER.error("Failed to read NBT data for $name", RuntimeException(it.message())); default }) }
+			.withSerializer { prop.get()?.let { codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow { throw IllegalStateException("Failed to write NBT data for $name: $it") } } }
+			.withDeserializer { codec.decode(NbtOps.INSTANCE, it).mapOrElse({ it.first }, { LOGGER.error("Failed to read NBT data for $name", RuntimeException(it.message())); default }) }
 			.withDefault { default }
 	}
 
@@ -193,14 +195,14 @@ class Savetables : INBTSerializable<CompoundTag?> {
 
 	fun <T : Any> codec(prop: Delegate<T>, codec: Codec<T>, name: String): Stateless<T, Tag> {
 		return Stateless(prop, name, Tag::class.java)
-			.withSerializer { codec.encode(prop.get(), NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow(false) { throw IllegalStateException("Failed to write NBT data for $name: $it") } }
-			.withDeserializer { codec.decode(NbtOps.INSTANCE, it).getOrThrow(false) { throw IllegalStateException("Failed to read NBT data for $name: $it") }.first }
+			.withSerializer { codec.encode(prop.get(), NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow { throw IllegalStateException("Failed to write NBT data for $name: $it") } }
+			.withDeserializer { codec.decode(NbtOps.INSTANCE, it).getOrThrow() { throw IllegalStateException("Failed to read NBT data for $name: $it") }.first }
 	}
 
 	fun <T : Any> codec(prop: Delegate<T>, codec: Codec<T>, name: String, default: T): Stateless<T, Tag> {
 		return Stateless(prop, name, Tag::class.java)
-			.withSerializer { codec.encode(prop.get(), NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow(false) { throw IllegalStateException("Failed to write NBT data for $name: $it") } }
-			.withDeserializer { codec.decode(NbtOps.INSTANCE, it).get().map({ it.first }, { LOGGER.error("Failed to read NBT data for $name", RuntimeException(it.message())); default }) }
+			.withSerializer { codec.encode(prop.get(), NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow { throw IllegalStateException("Failed to write NBT data for $name: $it") } }
+			.withDeserializer { codec.decode(NbtOps.INSTANCE, it).mapOrElse({ it.first }, { LOGGER.error("Failed to read NBT data for $name", RuntimeException(it.message())); default }) }
 			.withDefault { default }
 	}
 
@@ -228,8 +230,8 @@ class Savetables : INBTSerializable<CompoundTag?> {
 		return location(Delegate.Of(prop), name)
 	}
 
-	override fun serializeNBT(): CompoundTag {
-		return CompoundTag().also(::serializeNBT)
+	override fun serializeNBT(registry: Provider): CompoundTag {
+		return CompoundTag().also { this.serializeNBT(it, registry) }
 	}
 
 	private var validated = false
@@ -243,31 +245,31 @@ class Savetables : INBTSerializable<CompoundTag?> {
 		validated = true
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag?) {
+	override fun deserializeNBT(registry: Provider, nbt: CompoundTag?) {
 		validate()
 
 		if (nbt == null) {
 			for (entry in entries) {
-				entry.deserializeNBT(null)
+				entry.deserializeNBT(registry, null)
 			}
 		} else {
 			for (entry in entries) {
 				val value = nbt[entry.name]
 
 				if (value != null && entry.type.isAssignableFrom(value.javaClass)) {
-					(entry as INBTSerializable<Tag>).deserializeNBT(value)
+					(entry as INBTSerializable<Tag>).deserializeNBT(registry, value)
 				} else {
-					entry.deserializeNBT(null)
+					entry.deserializeNBT(registry, null)
 				}
 			}
 		}
 	}
 
-	fun serializeNBT(nbt: CompoundTag) {
+	fun serializeNBT(nbt: CompoundTag, registry: Provider) {
 		validate()
 
 		for (entry in entries) {
-			val value = entry.serializeNBT()
+			val value = entry.serializeNBT(registry)
 
 			if (value != null)
 				nbt[entry.name] = value
@@ -283,16 +285,16 @@ class Savetables : INBTSerializable<CompoundTag?> {
 			validated = false
 		}
 
-		private var serializer: ((V) -> T?)? = null
-		private var deserializer: ((V, T) -> Unit)? = null
+		private var serializer: ((V, HolderLookup.Provider) -> T?)? = null
+		private var deserializer: ((V, T, HolderLookup.Provider) -> Unit)? = null
 		private var resetter: ((V) -> Unit)? = null
 
-		fun withSerializer(serializer: (V) -> T?): Stateful<V, T> {
+		fun withSerializer(serializer: (V, HolderLookup.Provider) -> T?): Stateful<V, T> {
 			this.serializer = serializer
 			return this
 		}
 
-		fun withDeserializer(deserializer: (V, T) -> Unit): Stateful<V, T> {
+		fun withDeserializer(deserializer: (V, T, HolderLookup.Provider) -> Unit): Stateful<V, T> {
 			this.deserializer = deserializer
 			return this
 		}
@@ -302,15 +304,15 @@ class Savetables : INBTSerializable<CompoundTag?> {
 			return this
 		}
 
-		override fun serializeNBT(): T? {
-			return checkNotNull(serializer) { "No serializer specified for $name" }.invoke(prop.get())
+		override fun serializeNBT(registry: HolderLookup.Provider): T? {
+			return checkNotNull(serializer) { "No serializer specified for $name" }.invoke(prop.get(), registry)
 		}
 
-		override fun deserializeNBT(nbt: T?) {
+		override fun deserializeNBT(registry: Provider, nbt: T?) {
 			if (nbt == null) {
 				resetter?.invoke(prop.get())
 			} else {
-				checkNotNull(deserializer) { "No deserializer specified for $name" }.invoke(prop.get(), nbt)
+				checkNotNull(deserializer) { "No deserializer specified for $name" }.invoke(prop.get(), nbt, registry)
 			}
 		}
 
@@ -353,11 +355,11 @@ class Savetables : INBTSerializable<CompoundTag?> {
 			return this
 		}
 
-		override fun serializeNBT(): T? {
+		override fun serializeNBT(registry: Provider): T? {
 			return checkNotNull(serializer) { "No serializer specified for $name" }.invoke(prop.get())
 		}
 
-		override fun deserializeNBT(nbt: T?) {
+		override fun deserializeNBT(registry: Provider, nbt: T?) {
 			if (nbt == null) {
 				if (default != null) {
 					prop.accept(default!!.invoke())
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/StreamCodecs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/StreamCodecs.kt
index 79c8db41b..7d99dd590 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/StreamCodecs.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/StreamCodecs.kt
@@ -1,38 +1,22 @@
 package ru.dbotthepony.mc.otm.core.util
 
-import com.google.common.collect.ImmutableList
-import com.mojang.datafixers.util.Pair
-import com.mojang.serialization.Codec
-import com.mojang.serialization.DataResult
-import com.mojang.serialization.DynamicOps
-import net.minecraft.nbt.NbtAccounter
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.FluidStack
 import ru.dbotthepony.kommons.io.DelegateSyncher
 import ru.dbotthepony.kommons.io.StreamCodec
 import ru.dbotthepony.kommons.util.Delegate
 import ru.dbotthepony.kommons.util.DelegateGetter
 import ru.dbotthepony.kommons.util.DelegateSetter
 import ru.dbotthepony.kommons.util.ListenableDelegate
-import ru.dbotthepony.mc.otm.core.immutableMap
 import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.core.math.readDecimal
 import ru.dbotthepony.mc.otm.core.math.writeDecimal
 import ru.dbotthepony.mc.otm.core.readItemType
 import ru.dbotthepony.mc.otm.core.writeItemType
-import java.io.DataInput
 import java.io.DataInputStream
-import java.io.DataOutput
 import java.io.DataOutputStream
-import java.io.InputStream
-import java.io.OutputStream
 import java.util.*
-import java.util.function.Predicate
 import java.util.function.Supplier
-import kotlin.NoSuchElementException
-import kotlin.math.absoluteValue
-import kotlin.reflect.KClass
 
 val ItemStackValueCodec = StreamCodec.Impl(DataInputStream::readItem, DataOutputStream::writeItem, ItemStack::copy) { a, b -> a.equals(b, true) }
 val FluidStackValueCodec = StreamCodec.Impl(DataInputStream::readFluidStack, DataOutputStream::writeFluidStack, FluidStack::copy) { a, b -> a == b && a.amount == b.amount }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt
deleted file mode 100644
index b32702611..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt
+++ /dev/null
@@ -1,191 +0,0 @@
-package ru.dbotthepony.mc.otm.data
-
-import com.google.gson.JsonObject
-import com.google.gson.JsonParseException
-import com.google.gson.JsonSyntaxException
-import com.mojang.datafixers.util.Pair
-import com.mojang.serialization.Codec
-import com.mojang.serialization.DataResult
-import com.mojang.serialization.DynamicOps
-import com.mojang.serialization.JsonOps
-import io.netty.buffer.ByteBuf
-import io.netty.buffer.Unpooled
-import net.minecraft.advancements.AdvancementHolder
-import net.minecraft.data.recipes.FinishedRecipe
-import net.minecraft.network.FriendlyByteBuf
-import net.minecraft.resources.ResourceLocation
-import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.crafting.Ingredient
-import net.minecraft.world.item.crafting.Recipe
-import net.minecraft.world.item.crafting.RecipeSerializer
-import org.apache.logging.log4j.LogManager
-import ru.dbotthepony.mc.otm.core.set
-import ru.dbotthepony.mc.otm.core.util.readBinaryJsonWithCodecIndirect
-import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec
-import kotlin.collections.ArrayDeque
-import kotlin.concurrent.getOrSet
-import kotlin.reflect.KProperty
-
-/**
- * 1.20.2: Mojang FINALLY moved json IO of recipes to codecs
- *
- * ...but they forgot to do the same for Networking them, Ingredient does not have codec for network.
- *
- * Mojang.... Mojang never changes.
- */
-class Codec2RecipeSerializer<S : Recipe<*>>(
-	val empty: S?,
-	codec: (Codec2RecipeSerializer<S>.Context) -> Codec<S>,
-) : Codec<S>, RecipeSerializer<S> {
-	constructor(supplier: (Codec2RecipeSerializer<S>.Context) -> Codec<S>) : this(null, supplier)
-
-	private class CurrentContext {
-		var isNetwork = 0
-	}
-
-	inner class Context {
-		val ingredients: Codec<Ingredient> get() = ActualIngredientCodec
-
-		fun <P : Recipe<*>> wrap(serializer: RecipeSerializer<P>): Codec<P> {
-			return object : Codec<P> {
-				override fun <T : Any?> encode(input: P, ops: DynamicOps<T>, prefix: T): DataResult<T> {
-					if (context.isNetwork > 0) {
-						val parent = Unpooled.buffer()
-						val buff = FriendlyByteBuf(parent)
-						serializer.toNetwork(buff, input)
-						return DataResult.success(ops.createByteList(parent.nioBuffer()))
-					} else {
-						return serializer.codec().encode(input, ops, prefix)
-					}
-				}
-
-				override fun <T : Any?> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<P, T>> {
-					if (context.isNetwork > 0) {
-						return ops.getByteBuffer(input).flatMap {
-							val parent = Unpooled.buffer()
-							val buff = FriendlyByteBuf(parent)
-							parent.writeBytes(it)
-							parent.setIndex(0, 0)
-							val read = serializer.fromNetwork(buff)
-
-							if (read == null)
-								DataResult.error { "Unable to read parent recipe from network" }
-							else
-								DataResult.success(Pair(read, ops.empty()))
-						}
-					} else {
-						return serializer.codec().decode(ops, input)
-					}
-				}
-			}
-		}
-	}
-
-	private val codec = codec.invoke(Context())
-
-	override fun <T : Any> encode(input: S, ops: DynamicOps<T>, prefix: T): DataResult<T> {
-		return codec.encode(input, ops, prefix)
-	}
-
-	override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<S, T>> {
-		return codec.decode(ops, input)
-	}
-
-	fun <O : Recipe<*>> xmap(to: (S) -> O, from: (O) -> S): Codec2RecipeSerializer<O> {
-		return Codec2RecipeSerializer(empty?.let(to)) { _ ->
-			codec.xmap(to, from)
-		}
-	}
-
-	override fun codec(): Codec<S> {
-		return this
-	}
-
-	override fun fromNetwork(data: FriendlyByteBuf): S? {
-		try {
-			context.isNetwork++
-			return data.readBinaryJsonWithCodecIndirect(this)
-				.resultOrPartial { LOGGER.error("Failed to read recipe from network: $it") }.orElse(null)
-		} finally {
-			context.isNetwork--
-		}
-	}
-
-	override fun toNetwork(data: FriendlyByteBuf, recipe: S) {
-		try {
-			context.isNetwork++
-			data.writeBinaryJsonWithCodec(this, recipe)
-		} finally {
-			context.isNetwork--
-		}
-	}
-
-	fun toFinished(recipe: S, id: ResourceLocation): FinishedRecipe {
-		return object : FinishedRecipe {
-			override fun serializeRecipeData(p_125967_: JsonObject) {
-				encode(recipe, JsonOps.INSTANCE, p_125967_).get().map(
-					{
-						it as JsonObject
-
-						for ((k, v) in it.entrySet()) {
-							p_125967_[k] = v
-						}
-					},
-					{
-						throw JsonParseException("Failed to serialize recipe: ${it.message()}")
-					}
-				)
-			}
-
-			override fun id(): ResourceLocation {
-				return id
-			}
-
-			override fun type(): RecipeSerializer<*> {
-				return this@Codec2RecipeSerializer
-			}
-
-			override fun advancement(): AdvancementHolder? {
-				return null
-			}
-		}
-	}
-
-	private object ActualIngredientCodec : Codec<Ingredient> {
-		override fun <T : Any> encode(input: Ingredient, ops: DynamicOps<T>, prefix: T): DataResult<T> {
-			return if (context.isNetwork > 0) {
-				networkIngredientCodec.encode(input, ops, prefix)
-			} else {
-				Ingredient.CODEC.encode(input, ops, prefix)
-			}
-		}
-
-		override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<Ingredient, T>> {
-			return if (context.isNetwork > 0) {
-				networkIngredientCodec.decode(ops, input)
-			} else {
-				Ingredient.CODEC.decode(ops, input)
-			}
-		}
-	}
-
-	companion object {
-		private val LOGGER = LogManager.getLogger()
-		private val networkIngredientCodec = Codec.list(ItemStack.CODEC).xmap({ Ingredient.of(it.stream()) }, { it.items.toMutableList() })
-
-		/**
-		 * [ThreadLocal] because optimization mods can (and probably should) parallelize recipe deserialization,
-		 * since RecipeSerializers are expected to be stateless. [Codec2RecipeSerializer], however, is stateful (threading PoV).
-		 * To make it stateless, [ThreadLocal] is used.
-		 */
-		private val context by object : ThreadLocal<CurrentContext>() {
-			override fun initialValue(): CurrentContext {
-				return CurrentContext()
-			}
-		}
-	}
-}
-
-private operator fun <T> ThreadLocal<T>.getValue(companion: Codec2RecipeSerializer.Companion, property: KProperty<*>): T {
-	return get()
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/ComponentCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ComponentCodec.kt
deleted file mode 100644
index ec65bb31a..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/ComponentCodec.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package ru.dbotthepony.mc.otm.data
-
-import com.google.gson.JsonSyntaxException
-import com.mojang.datafixers.util.Pair
-import com.mojang.serialization.Codec
-import com.mojang.serialization.DataResult
-import com.mojang.serialization.DynamicOps
-import com.mojang.serialization.JsonOps
-import net.minecraft.network.chat.Component
-
-object ComponentCodec : Codec<Component> {
-	override fun <T : Any> encode(input: Component, ops: DynamicOps<T>, prefix: T): DataResult<T> {
-		return DataResult.success(JsonOps.INSTANCE.convertTo(ops, Component.Serializer.toJsonTree(input)))
-	}
-
-	override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<Component, T>> {
-		val value = ops.convertTo(JsonOps.INSTANCE, input)
-
-		try {
-			return DataResult.success(Pair(Component.Serializer.fromJson(value), ops.empty()))
-		} catch (err: JsonSyntaxException) {
-			return DataResult.error { "Error decoding component: ${err.message}" }
-		}
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt
index 062564586..353a4578a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt
@@ -6,14 +6,26 @@ import com.mojang.serialization.DataResult
 import com.mojang.serialization.DynamicOps
 import it.unimi.dsi.fastutil.bytes.ByteArrayList
 import net.minecraft.nbt.NbtOps
+import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
 import ru.dbotthepony.mc.otm.core.math.Decimal
+import ru.dbotthepony.mc.otm.core.math.Decimal.Companion.fromByteArray
 import java.nio.ByteBuffer
-import java.util.stream.Collector
-import java.util.stream.Stream
 
 object DecimalCodec : Codec<Decimal> {
+	val NETWORK = object : StreamCodec<FriendlyByteBuf, Decimal> {
+		override fun decode(buf: FriendlyByteBuf): Decimal {
+			return fromByteArray(buf.readByteArray())
+		}
+
+		override fun encode(buf: FriendlyByteBuf, p_320396_: Decimal) {
+			buf.writeByteArray(p_320396_.toByteArray())
+		}
+	}
+
 	override fun <T : Any> encode(input: Decimal, ops: DynamicOps<T>, prefix: T): DataResult<T> {
 		if (ops === NbtOps.INSTANCE) {
+			// this is a hack, but at least we will get binary representation in binary file
 			return DataResult.success((ops as DynamicOps<T>).createByteList(ByteBuffer.wrap(input.toByteArray())))
 		}
 
@@ -27,14 +39,14 @@ object DecimalCodec : Codec<Decimal> {
 			} catch (err: NumberFormatException) {
 				DataResult.error { "Not a valid number for converting into Decimal: $it" }
 			}
-		}.get().map(
+		}.mapOrElse(
 			{
 				DataResult.success(it)
 			},
 			{ e0 ->
 				ops.getIntStream(input).flatMap {
 					try {
-						DataResult.success(Pair(Decimal.fromByteArray(
+						DataResult.success(Pair(fromByteArray(
 							it
 								.collect(::ByteArrayList, { v, a -> v.add(a.toByte()) }, ByteArrayList::addAll)
 								.toByteArray()
@@ -42,14 +54,14 @@ object DecimalCodec : Codec<Decimal> {
 					} catch (err: NumberFormatException) {
 						DataResult.error { "Failed to convert array of bytes into Decimal: $it" }
 					}
-				}.get().map(
+				}.mapOrElse(
 					{
 						DataResult.success(it)
 					},
 					{ e1 ->
 						ops.getNumberValue(input).flatMap {
 							DataResult.success(Pair(Decimal(it.toString()), ops.empty()))
-						}.get().map(
+						}.mapOrElse(
 							{
 								DataResult.success(it)
 							},
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalProvider.kt
index 1143aac1f..442ae3f43 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalProvider.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalProvider.kt
@@ -2,14 +2,15 @@ package ru.dbotthepony.mc.otm.data
 
 import com.mojang.datafixers.util.Either
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
-import net.minecraft.resources.ResourceLocation
 import net.minecraft.util.RandomSource
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.math.nextDecimal
+import ru.dbotthepony.mc.otm.registry.MDeferredRegister
 import ru.dbotthepony.mc.otm.registry.RegistryDelegate
 
 fun interface SampledDecimal {
@@ -18,7 +19,7 @@ fun interface SampledDecimal {
 
 abstract class DecimalProvider : SampledDecimal {
 	interface Type<T : DecimalProvider> {
-		val codec: Codec<T>
+		val codec: MapCodec<T>
 	}
 
 	abstract val minValue: Decimal
@@ -27,13 +28,12 @@ abstract class DecimalProvider : SampledDecimal {
 
 	companion object {
 		private val registryHolder = RegistryDelegate<Type<*>>("decimal_provider_type") {
-			setDefaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "zero"))
-			disableSaving()
+			defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "zero"))
 		}
 
 		val CODEC: Codec<DecimalProvider> by lazy {
 			Codec
-				.either(DecimalCodec, registry.codec.dispatch({ it.type }, { it.codec }))
+				.either(DecimalCodec, registry.byNameCodec().dispatch({ it.type }, { it.codec }))
 				.xmap(
 					{ c -> c.map(::ConstantDecimal, { it }) },
 					{ if (it.type === ConstantDecimal.Companion) Either.left(it.minValue) else Either.right(it) }
@@ -43,7 +43,7 @@ abstract class DecimalProvider : SampledDecimal {
 		val registry by registryHolder
 		val registryKey get() = registryHolder.key
 
-		private val registror = DeferredRegister.create(registryKey, OverdriveThatMatters.MOD_ID)
+		private val registror = MDeferredRegister(registryKey, OverdriveThatMatters.MOD_ID)
 
 		init {
 			registror.register("zero") { ConstantDecimal.Zero }
@@ -51,7 +51,7 @@ abstract class DecimalProvider : SampledDecimal {
 			registror.register("uniform") { UniformDecimal.Companion }
 		}
 
-		internal fun register(bus: IEventBus) {
+		fun register(bus: IEventBus) {
 			bus.addListener(registryHolder::build)
 			registror.register(bus)
 		}
@@ -64,7 +64,7 @@ class ConstantDecimal(val value: Decimal) : DecimalProvider() {
 			return Decimal.ZERO
 		}
 
-		override val codec: Codec<Zero> = Codec.unit(this)
+		override val codec: MapCodec<Zero> = MapCodec.unit(this)
 
 		override val minValue: Decimal
 			get() = Decimal.ZERO
@@ -86,7 +86,7 @@ class ConstantDecimal(val value: Decimal) : DecimalProvider() {
 		get() = Companion
 
 	companion object : Type<ConstantDecimal> {
-		override val codec: Codec<ConstantDecimal> = RecordCodecBuilder.create {
+		override val codec: MapCodec<ConstantDecimal> = RecordCodecBuilder.mapCodec {
 			it.group(DecimalCodec.fieldOf("value").forGetter(ConstantDecimal::value)).apply(it, ::ConstantDecimal)
 		}
 	}
@@ -107,7 +107,7 @@ class UniformDecimal(override val minValue: Decimal, override val maxValue: Deci
 		get() = Companion
 
 	companion object : Type<UniformDecimal> {
-		override val codec: Codec<UniformDecimal> = RecordCodecBuilder.create {
+		override val codec: MapCodec<UniformDecimal> = RecordCodecBuilder.mapCodec {
 			it.group(
 				DecimalCodec.fieldOf("minValue").forGetter(UniformDecimal::minValue),
 				DecimalCodec.fieldOf("maxValue").forGetter(UniformDecimal::maxValue),
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt
index da9e8fde5..6660f58a2 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt
@@ -12,6 +12,7 @@ import net.minecraft.world.entity.player.Player
 import net.minecraft.world.level.storage.loot.LootContext
 import net.minecraft.world.level.storage.loot.parameters.LootContextParam
 import net.minecraft.world.level.storage.loot.parameters.LootContextParams
+import ru.dbotthepony.mc.otm.core.mapOrNull
 import java.util.Optional
 import kotlin.reflect.KProperty1
 
@@ -61,15 +62,15 @@ operator fun <T> LootContext.get(param: LootContextParam<T>): T? {
 }
 
 fun <T> DataResult<T>.getOrNull(): T? {
-	return get().left().orElse(null)
+	return mapOrNull { it }
 }
 
 fun LootContext.findPlayer(): Player? {
-	return getParamOrNull(LootContextParams.DIRECT_KILLER_ENTITY).let {
+	return getParamOrNull(LootContextParams.DIRECT_ATTACKING_ENTITY).let {
 		if (it != null)
 			it as? Player
 		else
-			getParamOrNull(LootContextParams.KILLER_ENTITY).let {
+			getParamOrNull(LootContextParams.ATTACKING_ENTITY).let {
 				if (it != null) it as? Player else getParamOrNull(LootContextParams.THIS_ENTITY) as? Player
 			}
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientMatrixCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientMatrixCodec.kt
index 12d7117cd..cafb075bb 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientMatrixCodec.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientMatrixCodec.kt
@@ -52,7 +52,7 @@ class IngredientMatrixCodec(ingredientCodec: Codec<Ingredient>) : Codec<IIngredi
 	}
 
 	override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<IIngredientMatrix, T>> {
-		return ops.getList(input).get().map(
+		return ops.getList(input).mapOrElse(
 			{
 				val lines = ArrayList<DataResult<List<Ingredient>>>()
 
@@ -65,7 +65,7 @@ class IngredientMatrixCodec(ingredientCodec: Codec<Ingredient>) : Codec<IIngredi
 
 				lines.withIndex().forEach {
 					val (line, result) = it
-					result.get().map({ ingredients.add(it) }, { errors.add { "Line $line: ${it.message()}" } })
+					result.mapOrElse({ ingredients.add(it) }, { errors.add { "Line $line: ${it.message()}" } })
 				}
 
 				if (errors.isNotEmpty()) {
@@ -87,7 +87,7 @@ class IngredientMatrixCodec(ingredientCodec: Codec<Ingredient>) : Codec<IIngredi
 				}
 			},
 			{ err1 ->
-				handwrittenCodec.decode(ops, input).get().map(
+				handwrittenCodec.decode(ops, input).mapOrElse(
 					{
 						DataResult.success(it)
 					},
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/SingletonCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/SingletonCodec.kt
index eb2ade209..07613af20 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/SingletonCodec.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/SingletonCodec.kt
@@ -1,16 +1,22 @@
 package ru.dbotthepony.mc.otm.data
 
-import com.mojang.datafixers.util.Pair
-import com.mojang.serialization.Codec
 import com.mojang.serialization.DataResult
 import com.mojang.serialization.DynamicOps
+import com.mojang.serialization.MapCodec
+import com.mojang.serialization.MapLike
+import com.mojang.serialization.RecordBuilder
+import java.util.stream.Stream
 
-class SingletonCodec<V : Any>(val value: V) : Codec<V> {
-	override fun <T : Any> encode(input: V, ops: DynamicOps<T>, prefix: T): DataResult<T> {
-		return DataResult.success(ops.empty())
+class SingletonCodec<V : Any>(val value: V) : MapCodec<V>() {
+	override fun <T : Any?> keys(ops: DynamicOps<T>): Stream<T> {
+		return Stream.empty()
 	}
 
-	override fun <T : Any> decode(ops: DynamicOps<T>, input: T? /* Так то, оно должно быть null */): DataResult<Pair<V, T>> {
-		return DataResult.success(Pair(value, ops.empty()))
+	override fun <T : Any?> decode(ops: DynamicOps<T>, input: MapLike<T>): DataResult<V> {
+		return DataResult.success(value)
+	}
+
+	override fun <T : Any?> encode(input: V, ops: DynamicOps<T>, prefix: RecordBuilder<T>): RecordBuilder<T> {
+		return prefix
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt
deleted file mode 100644
index a6fead1fb..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-package ru.dbotthepony.mc.otm.data
-
-import com.mojang.datafixers.util.Pair
-import com.mojang.serialization.Codec
-import com.mojang.serialization.DataResult
-import com.mojang.serialization.DynamicOps
-import net.minecraft.nbt.NbtOps
-import java.util.UUID
-import java.util.stream.LongStream
-
-// because UUIDUtil codec is in unexpected place
-// and can't work with both strings, array of longs and array of ints
-object UUIDCodec : Codec<UUID> {
-	override fun <T : Any> encode(input: UUID, ops: DynamicOps<T>, prefix: T): DataResult<T> {
-		if (ops === NbtOps.INSTANCE) {
-			return DataResult.success((ops as DynamicOps<T>).createLongList(LongStream.of(input.mostSignificantBits, input.leastSignificantBits)))
-		}
-
-		return DataResult.success(ops.createString(input.toString()))
-	}
-
-	override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<UUID, T>> {
-		return ops.getLongStream(input).flatMap {
-			val l = it.limit(4).toArray()
-
-			if (l.size == 4) {
-				// 4 int
-				DataResult.success(Pair(UUID((l[0] shl 32) or l[1], (l[2] shl 32) or l[3]), ops.empty()))
-			} else if (l.size == 2) {
-				DataResult.success(Pair(UUID(l[0], l[1]), ops.empty()))
-			} else {
-				DataResult.error { "Can't construct UUID from ${l.size} elements" }
-			}
-		}.get().map(
-			{
-				DataResult.success(it)
-			},
-			{ e0 ->
-				ops.getStringValue(input).map {
-					Pair(UUID.fromString(it), ops.empty())
-				}.get().map(
-					{
-						DataResult.success(it)
-					},
-					{
-						DataResult.error { "Unable to deserialize UUID: ${e0.message()}; ${it.message()}" }
-					}
-				)
-			}
-		)
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceCondition.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceCondition.kt
index 4f8316e44..05a2a0c1a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceCondition.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceCondition.kt
@@ -1,6 +1,7 @@
 package ru.dbotthepony.mc.otm.data.condition
 
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.world.level.storage.loot.LootContext
 import net.minecraft.world.level.storage.loot.predicates.LootItemCondition
@@ -28,8 +29,8 @@ data class ChanceCondition(val chance: Double) : LootItemCondition, LootItemCond
 	}
 
 	companion object {
-		val CODEC: Codec<ChanceCondition> by lazy {
-			RecordCodecBuilder.create {
+		val CODEC: MapCodec<ChanceCondition> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(
 					Codec.doubleRange(0.0, 1.0).fieldOf("chance").forGetter(ChanceCondition::chance)
 				).apply(it, ::ChanceCondition)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceWithPlaytimeCondition.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceWithPlaytimeCondition.kt
index 92e501c6d..2f77776e6 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceWithPlaytimeCondition.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceWithPlaytimeCondition.kt
@@ -1,6 +1,7 @@
 package ru.dbotthepony.mc.otm.data.condition
 
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.world.level.storage.loot.LootContext
 import net.minecraft.world.level.storage.loot.parameters.LootContextParams
@@ -48,8 +49,8 @@ data class ChanceWithPlaytimeCondition(
 	}
 
 	companion object {
-		val CODEC: Codec<ChanceWithPlaytimeCondition> by lazy {
-			RecordCodecBuilder.create {
+		val CODEC: MapCodec<ChanceWithPlaytimeCondition> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(
 					Codec.INT.optionalFieldOf("minPlaytime", 0).forGetter(ChanceWithPlaytimeCondition::minPlaytime),
 					Codec.INT.fieldOf("maxPlaytime").forGetter(ChanceWithPlaytimeCondition::maxPlaytime),
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt
index f25f12e7d..d103a4966 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt
@@ -1,6 +1,7 @@
 package ru.dbotthepony.mc.otm.data.condition
 
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.storage.loot.LootContext
@@ -14,25 +15,12 @@ import ru.dbotthepony.mc.otm.registry.MLootItemConditions
 
 data class ItemInInventoryCondition(
 	val item: ItemStack,
-	val matchDamage: Boolean = false,
-	val matchNBT: Boolean = false,
+	val matchComponents: Boolean = false,
 	val matchCosmetics: Boolean = true,
 ) : LootItemCondition, LootItemCondition.Builder {
 	override fun test(t: LootContext): Boolean {
 		val matches = t[LootContextParams.LAST_DAMAGE_PLAYER]?.items(matchCosmetics)?.filter {
-			if (it.isEmpty) {
-				return@filter false
-			}
-
-			if (matchDamage && matchNBT) {
-				it.item == item.item && it.tag == item.tag && it.damageValue == item.damageValue
-			} else if (matchDamage) {
-				it.item == item.item && it.damageValue == item.damageValue
-			} else if (matchNBT) {
-				it.item == item.item && it.tag == item.tag
-			} else {
-				it.item == item.item
-			}
+			!it.isEmpty && it.item == item.item && (!matchComponents || it.components == item.components)
 		} ?: return false
 
 		var count = 0
@@ -57,12 +45,11 @@ data class ItemInInventoryCondition(
 	}
 
 	companion object {
-		val CODEC: Codec<ItemInInventoryCondition> by lazy {
-			RecordCodecBuilder.create {
+		val CODEC: MapCodec<ItemInInventoryCondition> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(
 					ItemStack.CODEC.fieldOf("item").forGetter(ItemInInventoryCondition::item),
-					Codec.BOOL.optionalFieldOf("matchDamage", false).forGetter(ItemInInventoryCondition::matchDamage),
-					Codec.BOOL.optionalFieldOf("matchNBT", false).forGetter(ItemInInventoryCondition::matchNBT),
+					Codec.BOOL.optionalFieldOf("matchComponents", false).forGetter(ItemInInventoryCondition::matchComponents),
 					Codec.BOOL.optionalFieldOf("matchCosmetics", false).forGetter(ItemInInventoryCondition::matchCosmetics),
 				).apply(it, ::ItemInInventoryCondition)
 			}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayerOrIndirectly.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayerOrIndirectly.kt
index b181ada7a..624ad09fb 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayerOrIndirectly.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayerOrIndirectly.kt
@@ -16,8 +16,8 @@ object KilledByRealPlayerOrIndirectly : LootItemCondition, LootItemCondition.Bui
 			return true
 		}
 
-		if (t.hasParam(LootContextParams.KILLER_ENTITY)) {
-			val killer = t[LootContextParams.KILLER_ENTITY] as? OwnableEntity
+		if (t.hasParam(LootContextParams.ATTACKING_ENTITY)) {
+			val killer = t[LootContextParams.ATTACKING_ENTITY] as? OwnableEntity
 
 			if (killer != null) {
 				val owner = killer.owner
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/CopyTileNbtFunction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/CopyTileNbtFunction.kt
index 5cd4f3c64..c97a73f92 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/CopyTileNbtFunction.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/CopyTileNbtFunction.kt
@@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.data.loot
 
 import com.google.common.collect.ImmutableList
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.network.chat.Component
@@ -15,7 +16,6 @@ import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
 import ru.dbotthepony.mc.otm.core.nbt.getJson
 import ru.dbotthepony.mc.otm.core.nbt.set
 import ru.dbotthepony.mc.otm.core.stream
-import ru.dbotthepony.mc.otm.core.tagNotNull
 import ru.dbotthepony.mc.otm.registry.MItemFunctionTypes
 import java.util.Optional
 import java.util.stream.Stream
@@ -73,8 +73,8 @@ class CopyTileNbtFunction(filter: Stream<out String> = Stream.empty()) : LootIte
 	}
 
 	companion object {
-		val CODEC: Codec<CopyTileNbtFunction> by lazy {
-			RecordCodecBuilder.create {
+		val CODEC: MapCodec<CopyTileNbtFunction> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(
 					Codec.STRING.listOf()
 						.optionalFieldOf("filter")
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/LootPoolAppender.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/LootPoolAppender.kt
index 99556a944..514745168 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/LootPoolAppender.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/LootPoolAppender.kt
@@ -1,15 +1,15 @@
 package ru.dbotthepony.mc.otm.data.loot
 
 import com.google.common.collect.ImmutableList
-import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import it.unimi.dsi.fastutil.objects.ObjectArrayList
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.storage.loot.LootContext
 import net.minecraft.world.level.storage.loot.LootPool
 import net.minecraft.world.level.storage.loot.predicates.LootItemCondition
-import net.minecraftforge.common.loot.IGlobalLootModifier
-import net.minecraftforge.common.loot.LootModifier
+import net.neoforged.neoforge.common.loot.IGlobalLootModifier
+import net.neoforged.neoforge.common.loot.LootModifier
 import java.util.*
 import java.util.stream.Stream
 
@@ -27,13 +27,13 @@ class LootPoolAppender(conditions: Array<out LootItemCondition>, pools: Stream<L
 		return generatedLoot
 	}
 
-	override fun codec(): Codec<out IGlobalLootModifier> {
+	override fun codec(): MapCodec<out IGlobalLootModifier> {
 		return CODEC
 	}
 
 	companion object {
-		val CODEC: Codec<LootPoolAppender> =
-			RecordCodecBuilder.create {
+		val CODEC: MapCodec<LootPoolAppender> =
+			RecordCodecBuilder.mapCodec {
 				codecStart(it).and(
 					LootPool.CODEC.listOf().fieldOf("pools").forGetter(LootPoolAppender::pools)
 				).apply(it, ::LootPoolAppender)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/MinecartCargoCrate.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/MinecartCargoCrate.kt
index 8e55da5e9..3e9863b8b 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/MinecartCargoCrate.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/MinecartCargoCrate.kt
@@ -76,9 +76,9 @@ class MinecartCargoCrate(
 		return ItemStack(item, 1)
 	}
 
-	override fun defineSynchedData() {
-		super.defineSynchedData()
-		entityData.define(INTERACTING_PLAYERS, 0)
+	override fun defineSynchedData(p_326003_: SynchedEntityData.Builder) {
+		super.defineSynchedData(p_326003_)
+		p_326003_.define(INTERACTING_PLAYERS, 0)
 	}
 
 	var interactingPlayers by entityData.delegate(INTERACTING_PLAYERS)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/PlasmaProjectile.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/PlasmaProjectile.kt
index 405a5f41f..a876e4c90 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/PlasmaProjectile.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/PlasmaProjectile.kt
@@ -1,7 +1,7 @@
 package ru.dbotthepony.mc.otm.entity
 
 import net.minecraft.core.particles.ParticleTypes
-import net.minecraft.world.entity.EntityType
+import net.minecraft.network.syncher.SynchedEntityData
 import net.minecraft.world.entity.projectile.Projectile
 import net.minecraft.world.entity.projectile.ProjectileUtil
 import net.minecraft.world.item.ItemStack
@@ -11,18 +11,18 @@ import net.minecraft.world.level.block.LevelEvent
 import net.minecraft.world.phys.BlockHitResult
 import net.minecraft.world.phys.EntityHitResult
 import net.minecraft.world.phys.HitResult
-import net.minecraftforge.event.ForgeEventFactory
+import net.neoforged.neoforge.event.EventHooks
 import ru.dbotthepony.mc.otm.core.damageType
 import ru.dbotthepony.mc.otm.registry.MDamageTypes
 import ru.dbotthepony.mc.otm.registry.MEntityTypes
 import ru.dbotthepony.mc.otm.registry.MatteryDamageSource
 
-class PlasmaProjectile(level: Level) : Projectile(MEntityTypes.PLASMA as EntityType<out Projectile>, level) {
+class PlasmaProjectile(level: Level) : Projectile(MEntityTypes.PLASMA, level) {
 	var inflictor: ItemStack? = null
 	var damage = 6.0f
 	var ttl = 200
 
-	override fun defineSynchedData() {
+	override fun defineSynchedData(p_326003_: SynchedEntityData.Builder) {
 
 	}
 
@@ -71,7 +71,7 @@ class PlasmaProjectile(level: Level) : Projectile(MEntityTypes.PLASMA as EntityT
 			}
 		}
 
-		if (trace.type != HitResult.Type.MISS && !ForgeEventFactory.onProjectileImpact(this, trace)) {
+		if (trace.type != HitResult.Type.MISS && !EventHooks.onProjectileImpact(this, trace)) {
 			onHit(trace)
 		}
 
@@ -106,8 +106,4 @@ class PlasmaProjectile(level: Level) : Projectile(MEntityTypes.PLASMA as EntityT
 
 		setPos(x, y, z)
 	}
-
-	companion object {
-
-	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt
index 86d5f126e..151f87c8d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt
@@ -7,10 +7,9 @@ import net.minecraft.core.Direction
 import net.minecraft.core.SectionPos
 import net.minecraft.server.level.ServerLevel
 import net.minecraft.world.level.block.entity.BlockEntity
-import net.minecraftforge.common.capabilities.Capability
+import net.neoforged.neoforge.capabilities.BlockCapability
 import ru.dbotthepony.mc.otm.addTicker
 import ru.dbotthepony.mc.otm.core.math.plus
-import ru.dbotthepony.mc.otm.core.orNull
 import ru.dbotthepony.mc.otm.core.util.IConditionalTickable
 import ru.dbotthepony.mc.otm.core.util.ITickable
 import java.util.*
@@ -165,12 +164,12 @@ open class GraphNode<N : GraphNode<N, G>, G : GraphNodeList<N, G>>(val graphFact
 	fun discover(
 		level: ServerLevel,
 		blockPos: BlockPos,
-		capability: Capability<out N>
+		capability: BlockCapability<out N, *>
 	) {
 		if (!isValid) return
 
 		level.addTicker {
-			isValid && !discoverStep(level, blockPos) { it.getCapability(capability).orNull() }
+			isValid && !discoverStep(level, blockPos) { level.getCapability(capability, blockPos, level.getBlockState(blockPos), it, null) }
 		}
 	}
 
@@ -178,7 +177,7 @@ open class GraphNode<N : GraphNode<N, G>, G : GraphNodeList<N, G>>(val graphFact
 		discover(blockEntity.level as? ServerLevel ?: return, blockEntity.blockPos, nodeGetter)
 	}
 
-	fun discover(blockEntity: BlockEntity, capability: Capability<out N>) {
+	fun discover(blockEntity: BlockEntity, capability: BlockCapability<out N, *>) {
 		discover(blockEntity.level as? ServerLevel ?: return, blockEntity.blockPos, capability)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/BatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/BatteryItem.kt
index 183d94078..7e33e097c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/BatteryItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/BatteryItem.kt
@@ -12,12 +12,10 @@ import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.*
 import net.minecraft.world.level.Level
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.capabilities.ICapabilityProvider
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.config.BatteryBalanceValues
 import ru.dbotthepony.mc.otm.capability.*
 import ru.dbotthepony.mc.otm.capability.energy.EnergyCapacitorItem
-import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl
 import ru.dbotthepony.mc.otm.capability.energy.getBarColor
 import ru.dbotthepony.mc.otm.capability.energy.getBarWidth
 import ru.dbotthepony.mc.otm.client.minecraft
@@ -108,13 +106,14 @@ open class BatteryItem : MatteryItem {
 	}
 
 	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
+		stack.getCapability()
 		return EnergyCapacitorItem(stack, this@BatteryItem.capacity, this@BatteryItem.receive, this@BatteryItem.extract, initialBatteryLevel = this@BatteryItem.initialBatteryLevel)
 	}
 }
 
 class CrudeBatteryItem : BatteryItem(ItemsConfig.Batteries.CRUDE) {
 	init {
-		tooltips.addNormal { itemStack, acceptor ->
+		tooltips.addNormal { itemStack, context, acceptor ->
 			val isAndroid = runIfClient(false) { minecraft.player?.matteryPlayer?.isAndroid ?: false }
 
 			if (isAndroid) {
@@ -128,7 +127,7 @@ class CrudeBatteryItem : BatteryItem(ItemsConfig.Batteries.CRUDE) {
 		return UseAnim.BOW
 	}
 
-	override fun getUseDuration(itemStack: ItemStack): Int {
+	override fun getUseDuration(itemStack: ItemStack, p_344979_: LivingEntity): Int {
 		return 100
 	}
 
@@ -148,7 +147,7 @@ class CrudeBatteryItem : BatteryItem(ItemsConfig.Batteries.CRUDE) {
 
 		if (player is ServerPlayer) {
 			if (!mattery.androidEnergy.item.isEmpty) {
-				mattery.androidEnergy.item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
+				mattery.androidEnergy.item.getCapability(Capabilities.EnergyStorage.ITEM)?.let {
 					it.extractEnergy((it.maxEnergyStored * level.random.nextFloat() * .2f).roundToInt(), false)
 				}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ChestUpgraderItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ChestUpgraderItem.kt
index 2a0af4fcf..ffee079ab 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ChestUpgraderItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ChestUpgraderItem.kt
@@ -21,6 +21,7 @@ import net.minecraft.world.level.gameevent.GameEvent
 import net.minecraft.world.level.storage.loot.LootParams
 import net.minecraft.world.level.storage.loot.parameters.LootContextParams
 import net.minecraftforge.event.entity.player.PlayerInteractEvent
+import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent
 import ru.dbotthepony.mc.otm.OverdriveThatMatters.MOD_ID
 import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock
 import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt
index c070a58a0..7822330e7 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt
@@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.item
 
 import net.minecraft.core.BlockPos
 import net.minecraft.core.Direction
-import net.minecraft.nbt.CompoundTag
 import net.minecraft.network.chat.Component
 import net.minecraft.server.level.ServerLevel
 import net.minecraft.sounds.SoundEvent
@@ -12,9 +11,7 @@ import net.minecraft.world.InteractionHand
 import net.minecraft.world.InteractionResult
 import net.minecraft.world.InteractionResultHolder
 import net.minecraft.world.entity.player.Player
-import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.TooltipFlag
 import net.minecraft.world.item.context.UseOnContext
 import net.minecraft.world.level.ClipContext
 import net.minecraft.world.level.Level
@@ -26,27 +23,25 @@ import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.level.material.Fluid
 import net.minecraft.world.level.material.Fluids
 import net.minecraft.world.phys.HitResult
-import net.minecraftforge.common.SoundActions
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.FluidUtil
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
+import net.neoforged.neoforge.common.SoundActions
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.FluidUtil
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.capability.fluid.ItemMatteryFluidHandler
-import ru.dbotthepony.mc.otm.capability.fluidLevel
 import ru.dbotthepony.mc.otm.capability.fluid.iterator
 import ru.dbotthepony.mc.otm.capability.moveFluid
 import ru.dbotthepony.mc.otm.container.get
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.collect.any
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.immutableList
 import ru.dbotthepony.mc.otm.core.immutableMap
 import ru.dbotthepony.mc.otm.core.isNotEmpty
-import ru.dbotthepony.mc.otm.core.orNull
+import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
 import java.util.function.IntSupplier
 
-class FluidCapsuleItem(val capacity: IntSupplier) : MatteryItem(Properties().stacksTo(64)) {
+class FluidCapsuleItem(val capacity: IntSupplier) : MatteryItem(Properties().stacksTo(64)), CapabilitiesRegisterListener {
 	// TODO: Так как использование предмета заблокировано за player.abilities.canBuild
 	// капсулу нельзя использовать в режиме приключения
 	// почему же можно использовать вёдра на котлах?
@@ -60,11 +55,15 @@ class FluidCapsuleItem(val capacity: IntSupplier) : MatteryItem(Properties().sta
 		return super.onItemUseFirst(stack, pContext)
 	}
 
+	override fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		event.registerItem(Capabilities.FluidHandler.ITEM, { o, c -> ItemMatteryFluidHandler(o, capacity) }, this)
+	}
+
 	override fun getName(pStack: ItemStack): Component {
-		pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
+		pStack.getCapability(Capabilities.FluidHandler.ITEM)?.let {
 			it.getFluidInTank(0).also {
 				if (!it.isEmpty) {
-					return TranslatableComponent("$descriptionId.named", it.displayName)
+					return TranslatableComponent("$descriptionId.named", it.hoverName)
 				}
 			}
 		}
@@ -76,14 +75,10 @@ class FluidCapsuleItem(val capacity: IntSupplier) : MatteryItem(Properties().sta
 		tooltips.itemFluid()
 	}
 
-	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
-		return ItemMatteryFluidHandler(stack, capacity)
-	}
-
 	override fun use(level: Level, player: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
 		val item = player.getItemInHand(hand)
 		var targetItem = item.copyWithCount(1)
-		val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResultHolder.fail(item)
+		val cap = targetItem.getCapability(Capabilities.FluidHandler.ITEM) ?: return InteractionResultHolder.fail(item)
 		val fluid = cap.getFluidInTank(0)
 		if (fluid.isNotEmpty && fluid.amount != 1000) return InteractionResultHolder.pass(item)
 
@@ -147,7 +142,7 @@ class FluidCapsuleItem(val capacity: IntSupplier) : MatteryItem(Properties().sta
 
 			val targetItem = if (item.count == 1) item else item.copyWithCount(1)
 
-			val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL
+			val cap = targetItem.getCapability(Capabilities.FluidHandler.ITEM) ?: return InteractionResult.FAIL
 
 			for ((fluid, newState, soundEvent) in mapping) {
 				val toDrain = FluidStack(fluid, 1000)
@@ -190,7 +185,7 @@ class FluidCapsuleItem(val capacity: IntSupplier) : MatteryItem(Properties().sta
 
 			val targetItem = if (item.count == 1) item else item.copyWithCount(1)
 
-			val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL
+			val cap = targetItem.getCapability(Capabilities.FluidHandler.ITEM) ?: return InteractionResult.FAIL
 			val mapped = mapping[context.blockState] ?: return InteractionResult.FAIL
 
 			val toFill = FluidStack(mapped.first, 1000)
@@ -218,8 +213,8 @@ class FluidCapsuleItem(val capacity: IntSupplier) : MatteryItem(Properties().sta
 
 	private object FillEmptyCapability : Interaction {
 		override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean {
-			val target = player.level().getBlockEntity(context.blockPos)?.getCapability(ForgeCapabilities.FLUID_HANDLER, context.side)?.orNull() ?: return false
-			val cap = item.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return false
+			val target = player.level().getCapability(Capabilities.FluidHandler.BLOCK, context.blockPos, context.side) ?: return false
+			val cap = item.getCapability(Capabilities.FluidHandler.ITEM) ?: return false
 			return target.iterator().any { !it.isEmpty } || cap.iterator().any { !it.isEmpty }
 		}
 
@@ -228,8 +223,8 @@ class FluidCapsuleItem(val capacity: IntSupplier) : MatteryItem(Properties().sta
 
 			val targetItem = if (item.count == 1) item else item.copyWithCount(1)
 
-			val itemCap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL
-			val blockCap = player.level().getBlockEntity(context.blockPos)?.getCapability(ForgeCapabilities.FLUID_HANDLER, context.side)?.orNull() ?: return InteractionResult.FAIL
+			val itemCap = targetItem.getCapability(Capabilities.FluidHandler.ITEM) ?: return InteractionResult.FAIL
+			val blockCap = player.level().getCapability(Capabilities.FluidHandler.BLOCK, context.blockPos, context.side) ?: return InteractionResult.FAIL
 			val fluid = itemCap[0]
 
 			if (fluid.isEmpty) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt
index 168385514..1474eafd9 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt
@@ -1,41 +1,35 @@
 package ru.dbotthepony.mc.otm.item
 
 import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer
-import net.minecraft.nbt.CompoundTag
 import net.minecraft.network.chat.Component
 import net.minecraft.world.InteractionResult
 import net.minecraft.world.item.BlockItem
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.TooltipFlag
 import net.minecraft.world.item.context.UseOnContext
-import net.minecraft.world.level.Level
-import net.minecraftforge.client.extensions.common.IClientItemExtensions
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.capabilities.ICapabilityProvider
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
+import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions
 import ru.dbotthepony.mc.otm.block.decorative.FluidTankBlock
 import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
-import ru.dbotthepony.mc.otm.capability.FlowDirection
-import ru.dbotthepony.mc.otm.capability.MatteryCapability
-import ru.dbotthepony.mc.otm.capability.energy.BlockEnergyStorageImpl
 import ru.dbotthepony.mc.otm.capability.fluid.BlockMatteryFluidHandler
-import ru.dbotthepony.mc.otm.capability.fluidLevel
 import ru.dbotthepony.mc.otm.client.render.blockentity.FluidTankRenderer
-import ru.dbotthepony.mc.otm.config.MachinesConfig
 import ru.dbotthepony.mc.otm.core.TooltipList
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
-import ru.dbotthepony.mc.otm.core.ifPresentK
+import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
 import java.util.function.Consumer
 import java.util.function.IntSupplier
 
-class FluidTankItem(block: FluidTankBlock, properties: Properties, val capacity: IntSupplier) : BlockItem(block, properties) {
+class FluidTankItem(block: FluidTankBlock, properties: Properties, val capacity: IntSupplier) : BlockItem(block, properties),
+	CapabilitiesRegisterListener {
 	val tooltips = TooltipList()
 
 	init {
 		tooltips.itemFluid()
 	}
 
-	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
-		return BlockMatteryFluidHandler.Item(stack, capacity, FluidTankBlockEntity.FLUID_KEY)
+	override fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		event.registerItem(Capabilities.FluidHandler.ITEM, { o, c -> BlockMatteryFluidHandler.Item(o, capacity, FluidTankBlockEntity.FLUID_KEY) }, this)
 	}
 
 	override fun onItemUseFirst(stack: ItemStack, pContext: UseOnContext): InteractionResult {
@@ -51,10 +45,10 @@ class FluidTankItem(block: FluidTankBlock, properties: Properties, val capacity:
 	}
 
 	override fun getName(pStack: ItemStack): Component {
-		pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
+		pStack.getCapability(Capabilities.FluidHandler.ITEM)?.let {
 			it.getFluidInTank(0).also {
 				if (!it.isEmpty) {
-					return TranslatableComponent("$descriptionId.named", it.displayName)
+					return TranslatableComponent("$descriptionId.named", it.hoverName)
 				}
 			}
 		}
@@ -62,9 +56,14 @@ class FluidTankItem(block: FluidTankBlock, properties: Properties, val capacity:
 		return super.getName(pStack)
 	}
 
-	override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltip: MutableList<Component>, pFlag: TooltipFlag) {
-		super.appendHoverText(pStack, pLevel, pTooltip, pFlag)
-		tooltips.assemble(pStack, pTooltip::add)
+	override fun appendHoverText(
+		pStack: ItemStack,
+		p_339594_: TooltipContext,
+		pTooltip: MutableList<Component>,
+		pFlag: TooltipFlag
+	) {
+		super.appendHoverText(pStack, p_339594_, pTooltip, pFlag)
+		tooltips.assemble(pStack, p_339594_, pTooltip::add)
 	}
 
 	override fun initializeClient(consumer: Consumer<IClientItemExtensions>) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/GravitationalDisruptorItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/GravitationalDisruptorItem.kt
index 4839d319f..b2ac1e5c8 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/GravitationalDisruptorItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/GravitationalDisruptorItem.kt
@@ -14,11 +14,11 @@ class GravitationalDisruptorItem :
 	Item(Properties().stacksTo(1).rarity(Rarity.EPIC)) {
 	override fun appendHoverText(
 		p_41421_: ItemStack,
-		p_41422_: Level?,
+		p_339594_: TooltipContext,
 		list: MutableList<Component>,
 		p_41424_: TooltipFlag
 	) {
-		super.appendHoverText(p_41421_, p_41422_, list, p_41424_)
+		super.appendHoverText(p_41421_, p_339594_, list, p_41424_)
 		list.add(DESCRIPTION)
 		list.add(DESCRIPTION2)
 		list.add(DESCRIPTION3)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatteryItem.kt
index d74a6294c..2670c9320 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatteryItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatteryItem.kt
@@ -13,9 +13,9 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent
 open class MatteryItem(properties: Properties) : Item(properties) {
 	val tooltips = TooltipList()
 
-	override fun appendHoverText(itemStack: ItemStack, level: Level?, components: MutableList<Component>, tooltipType: TooltipFlag) {
-		super.appendHoverText(itemStack, level, components, tooltipType)
-		tooltips.assemble(itemStack, components)
+	override fun appendHoverText(itemStack: ItemStack, context: TooltipContext, components: MutableList<Component>, tooltipType: TooltipFlag) {
+		super.appendHoverText(itemStack, context, components, tooltipType)
+		tooltips.assemble(itemStack, context, components)
 	}
 }
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PillItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PillItem.kt
index 8b0f17bb1..0b58ee83d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PillItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PillItem.kt
@@ -13,13 +13,14 @@ import net.minecraft.world.item.*
 import net.minecraft.world.level.Level
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
+import ru.dbotthepony.mc.otm.capability.matteryPlayer
 
 enum class PillType {
 	BECOME_ANDROID, BECOME_HUMANE, OBLIVION
 }
 
 class HealPillItem : MatteryItem(Properties().stacksTo(64).rarity(Rarity.UNCOMMON)) {
-	override fun getUseDuration(itemStack: ItemStack): Int {
+	override fun getUseDuration(itemStack: ItemStack, p_344979_: LivingEntity): Int {
 		return 24
 	}
 
@@ -29,9 +30,7 @@ class HealPillItem : MatteryItem(Properties().stacksTo(64).rarity(Rarity.UNCOMMO
 	}
 
 	override fun use(level: Level, ply: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
-		val resolver = ply.getCapability(MatteryCapability.MATTERY_PLAYER).resolve()
-
-		if (!resolver.isEmpty && resolver.get().isAndroid)
+		if (ply.matteryPlayer.isAndroid)
 			return super.use(level, ply, hand)
 
 		ply.startUsingItem(hand)
@@ -39,16 +38,13 @@ class HealPillItem : MatteryItem(Properties().stacksTo(64).rarity(Rarity.UNCOMMO
 	}
 
 	override fun finishUsingItem(stack: ItemStack, level: Level, ent: LivingEntity): ItemStack {
-		val resolver = ent.getCapability(MatteryCapability.MATTERY_PLAYER).resolve()
-
-		if (!resolver.isEmpty && resolver.get().isAndroid)
+		if (ent.matteryPlayer?.isAndroid == true)
 			return super.finishUsingItem(stack, level, ent)
 
 		stack.shrink(1)
 		ent.addEffect(MobEffectInstance(MobEffects.ABSORPTION, 20 * 60 * 2, 4))
 		ent.addEffect(MobEffectInstance(MobEffects.REGENERATION, 20 * 8, 2))
 		ent.heal(8f)
-
 		return stack
 	}
 
@@ -69,17 +65,12 @@ class PillItem(val pillType: PillType) : MatteryItem(Properties().stacksTo(64).r
 		}
 	}
 
-	override fun getUseDuration(p_41454_: ItemStack): Int {
+	override fun getUseDuration(p_41454_: ItemStack, p_344979_: LivingEntity): Int {
 		return 32
 	}
 
 	override fun use(level: Level, ply: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
-		val resolver = ply.getCapability(MatteryCapability.MATTERY_PLAYER).resolve()
-
-		if (resolver.isEmpty)
-			return super.use(level, ply, hand)
-
-		val cap = resolver.get()
+		val cap = ply.matteryPlayer
 
 		if (
 			pillType == PillType.BECOME_ANDROID && !cap.isEverAndroid ||
@@ -95,12 +86,7 @@ class PillItem(val pillType: PillType) : MatteryItem(Properties().stacksTo(64).r
 
 	override fun finishUsingItem(stack: ItemStack, level: Level, ent: LivingEntity): ItemStack {
 		if (ent is Player) {
-			val resolver = ent.getCapability(MatteryCapability.MATTERY_PLAYER).resolve()
-
-			if (resolver.isEmpty)
-				return super.finishUsingItem(stack, level, ent)
-
-			val cap = resolver.get()
+			val cap = ent.matteryPlayer
 
 			if (pillType == PillType.OBLIVION && cap.isAndroid) {
 				if (!ent.abilities.instabuild) {
@@ -137,7 +123,7 @@ class PillItem(val pillType: PillType) : MatteryItem(Properties().stacksTo(64).r
 				}
 			}
 
-			return stack;
+			return stack
 		}
 
 		return super.finishUsingItem(stack, level, ent)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt
index 5a36f6891..dc9c23b7f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt
@@ -1,150 +1,107 @@
 package ru.dbotthepony.mc.otm.item
 
-import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.common.util.LazyOptional
-import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive
-import ru.dbotthepony.mc.otm.capability.drive.ItemMatteryDrive
-import net.minecraft.nbt.CompoundTag
-import ru.dbotthepony.mc.otm.capability.MatteryCapability
-import net.minecraft.world.item.TooltipFlag
 import net.minecraft.ChatFormatting
-import net.minecraft.core.Direction
+import net.minecraft.nbt.CompoundTag
 import net.minecraft.network.chat.Component
 import net.minecraft.stats.Stats
 import net.minecraft.world.item.Item
-import net.minecraft.world.level.Level
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.event.ForgeEventFactory
-import net.minecraftforge.event.entity.player.EntityItemPickupEvent
-import ru.dbotthepony.mc.otm.core.TextComponent
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.TooltipFlag
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
+import net.neoforged.neoforge.common.util.TriState
+import net.neoforged.neoforge.event.entity.player.ItemEntityPickupEvent
+import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.drive.DrivePool
+import ru.dbotthepony.mc.otm.capability.drive.ItemMatteryDrive
 import ru.dbotthepony.mc.otm.container.ItemFilter
-import ru.dbotthepony.mc.otm.core.nbt.map
-import ru.dbotthepony.mc.otm.core.nbt.set
-import ru.dbotthepony.mc.otm.core.tagNotNull
+import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.isServerThread
+import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
+import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
 import java.math.BigInteger
 import java.util.*
 
-class PortableCondensationDriveItem(capacity: Int) :
-	Item(Properties().stacksTo(1)) {
+class PortableCondensationDriveItem(capacity: Int) : Item(Properties().stacksTo(1)), CapabilitiesRegisterListener {
 	val capacity: BigInteger = capacity.toBigInteger()
 
-	private inner class DriveCapability(private val stack: ItemStack) : ICapabilityProvider {
-		private var uuid: UUID? = null
-
-		private val resolver = LazyOptional.of<IMatteryDrive<*>> {
-			if (!isServerThread()) {
-				return@of ItemMatteryDrive.DUMMY
-			}
-
-			val uuid = uuid
-
-			return@of DrivePool.get(uuid!!, { tag: CompoundTag? ->
-				val drive = ItemMatteryDrive(capacity, uuid)
-				drive.deserializeNBT(tag!!)
-				return@get drive
-			}, { ItemMatteryDrive(capacity, uuid) })
-		}
-
-		override fun <T> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
-			if (cap === MatteryCapability.DRIVE) {
-				if (uuid == null) {
-					val array = stack.orCreateTag.getLongArray("uuid")
-
-					if (array.size == 2) {
-						uuid = UUID(array[0], array[1])
-					} else {
-						uuid = UUID.randomUUID()
-						stack.orCreateTag.putLongArray("uuid", longArrayOf(uuid!!.mostSignificantBits, uuid!!.leastSignificantBits))
-					}
-				}
-
-				return resolver.cast()
-			}
-
-			return LazyOptional.empty()
-		}
-	}
-
 	override fun appendHoverText(
 		stack: ItemStack,
-		p_41422_: Level?,
+		p_339594_: TooltipContext,
 		p_41423_: MutableList<Component>,
 		p_41424_: TooltipFlag
 	) {
-		super.appendHoverText(stack, p_41422_, p_41423_, p_41424_)
-		if (stack.tag != null) {
-			val uuid = stack.tag!!.getLongArray("uuid")
-			if (uuid.size == 2) {
-				p_41423_.add(TextComponent(UUID(uuid[0], uuid[1]).toString()).withStyle(ChatFormatting.GRAY))
-			}
+		super.appendHoverText(stack, p_339594_, p_41423_, p_41424_)
+
+		if (stack.has(MDataComponentTypes.CONDENSATION_DRIVE_UUID)) {
+			p_41423_.add(TextComponent(stack[MDataComponentTypes.CONDENSATION_DRIVE_UUID].toString()).withStyle(ChatFormatting.GRAY))
 		}
 	}
 
-	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
-		return DriveCapability(stack)
-	}
+	override fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		event.registerItem(MatteryCapability.CONDENSATION_DRIVE, { o, _ ->
+			if (!o.has(MDataComponentTypes.CONDENSATION_DRIVE_UUID)) {
+				o[MDataComponentTypes.CONDENSATION_DRIVE_UUID] = UUID.randomUUID()
+			}
 
-	fun getFilterSettings(drive: ItemStack): ItemFilter {
-		var ignore = true
-		val filter = ItemFilter(MAX_FILTERS) { if (!ignore) drive.tagNotNull[FILTER_PATH] = it.serializeNBT() }
-		filter.isWhitelist = true
-		drive.tag?.map(FILTER_PATH, filter::deserializeNBT)
-		ignore = false
-		return filter
+			if (!isServerThread()) {
+				return@registerItem ItemMatteryDrive.DUMMY
+			}
+
+			DrivePool.get(o[MDataComponentTypes.CONDENSATION_DRIVE_UUID]!!, { tag: CompoundTag?, lookup ->
+				val drive = ItemMatteryDrive(capacity, o[MDataComponentTypes.CONDENSATION_DRIVE_UUID]!!)
+				drive.deserializeNBT(tag!!, lookup)
+				return@get drive
+			}, { ItemMatteryDrive(capacity, o[MDataComponentTypes.CONDENSATION_DRIVE_UUID]!!) })
+		}, this)
 	}
 
 	@Suppress("unused")
 	companion object {
-		const val MAX_FILTERS = 4 * 3
-		const val FILTER_PATH = "filter"
-
-		fun onPickupEvent(event: EntityItemPickupEvent) {
-			if (event.item.owner != null && event.item.owner != event.entity && event.item.age < 200 || event.item.item.isEmpty) {
+		fun onPickupEvent(event: ItemEntityPickupEvent.Pre) {
+			if (event.itemEntity.owner != null && event.itemEntity.owner != event.player && event.itemEntity.age < 200 || event.itemEntity.item.isEmpty) {
 				return
 			}
 
-			var amount = event.item.item.count
-			val item = event.item.item.item
+			var amount = event.itemEntity.item.count
+			val item = event.itemEntity.item.item
 
-			for (stack in event.entity.inventory.items) {
+			for (stack in event.player.inventory.items) {
 				val drive = stack.item
 
 				if (drive is PortableCondensationDriveItem) {
 					var doBreak = false
 
-					stack.getCapability(MatteryCapability.DRIVE).ifPresent {
-						val filter = drive.getFilterSettings(stack)
+					stack.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let {
+						val filter = stack[MDataComponentTypes.ITEM_FILTER] ?: ItemFilter.EMPTY
 
-						if (filter.match(event.item.item)) {
-							val copy = event.item.item.copy()
-							val remaining = (it as ItemMatteryDrive).insertStack(event.item.item, false)
+						if (filter.match(event.itemEntity.item)) {
+							val copy = event.itemEntity.item.copy()
+							val remaining = (it as ItemMatteryDrive).insertStack(event.itemEntity.item, false)
 
-							if (remaining.count == event.item.item.count) {
-								return@ifPresent
+							if (remaining.count == event.itemEntity.item.count) {
+								return@let
 							}
 
 							copy.count -= remaining.count
-							event.isCanceled = true
-							ForgeEventFactory.firePlayerItemPickupEvent(event.entity, event.item, copy)
+							event.setCanPickup(TriState.FALSE)
+							// TODO
+							// EventHooks.firePlayerItemPickupEvent(event.entity, event.item, copy)
 
 							if (remaining.count > 0) {
-								event.item.item.count = remaining.count
-								event.entity.take(event.item, amount - remaining.count)
+								event.itemEntity.item.count = remaining.count
+								event.player.take(event.itemEntity, amount - remaining.count)
 
-								event.entity.awardStat(Stats.ITEM_PICKED_UP.get(item), amount - remaining.count)
-								event.entity.onItemPickup(event.item)
+								event.player.awardStat(Stats.ITEM_PICKED_UP.get(item), amount - remaining.count)
+								event.player.onItemPickup(event.itemEntity)
 
 								amount = remaining.count
 							} else {
-								event.entity.take(event.item, amount)
+								event.player.take(event.itemEntity, amount)
 
-								event.entity.awardStat(Stats.ITEM_PICKED_UP.get(item), amount)
-								event.entity.onItemPickup(event.item)
+								event.player.awardStat(Stats.ITEM_PICKED_UP.get(item), amount)
+								event.player.onItemPickup(event.itemEntity)
 
-								event.item.discard()
+								event.itemEntity.discard()
 								doBreak = true
 							}
 						}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralBatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralBatteryItem.kt
index b50df462c..7ba2650ad 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralBatteryItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralBatteryItem.kt
@@ -1,19 +1,16 @@
 package ru.dbotthepony.mc.otm.item
 
-import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.ChatFormatting
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.network.chat.Component
-import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.TooltipFlag
-import net.minecraft.world.level.Level
 import net.minecraft.world.level.storage.loot.LootContext
 import net.minecraft.world.level.storage.loot.functions.LootItemFunction
 import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType
-import net.minecraftforge.common.capabilities.ICapabilityProvider
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
 import ru.dbotthepony.mc.otm.capability.FlowDirection
+import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl
 import ru.dbotthepony.mc.otm.capability.energy.batteryLevel
 import ru.dbotthepony.mc.otm.capability.energy.getBarColor
@@ -21,26 +18,25 @@ import ru.dbotthepony.mc.otm.capability.energy.getBarWidth
 import ru.dbotthepony.mc.otm.capability.matteryEnergy
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.mc.otm.core.math.set
-import ru.dbotthepony.mc.otm.core.nbt.mapPresent
-import ru.dbotthepony.mc.otm.core.tagNotNull
 import ru.dbotthepony.mc.otm.data.DecimalProvider
+import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
+import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
 import ru.dbotthepony.mc.otm.registry.MItemFunctionTypes
-import java.util.Optional
+import java.util.*
 
-class ProceduralBatteryItem : MatteryItem(Properties().stacksTo(1)) {
+class ProceduralBatteryItem : MatteryItem(Properties().stacksTo(1)), CapabilitiesRegisterListener {
 	class Cap(stack: ItemStack) : ItemEnergyStorageImpl(stack) {
 		override var maxInput: Decimal
-			get() = itemStack.tag?.mapPresent("maxInput", Decimal.Companion::deserializeNBT) ?: Decimal.ZERO
-			set(value) { itemStack.tagNotNull["maxInput"] = value }
+			get() = itemStack[MDataComponentTypes.MAX_BATTERY_INPUT] ?: Decimal.ZERO
+			set(value) { itemStack[MDataComponentTypes.MAX_BATTERY_INPUT] = value }
 
 		override var maxOutput: Decimal
-			get() = itemStack.tag?.mapPresent("maxOutput", Decimal.Companion::deserializeNBT) ?: Decimal.ZERO
-			set(value) { itemStack.tagNotNull["maxOutput"] = value }
+			get() = itemStack[MDataComponentTypes.MAX_BATTERY_OUTPUT] ?: Decimal.ZERO
+			set(value) { itemStack[MDataComponentTypes.MAX_BATTERY_OUTPUT] = value }
 
 		override var maxBatteryLevel: Decimal
-			get() = itemStack.tag?.mapPresent("maxBatteryLevel", Decimal.Companion::deserializeNBT) ?: Decimal.ZERO
-			set(value) { itemStack.tagNotNull["maxBatteryLevel"] = value }
+			get() = itemStack[MDataComponentTypes.MAX_BATTERY_LEVEL] ?: Decimal.ZERO
+			set(value) { itemStack[MDataComponentTypes.MAX_BATTERY_LEVEL] = value }
 
 		override val energyFlow: FlowDirection
 			get() = FlowDirection.BI_DIRECTIONAL
@@ -62,7 +58,7 @@ class ProceduralBatteryItem : MatteryItem(Properties().stacksTo(1)) {
 	}
 
 	init {
-		tooltips.addNormal { itemStack, acceptor ->
+		tooltips.addNormal { itemStack, context, acceptor ->
 			val energy = itemStack.matteryEnergy
 
 			if (energy is Cap) {
@@ -110,7 +106,7 @@ class ProceduralBatteryItem : MatteryItem(Properties().stacksTo(1)) {
 			return t
 		}
 
-		override fun getType(): LootItemFunctionType {
+		override fun getType(): LootItemFunctionType<Randomizer> {
 			return MItemFunctionTypes.PROCEDURAL_BATTERY
 		}
 
@@ -119,8 +115,8 @@ class ProceduralBatteryItem : MatteryItem(Properties().stacksTo(1)) {
 		}
 
 		companion object {
-			val CODEC: Codec<Randomizer> by lazy {
-				RecordCodecBuilder.create {
+			val CODEC: MapCodec<Randomizer> by lazy {
+				RecordCodecBuilder.mapCodec {
 					it.group(
 						DecimalProvider.CODEC.fieldOf("maxBatteryLevel").forGetter(Randomizer::maxBatteryLevel),
 						DecimalProvider.CODEC.optionalFieldOf("batteryLevel").forGetter(Randomizer::batteryLevel),
@@ -132,7 +128,8 @@ class ProceduralBatteryItem : MatteryItem(Properties().stacksTo(1)) {
 		}
 	}
 
-	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
-		return Cap(stack)
+	override fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		event.registerItem(Capabilities.EnergyStorage.ITEM, { o, _ -> Cap(o) }, this)
+		event.registerItem(MatteryCapability.ITEM_ENERGY, { o, _ -> Cap(o) }, this)
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt
index 9abcfed1b..fe3d2605f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt
@@ -4,9 +4,12 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
 import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
 import net.minecraft.ChatFormatting
 import net.minecraft.core.Direction
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.network.FriendlyByteBuf
 import net.minecraft.network.chat.Component
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.util.datafix.DataFixTypes
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
@@ -22,6 +25,8 @@ import net.minecraftforge.common.util.LazyOptional
 import net.minecraftforge.event.TickEvent
 import net.minecraftforge.event.TickEvent.ServerTickEvent
 import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.capability.FlowDirection
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.trackedItems
@@ -30,6 +35,7 @@ import ru.dbotthepony.mc.otm.capability.energy.getBarColor
 import ru.dbotthepony.mc.otm.capability.energy.getBarWidth
 import ru.dbotthepony.mc.otm.capability.matteryEnergy
 import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.collect.filter
 import ru.dbotthepony.mc.otm.core.getID
@@ -48,8 +54,6 @@ import ru.dbotthepony.mc.otm.core.util.formatPower
 import ru.dbotthepony.mc.otm.isClientThread
 import ru.dbotthepony.mc.otm.isServerThread
 import ru.dbotthepony.mc.otm.lazyPerServer
-import ru.dbotthepony.mc.otm.network.GenericNetworkChannel
-import ru.dbotthepony.mc.otm.network.MNetworkContext
 import ru.dbotthepony.mc.otm.network.MatteryPacket
 import java.util.*
 import java.util.function.Function
@@ -352,27 +356,34 @@ class QuantumBatteryItem(val savedataID: String, val balanceValues: EnergyBalanc
 				}
 			}
 		}
+
+		val PACKET_TYPE = CustomPacketPayload.Type<ChargePacket>(ResourceLocation(OverdriveThatMatters.MOD_ID, "quantum_charge"))
+		val PACKET_CODEC: StreamCodec<FriendlyByteBuf, ChargePacket> = StreamCodec.ofMember(ChargePacket::write, ::readPacket)
 	}
 
-	class ChargePacket(
+	data class ChargePacket(
 		val type: QuantumBatteryItem,
 		override val uuid: UUID,
 		override var energy: Decimal,
 		override var passed: Decimal,
 		override var received: Decimal,
-	) : MatteryPacket, IValues {
+	) : CustomPacketPayload, IValues {
 		override val isServer: Boolean
 			get() = false
 
-		override fun write(buff: FriendlyByteBuf) {
-			buff.writeInt(ForgeRegistries.ITEMS.getID(type))
+		override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+			return PACKET_TYPE
+		}
+
+		fun write(buff: FriendlyByteBuf) {
+			buff.writeInt(BuiltInRegistries.ITEM.getId(type))
 			buff.writeUUID(uuid)
 			buff.writeDecimal(energy)
 			buff.writeDecimal(passed)
 			buff.writeDecimal(received)
 		}
 
-		override fun play(context: MNetworkContext) {
+		fun play(context: IPayloadContext) {
 			val data = type.clientData.computeIfAbsent(uuid, Function { UnboundValues(it) })
 			data.energy = energy
 			data.passed = passed
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt
index bdee5e757..e3fe8dc83 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt
@@ -1,21 +1,12 @@
 package ru.dbotthepony.mc.otm.item
 
-import net.minecraft.core.Direction
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.network.chat.Component
-import net.minecraft.world.item.Item
-import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.TooltipFlag
-import net.minecraft.world.level.Level
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.common.util.LazyOptional
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
 import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.UpgradeType
 import ru.dbotthepony.mc.otm.capability.addUpgradeTooltipLines
-import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.core.math.Decimal
+import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
 
 class SimpleUpgrade(
 	properties: Properties,
@@ -30,25 +21,15 @@ class SimpleUpgrade(
 	override val matterStorage: Decimal = Decimal.ZERO,
 	override val matterStorageFlat: Decimal = Decimal.ZERO,
 	override val failureMultiplier: Double = 1.0,
-) : MatteryItem(properties), IMatteryUpgrade, ICapabilityProvider {
-	private val resolver = LazyOptional.of { this }
-
-	override fun <T : Any?> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
-		if (cap === MatteryCapability.UPGRADE) {
-			return resolver.cast()
-		}
-
-		return LazyOptional.empty()
-	}
-
+) : MatteryItem(properties), IMatteryUpgrade, CapabilitiesRegisterListener {
 	init {
-		tooltips.addNormal { _, acceptor ->
+		tooltips.addNormal { _, _, acceptor ->
 			addUpgradeTooltipLines(acceptor)
 		}
 	}
 
-	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
-		return this
+	override fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		event.registerItem(MatteryCapability.UPGRADE, { _, _ -> this }, this)
 	}
 }
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/SingleUseBatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SingleUseBatteryItem.kt
index a1e4f6d2d..b1b172ad7 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/SingleUseBatteryItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SingleUseBatteryItem.kt
@@ -1,29 +1,26 @@
 package ru.dbotthepony.mc.otm.item
 
 import net.minecraft.ChatFormatting
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.network.chat.Component
-import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Rarity
-import net.minecraft.world.item.TooltipFlag
-import net.minecraft.world.level.Level
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
-import ru.dbotthepony.mc.otm.capability.*
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
+import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.energy.EnergyProducerItem
-import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl
 import ru.dbotthepony.mc.otm.capability.energy.getBarColor
 import ru.dbotthepony.mc.otm.capability.energy.getBarWidth
+import ru.dbotthepony.mc.otm.capability.matteryEnergy
+import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
 import ru.dbotthepony.mc.otm.config.ItemsConfig
-import ru.dbotthepony.mc.otm.core.*
+import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.math.Decimal
+import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
 
 open class SingleUseBatteryItem(
 	private val _capacity: () -> Decimal,
 	private val _throughput: () -> Decimal? = { null },
 	properties: Properties = Properties().stacksTo(1)
-) : MatteryItem(properties) {
+) : MatteryItem(properties), CapabilitiesRegisterListener {
 	constructor(values: EnergyBalanceValues, properties: Properties = Properties().stacksTo(1)) : this(values::energyCapacity, values::energyThroughput, properties)
 	constructor(storage: Decimal, throughput: Decimal? = null, properties: Properties = Properties().stacksTo(1)) : this({ storage }, { throughput }, properties)
 
@@ -35,8 +32,9 @@ open class SingleUseBatteryItem(
 		tooltips.itemEnergy()
 	}
 
-	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
-		return EnergyProducerItem(stack, capacity, throughput, initialBatteryLevel = capacity)
+	override fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		event.registerItem(MatteryCapability.ITEM_ENERGY, { o, _ -> EnergyProducerItem(o, capacity, throughput, initialBatteryLevel = capacity) }, this)
+		event.registerItem(Capabilities.EnergyStorage.ITEM, { o, _ -> EnergyProducerItem(o, capacity, throughput, initialBatteryLevel = capacity) }, this)
 	}
 
 	override fun isBarVisible(p_150899_: ItemStack): Boolean {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/PortableGravitationStabilizerItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/PortableGravitationStabilizerItem.kt
index 39eff1c51..9df928cca 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/PortableGravitationStabilizerItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/PortableGravitationStabilizerItem.kt
@@ -9,8 +9,7 @@ import net.minecraft.world.entity.EquipmentSlot
 import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.item.*
 import net.minecraft.world.item.crafting.Ingredient
-import net.minecraft.world.level.Level
-import net.minecraftforge.client.extensions.common.IClientItemExtensions
+import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.client.model.GravitationStabilizerModel
@@ -49,11 +48,11 @@ class PortableGravitationStabilizerItem : ArmorItem(GravitationStabilizerArmorMa
 
 	override fun appendHoverText(
 		p_41421_: ItemStack,
-		p_41422_: Level?,
+		p_339594_: TooltipContext,
 		p_41423_: MutableList<Component>,
 		p_41424_: TooltipFlag
 	) {
-		super.appendHoverText(p_41421_, p_41422_, p_41423_, p_41424_)
+		super.appendHoverText(p_41421_, p_339594_, p_41423_, p_41424_)
 
 		p_41423_.add(DESCRIPTION)
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/SimpleTritaniumArmorItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/SimpleTritaniumArmorItem.kt
index 4441e7237..3ed2400e7 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/SimpleTritaniumArmorItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/SimpleTritaniumArmorItem.kt
@@ -1,5 +1,6 @@
 package ru.dbotthepony.mc.otm.item.armor
 
+import net.minecraft.resources.ResourceLocation
 import net.minecraft.sounds.SoundEvent
 import net.minecraft.sounds.SoundEvents
 import net.minecraft.world.entity.Entity
@@ -9,46 +10,22 @@ import net.minecraft.world.item.ArmorMaterial
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.crafting.Ingredient
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
+import ru.dbotthepony.mc.otm.registry.MArmorMaterials
 import ru.dbotthepony.mc.otm.registry.MItemTags
 
-private object SimpleTritaniumArmorMaterial : ArmorMaterial {
-	override fun getDurabilityForType(p_40410_: ArmorItem.Type): Int {
-		return when (p_40410_) {
-			ArmorItem.Type.HELMET -> 380
-			ArmorItem.Type.CHESTPLATE -> 590
-			ArmorItem.Type.LEGGINGS -> 500
-			ArmorItem.Type.BOOTS -> 420
-			else -> throw IllegalArgumentException("yo dude what the fuck $p_40410_")
-		}
-	}
-
-	override fun getDefenseForType(p_40411_: ArmorItem.Type): Int {
-		return when (p_40411_) {
-			ArmorItem.Type.HELMET -> 2
-			ArmorItem.Type.CHESTPLATE -> 6
-			ArmorItem.Type.LEGGINGS -> 7
-			ArmorItem.Type.BOOTS -> 2
-			else -> throw IllegalArgumentException("yo dude what the fuck $p_40411_")
-		}
-	}
-
-	override fun getEnchantmentValue() = 9
-	override fun getEquipSound(): SoundEvent = SoundEvents.ARMOR_EQUIP_IRON
-	override fun getRepairIngredient(): Ingredient = Ingredient.of(MItemTags.TRITANIUM_INGOTS)
-
-	const val ID = "${OverdriveThatMatters.MOD_ID}:simple_tritanium_armor"
-
-	override fun getName(): String = ID
-	override fun getToughness() = 0.3f
-	override fun getKnockbackResistance() = 0f
-}
-
-class SimpleTritaniumArmorItem(slot: Type) : ArmorItem(SimpleTritaniumArmorMaterial, slot, Properties().stacksTo(1)) {
-	override fun getArmorTexture(stack: ItemStack, entity: Entity?, slot: EquipmentSlot, type: String?): String? {
-		if (type != "overlay" || slot == EquipmentSlot.FEET)
+class SimpleTritaniumArmorItem(slot: Type) : ArmorItem(MArmorMaterials.SIMPLE_TRITANIUM, slot, Properties().stacksTo(1)) {
+	override fun getArmorTexture(
+		stack: ItemStack,
+		entity: Entity,
+		slot: EquipmentSlot,
+		layer: ArmorMaterial.Layer,
+		innerModel: Boolean
+	): ResourceLocation? {
+		if (!innerModel || slot == EquipmentSlot.FEET)
 			return when (slot) {
-				EquipmentSlot.LEGS -> "${OverdriveThatMatters.MOD_ID}:textures/models/armor/tritanium_simple_layer_2.png"
-				EquipmentSlot.FEET, EquipmentSlot.CHEST, EquipmentSlot.HEAD -> "${OverdriveThatMatters.MOD_ID}:textures/models/armor/tritanium_simple_layer_1.png"
+				EquipmentSlot.LEGS -> ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/models/armor/tritanium_simple_layer_2.png")
+				EquipmentSlot.FEET, EquipmentSlot.CHEST, EquipmentSlot.HEAD -> ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/models/armor/tritanium_simple_layer_1.png")
 				else -> throw IllegalArgumentException("Invalid slot $slot")
 			}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/TritaniumArmorItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/TritaniumArmorItem.kt
index dcb2beb0b..9efa14e31 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/TritaniumArmorItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/TritaniumArmorItem.kt
@@ -1,53 +1,18 @@
 package ru.dbotthepony.mc.otm.item.armor
 
 import net.minecraft.client.model.HumanoidModel
-import net.minecraft.sounds.SoundEvent
-import net.minecraft.sounds.SoundEvents
 import net.minecraft.world.damagesource.DamageTypes
-import net.minecraft.world.entity.Entity
 import net.minecraft.world.entity.EquipmentSlot
 import net.minecraft.world.entity.LivingEntity
 import net.minecraft.world.item.*
-import net.minecraft.world.item.crafting.Ingredient
-import net.minecraftforge.client.extensions.common.IClientItemExtensions
-import net.minecraftforge.event.entity.living.LivingAttackEvent
-import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.client.model.TritaniumArmorModel
+import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions
+import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent
 import ru.dbotthepony.kommons.math.RGBAColor
-import ru.dbotthepony.mc.otm.registry.MItemTags
+import ru.dbotthepony.mc.otm.client.model.TritaniumArmorModel
+import ru.dbotthepony.mc.otm.registry.MArmorMaterials
 import ru.dbotthepony.mc.otm.registry.MItems
 import java.util.function.Consumer
 
-private object TritaniumArmorMaterial : ArmorMaterial {
-	override fun getDurabilityForType(p_40410_: ArmorItem.Type): Int {
-		return when (p_40410_) {
-			ArmorItem.Type.HELMET -> 520
-			ArmorItem.Type.CHESTPLATE -> 920
-			ArmorItem.Type.LEGGINGS -> 650
-			ArmorItem.Type.BOOTS -> 540
-		}
-	}
-
-	override fun getDefenseForType(p_40411_: ArmorItem.Type): Int {
-		return when (p_40411_) {
-			ArmorItem.Type.HELMET -> 4
-			ArmorItem.Type.CHESTPLATE -> 9
-			ArmorItem.Type.LEGGINGS -> 7
-			ArmorItem.Type.BOOTS -> 3
-		}
-	}
-
-	override fun getEnchantmentValue() = 9
-	override fun getEquipSound(): SoundEvent = SoundEvents.ARMOR_EQUIP_IRON
-	override fun getRepairIngredient(): Ingredient = Ingredient.of(MItemTags.REINFORCED_TRITANIUM_PLATES)
-
-	const val ID = "${OverdriveThatMatters.MOD_ID}:tritanium_armor"
-
-	override fun getName(): String = ID
-	override fun getToughness() = 1f
-	override fun getKnockbackResistance() = 0.08f
-}
-
 private object TritaniumArmorRenderProperties : IClientItemExtensions {
 	override fun getHumanoidArmorModel(
 		entityLiving: LivingEntity,
@@ -63,30 +28,16 @@ private object TritaniumArmorRenderProperties : IClientItemExtensions {
 	}
 }
 
-class TritaniumArmorItem(slot: Type) : DyeableArmorItem(TritaniumArmorMaterial, slot, Properties().stacksTo(1).rarity(Rarity.RARE)) {
+class TritaniumArmorItem(slot: Type) : ArmorItem(MArmorMaterials.TRITANIUM, slot, Properties().stacksTo(1).rarity(Rarity.RARE)) {
 	override fun initializeClient(consumer: Consumer<IClientItemExtensions>) {
 		super.initializeClient(consumer)
 		consumer.accept(TritaniumArmorRenderProperties)
 	}
 
-	override fun getArmorTexture(stack: ItemStack, entity: Entity, slot: EquipmentSlot, type: String?): String =
-		if (type.equals("overlay")) TEXTURE_LOCATION_OVERLAY else TEXTURE_LOCATION_BASE
-
-	override fun getColor(stack: ItemStack): Int {
-		val tag = stack.getTagElement("display")?: return TRITANIUM_COLOR
-		if (tag.contains("color", 99)) {
-			return tag.getInt("color")
-		}
-
-		return TRITANIUM_COLOR
-	}
-
 	companion object {
 		val TRITANIUM_COLOR = RGBAColor(157, 187, 204).toBGRA()
-		const val TEXTURE_LOCATION_BASE = "${OverdriveThatMatters.MOD_ID}:textures/models/armor/tritanium_armor_base.png"
-		const val TEXTURE_LOCATION_OVERLAY = "${OverdriveThatMatters.MOD_ID}:textures/models/armor/tritanium_armor_overlay.png"
 
-		fun onHurt(event: LivingAttackEvent) {
+		fun onHurt(event: LivingIncomingDamageEvent) {
 			if (event.source.typeHolder().`is`(DamageTypes.SWEET_BERRY_BUSH) || event.source.msgId == "sweetBerryBush") {
 				if (
 					event.entity.getItemBySlot(EquipmentSlot.FEET).let { !it.isEmpty && it.item == MItems.TRITANIUM_BOOTS } &&
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/AbstractExopackSlotUpgradeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/AbstractExopackSlotUpgradeItem.kt
index d99790504..949c648e5 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/AbstractExopackSlotUpgradeItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/AbstractExopackSlotUpgradeItem.kt
@@ -34,12 +34,17 @@ abstract class AbstractExopackSlotUpgradeItem(properties: Properties = defaultPr
 		return super.canBeHurtBy(p_41387_) && !p_41387_.isExplosion && !p_41387_.isFire
 	}
 
-	override fun getUseDuration(p_41454_: ItemStack): Int {
+	override fun getUseDuration(p_41454_: ItemStack, p_344979_: LivingEntity): Int {
 		return 30
 	}
 
-	override fun appendHoverText(p_41421_: ItemStack, p_41422_: Level?, tooltip: MutableList<Component>, p_41424_: TooltipFlag) {
-		super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_)
+	override fun appendHoverText(
+		p_41421_: ItemStack,
+		p_339594_: TooltipContext,
+		tooltip: MutableList<Component>,
+		p_41424_: TooltipFlag
+	) {
+		super.appendHoverText(p_41421_, p_339594_, tooltip, p_41424_)
 
 		val alreadyHasExosuit = runIfClient(true) {
 			minecraft.player?.matteryPlayer?.hasExopack == true
@@ -85,7 +90,7 @@ abstract class AbstractExopackSlotUpgradeItem(properties: Properties = defaultPr
 
 	override fun finishUsingItem(itemStack: ItemStack, level: Level, player: LivingEntity): ItemStack {
 		if (player !is Player) return super.finishUsingItem(itemStack, level, player)
-		val matteryPlayer = player.matteryPlayer ?: return super.finishUsingItem(itemStack, level, player)
+		val matteryPlayer = player.matteryPlayer
 		val uuid = uuid(itemStack)
 		val slotCount = slotCount(itemStack)
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackProbeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackProbeItem.kt
index fd4a6c8a4..4be2ec216 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackProbeItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackProbeItem.kt
@@ -19,12 +19,17 @@ import ru.dbotthepony.mc.otm.registry.MatteryDamageSource
 import ru.dbotthepony.mc.otm.runIfClient
 
 class ExopackProbeItem : Item(Properties().stacksTo(1).rarity(Rarity.EPIC)) {
-	override fun getUseDuration(p_41454_: ItemStack): Int {
+	override fun getUseDuration(p_41454_: ItemStack, p_344979_: LivingEntity): Int {
 		return 30
 	}
 
-	override fun appendHoverText(p_41421_: ItemStack, p_41422_: Level?, tooltip: MutableList<Component>, p_41424_: TooltipFlag) {
-		super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_)
+	override fun appendHoverText(
+		p_41421_: ItemStack,
+		p_339594_: TooltipContext,
+		tooltip: MutableList<Component>,
+		p_41424_: TooltipFlag
+	) {
+		super.appendHoverText(p_41421_, p_339594_, tooltip, p_41424_)
 
 		val alreadyHas = runIfClient(false) {
 			return@runIfClient minecraft.player?.matteryPlayer?.hasExopack ?: false
@@ -40,7 +45,7 @@ class ExopackProbeItem : Item(Properties().stacksTo(1).rarity(Rarity.EPIC)) {
 	}
 
 	override fun use(p_41432_: Level, player: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
-		if (player.matteryPlayer?.hasExopack == false) {
+		if (!player.matteryPlayer.hasExopack) {
 			player.startUsingItem(hand)
 			return InteractionResultHolder.consume(player.getItemInHand(hand))
 		}
@@ -50,7 +55,7 @@ class ExopackProbeItem : Item(Properties().stacksTo(1).rarity(Rarity.EPIC)) {
 
 	override fun finishUsingItem(itemStack: ItemStack, level: Level, player: LivingEntity): ItemStack {
 		if (player !is Player) return super.finishUsingItem(itemStack, level, player)
-		val mattery = player.matteryPlayer ?: return super.finishUsingItem(itemStack, level, player)
+		val mattery = player.matteryPlayer
 
 		if (mattery.hasExopack) {
 			return super.finishUsingItem(itemStack, level, player)
@@ -72,7 +77,7 @@ class ExopackProbeItem : Item(Properties().stacksTo(1).rarity(Rarity.EPIC)) {
 				onceServer((i - 1) * 100) {
 					if (!player.hasDisconnected()) {
 						if (i == 6) {
-							player.displayClientMessage(TranslatableComponent("otm.exopack.granted$i", player.displayName.copy().withStyle(ChatFormatting.DARK_PURPLE)).withStyle(ChatFormatting.DARK_AQUA), false)
+							player.displayClientMessage(TranslatableComponent("otm.exopack.granted$i", player.displayName!!.copy().withStyle(ChatFormatting.DARK_PURPLE)).withStyle(ChatFormatting.DARK_AQUA), false)
 						} else {
 							player.displayClientMessage(TranslatableComponent("otm.exopack.granted$i").withStyle(ChatFormatting.GRAY), false)
 						}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackUpgradeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackUpgradeItem.kt
index 668539320..2bc925cc7 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackUpgradeItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackUpgradeItem.kt
@@ -13,23 +13,28 @@ import net.minecraft.world.item.Rarity
 import net.minecraft.world.item.TooltipFlag
 import net.minecraft.world.item.UseAnim
 import net.minecraft.world.level.Level
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.runIfClient
 
 class ExopackUpgradeItem(
-	val type: MatteryPlayerCapability.UpgradeType,
+	val type: MatteryPlayer.UpgradeType,
 	val upgradeName: String,
 	val upgradedName: String,
 ) : Item(Properties().stacksTo(1).rarity(Rarity.RARE)) {
-	override fun getUseDuration(p_41454_: ItemStack): Int {
+	override fun getUseDuration(p_41454_: ItemStack, p_344979_: LivingEntity): Int {
 		return 30
 	}
 
-	override fun appendHoverText(p_41421_: ItemStack, p_41422_: Level?, tooltip: MutableList<Component>, p_41424_: TooltipFlag) {
-		super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_)
+	override fun appendHoverText(
+		p_41421_: ItemStack,
+		p_339594_: TooltipContext,
+		tooltip: MutableList<Component>,
+		p_41424_: TooltipFlag
+	) {
+		super.appendHoverText(p_41421_, p_339594_, tooltip, p_41424_)
 
 		val alreadyHasExosuit = runIfClient(true) {
 			minecraft.player?.matteryPlayer?.hasExopack == true
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ProceduralExopackSlotUpgradeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ProceduralExopackSlotUpgradeItem.kt
index 1a0188d3d..c4e4c2832 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ProceduralExopackSlotUpgradeItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ProceduralExopackSlotUpgradeItem.kt
@@ -1,6 +1,7 @@
 package ru.dbotthepony.mc.otm.item.exopack
 
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.ChatFormatting
 import net.minecraft.network.chat.Component
@@ -13,19 +14,18 @@ import net.minecraft.world.level.storage.loot.LootContext
 import net.minecraft.world.level.storage.loot.functions.LootItemFunction
 import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
-import ru.dbotthepony.mc.otm.core.nbt.set
-import ru.dbotthepony.mc.otm.core.tagNotNull
+import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
 import ru.dbotthepony.mc.otm.registry.MItemFunctionTypes
 import java.util.*
 
 class ProceduralExopackSlotUpgradeItem : AbstractExopackSlotUpgradeItem(defaultProperties()) {
 	class Randomizer(val slots: IntProvider, val luckBias: IntProvider = ConstantInt.ZERO) : LootItemFunction, LootItemFunction.Builder {
 		override fun apply(t: ItemStack, u: LootContext): ItemStack {
-			t.tagNotNull[SLOT_COUNT_KEY] = slots.sample(u.random) + (luckBias.sample(u.random) * u.luck / 1024f).coerceAtLeast(0f).toInt()
+			t[MDataComponentTypes.EXOPACK_SLOT_COUNT] = slots.sample(u.random) + (luckBias.sample(u.random) * u.luck / 1024f).coerceAtLeast(0f).toInt()
 			return t
 		}
 
-		override fun getType(): LootItemFunctionType {
+		override fun getType(): LootItemFunctionType<Randomizer> {
 			return MItemFunctionTypes.PROCEDURAL_EXOPACK_UPGRADE
 		}
 
@@ -34,7 +34,7 @@ class ProceduralExopackSlotUpgradeItem : AbstractExopackSlotUpgradeItem(defaultP
 		}
 
 		companion object {
-			val CODEC: Codec<Randomizer> = RecordCodecBuilder.create {
+			val CODEC: MapCodec<Randomizer> = RecordCodecBuilder.mapCodec {
 				it.group(
 					IntProvider.CODEC.fieldOf("slots").forGetter(Randomizer::slots),
 					IntProvider.CODEC.optionalFieldOf("luck_bias", ConstantInt.ZERO).forGetter(Randomizer::luckBias),
@@ -48,7 +48,7 @@ class ProceduralExopackSlotUpgradeItem : AbstractExopackSlotUpgradeItem(defaultP
 	}
 
 	override fun slotCount(itemStack: ItemStack): Int {
-		return itemStack.tag?.getInt(SLOT_COUNT_KEY) ?: 0
+		return itemStack[MDataComponentTypes.EXOPACK_SLOT_COUNT] ?: 0
 	}
 
 	override fun uuid(itemStack: ItemStack): UUID? {
@@ -57,13 +57,13 @@ class ProceduralExopackSlotUpgradeItem : AbstractExopackSlotUpgradeItem(defaultP
 
 	override fun appendHoverText(
 		p_41421_: ItemStack,
-		p_41422_: Level?,
+		p_339594_: TooltipContext,
 		tooltip: MutableList<Component>,
 		p_41424_: TooltipFlag
 	) {
-		super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_)
+		super.appendHoverText(p_41421_, p_339594_, tooltip, p_41424_)
 
-		if (p_41421_.tag?.get(SLOT_COUNT_KEY) == null) {
+		if (!p_41421_.has(MDataComponentTypes.EXOPACK_SLOT_COUNT)) {
 			tooltip.add(TranslatableComponent("$descriptionId.description").withStyle(ChatFormatting.GRAY))
 		}
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/CreativePatternItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/CreativePatternItem.kt
index b06eb053f..18d3b215f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/CreativePatternItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/CreativePatternItem.kt
@@ -1,35 +1,28 @@
 package ru.dbotthepony.mc.otm.item.matter
 
 import net.minecraft.ChatFormatting
-import net.minecraft.core.Direction
-import net.minecraft.nbt.CompoundTag
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.network.chat.Component
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Rarity
 import net.minecraft.world.item.TooltipFlag
-import net.minecraft.world.level.Level
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.common.util.LazyOptional
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.matter.PatternState
 import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage
 import ru.dbotthepony.mc.otm.capability.matter.PatternInsertFailure
 import ru.dbotthepony.mc.otm.capability.matter.PatternInsertStatus
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
-import ru.dbotthepony.mc.otm.core.getID
+import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
 import ru.dbotthepony.mc.otm.matter.MatterManager
 import java.util.*
 import java.util.stream.Stream
 
-class CreativePatternItem : Item(Properties().rarity(Rarity.EPIC).stacksTo(1)) {
-	private object Patterns : IPatternStorage, ICapabilityProvider {
-		private val resolver = LazyOptional.of<IPatternStorage> { this }
-
+class CreativePatternItem : Item(Properties().rarity(Rarity.EPIC).stacksTo(1)), CapabilitiesRegisterListener {
+	private object Patterns : IPatternStorage {
 		override val patterns: Stream<PatternState>
-			get() = MatterManager.map.keys.stream().map { PatternState(UUID(34783464838L, 4463458382L + ForgeRegistries.ITEMS.getID(it)), it, 1.0) }
+			get() = MatterManager.map.keys.stream().map { PatternState(UUID(34783464838L, 4463458382L + BuiltInRegistries.ITEM.getId(it)), it, 1.0) }
 
 		override val patternCapacity: Int
 			get() {
@@ -48,23 +41,19 @@ class CreativePatternItem : Item(Properties().rarity(Rarity.EPIC).stacksTo(1)) {
 		): PatternInsertStatus {
 			return PatternInsertFailure
 		}
-
-		override fun <T> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
-			return if (cap == MatteryCapability.PATTERN) resolver.cast() else LazyOptional.empty()
-		}
 	}
 
-	override fun initCapabilities(stack: ItemStack?, nbt: CompoundTag?): ICapabilityProvider {
-		return Patterns
+	override fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		event.registerItem(MatteryCapability.PATTERN_ITEM, { o, c -> Patterns }, this)
 	}
 
 	override fun appendHoverText(
 		p_41421_: ItemStack,
-		p_41422_: Level?,
+		p_339594_: TooltipContext,
 		p_41423_: MutableList<Component>,
 		p_41424_: TooltipFlag
 	) {
-		super.appendHoverText(p_41421_, p_41422_, p_41423_, p_41424_)
+		super.appendHoverText(p_41421_, p_339594_, p_41423_, p_41424_)
 		p_41423_.add(TranslatableComponent("$descriptionId.description1").withStyle(ChatFormatting.DARK_GRAY))
 		p_41423_.add(TranslatableComponent("$descriptionId.description2").withStyle(ChatFormatting.DARK_GRAY))
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterCapacitorItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterCapacitorItem.kt
index 6d4e9a693..956479be7 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterCapacitorItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterCapacitorItem.kt
@@ -1,40 +1,31 @@
 package ru.dbotthepony.mc.otm.item.matter
 
 import net.minecraft.ChatFormatting
-import net.minecraft.core.Direction
-import net.minecraft.nbt.CompoundTag
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Rarity
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.common.util.LazyOptional
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
 import ru.dbotthepony.mc.otm.capability.FlowDirection
-import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
-import ru.dbotthepony.mc.otm.capability.matter.*
+import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage
+import ru.dbotthepony.mc.otm.capability.matter.getBarColor
+import ru.dbotthepony.mc.otm.capability.matter.getBarWidth
 import ru.dbotthepony.mc.otm.client.ShiftPressedCond
+import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.util.formatMatter
-import ru.dbotthepony.mc.otm.core.ifPresentK
-import ru.dbotthepony.mc.otm.core.nbt.map
-import ru.dbotthepony.mc.otm.core.tagNotNull
+import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
 import ru.dbotthepony.mc.otm.item.MatteryItem
+import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
 
-class MatterCapacitorItem : MatteryItem {
-	private inner class Matter(private val stack: ItemStack) : ICapabilityProvider, IMatterStorage {
-		private val resolver = LazyOptional.of<IMatterStorage> { this }
-
-		override fun <T> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
-			return if (cap === MatteryCapability.MATTER) resolver.cast() else LazyOptional.empty()
-		}
-
+class MatterCapacitorItem : MatteryItem, CapabilitiesRegisterListener {
+	private inner class Matter(private val stack: ItemStack) : IMatterStorage {
 		override var storedMatter: Decimal
 			get() {
 				if (isCreative) return Decimal.POSITIVE_INFINITY
-				return stack.tag?.map("matter", Decimal.Companion::deserializeNBT) ?: Decimal.ZERO
+				return stack.getOrDefault(MDataComponentTypes.MATTER_LEVEL, Decimal.ZERO)
 			}
 			set(value) {
-				stack.tagNotNull.put("matter", value.serializeNBT())
+				stack[MDataComponentTypes.MATTER_LEVEL] = value
 			}
 
 		override val maxStoredMatter: Decimal get() {
@@ -96,26 +87,26 @@ class MatterCapacitorItem : MatteryItem {
 		if (isCreative)
 			return false
 
-		return p_150899_.matter != null
+		return p_150899_.getCapability(MatteryCapability.MATTER_ITEM) != null
 	}
 
 	override fun getBarWidth(p_150900_: ItemStack): Int {
 		if (isCreative)
 			return 13
 
-		return p_150900_.matter?.getBarWidth() ?: super.getBarWidth(p_150900_)
+		return p_150900_.getCapability(MatteryCapability.MATTER_ITEM)?.getBarWidth() ?: super.getBarWidth(p_150900_)
 	}
 
 	override fun getBarColor(p_150901_: ItemStack): Int {
 		if (isCreative)
 			return 0
 
-		return p_150901_.matter?.getBarColor() ?: super.getBarColor(p_150901_)
+		return p_150901_.getCapability(MatteryCapability.MATTER_ITEM)?.getBarColor() ?: super.getBarColor(p_150901_)
 	}
 
 	init {
-		tooltips.addNormal { itemStack, acceptor ->
-			itemStack.getCapability(MatteryCapability.MATTER).ifPresentK {
+		tooltips.addNormal { itemStack, context, acceptor ->
+			itemStack.getCapability(MatteryCapability.MATTER_ITEM)?.let {
 				acceptor(
 					TranslatableComponent(
 						"otm.item.matter.storage",
@@ -127,8 +118,8 @@ class MatterCapacitorItem : MatteryItem {
 		}
 	}
 
-	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
-		return Matter(stack)
+	override fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		event.registerItem(MatteryCapability.MATTER_ITEM, { o, _ -> Matter(o) }, this)
 	}
 
 	companion object {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt
index 8447d888a..5965e0c15 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt
@@ -18,20 +18,21 @@ import ru.dbotthepony.mc.otm.matter.IMatterItem
 import ru.dbotthepony.mc.otm.core.nbt.set
 import ru.dbotthepony.mc.otm.matter.IMatterValue
 import ru.dbotthepony.mc.otm.matter.MatterValue
+import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
 import ru.dbotthepony.mc.otm.registry.MItems
 
 class MatterDustItem : Item(Properties().stacksTo(64)), IMatterItem {
 	private fun matter(stack: ItemStack): Decimal {
-		return stack.tag?.get("matter")?.let { return@let Decimal.deserializeNBT(it) } ?: return Decimal.ZERO
+		return stack.getOrDefault(MDataComponentTypes.MATTER_LEVEL, Decimal.ZERO)
 	}
 
 	private fun matter(stack: ItemStack, matter: Decimal) {
-		stack.orCreateTag["matter"] = matter.serializeNBT()
+		stack[MDataComponentTypes.MATTER_LEVEL] = matter
 	}
 
 	override fun getMatterValue(stack: ItemStack): IMatterValue? {
-		val value = stack.tag?.get("matter") ?: return null
-		return MatterValue(Decimal.deserializeNBT(value), 1.0)
+		val value = stack[MDataComponentTypes.MATTER_LEVEL] ?: return null
+		return MatterValue(value, 1.0)
 	}
 
 	fun moveIntoContainer(matterValue: Decimal, container: MatteryContainer, mainSlot: Int, stackingSlot: Int): Decimal {
@@ -71,7 +72,7 @@ class MatterDustItem : Item(Properties().stacksTo(64)), IMatterItem {
 
 				// можем ли мы стакнуть материю из второго слота в первый?
 				if (!stack2.isEmpty && isFull(stack2)) {
-					if (ItemStack.isSameItemSameTags(stack, stack2) && container.getMaxStackSize(mainSlot, stack) >= stack.count + 1) {
+					if (ItemStack.isSameItemSameComponents(stack, stack2) && container.getMaxStackSize(mainSlot, stack) >= stack.count + 1) {
 						stack.count++
 						stack2.count--
 
@@ -91,11 +92,11 @@ class MatterDustItem : Item(Properties().stacksTo(64)), IMatterItem {
 
 	override fun appendHoverText(
 		p_41421_: ItemStack,
-		p_41422_: Level?,
+		p_339594_: TooltipContext,
 		p_41423_: MutableList<Component>,
 		p_41424_: TooltipFlag
 	) {
-		super.appendHoverText(p_41421_, p_41422_, p_41423_, p_41424_)
+		super.appendHoverText(p_41421_, p_339594_, p_41423_, p_41424_)
 		val matter = this.getMatterValue(p_41421_)
 
 		if (matter == null) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/PatternStorageItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/PatternStorageItem.kt
index 6358a8258..514561761 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/PatternStorageItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/PatternStorageItem.kt
@@ -1,27 +1,27 @@
 package ru.dbotthepony.mc.otm.item.matter
 
-import net.minecraft.world.item.ItemStack
-import net.minecraft.nbt.CompoundTag
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraft.world.item.TooltipFlag
-import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import net.minecraft.ChatFormatting
-import net.minecraftforge.common.util.LazyOptional
-import net.minecraft.nbt.ListTag
-import net.minecraft.core.Direction
 import net.minecraft.network.chat.Component
 import net.minecraft.world.item.Item
+import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Rarity
-import net.minecraft.world.level.Level
-import net.minecraftforge.common.capabilities.Capability
+import net.minecraft.world.item.TooltipFlag
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
+import ru.dbotthepony.mc.otm.capability.MatteryCapability
+import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage
+import ru.dbotthepony.mc.otm.capability.matter.PatternInsertFailure
+import ru.dbotthepony.mc.otm.capability.matter.PatternInsertInserted
+import ru.dbotthepony.mc.otm.capability.matter.PatternInsertStatus
+import ru.dbotthepony.mc.otm.capability.matter.PatternInsertUpdated
+import ru.dbotthepony.mc.otm.capability.matter.PatternState
+import ru.dbotthepony.mc.otm.capability.matter.getBarColor
+import ru.dbotthepony.mc.otm.capability.matter.getBarWidth
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
-import ru.dbotthepony.mc.otm.capability.matter.*
-import ru.dbotthepony.mc.otm.core.filterNotNull
-import ru.dbotthepony.mc.otm.core.nbt.map
-import java.util.*
+import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
+import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
 import java.util.stream.Stream
 
-class PatternStorageItem : Item {
+class PatternStorageItem : Item, CapabilitiesRegisterListener {
 	private val _capacity: () -> Int
 	val capacity get() = _capacity.invoke()
 	var isCreative: Boolean
@@ -41,17 +41,17 @@ class PatternStorageItem : Item {
 		_capacity = { Int.MAX_VALUE }
 	}
 
-	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider? {
-		return ItemPatternStorageCapability(stack)
+	override fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		event.registerItem(MatteryCapability.PATTERN_ITEM, { o, _ -> ItemPatternStorageCapability(o) }, this)
 	}
 
 	override fun appendHoverText(
 		p_41421_: ItemStack,
-		p_41422_: Level?,
+		p_339594_: TooltipContext,
 		list: MutableList<Component>,
 		p_41424_: TooltipFlag
 	) {
-		p_41421_.getCapability(MatteryCapability.PATTERN).ifPresent {
+		p_41421_.getCapability(MatteryCapability.PATTERN_ITEM)?.let {
 			if (isCreative)
 				list.add(TranslatableComponent("otm.item.pattern.infinite.stored", it.storedPatterns).withStyle(ChatFormatting.GRAY))
 			else
@@ -68,51 +68,41 @@ class PatternStorageItem : Item {
 			}
 		}
 
-		super.appendHoverText(p_41421_, p_41422_, list, p_41424_)
+		super.appendHoverText(p_41421_, p_339594_, list, p_41424_)
 	}
 
 	override fun isBarVisible(p_150899_: ItemStack): Boolean {
 		if (isCreative)
 			return false
 
-		return p_150899_.patterns != null
+		return p_150899_.getCapability(MatteryCapability.PATTERN_ITEM) != null
 	}
 
 	override fun getBarWidth(p_150900_: ItemStack): Int {
 		if (isCreative)
 			return 13
 
-		return p_150900_.patterns?.getBarWidth() ?: super.getBarWidth(p_150900_)
+		return p_150900_.getCapability(MatteryCapability.PATTERN_ITEM)?.getBarWidth() ?: super.getBarWidth(p_150900_)
 	}
 
 	override fun getBarColor(p_150901_: ItemStack): Int {
 		if (isCreative)
 			return 0
 
-		return p_150901_.patterns?.getBarColor() ?: super.getBarColor(p_150901_)
+		return p_150901_.getCapability(MatteryCapability.PATTERN_ITEM)?.getBarColor() ?: super.getBarColor(p_150901_)
 	}
 
-	private inner class ItemPatternStorageCapability(val stack: ItemStack) : IPatternStorage, ICapabilityProvider {
-		private val resolver = LazyOptional.of<IPatternStorage> { this }
-
+	inner class ItemPatternStorageCapability(val stack: ItemStack) : IPatternStorage {
 		override val patternCapacity: Int get() {
 			return this@PatternStorageItem.capacity
 		}
 
 		override val storedPatterns: Int get() {
-			return stack.tag?.map("otm_patterns") { it: ListTag ->
-				it.size
-			} ?: 0
-		}
-
-		override fun <T> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
-			return if (cap == MatteryCapability.PATTERN) resolver.cast() else LazyOptional.empty()
+			return stack[MDataComponentTypes.PATTERNS]?.size ?: 0
 		}
 
 		override val patterns: Stream<PatternState> get() {
-			return stack.tag?.map("otm_patterns") { it: ListTag ->
-				 it.stream().map { PatternState.deserializeNBT(it) }.filterNotNull()
-			} ?: Stream.empty()
+			return stack[MDataComponentTypes.PATTERNS]?.stream() ?: Stream.empty()
 		}
 
 		override fun insertPattern(
@@ -120,62 +110,28 @@ class PatternStorageItem : Item {
 			onlyUpdate: Boolean,
 			simulate: Boolean
 		): PatternInsertStatus {
-			val tag = stack.orCreateTag
-			var list = tag["otm_patterns"]
+			val patterns = ArrayList(stack[MDataComponentTypes.PATTERNS] ?: listOf())
 
-			if (list !is ListTag) {
-				if (onlyUpdate)
-					return PatternInsertFailure
-
-				if (simulate) {
-					if (patternCapacity > 0)
-						return PatternInsertInserted(pattern)
-					else
-						return PatternInsertFailure
-				}
-
-				list = ListTag()
-				tag.put("otm_patterns", list)
-			}
-
-			var invalidCounter = 0
-
-			for (i in list.indices) {
-				val state = PatternState.deserializeNBT(list.getCompound(i))
-
-				if (state != null) {
-					if (state.id == pattern.id) {
-						if (!simulate) {
-							list[i] = pattern.serializeNBT()
-						}
-
-						return PatternInsertUpdated(pattern, state)
+			for ((i, ePattern) in patterns.withIndex()) {
+				if (ePattern.id == pattern.id) {
+					if (simulate) {
+						return PatternInsertUpdated(pattern, ePattern)
 					}
-				} else {
-					invalidCounter++
+
+					patterns[i] = pattern
+					stack[MDataComponentTypes.PATTERNS] = patterns
+					return PatternInsertUpdated(pattern, ePattern)
 				}
 			}
 
-			if (onlyUpdate || patternCapacity <= list.size - invalidCounter)
+			if (onlyUpdate || patternCapacity <= patterns.size)
 				return PatternInsertFailure
 
-			if (invalidCounter > 0) {
-				if (simulate)
-					return PatternInsertInserted(pattern)
-
-				for (i in list.indices) {
-					val state = PatternState.deserializeNBT(list.getCompound(i))
-
-					if (state == null) {
-						list[i] = pattern.serializeNBT()
-						return PatternInsertInserted(pattern)
-					}
-				}
+			if (!simulate) {
+				patterns.add(pattern)
+				stack[MDataComponentTypes.PATTERNS] = patterns
 			}
 
-			if (!simulate)
-				list.add(pattern.serializeNBT())
-
 			return PatternInsertInserted(pattern)
 		}
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/ExplosiveHammerItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/ExplosiveHammerItem.kt
index a5181c8b2..1e297179f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/ExplosiveHammerItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/ExplosiveHammerItem.kt
@@ -25,21 +25,22 @@ import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.TooltipFlag
 import net.minecraft.world.item.UseAnim
+import net.minecraft.world.item.component.ItemAttributeModifiers
 import net.minecraft.world.level.Explosion
 import net.minecraft.world.level.Level
 import net.minecraft.world.phys.AABB
 import net.minecraft.world.phys.Vec3
-import net.minecraftforge.common.ForgeHooks
-import net.minecraftforge.common.MinecraftForge
-import net.minecraftforge.common.Tags
-import net.minecraftforge.event.entity.player.PlayerInteractEvent
-import net.minecraftforge.event.level.BlockEvent
+import net.neoforged.neoforge.common.CommonHooks
+import net.neoforged.neoforge.common.Tags
+import net.neoforged.neoforge.event.EventHooks
+import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent
 import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
 import ru.dbotthepony.mc.otm.config.ToolsConfig
 import ru.dbotthepony.mc.otm.core.*
 import ru.dbotthepony.mc.otm.core.math.*
 import ru.dbotthepony.mc.otm.core.nbt.set
 import ru.dbotthepony.mc.otm.registry.MDamageTypes
+import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
 import ru.dbotthepony.mc.otm.registry.MatteryDamageSource
 import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger
 import java.util.function.Predicate
@@ -50,16 +51,16 @@ class ExplosiveHammerItem(durability: Int = 512) : Item(Properties().stacksTo(1)
 	}
 
 	fun isPrimed(itemStack: ItemStack): Boolean {
-		return itemStack.tag?.getBoolean("primed") ?: false
+		return itemStack[MDataComponentTypes.PRIMED] ?: false
 	}
 
 	fun prime(itemStack: ItemStack) {
-		itemStack.tagNotNull["primed"] = true
+		itemStack[MDataComponentTypes.PRIMED] = true
 	}
 
 	fun unprime(itemStack: ItemStack) {
 		if (isPrimed(itemStack))
-			itemStack.tagNotNull["primed"] = false
+			itemStack[MDataComponentTypes.PRIMED] = false
 	}
 
 	fun canPrime(player: Player): Boolean {
@@ -77,7 +78,7 @@ class ExplosiveHammerItem(durability: Int = 512) : Item(Properties().stacksTo(1)
 	override fun hasCraftingRemainingItem(itemStack: ItemStack): Boolean = true
 
 	override fun getCraftingRemainingItem(itemStack: ItemStack): ItemStack {
-		val player = ForgeHooks.getCraftingPlayer() ?: return itemStack.copy()
+		val player = CommonHooks.getCraftingPlayer() ?: return itemStack.copy()
 		if (player.level().isClientSide) return itemStack.copy()
 
 		if (!isPrimed(itemStack)) {
@@ -234,9 +235,7 @@ class ExplosiveHammerItem(durability: Int = 512) : Item(Properties().stacksTo(1)
 
 			val copy = itemStack.copy()
 
-			itemStack.hurtAndBreak(level.random.nextInt(1, 20), attacker) {
-				it.broadcastBreakEvent(hand)
-			}
+			itemStack.hurtAndBreak(level.random.nextInt(1, 20), attacker, EquipmentSlot.MAINHAND)
 
 			if (!itemStack.isEmpty && attacker.random.nextDouble() <= ToolsConfig.ExplosiveHammer.FLY_OFF_CHANCE) {
 				attacker.setItemInHand(hand, ItemStack.EMPTY)
@@ -274,8 +273,13 @@ class ExplosiveHammerItem(durability: Int = 512) : Item(Properties().stacksTo(1)
 		return true
 	}
 
-	override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList<Component>, pIsAdvanced: TooltipFlag) {
-		super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced)
+	override fun appendHoverText(
+		pStack: ItemStack,
+		p_339594_: TooltipContext,
+		pTooltipComponents: MutableList<Component>,
+		pIsAdvanced: TooltipFlag
+	) {
+		super.appendHoverText(pStack, p_339594_, pTooltipComponents, pIsAdvanced)
 
 		pTooltipComponents.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.DARK_GRAY))
 
@@ -291,8 +295,8 @@ class ExplosiveHammerItem(durability: Int = 512) : Item(Properties().stacksTo(1)
 		return if (isPrimed(stack)) super.getUseAnimation(stack) else UseAnim.CROSSBOW
 	}
 
-	override fun getUseDuration(stack: ItemStack): Int {
-		return if (isPrimed(stack)) super.getUseDuration(stack) else 20
+	override fun getUseDuration(stack: ItemStack, p_344979_: LivingEntity): Int {
+		return if (isPrimed(stack)) super.getUseDuration(stack, p_344979_) else 20
 	}
 
 	override fun use(level: Level, player: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
@@ -321,7 +325,7 @@ class ExplosiveHammerItem(durability: Int = 512) : Item(Properties().stacksTo(1)
 	}
 
 	companion object {
-		val GUNPOWDER_PREDICATE = Predicate { stack: ItemStack -> stack.`is`(Tags.Items.GUNPOWDER) }
+		val GUNPOWDER_PREDICATE = Predicate { stack: ItemStack -> stack.`is`(Tags.Items.GUNPOWDERS) }
 		val IRON_NUGGET_PREDICATE = Predicate { stack: ItemStack -> stack.`is`(Tags.Items.NUGGETS_IRON) }
 
 		fun onLeftClickBlock(event: PlayerInteractEvent.LeftClickBlock) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/MatteryAxeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/MatteryAxeItem.kt
index fcb65eaf3..dc01429aa 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/MatteryAxeItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/MatteryAxeItem.kt
@@ -7,7 +7,7 @@ import net.minecraft.world.item.Tier
 import net.minecraft.world.level.block.state.BlockState
 import ru.dbotthepony.mc.otm.config.ToolsConfig
 
-class MatteryAxeItem(pTier: Tier, pAttackDamageModifier: Float, pAttackSpeedModifier: Float,  pProperties: Properties) : AxeItem(pTier, pAttackDamageModifier, pAttackSpeedModifier, pProperties) {
+class MatteryAxeItem(pTier: Tier, pProperties: Properties) : AxeItem(pTier, pProperties) {
 	override fun getDestroySpeed(pStack: ItemStack, pState: BlockState): Float {
 		if (pState.`is`(BlockTags.LEAVES) && ToolsConfig.AXES_BREAK_LEAVES_INSTANTLY)
 			return 64f
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/AbstractWeaponItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/AbstractWeaponItem.kt
deleted file mode 100644
index ed973cc3e..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/AbstractWeaponItem.kt
+++ /dev/null
@@ -1,598 +0,0 @@
-package ru.dbotthepony.mc.otm.item.weapon
-
-import com.mojang.blaze3d.systems.RenderSystem
-import net.minecraft.client.Minecraft
-import net.minecraft.client.model.HumanoidModel
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.network.FriendlyByteBuf
-import net.minecraft.server.level.ServerPlayer
-import net.minecraft.world.entity.Entity
-import net.minecraft.world.entity.player.Player
-import net.minecraft.world.item.Item
-import net.minecraft.world.item.ItemDisplayContext
-import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.Rarity
-import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.client.event.InputEvent
-import net.minecraftforge.client.event.RenderHandEvent
-import net.minecraftforge.client.event.RenderPlayerEvent
-import net.minecraftforge.client.event.ViewportEvent
-import net.minecraftforge.event.TickEvent
-import net.minecraftforge.fml.LogicalSide
-import ru.dbotthepony.mc.otm.capability.matteryEnergy
-import ru.dbotthepony.mc.otm.client.font
-import ru.dbotthepony.mc.otm.client.render.draw
-import ru.dbotthepony.mc.otm.client.render.renderRect
-import ru.dbotthepony.mc.otm.core.math.Angle
-import ru.dbotthepony.kommons.math.RGBAColor
-import ru.dbotthepony.mc.otm.core.math.Vector
-import ru.dbotthepony.mc.otm.core.math.bezierCurve
-import ru.dbotthepony.mc.otm.core.math.component1
-import ru.dbotthepony.mc.otm.core.math.component2
-import ru.dbotthepony.mc.otm.core.math.component3
-import ru.dbotthepony.mc.otm.core.math.linearInterpolation
-import ru.dbotthepony.mc.otm.core.math.rotateX
-import ru.dbotthepony.mc.otm.core.math.rotateY
-import ru.dbotthepony.mc.otm.core.math.rotateZ
-import ru.dbotthepony.mc.otm.core.nbt.EMPTY_UUID
-import ru.dbotthepony.mc.otm.core.nbt.booleans
-import ru.dbotthepony.mc.otm.core.nbt.ints
-import ru.dbotthepony.mc.otm.core.nbt.uuids
-import ru.dbotthepony.mc.otm.core.tagNotNull
-import ru.dbotthepony.mc.otm.core.util.formatPower
-import ru.dbotthepony.mc.otm.item.MatteryItem
-import ru.dbotthepony.mc.otm.network.MNetworkContext
-import ru.dbotthepony.mc.otm.network.MatteryPacket
-import ru.dbotthepony.mc.otm.network.WeaponNetworkChannel
-import java.util.*
-import kotlin.collections.set
-import kotlin.math.PI
-import kotlin.math.abs
-import kotlin.reflect.KClass
-import kotlin.reflect.full.isSubclassOf
-import kotlin.reflect.full.primaryConstructor
-
-private val ItemStack.weaponDataTable get() = tag?.let(::WeaponDataTable)
-
-enum class WeaponScopePacket(val scope: Boolean) : MatteryPacket {
-	SCOPE_IN(true), SCOPE_OUT(false);
-
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeBoolean(scope)
-	}
-
-	override fun play(context: MNetworkContext) {
-		// TODO: Manual synchronization
-		val stack = context.sender!!.mainHandItem
-		val item = stack.item as? AbstractWeaponItem<*> ?: return
-
-		item.dataTable(stack).wantsToScope = scope
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf) = if (buff.readBoolean()) SCOPE_IN else SCOPE_OUT
-	}
-}
-
-enum class WeaponFireInputPacket(val primary: Boolean) : MatteryPacket {
-	PRIMARY(true), SECONDARY(false);
-
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeBoolean(primary)
-	}
-
-	override fun play(context: MNetworkContext) {
-		// TODO: Manual synchronization
-		val stack = context.sender!!.mainHandItem
-		val item = stack.item as? AbstractWeaponItem<*> ?: return
-
-		// Listen server: client and server thread compete for lock
-		// so it is very likely item is being in predicted context
-		val predictedData = item.dataTable
-		item.dataTable = null
-
-		if (primary)
-			item.tryPrimaryFire(stack, context.sender)
-		else
-			item.trySecondaryFire(stack, context.sender)
-
-		(item as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf) = if (buff.readBoolean()) PRIMARY else SECONDARY
-	}
-}
-
-open class WeaponDataTable(val tag: CompoundTag) {
-	var shotCooldown by tag.ints
-	var wantsToScope by tag.booleans
-	var scoped by tag.booleans
-	var scopeTicks by tag.ints
-	var nextPrimaryFire by tag.ints
-	var nextSecondaryFire by tag.ints
-	var uuid by tag.uuids
-
-	var fireAnim = 0.0
-	var fireAnimDeviation = Angle.ZERO
-
-	fun doFireAnim(amount: Double = 0.8, deviation: Angle? = null) {
-		fireAnim = (fireAnim + amount).coerceAtLeast(0.0).coerceAtMost(1.0)
-
-		if (deviation != null) {
-			fireAnimDeviation = Angle(
-				deviation.pitch * RANDOM.nextGaussian(),
-				deviation.yaw * RANDOM.nextGaussian(),
-				deviation.roll * RANDOM.nextGaussian(),
-			)
-		} else {
-			fireAnimDeviation = Angle.ZERO
-		}
-	}
-
-	var deployTime = 0.0
-
-	companion object {
-		private val RANDOM = Random()
-	}
-}
-
-abstract class AbstractWeaponItem<D : WeaponDataTable>(val tables: KClass<D>, properties: Properties = Properties().stacksTo(1).rarity(Rarity.RARE)) : MatteryItem(properties) {
-	fun makeDataTable(tag: CompoundTag) = tables.primaryConstructor!!.call(tag)
-
-	/**
-	 * Predicted data table, for client use only
-	 */
-	var dataTable: D? = null
-		set(value) {
-			field = if (value == null || value::class.isSubclassOf(tables)) {
-				value
-			} else {
-				throw ClassCastException("Reified generics: ${value::class.qualifiedName} cannot be cast to ${tables.qualifiedName}")
-			}
-		}
-
-	fun compatibleDataTable(value: WeaponDataTable?) = value == null || value::class.isSubclassOf(tables)
-
-	@Suppress("unchecked_cast")
-	fun dataTable(itemStack: ItemStack): D {
-		if (dataTable != null) {
-			return dataTable!!
-		}
-
-		return makeDataTable(itemStack.tagNotNull)
-	}
-
-	open fun holster(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
-		dt.wantsToScope = false
-	}
-
-	open fun deploy(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
-		dt.wantsToScope = false
-
-		if (dt.uuid == EMPTY_UUID) {
-			dt.uuid = UUID.randomUUID()
-		}
-	}
-
-	abstract val roundsPerMinute: Int
-	val roundsPerSecond get() = roundsPerMinute / 60
-	val fireCooldown get() = (1200 / roundsPerMinute).coerceAtLeast(1)
-
-	open val positionHolstered get() = Vector(1.0, -2.5, -1.0)
-	open val positionIdle get() = Vector(1.0, -0.5, -1.0)
-	open val positionIronSights get() = Vector(0.0, -0.23, -1.0)
-	open val rotIdle get() = Angle(PI / 36, PI / 18)
-	open val rotIronSights get() = Angle.ZERO
-	open val rotFireAnim get() = Angle.deg(20.0, 0.0, 0.0)
-	open val rotFireAnimDeviation get() = Angle.deg(2.0, 1.0, 1.0)
-
-	open val fireAnimRecovery = 1.0
-	open val deployAnimSpeed = 1.0
-
-	open val primaryAutomatic = true
-	open val secondaryAutomatic = true
-
-	open val ironSightsFOV: Double get() = 2.0
-
-	fun ironSightsProgress(itemStack: ItemStack, partialTicks: Double = 0.0, dt: D = dataTable(itemStack)): Double {
-		if (!dt.wantsToScope)
-			return (((dt.scopeTicks.toDouble() - partialTicks).coerceAtLeast(0.0) / scopingTime.toDouble()) * 1.4).coerceAtMost(1.0)
-
-		return (((dt.scopeTicks.toDouble() + partialTicks) / scopingTime.toDouble()) * 1.4).coerceAtMost(1.0)
-	}
-
-	abstract val scopingTime: Int
-
-	open fun think(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
-		if (dt.wantsToScope && dt.scopeTicks < scopingTime) {
-			dt.scopeTicks++
-		} else if (!dt.wantsToScope && dt.scopeTicks > 0) {
-			dt.scopeTicks--
-		}
-	}
-
-	open fun thinkHolstered(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
-		if (dt.shotCooldown > 0) {
-			dt.shotCooldown--
-		}
-	}
-
-	open fun think2(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)) {
-		if (dt.nextPrimaryFire > 0) {
-			dt.nextPrimaryFire--
-		}
-
-		if (dt.nextSecondaryFire > 0) {
-			dt.nextSecondaryFire--
-		}
-	}
-
-	open fun primaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
-		return false
-	}
-
-	open fun secondaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
-		return false
-	}
-
-	open fun canPrimaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
-		if (player is ServerPlayer) {
-			// allow a little deviation, because nothing is perfect
-			return dt.nextPrimaryFire <= 2
-		}
-
-		return dt.nextPrimaryFire <= 0
-	}
-
-	fun tryPrimaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
-		if (canPrimaryFire(itemStack, player, dt) && primaryFire(itemStack, player, dt)) {
-			dt.nextPrimaryFire = fireCooldown
-			return true
-		}
-
-		return false
-	}
-
-	fun trySecondaryFire(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
-		if (dt.nextSecondaryFire <= 0 && secondaryFire(itemStack, player, dt)) {
-			dt.nextSecondaryFire = fireCooldown
-			return true
-		}
-
-		return false
-	}
-
-	override fun getDestroySpeed(p_41425_: ItemStack, p_41426_: BlockState): Float {
-		return 0f
-	}
-
-	override fun onLeftClickEntity(stack: ItemStack?, player: Player?, entity: Entity?): Boolean {
-		return true
-	}
-
-	companion object {
-		// shared
-		private val items = WeakHashMap<Player, ItemStack>()
-
-		// client only
-		private var inRender = false
-		var predictedData: WeaponDataTable? = null
-		private var lastFov = 1.0
-		private var lastFovTime = 0L
-
-		fun fovHook(event: ViewportEvent.ComputeFov) {
-			val player = event.camera.entity as? Player ?: return
-
-			val it = (player.mainHandItem.item as? AbstractWeaponItem<*>)
-
-			if (it != null) {
-				@Suppress("unchecked_cast")
-				if (it.compatibleDataTable(predictedData))
-					(it as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
-
-				val interp = ru.dbotthepony.mc.otm.core.math.linearInterpolation(
-					it.ironSightsProgress(
-						player.mainHandItem,
-						event.partialTick
-					) * 1.7 - 0.6, 1.0, it.ironSightsFOV
-				)
-
-				val time = System.nanoTime()
-				val diff = abs(lastFovTime - time)
-				lastFov = ru.dbotthepony.mc.otm.core.math.linearInterpolation(
-					diff.coerceIn(0L, 10_000_000L).toDouble() / 10_000_000.0, lastFov, interp
-				).coerceAtLeast(0.001)
-				lastFovTime = time
-
-				event.fov /= lastFov
-
-				it.dataTable = null
-			} else {
-				lastFov = 1.0
-				lastFovTime = 0L
-			}
-		}
-
-		fun playerRenderHook(event: RenderPlayerEvent.Pre) {
-			if (event.entity.mainHandItem.item is AbstractWeaponItem<*> && event.entity.offhandItem.isEmpty) {
-				event.renderer.model.rightArmPose = HumanoidModel.ArmPose.BOW_AND_ARROW
-				event.renderer.model.leftArmPose = HumanoidModel.ArmPose.EMPTY
-			}
-		}
-
-		fun clickHook(event: InputEvent.InteractionKeyMappingTriggered) {
-			val player = Minecraft.getInstance().player!!
-
-			if (player.mainHandItem.item is AbstractWeaponItem<*> && player.offhandItem.isEmpty && event.isAttack) {
-				event.isCanceled = true
-				event.setSwingHand(false)
-			}
-		}
-
-		private var lastClientRender = 0L
-
-		private val fireAnimInterp = doubleArrayOf(
-			0.0,
-			0.01,
-			0.03,
-			0.04,
-			0.05,
-			0.06,
-			0.07,
-			0.1,
-			0.15,
-			0.3,
-			0.45,
-			0.6,
-			0.8,
-			0.85,
-			0.9,
-			1.0,
-			0.9,
-			0.6,
-		)
-
-		@Suppress("unchecked_cast")
-		fun renderViewModel(event: RenderHandEvent) {
-			if (inRender) {
-				return
-			}
-
-			val time = System.nanoTime()
-			val diff = abs(lastClientRender - time)
-			lastClientRender = time
-
-			val stack = event.itemStack
-			val item = stack.item as? AbstractWeaponItem<*> ?: return
-
-			inRender = true
-			event.isCanceled = true
-
-			(item as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
-
-			predictedData?.deployTime = (predictedData!!.deployTime + diff.toDouble() / (300_000_000.0 * item.deployAnimSpeed)).coerceAtMost(1.0).coerceAtLeast(0.0)
-			predictedData?.fireAnim = (predictedData!!.fireAnim - diff.toDouble() / (400_000_000.0 * item.fireAnimRecovery)).coerceAtMost(1.0).coerceAtLeast(0.0)
-
-			val player = Minecraft.getInstance().player!!
-			val pose = event.poseStack
-			val itemInHandRenderer = Minecraft.getInstance().entityRenderDispatcher.itemInHandRenderer
-
-			pose.pushPose()
-
-			val progress = item.ironSightsProgress(stack, event.partialTick.toDouble())
-
-			val (x, y, z) = linearInterpolation(
-				// 1.0 - event.equipProgress.toDouble(),
-				item.dataTable?.deployTime ?: 0.0,
-				item.positionHolstered,
-				bezierCurve(
-					progress,
-					item.positionIdle,
-					item.positionIdle,
-					item.positionIdle,
-					Vector(0.0, -1.0, -1.0),
-					item.positionIronSights,
-					item.positionIronSights,
-					item.positionIronSights,
-				)
-			)
-
-			pose.translate(x, y, z)
-
-			var (pitch, yaw, roll) = bezierCurve(
-				progress,
-				item.rotIdle,
-				item.rotIdle,
-				item.rotIdle,
-				item.rotIdle.copy(roll = -PI.toFloat() / 6f + item.rotIdle.roll),
-				item.rotIronSights,
-				item.rotIronSights,
-				item.rotIronSights,
-				item.rotIronSights,
-				item.rotIronSights,
-			)
-
-			val fireAnim = bezierCurve(
-				predictedData?.fireAnim ?: 0.0,
-				fireAnimInterp
-			)
-
-			val rotFire = item.rotFireAnim
-			pitch += (rotFire.pitch + (predictedData?.fireAnimDeviation?.pitch ?: 0.0)) * fireAnim * (1.0 - progress * 0.6)
-			yaw += (rotFire.yaw + (predictedData?.fireAnimDeviation?.yaw ?: 0.0)) * fireAnim * (1.0 - progress * 0.6)
-			roll += (rotFire.roll + (predictedData?.fireAnimDeviation?.roll ?: 0.0)) * fireAnim * (1.0 - progress * 0.6)
-
-			pose.rotateZ(roll.toFloat())
-			pose.rotateY(yaw.toFloat())
-			pose.rotateX(pitch.toFloat())
-
-			itemInHandRenderer.renderItem(
-				player,
-				stack,
-				ItemDisplayContext.FIRST_PERSON_RIGHT_HAND,
-				false,
-				pose,
-				event.multiBufferSource,
-				event.packedLight
-			)
-
-			if (item is PlasmaWeaponItem<*>) {
-				// RenderSystem.setShader(GameRenderer::getPositionColorShader)
-				// RenderSystem.disableTexture()
-				// RenderSystem.enableBlend()
-
-				RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
-
-				pose.translate(-0.85, 0.25, -0.25)
-				pose.rotateZ(PI.toFloat())
-				pose.rotateY(PI.toFloat())
-				pose.scale(0.01f, 0.01f, 0.01f)
-
-				renderRect(pose.last().pose(), -2f, -2f, 72f, 34f, color = holoHudBackground)
-
-				stack.matteryEnergy?.let {
-					pose.pushPose()
-					pose.translate(0.0, 0.0, -1.0)
-					pose.scale(0.7f, 0.7f, 0.7f)
-					val text = it.batteryLevel.formatPower()
-					font.draw(pose, text, 2f, 2f, color = RGBAColor.WHITE)
-					pose.popPose()
-				}
-
-				pose.translate(60.0, 0.0, 0.0)
-
-				renderRect(pose.last().pose(), -1f, -1f, 9f, 32f, color = heatBackground)
-
-				val heat = item.heatProgress(stack, event.partialTick.toDouble()).toFloat()
-				renderRect(pose.last().pose(), 0f, 30f * (1f - heat), 7f, 30f * heat, color = initialHeatColor.linearInterpolation(heat, finalHeatColor))
-			}
-
-			pose.popPose()
-
-			item.dataTable = null
-			inRender = false
-		}
-
-		private val holoHudBackground get() = RGBAColor(75, 75, 75, 160)
-		private val heatBackground get() = RGBAColor(40, 144, 210, 160)
-		private val initialHeatColor get() = RGBAColor(85, 68, 7, 160)
-		private val finalHeatColor get() = RGBAColor(245, 118, 41, 160)
-
-		private val localPlayer: Player? get() {
-			return Minecraft.getInstance().player
-		}
-
-		private var firedPrimary = false
-		private var firedSecondary = false
-
-		private fun processClientInputs(item: AbstractWeaponItem<*>, itemStack: ItemStack) {
-			val minecraft = Minecraft.getInstance()
-			val player = minecraft.player ?: return
-			val dt = item.dataTable(itemStack)
-
-			if (!dt.wantsToScope && minecraft.options.keyUse.isDown) {
-				dt.wantsToScope = true
-				WeaponNetworkChannel.sendToServer(WeaponScopePacket.SCOPE_IN)
-			} else if (dt.wantsToScope && !minecraft.options.keyUse.isDown) {
-				dt.wantsToScope = false
-				WeaponNetworkChannel.sendToServer(WeaponScopePacket.SCOPE_OUT)
-			}
-
-			if (minecraft.options.keyAttack.isDown && (item.primaryAutomatic || !firedPrimary)) {
-				firedPrimary = true
-
-				if (item.tryPrimaryFire(itemStack, player)) {
-					WeaponNetworkChannel.sendToServer(WeaponFireInputPacket.PRIMARY)
-				}
-			} else if (!minecraft.options.keyAttack.isDown) {
-				firedPrimary = false
-			}
-
-			if (minecraft.options.keyAttack.isDown && (item.secondaryAutomatic || !firedSecondary)) {
-				firedSecondary = true
-
-				if (item.trySecondaryFire(itemStack, player)) {
-					WeaponNetworkChannel.sendToServer(WeaponFireInputPacket.SECONDARY)
-				}
-			} else if (!minecraft.options.keyAttack.isDown) {
-				firedSecondary = false
-			}
-		}
-
-		@Suppress("unchecked_cast")
-		fun tick(event: TickEvent.PlayerTickEvent) {
-			if (event.phase != TickEvent.Phase.START)
-				return
-
-			if (event.side == LogicalSide.CLIENT && event.player != localPlayer)
-				return
-
-			val held = event.player.mainHandItem
-
-			for (stack in event.player.inventory.items) {
-				if (held !== stack) {
-					(stack.item as? AbstractWeaponItem<*>)?.thinkHolstered(stack, event.player)
-				}
-
-				(stack.item as? AbstractWeaponItem<*>)?.let {
-					if (event.side == LogicalSide.CLIENT && stack.weaponDataTable?.uuid != predictedData?.uuid)
-						return@let
-
-					if (event.side == LogicalSide.CLIENT) {
-						(it as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
-					}
-
-					it.think2(stack, event.player)
-
-					if (event.side == LogicalSide.CLIENT) {
-						it.dataTable = null
-					}
-				}
-			}
-
-			val prev = items[event.player]
-			val heldItem = held.item
-
-			if (held.weaponDataTable?.uuid != prev?.weaponDataTable?.uuid) {
-				(prev?.item as? AbstractWeaponItem<*>)?.let {
-					if (event.side == LogicalSide.CLIENT) {
-						(it as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
-					}
-
-					it.holster(prev, event.player)
-
-					if (event.side == LogicalSide.CLIENT) {
-						it.dataTable = null
-					}
-				}
-
-				(held.item as? AbstractWeaponItem<*>)?.deploy(held, event.player)
-
-				items[event.player] = held
-
-				if (event.side == LogicalSide.CLIENT && heldItem is AbstractWeaponItem<*>) {
-					predictedData = heldItem.makeDataTable(held.tagNotNull.copy())
-
-					firedPrimary = false
-					firedSecondary = false
-				}
-			}
-
-			if (heldItem is AbstractWeaponItem<*>) {
-				// this can fail horribly, and die horribly, if weapon item changed,
-				// but its UUID did not (compound tag fraud)
-				if (event.side == LogicalSide.CLIENT) {
-					(heldItem as AbstractWeaponItem<WeaponDataTable>).dataTable = predictedData
-					processClientInputs(heldItem, held)
-				}
-
-				heldItem.think(held, event.player)
-
-				if (event.side == LogicalSide.CLIENT) {
-					heldItem.dataTable = null
-				}
-			}
-		}
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt
index 072273f12..cc39cae4b 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt
@@ -2,10 +2,9 @@ package ru.dbotthepony.mc.otm.item.weapon
 
 import com.google.common.collect.ImmutableMultimap
 import com.google.common.collect.Multimap
-import net.minecraft.ChatFormatting
 import net.minecraft.core.BlockPos
+import net.minecraft.core.Holder
 import net.minecraft.nbt.CompoundTag
-import net.minecraft.network.chat.Component
 import net.minecraft.tags.BlockTags
 import net.minecraft.world.entity.EquipmentSlot
 import net.minecraft.world.entity.LivingEntity
@@ -13,49 +12,40 @@ import net.minecraft.world.entity.ai.attributes.Attribute
 import net.minecraft.world.entity.ai.attributes.AttributeModifier
 import net.minecraft.world.entity.ai.attributes.Attributes
 import net.minecraft.world.entity.player.Player
-import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.Rarity
-import net.minecraft.world.item.TooltipFlag
-import net.minecraft.world.item.Vanishable
+import net.minecraft.world.item.component.ItemAttributeModifiers
 import net.minecraft.world.item.enchantment.Enchantment
 import net.minecraft.world.item.enchantment.EnchantmentCategory
 import net.minecraft.world.item.enchantment.Enchantments
 import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.Blocks
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.common.ForgeConfigSpec
-import net.minecraftforge.common.ToolAction
-import net.minecraftforge.common.ToolActions
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import ru.dbotthepony.mc.otm.capability.energy.EnergyConsumerItem
-import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl
+import net.neoforged.neoforge.common.ModConfigSpec
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
+import ru.dbotthepony.mc.otm.capability.energy.EnergyConsumerItem
 import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
 import ru.dbotthepony.mc.otm.capability.energy.getBarColor
 import ru.dbotthepony.mc.otm.capability.energy.getBarWidth
 import ru.dbotthepony.mc.otm.capability.matteryEnergy
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
+import ru.dbotthepony.mc.otm.core.damageType
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.math.DecimalConfigValue
-import ru.dbotthepony.mc.otm.core.TranslatableComponent
-import ru.dbotthepony.mc.otm.core.damageType
 import ru.dbotthepony.mc.otm.core.math.defineDecimal
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.math.nextVariance
-import ru.dbotthepony.mc.otm.core.orNull
 import ru.dbotthepony.mc.otm.core.util.WriteOnce
 import ru.dbotthepony.mc.otm.item.MatteryItem
 import ru.dbotthepony.mc.otm.item.addSimpleDescription
 import ru.dbotthepony.mc.otm.registry.MDamageTypes
 import ru.dbotthepony.mc.otm.registry.MatteryDamageSource
 
-class EnergySwordItem : MatteryItem(Properties().stacksTo(1).rarity(Rarity.RARE)), Vanishable {
-	val chargedAttributes: Multimap<Attribute, AttributeModifier>
-	val dischargedAttributes: Multimap<Attribute, AttributeModifier>
+class EnergySwordItem : MatteryItem(Properties().stacksTo(1).rarity(Rarity.RARE)) {
+	val chargedAttributes: ItemAttributeModifiers
+	val dischargedAttributes: ItemAttributeModifiers
 
 	init {
-		var builder = ImmutableMultimap.builder<Attribute, AttributeModifier>()
+		var builder = ImmutableMultimap.builder<Holder<Attribute>, AttributeModifier>()
 
 		builder.put(Attributes.ATTACK_DAMAGE, AttributeModifier(BASE_ATTACK_DAMAGE_UUID, "Weapon modifier", 11.0, AttributeModifier.Operation.ADDITION))
 		builder.put(Attributes.ATTACK_SPEED, AttributeModifier(BASE_ATTACK_SPEED_UUID, "Weapon modifier", -2.4, AttributeModifier.Operation.ADDITION))
@@ -83,7 +73,7 @@ class EnergySwordItem : MatteryItem(Properties().stacksTo(1).rarity(Rarity.RARE)
 	}
 
 	override fun getDestroySpeed(itemStack: ItemStack, blockState: BlockState): Float {
-		val energy = itemStack.getCapability(MatteryCapability.ENERGY).orNull() ?: return 1f
+		val energy = itemStack.getCapability(MatteryCapability.ITEM_ENERGY) ?: return 1f
 
 		if (blockState.`is`(Blocks.COBWEB)) {
 			return if (energy.batteryLevel < COBWEB_POWER_COST) 2f else 25f
@@ -110,7 +100,7 @@ class EnergySwordItem : MatteryItem(Properties().stacksTo(1).rarity(Rarity.RARE)
 			return true
 		}
 
-		itemStack.getCapability(MatteryCapability.ENERGY).ifPresentK {
+		itemStack.getCapability(MatteryCapability.ITEM_ENERGY)?.let {
 			if (it.extractEnergyExact(ENERGY_PER_SWING, false)) {
 				it.extractEnergy(attacker.level().random.nextVariance(ENERGY_PER_SWING_VARIANCE), false)
 				victim.matteryPlayer?.let {
@@ -174,6 +164,10 @@ class EnergySwordItem : MatteryItem(Properties().stacksTo(1).rarity(Rarity.RARE)
 		return EnergyConsumerItem(stack, MAX_ENERGY)
 	}
 
+	override fun getDefaultAttributeModifiers(stack: ItemStack): ItemAttributeModifiers {
+		return super.getDefaultAttributeModifiers(stack)
+	}
+
 	override fun getAttributeModifiers(
 		slot: EquipmentSlot,
 		itemStack: ItemStack
@@ -220,7 +214,7 @@ class EnergySwordItem : MatteryItem(Properties().stacksTo(1).rarity(Rarity.RARE)
 		private var _PLANT_POWER_COST: DecimalConfigValue by WriteOnce()
 		private var _PLANT_POWER_COST_VARIANCE: DecimalConfigValue by WriteOnce()
 
-		fun registerConfig(builder: ForgeConfigSpec.Builder) {
+		fun registerConfig(builder: ModConfigSpec.Builder) {
 			builder.comment("Energy sword values").push("EnergySword")
 
 			_MAX_ENERGY = builder.defineDecimal("MAX_ENERGY", Decimal(2_000_000), Decimal.ZERO)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaRifleItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaRifleItem.kt
deleted file mode 100644
index afdbc7a1d..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaRifleItem.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-package ru.dbotthepony.mc.otm.item.weapon
-
-import net.minecraft.sounds.SoundSource
-import net.minecraft.world.entity.player.Player
-import net.minecraft.world.item.ItemStack
-import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
-import ru.dbotthepony.mc.otm.core.math.Angle
-import ru.dbotthepony.mc.otm.core.math.Vector
-import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.mc.otm.entity.PlasmaProjectile
-import ru.dbotthepony.mc.otm.core.position
-import ru.dbotthepony.mc.otm.registry.MSoundEvents
-
-class PlasmaRifleItem : PlasmaWeaponItem<PlasmaWeaponDataTable>(PlasmaWeaponDataTable::class, Decimal(200_000)) {
-	override val roundsPerMinute: Int = 200
-	override val scopingTime: Int = 7
-
-	override val positionIdle: Vector
-		get() = Vector(0.2, -0.4, -0.6)
-	override val positionIronSights: Vector
-		get() = Vector(0.0, -0.24, -1.0)
-	override val rotIdle: Angle
-		get() = Angle(0.0, -0.02, 0.0)
-	override val rotIronSights: Angle
-		get() = Angle(-0.02, 0.0, 0.0)
-
-	override fun primaryFire(itemStack: ItemStack, player: Player, dt: PlasmaWeaponDataTable): Boolean {
-		if (!player.level().isClientSide) {
-			val arrow = PlasmaProjectile(player.level())
-			arrow.position = player.eyePosition
-
-			val calc = VelocityCalculation(player, force = 4.0, deviation = 0.3)
-			calc.load(arrow)
-
-			arrow.owner = player
-
-			player.level().addFreshEntity(arrow)
-		} else {
-			dt.doFireAnim(deviation = rotFireAnimDeviation)
-		}
-
-		receiveHeat(itemStack, player, 10.0, dt)
-
-		player.level().playSound(
-			player,
-			player,
-			MSoundEvents.RIFLE_SHOT,
-			SoundSource.PLAYERS,
-			1f,
-			1f
-		)
-
-		if (!player.abilities.instabuild)
-			energyData(itemStack).extractEnergy(ENERGY_PER_SHOT, false)
-
-		return true
-	}
-
-	override fun canPrimaryFire(itemStack: ItemStack, player: Player, dt: PlasmaWeaponDataTable): Boolean {
-		return super.canPrimaryFire(itemStack, player, dt) && (player.abilities.instabuild || energyData(itemStack).extractEnergyExact(ENERGY_PER_SHOT, true))
-	}
-
-	companion object {
-		private val ENERGY_PER_SHOT = Decimal(3_000)
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt
deleted file mode 100644
index d593ec91e..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt
+++ /dev/null
@@ -1,295 +0,0 @@
-package ru.dbotthepony.mc.otm.item.weapon
-
-import net.minecraft.ChatFormatting
-import net.minecraft.core.Direction
-import net.minecraft.nbt.CompoundTag
-import net.minecraft.network.chat.Component
-import net.minecraft.server.level.ServerPlayer
-import net.minecraft.sounds.SoundSource
-import net.minecraft.world.entity.player.Player
-import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.TooltipFlag
-import net.minecraft.world.level.Level
-import net.minecraftforge.common.capabilities.Capability
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.capabilities.ICapabilityProvider
-import net.minecraftforge.common.util.INBTSerializable
-import net.minecraftforge.common.util.LazyOptional
-import ru.dbotthepony.mc.otm.*
-import ru.dbotthepony.mc.otm.capability.*
-import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
-import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl
-import ru.dbotthepony.mc.otm.capability.energy.getBarColor
-import ru.dbotthepony.mc.otm.capability.energy.getBarWidth
-import ru.dbotthepony.mc.otm.core.*
-import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.mc.otm.core.math.bezierCurve
-import ru.dbotthepony.mc.otm.core.nbt.doubles
-import ru.dbotthepony.mc.otm.core.nbt.ints
-import ru.dbotthepony.mc.otm.core.nbt.set
-import ru.dbotthepony.mc.otm.core.util.formatPower
-import ru.dbotthepony.mc.otm.registry.MSoundEvents
-import kotlin.reflect.KClass
-
-class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: Decimal) : IMatteryEnergyStorage, ICapabilityProvider, INBTSerializable<CompoundTag> {
-	private val energyResolver = LazyOptional.of { this }
-	private var innerBatteryLevel = Decimal.ZERO
-
-	override val energyFlow: FlowDirection
-		get() = FlowDirection.INPUT
-
-	var battery: ItemStack = ItemStack.EMPTY
-
-	override fun <T : Any> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
-		if (cap == MatteryCapability.ENERGY || cap == ForgeCapabilities.ENERGY) {
-			return energyResolver.cast()
-		}
-
-		return LazyOptional.empty()
-	}
-
-	override fun serializeNBT(): CompoundTag {
-		val nbt = CompoundTag()
-		nbt["battery_level"] = innerBatteryLevel.serializeNBT()
-		nbt["battery"] = battery.serializeNBT()
-		return nbt
-	}
-
-	override fun deserializeNBT(nbt: CompoundTag) {
-		innerBatteryLevel = Decimal.deserializeNBT(nbt["battery_level"])
-		battery = ItemStack.of(nbt["battery"] as CompoundTag)
-	}
-
-	override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
-		if (!howMuch.isPositive)
-			return Decimal.ZERO
-
-		@Suppress("NAME_SHADOWING")
-		var howMuch = howMuch
-		var totalExtracted = Decimal.ZERO
-
-		if (!battery.isEmpty) {
-			battery.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
-				val extracted = it.extractEnergy(howMuch, simulate)
-
-				if (extracted >= howMuch) {
-					return extracted
-				}
-
-				howMuch -= extracted
-				totalExtracted += extracted
-			}
-		}
-
-		val newEnergy = (innerBatteryLevel - howMuch).moreThanZero()
-		val diff = innerBatteryLevel - newEnergy
-
-		if (!simulate) {
-			innerBatteryLevel = newEnergy
-		}
-
-		return diff + totalExtracted
-	}
-
-	override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
-		if (!howMuch.isPositive)
-			return Decimal.ZERO
-
-		@Suppress("NAME_SHADOWING")
-		var howMuch = howMuch
-		var totalReceived = Decimal.ZERO
-
-		if (!battery.isEmpty) {
-			battery.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
-				val received = it.receiveEnergy(howMuch, simulate)
-
-				if (received >= howMuch) {
-					return received
-				}
-
-				howMuch -= received
-				totalReceived += received
-			}
-		}
-
-		if (innerBatteryLevel >= innerCapacity) {
-			return totalReceived
-		}
-
-		val newEnergy = (innerBatteryLevel + howMuch).coerceAtMost(innerCapacity)
-		val diff = newEnergy - innerBatteryLevel
-
-		if (!simulate) {
-			innerBatteryLevel = newEnergy
-		}
-
-		return diff + totalReceived
-	}
-
-	fun think() {
-		if (!battery.isEmpty && innerBatteryLevel < innerCapacity) {
-			battery.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
-				innerBatteryLevel += it.extractEnergy(innerCapacity - innerBatteryLevel, false)
-			}
-		}
-	}
-
-	override fun drainBattery(): Boolean {
-		innerBatteryLevel = Decimal.ZERO
-
-		if (!battery.isEmpty) {
-			battery.getCapability(MatteryCapability.ENERGY).ifPresentK {
-				return it.drainBattery()
-			}
-		}
-
-		return true
-	}
-
-	override fun fillBattery(): Boolean {
-		innerBatteryLevel = innerCapacity
-
-		if (!battery.isEmpty) {
-			battery.getCapability(MatteryCapability.ENERGY).ifPresentK {
-				return it.fillBattery()
-			}
-		}
-
-		return true
-	}
-
-	override var batteryLevel: Decimal
-		get() = (battery.energy?.energyStoredMattery ?: Decimal.ZERO) + innerBatteryLevel
-		set(value) {
-			innerBatteryLevel = value
-		}
-
-	override val maxBatteryLevel: Decimal
-		get() = (battery.energy?.maxEnergyStoredMattery ?: Decimal.ZERO) + innerCapacity
-}
-
-open class PlasmaWeaponDataTable(tag: CompoundTag) : WeaponDataTable(tag) {
-	var heatScore by tag.doubles
-	var heatCooldown by tag.ints
-}
-
-abstract class PlasmaWeaponItem<D : PlasmaWeaponDataTable>(tables: KClass<D>, private val energyCapacity: Decimal) : AbstractWeaponItem<D>(tables) {
-	init {
-		tooltips.itemEnergy()
-	}
-
-	fun energyData(itemStack: ItemStack) = itemStack.matteryEnergy as PlasmaWeaponEnergy
-
-	open val cooldownSpeed get() = 1.0
-	open val maxHeat = 40.0
-	open val overheatCooldown = 30
-
-	open val cooldownSpline = doubleArrayOf(
-		0.1,
-		0.14,
-		0.18,
-		0.25,
-		0.35,
-		0.45,
-		0.6,
-		0.8,
-		0.97,
-		1.0,
-		1.0,
-		1.0,
-		1.0,
-		1.0,
-		1.0,
-		1.1,
-		1.2,
-		1.3,
-		1.4,
-	)
-
-	override fun canPrimaryFire(itemStack: ItemStack, player: Player, dt: D): Boolean {
-		if (player is ServerPlayer) {
-			// allow little deviation because nothing is perfect
-			return super.canPrimaryFire(itemStack, player, dt) && dt.heatCooldown <= 2
-		}
-
-		return super.canPrimaryFire(itemStack, player, dt) && dt.heatCooldown <= 0
-	}
-
-	open fun canCooldown(itemStack: ItemStack, player: Player, dt: D = dataTable(itemStack)): Boolean {
-		return true
-	}
-
-	override fun isBarVisible(p_150899_: ItemStack): Boolean {
-		return p_150899_.matteryEnergy != null
-	}
-
-	override fun getBarWidth(p_150900_: ItemStack): Int {
-		return p_150900_.matteryEnergy?.getBarWidth() ?: super.getBarWidth(p_150900_)
-	}
-
-	override fun getBarColor(p_150901_: ItemStack): Int {
-		return p_150901_.matteryEnergy?.getBarColor() ?: super.getBarColor(p_150901_)
-	}
-
-	protected open fun coolWeaponDown(itemStack: ItemStack, player: Player, dt: D) {
-		if (!canCooldown(itemStack, player, dt))
-			return
-
-		if (dt.heatScore > 0.0)
-			dt.heatScore = (dt.heatScore - cooldownSpeed * bezierCurve(heatProgress(itemStack, dt = dt), cooldownSpline)).coerceAtLeast(0.0)
-
-		if (dt.heatCooldown > 0)
-			dt.heatCooldown--
-	}
-
-	open fun heatProgress(itemStack: ItemStack, partialTicks: Double = 0.0, dt: D = dataTable(itemStack)): Double {
-		if (dt.heatScore <= 0)
-			return 0.0
-
-		if (dt.heatScore - cooldownSpeed > maxHeat)
-			return 1.0
-
-		val result = dt.heatScore - cooldownSpeed * partialTicks
-		return result / maxHeat
-	}
-
-	open fun overheatProgress(itemStack: ItemStack, partialTicks: Double = 0.0, dt: D = dataTable(itemStack)): Double {
-		if (dt.heatCooldown <= 0)
-			return 0.0
-
-		if (dt.heatCooldown - partialTicks > overheatCooldown)
-			return 1.0
-
-		val result = dt.heatCooldown - partialTicks
-		return result / overheatCooldown
-	}
-
-	open fun receiveHeat(itemStack: ItemStack, player: Player, byTicks: Double, dt: D = dataTable(itemStack)) {
-		dt.heatScore = (dt.heatScore + byTicks).coerceAtMost(maxHeat * 2)
-
-		if (dt.heatScore > maxHeat) {
-			val before = dt.heatCooldown > 0
-
-			dt.heatCooldown = overheatCooldown
-
-			if (!before) {
-				player.level().playSound(player, player, MSoundEvents.PLASMA_WEAPON_OVERHEAT, SoundSource.PLAYERS, 1f, 1f)
-			}
-		}
-	}
-
-	override fun think2(itemStack: ItemStack, player: Player, dt: D) {
-		super.think2(itemStack, player, dt)
-
-		coolWeaponDown(itemStack, player, dt)
-
-		itemStack.getCapability(MatteryCapability.ENERGY).ifPresentK {
-			it as PlasmaWeaponEnergy
-			it.think()
-		}
-	}
-
-	override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
-		return PlasmaWeaponEnergy(stack, energyCapacity)
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt
index d03613743..69147d98b 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt
@@ -6,13 +6,14 @@ import com.mojang.datafixers.util.Pair
 import com.mojang.serialization.Codec
 import com.mojang.serialization.DataResult
 import com.mojang.serialization.DynamicOps
+import com.mojang.serialization.MapCodec
 import net.minecraft.core.registries.Registries
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.tags.TagKey
 import net.minecraft.world.item.Item
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.registry.MDeferredRegister
 import ru.dbotthepony.mc.otm.registry.RegistryDelegate
 import java.util.*
 
@@ -22,7 +23,7 @@ abstract class AbstractRegistryAction(
 	val priority: Optional<Int> = Optional.empty(),
 ) : Comparable<AbstractRegistryAction> {
 	interface Type<T : AbstractRegistryAction> {
-		val codec: Codec<T>
+		val codec: MapCodec<T>
 	}
 
 	fun update(registry: MutableCollection<IMutableRegistryEntry>, source: ResourceLocation) {
@@ -98,12 +99,12 @@ abstract class AbstractRegistryAction(
 	}
 
 	companion object {
-		private val registryDelegate = RegistryDelegate<Type<*>>("matter_registry_action") { disableSaving(); disableSync() }
+		private val registryDelegate = RegistryDelegate<Type<*>>("matter_registry_action") { sync(false) }
 
 		val registryKey get() = registryDelegate.key
 		val registry by registryDelegate
 
-		private val registrar = DeferredRegister.create(registryKey, OverdriveThatMatters.MOD_ID)
+		private val registrar = MDeferredRegister(registryKey, OverdriveThatMatters.MOD_ID)
 
 		init {
 			registrar.register("insert") { InsertAction.Companion }
@@ -113,13 +114,13 @@ abstract class AbstractRegistryAction(
 			registrar.register("blacklist") { BlacklistAction.Companion }
 		}
 
-		internal fun register(bus: IEventBus) {
+		fun register(bus: IEventBus) {
 			registrar.register(bus)
 			bus.addListener(registryDelegate::build)
 		}
 
 		val CODEC: Codec<AbstractRegistryAction> by lazy {
-			registry.codec.dispatch({ it.type }, { it.codec })
+			registry.byNameCodec().dispatch({ it.type }, { it.codec })
 		}
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt
index 50449c5dd..6932d86af 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt
@@ -6,11 +6,12 @@ import com.mojang.datafixers.util.Pair
 import com.mojang.serialization.Codec
 import com.mojang.serialization.DataResult
 import com.mojang.serialization.DynamicOps
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.tags.TagKey
 import net.minecraft.world.item.Item
-import net.minecraftforge.registries.ForgeRegistries
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.data.DecimalCodec
 import ru.dbotthepony.mc.otm.data.PredicatedCodecList
@@ -28,8 +29,8 @@ class ComputeAction(
 				it.group(
 					DecimalCodec.fieldOf("matter").forGetter(Constant::matter),
 					Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity),
-					IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Constant::matterFunction),
-					IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Constant::complexityFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("matterFunction").forGetter(Constant::matterFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("complexityFunction").forGetter(Constant::complexityFunction),
 				).apply(it, ::Constant)
 			} to Predicate { true }
 		}
@@ -39,7 +40,7 @@ class ComputeAction(
 				it.group(
 					DecimalCodec.fieldOf("matter").forGetter(Constant::matter),
 					Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity),
-					IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("function").forGetter(Constant::matterFunction),
 				).apply(it) { a, b, c -> Constant(a, b, c, c) }
 			} to Predicate { it.matterFunction == it.complexityFunction }
 		}
@@ -48,8 +49,8 @@ class ComputeAction(
 			RecordCodecBuilder.create<Constant> {
 				it.group(
 					DecimalCodec.fieldOf("value").forGetter(Constant::matter),
-					IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Constant::matterFunction),
-					IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Constant::complexityFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("matterFunction").forGetter(Constant::matterFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("complexityFunction").forGetter(Constant::complexityFunction),
 				).apply(it) { a, b, c -> Constant(a, a.toDouble(), b, c) }
 			} to Predicate { it.matter == Decimal(it.complexity.toString()) }
 		}
@@ -58,7 +59,7 @@ class ComputeAction(
 			RecordCodecBuilder.create<Constant> {
 				it.group(
 					DecimalCodec.fieldOf("value").forGetter(Constant::matter),
-					IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("function").forGetter(Constant::matterFunction),
 				).apply(it) { a, b -> Constant(a, a.toDouble(), b, b) }
 			} to Predicate { it.matter == Decimal(it.complexity.toString()) && it.matterFunction == it.complexityFunction }
 		}
@@ -67,7 +68,7 @@ class ComputeAction(
 			RecordCodecBuilder.create<Constant> {
 				it.group(
 					Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity),
-					IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::complexityFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("function").forGetter(Constant::complexityFunction),
 				).apply(it) { a, b -> Constant(Decimal.ZERO, a.toDouble(), IMatterFunction.NOOP, b) }
 			} to Predicate { it.matterFunction == IMatterFunction.NOOP }
 		}
@@ -76,7 +77,7 @@ class ComputeAction(
 			RecordCodecBuilder.create<Constant> {
 				it.group(
 					Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity),
-					IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Constant::complexityFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("complexityFunction").forGetter(Constant::complexityFunction),
 				).apply(it) { a, b -> Constant(Decimal.ZERO, a.toDouble(), IMatterFunction.NOOP, b) }
 			} to Predicate { it.matterFunction == IMatterFunction.NOOP }
 		}
@@ -85,7 +86,7 @@ class ComputeAction(
 			RecordCodecBuilder.create<Constant> {
 				it.group(
 					DecimalCodec.fieldOf("matter").forGetter(Constant::matter),
-					IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("function").forGetter(Constant::matterFunction),
 				).apply(it) { a, b -> Constant(a, 0.0, b, IMatterFunction.NOOP) }
 			} to Predicate { it.complexityFunction == IMatterFunction.NOOP }
 		}
@@ -94,7 +95,7 @@ class ComputeAction(
 			RecordCodecBuilder.create<Constant> {
 				it.group(
 					DecimalCodec.fieldOf("matter").forGetter(Constant::matter),
-					IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Constant::matterFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("matterFunction").forGetter(Constant::matterFunction),
 				).apply(it) { a, b -> Constant(a, 0.0, b, IMatterFunction.NOOP) }
 			} to Predicate { it.complexityFunction == IMatterFunction.NOOP }
 		}
@@ -181,7 +182,7 @@ class ComputeAction(
 			RecordCodecBuilder.create {
 				it.group(
 					TargetCodec.fieldOf("id").forGetter(Value::id),
-					IMatterFunction.registry.codec.fieldOf("function").forGetter(Value::matterFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("function").forGetter(Value::matterFunction),
 				).apply(it, ::Value)
 			}
 		}
@@ -190,8 +191,8 @@ class ComputeAction(
 			RecordCodecBuilder.create {
 				it.group(
 					TargetCodec.fieldOf("id").forGetter(Value::id),
-					IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Value::matterFunction),
-					IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Value::complexityFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("matterFunction").forGetter(Value::matterFunction),
+					IMatterFunction.registry.byNameCodec().fieldOf("complexityFunction").forGetter(Value::complexityFunction),
 				).apply(it, ::Value)
 			}
 		}
@@ -221,8 +222,8 @@ class ComputeAction(
 			}
 		}
 
-		override val codec: Codec<ComputeAction> by lazy {
-			RecordCodecBuilder.create {
+		override val codec: MapCodec<ComputeAction> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(
 					TargetCodec.fieldOf("id").forGetter(ComputeAction::id),
 					Codec.list(Codec
@@ -284,7 +285,7 @@ class ComputeAction(
 		override fun provideValues(): MatterManager.Result {
 			return id.map(
 				{
-					MatterManager.compute(ForgeRegistries.ITEMS.getValue(it) ?: throw NullPointerException("$it does not point to anything"))
+					MatterManager.compute(BuiltInRegistries.ITEM.get(it))
 				},
 				{
 					MatterManager.compute(it)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt
index f1b91b715..cd145fc6e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt
@@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.matter
 
 import com.mojang.datafixers.util.Either
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.tags.TagKey
@@ -28,8 +29,8 @@ open class DeleteAction(
 		get() = Companion
 
 	companion object : Type<DeleteAction> {
-		override val codec: Codec<DeleteAction> by lazy {
-			RecordCodecBuilder.create {
+		override val codec: MapCodec<DeleteAction> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(
 					TargetCodec.fieldOf("id").forGetter(DeleteAction::id),
 					Codec.BOOL.optionalFieldOf("errorOnFailure", false).forGetter(DeleteAction::errorOnFailure)
@@ -44,8 +45,8 @@ class BlacklistAction(id: Either<ResourceLocation, TagKey<Item>>) : DeleteAction
 		get() = Companion
 
 	companion object : Type<BlacklistAction> {
-		override val codec: Codec<BlacklistAction> by lazy {
-			RecordCodecBuilder.create {
+		override val codec: MapCodec<BlacklistAction> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(
 					TargetCodec.fieldOf("id").forGetter(BlacklistAction::id)
 				).apply(it, ::BlacklistAction)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterFunction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterFunction.kt
index f7bf8df86..4c6e670e9 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterFunction.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterFunction.kt
@@ -1,11 +1,12 @@
 package ru.dbotthepony.mc.otm.matter
 
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
-import ru.dbotthepony.kommons.util.getValue
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.core.getValue
 import ru.dbotthepony.mc.otm.core.math.Decimal
+import ru.dbotthepony.mc.otm.matter.SimpleMatterFunction.DecimalFunction
+import ru.dbotthepony.mc.otm.matter.SimpleMatterFunction.DoubleFunction
+import ru.dbotthepony.mc.otm.matter.SimpleMatterFunction.IntFunction
+import ru.dbotthepony.mc.otm.registry.MDeferredRegister
 import ru.dbotthepony.mc.otm.registry.RegistryDelegate
 
 interface IMatterFunction {
@@ -14,12 +15,12 @@ interface IMatterFunction {
 	fun updateValue(self: Double, other: Double): Double
 
 	companion object : IMatterFunction {
-		private val registryDelegate = RegistryDelegate<IMatterFunction>("matter_function") { disableSync(); disableSaving() }
+		private val registryDelegate = RegistryDelegate<IMatterFunction>("matter_function") { sync(false) }
 
 		val registryKey get() = registryDelegate.key
 		val registry by registryDelegate
 
-		private val registrar = DeferredRegister.create(registryKey, OverdriveThatMatters.MOD_ID)
+		private val registrar = MDeferredRegister(registryKey, OverdriveThatMatters.MOD_ID)
 
 		init {
 			registrar.register("noop") { this }
@@ -35,7 +36,7 @@ interface IMatterFunction {
 		val AT_MOST: IMatterFunction by registrar.register("at_most") { SimpleMatterFunction(Int::coerceAtMost, Double::coerceAtMost, Decimal::coerceAtMost) }
 		val REPLACE: IMatterFunction by registrar.register("replace") { SimpleMatterFunction({ _, value -> value }, { _, value -> value }, { _, value -> value }) }
 
-		internal fun register(bus: IEventBus) {
+		fun register(bus: IEventBus) {
 			registrar.register(bus)
 			bus.addListener(registryDelegate::build)
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt
index b0fccf9c5..87bab4b7e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt
@@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.matter
 
 import com.mojang.datafixers.util.Either
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.tags.TagKey
@@ -52,8 +53,8 @@ class InsertAction(
 		get() = Companion
 
 	companion object : Type<InsertAction> {
-		override val codec: Codec<InsertAction> by lazy {
-			RecordCodecBuilder.create {
+		override val codec: MapCodec<InsertAction> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(
 					TargetCodec.fieldOf("id").forGetter(InsertAction::id),
 					DecimalCodec.fieldOf("matter").forGetter(InsertAction::matter),
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt
index 6f45e0283..e490e82a1 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt
@@ -8,7 +8,8 @@ import net.minecraft.resources.ResourceLocation
 import net.minecraft.tags.TagKey
 import net.minecraft.world.item.Item
 import net.minecraft.world.level.ItemLike
-import net.minecraftforge.data.event.GatherDataEvent
+import net.neoforged.neoforge.data.event.GatherDataEvent
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.util.WriteOnce
 import ru.dbotthepony.mc.otm.core.registryName
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt
index 125672996..5e3ae2c6a 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt
@@ -26,43 +26,44 @@ import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
 import net.minecraft.ChatFormatting
 import net.minecraft.commands.CommandSourceStack
 import net.minecraft.commands.Commands
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.network.FriendlyByteBuf
 import net.minecraft.network.chat.Component
 import net.minecraft.network.chat.MutableComponent
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.resources.ResourceKey
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.MinecraftServer
+import net.minecraft.server.level.ServerPlayer
 import net.minecraft.server.packs.resources.ResourceManager
 import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener
 import net.minecraft.tags.TagKey
 import net.minecraft.util.profiling.ProfilerFiller
-import net.minecraft.world.Container
-import net.minecraft.world.entity.player.Player
-import net.minecraft.world.inventory.AbstractContainerMenu
-import net.minecraft.world.inventory.TransientCraftingContainer
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.crafting.CraftingInput
 import net.minecraft.world.item.crafting.Ingredient
 import net.minecraft.world.item.crafting.Recipe
+import net.minecraft.world.item.crafting.RecipeInput
 import net.minecraft.world.item.crafting.RecipeType
+import net.minecraft.world.item.crafting.SmithingRecipeInput
 import net.minecraft.world.item.crafting.SmithingTransformRecipe
 import net.minecraft.world.level.ItemLike
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.common.crafting.IShapedRecipe
-import net.minecraftforge.event.AddReloadListenerEvent
-import net.minecraftforge.event.OnDatapackSyncEvent
-import net.minecraftforge.event.RegisterCommandsEvent
-import net.minecraftforge.event.entity.player.ItemTooltipEvent
-import net.minecraftforge.event.network.CustomPayloadEvent
-import net.minecraftforge.event.server.ServerStartedEvent
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.ModList
-import net.minecraftforge.network.PacketDistributor
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.server.command.EnumArgument
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.fml.ModList
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.common.crafting.IShapedRecipe
+import net.neoforged.neoforge.event.AddReloadListenerEvent
+import net.neoforged.neoforge.event.OnDatapackSyncEvent
+import net.neoforged.neoforge.event.RegisterCommandsEvent
+import net.neoforged.neoforge.event.entity.player.ItemTooltipEvent
+import net.neoforged.neoforge.event.server.ServerStartedEvent
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import net.neoforged.neoforge.registries.DeferredRegister
+import net.neoforged.neoforge.server.command.EnumArgument
 import org.apache.logging.log4j.LogManager
-import ru.dbotthepony.mc.otm.config.ClientConfig
 import ru.dbotthepony.mc.otm.MINECRAFT_SERVER
 import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
@@ -72,33 +73,34 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive
 import ru.dbotthepony.mc.otm.client.isShiftDown
 import ru.dbotthepony.mc.otm.client.minecraft
-import ru.dbotthepony.mc.otm.container.set
+import ru.dbotthepony.mc.otm.config.ClientConfig
+import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer
 import ru.dbotthepony.mc.otm.container.util.stream
-import ru.dbotthepony.mc.otm.core.math.Decimal
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.TextComponent
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
+import ru.dbotthepony.mc.otm.core.collect.any
+import ru.dbotthepony.mc.otm.core.collect.filter
 import ru.dbotthepony.mc.otm.core.filterNotNull
+import ru.dbotthepony.mc.otm.core.getReverseTag
+import ru.dbotthepony.mc.otm.core.isNotEmpty
+import ru.dbotthepony.mc.otm.core.math.Decimal
+import ru.dbotthepony.mc.otm.core.math.isZero
+import ru.dbotthepony.mc.otm.core.readItemType
+import ru.dbotthepony.mc.otm.core.registryName
+import ru.dbotthepony.mc.otm.core.stream
 import ru.dbotthepony.mc.otm.core.util.formatMatter
 import ru.dbotthepony.mc.otm.core.util.formatMatterFull
 import ru.dbotthepony.mc.otm.core.util.formatSiComponent
 import ru.dbotthepony.mc.otm.core.util.formatTickDuration
-import ru.dbotthepony.mc.otm.core.ifPresentK
-import ru.dbotthepony.mc.otm.core.isActuallyEmpty
-import ru.dbotthepony.mc.otm.core.isNotEmpty
-import ru.dbotthepony.mc.otm.core.math.isZero
-import ru.dbotthepony.mc.otm.core.orNull
-import ru.dbotthepony.mc.otm.core.readItemType
-import ru.dbotthepony.mc.otm.core.registryName
-import ru.dbotthepony.mc.otm.core.stream
 import ru.dbotthepony.mc.otm.core.util.readBinaryComponent
 import ru.dbotthepony.mc.otm.core.util.readCollection
 import ru.dbotthepony.mc.otm.core.util.writeBinaryComponent
 import ru.dbotthepony.mc.otm.core.util.writeCollection
 import ru.dbotthepony.mc.otm.core.writeItemType
+import ru.dbotthepony.mc.otm.matter.MatterManager.Finder
 import ru.dbotthepony.mc.otm.milliTime
-import ru.dbotthepony.mc.otm.network.GenericNetworkChannel
-import ru.dbotthepony.mc.otm.network.MNetworkContext
-import ru.dbotthepony.mc.otm.network.MatteryPacket
+import ru.dbotthepony.mc.otm.registry.MDeferredRegister
 import ru.dbotthepony.mc.otm.registry.RegistryDelegate
 import ru.dbotthepony.mc.otm.secondTime
 import ru.dbotthepony.mc.otm.storage.ItemStorageStack
@@ -113,12 +115,37 @@ import java.util.function.BooleanSupplier
 import java.util.stream.Stream
 import java.util.zip.Deflater
 import java.util.zip.Inflater
-import kotlin.ConcurrentModificationException
 import kotlin.collections.ArrayDeque
 import kotlin.collections.ArrayList
+import kotlin.collections.Collection
 import kotlin.collections.HashMap
 import kotlin.collections.LinkedHashMap
-import kotlin.jvm.optionals.getOrNull
+import kotlin.collections.List
+import kotlin.collections.Map
+import kotlin.collections.MutableMap
+import kotlin.collections.all
+import kotlin.collections.component1
+import kotlin.collections.component2
+import kotlin.collections.contains
+import kotlin.collections.copyOfRange
+import kotlin.collections.count
+import kotlin.collections.filter
+import kotlin.collections.firstOrNull
+import kotlin.collections.forEach
+import kotlin.collections.indices
+import kotlin.collections.isNotEmpty
+import kotlin.collections.isNullOrEmpty
+import kotlin.collections.iterator
+import kotlin.collections.joinToString
+import kotlin.collections.last
+import kotlin.collections.listOf
+import kotlin.collections.map
+import kotlin.collections.mapOf
+import kotlin.collections.set
+import kotlin.collections.sortBy
+import kotlin.collections.sortWith
+import kotlin.collections.toTypedArray
+import kotlin.collections.withIndex
 import kotlin.math.pow
 import kotlin.math.roundToInt
 
@@ -159,18 +186,12 @@ object MatterManager {
 				return keyEntry
 			}
 
-			val reverse = ForgeRegistries.ITEMS.tags()!!.getReverseTag(value).orElse(null)
-
-			if (reverse != null) {
-				return reverse.tagKeys
-					.map { tagEntries[it] }
-					.filterNotNull()
-					.sorted { o1, o2 -> o1.priority.compareTo(o2.priority) }
-					.findFirst()
-					.orElse(null) ?: IMatterValue.Companion
-			}
-
-			return IRegistryEntry.Companion
+			return BuiltInRegistries.ITEM.getReverseTag(value)
+				.map { tagEntries[it] }
+				.filterNotNull()
+				.sorted { o1, o2 -> o1.priority.compareTo(o2.priority) }
+				.findFirst()
+				.orElse(null) ?: IMatterValue.Companion
 		}
 
 		/**
@@ -185,16 +206,12 @@ object MatterManager {
 			var compute = computeKeys[value.registryName!!]
 
 			if (compute == null) {
-				val reverse = ForgeRegistries.ITEMS.tags()!!.getReverseTag(value).orElse(null)
-
-				if (reverse != null) {
-					compute = reverse.tagKeys
-						.map { computeTags[it] }
-						.filterNotNull()
-						.sorted()
-						.findFirst()
-						.orElse(null)
-				}
+				compute = BuiltInRegistries.ITEM.getReverseTag(value)
+					.map { computeTags[it] }
+					.filterNotNull()
+					.sorted()
+					.findFirst()
+					.orElse(null)
 			}
 
 			if (compute != null) {
@@ -214,25 +231,15 @@ object MatterManager {
 				if (key in keyEntriesBlacklist)
 					return@fn true
 
-				val reverse = ForgeRegistries.ITEMS.tags()!!.getReverseTag(value).orElse(null)
-
-				if (reverse != null) {
-					return@fn reverse.tagKeys.anyMatch { it in tagEntriesBlacklist }
-				}
-
-				return@fn false
+				return@fn BuiltInRegistries.ITEM.getReverseTag(value).anyMatch { it in tagEntriesBlacklist }
 			})
 		}
 
 		fun isBlacklisted(value: TagKey<Item>): Boolean {
 			return blacklistCacheTag.computeIfAbsent(value, Object2BooleanFunction fn@{
-				return@fn value in tagEntriesBlacklist || ForgeRegistries.ITEMS.tags()!!.getTag(value).let {
-					if (it.isEmpty || !it.isBound) {
-						false
-					} else {
-						it.stream().anyMatch { item -> !isBlacklisted(item) }
-					}
-				}
+				return@fn value in tagEntriesBlacklist || BuiltInRegistries.ITEM.getTag(value).map {
+					it.stream().anyMatch { item -> !isBlacklisted(item.value()) }
+				}.orElse(false)
 			})
 		}
 
@@ -297,7 +304,7 @@ object MatterManager {
 
 					val result = AbstractRegistryAction.CODEC
 						.decode(JsonOps.INSTANCE, json)
-						.getOrThrow(false) { throw JsonSyntaxException("Failed to deserialize matter registry entry $key: $it") }
+						.getOrThrow { throw JsonSyntaxException("Failed to deserialize matter registry entry $key: $it") }
 						.first
 
 					when (result) {
@@ -419,33 +426,30 @@ object MatterManager {
 	}
 
 	private object Resolver : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), FINDER_DIRECTORY) {
-		val delegate = RegistryDelegate<Finder>("recipe_finder") {
-			disableSync()
-			disableSaving()
-		}
+		val delegate = RegistryDelegate<Finder>("recipe_finder") { sync(false) }
 
 		var ready = false
 			private set
 
-		val registrar: DeferredRegister<Finder> = DeferredRegister.create(delegate.key, OverdriveThatMatters.MOD_ID)
+		val registrar = MDeferredRegister(delegate.key, OverdriveThatMatters.MOD_ID)
 
 		init {
 			registrar.register("simple") {
 				Finder { server, data ->
 					val location = (data["recipe_type"] ?: throw JsonSyntaxException("Missing recipe type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid recipe type: ${data["recipe_type"]}")
 
-					if (!ForgeRegistries.RECIPE_TYPES.containsKey(location)) {
+					if (!BuiltInRegistries.RECIPE_TYPE.containsKey(location)) {
 						LOGGER.error("Invalid or missing recipe category: $location!")
 						return@Finder Stream.empty()
 					}
 
-					val findRecipeType = ForgeRegistries.RECIPE_TYPES.getValue(location) as RecipeType<Recipe<Container>>? ?: throw ConcurrentModificationException()
+					val findRecipeType = BuiltInRegistries.RECIPE_TYPE.get(location) as RecipeType<Recipe<RecipeInput>>? ?: throw ConcurrentModificationException()
 
 					val isCritical = data["is_critical"]?.asBoolean ?: true
 					val ignoreDamageables = data["ignore_damageables"]?.asBoolean ?: false
 					val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true
 
-					var stream = server.recipeManager.byType(findRecipeType).values.stream().filter { !it.value.isIncomplete }
+					var stream = server.recipeManager.byType(findRecipeType).stream().filter { !it.value.isIncomplete }
 
 					if (ignoreDamageables) {
 						stream = stream.filter { it.value.ingredients.stream().flatMap { it.items.stream() }.noneMatch { it.isDamageableItem } }
@@ -455,7 +459,7 @@ object MatterManager {
 						try {
 							ResolvedRecipe(
 								it.value.ingredients.stream()
-									.filter { !it.isActuallyEmpty }
+									.filter { !it.hasNoItems() }
 									.map { it.items.stream().filter { it.isNotEmpty }.map(::RecipeEntry) },
 								ImmutableStack(it.value.getResultItem(server.registryAccess())),
 								isCritical = isCritical,
@@ -481,17 +485,17 @@ object MatterManager {
 				Finder { server, data ->
 					val location = (data["recipe_type"] ?: throw JsonSyntaxException("Missing recipe type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid recipe type: ${data["recipe_type"]}")
 
-					if (!ForgeRegistries.RECIPE_TYPES.containsKey(location)) {
+					if (!BuiltInRegistries.RECIPE_TYPE.containsKey(location)) {
 						LOGGER.error("Invalid or missing recipe category: $location!")
 						return@Finder Stream.empty()
 					}
 
-					val findRecipeType = ForgeRegistries.RECIPE_TYPES.getValue(location) as RecipeType<Recipe<Container>>? ?: throw ConcurrentModificationException()
+					val findRecipeType = BuiltInRegistries.RECIPE_TYPE.get(location) as RecipeType<Recipe<CraftingInput>>? ?: throw ConcurrentModificationException()
 
 					val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true
 					val ignoreDamageables = data["ignore_damageables"]?.asBoolean ?: false
 					val isCritical = data["is_critical"]?.asBoolean ?: true
-					var stream = server.recipeManager.byType(findRecipeType).values.stream().filter { !it.value.isIncomplete }
+					var stream = server.recipeManager.byType(findRecipeType).stream().filter { !it.value.isIncomplete }
 
 					if (ignoreDamageables) {
 						stream = stream.filter { it.value.ingredients.stream().flatMap { it.items.stream() }.noneMatch { it.isDamageableItem } }
@@ -508,9 +512,10 @@ object MatterManager {
 								var width: Int
 								var height: Int
 
+								// FIXME: this gonna break after neoforge update
 								if (it.value is IShapedRecipe<*>) {
-									width = (it.value as IShapedRecipe<*>).recipeWidth
-									height = (it.value as IShapedRecipe<*>).recipeHeight
+									width = (it.value as IShapedRecipe<*>).width
+									height = (it.value as IShapedRecipe<*>).height
 								} else {
 									width = 3
 									height = 3
@@ -521,26 +526,17 @@ object MatterManager {
 									height = it.value.ingredients.size.coerceAtLeast(height)
 								}
 
-								val container = TransientCraftingContainer(object : AbstractContainerMenu(null, 0) {
-									override fun quickMoveStack(pPlayer: Player, pIndex: Int): ItemStack {
-										return ItemStack.EMPTY
-									}
-
-									override fun stillValid(pPlayer: Player): Boolean {
-										return false
-									}
-								}, width, height)
-
+								val container = MatteryCraftingContainer(width, height)
 								val realIngredients = ArrayList<ArrayList<RecipeEntry>>()
 
 								for (c in it.value.ingredients.indices) {
-									if (it.value.ingredients[c].isActuallyEmpty) {
+									if (it.value.ingredients[c].hasNoItems()) {
 										continue
 									}
 
 									for ((i, ingredient) in it.value.ingredients.withIndex()) {
 										if (i != c) {
-											container[i] = if (ingredient.isActuallyEmpty) ItemStack.EMPTY else ingredient.items.firstOrNull() ?: ItemStack.EMPTY
+											container[i] = if (ingredient.hasNoItems()) ItemStack.EMPTY else ingredient.items.firstOrNull() ?: ItemStack.EMPTY
 										}
 									}
 
@@ -548,9 +544,13 @@ object MatterManager {
 
 									for (item in it.value.ingredients[c].items) {
 										container[c] = item
+										// this gonna create enormous GC pressure but who cares lmao
+										// if you are playing post 1.12.2 you own supercomputer anyway
+										// deadass.
+										val input = CraftingInput.of(width, height, container.toList())
 
-										if (!it.value.assemble(container, server.registryAccess()).isEmpty) {
-											val residue = it.value.getRemainingItems(container)
+										if (!it.value.assemble(input, server.registryAccess()).isEmpty) {
+											val residue = it.value.getRemainingItems(input)
 
 											val thisResidue = residue[c]
 
@@ -593,17 +593,17 @@ object MatterManager {
 				Finder { server, data ->
 					val location = (data["recipe_type"] ?: throw JsonSyntaxException("Missing recipe type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid recipe type: ${data["recipe_type"]}")
 
-					if (!ForgeRegistries.RECIPE_TYPES.containsKey(location)) {
+					if (!BuiltInRegistries.RECIPE_TYPE.containsKey(location)) {
 						LOGGER.error("Invalid or missing recipe category: $location!")
 						return@Finder Stream.empty()
 					}
 
-					val findRecipeType = ForgeRegistries.RECIPE_TYPES.getValue(location) as RecipeType<Recipe<Container>>? ?: throw ConcurrentModificationException()
+					val findRecipeType = BuiltInRegistries.RECIPE_TYPE.get(location) as RecipeType<Recipe<SmithingRecipeInput>>? ?: throw ConcurrentModificationException()
 
 					val isCritical = data["is_critical"]?.asBoolean ?: true
 					val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true
 
-					var stream = server.recipeManager.byType(findRecipeType).values.stream().filter { !it.value.isIncomplete }
+					var stream = server.recipeManager.byType(findRecipeType).stream().filter { !it.value.isIncomplete }
 					stream = stream.filter { it.value is SmithingTransformRecipe }
 
 					stream.filter { it.value.getResultItem(server.registryAccess()).isNotEmpty }.map {
@@ -617,7 +617,7 @@ object MatterManager {
 
 							ResolvedRecipe(
 								ingredients.stream()
-									.filter { !it.isActuallyEmpty }
+									.filter { !it.hasNoItems() }
 									.map { it.items.stream().filter { it.isNotEmpty }.map(::RecipeEntry) },
 								ImmutableStack(it.value.getResultItem(server.registryAccess())),
 								isCritical = isCritical,
@@ -667,7 +667,7 @@ object MatterManager {
 					throw JsonParseException("Resolver type $location does not exist (in $key)")
 				}
 
-				val resolver = recipeFinders.getValue(location) ?: throw ConcurrentModificationException()
+				val resolver = recipeFinders.get(location) ?: throw ConcurrentModificationException()
 				builder.put(key, resolver to json)
 			}
 
@@ -936,7 +936,6 @@ object MatterManager {
 						 * Если у 60% <= предметов со значением материи есть тег, и он есть у всех
 						 * предметов без значения материи, то предметы без материи игнорируются
 						 */
-						val manager = ForgeRegistries.ITEMS.tags()!!
 						val tagSetsPresent = Object2IntArrayMap<TagKey<Item>>()
 						val tagSetsMissing = Object2IntArrayMap<TagKey<Item>>()
 
@@ -945,7 +944,7 @@ object MatterManager {
 								continue
 							}
 
-							val list = manager.getReverseTag(input.input.item).getOrNull()?.tagKeys?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of()
+							val list = BuiltInRegistries.ITEM.getReverseTag(input.input.item).toList()
 
 							if (input !in skips) {
 								for (tag in list)
@@ -1268,14 +1267,14 @@ object MatterManager {
 			undamagedMatterValue = matter
 		}
 
-		val matterCap = value.getCapability(MatteryCapability.MATTER).orNull()
+		val matterCap = value.getCapability(MatteryCapability.MATTER_ITEM)
 
 		if (matterCap != null) {
 			matter = MatterValue(matter.matter + matterCap.storedMatter, matter.complexity.coerceAtLeast(1.0))
 			undamagedMatterValue = MatterValue(undamagedMatterValue.matter + matterCap.storedMatter, undamagedMatterValue.complexity)
 		}
 
-		val drive = value.getCapability(MatteryCapability.DRIVE).orNull()
+		val drive = value.getCapability(MatteryCapability.CONDENSATION_DRIVE)
 
 		if (drive != null && drive.storageType == StorageStack.ITEMS) {
 			(drive as IMatteryDrive<ItemStorageStack>).stacks
@@ -1289,7 +1288,7 @@ object MatterManager {
 				}
 		}
 
-		value.getCapability(ForgeCapabilities.ITEM_HANDLER).ifPresentK {
+		value.getCapability(Capabilities.ItemHandler.ITEM)?.let {
 			it.stream().filter { !it.isEmpty }.map { get(it, level + 1, true) }.reduce(::reduce).ifPresent {
 				matter += it
 				undamagedMatterValue += it
@@ -1332,8 +1331,8 @@ object MatterManager {
 		if (value.item is IMatterItem)
 			can = (value.item as IMatterItem).canDecompose(value)
 
-		can = can && (value.getCapability(MatteryCapability.MATTER).orNull()?.storedMatter ?: Decimal.ZERO).isZero
-		can = can && (value.getCapability(MatteryCapability.DRIVE).orNull()?.storedCount ?: BigInteger.ZERO).isZero
+		can = can && (value.getCapability(MatteryCapability.MATTER_ITEM)?.storedMatter ?: Decimal.ZERO).isZero
+		can = can && (value.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.storedCount ?: BigInteger.ZERO).isZero
 
 		return can && get(value).hasMatterValue
 	}
@@ -1589,8 +1588,8 @@ object MatterManager {
 	}
 
 	private fun dumpRegistry(stack: CommandContext<CommandSourceStack>, filter: DumpFilter = DumpFilter.ALL, mod: String? = null): Int {
-		val targetFile = File(MINECRAFT_SERVER.serverDirectory, "otm/registry_dumps/${filter.name.lowercase()}_${System.currentTimeMillis() / 1_000L}.csv")
-		File(MINECRAFT_SERVER.serverDirectory, "otm/registry_dumps").mkdirs()
+		val targetFile = MINECRAFT_SERVER.serverDirectory.resolve("otm/registry_dumps/${filter.name.lowercase()}_${System.currentTimeMillis() / 1_000L}.csv").toFile()
+		MINECRAFT_SERVER.serverDirectory.resolve("otm/registry_dumps").toFile().mkdirs()
 
 		stack.source.sendSuccess({ TranslatableComponent("otm.dumping_matter_registry", targetFile.absolutePath) }, false)
 
@@ -1599,7 +1598,7 @@ object MatterManager {
 		writer.write(arrayOf("Registry ID", "Matter Value", "Complexity", "Commentary").joinToString(";", transform = ::transformQuotes))
 		writer.write("\n")
 
-		var stream = ForgeRegistries.ITEMS.entries.stream()
+		var stream = BuiltInRegistries.ITEM.entrySet().stream()
 
 		if (mod != null) {
 			stream = stream.filter { it.key.location().namespace == mod }
@@ -1668,11 +1667,11 @@ object MatterManager {
 							.executes { dumpRegistry(it, it.getArgument("mode", DumpFilter::class.java), StringArgumentType.getString(it, "mod")) }
 							.suggests { context, builder ->
 								val startingWith = builder.input.substring(builder.start).lowercase()
-								ModList.get().mods.stream()
+								ModList.get().mods.iterator()
 									.filter { it.modId.startsWith(startingWith) }
-									.filter { key -> ForgeRegistries.ITEMS.entries.stream()
-										.filter { it.key.location() == key }
-										.anyMatch { !Registry.isBlacklisted(it.value) } }
+									.filter { key -> BuiltInRegistries.ITEM.entrySet().iterator()
+										.filter { it.key.location().namespace == key.namespace }
+										.any { !Registry.isBlacklisted(it.value) } }
 									.forEach { builder.suggest(it.modId, TextComponent(it.displayName)) }
 								builder.buildFuture()
 							})
@@ -1700,14 +1699,14 @@ object MatterManager {
 
 		Resolver.resolve(server)
 
-		for (item in ForgeRegistries.ITEMS) {
+		for (item in BuiltInRegistries.ITEM) {
 			val value = compute(item).value
 
 			if (value?.hasMatterValue == true)
 				matterValues[item] = value
 		}
 
-		syncRegistry(PacketDistributor.ALL.noArg())
+		syncRegistry(PacketDistributor::sendToAllPlayers)
 	}
 
 	fun onDataPackSync(event: OnDatapackSyncEvent) {
@@ -1715,9 +1714,9 @@ object MatterManager {
 			return
 
 		if (event.player == null) {
-			syncRegistry(PacketDistributor.ALL.noArg())
+			syncRegistry(PacketDistributor::sendToAllPlayers)
 		} else {
-			syncRegistry(PacketDistributor.PLAYER.with(event.player!!))
+			syncRegistry { PacketDistributor.sendToPlayer(event.player as ServerPlayer, it) }
 		}
 	}
 
@@ -1733,7 +1732,7 @@ object MatterManager {
 
 	private val receivedPackets = ArrayList<SyncPacket>()
 
-	private fun syncRegistry(distributor: PacketDistributor.PacketTarget) {
+	private fun syncRegistry(distributor: (CustomPacketPayload) -> Unit) {
 		val time = SystemTime()
 		val stream = FastByteArrayOutputStream()
 		val data = DataOutputStream(stream)
@@ -1794,7 +1793,7 @@ object MatterManager {
 		LOGGER.debug("Encoding matter registry packet took ${time.millis}ms, (${stream.length} bytes total, $totalSize bytes compressed)")
 
 		for (chunk in chunks) {
-			GenericNetworkChannel.send(distributor, chunk)
+			distributor.invoke(chunk)
 		}
 	}
 
@@ -1897,13 +1896,20 @@ object MatterManager {
 	private const val LAST = 2
 	private const val FIRST_AND_LAST = 3
 
-	class SyncPacket(val payload: ByteArray, val length: Int, var mode: Int) : MatteryPacket {
-		override fun write(buff: FriendlyByteBuf) {
+	val SYNC_TYPE = CustomPacketPayload.Type<SyncPacket>(ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_sync"))
+	val SYNC_CODEC: StreamCodec<FriendlyByteBuf, SyncPacket> = StreamCodec.ofMember(SyncPacket::write, ::readSyncPacket)
+
+	class SyncPacket(val payload: ByteArray, val length: Int, var mode: Int) : CustomPacketPayload {
+		fun write(buff: FriendlyByteBuf) {
 			buff.writeByte(mode)
 			buff.writeBytes(payload, 0, length)
 		}
 
-		override fun play(context: MNetworkContext) {
+		override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+			return SYNC_TYPE
+		}
+
+		fun play(context: IPayloadContext) {
 			if (SERVER_IS_LIVE)
 				return // singleplayer or LAN host
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RegistryEntries.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RegistryEntries.kt
index ddab4fed1..fcc57f483 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RegistryEntries.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RegistryEntries.kt
@@ -4,6 +4,7 @@ import com.mojang.datafixers.util.Either
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.tags.TagKey
 import net.minecraft.world.item.Item
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.math.Decimal
 
 interface IRegistryEntry : IMatterValue {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt
index 6f6748ecf..12fc4eab1 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt
@@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.matter
 import com.google.common.collect.ImmutableList
 import com.mojang.datafixers.util.Either
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.tags.TagKey
@@ -21,7 +22,7 @@ class UpdateAction(
 	data class MatterFunction(val function: IMatterFunction, val value: Decimal) {
 		companion object {
 			val CODEC: Codec<MatterFunction> by lazy {
-				simpleCodec(::MatterFunction, MatterFunction::function, IMatterFunction.registry.codec, MatterFunction::value, DecimalCodec)
+				simpleCodec(::MatterFunction, MatterFunction::function, IMatterFunction.registry.byNameCodec(), MatterFunction::value, DecimalCodec)
 			}
 		}
 	}
@@ -29,7 +30,7 @@ class UpdateAction(
 	data class ComplexityFunction(val function: IMatterFunction, val value: Double)  {
 		companion object {
 			val CODEC: Codec<ComplexityFunction> by lazy {
-				simpleCodec(::ComplexityFunction, ComplexityFunction::function, IMatterFunction.registry.codec, ComplexityFunction::value, Codec.DOUBLE)
+				simpleCodec(::ComplexityFunction, ComplexityFunction::function, IMatterFunction.registry.byNameCodec(), ComplexityFunction::value, Codec.DOUBLE)
 			}
 		}
 	}
@@ -37,14 +38,14 @@ class UpdateAction(
 	data class PriorityFunction(val function: IMatterFunction, val value: Int)  {
 		companion object {
 			val CODEC: Codec<PriorityFunction> by lazy {
-				simpleCodec(::PriorityFunction, PriorityFunction::function, IMatterFunction.registry.codec, PriorityFunction::value, Codec.INT)
+				simpleCodec(::PriorityFunction, PriorityFunction::function, IMatterFunction.registry.byNameCodec(), PriorityFunction::value, Codec.INT)
 			}
 		}
 	}
 
 	companion object : Type<UpdateAction> {
-		override val codec: Codec<UpdateAction> by lazy {
-			RecordCodecBuilder.create {
+		override val codec: MapCodec<UpdateAction> by lazy {
+			RecordCodecBuilder.mapCodec {
 				it.group(
 					TargetCodec.fieldOf("id").forGetter(UpdateAction::id),
 					Codec.list(MatterFunction.CODEC).fieldOf("matterFunctions").forGetter(UpdateAction::matterFunctions),
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt
index d93732cb3..e6a145df1 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt
@@ -9,7 +9,8 @@ import net.minecraft.world.entity.ExperienceOrb
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.*
 import net.minecraft.world.item.ItemStack
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import net.neoforged.neoforge.network.PacketDistributor
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.compat.curios.curiosSlots
 import ru.dbotthepony.mc.otm.container.util.slotIterator
 import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput
@@ -17,9 +18,8 @@ import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget
 import ru.dbotthepony.mc.otm.network.ExopackCarriedPacket
 import ru.dbotthepony.mc.otm.network.ExopackMenuInitPacket
 import ru.dbotthepony.mc.otm.network.ExopackSlotPacket
-import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
 
-class ExopackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMenu(null, CONTAINER_ID, capability.ply.inventory) {
+class ExopackInventoryMenu(val capability: MatteryPlayer) : MatteryMenu(null, CONTAINER_ID, capability.ply.inventory) {
 	val craftingGrid: CraftingContainer
 	val craftingSlots: List<MatterySlot>
 
@@ -241,16 +241,16 @@ class ExopackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMen
 		}
 
 		fun sendInitialData(container: ExopackInventoryMenu, itemStacks: NonNullList<ItemStack>, carried: ItemStack, remoteDataSlots: IntArray) {
-			MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackMenuInitPacket(itemStacks, carried, container.incrementStateId()))
+			PacketDistributor.sendToPlayer(container.player as ServerPlayer, ExopackMenuInitPacket(itemStacks, carried, container.incrementStateId()))
 		}
 
 		fun sendSlotChange(container: ExopackInventoryMenu, slotId: Int, itemStack: ItemStack) {
 			if (container.slots[slotId].container != container.player.inventory || container.player.containerMenu is ExopackInventoryMenu)
-				MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackSlotPacket(slotId, itemStack, container.stateId))
+				PacketDistributor.sendToPlayer(container.player as ServerPlayer, ExopackSlotPacket(slotId, itemStack, container.stateId))
 		}
 
 		fun sendCarriedChange(container: ExopackInventoryMenu, itemStack: ItemStack) {
-			MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackCarriedPacket(itemStack, container.stateId))
+			PacketDistributor.sendToPlayer(container.player as ServerPlayer, ExopackCarriedPacket(itemStack, container.stateId))
 		}
 
 		fun sendDataChange(container: ExopackInventoryMenu, dataSlotId: Int, shortData: Int) {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ISortingSettings.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ISortingSettings.kt
index 04d414327..aae04954d 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ISortingSettings.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ISortingSettings.kt
@@ -1,9 +1,10 @@
 package ru.dbotthepony.mc.otm.menu
 
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.util.INBTSerializable
+import net.neoforged.neoforge.common.util.INBTSerializable
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.core.nbt.getBoolean
@@ -17,13 +18,13 @@ import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
 interface IBaseSortingSettings : INBTSerializable<CompoundTag?> {
 	var isAscending: Boolean
 
-	override fun serializeNBT(): CompoundTag {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
 		return CompoundTag().also {
 			it["isAscending"] = isAscending
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
 		nbt ?: return
 		isAscending = nbt.getBoolean("isAscending", true)
 	}
@@ -37,15 +38,15 @@ interface IItemStackSortingSettings : IBaseSortingSettings {
 
 	var sorting: ItemStackSorter
 
-	override fun serializeNBT(): CompoundTag {
-		return super.serializeNBT().also {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
+		return super.serializeNBT(registry).also {
 			it["sorting"] = sorting.name
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
 		nbt ?: return
-		super.deserializeNBT(nbt)
+		super.deserializeNBT(registry, nbt)
 		sorting = nbt.mapString("sorting", ItemStackSorter::valueOf, ItemStackSorter.DEFAULT)
 	}
 
@@ -85,15 +86,15 @@ interface IItemSortingSettings : IBaseSortingSettings {
 
 	var sorting: ItemSorter
 
-	override fun serializeNBT(): CompoundTag {
-		return super.serializeNBT().also {
+	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
+		return super.serializeNBT(registry).also {
 			it["sorting"] = sorting.name
 		}
 	}
 
-	override fun deserializeNBT(nbt: CompoundTag?) {
+	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
 		nbt ?: return
-		super.deserializeNBT(nbt)
+		super.deserializeNBT(registry, nbt)
 		sorting = nbt.mapString("sorting", ItemSorter::valueOf, ItemSorter.DEFAULT)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt
index 1d0727448..f19b76614 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt
@@ -10,6 +10,7 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
 import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
 import it.unimi.dsi.fastutil.objects.ReferenceArrayList
 import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.world.Container
@@ -22,8 +23,9 @@ import net.minecraft.world.inventory.MenuType
 import net.minecraft.world.inventory.Slot
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.enchantment.EnchantmentHelper.hasBindingCurse
 import net.minecraft.world.level.block.entity.BlockEntity
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
 import ru.dbotthepony.kommons.io.BinaryStringCodec
 import ru.dbotthepony.kommons.io.BooleanValueCodec
 import ru.dbotthepony.kommons.io.DelegateSyncher
@@ -35,6 +37,7 @@ import ru.dbotthepony.kommons.io.nullable
 import ru.dbotthepony.kommons.util.Delegate
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.UpgradeType
@@ -49,6 +52,7 @@ import ru.dbotthepony.mc.otm.container.ItemFilter
 import ru.dbotthepony.mc.otm.container.UpgradeContainer
 import ru.dbotthepony.mc.otm.container.computeSortedIndices
 import ru.dbotthepony.mc.otm.container.sortWithIndices
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.collect.ConditionalEnumSet
 import ru.dbotthepony.mc.otm.core.collect.ConditionalSet
 import ru.dbotthepony.mc.otm.core.immutableList
@@ -61,10 +65,7 @@ import ru.dbotthepony.mc.otm.core.util.computedDecimal
 import ru.dbotthepony.mc.otm.core.util.computedItem
 import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput
 import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
-import ru.dbotthepony.mc.otm.network.MNetworkContext
-import ru.dbotthepony.mc.otm.network.MatteryPacket
-import ru.dbotthepony.mc.otm.network.MenuFieldPacket
-import ru.dbotthepony.mc.otm.network.MenuNetworkChannel
+import ru.dbotthepony.mc.otm.network.MenuDataPacket
 import ru.dbotthepony.mc.otm.network.SetCarriedPacket
 import java.io.DataInputStream
 import java.io.DataOutputStream
@@ -114,22 +115,38 @@ abstract class MatteryMenu(
 
 	private val playerInputs = ArrayList<PlayerInput<*>>()
 
-	class PlayerInputPacket(val containerId: Int, val inputId: Int, val payload: ByteArray) : MatteryPacket {
+	class PlayerInputPacket(val containerId: Int, val inputId: Int, val payload: ByteArray) : CustomPacketPayload {
 		constructor(buff: FriendlyByteBuf) : this(buff.readVarInt(), buff.readVarInt(), ByteArray(buff.readableBytes()).also { buff.readBytes(it) })
 
-		override fun write(buff: FriendlyByteBuf) {
+		fun write(buff: FriendlyByteBuf) {
 			buff.writeVarInt(containerId)
 			buff.writeVarInt(inputId)
 			buff.writeBytes(payload)
 		}
 
-		override fun play(context: MNetworkContext) {
-			val menu = context.sender?.containerMenu as? MatteryMenu ?: return
-			if (menu.containerId != containerId || !menu.stillValid(context.sender)) return
+		fun play(context: IPayloadContext) {
+			val menu = context.player().containerMenu as? MatteryMenu ?: return
+			if (menu.containerId != containerId || !menu.stillValid(context.player())) return
 			val input = menu.playerInputs.getOrNull(inputId) ?: return
-			if (!input.test(context.sender)) return
+			if (!input.test(context.player())) return
 			input.invoke(input.codec.read(DataInputStream(FastByteArrayInputStream(payload))))
 		}
+
+		override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+			return TYPE
+		}
+
+		companion object {
+			val TYPE = CustomPacketPayload.Type<PlayerInputPacket>(
+				ResourceLocation(
+					OverdriveThatMatters.MOD_ID,
+					"menu_input"
+				)
+			)
+
+			val CODEC: net.minecraft.network.codec.StreamCodec<FriendlyByteBuf, PlayerInputPacket> =
+				net.minecraft.network.codec.StreamCodec.ofMember(PlayerInputPacket::write, ::PlayerInputPacket)
+		}
 	}
 
 	/**
@@ -163,7 +180,7 @@ abstract class MatteryMenu(
 			if (test(minecraft.player as Player?)) {
 				val stream = FastByteArrayOutputStream()
 				codec.write(DataOutputStream(stream), value)
-				MenuNetworkChannel.sendToServer(PlayerInputPacket(containerId, id, stream.array.copyOfRange(0, stream.length)))
+				PacketDistributor.sendToServer(PlayerInputPacket(containerId, id, stream.array.copyOfRange(0, stream.length)))
 			}
 		}
 
@@ -283,7 +300,7 @@ abstract class MatteryMenu(
 			private set
 
 		init {
-			val mattery = player.matteryPlayer!!
+			val mattery = player.matteryPlayer
 
 			if (addFilter) {
 				val mContainer = container as IMatteryContainer
@@ -328,7 +345,7 @@ abstract class MatteryMenu(
 
 	protected fun addInventorySlots(autoFrame: Boolean = !player.isSpectator) {
 		check(_playerInventorySlots.isEmpty()) { "Already created inventory slots" }
-		val mattery = player.matteryPlayer ?: return
+		val mattery = player.matteryPlayer
 
 		autoCreateInventoryFrame = autoFrame
 
@@ -381,14 +398,14 @@ abstract class MatteryMenu(
 
 	}
 
-	protected fun matteryBroadcast() {
+	private fun matteryBroadcast() {
 		beforeBroadcast()
 
 		mSynchronizer.observe()
 		val payload = synchronizerRemote.write()
 
 		if (payload != null) {
-			MenuNetworkChannel.send(player, MenuFieldPacket(containerId, payload))
+			PacketDistributor.sendToPlayer(player as ServerPlayer, MenuDataPacket(containerId, payload))
 		}
 
 		broadcastOnce = true
@@ -419,7 +436,7 @@ abstract class MatteryMenu(
 
 	fun syncCarried() {
 		setRemoteCarried(carried.copy())
-		MenuNetworkChannel.send(player as ServerPlayer, SetCarriedPacket(carried))
+		PacketDistributor.sendToPlayer(player as ServerPlayer, SetCarriedPacket(carried))
 	}
 
 	fun syncCarried(stack: ItemStack) {
@@ -633,7 +650,7 @@ abstract class MatteryMenu(
 
 				val limit = slot.getMaxStackSize(copy)
 
-				if (limit > slot.item.count && slot.mayPlace(item) && ItemStack.isSameItemSameTags(slot.item, copy)) {
+				if (limit > slot.item.count && slot.mayPlace(item) && ItemStack.isSameItemSameComponents(slot.item, copy)) {
 					val newCount = (slot.item.count + copy.count).coerceAtMost(limit)
 					val diff = newCount - slot.item.count
 					copy.count -= diff
@@ -803,7 +820,7 @@ abstract class MatteryMenu(
 					}
 
 					override fun mayPlace(itemStack: ItemStack): Boolean {
-						return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.UPGRADE).map { it.upgradeTypes.any { allowedTypes[it]!!.asBoolean } }.orElse(false)
+						return super.mayPlace(itemStack) && (itemStack.getCapability(MatteryCapability.UPGRADE)?.let { it.upgradeTypes.any { allowedTypes[it]!!.asBoolean } } == true)
 					}
 				}
 			}.also { for (i in it.indices.reversed()) addStorageSlot(it[i], prepend = true, condition = isOpen) },
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt
index c7995158a..89c463723 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt
@@ -7,7 +7,7 @@ import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.Slot
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.kommons.util.Delegate
 import ru.dbotthepony.mc.otm.capability.FlowDirection
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
@@ -131,7 +131,7 @@ open class ChargeSlot(container: Container, index: Int, x: Int = 0, y: Int = 0)
 
 open class EnergyContainerInputSlot(container: Container, index: Int, x: Int = 0, y: Int = 0, val direction: FlowDirection = FlowDirection.BI_DIRECTIONAL) : MatterySlot(container, index, x, y) {
 	override fun mayPlace(itemStack: ItemStack): Boolean {
-		return super.mayPlace(itemStack) && itemStack.getCapability(ForgeCapabilities.ENERGY).map { direction.test(FlowDirection.of(it.canReceive(), it.canExtract())) }.orElse(false)
+		return super.mayPlace(itemStack) && (itemStack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { direction.test(FlowDirection.of(it.canReceive(), it.canExtract())) } ?: false)
 	}
 }
 
@@ -143,20 +143,19 @@ open class MatterContainerInputSlot(
 	val direction: FlowDirection = FlowDirection.BI_DIRECTIONAL
 ) : MatterySlot(container, index, x, y) {
 	override fun mayPlace(itemStack: ItemStack): Boolean {
-		val handler = itemStack.getCapability(MatteryCapability.MATTER).resolve()
-		if (handler.isEmpty) return false
-		return super.mayPlace(itemStack) && this.direction.test(handler.get().matterFlow)
+		val handler = itemStack.getCapability(MatteryCapability.MATTER_ITEM)
+		return handler != null && super.mayPlace(itemStack) && this.direction.test(handler.matterFlow)
 	}
 }
 
 open class PatternSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) {
 	override fun mayPlace(itemStack: ItemStack): Boolean {
-		return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.PATTERN).isPresent
+		return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.PATTERN_ITEM) != null
 	}
 }
 
 open class DriveSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) {
 	override fun mayPlace(itemStack: ItemStack): Boolean {
-		return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.DRIVE).isPresent
+		return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.CONDENSATION_DRIVE) != null
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt
index f79daf008..0a0f43408 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt
@@ -6,13 +6,18 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
 import net.minecraft.client.Minecraft
 import net.minecraft.client.gui.screens.Screen
 import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.inventory.ClickAction
 import net.minecraft.world.inventory.ClickType
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.network.PacketDistributor
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.client.minecraft
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.addSorted
 import ru.dbotthepony.mc.otm.core.isNotEmpty
 import ru.dbotthepony.mc.otm.core.map
@@ -34,15 +39,15 @@ interface INetworkedItemViewProvider {
 private fun all(stack: ItemStack): Int = stack.maxStackSize.coerceAtMost(stack.count)
 private fun half(stack: ItemStack): Int = (stack.maxStackSize.coerceAtMost(stack.count) / 2).coerceAtLeast(1)
 
-data class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: ClickAction) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
+data class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: ClickAction) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeInt(stackID)
 		buff.writeEnum(type)
 		buff.writeEnum(action)
 	}
 
-	override fun play(context: MNetworkContext) {
-		val sender = context.sender ?: return
+	fun play(context: IPayloadContext) {
+		val sender = context.player() as ServerPlayer
 
 		if (!sender.isSpectator) {
 			sender.resetLastActionTime()
@@ -50,15 +55,29 @@ data class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val act
 		}
 	}
 
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
 	companion object {
+		val TYPE = CustomPacketPayload.Type<ItemViewInteractPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"item_view_interaction"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, ItemViewInteractPacket> =
+			StreamCodec.ofMember(ItemViewInteractPacket::write, ::read)
+
 		fun read(buff: FriendlyByteBuf): ItemViewInteractPacket {
 			return ItemViewInteractPacket(buff.readInt(), buff.readEnum(ClickType::class.java), buff.readEnum(ClickAction::class.java))
 		}
 	}
 }
 
-abstract class NetworkedItemViewPacket : MatteryPacket {
-	final override fun play(context: MNetworkContext) {
+abstract class NetworkedItemViewPacket : CustomPacketPayload {
+	fun play(context: IPayloadContext) {
 		val get = Minecraft.getInstance().player?.containerMenu ?: return
 		val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No NetworkedItemView is present in currently open menu")
 		action(view)
@@ -68,7 +87,19 @@ abstract class NetworkedItemViewPacket : MatteryPacket {
 }
 
 object ClearItemViewPacket : NetworkedItemViewPacket() {
-	override fun write(buff: FriendlyByteBuf) {}
+	val TYPE = CustomPacketPayload.Type<ClearItemViewPacket>(
+		ResourceLocation(
+			OverdriveThatMatters.MOD_ID,
+			"clear_item_view"
+		)
+	)
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	val CODEC: StreamCodec<FriendlyByteBuf, ClearItemViewPacket> =
+		StreamCodec.ofMember({ _, _ -> }, { ClearItemViewPacket })
 
 	override fun action(view: NetworkedItemView) {
 		view.clear()
@@ -76,7 +107,7 @@ object ClearItemViewPacket : NetworkedItemViewPacket() {
 }
 
 class StackAddPacket(val stackId: Int, val stack: ItemStorageStack) : NetworkedItemViewPacket() {
-	override fun write(buff: FriendlyByteBuf) {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeInt(stackId)
 		stack.write(buff)
 	}
@@ -91,7 +122,21 @@ class StackAddPacket(val stackId: Int, val stack: ItemStorageStack) : NetworkedI
 		view.sortedView.addSorted(tuple, view.sorter.map(NetworkedItemView.Tuple::stack))
 	}
 
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
 	companion object {
+		val TYPE = CustomPacketPayload.Type<StackAddPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"item_view_addition"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, StackAddPacket> =
+			StreamCodec.ofMember(StackAddPacket::write, ::read)
+
 		fun read(buffer: FriendlyByteBuf): StackAddPacket {
 			val id = buffer.readInt()
 			val item = StorageStack.ITEMS.read(buffer)
@@ -101,7 +146,7 @@ class StackAddPacket(val stackId: Int, val stack: ItemStorageStack) : NetworkedI
 }
 
 class StackChangePacket(val stackId: Int, val newCount: BigInteger) : NetworkedItemViewPacket() {
-	override fun write(buff: FriendlyByteBuf) {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeInt(stackId)
 		buff.writeBigInteger(newCount)
 	}
@@ -112,7 +157,21 @@ class StackChangePacket(val stackId: Int, val newCount: BigInteger) : NetworkedI
 		view.resort()
 	}
 
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
 	companion object {
+		val TYPE = CustomPacketPayload.Type<StackChangePacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"item_view_change"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, StackChangePacket> =
+			StreamCodec.ofMember(StackChangePacket::write, ::read)
+
 		fun read(buffer: FriendlyByteBuf): StackChangePacket {
 			val stackID = buffer.readInt()
 			val newCount = buffer.readBigInteger()
@@ -122,7 +181,7 @@ class StackChangePacket(val stackId: Int, val newCount: BigInteger) : NetworkedI
 }
 
 class StackRemovePacket(val stackId: Int) : NetworkedItemViewPacket() {
-	override fun write(buff: FriendlyByteBuf) {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeInt(stackId)
 	}
 
@@ -132,7 +191,21 @@ class StackRemovePacket(val stackId: Int) : NetworkedItemViewPacket() {
 		view.resort()
 	}
 
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
 	companion object {
+		val TYPE = CustomPacketPayload.Type<StackRemovePacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"item_view_remove"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, StackRemovePacket> =
+			StreamCodec.ofMember(StackRemovePacket::write, ::read)
+
 		fun read(buffer: FriendlyByteBuf): StackRemovePacket {
 			val stackID = buffer.readInt()
 			return StackRemovePacket(stackID)
@@ -167,7 +240,7 @@ class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val isRemote: Bo
 
 	private var nextItemID = 0
 	private val uuid2tuple = Object2ObjectOpenHashMap<UUID, Tuple>()
-	private val networkBacklog = ArrayList<Any>()
+	private val networkBacklog = ArrayList<CustomPacketPayload>()
 
 	operator fun get(id: Int): Tuple? = id2tuple[id]
 
@@ -189,7 +262,7 @@ class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val isRemote: Bo
 	fun mouseClick(index: Int, mouseButton: Int) {
 		if (minecraft.player?.isSpectator == true) return
 
-		MenuNetworkChannel.sendToServer(ItemViewInteractPacket(
+		PacketDistributor.sendToServer(ItemViewInteractPacket(
 			sortedView.getOrNull(index)?.networkId ?: -1,
 			if (mouseButton == InputConstants.MOUSE_BUTTON_MIDDLE) ClickType.CLONE else if (Screen.hasShiftDown()) ClickType.QUICK_MOVE else ClickType.PICKUP,
 			if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) ClickAction.PRIMARY else ClickAction.SECONDARY
@@ -223,7 +296,7 @@ class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val isRemote: Bo
 		network { StackRemovePacket(get.networkId) }
 	}
 
-	private inline fun network(fn: () -> Any) {
+	private inline fun network(fn: () -> CustomPacketPayload) {
 		if (!isRemote) {
 			networkBacklog.add(fn())
 		}
@@ -242,10 +315,9 @@ class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val isRemote: Bo
 
 	fun network() {
 		check(!isRemote) { "Not a server" }
-		val consumer = PacketDistributor.PLAYER.with(ply as ServerPlayer)
 
 		for (packet in networkBacklog) {
-			MenuNetworkChannel.send(consumer, packet)
+			PacketDistributor.sendToPlayer(ply as ServerPlayer, packet)
 		}
 
 		networkBacklog.clear()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt
index 401057a94..d3b3795f9 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt
@@ -3,8 +3,8 @@ package ru.dbotthepony.mc.otm.menu.decorative
 import net.minecraft.world.SimpleContainer
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting
 import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
 import ru.dbotthepony.mc.otm.capability.isNotEmpty
@@ -27,25 +27,22 @@ class FluidTankMenu(containerId: Int, inventory: Inventory, tile: FluidTankBlock
 	val drainInput = object : MatterySlot(tile?.drainInput ?: SimpleContainer(1), 0) {
 		override fun mayPlace(itemStack: ItemStack): Boolean {
 			return super.mayPlace(itemStack) &&
-				itemStack
-				.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM)
-				.map { it.isNotEmpty }
-				.orElse(false)
+				(itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.isNotEmpty ?: false)
 		}
 	}
 
 	val fillInput = object : MatterySlot(tile?.fillInput ?: SimpleContainer(1), 0) {
 		override fun mayPlace(itemStack: ItemStack): Boolean {
 			return super.mayPlace(itemStack) &&
-				(if (itemStack.count <= 1) itemStack else itemStack.copyWithCount(1))
-				.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM)
-				.map {
-					if (fluid.fluid.isEmpty)
-						it.tanks > 0
-					else
-						it.fill(fluid.fluid, IFluidHandler.FluidAction.SIMULATE) > 0
-				}
-				.orElse(false)
+				(if (itemStack.count <= 1) itemStack
+				else itemStack.copyWithCount(1))
+					.getCapability(Capabilities.FluidHandler.ITEM)
+					?.let {
+						if (fluid.fluid.isEmpty)
+							it.tanks > 0
+						else
+							it.fill(fluid.fluid, IFluidHandler.FluidAction.SIMULATE) > 0
+					} == true
 		}
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt
index ed6279d1a..732ca65d0 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt
@@ -6,9 +6,9 @@ import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.*
 import net.minecraft.world.item.crafting.RecipeHolder
 import net.minecraft.world.level.material.Fluids
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.kommons.io.nullable
 import ru.dbotthepony.kommons.util.Listenable
 import ru.dbotthepony.kommons.util.ListenableDelegate
@@ -61,7 +61,7 @@ class PainterMenu(
 				return super.mayPlace(itemStack)
 			}
 
-			return super.mayPlace(itemStack) && inventory.player.level().recipeManager.byType(MRecipes.PAINTER).values.any { it.value.matches(itemStack) }
+			return super.mayPlace(itemStack) && inventory.player.level().recipeManager.byType(MRecipes.PAINTER).any { it.value.matches(itemStack) }
 		}
 	}
 
@@ -76,9 +76,9 @@ class PainterMenu(
 				lastRecipe?.value?.dyes?.let { tile?.takeDyes(it) }
 
 				if (isBulk.value) {
-					val found = player.matteryPlayer!!.inventoryAndExopack
+					val found = player.matteryPlayer.inventoryAndExopack
 						.slotIterator()
-						.filter { !it.isForbiddenForAutomation && ItemStack.isSameItemSameTags(it.item, inputSlot.item) }
+						.filter { !it.isForbiddenForAutomation && ItemStack.isSameItemSameComponents(it.item, inputSlot.item) }
 						.maybe()
 
 					if (found != null) {
@@ -103,9 +103,9 @@ class PainterMenu(
 	val dyeSlot = object : MatterySlot(tile?.dyeInput ?: SimpleContainer(1), 0) {
 		override fun mayPlace(itemStack: ItemStack): Boolean {
 			return super.mayPlace(itemStack) && ((
-				itemStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).resolve().map {
+				itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.let {
 					dyeStoredDirect[null]!! < PainterBlockEntity.MAX_WATER_STORAGE && it.drain(FluidStack(Fluids.WATER, PainterBlockEntity.MAX_WATER_STORAGE - dyeStoredDirect[null]!!), IFluidHandler.FluidAction.SIMULATE).isNotEmpty
-				}.orElse(false)
+				} ?: false
 			) || (DyeColor.getColor(itemStack)?.let { dyeStoredDirect[it]!! + PainterBlockEntity.HUE_PER_ITEM <= PainterBlockEntity.MAX_STORAGE } ?: false))
 		}
 	}
@@ -132,7 +132,7 @@ class PainterMenu(
 
 	private fun rescan() {
 		possibleRecipes.clear()
-		possibleRecipes.addAll(inventory.player.level().recipeManager.byType(MRecipes.PAINTER).values.iterator().filter { it.value.matches(inputContainer[0]) })
+		possibleRecipes.addAll(inventory.player.level().recipeManager.byType(MRecipes.PAINTER).iterator().filter { it.value.matches(inputContainer[0]) })
 
 		listeners.run()
 		if (tile !is PainterBlockEntity) return
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt
index 0e0ad51f7..016d0824e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt
@@ -7,21 +7,19 @@ import net.minecraft.world.item.ItemStack
 import ru.dbotthepony.mc.otm.core.immutableList
 import ru.dbotthepony.mc.otm.block.entity.matter.MatterBottlerBlockEntity
 import ru.dbotthepony.mc.otm.capability.MatteryCapability
-import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage
 import ru.dbotthepony.mc.otm.capability.matter.canExtractMatter
 import ru.dbotthepony.mc.otm.capability.matter.canReceiveMatter
 import ru.dbotthepony.mc.otm.container.CombinedContainer
 import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
 import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget
 import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget
-import ru.dbotthepony.mc.otm.core.orNull
 import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
 import ru.dbotthepony.mc.otm.menu.MatterySlot
 import ru.dbotthepony.mc.otm.menu.makeSlots
 import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
 import ru.dbotthepony.mc.otm.registry.MMenus
 
-class MatterBottlerMenu @JvmOverloads constructor(
+class MatterBottlerMenu(
 	p_38852_: Int,
 	inventory: Inventory,
 	tile: MatterBottlerBlockEntity? = null
@@ -33,7 +31,7 @@ class MatterBottlerMenu @JvmOverloads constructor(
 	val storageSlots: ImmutableList<MatterySlot> = makeSlots(CombinedContainer(tile?.bottling ?: SimpleContainer(3), tile?.unbottling ?: SimpleContainer(3))) { it, index ->
 		object : MatterySlot(it, index) {
 			override fun mayPlace(itemStack: ItemStack): Boolean {
-				val cap = itemStack.getCapability(MatteryCapability.MATTER).orNull() ?: return false
+				val cap = itemStack.getCapability(MatteryCapability.MATTER_ITEM) ?: return false
 
 				if (workFlow.value) {
 					return index < 3 && cap.canReceiveMatter
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt
index 7b3884ee5..e3ebd7ebd 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt
@@ -6,6 +6,7 @@ import net.minecraft.world.SimpleContainer
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.crafting.CraftingInput
 import ru.dbotthepony.mc.otm.block.entity.matter.MatterEntanglerBlockEntity
 import ru.dbotthepony.mc.otm.container.MatteryContainer
 import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer
@@ -45,13 +46,14 @@ class MatterEntanglerMenu(
 	}) { it, i ->
 		object : MatterySlot(it, i) {
 			override fun mayPlace(itemStack: ItemStack): Boolean {
-				val shadow = ShadowCraftingContainer.shadow(it, i, itemStack)
+				val list = it.toList()
+				list[i] = itemStack
+				val shadow = CraftingInput.of(3, 3, list)
 				val level = player.level()
 
 				return super.mayPlace(itemStack) && (level ?: return false)
 					.recipeManager
 					.byType(MRecipes.MATTER_ENTANGLER)
-					.values
 					.any { it.value.preemptivelyMatches(shadow, level) }
 			}
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt
index 451b0549b..c06f06170 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt
@@ -1,17 +1,23 @@
 package ru.dbotthepony.mc.otm.menu.matter
 
 import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.Item
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.kommons.io.NullValueCodec
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.block.entity.matter.MatterPanelBlockEntity
 import ru.dbotthepony.mc.otm.capability.matter.*
 import ru.dbotthepony.mc.otm.client.minecraft
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.addSorted
 import ru.dbotthepony.mc.otm.core.map
 import ru.dbotthepony.mc.otm.core.math.Decimal
@@ -27,30 +33,44 @@ import ru.dbotthepony.mc.otm.registry.MMenus
 import java.util.*
 import java.util.function.Predicate
 
-class CancelTaskPacket(val id: UUID) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
+class CancelTaskPacket(val id: UUID) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeUUID(id)
 	}
 
-	override fun play(context: MNetworkContext) {
-		val sender = context.sender!!
+	fun play(context: IPayloadContext) {
+		val sender = context.player() as ServerPlayer
 		(sender.containerMenu as? MatterPanelMenu)?.receiveTaskCancel(sender, id)
 	}
 
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
 	companion object {
+		val TYPE = CustomPacketPayload.Type<CancelTaskPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"cancel_replication_task"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, CancelTaskPacket> =
+			StreamCodec.ofMember(CancelTaskPacket::write, ::read)
+
 		fun read(buff: FriendlyByteBuf): CancelTaskPacket {
 			return CancelTaskPacket(buff.readUUID())
 		}
 	}
 }
 
-class PatternsChangePacket(val isUpdate: Boolean, val patterns: Collection<PatternState>) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
+class PatternsChangePacket(val isUpdate: Boolean, val patterns: Collection<PatternState>) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeBoolean(isUpdate)
 		buff.writeCollection(patterns, PatternState::write)
 	}
 
-	override fun play(context: MNetworkContext) {
+	fun play(context: IPayloadContext) {
 		val menu = minecraft.player?.containerMenu as? MatterPanelMenu ?: return
 
 		if (isUpdate) {
@@ -60,20 +80,34 @@ class PatternsChangePacket(val isUpdate: Boolean, val patterns: Collection<Patte
 		}
 	}
 
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
 	companion object {
+		val TYPE = CustomPacketPayload.Type<PatternsChangePacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"pattern_change_data"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, PatternsChangePacket> =
+			StreamCodec.ofMember(PatternsChangePacket::write, ::read)
+
 		fun read(buff: FriendlyByteBuf): PatternsChangePacket {
 			return PatternsChangePacket(buff.readBoolean(), buff.readCollection(::ArrayList, PatternState::read))
 		}
 	}
 }
 
-class TasksChangePacket(val isUpdate: Boolean, val tasks: Collection<ReplicationTask>) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
+class TasksChangePacket(val isUpdate: Boolean, val tasks: Collection<ReplicationTask>) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeBoolean(isUpdate)
 		buff.writeCollection(tasks, ReplicationTask::write)
 	}
 
-	override fun play(context: MNetworkContext) {
+	fun play(context: IPayloadContext) {
 		val menu = minecraft.player?.containerMenu as? MatterPanelMenu ?: return
 
 		if (isUpdate) {
@@ -83,29 +117,55 @@ class TasksChangePacket(val isUpdate: Boolean, val tasks: Collection<Replication
 		}
 	}
 
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
 	companion object {
+		val TYPE = CustomPacketPayload.Type<TasksChangePacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"task_change_data"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, TasksChangePacket> =
+			StreamCodec.ofMember(TasksChangePacket::write, ::read)
+
 		fun read(buff: FriendlyByteBuf): TasksChangePacket {
 			return TasksChangePacket(buff.readBoolean(), buff.readCollection(::ArrayList, ReplicationTask::read))
 		}
 	}
 }
 
-class ReplicationRequestPacket(val id: UUID, amount: Int) : MatteryPacket {
+class ReplicationRequestPacket(val id: UUID, amount: Int) : CustomPacketPayload {
 	val amount = amount.coerceAtLeast(1).coerceAtMost(99999)
 
-	override fun write(buff: FriendlyByteBuf) {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeUUID(id)
 		buff.writeInt(amount)
 	}
 
-	override fun play(context: MNetworkContext) {
-		val sender = context.sender ?: return
-		val menu = sender.containerMenu as? MatterPanelMenu ?: return
+	fun play(context: IPayloadContext) {
+		val sender = context.player() as ServerPlayer
+		(sender.containerMenu as? MatterPanelMenu)?.requestReplication(sender, id, amount)
+	}
 
-		menu.requestReplication(sender, id, amount)
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
 	}
 
 	companion object {
+		val TYPE = CustomPacketPayload.Type<ReplicationRequestPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"replication_request"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, ReplicationRequestPacket> =
+			StreamCodec.ofMember(ReplicationRequestPacket::write, ::read)
+
 		fun read(buff: FriendlyByteBuf): ReplicationRequestPacket {
 			return ReplicationRequestPacket(buff.readUUID(), buff.readInt())
 		}
@@ -284,7 +344,7 @@ class MatterPanelMenu(
 
 	fun requestTaskCancel(id: UUID) {
 		if (minecraft.player?.isSpectator != true)
-			MenuNetworkChannel.sendToServer(CancelTaskPacket(id))
+			PacketDistributor.sendToServer(CancelTaskPacket(id))
 	}
 
 	override fun onPatternAdded(state: PatternState) {
@@ -321,8 +381,8 @@ class MatterPanelMenu(
 		if (!initialSend) fullPatternBroadcast()
 	}
 
-	private fun sendNetwork(packet: Any) {
-		MenuNetworkChannel.send(inventory.player, packet)
+	private fun sendNetwork(packet: CustomPacketPayload) {
+		PacketDistributor.sendToPlayer(inventory.player as ServerPlayer, packet)
 	}
 
 	fun fullPatternBroadcast() {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt
index ff2b8e2a7..f45713220 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt
@@ -4,7 +4,7 @@ import net.minecraft.world.SimpleContainer
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.kommons.util.Delegate
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
@@ -13,7 +13,6 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive
 import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
 import ru.dbotthepony.mc.otm.container.ItemFilter
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.immutableList
 import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
 import ru.dbotthepony.mc.otm.core.util.computedItem
@@ -40,7 +39,7 @@ class DriveViewerMenu(
 
 	val driveSlot = object : MatterySlot(tile?.container ?: SimpleContainer(1), 0) {
 		override fun mayPlace(itemStack: ItemStack): Boolean {
-			return itemStack.getCapability(MatteryCapability.DRIVE).isPresent
+			return itemStack.getCapability(MatteryCapability.CONDENSATION_DRIVE) != null
 		}
 	}
 
@@ -98,7 +97,7 @@ class DriveViewerMenu(
 
 	val isWhitelist = BooleanInputWithFeedback(this, make { Delegate.Of(it::isWhitelist) }).also { it.filter { drivePresent } }
 	val matchTag = BooleanInputWithFeedback(this, make { Delegate.Of(it::matchTag) }).also { it.filter { drivePresent } }
-	val matchNBT = BooleanInputWithFeedback(this, make { Delegate.Of(it::matchNBT) }).also { it.filter { drivePresent } }
+	val matchNBT = BooleanInputWithFeedback(this, make { Delegate.Of(it::matchComponents) }).also { it.filter { drivePresent } }
 
 	override fun broadcastChanges() {
 		super.broadcastChanges()
@@ -110,7 +109,7 @@ class DriveViewerMenu(
 			lastDrive = null
 
 			if (!itemStack.isEmpty) {
-				itemStack.getCapability(MatteryCapability.DRIVE).ifPresentK {
+				itemStack.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let {
 					if (it.storageType == StorageStack.ITEMS) {
 						lastDrive = it as IMatteryDrive<ItemStorageStack>
 					}
@@ -139,7 +138,7 @@ class DriveViewerMenu(
 		val slot = slots[slotIndex]
 		val item = slot.item
 
-		if (item.isEmpty || item.getCapability(MatteryCapability.DRIVE).isPresent || item.getCapability(ForgeCapabilities.ENERGY).isPresent)
+		if (item.isEmpty || item.getCapability(MatteryCapability.CONDENSATION_DRIVE) != null || item.getCapability(Capabilities.EnergyStorage.ITEM) != null)
 			return super.quickMoveStack(ply, slotIndex)
 
 		val powered = powered
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt
index f0573601a..4ab3901f6 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt
@@ -13,7 +13,7 @@ class StorageImporterExporterMenu(
 ) : MatteryPoweredMenu(MMenus.STORAGE_IMPORTER_EXPORTER, containerId, inventory, tile)  {
 	val filterSlots = addFilterSlots(tile?.filter, AbstractStorageImportExport.MAX_FILTERS)
 	val isWhitelist = BooleanInputWithFeedback(this, tile?.let { it.filter::isWhitelist })
-	val matchNBT = BooleanInputWithFeedback(this, tile?.let { it.filter::matchNBT })
+	val matchNBT = BooleanInputWithFeedback(this, tile?.let { it.filter::matchComponents })
 	val matchTag = BooleanInputWithFeedback(this, tile?.let { it.filter::matchTag })
 	val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget)
 	val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt
index 301f24d27..c919c8d9c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt
@@ -6,9 +6,9 @@ import net.minecraft.world.SimpleContainer
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.entity.player.Player
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.mc.otm.block.entity.tech.AndroidStationBlockEntity
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
 import ru.dbotthepony.mc.otm.config.MachinesConfig
 import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
@@ -23,7 +23,7 @@ class AndroidStationMenu @JvmOverloads constructor(
 	inventory: Inventory,
 	tile: AndroidStationBlockEntity? = null
 ) : MatteryPoweredMenu(MMenus.ANDROID_STATION, containerID, inventory, tile) {
-	private fun container(target: (MatteryPlayerCapability) -> KMutableProperty0<ItemStack>): Container {
+	private fun container(target: (MatteryPlayer) -> KMutableProperty0<ItemStack>): Container {
 		if (player is ServerPlayer)
 			return PartContainer(target.invoke(player.matteryPlayer ?: throw NullPointerException("OTM player capability is missing")))
 		else
@@ -109,7 +109,7 @@ class AndroidStationMenu @JvmOverloads constructor(
 	}
 
 	val androidBattery: MatterySlot = AndroidSlot(container { it.androidEnergy::item }) {
-		it.getCapability(ForgeCapabilities.ENERGY).isPresent
+		it.getCapability(Capabilities.EnergyStorage.ITEM) != null
 	}
 
 	val equipment = makeEquipmentSlots()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt
index 523d7ddd2..1ca7495a7 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt
@@ -3,13 +3,11 @@ package ru.dbotthepony.mc.otm.menu.tech
 import net.minecraft.world.SimpleContainer
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.common.ForgeHooks
-import net.minecraftforge.common.capabilities.ForgeCapabilities
+import net.neoforged.neoforge.capabilities.Capabilities
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.kommons.util.setValue
 import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting
 import ru.dbotthepony.mc.otm.block.entity.tech.ChemicalGeneratorBlockEntity
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.menu.MatteryMenu
 import ru.dbotthepony.mc.otm.menu.MatterySlot
 import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
@@ -34,7 +32,7 @@ class ChemicalGeneratorMenu @JvmOverloads constructor(id: Int, inv: Inventory, t
 
 	val fuelSlot = object : MatterySlot(tile?.fuelContainer ?: SimpleContainer(1), 0) {
 		override fun mayPlace(itemStack: ItemStack): Boolean {
-			return ForgeHooks.getBurnTime(itemStack, null) > 0
+			return itemStack.getBurnTime(null) > 0
 		}
 	}
 
@@ -46,11 +44,7 @@ class ChemicalGeneratorMenu @JvmOverloads constructor(id: Int, inv: Inventory, t
 
 	val batterySlot = object : MatterySlot(tile?.batteryContainer ?: SimpleContainer(1), 0) {
 		override fun mayPlace(itemStack: ItemStack): Boolean {
-			itemStack.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
-				return it.canReceive()
-			}
-
-			return false
+			return itemStack.getCapability(Capabilities.EnergyStorage.ITEM)?.canReceive() ?: false
 		}
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt
index 06fb5781f..3a7eadedf 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt
@@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.menu.tech
 import mezz.jei.api.constants.RecipeTypes
 import mezz.jei.api.recipe.RecipeType
 import net.minecraft.server.level.ServerPlayer
+import net.minecraft.world.SimpleContainer
 import net.minecraft.world.entity.player.Inventory
 import net.minecraft.world.inventory.MenuType
 import ru.dbotthepony.mc.otm.block.entity.tech.AbstractPoweredFurnaceBlockEntity
@@ -30,8 +31,8 @@ class PoweredFurnaceMenu(
 	inventory: Inventory,
 	tile: AbstractPoweredFurnaceBlockEntity<*, *>? = null
 ) : AbstractProcessingMachineMenu(type, containerID, inventory, tile) {
-	val inputSlots = makeSlots(tile?.inputs, 2, ::MatterySlot)
-	val outputSlots = makeSlots(tile?.outputs, 2) { c, s -> OutputSlot(c, s) { tile?.experience?.popExperience(player as ServerPlayer) } }
+	val inputSlots = makeSlots(tile?.inputs ?: SimpleContainer(2), ::MatterySlot)
+	val outputSlots = makeSlots(tile?.outputs ?: SimpleContainer(2)) { c, s -> OutputSlot(c, s) { tile?.experience?.popExperience(player as ServerPlayer) } }
 	val progressGauge = immutableList(2) { ProgressGaugeWidget(this, tile?.jobEventLoops?.get(it)) }
 
 	override val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt
index a6cde9eb0..1b9391bdd 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt
@@ -1,7 +1,7 @@
 package ru.dbotthepony.mc.otm.menu.widget
 
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.kommons.io.DelegateSyncher
 import ru.dbotthepony.kommons.util.getValue
 import ru.dbotthepony.mc.otm.container.get
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/AndroidPackets.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/AndroidPackets.kt
new file mode 100644
index 000000000..136c4fc2d
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/AndroidPackets.kt
@@ -0,0 +1,400 @@
+package ru.dbotthepony.mc.otm.network
+
+import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
+import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.RegistryFriendlyByteBuf
+import net.minecraft.network.chat.Component
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
+import net.minecraft.server.level.ServerPlayer
+import net.minecraft.sounds.SoundSource
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.android.AndroidActiveFeature
+import ru.dbotthepony.mc.otm.android.AndroidFeatureType
+import ru.dbotthepony.mc.otm.android.AndroidResearchManager
+import ru.dbotthepony.mc.otm.android.AndroidResearchType
+import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature
+import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
+import ru.dbotthepony.mc.otm.capability.matteryPlayer
+import ru.dbotthepony.mc.otm.client.MatteryGUI
+import ru.dbotthepony.mc.otm.client.minecraft
+import ru.dbotthepony.mc.otm.client.render.GlitchRenderer
+import ru.dbotthepony.mc.otm.config.AndroidConfig
+import ru.dbotthepony.mc.otm.core.ResourceLocation
+import ru.dbotthepony.mc.otm.core.readComponent
+import ru.dbotthepony.mc.otm.core.writeComponent
+import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
+import ru.dbotthepony.mc.otm.onceServer
+import ru.dbotthepony.mc.otm.registry.AndroidFeatures
+import ru.dbotthepony.mc.otm.registry.MRegistry
+import ru.dbotthepony.mc.otm.registry.MSoundEvents
+import java.io.ByteArrayInputStream
+
+class AndroidFeatureSyncPacket(val type: AndroidFeatureType<*>, val dataList: FastByteArrayOutputStream?, val dataBytes: ByteArray?) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		dataList ?: throw NullPointerException("No byte list is present")
+		buff.writeInt(MRegistry.ANDROID_FEATURES.getId(type))
+		buff.writeBytes(dataList.array, 0, dataList.length)
+	}
+
+	fun play(context: IPayloadContext) {
+		dataBytes ?: throw NullPointerException("No data bytes array is present")
+		val android = minecraft.player?.matteryPlayer ?: return
+		android.computeIfAbsent(type).applyNetworkPayload(ByteArrayInputStream(dataBytes))
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<AndroidFeatureSyncPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"android_feature_sync"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, AndroidFeatureSyncPacket> =
+			StreamCodec.ofMember(AndroidFeatureSyncPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): AndroidFeatureSyncPacket {
+			return AndroidFeatureSyncPacket(
+				MRegistry.ANDROID_FEATURES.byIdOrThrow(buff.readInt()),
+				null, ByteArray(buff.readableBytes()).also { buff.readBytes(it) }
+			)
+		}
+	}
+}
+
+class AndroidResearchSyncPacket(val type: AndroidResearchType, val dataList: FastByteArrayOutputStream?, val dataBytes: ByteArray?) :
+	CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		dataList ?: throw NullPointerException("No byte list is present")
+		buff.writeUtf(type.id.toString())
+		buff.writeBytes(dataList.array, 0, dataList.length)
+	}
+
+	fun play(context: IPayloadContext) {
+		dataBytes ?: throw NullPointerException("No data bytes array is present")
+		val android = minecraft.player?.matteryPlayer ?: return
+		android.getResearch(type).applyNetworkPayload(ByteArrayInputStream(dataBytes))
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<AndroidResearchSyncPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"android_research_sync"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, AndroidResearchSyncPacket> =
+			StreamCodec.ofMember(AndroidResearchSyncPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): AndroidResearchSyncPacket {
+			return AndroidResearchSyncPacket(
+				AndroidResearchManager[net.minecraft.resources.ResourceLocation.parse(buff.readUtf())] ?: throw NoSuchElementException(),
+				null, ByteArray(buff.readableBytes()).also { buff.readBytes(it) }
+			)
+		}
+	}
+}
+
+class AndroidResearchRequestPacket(val type: AndroidResearchType) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeUtf(type.id.toString())
+	}
+
+	fun play(context: IPayloadContext) {
+		val ply = context.player()
+		if (ply.isSpectator) return
+
+		if (!ply.matteryPlayer.isAndroid || ply.containerMenu !is AndroidStationMenu)
+			return
+
+		ply.matteryPlayer.getResearch(type).research()
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<AndroidResearchRequestPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"android_research_request"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, AndroidResearchRequestPacket> = StreamCodec.ofMember(AndroidResearchRequestPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): AndroidResearchRequestPacket {
+			return AndroidResearchRequestPacket(
+				AndroidResearchManager[net.minecraft.resources.ResourceLocation.parse(buff.readUtf())] ?: throw NoSuchElementException()
+			)
+		}
+	}
+}
+
+class AndroidFeatureRemovePacket(val type: AndroidFeatureType<*>) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeInt(MRegistry.ANDROID_FEATURES.getId(type))
+	}
+
+	fun play(context: IPayloadContext) {
+		val android = minecraft.player?.matteryPlayer ?: return
+		android.removeFeature(type)
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<AndroidFeatureRemovePacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"android_feature_remove"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, AndroidFeatureRemovePacket> =
+			StreamCodec.ofMember(AndroidFeatureRemovePacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): AndroidFeatureRemovePacket {
+			return AndroidFeatureRemovePacket(MRegistry.ANDROID_FEATURES.byIdOrThrow(buff.readInt()))
+		}
+	}
+}
+
+class PlayerIterationPacket(val iteration: Int, val deathLog: List<Pair<Int, Component>>) : CustomPacketPayload {
+	fun write(buff: RegistryFriendlyByteBuf) {
+		buff.writeInt(iteration)
+		buff.writeInt(deathLog.size)
+
+		for ((ticks, value) in deathLog) {
+			buff.writeInt(ticks)
+			buff.writeComponent(value)
+		}
+	}
+
+	fun play(context: IPayloadContext) {
+		MatteryGUI.iteration = iteration
+		MatteryGUI.deathLog.clear()
+		MatteryGUI.deathLog.addAll(deathLog)
+		MatteryGUI.showIterationUntil = System.currentTimeMillis() + 4000L
+		MatteryGUI.showIterationUntilFade = System.currentTimeMillis() + 5000L
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<PlayerIterationPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"player_iteration"
+			)
+		)
+		val CODEC: StreamCodec<RegistryFriendlyByteBuf, PlayerIterationPacket> =
+			StreamCodec.ofMember(PlayerIterationPacket::write, ::read)
+
+		fun read(buff: RegistryFriendlyByteBuf): PlayerIterationPacket {
+			val iteration = buff.readInt()
+			val size = buff.readInt()
+
+			val list = ArrayList<Pair<Int, Component>>()
+
+			for (i in 0 until size) {
+				list.add(buff.readInt() to buff.readComponent())
+			}
+
+			return PlayerIterationPacket(iteration, list)
+		}
+	}
+}
+
+class SwitchAndroidFeaturePacket(val type: AndroidFeatureType<*>, val newState: Boolean) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeInt(MRegistry.ANDROID_FEATURES.getId(type))
+		buff.writeBoolean(newState)
+	}
+
+	fun play(context: IPayloadContext) {
+		val matteryPlayer = context.player().matteryPlayer
+
+		if (!matteryPlayer.isAndroid) {
+			return
+		}
+
+		val feature = matteryPlayer.getFeature(type) ?: return
+
+		if (feature is AndroidActiveFeature && feature.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || feature.allowToSwitchByPlayerWhileSpectator)) {
+			matteryPlayer.features
+				.map { it as? AndroidActiveFeature }
+				.filter { it != null && it !== feature && it.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || it.allowToSwitchByPlayerWhileSpectator) }
+				.forEach { it!!.isActive = false }
+
+			feature.isActive = newState
+		} else if (feature is AndroidSwitchableFeature && feature.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || feature.allowToSwitchByPlayerWhileSpectator)) {
+			feature.isActive = newState
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<SwitchAndroidFeaturePacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"switch_android_feature"
+			)
+		)
+		val CODEC: StreamCodec<FriendlyByteBuf, SwitchAndroidFeaturePacket> =
+			StreamCodec.ofMember(SwitchAndroidFeaturePacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): SwitchAndroidFeaturePacket {
+			return SwitchAndroidFeaturePacket(MRegistry.ANDROID_FEATURES.byIdOrThrow(buff.readInt()), buff.readBoolean())
+		}
+	}
+}
+
+class ActivateAndroidFeaturePacket(val type: AndroidFeatureType<*>) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeInt(MRegistry.ANDROID_FEATURES.getId(type))
+	}
+
+	fun play(context: IPayloadContext) {
+		val matteryPlayer = context.player().matteryPlayer
+
+		if (!matteryPlayer.isAndroid || matteryPlayer.ply.isSpectator) {
+			return
+		}
+
+		val feature = matteryPlayer.getFeature(type) as? AndroidActiveFeature ?: return
+
+		if (feature.isActive || feature.allowToSwitchByPlayer) {
+			feature.activate(false)
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<ActivateAndroidFeaturePacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"activate_android_feature"
+			)
+		)
+		val CODEC: StreamCodec<FriendlyByteBuf, ActivateAndroidFeaturePacket> =
+			StreamCodec.ofMember(ActivateAndroidFeaturePacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): ActivateAndroidFeaturePacket {
+			return ActivateAndroidFeaturePacket(MRegistry.ANDROID_FEATURES.byIdOrThrow(buff.readInt()))
+		}
+	}
+}
+
+class GlitchPacket(val millis: Long) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeVarLong(millis)
+	}
+
+	fun play(context: IPayloadContext) {
+		GlitchRenderer.glitchFor(millis)
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<GlitchPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"android_glitch"
+			)
+		)
+		val CODEC: StreamCodec<FriendlyByteBuf, GlitchPacket> =
+			StreamCodec.ofMember(GlitchPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): GlitchPacket {
+			return GlitchPacket(buff.readVarLong())
+		}
+	}
+}
+
+object TriggerShockwavePacket : CustomPacketPayload {
+	fun play(context: IPayloadContext) {
+		val shockwave = context.player().matteryPlayer.getFeature(AndroidFeatures.SHOCKWAVE) ?: return
+
+		if (!shockwave.isOnCooldown && shockwave.isActive && shockwave.airTicks > 0) {
+			onceServer { // delay by one tick so player update its position as well
+				shockwave.shockwave()
+			}
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	val TYPE = CustomPacketPayload.Type<TriggerShockwavePacket>(
+		ResourceLocation(
+			OverdriveThatMatters.MOD_ID,
+			"trigger_shockwave"
+		)
+	)
+	val CODEC: StreamCodec<FriendlyByteBuf, TriggerShockwavePacket> =
+		StreamCodec.ofMember({ _, _ -> }, { TriggerShockwavePacket })
+}
+
+object TriggerJumpBoostPacket : CustomPacketPayload {
+	fun play(context: IPayloadContext) {
+		val mattery = context.player().matteryPlayer
+
+		if (!mattery.isAndroid)
+			return
+
+		val feature = mattery.getFeature(AndroidFeatures.JUMP_BOOST) ?: return
+
+		if (feature.isActive && feature.cooldown <= 4 && mattery.androidEnergy.extractEnergyExact(AndroidConfig.JumpBoost.ENERGY_COST, false)) {
+			feature.putOnCooldown()
+
+			context.player().level().playSound(
+				context.player(), context.player(),
+				MSoundEvents.ANDROID_JUMP_BOOST, SoundSource.PLAYERS,
+				1f, 1f
+			)
+
+			SmokeParticlesPacket.makeSmoke(context.player() as ServerPlayer, context.player().x, context.player().y, context.player().z)
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	val TYPE = CustomPacketPayload.Type<TriggerJumpBoostPacket>(
+		ResourceLocation(
+			OverdriveThatMatters.MOD_ID,
+			"trigger_jump_boost"
+		)
+	)
+	val CODEC: StreamCodec<FriendlyByteBuf, TriggerJumpBoostPacket> =
+		StreamCodec.ofMember({ _, _ -> }, { TriggerJumpBoostPacket })
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/GenericNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/BlockEntitySyncPacket.kt
similarity index 56%
rename from src/main/kotlin/ru/dbotthepony/mc/otm/network/GenericNetworkChannel.kt
rename to src/main/kotlin/ru/dbotthepony/mc/otm/network/BlockEntitySyncPacket.kt
index 9d3c19820..81732437e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/GenericNetworkChannel.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/BlockEntitySyncPacket.kt
@@ -4,53 +4,24 @@ import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
 import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
 import net.minecraft.core.BlockPos
-import net.minecraft.core.particles.ParticleTypes
 import net.minecraft.network.FriendlyByteBuf
-import net.minecraft.server.level.ServerPlayer
-import net.minecraft.util.RandomSource
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
 import net.minecraft.world.level.Level
-import net.minecraftforge.network.NetworkDirection
-import net.minecraftforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
 import org.apache.logging.log4j.LogManager
-import ru.dbotthepony.mc.otm.android.AndroidResearchManager
-import ru.dbotthepony.mc.otm.android.feature.ItemEntityDataPacket
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
 import ru.dbotthepony.mc.otm.client.minecraft
 import ru.dbotthepony.mc.otm.client.onceClient
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.collect.map
 import ru.dbotthepony.mc.otm.core.collect.reduce
-import ru.dbotthepony.mc.otm.item.QuantumBatteryItem
-import ru.dbotthepony.mc.otm.matter.MatterManager
 import java.util.*
 import kotlin.collections.ArrayList
 
-class SmokeParticlesPacket(val x: Double, val y: Double, val z: Double) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeDouble(x)
-		buff.writeDouble(y)
-		buff.writeDouble(z)
-	}
-
-	override fun play(context: MNetworkContext) {
-		minecraft.player?.level()?.let {
-			makeSmoke(x, y, z, it.random, it)
-		}
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): SmokeParticlesPacket {
-			return SmokeParticlesPacket(buff.readDouble(), buff.readDouble(), buff.readDouble())
-		}
-
-		fun makeSmoke(x: Double, y: Double, z: Double, random: RandomSource, level: Level) {
-			for (i in 0 .. random.nextInt(4, 8))
-				level.addParticle(ParticleTypes.POOF, x + random.nextDouble() * 0.5 - 0.25, y + random.nextDouble() * 0.5 - 0.25, z + random.nextDouble() * 0.5 - 0.25, random.nextGaussian() * 0.02, random.nextGaussian() * 0.02, random.nextGaussian() * 0.02)
-		}
-	}
-}
-
-class BlockEntitySyncPacket(val position: BlockPos, val buffer: ByteArray, val validBytes: Int) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
+class BlockEntitySyncPacket(val position: BlockPos, val buffer: ByteArray, val validBytes: Int) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
 		buff.writeBlockPos(position)
 		buff.writeBytes(buffer, 0, validBytes)
 	}
@@ -100,13 +71,20 @@ class BlockEntitySyncPacket(val position: BlockPos, val buffer: ByteArray, val v
 		}
 	}
 
-	override fun play(context: MNetworkContext) {
+	fun play(context: IPayloadContext) {
 		execute()
 	}
 
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
 	companion object {
 		private val backlog = WeakHashMap<Level, Object2ObjectOpenHashMap<BlockPos, ArrayList<ByteArray>>>()
 
+		val CODEC: StreamCodec<FriendlyByteBuf, BlockEntitySyncPacket> = StreamCodec.ofMember(BlockEntitySyncPacket::write, ::read)
+		val TYPE = CustomPacketPayload.Type<BlockEntitySyncPacket>(ResourceLocation(OverdriveThatMatters.MOD_ID, "block_entity_data"))
+
 		fun read(buff: FriendlyByteBuf): BlockEntitySyncPacket {
 			val position = buff.readBlockPos()
 			val size = buff.readableBytes()
@@ -134,28 +112,3 @@ class BlockEntitySyncPacket(val position: BlockPos, val buffer: ByteArray, val v
 		}
 	}
 }
-
-object GenericNetworkChannel : MatteryNetworkChannel(
-	version = 5,
-	name = "generic"
-) {
-	fun makeSmoke(x: Double, y: Double, z: Double, level: Level) {
-		send(PacketDistributor.NEAR.with(PacketDistributor.TargetPoint(x, y, z, 64.0, level.dimension())), SmokeParticlesPacket(x, y, z))
-	}
-
-	fun makeSmoke(excluded: ServerPlayer, x: Double, y: Double, z: Double) {
-		send(PacketDistributor.NEAR.with(PacketDistributor.TargetPoint(excluded, x, y, z, 64.0, excluded.level().dimension())), SmokeParticlesPacket(x, y, z))
-	}
-
-	fun register() {
-		add(QuantumBatteryItem.ChargePacket::class.java, QuantumBatteryItem.Companion::readPacket, NetworkDirection.PLAY_TO_CLIENT)
-
-		add(BlockEntitySyncPacket::class.java, BlockEntitySyncPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-		add(ItemEntityDataPacket::class.java, ItemEntityDataPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-
-		add(AndroidResearchManager.SyncPacket::class.java, AndroidResearchManager::readSyncPacket, NetworkDirection.PLAY_TO_CLIENT, handleOnMainThread = false)
-		add(MatterManager.SyncPacket::class.java, MatterManager::readSyncPacket, NetworkDirection.PLAY_TO_CLIENT)
-
-		add(SmokeParticlesPacket::class, SmokeParticlesPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/Ext.kt
new file mode 100644
index 000000000..649826d53
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/Ext.kt
@@ -0,0 +1,20 @@
+package ru.dbotthepony.mc.otm.network
+
+import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import net.neoforged.neoforge.network.registration.PayloadRegistrar
+import kotlin.reflect.KFunction1
+
+fun <T : CustomPacketPayload> PayloadRegistrar.playToClient(
+	type: CustomPacketPayload.Type<T>,
+	codec: StreamCodec<FriendlyByteBuf, T>,
+	handler: KFunction1<IPayloadContext, Unit>
+): PayloadRegistrar = playToClient(type, codec) { _, context -> handler(context) }
+
+fun <T : CustomPacketPayload> PayloadRegistrar.playToServer(
+	type: CustomPacketPayload.Type<T>,
+	codec: StreamCodec<FriendlyByteBuf, T>,
+	handler: KFunction1<IPayloadContext, Unit>
+): PayloadRegistrar = playToServer(type, codec) { _, context -> handler(context) }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt
deleted file mode 100644
index 8777da206..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-package ru.dbotthepony.mc.otm.network
-
-import net.minecraft.network.FriendlyByteBuf
-import net.minecraft.resources.ResourceLocation
-import net.minecraft.server.level.ServerPlayer
-import net.minecraft.world.entity.Entity
-import net.minecraft.world.entity.player.Player
-import net.minecraftforge.event.network.CustomPayloadEvent
-import net.minecraftforge.network.Channel
-import net.minecraftforge.network.ChannelBuilder
-import net.minecraftforge.network.NetworkDirection
-import net.minecraftforge.network.PacketDistributor
-import net.minecraftforge.network.SimpleChannel
-import org.apache.logging.log4j.LogManager
-import ru.dbotthepony.kommons.util.Delegate
-import ru.dbotthepony.kommons.util.getValue
-import ru.dbotthepony.kommons.util.setValue
-import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
-import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import java.util.concurrent.CompletableFuture
-import java.util.concurrent.ConcurrentLinkedQueue
-import java.util.concurrent.locks.LockSupport
-import java.util.function.BiConsumer
-import java.util.function.Function
-import kotlin.reflect.KClass
-
-class MNetworkContext(val sender: ServerPlayer?, packetHandled: Delegate<Boolean>, private val enqueuer: (Runnable) -> CompletableFuture<*>) {
-	var packetHandled by packetHandled
-
-	fun enqueueWork(callback: Runnable): CompletableFuture<*> {
-		return enqueuer(callback)
-	}
-}
-
-interface MatteryPacket {
-	fun write(buff: FriendlyByteBuf)
-	fun play(context: MNetworkContext)
-}
-
-abstract class MatteryNetworkChannel(val version: Int, val name: String) {
-	val channel: SimpleChannel = ChannelBuilder
-		.named(ResourceLocation(OverdriveThatMatters.MOD_ID, name))
-		.clientAcceptedVersions { status, version -> (status == Channel.VersionTest.Status.MISSING || status == Channel.VersionTest.Status.VANILLA) || version == this.version }
-		.serverAcceptedVersions { status, version -> status == Channel.VersionTest.Status.PRESENT || version == this.version }
-		.networkProtocolVersion(version)
-		.simpleChannel()
-
-	fun sendToServer(packet: Any) = channel.send(packet, PacketDistributor.SERVER.noArg())
-
-	fun send(ply: Player, packet: Any) {
-		if (ply is ServerPlayer) {
-			channel.send(packet, PacketDistributor.PLAYER.with(ply))
-		}
-	}
-
-	fun sendTracking(entity: Entity, packet: Any) {
-		if ((NULLABLE_MINECRAFT_SERVER?.playerCount ?: 0) <= 0) {
-			return
-		}
-
-		channel.send(packet, PacketDistributor.TRACKING_ENTITY.with(entity))
-	}
-
-	fun sendTrackingAndSelf(entity: Entity, packet: Any) {
-		if ((NULLABLE_MINECRAFT_SERVER?.playerCount ?: 0) <= 0) {
-			return
-		}
-
-		channel.send(packet, PacketDistributor.TRACKING_ENTITY_AND_SELF.with(entity))
-	}
-
-	fun send(distributor: PacketDistributor.PacketTarget, packet: Any) {
-		channel.send(packet, distributor)
-	}
-
-	private var nextNetworkPacketID = 0
-
-	fun <T> add(
-		packetClass: Class<T>,
-		writer: BiConsumer<T, FriendlyByteBuf>,
-		reader: Function<FriendlyByteBuf, T>,
-		handler: (T, MNetworkContext) -> Unit,
-		direction: NetworkDirection? = null,
-		handleOnMainThread: Boolean = true,
-	) {
-		if (nextNetworkPacketID >= 256) {
-			throw IndexOutOfBoundsException("Network message ID overflow!")
-		}
-
-		val builder = channel.messageBuilder(packetClass, direction)
-		val bridgeHandler = BiConsumer<T, CustomPayloadEvent.Context> { a, b -> handler(a, MNetworkContext(b.sender, Delegate.Of({ b.packetHandled }, { b.packetHandled = it }), b::enqueueWork)) }
-
-		if (handleOnMainThread) {
-			builder.consumerMainThread(bridgeHandler)
-		} else {
-			builder.consumerNetworkThread(bridgeHandler)
-		}
-
-		builder.encoder(writer)
-		builder.decoder(reader)
-		builder.add()
-	}
-
-	fun <T : MatteryPacket> add(
-		packetClass: Class<T>,
-		reader: Function<FriendlyByteBuf, T>,
-		direction: NetworkDirection? = null,
-		handleOnMainThread: Boolean = true,
-	) {
-		add(packetClass, MatteryPacket::write, reader, MatteryPacket::play, direction, handleOnMainThread)
-	}
-
-	fun <T : MatteryPacket> add(
-		packetClass: KClass<T>,
-		reader: Function<FriendlyByteBuf, T>,
-		direction: NetworkDirection? = null,
-		handleOnMainThread: Boolean = true,
-	) {
-		add(packetClass.java, MatteryPacket::write, reader, MatteryPacket::play, direction, handleOnMainThread)
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt
deleted file mode 100644
index 42b9e281f..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt
+++ /dev/null
@@ -1,600 +0,0 @@
-package ru.dbotthepony.mc.otm.network
-
-import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
-import net.minecraft.core.particles.ParticleTypes
-import net.minecraft.network.FriendlyByteBuf
-import net.minecraft.network.chat.Component
-import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket
-import net.minecraft.resources.ResourceLocation
-import net.minecraft.world.entity.player.Inventory
-import net.minecraft.world.item.ItemStack
-import net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT
-import net.minecraftforge.network.NetworkDirection.PLAY_TO_SERVER
-import org.apache.logging.log4j.LogManager
-import ru.dbotthepony.mc.otm.android.AndroidActiveFeature
-import ru.dbotthepony.mc.otm.android.AndroidFeatureType
-import ru.dbotthepony.mc.otm.android.AndroidResearchManager
-import ru.dbotthepony.mc.otm.android.AndroidResearchType
-import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature
-import ru.dbotthepony.mc.otm.android.feature.TriggerJumpBoostPacket
-import ru.dbotthepony.mc.otm.android.feature.TriggerShockwavePacket
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
-import ru.dbotthepony.mc.otm.capability.matteryPlayer
-import ru.dbotthepony.mc.otm.client.MatteryGUI
-import ru.dbotthepony.mc.otm.client.minecraft
-import ru.dbotthepony.mc.otm.client.render.GlitchRenderer
-import ru.dbotthepony.mc.otm.client.render.ShockwaveRenderer
-import ru.dbotthepony.mc.otm.container.get
-import ru.dbotthepony.mc.otm.container.set
-import ru.dbotthepony.kommons.math.RGBAColor
-import ru.dbotthepony.mc.otm.core.math.Vector
-import ru.dbotthepony.mc.otm.core.math.component1
-import ru.dbotthepony.mc.otm.core.math.component2
-import ru.dbotthepony.mc.otm.core.math.component3
-import ru.dbotthepony.mc.otm.core.math.toRadians
-import ru.dbotthepony.mc.otm.core.position
-import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
-import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu
-import ru.dbotthepony.mc.otm.registry.AndroidFeatures
-import ru.dbotthepony.mc.otm.registry.MRegistry
-import java.io.ByteArrayInputStream
-import java.util.UUID
-
-class MatteryPlayerFieldPacket(val bytes: ByteArray, val length: Int, val isPublic: Boolean, val target: UUID? = null) : MatteryPacket {
-	constructor(stream: FastByteArrayOutputStream, isPublic: Boolean, target: UUID? = null) : this(stream.array, stream.length, isPublic, target)
-
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeBoolean(target != null)
-
-		if (target != null)
-			buff.writeUUID(target)
-
-		buff.writeBoolean(isPublic)
-		buff.writeBytes(bytes, 0, length)
-	}
-
-	override fun play(context: MNetworkContext) {
-		val player: MatteryPlayerCapability
-
-		if (target != null) {
-			player = minecraft.level?.players()?.firstOrNull { it.uuid == target }?.matteryPlayer ?: return
-		} else {
-			player = minecraft.player?.matteryPlayer ?: return
-		}
-
-		if (isPublic) {
-			player.publicSyncher.read(ByteArrayInputStream(bytes, 0, length))
-		} else {
-			player.syncher.read(ByteArrayInputStream(bytes, 0, length))
-		}
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): MatteryPlayerFieldPacket {
-			val target = if (buff.readBoolean())
-				buff.readUUID()
-			else
-				null
-
-			val isPublic = buff.readBoolean()
-			val readable = buff.readableBytes()
-			return MatteryPlayerFieldPacket(ByteArray(readable).also(buff::readBytes), readable, isPublic, target)
-		}
-	}
-}
-
-class AndroidResearchRequestPacket(val type: AndroidResearchType) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeUtf(type.id.toString())
-	}
-
-	override fun play(context: MNetworkContext) {
-		val ply = context.sender ?: return
-		if (ply.isSpectator) return
-		val android = ply.matteryPlayer ?: return
-
-		if (!android.isAndroid || ply.containerMenu !is AndroidStationMenu)
-			return
-
-		android.getResearch(type).research()
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): AndroidResearchRequestPacket {
-			return AndroidResearchRequestPacket(AndroidResearchManager[ResourceLocation(buff.readUtf())] ?: throw NoSuchElementException())
-		}
-	}
-}
-
-class AndroidResearchSyncPacket(val type: AndroidResearchType, val dataList: FastByteArrayOutputStream?, val dataBytes: ByteArray?) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		dataList ?: throw NullPointerException("No byte list is present")
-		buff.writeUtf(type.id.toString())
-		buff.writeBytes(dataList.array, 0, dataList.length)
-	}
-
-	override fun play(context: MNetworkContext) {
-		dataBytes ?: throw NullPointerException("No data bytes array is present")
-		val android = minecraft.player?.matteryPlayer ?: return
-		android.getResearch(type).applyNetworkPayload(ByteArrayInputStream(dataBytes))
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): AndroidResearchSyncPacket {
-			return AndroidResearchSyncPacket(
-				AndroidResearchManager[ResourceLocation(buff.readUtf())] ?: throw NoSuchElementException(),
-				null, ByteArray(buff.readableBytes()).also { buff.readBytes(it) }
-			)
-		}
-	}
-}
-
-class AndroidFeatureSyncPacket(val type: AndroidFeatureType<*>, val dataList: FastByteArrayOutputStream?, val dataBytes: ByteArray?) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		dataList ?: throw NullPointerException("No byte list is present")
-		buff.writeInt(MRegistry.ANDROID_FEATURES.getID(type))
-		buff.writeBytes(dataList.array, 0, dataList.length)
-	}
-
-	override fun play(context: MNetworkContext) {
-		dataBytes ?: throw NullPointerException("No data bytes array is present")
-		val android = minecraft.player?.matteryPlayer ?: return
-		android.computeIfAbsent(type).applyNetworkPayload(ByteArrayInputStream(dataBytes))
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): AndroidFeatureSyncPacket {
-			return AndroidFeatureSyncPacket(
-				MRegistry.ANDROID_FEATURES.getValue(buff.readInt()),
-				null, ByteArray(buff.readableBytes()).also { buff.readBytes(it) }
-			)
-		}
-	}
-}
-
-class AndroidFeatureRemovePacket(val type: AndroidFeatureType<*>) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeInt(MRegistry.ANDROID_FEATURES.getID(type))
-	}
-
-	override fun play(context: MNetworkContext) {
-		val android = minecraft.player?.matteryPlayer ?: return
-		android.removeFeature(type)
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): AndroidFeatureRemovePacket {
-			return AndroidFeatureRemovePacket(MRegistry.ANDROID_FEATURES.getValue(buff.readInt()))
-		}
-	}
-}
-
-class PlayerIterationPacket(val iteration: Int, val deathLog: List<Pair<Int, Component>>) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeInt(iteration)
-		buff.writeInt(deathLog.size)
-
-		for ((ticks, value) in deathLog) {
-			buff.writeInt(ticks)
-			buff.writeComponent(value)
-		}
-	}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-
-		MatteryGUI.iteration = iteration
-		MatteryGUI.deathLog.clear()
-		MatteryGUI.deathLog.addAll(deathLog)
-		MatteryGUI.showIterationUntil = System.currentTimeMillis() + 4000L
-		MatteryGUI.showIterationUntilFade = System.currentTimeMillis() + 5000L
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): PlayerIterationPacket {
-			val iteration = buff.readInt()
-			val size = buff.readInt()
-
-			val list = ArrayList<Pair<Int, Component>>()
-
-			for (i in 0 until size) {
-				list.add(buff.readInt() to buff.readComponent())
-			}
-
-			return PlayerIterationPacket(iteration, list)
-		}
-	}
-}
-
-class ExopackCarriedPacket(val itemStack: ItemStack, val containerState: Int) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeItem(itemStack)
-		buff.writeInt(containerState)
-	}
-
-	override fun play(context: MNetworkContext) {
-		val mattery = minecraft.player?.matteryPlayer ?: return
-
-		if (mattery.hasExopack) {
-			mattery.exoPackMenu.carried = itemStack
-			mattery.exoPackMenu.stateId = containerState
-		}
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): ExopackCarriedPacket {
-			return ExopackCarriedPacket(buff.readItem(), buff.readInt())
-		}
-	}
-}
-
-class ExopackSlotPacket(val slotId: Int, val itemStack: ItemStack, val containerState: Int) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeInt(slotId)
-		buff.writeItem(itemStack)
-		buff.writeInt(containerState)
-	}
-
-	override fun play(context: MNetworkContext) {
-		if (slotId < 0) {
-			LOGGER.error("Unknown slot with ID {} in exosuit menu", slotId)
-			return
-		}
-
-		val mattery = minecraft.player?.matteryPlayer ?: return
-
-		if (mattery.hasExopack) {
-			if (slotId >= mattery.exoPackMenu.slots.size) {
-				LOGGER.error("Unknown slot with ID {} in exosuit menu", slotId)
-				return
-			}
-
-			// don't duplicate data
-			// really.
-			if (mattery.exoPackMenu.slots[slotId].container == minecraft.player?.inventory && minecraft.player?.containerMenu !is ExopackInventoryMenu)
-				return
-
-			mattery.exoPackMenu.slots[slotId].set(itemStack)
-			mattery.exoPackMenu.stateId = containerState
-		}
-	}
-
-	companion object {
-		private val LOGGER = LogManager.getLogger()
-
-		fun read(buff: FriendlyByteBuf): ExopackSlotPacket {
-			return ExopackSlotPacket(buff.readInt(), buff.readItem(), buff.readInt())
-		}
-	}
-}
-
-class ExopackMenuInitPacket(val slots: List<ItemStack>, val carried: ItemStack, val containerState: Int) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeInt(slots.size)
-
-		for (item in slots) {
-			buff.writeItem(item)
-		}
-
-		buff.writeItem(carried)
-		buff.writeInt(containerState)
-	}
-
-	override fun play(context: MNetworkContext) {
-		val mattery = minecraft.player?.matteryPlayer ?: return
-
-		if (mattery.hasExopack) {
-			mattery.exoPackMenu.initializeContents(containerState, slots, carried)
-		}
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): ExopackMenuInitPacket {
-			val size = buff.readInt()
-			val slots = ArrayList<ItemStack>(size)
-
-			for (i in 0 until size) {
-				slots.add(buff.readItem())
-			}
-
-			val carried = buff.readItem()
-			val containerState = buff.readInt()
-
-			return ExopackMenuInitPacket(slots, carried, containerState)
-		}
-	}
-}
-
-object ExopackMenuOpen : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {}
-
-	override fun play(context: MNetworkContext) {
-		val player = context.sender ?: return
-		val mattery = player.matteryPlayer ?: return
-
-		if (mattery.hasExopack) {
-			player.containerMenu = mattery.exoPackMenu
-		}
-	}
-}
-
-class SwitchAndroidFeaturePacket(val type: AndroidFeatureType<*>, val newState: Boolean) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeInt(MRegistry.ANDROID_FEATURES.getID(type))
-		buff.writeBoolean(newState)
-	}
-
-	override fun play(context: MNetworkContext) {
-		val matteryPlayer = context.sender?.matteryPlayer ?: return
-
-		if (!matteryPlayer.isAndroid) {
-			return
-		}
-
-		val feature = matteryPlayer.getFeature(type) ?: return
-
-		if (feature is AndroidActiveFeature && feature.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || feature.allowToSwitchByPlayerWhileSpectator)) {
-			matteryPlayer.features
-				.map { it as? AndroidActiveFeature }
-				.filter { it != null }
-				.filter { it !== feature && it!!.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || it.allowToSwitchByPlayerWhileSpectator) }
-				.forEach { it!!.isActive = false }
-			feature.isActive = newState
-		} else if (feature is AndroidSwitchableFeature && feature.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || feature.allowToSwitchByPlayerWhileSpectator)) {
-			feature.isActive = newState
-		}
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): SwitchAndroidFeaturePacket {
-			return SwitchAndroidFeaturePacket(MRegistry.ANDROID_FEATURES.getValue(buff.readInt()) ?: AndroidFeatures.AIR_BAGS, buff.readBoolean())
-		}
-	}
-}
-
-class ActivateAndroidFeaturePacket(val type: AndroidFeatureType<*>) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeInt(MRegistry.ANDROID_FEATURES.getID(type))
-	}
-
-	override fun play(context: MNetworkContext) {
-		val matteryPlayer = context.sender?.matteryPlayer ?: return
-
-		if (!matteryPlayer.isAndroid || matteryPlayer.ply.isSpectator) {
-			return
-		}
-
-		val feature = matteryPlayer.getFeature(type) as? AndroidActiveFeature ?: return
-
-		if (feature.isActive || feature.allowToSwitchByPlayer) {
-			feature.activate(false)
-		}
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): ActivateAndroidFeaturePacket {
-			return ActivateAndroidFeaturePacket(MRegistry.ANDROID_FEATURES.getValue(buff.readInt()) ?: AndroidFeatures.AIR_BAGS)
-		}
-	}
-}
-
-class PickItemFromInventoryPacket(
-	val targetHotbarSlot: Int,
-	val sourceExosuitSlot: Int
-) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeVarInt(targetHotbarSlot)
-		buff.writeVarInt(sourceExosuitSlot)
-	}
-
-	override fun play(context: MNetworkContext) {
-		val player = context.sender ?: return
-		val mattery = player.matteryPlayer ?: return
-
-		if (!mattery.hasExopack || sourceExosuitSlot !in 0 until mattery.exopackContainer.containerSize) {
-			return
-		}
-
-		if (!Inventory.isHotbarSlot(targetHotbarSlot)) {
-			return
-		}
-
-		player.inventory.selected = targetHotbarSlot
-		val existingItem = player.inventory[targetHotbarSlot].copy()
-		val inventoryItem = mattery.exopackContainer[sourceExosuitSlot].copy()
-
-		player.inventory[targetHotbarSlot] = if (inventoryItem.isEmpty) ItemStack.EMPTY else inventoryItem
-		mattery.exopackContainer[sourceExosuitSlot] = if (existingItem.isEmpty) ItemStack.EMPTY else existingItem
-
-		player.connection.send(ClientboundSetCarriedItemPacket(targetHotbarSlot))
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): PickItemFromInventoryPacket {
-			return PickItemFromInventoryPacket(buff.readVarInt(), buff.readVarInt())
-		}
-	}
-}
-
-class GlitchPacket(val millis: Long) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeVarLong(millis)
-	}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-		GlitchRenderer.glitchFor(millis)
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): GlitchPacket {
-			return GlitchPacket(buff.readVarLong())
-		}
-	}
-}
-
-class ShockwaveEffectPacket(val pos: Vector) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeDouble(pos.x)
-		buff.writeDouble(pos.y)
-		buff.writeDouble(pos.z)
-	}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-		ShockwaveRenderer.handle(this)
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): ShockwaveEffectPacket {
-			return ShockwaveEffectPacket(Vector(buff.readDouble(), buff.readDouble(), buff.readDouble()))
-		}
-	}
-}
-
-object DisplayExopackPacket : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-		context.sender?.matteryPlayer?.isExopackVisible = true
-	}
-}
-
-object HideExopackPacket : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-		context.sender?.matteryPlayer?.isExopackVisible = false
-	}
-}
-
-object EnableExopackGlowPacket : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-		context.sender?.matteryPlayer?.exopackGlows = true
-	}
-}
-
-object DisableExopackGlowPacket : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-		context.sender?.matteryPlayer?.exopackGlows = false
-	}
-}
-
-object ResetExopackColorPacket : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-		context.sender?.matteryPlayer?.exopackColor = null
-	}
-}
-
-data class SetExopackColorPacket(val color: RGBAColor) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeFloat(color.red)
-		buff.writeFloat(color.green)
-		buff.writeFloat(color.blue)
-	}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-		context.sender?.matteryPlayer?.exopackColor = color
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): SetExopackColorPacket {
-			return SetExopackColorPacket(RGBAColor(buff.readFloat(), buff.readFloat(), buff.readFloat()))
-		}
-	}
-}
-
-data class ExopackSmokePacket(val player: UUID) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeUUID(player)
-	}
-
-	override fun play(context: MNetworkContext) {
-		context.packetHandled = true
-		// minecraft.player?.level()?.getPlayerByUUID(player)?.matteryPlayer?.spawnExopackSmoke = true
-
-		minecraft.player?.level()?.getPlayerByUUID(player)?.let { ply ->
-			if (ply != minecraft.player || minecraft.gameRenderer.mainCamera.isDetached) {
-				var (x, y, z) = ply.position
-
-				y += 1.5
-				val deg = toRadians(ply.yBodyRot + 90f)
-
-				x += kotlin.math.cos(deg) * -0.4
-				z += kotlin.math.sin(deg) * -0.4
-
-				val level = ply.level()
-				val random = level.random
-
-				for (i in 0 .. random.nextInt(2, 4))
-					level.addParticle(
-						ParticleTypes.SMOKE,
-						x + random.nextDouble() * 0.4 - 0.2,
-						y + random.nextDouble() * 0.4 - 0.2,
-						z + random.nextDouble() * 0.4 - 0.2,
-						random.nextGaussian() * 0.02,
-						random.nextGaussian() * 0.02,
-						random.nextGaussian() * 0.02)
-			}
-		}
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): ExopackSmokePacket {
-			return ExopackSmokePacket(buff.readUUID())
-		}
-	}
-}
-
-object MatteryPlayerNetworkChannel : MatteryNetworkChannel(
-	version = 7,
-	name = "player"
-) {
-	fun register() {
-		add(MatteryPlayerFieldPacket::class, MatteryPlayerFieldPacket.Companion::read, PLAY_TO_CLIENT)
-
-		add(AndroidResearchRequestPacket::class, AndroidResearchRequestPacket.Companion::read, PLAY_TO_SERVER)
-		add(AndroidResearchSyncPacket::class, AndroidResearchSyncPacket.Companion::read, PLAY_TO_CLIENT)
-		add(AndroidFeatureSyncPacket::class, AndroidFeatureSyncPacket.Companion::read, PLAY_TO_CLIENT)
-		add(AndroidFeatureRemovePacket::class, AndroidFeatureRemovePacket.Companion::read, PLAY_TO_CLIENT)
-
-		add(PlayerIterationPacket::class, PlayerIterationPacket.Companion::read, PLAY_TO_CLIENT)
-
-		add(ExopackCarriedPacket::class, ExopackCarriedPacket.Companion::read, PLAY_TO_CLIENT)
-		add(ExopackSlotPacket::class, ExopackSlotPacket.Companion::read, PLAY_TO_CLIENT)
-		add(ExopackMenuInitPacket::class, ExopackMenuInitPacket.Companion::read, PLAY_TO_CLIENT)
-		add(ExopackSmokePacket::class, ExopackSmokePacket.Companion::read, PLAY_TO_CLIENT)
-
-		add(ExopackMenuOpen::class, { ExopackMenuOpen }, PLAY_TO_SERVER)
-
-		add(SwitchAndroidFeaturePacket::class, SwitchAndroidFeaturePacket.Companion::read, PLAY_TO_SERVER)
-		add(ActivateAndroidFeaturePacket::class, ActivateAndroidFeaturePacket.Companion::read, PLAY_TO_SERVER)
-
-		add(TriggerShockwavePacket::class, { TriggerShockwavePacket }, PLAY_TO_SERVER)
-		add(TriggerJumpBoostPacket::class, { TriggerJumpBoostPacket }, PLAY_TO_SERVER)
-
-		add(PickItemFromInventoryPacket::class, PickItemFromInventoryPacket.Companion::read, PLAY_TO_SERVER)
-
-		add(GlitchPacket::class, GlitchPacket.Companion::read, PLAY_TO_CLIENT)
-		add(ShockwaveEffectPacket::class, ShockwaveEffectPacket.Companion::read, PLAY_TO_CLIENT)
-
-		add(DisplayExopackPacket::class, { DisplayExopackPacket }, PLAY_TO_SERVER)
-		add(HideExopackPacket::class, { HideExopackPacket }, PLAY_TO_SERVER)
-		add(EnableExopackGlowPacket::class, { EnableExopackGlowPacket }, PLAY_TO_SERVER)
-		add(DisableExopackGlowPacket::class, { DisableExopackGlowPacket }, PLAY_TO_SERVER)
-		add(ResetExopackColorPacket::class, { ResetExopackColorPacket }, PLAY_TO_SERVER)
-		add(SetExopackColorPacket::class, SetExopackColorPacket.Companion::read, PLAY_TO_SERVER)
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt
new file mode 100644
index 000000000..77c5b6d85
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt
@@ -0,0 +1,484 @@
+package ru.dbotthepony.mc.otm.network
+
+import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
+import net.minecraft.core.particles.ParticleTypes
+import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.RegistryFriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
+import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket
+import net.minecraft.server.level.ServerPlayer
+import net.minecraft.world.entity.player.Inventory
+import net.minecraft.world.item.ItemStack
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import org.apache.logging.log4j.LogManager
+import ru.dbotthepony.kommons.math.RGBAColor
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
+import ru.dbotthepony.mc.otm.capability.matteryPlayer
+import ru.dbotthepony.mc.otm.client.minecraft
+import ru.dbotthepony.mc.otm.container.get
+import ru.dbotthepony.mc.otm.container.set
+import ru.dbotthepony.mc.otm.core.ResourceLocation
+import ru.dbotthepony.mc.otm.core.math.component1
+import ru.dbotthepony.mc.otm.core.math.component2
+import ru.dbotthepony.mc.otm.core.math.component3
+import ru.dbotthepony.mc.otm.core.math.toRadians
+import ru.dbotthepony.mc.otm.core.position
+import ru.dbotthepony.mc.otm.core.readItem
+import ru.dbotthepony.mc.otm.core.writeItem
+import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu
+import java.io.ByteArrayInputStream
+import java.util.*
+
+
+class MatteryPlayerDataPacket(val bytes: ByteArray, val length: Int, val isPublic: Boolean, val target: UUID? = null) :
+	CustomPacketPayload {
+	constructor(stream: FastByteArrayOutputStream, isPublic: Boolean, target: UUID? = null) : this(stream.array, stream.length, isPublic, target)
+
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeBoolean(target != null)
+
+		if (target != null)
+			buff.writeUUID(target)
+
+		buff.writeBoolean(isPublic)
+		buff.writeBytes(bytes, 0, length)
+	}
+
+	fun play(context: IPayloadContext) {
+		val player: MatteryPlayer
+
+		if (target != null) {
+			player = minecraft.level?.players()?.firstOrNull { it.uuid == target }?.matteryPlayer ?: return
+		} else {
+			player = minecraft.player?.matteryPlayer ?: return
+		}
+
+		if (isPublic) {
+			player.publicSyncher.read(ByteArrayInputStream(bytes, 0, length))
+		} else {
+			player.syncher.read(ByteArrayInputStream(bytes, 0, length))
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<MatteryPlayerDataPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"player_data"
+			)
+		)
+		val CODEC: StreamCodec<FriendlyByteBuf, MatteryPlayerDataPacket> =
+			StreamCodec.ofMember(MatteryPlayerDataPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): MatteryPlayerDataPacket {
+			val target = if (buff.readBoolean())
+				buff.readUUID()
+			else
+				null
+
+			val isPublic = buff.readBoolean()
+			val readable = buff.readableBytes()
+			return MatteryPlayerDataPacket(ByteArray(readable).also(buff::readBytes), readable, isPublic, target)
+		}
+	}
+}
+
+class ExopackCarriedPacket(val itemStack: ItemStack, val containerState: Int) : CustomPacketPayload {
+	fun write(buff: RegistryFriendlyByteBuf) {
+		buff.writeItem(itemStack)
+		buff.writeInt(containerState)
+	}
+
+	fun play(context: IPayloadContext) {
+		val mattery = minecraft.player?.matteryPlayer ?: return
+
+		if (mattery.hasExopack) {
+			mattery.exoPackMenu.carried = itemStack
+			mattery.exoPackMenu.stateId = containerState
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<ExopackCarriedPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"exopack_carried"
+			)
+		)
+		val CODEC: StreamCodec<RegistryFriendlyByteBuf, ExopackCarriedPacket> =
+			StreamCodec.ofMember(ExopackCarriedPacket::write, ::read)
+
+		fun read(buff: RegistryFriendlyByteBuf): ExopackCarriedPacket {
+			return ExopackCarriedPacket(buff.readItem(), buff.readInt())
+		}
+	}
+}
+
+class ExopackSlotPacket(val slotId: Int, val itemStack: ItemStack, val containerState: Int) : CustomPacketPayload {
+	fun write(buff: RegistryFriendlyByteBuf) {
+		buff.writeInt(slotId)
+		buff.writeItem(itemStack)
+		buff.writeInt(containerState)
+	}
+
+	fun play(context: IPayloadContext) {
+		if (slotId < 0) {
+			LOGGER.error("Unknown slot with ID {} in exosuit menu", slotId)
+			return
+		}
+
+		val mattery = minecraft.player?.matteryPlayer ?: return
+
+		if (mattery.hasExopack) {
+			if (slotId >= mattery.exoPackMenu.slots.size) {
+				LOGGER.error("Unknown slot with ID {} in exosuit menu", slotId)
+				return
+			}
+
+			// don't duplicate data
+			// really.
+			if (mattery.exoPackMenu.slots[slotId].container == minecraft.player?.inventory && minecraft.player?.containerMenu !is ExopackInventoryMenu)
+				return
+
+			mattery.exoPackMenu.slots[slotId].set(itemStack)
+			mattery.exoPackMenu.stateId = containerState
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		private val LOGGER = LogManager.getLogger()
+
+		val TYPE = CustomPacketPayload.Type<ExopackSlotPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"exopack_slot"
+			)
+		)
+		val CODEC: StreamCodec<RegistryFriendlyByteBuf, ExopackSlotPacket> =
+			StreamCodec.ofMember(ExopackSlotPacket::write, ::read)
+
+		fun read(buff: RegistryFriendlyByteBuf): ExopackSlotPacket {
+			return ExopackSlotPacket(buff.readInt(), buff.readItem(), buff.readInt())
+		}
+	}
+}
+
+class ExopackMenuInitPacket(val slots: List<ItemStack>, val carried: ItemStack, val containerState: Int) : CustomPacketPayload {
+	fun write(buff: RegistryFriendlyByteBuf) {
+		buff.writeInt(slots.size)
+
+		for (item in slots) {
+			buff.writeItem(item)
+		}
+
+		buff.writeItem(carried)
+		buff.writeInt(containerState)
+	}
+
+	fun play(context: IPayloadContext) {
+		val mattery = minecraft.player?.matteryPlayer ?: return
+
+		if (mattery.hasExopack) {
+			mattery.exoPackMenu.initializeContents(containerState, slots, carried)
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<ExopackMenuInitPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"exopack_menu_init"
+			)
+		)
+		val CODEC: StreamCodec<RegistryFriendlyByteBuf, ExopackMenuInitPacket> =
+			StreamCodec.ofMember(ExopackMenuInitPacket::write, ::read)
+
+		fun read(buff: RegistryFriendlyByteBuf): ExopackMenuInitPacket {
+			val size = buff.readInt()
+			val slots = ArrayList<ItemStack>(size)
+
+			for (i in 0 until size) {
+				slots.add(buff.readItem())
+			}
+
+			val carried = buff.readItem()
+			val containerState = buff.readInt()
+
+			return ExopackMenuInitPacket(slots, carried, containerState)
+		}
+	}
+}
+
+object ExopackMenuOpen : CustomPacketPayload {
+	fun play(context: IPayloadContext) {
+		val player = context.player()
+		val mattery = player.matteryPlayer
+
+		if (mattery.hasExopack) {
+			player.containerMenu = mattery.exoPackMenu
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	val TYPE = CustomPacketPayload.Type<ExopackMenuOpen>(
+		ResourceLocation(
+			OverdriveThatMatters.MOD_ID,
+			"exopack_menu_open"
+		)
+	)
+
+	val CODEC: StreamCodec<FriendlyByteBuf, ExopackMenuOpen> =
+		StreamCodec.ofMember({ _, _ -> }, { ExopackMenuOpen })
+}
+
+class PickItemFromInventoryPacket(
+	val targetHotbarSlot: Int,
+	val sourceExosuitSlot: Int
+) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeVarInt(targetHotbarSlot)
+		buff.writeVarInt(sourceExosuitSlot)
+	}
+
+	fun play(context: IPayloadContext) {
+		val player = context.player() as ServerPlayer
+		val mattery = player.matteryPlayer
+
+		if (!mattery.hasExopack || sourceExosuitSlot !in 0 until mattery.exopackContainer.containerSize) {
+			return
+		}
+
+		if (!Inventory.isHotbarSlot(targetHotbarSlot)) {
+			return
+		}
+
+		player.inventory.selected = targetHotbarSlot
+		val existingItem = player.inventory[targetHotbarSlot].copy()
+		val inventoryItem = mattery.exopackContainer[sourceExosuitSlot].copy()
+
+		player.inventory[targetHotbarSlot] = if (inventoryItem.isEmpty) ItemStack.EMPTY else inventoryItem
+		mattery.exopackContainer[sourceExosuitSlot] = if (existingItem.isEmpty) ItemStack.EMPTY else existingItem
+
+		player.connection.send(ClientboundSetCarriedItemPacket(targetHotbarSlot))
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<PickItemFromInventoryPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"pick_item_from_inventory"
+			)
+		)
+		val CODEC: StreamCodec<FriendlyByteBuf, PickItemFromInventoryPacket> =
+			StreamCodec.ofMember(PickItemFromInventoryPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): PickItemFromInventoryPacket {
+			return PickItemFromInventoryPacket(buff.readVarInt(), buff.readVarInt())
+		}
+	}
+}
+
+class SetExopackColorPacket(val color: RGBAColor) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeFloat(color.red)
+		buff.writeFloat(color.green)
+		buff.writeFloat(color.blue)
+	}
+
+	fun play(context: IPayloadContext) {
+		context.player().matteryPlayer.exopackColor = color
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<SetExopackColorPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"set_exopack_color"
+			)
+		)
+		val CODEC: StreamCodec<FriendlyByteBuf, SetExopackColorPacket> =
+			StreamCodec.ofMember(SetExopackColorPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): SetExopackColorPacket {
+			return SetExopackColorPacket(RGBAColor(buff.readFloat(), buff.readFloat(), buff.readFloat()))
+		}
+	}
+}
+
+object DisplayExopackPacket : CustomPacketPayload {
+	fun play(context: IPayloadContext) {
+		context.player().matteryPlayer.isExopackVisible = true
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	val TYPE = CustomPacketPayload.Type<DisplayExopackPacket>(
+		ResourceLocation(
+			OverdriveThatMatters.MOD_ID,
+			"display_exopack"
+		)
+	)
+	val CODEC: StreamCodec<FriendlyByteBuf, DisplayExopackPacket> =
+		StreamCodec.ofMember({ _, _ -> }, { DisplayExopackPacket })
+}
+
+object HideExopackPacket : CustomPacketPayload {
+	fun play(context: IPayloadContext) {
+		context.player().matteryPlayer.isExopackVisible = false
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	val TYPE = CustomPacketPayload.Type<HideExopackPacket>(
+		ResourceLocation(
+			OverdriveThatMatters.MOD_ID,
+			"hide_exopack"
+		)
+	)
+	val CODEC: StreamCodec<FriendlyByteBuf, HideExopackPacket> =
+		StreamCodec.ofMember({ _, _ -> }, { HideExopackPacket })
+}
+
+object EnableExopackGlowPacket : CustomPacketPayload {
+	fun play(context: IPayloadContext) {
+		context.player().matteryPlayer.exopackGlows = true
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	val TYPE = CustomPacketPayload.Type<EnableExopackGlowPacket>(
+		ResourceLocation(
+			OverdriveThatMatters.MOD_ID,
+			"enable_exopack_glow"
+		)
+	)
+	val CODEC: StreamCodec<FriendlyByteBuf, EnableExopackGlowPacket> =
+		StreamCodec.ofMember({ _, _ -> }, { EnableExopackGlowPacket })
+}
+
+object DisableExopackGlowPacket : CustomPacketPayload {
+	fun play(context: IPayloadContext) {
+		context.player().matteryPlayer.exopackGlows = false
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	val TYPE = CustomPacketPayload.Type<DisableExopackGlowPacket>(
+		ResourceLocation(
+			OverdriveThatMatters.MOD_ID,
+			"disable_exopack_glow"
+		)
+	)
+	val CODEC: StreamCodec<FriendlyByteBuf, DisableExopackGlowPacket> =
+		StreamCodec.ofMember({ _, _ -> }, { DisableExopackGlowPacket })
+}
+
+object ResetExopackColorPacket : CustomPacketPayload {
+	fun play(context: IPayloadContext) {
+		context.player().matteryPlayer.exopackColor = null
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	val TYPE = CustomPacketPayload.Type<ResetExopackColorPacket>(
+		ResourceLocation(
+			OverdriveThatMatters.MOD_ID,
+			"reset_exopack_color"
+		)
+	)
+	val CODEC: StreamCodec<FriendlyByteBuf, ResetExopackColorPacket> =
+		StreamCodec.ofMember({ _, _ -> }, { ResetExopackColorPacket })
+}
+
+class ExopackSmokePacket(val player: UUID) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeUUID(player)
+	}
+
+	fun play(context: IPayloadContext) {
+		// minecraft.player?.level()?.getPlayerByUUID(player)?.matteryPlayer?.spawnExopackSmoke = true
+
+		minecraft.player?.level()?.getPlayerByUUID(player)?.let { ply ->
+			if (ply != minecraft.player || minecraft.gameRenderer.mainCamera.isDetached) {
+				var (x, y, z) = ply.position
+
+				y += 1.5
+				val deg = toRadians(ply.yBodyRot + 90f)
+
+				x += kotlin.math.cos(deg) * -0.4
+				z += kotlin.math.sin(deg) * -0.4
+
+				val level = ply.level()
+				val random = level.random
+
+				for (i in 0 .. random.nextInt(2, 4))
+					level.addParticle(
+						ParticleTypes.SMOKE,
+						x + random.nextDouble() * 0.4 - 0.2,
+						y + random.nextDouble() * 0.4 - 0.2,
+						z + random.nextDouble() * 0.4 - 0.2,
+						random.nextGaussian() * 0.02,
+						random.nextGaussian() * 0.02,
+						random.nextGaussian() * 0.02)
+			}
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<ExopackSmokePacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"exopack_smoke"
+			)
+		)
+		val CODEC: StreamCodec<FriendlyByteBuf, ExopackSmokePacket> =
+			StreamCodec.ofMember(ExopackSmokePacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): ExopackSmokePacket {
+			return ExopackSmokePacket(buff.readUUID())
+		}
+	}
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuDataPacket.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuDataPacket.kt
new file mode 100644
index 000000000..be8e83470
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuDataPacket.kt
@@ -0,0 +1,56 @@
+package ru.dbotthepony.mc.otm.network
+
+import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
+import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.capability.matteryPlayer
+import ru.dbotthepony.mc.otm.client.minecraft
+import ru.dbotthepony.mc.otm.core.ResourceLocation
+import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu
+import ru.dbotthepony.mc.otm.menu.MatteryMenu
+import java.io.ByteArrayInputStream
+
+class MenuDataPacket(val containerId: Int, val bytes: ByteArray, val length: Int) : CustomPacketPayload {
+	constructor(containerId: Int, stream: FastByteArrayOutputStream) : this(containerId, stream.array, stream.length)
+
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeVarInt(containerId)
+		buff.writeBytes(bytes, 0, length)
+	}
+
+	fun play(context: IPayloadContext) {
+		if (containerId == ExopackInventoryMenu.CONTAINER_ID) {
+			minecraft.player?.matteryPlayer?.exoPackMenu?.mSynchronizer?.read(ByteArrayInputStream(bytes, 0, length))
+		} else {
+			val menu = minecraft.player?.containerMenu as? MatteryMenu ?: return
+
+			if (menu.containerId == containerId)
+				menu.mSynchronizer.read(ByteArrayInputStream(bytes, 0, length))
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<MenuDataPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"menu_data"
+			)
+		)
+
+		val CODEC: StreamCodec<FriendlyByteBuf, MenuDataPacket> =
+			StreamCodec.ofMember(MenuDataPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): MenuDataPacket {
+			val containerId = buff.readVarInt()
+			val readable = buff.readableBytes()
+			return MenuDataPacket(containerId, ByteArray(readable).also(buff::readBytes), readable)
+		}
+	}
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt
deleted file mode 100644
index dacc8ad7f..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-package ru.dbotthepony.mc.otm.network
-
-import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
-import net.minecraft.network.FriendlyByteBuf
-import net.minecraft.world.item.ItemStack
-import net.minecraftforge.network.NetworkDirection
-import ru.dbotthepony.mc.otm.capability.matteryPlayer
-import ru.dbotthepony.mc.otm.client.minecraft
-import ru.dbotthepony.mc.otm.compat.vanilla.InventoryScrollPacket
-import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu
-import ru.dbotthepony.mc.otm.menu.matter.CancelTaskPacket
-import ru.dbotthepony.mc.otm.menu.MatteryMenu
-import ru.dbotthepony.mc.otm.menu.matter.PatternsChangePacket
-import ru.dbotthepony.mc.otm.menu.matter.ReplicationRequestPacket
-import ru.dbotthepony.mc.otm.menu.matter.TasksChangePacket
-import ru.dbotthepony.mc.otm.menu.data.ClearItemViewPacket
-import ru.dbotthepony.mc.otm.menu.data.ItemViewInteractPacket
-import ru.dbotthepony.mc.otm.menu.data.StackAddPacket
-import ru.dbotthepony.mc.otm.menu.data.StackChangePacket
-import ru.dbotthepony.mc.otm.menu.data.StackRemovePacket
-import java.io.ByteArrayInputStream
-
-class MenuFieldPacket(val containerId: Int, val bytes: ByteArray, val length: Int) : MatteryPacket {
-	constructor(containerId: Int, stream: FastByteArrayOutputStream) : this(containerId, stream.array, stream.length)
-
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeVarInt(containerId)
-		buff.writeBytes(bytes, 0, length)
-	}
-
-	override fun play(context: MNetworkContext) {
-		if (containerId == ExopackInventoryMenu.CONTAINER_ID) {
-			minecraft.player?.matteryPlayer?.exoPackMenu?.mSynchronizer?.read(ByteArrayInputStream(bytes, 0, length))
-		} else {
-			val menu = minecraft.player?.containerMenu as? MatteryMenu ?: return
-
-			if (menu.containerId == containerId)
-				menu.mSynchronizer.read(ByteArrayInputStream(bytes, 0, length))
-		}
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): MenuFieldPacket {
-			val containerId = buff.readVarInt()
-			val readable = buff.readableBytes()
-			return MenuFieldPacket(containerId, ByteArray(readable).also(buff::readBytes), readable)
-		}
-	}
-}
-
-class SetCarriedPacket(val item: ItemStack) : MatteryPacket {
-	override fun write(buff: FriendlyByteBuf) {
-		buff.writeItem(item)
-	}
-
-	override fun play(context: MNetworkContext) {
-		minecraft.player?.containerMenu?.carried = item
-	}
-
-	companion object {
-		fun read(buff: FriendlyByteBuf): SetCarriedPacket {
-			return SetCarriedPacket(buff.readItem())
-		}
-	}
-}
-
-object MenuNetworkChannel : MatteryNetworkChannel(
-	version = 5,
-	name = "menu"
-) {
-	fun register() {
-		add(SetCarriedPacket::class.java, SetCarriedPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-
-		// networked item view
-		add(ClearItemViewPacket::class.java, { ClearItemViewPacket }, NetworkDirection.PLAY_TO_CLIENT)
-		add(ItemViewInteractPacket::class.java, ItemViewInteractPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
-		add(StackAddPacket::class.java, StackAddPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-		add(StackChangePacket::class.java, StackChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-		add(StackRemovePacket::class.java, StackRemovePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-
-		// Menu data
-		// Server->Client
-		add(MenuFieldPacket::class.java, MenuFieldPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-		// Client->Server
-		add(MatteryMenu.PlayerInputPacket::class.java, MatteryMenu::PlayerInputPacket, NetworkDirection.PLAY_TO_SERVER)
-
-		// matter panel menu
-		add(CancelTaskPacket::class.java, CancelTaskPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
-		add(PatternsChangePacket::class.java, PatternsChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-		add(TasksChangePacket::class.java, TasksChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
-		add(ReplicationRequestPacket::class.java, ReplicationRequestPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
-
-		add(InventoryScrollPacket::class.java, InventoryScrollPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/NetworkPackets.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/NetworkPackets.kt
new file mode 100644
index 000000000..516ee51ee
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/NetworkPackets.kt
@@ -0,0 +1,103 @@
+package ru.dbotthepony.mc.otm.network
+
+import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent
+import net.neoforged.neoforge.network.registration.HandlerThread
+import ru.dbotthepony.mc.otm.android.AndroidResearchManager
+import ru.dbotthepony.mc.otm.android.feature.ItemEntityDataPacket
+import ru.dbotthepony.mc.otm.compat.vanilla.InventoryScrollPacket
+import ru.dbotthepony.mc.otm.item.QuantumBatteryItem
+import ru.dbotthepony.mc.otm.matter.MatterManager
+import ru.dbotthepony.mc.otm.menu.MatteryMenu
+import ru.dbotthepony.mc.otm.menu.data.ClearItemViewPacket
+import ru.dbotthepony.mc.otm.menu.data.ItemViewInteractPacket
+import ru.dbotthepony.mc.otm.menu.data.StackAddPacket
+import ru.dbotthepony.mc.otm.menu.data.StackChangePacket
+import ru.dbotthepony.mc.otm.menu.data.StackRemovePacket
+import ru.dbotthepony.mc.otm.menu.matter.CancelTaskPacket
+import ru.dbotthepony.mc.otm.menu.matter.PatternsChangePacket
+import ru.dbotthepony.mc.otm.menu.matter.ReplicationRequestPacket
+import ru.dbotthepony.mc.otm.menu.matter.TasksChangePacket
+
+fun registerNetworkPackets(event: RegisterPayloadHandlersEvent) {
+	val r = event.registrar("1.5.0")
+
+	// world
+	r
+		.playToClient(BlockEntitySyncPacket.TYPE, BlockEntitySyncPacket.CODEC, BlockEntitySyncPacket::play)
+		.playToClient(SmokeParticlesPacket.TYPE, SmokeParticlesPacket.CODEC, SmokeParticlesPacket::play)
+
+	// items
+	r
+		.playToClient(QuantumBatteryItem.PACKET_TYPE, QuantumBatteryItem.PACKET_CODEC, QuantumBatteryItem.ChargePacket::play)
+		.playToClient(ItemEntityDataPacket.TYPE, ItemEntityDataPacket.CODEC, ItemEntityDataPacket::play)
+
+	// registries
+	r.executesOn(HandlerThread.NETWORK)
+		.playToClient(AndroidResearchManager.SYNC_TYPE, AndroidResearchManager.SYNC_CODEC, AndroidResearchManager.SyncPacket::play)
+		.playToClient(MatterManager.SYNC_TYPE, MatterManager.SYNC_CODEC, MatterManager.SyncPacket::play)
+
+	// exopack
+	r
+		.playToClient(ExopackCarriedPacket.TYPE, ExopackCarriedPacket.CODEC, ExopackCarriedPacket::play)
+		.playToClient(ExopackSlotPacket.TYPE, ExopackSlotPacket.CODEC, ExopackSlotPacket::play)
+		.playToClient(ExopackMenuInitPacket.TYPE, ExopackMenuInitPacket.CODEC, ExopackMenuInitPacket::play)
+		.playToClient(ExopackSmokePacket.TYPE, ExopackSmokePacket.CODEC, ExopackSmokePacket::play)
+		.playToServer(ExopackMenuOpen.TYPE, ExopackMenuOpen.CODEC, ExopackMenuOpen::play)
+
+		.playToServer(PickItemFromInventoryPacket.TYPE, PickItemFromInventoryPacket.CODEC, PickItemFromInventoryPacket::play)
+
+		.playToServer(DisplayExopackPacket.TYPE, DisplayExopackPacket.CODEC, DisplayExopackPacket::play)
+		.playToServer(HideExopackPacket.TYPE, HideExopackPacket.CODEC, HideExopackPacket::play)
+		.playToServer(EnableExopackGlowPacket.TYPE, EnableExopackGlowPacket.CODEC, EnableExopackGlowPacket::play)
+		.playToServer(DisableExopackGlowPacket.TYPE, DisableExopackGlowPacket.CODEC, DisableExopackGlowPacket::play)
+		.playToServer(ResetExopackColorPacket.TYPE, ResetExopackColorPacket.CODEC, ResetExopackColorPacket::play)
+		.playToServer(SetExopackColorPacket.TYPE, SetExopackColorPacket.CODEC, SetExopackColorPacket::play)
+
+	// otm player general
+	r
+		.playToClient(MatteryPlayerDataPacket.TYPE, MatteryPlayerDataPacket.CODEC, MatteryPlayerDataPacket::play)
+
+	// android
+	r
+		.playToClient(PlayerIterationPacket.TYPE, PlayerIterationPacket.CODEC, PlayerIterationPacket::play)
+		.playToClient(GlitchPacket.TYPE, GlitchPacket.CODEC, GlitchPacket::play)
+		.playToClient(ShockwaveEffectPacket.TYPE, ShockwaveEffectPacket.CODEC, ShockwaveEffectPacket::play)
+
+		.playToServer(AndroidResearchRequestPacket.TYPE, AndroidResearchRequestPacket.CODEC, AndroidResearchRequestPacket::play)
+		.playToClient(AndroidResearchSyncPacket.TYPE, AndroidResearchSyncPacket.CODEC, AndroidResearchSyncPacket::play)
+
+		.playToClient(AndroidFeatureSyncPacket.TYPE, AndroidFeatureSyncPacket.CODEC, AndroidFeatureSyncPacket::play)
+		.playToClient(AndroidFeatureRemovePacket.TYPE, AndroidFeatureRemovePacket.CODEC, AndroidFeatureRemovePacket::play)
+
+		.playToServer(SwitchAndroidFeaturePacket.TYPE, SwitchAndroidFeaturePacket.CODEC, SwitchAndroidFeaturePacket::play)
+		.playToServer(ActivateAndroidFeaturePacket.TYPE, ActivateAndroidFeaturePacket.CODEC, ActivateAndroidFeaturePacket::play)
+
+		.playToServer(TriggerShockwavePacket.TYPE, TriggerShockwavePacket.CODEC, TriggerShockwavePacket::play)
+		.playToServer(TriggerJumpBoostPacket.TYPE, TriggerJumpBoostPacket.CODEC, TriggerJumpBoostPacket::play)
+
+	// menus
+	r
+		// carried item stack set
+		.playToClient(SetCarriedPacket.TYPE, SetCarriedPacket.CODEC, SetCarriedPacket::play)
+
+		// server->client data
+		.playToClient(MenuDataPacket.TYPE, MenuDataPacket.CODEC, MenuDataPacket::play)
+		// client->server input
+		.playToServer(MatteryMenu.PlayerInputPacket.TYPE, MatteryMenu.PlayerInputPacket.CODEC, MatteryMenu.PlayerInputPacket::play)
+
+		// extended inventory compat
+		.playToServer(InventoryScrollPacket.TYPE, InventoryScrollPacket.CODEC, InventoryScrollPacket::play)
+
+		// matter replication menu
+		.playToClient(PatternsChangePacket.TYPE, PatternsChangePacket.CODEC, PatternsChangePacket::play)
+		.playToClient(TasksChangePacket.TYPE, TasksChangePacket.CODEC, TasksChangePacket::play)
+		.playToServer(ReplicationRequestPacket.TYPE, ReplicationRequestPacket.CODEC, ReplicationRequestPacket::play)
+		.playToServer(CancelTaskPacket.TYPE, CancelTaskPacket.CODEC, CancelTaskPacket::play)
+
+		// item view
+		.playToClient(StackAddPacket.TYPE, StackAddPacket.CODEC, StackAddPacket::play)
+		.playToClient(StackChangePacket.TYPE, StackChangePacket.CODEC, StackChangePacket::play)
+		.playToClient(StackRemovePacket.TYPE, StackRemovePacket.CODEC, StackRemovePacket::play)
+		.playToClient(ClearItemViewPacket.TYPE, ClearItemViewPacket.CODEC, ClearItemViewPacket::play)
+		.playToServer(ItemViewInteractPacket.TYPE, ItemViewInteractPacket.CODEC, ItemViewInteractPacket::play)
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/SetCarriedPacket.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/SetCarriedPacket.kt
new file mode 100644
index 000000000..24651ff9b
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/SetCarriedPacket.kt
@@ -0,0 +1,42 @@
+package ru.dbotthepony.mc.otm.network
+
+import net.minecraft.network.RegistryFriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
+import net.minecraft.world.item.ItemStack
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.client.minecraft
+import ru.dbotthepony.mc.otm.core.ResourceLocation
+import ru.dbotthepony.mc.otm.core.readItem
+import ru.dbotthepony.mc.otm.core.writeItem
+
+class SetCarriedPacket(val item: ItemStack) : CustomPacketPayload {
+	fun write(buff: RegistryFriendlyByteBuf) {
+		buff.writeItem(item)
+	}
+
+	fun play(context: IPayloadContext) {
+		minecraft.player?.containerMenu?.carried = item
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<SetCarriedPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"menu_set_carried"
+			)
+		)
+
+		val CODEC: StreamCodec<RegistryFriendlyByteBuf, SetCarriedPacket> =
+			StreamCodec.ofMember(SetCarriedPacket::write, ::read)
+
+		fun read(buff: RegistryFriendlyByteBuf): SetCarriedPacket {
+			return SetCarriedPacket(buff.readItem())
+		}
+	}
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/ShockwaveEffectPacket.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/ShockwaveEffectPacket.kt
new file mode 100644
index 000000000..b5b125ca7
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/ShockwaveEffectPacket.kt
@@ -0,0 +1,41 @@
+package ru.dbotthepony.mc.otm.network
+
+import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.client.render.ShockwaveRenderer
+import ru.dbotthepony.mc.otm.core.ResourceLocation
+import ru.dbotthepony.mc.otm.core.math.Vector
+
+class ShockwaveEffectPacket(val pos: Vector) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeDouble(pos.x)
+		buff.writeDouble(pos.y)
+		buff.writeDouble(pos.z)
+	}
+
+	fun play(context: IPayloadContext) {
+		ShockwaveRenderer.handle(this)
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE = CustomPacketPayload.Type<ShockwaveEffectPacket>(
+			ResourceLocation(
+				OverdriveThatMatters.MOD_ID,
+				"shockwave_effect"
+			)
+		)
+		val CODEC: StreamCodec<FriendlyByteBuf, ShockwaveEffectPacket> =
+			StreamCodec.ofMember(ShockwaveEffectPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): ShockwaveEffectPacket {
+			return ShockwaveEffectPacket(Vector(buff.readDouble(), buff.readDouble(), buff.readDouble()))
+		}
+	}
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/SmokeParticlesPacket.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/SmokeParticlesPacket.kt
new file mode 100644
index 000000000..a80bb1ec5
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/SmokeParticlesPacket.kt
@@ -0,0 +1,57 @@
+package ru.dbotthepony.mc.otm.network
+
+import net.minecraft.core.particles.ParticleTypes
+import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload
+import net.minecraft.server.level.ServerLevel
+import net.minecraft.server.level.ServerPlayer
+import net.minecraft.util.RandomSource
+import net.minecraft.world.level.Level
+import net.neoforged.neoforge.network.PacketDistributor
+import net.neoforged.neoforge.network.handling.IPayloadContext
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.client.minecraft
+import ru.dbotthepony.mc.otm.core.ResourceLocation
+
+class SmokeParticlesPacket(val x: Double, val y: Double, val z: Double) : CustomPacketPayload {
+	fun write(buff: FriendlyByteBuf) {
+		buff.writeDouble(x)
+		buff.writeDouble(y)
+		buff.writeDouble(z)
+	}
+
+	fun play(context: IPayloadContext) {
+		minecraft.player?.level()?.let {
+			makeSmoke(x, y, z, it.random, it)
+		}
+	}
+
+	override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> {
+		return TYPE
+	}
+
+	companion object {
+		val TYPE =
+			CustomPacketPayload.Type<SmokeParticlesPacket>(ResourceLocation(OverdriveThatMatters.MOD_ID, "smoke"))
+		val CODEC: StreamCodec<FriendlyByteBuf, SmokeParticlesPacket> =
+			StreamCodec.ofMember(SmokeParticlesPacket::write, ::read)
+
+		fun read(buff: FriendlyByteBuf): SmokeParticlesPacket {
+			return SmokeParticlesPacket(buff.readDouble(), buff.readDouble(), buff.readDouble())
+		}
+
+		fun makeSmoke(x: Double, y: Double, z: Double, level: ServerLevel) {
+			PacketDistributor.sendToPlayersNear(level, null, x, y, z, 64.0, SmokeParticlesPacket(x, y, z))
+		}
+
+		fun makeSmoke(excluded: ServerPlayer, x: Double, y: Double, z: Double) {
+			PacketDistributor.sendToPlayersNear(excluded.serverLevel(), excluded, x, y, z, 64.0, SmokeParticlesPacket(x, y, z))
+		}
+
+		fun makeSmoke(x: Double, y: Double, z: Double, random: RandomSource, level: Level) {
+			for (i in 0 .. random.nextInt(4, 8))
+				level.addParticle(ParticleTypes.POOF, x + random.nextDouble() * 0.5 - 0.25, y + random.nextDouble() * 0.5 - 0.25, z + random.nextDouble() * 0.5 - 0.25, random.nextGaussian() * 0.02, random.nextGaussian() * 0.02, random.nextGaussian() * 0.02)
+		}
+	}
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt
new file mode 100644
index 000000000..a9803ffac
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt
@@ -0,0 +1,13 @@
+package ru.dbotthepony.mc.otm.network
+
+import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+
+// because mojang didn't bother
+object StreamCodecs {
+	val INT: StreamCodec<FriendlyByteBuf, Int> = StreamCodec.of(FriendlyByteBuf::writeInt, FriendlyByteBuf::readInt)
+	val LONG: StreamCodec<FriendlyByteBuf, Long> = StreamCodec.of(FriendlyByteBuf::writeLong, FriendlyByteBuf::readLong)
+	val DOUBLE: StreamCodec<FriendlyByteBuf, Double> = StreamCodec.of(FriendlyByteBuf::writeDouble, FriendlyByteBuf::readDouble)
+	val FLOAT: StreamCodec<FriendlyByteBuf, Float> = StreamCodec.of(FriendlyByteBuf::writeFloat, FriendlyByteBuf::readFloat)
+	val BOOLEAN: StreamCodec<FriendlyByteBuf, Boolean> = StreamCodec.of(FriendlyByteBuf::writeBoolean, FriendlyByteBuf::readBoolean)
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/WeaponNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/WeaponNetworkChannel.kt
deleted file mode 100644
index 6c54049f6..000000000
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/WeaponNetworkChannel.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package ru.dbotthepony.mc.otm.network
-
-import net.minecraftforge.network.NetworkDirection
-import ru.dbotthepony.mc.otm.item.weapon.WeaponFireInputPacket
-import ru.dbotthepony.mc.otm.item.weapon.WeaponScopePacket
-
-object WeaponNetworkChannel : MatteryNetworkChannel(
-	version = 3,
-	name = "weapon"
-) {
-	fun register() {
-		add(WeaponScopePacket::class.java, WeaponScopePacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
-		add(WeaponFireInputPacket::class.java, WeaponFireInputPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
-	}
-}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IMatteryRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IMatteryRecipe.kt
index 8d3e9ae30..83ff38568 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IMatteryRecipe.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IMatteryRecipe.kt
@@ -1,13 +1,10 @@
 package ru.dbotthepony.mc.otm.recipe
 
 import net.minecraft.core.NonNullList
-import net.minecraft.world.Container
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.crafting.Ingredient
 import net.minecraft.world.item.crafting.Recipe
 import net.minecraft.world.item.crafting.RecipeInput
-import net.minecraft.world.item.crafting.RecipeSerializer
-import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
 
 // passthrough all default methods to fix Kotlin bug related to implementation delegation not properly working on Java interfaces
 // https://youtrack.jetbrains.com/issue/KT-55080/Change-the-behavior-of-inheritance-delegation-to-delegates-implementing-Java-interfaces-with-default-methods
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IngredientMatrix.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IngredientMatrix.kt
index a9d6a96d9..407806a1c 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IngredientMatrix.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IngredientMatrix.kt
@@ -1,19 +1,15 @@
 package ru.dbotthepony.mc.otm.recipe
 
 import net.minecraft.core.NonNullList
-import net.minecraft.world.inventory.CraftingContainer
 import net.minecraft.world.item.crafting.CraftingInput
 import net.minecraft.world.item.crafting.Ingredient
-import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.container.get
 import ru.dbotthepony.mc.otm.core.collect.allEqual
 import ru.dbotthepony.mc.otm.core.collect.any
 import ru.dbotthepony.mc.otm.core.collect.flatMap
 import ru.dbotthepony.mc.otm.core.collect.map
 import ru.dbotthepony.mc.otm.core.get
-import ru.dbotthepony.mc.otm.core.isActuallyEmpty
 import ru.dbotthepony.mc.otm.core.isNotEmpty
-import ru.dbotthepony.mc.otm.core.util.countingLazy
+import ru.dbotthepony.mc.otm.core.util.InvalidableLazy
 import java.util.function.Predicate
 
 interface IIngredientMatrix : Predicate<CraftingInput>, Iterable<Ingredient> {
@@ -23,7 +19,7 @@ interface IIngredientMatrix : Predicate<CraftingInput>, Iterable<Ingredient> {
 		get() = width == 0 || height == 0
 
 	val isIncomplete: Boolean
-		get() = iterator().any { it.isActuallyEmpty }
+		get() = iterator().any { it.hasNoItems() }
 
 	operator fun get(column: Int, row: Int): Ingredient
 
@@ -123,7 +119,7 @@ interface IIngredientMatrix : Predicate<CraftingInput>, Iterable<Ingredient> {
 class IngredientMatrix(override val width: Int, override val height: Int) : IIngredientMatrix {
 	private val data = Array(width * height) { Ingredient.EMPTY }
 
-	private val lazy = countingLazy(OverdriveThatMatters.INGREDIENT_CACHE_INVALIDATION_COUNTER) {
+	private val lazy = InvalidableLazy.Impl {
 		super.isIncomplete
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt
index a95f676e5..689182a1b 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt
@@ -6,24 +6,18 @@ import net.minecraft.core.HolderLookup
 import net.minecraft.core.NonNullList
 import net.minecraft.core.RegistryAccess
 import net.minecraft.core.UUIDUtil
-import net.minecraft.data.recipes.FinishedRecipe
 import net.minecraft.resources.ResourceLocation
-import net.minecraft.world.inventory.CraftingContainer
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.item.crafting.CraftingInput
 import net.minecraft.world.item.crafting.Ingredient
 import net.minecraft.world.item.crafting.RecipeSerializer
 import net.minecraft.world.item.crafting.RecipeType
 import net.minecraft.world.level.Level
-import ru.dbotthepony.mc.otm.capability.matter.matter
 import ru.dbotthepony.mc.otm.capability.matteryEnergy
-import ru.dbotthepony.mc.otm.container.util.iterator
 import ru.dbotthepony.mc.otm.core.collect.filterNotNull
 import ru.dbotthepony.mc.otm.core.collect.map
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.nbt.set
-import ru.dbotthepony.mc.otm.core.tagNotNull
-import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
 import ru.dbotthepony.mc.otm.data.DecimalCodec
 import ru.dbotthepony.mc.otm.data.IngredientMatrixCodec
 import ru.dbotthepony.mc.otm.data.minRange
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatteryCookingRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatteryCookingRecipe.kt
index f3bb8f4dd..cce6310d7 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatteryCookingRecipe.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatteryCookingRecipe.kt
@@ -4,23 +4,15 @@ import com.mojang.serialization.Codec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.core.HolderLookup
 import net.minecraft.core.NonNullList
-import net.minecraft.core.RegistryAccess
 import net.minecraft.data.recipes.FinishedRecipe
-import net.minecraft.network.FriendlyByteBuf
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.util.valueproviders.ConstantFloat
 import net.minecraft.util.valueproviders.FloatProvider
-import net.minecraft.world.Container
 import net.minecraft.world.item.ItemStack
-import net.minecraft.world.item.Items
 import net.minecraft.world.item.crafting.*
 import net.minecraft.world.level.Level
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.container.get
-import ru.dbotthepony.mc.otm.core.get
-import ru.dbotthepony.mc.otm.core.isActuallyEmpty
 import ru.dbotthepony.mc.otm.core.registryName
-import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
 import ru.dbotthepony.mc.otm.data.minRange
 import ru.dbotthepony.mc.otm.registry.MItems
 import ru.dbotthepony.mc.otm.registry.MRecipes
@@ -31,19 +23,12 @@ abstract class MatteryCookingRecipe(
 	val count: Int = 1,
 	val workTime: Int = 200,
 	val experience: FloatProvider = ConstantFloat.ZERO
-) : Recipe<RecipeInput> {
-	override fun matches(container: RecipeInput, level: Level): Boolean {
+) : Recipe<SingleRecipeInput> {
+	override fun matches(container: SingleRecipeInput, level: Level): Boolean {
 		if (isIncomplete)
 			return false
 
-		return input.test(container[0])
-	}
-
-	fun matches(container: RecipeInput, slot: Int): Boolean {
-		if (isIncomplete)
-			return false
-
-		return input.test(container[slot])
+		return input.test(container.item)
 	}
 
 	private val outputStack: ItemStack by lazy {
@@ -63,9 +48,9 @@ abstract class MatteryCookingRecipe(
 		return NonNullList.of(Ingredient.EMPTY, input)
 	}
 
-	override fun isIncomplete(): Boolean = input.isActuallyEmpty || output.isActuallyEmpty
+	override fun isIncomplete(): Boolean = input.hasNoItems() || output.hasNoItems()
 
-	override fun assemble(container: RecipeInput, registry: HolderLookup.Provider): ItemStack = outputStack.copy()
+	override fun assemble(container: SingleRecipeInput, registry: HolderLookup.Provider): ItemStack = outputStack.copy()
 
 	override fun canCraftInDimensions(width: Int, height: Int): Boolean = true
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt
index 124d68946..da1f91b92 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt
@@ -8,7 +8,6 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap
 import it.unimi.dsi.fastutil.objects.Object2IntMaps
 import net.minecraft.core.HolderLookup
 import net.minecraft.core.NonNullList
-import net.minecraft.core.RegistryAccess
 import net.minecraft.data.recipes.FinishedRecipe
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.util.StringRepresentable
@@ -25,7 +24,6 @@ import ru.dbotthepony.mc.otm.core.get
 import ru.dbotthepony.mc.otm.core.isActuallyEmpty
 import ru.dbotthepony.mc.otm.core.nbt.set
 import ru.dbotthepony.mc.otm.core.tagNotNull
-import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
 import ru.dbotthepony.mc.otm.data.PredicatedCodecList
 import ru.dbotthepony.mc.otm.data.minRange
 import ru.dbotthepony.mc.otm.registry.MItems
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt
index cba8be8c9..84e49d68b 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt
@@ -4,7 +4,6 @@ import com.mojang.serialization.Codec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.core.HolderLookup
 import net.minecraft.core.NonNullList
-import net.minecraft.core.RegistryAccess
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.util.valueproviders.ConstantFloat
 import net.minecraft.util.valueproviders.FloatProvider
@@ -19,10 +18,8 @@ import net.minecraft.world.level.Level
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.container.get
 import ru.dbotthepony.mc.otm.core.get
-import ru.dbotthepony.mc.otm.core.isActuallyEmpty
 import ru.dbotthepony.mc.otm.registry.MRecipes
 import ru.dbotthepony.mc.otm.core.registryName
-import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
 import ru.dbotthepony.mc.otm.data.minRange
 import ru.dbotthepony.mc.otm.registry.MItems
 
@@ -65,7 +62,7 @@ class PlatePressRecipe(
 	}
 
 	override fun isIncomplete(): Boolean {
-		return input.isActuallyEmpty || output.isActuallyEmpty
+		return input.hasNoItems() || output.hasNoItems()
 	}
 
 	override fun assemble(p_44001_: RecipeInput, registry: HolderLookup.Provider): ItemStack = outputStack.copy()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/UpgradeRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/UpgradeRecipe.kt
index ea8bddc0d..c00371944 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/UpgradeRecipe.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/UpgradeRecipe.kt
@@ -1,16 +1,13 @@
 package ru.dbotthepony.mc.otm.recipe
 
 import com.google.common.collect.ImmutableList
-import com.google.gson.JsonObject
-import com.google.gson.JsonPrimitive
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.core.NonNullList
 import net.minecraft.core.RegistryAccess
 import net.minecraft.nbt.CompoundTag
-import net.minecraft.network.FriendlyByteBuf
 import net.minecraft.resources.ResourceLocation
-import net.minecraft.util.GsonHelper
 import net.minecraft.util.StringRepresentable
 import net.minecraft.world.inventory.CraftingContainer
 import net.minecraft.world.item.ItemStack
@@ -25,12 +22,7 @@ import net.minecraftforge.common.crafting.IShapedRecipe
 import ru.dbotthepony.mc.otm.container.util.stream
 import ru.dbotthepony.mc.otm.core.nbt.set
 import ru.dbotthepony.mc.otm.core.registryName
-import ru.dbotthepony.mc.otm.core.set
 import ru.dbotthepony.mc.otm.core.tagNotNull
-import ru.dbotthepony.mc.otm.core.util.readBinaryJson
-import ru.dbotthepony.mc.otm.core.util.writeBinaryJson
-import ru.dbotthepony.mc.otm.core.collect.stream
-import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
 import ru.dbotthepony.mc.otm.data.SingletonCodec
 import java.util.stream.Stream
 
@@ -91,14 +83,14 @@ class UpgradeRecipe(
 
 	enum class OpType : StringRepresentable {
 		DIRECT {
-			override val codec: Codec<Direct> = RecordCodecBuilder.create {
+			override val codec: MapCodec<Direct> = RecordCodecBuilder.mapCodec {
 				it.group(
 					Codec.STRING.fieldOf("path").forGetter(Direct::path)
 				).apply(it, ::Direct)
 			}
 		},
 		INDIRECT {
-			override val codec: Codec<Indirect> = RecordCodecBuilder.create {
+			override val codec: MapCodec<Indirect> = RecordCodecBuilder.mapCodec {
 				it.group(
 					Codec.STRING.fieldOf("source").forGetter(Indirect::pathSource),
 					Codec.STRING.fieldOf("destination").forGetter(Indirect::pathDestination),
@@ -106,7 +98,7 @@ class UpgradeRecipe(
 			}
 		},
 		ALL {
-			override val codec: Codec<All> by lazy { SingletonCodec(All) }
+			override val codec: MapCodec<All> by lazy { MapCodec.unit(All) }
 		},
 		;
 
@@ -114,7 +106,7 @@ class UpgradeRecipe(
 			return name.lowercase()
 		}
 
-		abstract val codec: Codec<out Op>
+		abstract val codec: MapCodec<out Op>
 	}
 
 	sealed class Op {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt
index c8c195727..6b19e61ed 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt
@@ -1,8 +1,6 @@
 package ru.dbotthepony.mc.otm.registry
 
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
-import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.mc.otm.android.AndroidFeatureType
 import ru.dbotthepony.mc.otm.android.DummyAndroidFeature
 import ru.dbotthepony.mc.otm.android.feature.*
@@ -25,7 +23,7 @@ object AndroidFeatures {
 	val JUMP_BOOST by registry.register(MNames.JUMP_BOOST) { AndroidFeatureType(::JumpBoostFeature) }
 	val ENDER_TELEPORTER by registry.register(MNames.ENDER_TELEPORTER) { AndroidFeatureType(::EnderTeleporterFeature) }
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registry.register(bus)
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CapabilitiesRegisterListener.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CapabilitiesRegisterListener.kt
new file mode 100644
index 000000000..945c89500
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CapabilitiesRegisterListener.kt
@@ -0,0 +1,7 @@
+package ru.dbotthepony.mc.otm.registry
+
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
+
+interface CapabilitiesRegisterListener {
+	fun registerCapabilities(event: RegisterCapabilitiesEvent)
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/LootModifiers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/LootModifiers.kt
index 8ae067580..4b11c5876 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/LootModifiers.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/LootModifiers.kt
@@ -1,19 +1,18 @@
 package ru.dbotthepony.mc.otm.registry
 
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.registries.NeoForgeRegistries
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.data.loot.LootPoolAppender
 
 object LootModifiers {
-	private val registry = DeferredRegister.create(ForgeRegistries.Keys.GLOBAL_LOOT_MODIFIER_SERIALIZERS, OverdriveThatMatters.MOD_ID)
+	private val registry = MDeferredRegister(NeoForgeRegistries.Keys.GLOBAL_LOOT_MODIFIER_SERIALIZERS, OverdriveThatMatters.MOD_ID)
 
 	init {
 		registry.register("loot_appender") { LootPoolAppender.CODEC }
 	}
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registry.register(bus)
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MArmorMaterials.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MArmorMaterials.kt
new file mode 100644
index 000000000..8a9916df7
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MArmorMaterials.kt
@@ -0,0 +1,59 @@
+package ru.dbotthepony.mc.otm.registry
+
+import net.minecraft.core.registries.BuiltInRegistries
+import net.minecraft.sounds.SoundEvents
+import net.minecraft.world.item.ArmorItem
+import net.minecraft.world.item.ArmorMaterial
+import net.minecraft.world.item.crafting.Ingredient
+import net.neoforged.bus.api.IEventBus
+import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
+
+// why is this even a thing holy shit
+object MArmorMaterials {
+	private val registrar = MDeferredRegister(BuiltInRegistries.ARMOR_MATERIAL)
+
+	fun register(bus: IEventBus) {
+		registrar.register(bus)
+	}
+
+	val TRITANIUM = registrar.register("tritanium") {
+		ArmorMaterial(
+			mapOf(
+				ArmorItem.Type.HELMET to 4,
+				ArmorItem.Type.CHESTPLATE to 9,
+				ArmorItem.Type.LEGGINGS to 7,
+				ArmorItem.Type.BOOTS to 3,
+			),
+			9,
+			SoundEvents.ARMOR_EQUIP_IRON,
+			{ Ingredient.of(MItemTags.REINFORCED_TRITANIUM_PLATES) },
+			listOf(
+				ArmorMaterial.Layer(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/models/armor/tritanium_armor_base.png")),
+				ArmorMaterial.Layer(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/models/armor/tritanium_armor_overlay.png"), "", true),
+			),
+			1f,
+			0.08f
+		)
+	}
+
+	val SIMPLE_TRITANIUM = registrar.register("simple_tritanium") {
+		ArmorMaterial(
+			mapOf(
+				ArmorItem.Type.HELMET to 2,
+				ArmorItem.Type.CHESTPLATE to 6,
+				ArmorItem.Type.LEGGINGS to 7,
+				ArmorItem.Type.BOOTS to 2,
+			),
+			9,
+			SoundEvents.ARMOR_EQUIP_IRON,
+			{ Ingredient.of(MItemTags.TRITANIUM_INGOTS) },
+			listOf(
+				ArmorMaterial.Layer(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/models/armor/simple_tritanium_armor.png")),
+				// ArmorMaterial.Layer(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/models/armor/simple_tritanium_armor_overlay.png"), "", true),
+			),
+			0.3f,
+			0f
+		)
+	}
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockColors.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockColors.kt
index 398052552..0e4471da9 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockColors.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockColors.kt
@@ -7,8 +7,8 @@ import net.minecraft.core.BlockPos
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.BlockAndTintGetter
 import net.minecraft.world.level.block.state.BlockState
-import net.minecraftforge.client.event.RegisterColorHandlersEvent
-import net.minecraftforge.eventbus.api.IEventBus
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent
 import ru.dbotthepony.mc.otm.block.entity.decorative.HoloSignBlockEntity
 import ru.dbotthepony.kommons.math.RGBAColor
 
@@ -64,7 +64,7 @@ object MBlockColors {
 		event.register(HoloSightColor, MBlocks.HOLO_SIGN)
 	}
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		bus.addListener(this::registerBlockColors)
 		bus.addListener(this::registerItemColors)
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt
index 27aea31ec..ad8e53695 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt
@@ -1,12 +1,12 @@
 package ru.dbotthepony.mc.otm.registry
 
 import net.minecraft.client.renderer.blockentity.BlockEntityRenderers
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.world.level.block.Block
 import net.minecraft.world.level.block.entity.BlockEntity
 import net.minecraft.world.level.block.entity.BlockEntityType
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent
 import ru.dbotthepony.mc.otm.block.entity.*
 import ru.dbotthepony.mc.otm.block.entity.tech.*
 import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity
@@ -34,7 +34,7 @@ import java.util.function.Supplier
 
 @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") // Type<*> is unused in BlockEntityType.Builder
 object MBlockEntities {
-	private val registry = MDeferredRegister(ForgeRegistries.BLOCK_ENTITY_TYPES)
+	private val registry = MDeferredRegister(BuiltInRegistries.BLOCK_ENTITY_TYPE)
 
 	private fun <T : BlockEntity> register(name: String, factory: BlockEntityType.BlockEntitySupplier<T>, vararg blocks: Supplier<Block>): MDeferredRegister<*>.Entry<BlockEntityType<T>> {
 		return registry.register(name) { BlockEntityType.Builder.of(factory, *blocks.map { it.get() }.toTypedArray()).build(null) }
@@ -112,7 +112,7 @@ object MBlockEntities {
 	val DEBUG_EXPLOSION_SMALL: BlockEntityType<BlockEntityExplosionDebugger> by registry.register(MNames.DEBUG_EXPLOSION_SMALL) { BlockEntityType.Builder.of(::BlockEntityExplosionDebugger, MBlocks.DEBUG_EXPLOSION_SMALL).build(null) }
 	val DEBUG_SPHERE_POINTS: BlockEntityType<BlockEntitySphereDebugger> by registry.register(MNames.DEBUG_SPHERE_POINTS) { BlockEntityType.Builder.of(::BlockEntitySphereDebugger, MBlocks.DEBUG_SPHERE_POINTS).build(null) }
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registry.register(bus)
 		bus.addListener(this::registerClient)
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockTags.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockTags.kt
index 57b04b64b..d0d8814b4 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockTags.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockTags.kt
@@ -1,10 +1,10 @@
 package ru.dbotthepony.mc.otm.registry
 
-import net.minecraft.resources.ResourceLocation
 import net.minecraft.tags.BlockTags
 import net.minecraft.tags.TagKey
 import net.minecraft.world.level.block.Block
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 @Suppress("unused")
 object MBlockTags {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt
index ac49301a8..447bca469 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt
@@ -1,5 +1,7 @@
 package ru.dbotthepony.mc.otm.registry
 
+import net.minecraft.core.Direction
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.util.valueproviders.UniformInt
 import net.minecraft.world.level.block.AnvilBlock
 import net.minecraft.world.level.block.Block
@@ -15,8 +17,10 @@ import net.minecraft.world.level.block.state.BlockBehaviour
 import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
 import net.minecraft.world.level.material.MapColor
 import net.minecraft.world.level.material.PushReaction
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.capabilities.BlockCapability
+import net.neoforged.neoforge.capabilities.IBlockCapabilityProvider
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
 import ru.dbotthepony.mc.otm.block.BlackHoleBlock
 import ru.dbotthepony.mc.otm.block.BlockExplosionDebugger
 import ru.dbotthepony.mc.otm.block.BlockSphereDebugger
@@ -37,6 +41,7 @@ import ru.dbotthepony.mc.otm.block.decorative.LaboratoryLampLight
 import ru.dbotthepony.mc.otm.block.decorative.PainterBlock
 import ru.dbotthepony.mc.otm.block.decorative.TritaniumDoorBlock
 import ru.dbotthepony.mc.otm.block.decorative.TritaniumTrapdoorBlock
+import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
 import ru.dbotthepony.mc.otm.block.entity.tech.EnergyHatchBlockEntity
 import ru.dbotthepony.mc.otm.block.entity.tech.ItemHatchBlockEntity
 import ru.dbotthepony.mc.otm.block.entity.tech.MatterHatchBlockEntity
@@ -80,10 +85,28 @@ import ru.dbotthepony.mc.otm.core.collect.SupplierMap
 import java.util.function.Supplier
 
 object MBlocks {
-	private val registry = MDeferredRegister(ForgeRegistries.BLOCKS)
+	private val registry = MDeferredRegister(BuiltInRegistries.BLOCK)
 
-	internal fun register(bus: IEventBus) {
+	private fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		// ugly
+		for (cap in BlockCapability.getAll()) {
+			val provider = IBlockCapabilityProvider<Any?, Direction?> { level, pos, state, be, context: Direction? ->
+				if (be is MatteryBlockEntity) {
+					return@IBlockCapabilityProvider be.getCapability(cap as BlockCapability<Any, Direction?>, context)
+				}
+
+				return@IBlockCapabilityProvider null
+			}
+
+			for (block in registry.entries.values) {
+				event.registerBlock(cap as BlockCapability<Any, Direction?>, provider, block.value)
+			}
+		}
+	}
+
+	fun register(bus: IEventBus) {
 		registry.register(bus)
+		bus.addListener(::registerCapabilities)
 	}
 
 	val ANDROID_STATION = registry.coloredWithBase(MNames.ANDROID_STATION, ::AndroidStationBlock)
@@ -150,14 +173,14 @@ object MBlocks {
 	val MATTER_INPUT_HATCH by registry.register(MNames.MATTER_INPUT_HATCH) { HatchBlock(MatterHatchBlockEntity::input, true) }
 	val MATTER_OUTPUT_HATCH by registry.register(MNames.MATTER_OUTPUT_HATCH) { HatchBlock(MatterHatchBlockEntity::output, true) }
 
-	val LIQUID_XP: LiquidBlock by registry.register("liquid_xp") { LiquidBlock(MFluids::LIQUID_XP, BlockBehaviour.Properties.of().mapColor(MapColor.EMERALD).replaceable().noCollission().strength(100.0f).pushReaction(PushReaction.DESTROY).noLootTable().liquid().sound(SoundType.EMPTY)) }
+	val LIQUID_XP: LiquidBlock by registry.register("liquid_xp") { LiquidBlock(MFluids.LIQUID_XP_FLOWING, BlockBehaviour.Properties.of().mapColor(MapColor.EMERALD).replaceable().noCollission().strength(100.0f).pushReaction(PushReaction.DESTROY).noLootTable().liquid().sound(SoundType.EMPTY)) }
 
 	val TRITANIUM_ORE: Block by registry.register(MNames.TRITANIUM_ORE) { DropExperienceBlock(
+		UniformInt.of(0, 3),
 		BlockBehaviour.Properties.of()
 			.mapColor(MapColor.STONE)
 			.strength(3.25f, 6.0f)
 			.requiresCorrectToolForDrops(),
-		UniformInt.of(0, 3)
 	) }
 
 	val TRITANIUM_INGOT_BLOCK: Block by registry.register(MNames.TRITANIUM_INGOT_BLOCK) {
@@ -188,12 +211,12 @@ object MBlocks {
 	}
 
 	val DEEPSLATE_TRITANIUM_ORE: Block by registry.register(MNames.DEEPSLATE_TRITANIUM_ORE) { DropExperienceBlock(
+		UniformInt.of(0, 3),
 		BlockBehaviour.Properties.of()
 			.mapColor(MapColor.DEEPSLATE)
 			.sound(SoundType.DEEPSLATE)
 			.strength(4.75f, 6.5f)
 			.requiresCorrectToolForDrops().sound(SoundType.DEEPSLATE),
-		UniformInt.of(0, 3)
 	) }
 
 	val TRITANIUM_RAW_BLOCK: Block by registry.register(MNames.TRITANIUM_RAW_BLOCK) { Block(
@@ -244,16 +267,16 @@ object MBlocks {
 	) }
 
 	val TRITANIUM_STRIPED_STAIRS: Block by registry.register(MNames.TRITANIUM_STRIPED_STAIRS) { StairBlock(
-		{ TRITANIUM_STRIPED_BLOCK.defaultBlockState() },
-		BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK)
+		TRITANIUM_STRIPED_BLOCK.defaultBlockState(),
+		BlockBehaviour.Properties.ofLegacyCopy(TRITANIUM_STRIPED_BLOCK)
 	) }
 
 	val TRITANIUM_STRIPED_SLAB: Block by registry.register(MNames.TRITANIUM_STRIPED_SLAB) {
-		SlabBlock(BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK))
+		SlabBlock(BlockBehaviour.Properties.ofLegacyCopy(TRITANIUM_STRIPED_BLOCK))
 	}
 
 	val TRITANIUM_STRIPED_WALL: Block by registry.register(MNames.TRITANIUM_STRIPED_WALL) {
-		WallBlock(BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK))
+		WallBlock(BlockBehaviour.Properties.ofLegacyCopy(TRITANIUM_STRIPED_BLOCK))
 	}
 
 	val CARBON_FIBRE_BLOCK: Block by registry.register(MNames.CARBON_FIBRE_BLOCK) { Block(
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt
index 3379b32f3..91d7bfa36 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt
@@ -1,27 +1,25 @@
 package ru.dbotthepony.mc.otm.registry
 
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.core.registries.Registries
-import net.minecraft.resources.ResourceLocation
 import net.minecraft.world.item.CreativeModeTab
 import net.minecraft.world.item.CreativeModeTabs
 import net.minecraft.world.item.DyeColor
 import net.minecraft.world.item.Item
 import net.minecraft.world.item.ItemStack
 import net.minecraft.world.level.material.Fluids
-import net.minecraftforge.common.capabilities.ForgeCapabilities
-import net.minecraftforge.event.BuildCreativeModeTabContentsEvent
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fluids.FluidStack
-import net.minecraftforge.fluids.capability.IFluidHandler
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.capabilities.Capabilities
+import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent
+import net.neoforged.neoforge.fluids.FluidStack
+import net.neoforged.neoforge.fluids.capability.IFluidHandler
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.capability.matter.matter
+import ru.dbotthepony.mc.otm.capability.MatteryCapability
 import ru.dbotthepony.mc.otm.capability.matteryEnergy
-import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.TranslatableComponent
-import ru.dbotthepony.mc.otm.core.ifPresentK
 import ru.dbotthepony.mc.otm.core.registryName
+import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator
 import ru.dbotthepony.mc.otm.registry.MItems.BATTERY_CREATIVE
 
 private fun CreativeModeTab.Output.accept(values: Collection<Item>) {
@@ -99,11 +97,11 @@ private fun CreativeModeTab.Output.mattery(value: Item) {
 	accept(value)
 
 	val stack = ItemStack(value, 1)
-	val matter = stack.matter ?: throw IllegalArgumentException("${value.registryName} does not implement matter capability")
+	val matter = stack.getCapability(MatteryCapability.MATTER_ITEM) ?: throw IllegalArgumentException("${value.registryName} does not implement matter capability")
 
 	matter.fillMatter()
 
-	if (ItemStack(value, 1).matter!!.storedMatter != matter.storedMatter)
+	if (ItemStack(value, 1).getCapability(MatteryCapability.MATTER_ITEM)!!.storedMatter != matter.storedMatter)
 		accept(stack)
 }
 
@@ -116,10 +114,10 @@ private fun CreativeModeTab.Output.mattery(values: Iterable<Item>) {
 private fun CreativeModeTab.Output.fluids(value: Item) {
 	accept(value)
 
-	for (fluid in ForgeRegistries.FLUIDS.values) {
+	for (fluid in BuiltInRegistries.FLUID) {
 		if (fluid != Fluids.EMPTY && fluid.isSource(fluid.defaultFluidState())) {
 			accept(ItemStack(value, 1).also {
-				it.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
+				it.getCapability(Capabilities.FluidHandler.ITEM)?.let {
 					it.fill(FluidStack(fluid, it.getTankCapacity(0)), IFluidHandler.FluidAction.EXECUTE)
 				}
 			})
@@ -162,7 +160,6 @@ private fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) {
 		accept(MItems.TRITANIUM_ARMOR)
 
 		energized(MItems.ENERGY_SWORD)
-		energized(MItems.PLASMA_RIFLE)
 
 		accept(MItems.EXPLOSIVE_HAMMER)
 		accept(ItemStack(MItems.EXPLOSIVE_HAMMER).also { MItems.EXPLOSIVE_HAMMER.prime(it) })
@@ -293,7 +290,7 @@ object MCreativeTabs {
 			.build()
 	}
 
-	internal fun initialize(bus: IEventBus) {
+	fun initialize(bus: IEventBus) {
 		registry.register(bus)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDamageTypes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDamageTypes.kt
index a1af1bade..4066db2ad 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDamageTypes.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDamageTypes.kt
@@ -5,6 +5,7 @@ import net.minecraft.resources.ResourceKey
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.world.damagesource.DamageType
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 object MDamageTypes {
 	private fun register(name: String): ResourceKey<DamageType> = ResourceKey.create(Registries.DAMAGE_TYPE, ResourceLocation(OverdriveThatMatters.MOD_ID, name))
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDataComponentTypes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDataComponentTypes.kt
new file mode 100644
index 000000000..b7bd1a49c
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDataComponentTypes.kt
@@ -0,0 +1,71 @@
+package ru.dbotthepony.mc.otm.registry
+
+import com.mojang.serialization.Codec
+import net.minecraft.core.UUIDUtil
+import net.minecraft.core.component.DataComponentType
+import net.minecraft.core.registries.BuiltInRegistries
+import net.minecraft.network.RegistryFriendlyByteBuf
+import net.minecraft.network.codec.StreamCodec
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.fluids.FluidStack
+import ru.dbotthepony.mc.otm.capability.matter.PatternState
+import ru.dbotthepony.mc.otm.container.ItemFilter
+import ru.dbotthepony.mc.otm.core.math.Decimal
+import ru.dbotthepony.mc.otm.data.DecimalCodec
+import ru.dbotthepony.mc.otm.network.StreamCodecs
+import java.util.UUID
+
+object MDataComponentTypes {
+	private val registry = MDeferredRegister(BuiltInRegistries.DATA_COMPONENT_TYPE)
+
+	// class instead of object to preserve identity distinction for registry reversal search
+	private class DecimalComponent : DataComponentType<Decimal> {
+		override fun codec(): Codec<Decimal> {
+			return DecimalCodec
+		}
+
+		override fun streamCodec(): StreamCodec<in RegistryFriendlyByteBuf, Decimal> {
+			return DecimalCodec.NETWORK
+		}
+	}
+
+	val FLUID_STACK by registry.register("fluid_stack") {
+		object : DataComponentType<FluidStack> {
+			override fun codec(): Codec<FluidStack> {
+				return FluidStack.OPTIONAL_CODEC
+			}
+
+			override fun streamCodec(): StreamCodec<in RegistryFriendlyByteBuf, FluidStack> {
+				return FluidStack.OPTIONAL_STREAM_CODEC
+			}
+		}
+	}
+
+	val BATTERY_LEVEL: DataComponentType<Decimal> by registry.register("battery_level") { DecimalComponent() }
+	val MAX_BATTERY_LEVEL: DataComponentType<Decimal> by registry.register("max_battery_level") { DecimalComponent() }
+	val MAX_BATTERY_INPUT: DataComponentType<Decimal> by registry.register("max_battery_input") { DecimalComponent() }
+	val MAX_BATTERY_OUTPUT: DataComponentType<Decimal> by registry.register("max_battery_output") { DecimalComponent() }
+	val MATTER_LEVEL: DataComponentType<Decimal> by registry.register("matter_level") { DecimalComponent() }
+
+	val EXOPACK_SLOT_COUNT: DataComponentType<Int> by registry.register("exopack_slot_count") { DataComponentType.builder<Int>().persistent(Codec.INT).networkSynchronized(StreamCodecs.INT).build() }
+	val EXOPACK_UPGRADE_UUID: DataComponentType<UUID> by registry.register("exopack_upgrade_uuid") { DataComponentType.builder<UUID>().persistent(UUIDUtil.CODEC).networkSynchronized(UUIDUtil.STREAM_CODEC).build() }
+	val CONDENSATION_DRIVE_UUID: DataComponentType<UUID> by registry.register("condensation_drive_uuid") { DataComponentType.builder<UUID>().persistent(UUIDUtil.CODEC).networkSynchronized(UUIDUtil.STREAM_CODEC).build() }
+	val PATTERNS: DataComponentType<List<PatternState>> by registry.register("patterns") { DataComponentType.builder<List<PatternState>>().persistent(Codec.list(PatternState.CODEC)).build() }
+	val ITEM_FILTER: DataComponentType<ItemFilter> by registry.register("item_filter") { DataComponentType.builder<ItemFilter>().persistent(ItemFilter.CODEC).build() }
+
+	val PRIMED by registry.register("primed") {
+		object : DataComponentType<Boolean> {
+			override fun codec(): Codec<Boolean> {
+				return Codec.BOOL
+			}
+
+			override fun streamCodec(): StreamCodec<in RegistryFriendlyByteBuf, Boolean> {
+				return StreamCodecs.BOOLEAN
+			}
+		}
+	}
+
+	fun register(bus: IEventBus) {
+		registry.register(bus)
+	}
+}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDeferredRegister.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDeferredRegister.kt
index 24bb19ba8..89c14cfd6 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDeferredRegister.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDeferredRegister.kt
@@ -1,35 +1,44 @@
 package ru.dbotthepony.mc.otm.registry
 
+import com.mojang.datafixers.util.Either
 import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap
 import net.minecraft.Util
+import net.minecraft.core.Holder
+import net.minecraft.core.HolderOwner
 import net.minecraft.core.Registry
 import net.minecraft.resources.ResourceKey
 import net.minecraft.resources.ResourceLocation
+import net.minecraft.tags.TagKey
 import net.minecraft.world.item.DyeColor
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.IForgeRegistry
-import net.minecraftforge.registries.RegisterEvent
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
+import net.neoforged.neoforge.registries.RegisterEvent
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.SystemTime
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.collect.SupplierMap
-import java.util.LinkedList
+import java.util.*
 import java.util.concurrent.FutureTask
 import java.util.concurrent.locks.LockSupport
+import java.util.function.Predicate
 import java.util.function.Supplier
+import java.util.stream.Stream
 import kotlin.reflect.KProperty
 
 /**
  * DeferredRegister which allows parallel initialization
  */
 class MDeferredRegister<R : Any>(val registry: ResourceKey<out Registry<R>>, val modId: String = OverdriveThatMatters.MOD_ID, val allowParallel: Boolean = false) {
-	constructor(registry: IForgeRegistry<R>, modId: String = OverdriveThatMatters.MOD_ID, allowParallel: Boolean = false) : this(registry.registryKey, modId, allowParallel)
+	constructor(registry: Registry<R>, modId: String = OverdriveThatMatters.MOD_ID, allowParallel: Boolean = false) : this(registry.key(), modId, allowParallel)
 
-	private val entries = Object2ObjectLinkedOpenHashMap<String, Entry<R>>()
+	private val entriesInternal = Object2ObjectLinkedOpenHashMap<String, Entry<R>>()
 	private var allowRegistration = true
 	private var eventRegistered = false
 	private val stages = ArrayList<RegistrationStage>()
 
+	val entries: Map<String, Entry<R>> = Collections.unmodifiableMap(entriesInternal)
+
 	private inner class RegistrationStage(val serial: Boolean) {
 		private val entries = ArrayList<Entry<R>>()
 
@@ -37,17 +46,19 @@ class MDeferredRegister<R : Any>(val registry: ResourceKey<out Registry<R>>, val
 			entries.add(entry)
 		}
 
-		fun run(): List<Entry<R>> {
+		fun run(registry: Registry<R>) {
+			check(registry.key() == this@MDeferredRegister.registry) { "Hey." }
+
 			if (serial) {
-				LOGGER.debug("Serial registration of {} entries from {} for {}...", entries.size, modId, registry.location())
+				LOGGER.debug("Serial registration of {} entries from {} for {}...", entries.size, modId, registry.key().location())
 				val t = SystemTime()
-				entries.forEach { it.initialize() }
-				LOGGER.debug("Serial registration of {} entries from {} for {} took {} ms", entries.size, modId, registry.location(), t.millis)
+				entries.forEach { it.initialize(registry) }
+				LOGGER.debug("Serial registration of {} entries from {} for {} took {} ms", entries.size, modId, registry.key().location(), t.millis)
 			} else {
-				LOGGER.debug("Parallel registration of {} entries from {} for {}...", entries.size, modId, registry.location())
+				LOGGER.debug("Parallel registration of {} entries from {} for {}...", entries.size, modId, registry.key().location())
 				val t = SystemTime()
 				val futures = LinkedList<FutureTask<R>>()
-				entries.forEach { futures.add(FutureTask(it::initialize)) }
+				entries.forEach { futures.add(FutureTask { it.initialize(registry) }) }
 				futures.forEach { Util.backgroundExecutor().submit(it) }
 
 				while (futures.isNotEmpty()) {
@@ -60,19 +71,17 @@ class MDeferredRegister<R : Any>(val registry: ResourceKey<out Registry<R>>, val
 				// memory barrier to avoid specifying _value as @Volatile
 				LockSupport.parkNanos(1_000_000L)
 
-				LOGGER.debug("Parallel registration of {} entries from {} for {} took {} ms", entries.size, modId, registry.location(), t.millis)
+				LOGGER.debug("Parallel registration of {} entries from {} for {} took {} ms", entries.size, modId, registry.key().location(), t.millis)
 			}
-
-			return entries
 		}
 	}
 
-	inner class Entry<out T : R>(val name: String, private val factory: Supplier<T>, serial: Boolean) : Supplier<@UnsafeVariance T>, Lazy<T> {
+	inner class Entry<out T : R>(val name: String, private val factory: Supplier<T>, serial: Boolean) : Supplier<@UnsafeVariance T>, Lazy<T>, Holder<R> {
 		constructor(name: String, factory: Supplier<T>) : this(name, factory, false)
 
 		init {
 			check(allowRegistration) { "Unable to register new entries after RegisterEvent has been fired" }
-			require(entries.put(name, this) == null) { "Duplicate entry $name for registry $registry" }
+			require(entriesInternal.put(name, this) == null) { "Duplicate entry $name for registry $registry" }
 
 			val actualSerial = if (allowParallel) serial else true
 
@@ -85,7 +94,58 @@ class MDeferredRegister<R : Any>(val registry: ResourceKey<out Registry<R>>, val
 			}
 		}
 
-		val key = ResourceLocation(modId, name)
+		val key: ResourceKey<R> = ResourceKey.create(registry, ResourceLocation(modId, name))
+		private var parentHolder: Holder<R>? = null
+
+		override fun value(): T {
+			return value
+		}
+
+		override fun isBound(): Boolean {
+			return _value != null
+		}
+
+		override fun `is`(p_205713_: ResourceLocation): Boolean {
+			return p_205713_ == key.location()
+		}
+
+		override fun `is`(p_205712_: ResourceKey<R>): Boolean {
+			return p_205712_ == key
+		}
+
+		override fun `is`(p_205711_: Predicate<ResourceKey<R>>): Boolean {
+			return p_205711_.test(key)
+		}
+
+		override fun `is`(p_205705_: TagKey<R>): Boolean {
+			return parentHolder?.`is`(p_205705_) ?: false
+		}
+
+		@Deprecated("Deprecated in Java")
+		override fun `is`(p_316447_: Holder<R>): Boolean {
+			return p_316447_.`is`(key)
+		}
+
+		override fun tags(): Stream<TagKey<R>> {
+			return parentHolder?.tags() ?: Stream.empty()
+		}
+
+		override fun unwrap(): Either<ResourceKey<R>, R> {
+			return Either.left(key)
+		}
+
+		override fun unwrapKey(): Optional<ResourceKey<R>> {
+			return Optional.of(key)
+		}
+
+		override fun kind(): Holder.Kind {
+			return Holder.Kind.REFERENCE
+		}
+
+		override fun canSerializeIn(p_255833_: HolderOwner<R>): Boolean {
+			return parentHolder?.canSerializeIn(p_255833_) ?: false
+		}
+
 		private var _value: T? = null
 
 		override val value: T get() {
@@ -104,16 +164,17 @@ class MDeferredRegister<R : Any>(val registry: ResourceKey<out Registry<R>>, val
 			return value
 		}
 
-		fun initialize(): T {
+		fun initialize(registry: Registry<R>): T {
 			check(_value == null) { "Already initialized $name of $registry!" }
 
-			val getValue = try {
+			val compute = try {
 				factory.get()
 			} catch (err: Throwable) {
 				throw RuntimeException("Unable to initialize registry entry $name of $registry", err)
 			}
 
-			_value = getValue
+			_value = compute
+			parentHolder = Registry.registerForHolder(registry, key, compute)
 			return value
 		}
 	}
@@ -140,9 +201,7 @@ class MDeferredRegister<R : Any>(val registry: ResourceKey<out Registry<R>>, val
 			allowRegistration = false
 
 			for (stage in stages) {
-				stage.run().forEach {
-					event.register(registry, it.key, it)
-				}
+				stage.run(event.registry as Registry<R>)
 			}
 		}
 	}
@@ -151,6 +210,17 @@ class MDeferredRegister<R : Any>(val registry: ResourceKey<out Registry<R>>, val
 		check(!eventRegistered) { "Already registered!" }
 		eventRegistered = true
 		bus.addListener(this::onRegisterEvent)
+		bus.addListener(this::registerCapabilities)
+	}
+
+	private fun registerCapabilities(event: RegisterCapabilitiesEvent) {
+		entriesInternal.values.forEach {
+			val value = it.value
+
+			if (value is CapabilitiesRegisterListener) {
+				value.registerCapabilities(event)
+			}
+		}
 	}
 
 	companion object {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MEntityTypes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MEntityTypes.kt
index 237fa3885..149e14f7e 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MEntityTypes.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MEntityTypes.kt
@@ -4,30 +4,29 @@ import net.minecraft.client.model.geom.ModelLayers
 import net.minecraft.client.renderer.entity.EntityRenderer
 import net.minecraft.client.renderer.entity.EntityRenderers
 import net.minecraft.client.renderer.entity.MinecartRenderer
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.world.entity.Entity
 import net.minecraft.world.entity.EntityType
 import net.minecraft.world.entity.MobCategory
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.ForgeRegistries
-import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import net.minecraft.world.item.DyeColor
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent
 import ru.dbotthepony.mc.otm.client.render.entity.PlasmaProjectileRenderer
 import ru.dbotthepony.mc.otm.entity.MinecartCargoCrate
 import ru.dbotthepony.mc.otm.entity.PlasmaProjectile
 
 object MEntityTypes {
-	private val registry = MDeferredRegister(ForgeRegistries.ENTITY_TYPES)
+	private val registry = MDeferredRegister(BuiltInRegistries.ENTITY_TYPE)
 
-	val PLASMA: EntityType<*> by registry.register(MNames.PLASMA) {
-		EntityType.Builder.of<PlasmaProjectile>({ _, level -> PlasmaProjectile(level) }, MobCategory.MISC).sized(0.4f, 0.4f).build(MNames.PLASMA)
+	val PLASMA: EntityType<PlasmaProjectile> by registry.register(MNames.PLASMA) {
+		EntityType.Builder.of({ _, level -> PlasmaProjectile(level) }, MobCategory.MISC).sized(0.4f, 0.4f).build(MNames.PLASMA)
 	}
 
-	val CARGO_CRATE_MINECARTS = registry.coloredWithBase(MNames.MINECART_CARGO_CRATE) { color ->
-		EntityType.Builder.of<MinecartCargoCrate>({ it, level -> MinecartCargoCrate(it, color, level)}, MobCategory.MISC).sized(0.98F, 0.7F).clientTrackingRange(8).build("dfu doesn't works ✅")
+	val CARGO_CRATE_MINECARTS: Map<DyeColor?, EntityType<MinecartCargoCrate>> = registry.coloredWithBase(MNames.MINECART_CARGO_CRATE) { color ->
+		EntityType.Builder.of({ it, level -> MinecartCargoCrate(it, color, level)}, MobCategory.MISC).sized(0.98F, 0.7F).clientTrackingRange(8).build("dfu doesn't works ✅")
 	}
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registry.register(bus)
 		bus.addListener(this::registerClient)
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MFluids.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MFluids.kt
index 2212399a4..2bc9bd59f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MFluids.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MFluids.kt
@@ -1,30 +1,30 @@
 package ru.dbotthepony.mc.otm.registry
 
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.sounds.SoundEvents
 import net.minecraft.world.item.Rarity
-import net.minecraft.world.level.material.Fluid
-import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions
-import net.minecraftforge.common.SoundActions
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fluids.FluidType
-import net.minecraftforge.fluids.ForgeFlowingFluid
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions
+import net.neoforged.neoforge.common.SoundActions
+import net.neoforged.neoforge.fluids.BaseFlowingFluid
+import net.neoforged.neoforge.fluids.FluidType
+import net.neoforged.neoforge.registries.NeoForgeRegistries
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import java.util.function.Consumer
 
 object MFluids {
-	private val types = MDeferredRegister(ForgeRegistries.Keys.FLUID_TYPES)
-	private val fluids = MDeferredRegister(ForgeRegistries.FLUIDS)
+	private val types = MDeferredRegister(NeoForgeRegistries.FLUID_TYPES.key())
+	private val fluids = MDeferredRegister(BuiltInRegistries.FLUID)
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		types.register(bus)
 		fluids.register(bus)
 	}
 
-	private fun makeXpProps(): ForgeFlowingFluid.Properties {
-		return ForgeFlowingFluid.Properties(::LIQUID_XP_TYPE, ::LIQUID_XP, ::LIQUID_XP_FLOWING).bucket(MItems::LIQUID_XP_BUCKET).block(MBlocks::LIQUID_XP)
+	private fun makeXpProps(): BaseFlowingFluid.Properties {
+		return BaseFlowingFluid.Properties(::LIQUID_XP_TYPE, ::LIQUID_XP, ::LIQUID_XP_FLOWING).bucket(MItems::LIQUID_XP_BUCKET).block(MBlocks::LIQUID_XP)
 	}
 
 	private val xpProps = makeXpProps()
@@ -62,6 +62,6 @@ object MFluids {
 		}
 	}
 
-	val LIQUID_XP: ForgeFlowingFluid.Source by fluids.register("liquid_xp") { ForgeFlowingFluid.Source(xpProps) }
-	val LIQUID_XP_FLOWING: ForgeFlowingFluid.Flowing by fluids.register("liquid_xp_flowing") { ForgeFlowingFluid.Flowing(xpProps) }
+	val LIQUID_XP: BaseFlowingFluid.Source by fluids.register("liquid_xp") { BaseFlowingFluid.Source(xpProps) }
+	val LIQUID_XP_FLOWING: BaseFlowingFluid.Flowing by fluids.register("liquid_xp_flowing") { BaseFlowingFluid.Flowing(xpProps) }
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemFunctionTypes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemFunctionTypes.kt
index 89052a1a9..f3afca109 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemFunctionTypes.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemFunctionTypes.kt
@@ -2,8 +2,7 @@ package ru.dbotthepony.mc.otm.registry
 
 import net.minecraft.core.registries.Registries
 import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.data.loot.CopyTileNbtFunction
 import ru.dbotthepony.mc.otm.item.ProceduralBatteryItem
@@ -12,11 +11,11 @@ import ru.dbotthepony.mc.otm.item.exopack.ProceduralExopackSlotUpgradeItem
 object MItemFunctionTypes {
 	private val registry = MDeferredRegister(Registries.LOOT_FUNCTION_TYPE, OverdriveThatMatters.MOD_ID)
 
-	val COPY_TILE_NBT: LootItemFunctionType by registry.register("copy_tile_nbt") { LootItemFunctionType(CopyTileNbtFunction.CODEC) }
-	val PROCEDURAL_BATTERY: LootItemFunctionType by registry.register(MNames.PROCEDURAL_BATTERY) { LootItemFunctionType(ProceduralBatteryItem.Randomizer.CODEC) }
-	val PROCEDURAL_EXOPACK_UPGRADE: LootItemFunctionType by registry.register("exopack_upgrade") { LootItemFunctionType(ProceduralExopackSlotUpgradeItem.Randomizer.CODEC) }
+	val COPY_TILE_NBT by registry.register("copy_tile_nbt") { LootItemFunctionType(CopyTileNbtFunction.CODEC) }
+	val PROCEDURAL_BATTERY by registry.register(MNames.PROCEDURAL_BATTERY) { LootItemFunctionType(ProceduralBatteryItem.Randomizer.CODEC) }
+	val PROCEDURAL_EXOPACK_UPGRADE by registry.register("exopack_upgrade") { LootItemFunctionType(ProceduralExopackSlotUpgradeItem.Randomizer.CODEC) }
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registry.register(bus)
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemTags.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemTags.kt
index cf19f67a7..3f04e15ad 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemTags.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemTags.kt
@@ -5,6 +5,7 @@ import net.minecraft.tags.ItemTags
 import net.minecraft.tags.TagKey
 import net.minecraft.world.item.Item
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 @Suppress("unused")
 object MItemTags {
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt
index b006169ff..0dea12847 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt
@@ -1,60 +1,84 @@
 
 package ru.dbotthepony.mc.otm.registry
 
-import net.minecraft.ChatFormatting
-import net.minecraft.network.chat.Component
-import net.minecraft.resources.ResourceLocation
+import net.minecraft.core.registries.BuiltInRegistries
+import net.minecraft.tags.BlockTags
 import net.minecraft.world.food.FoodProperties
-import net.minecraft.world.item.*
+import net.minecraft.world.item.ArmorItem
+import net.minecraft.world.item.BlockItem
+import net.minecraft.world.item.BucketItem
+import net.minecraft.world.item.DoubleHighBlockItem
+import net.minecraft.world.item.DyeColor
+import net.minecraft.world.item.HoeItem
+import net.minecraft.world.item.Item
 import net.minecraft.world.item.Item.Properties
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.PickaxeItem
+import net.minecraft.world.item.Rarity
+import net.minecraft.world.item.ShearsItem
+import net.minecraft.world.item.ShieldItem
+import net.minecraft.world.item.ShovelItem
+import net.minecraft.world.item.SwordItem
+import net.minecraft.world.item.Tiers
 import net.minecraft.world.item.crafting.Ingredient
-import net.minecraft.world.level.Level
 import net.minecraft.world.level.block.Block
-import net.minecraftforge.common.ForgeTier
-import net.minecraftforge.common.TierSortingRegistry
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.registries.RegistryObject
-import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent
+import net.neoforged.neoforge.common.SimpleTier
 import ru.dbotthepony.mc.otm.capability.ITieredUpgradeSet
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.UpgradeType
 import ru.dbotthepony.mc.otm.config.CablesConfig
 import ru.dbotthepony.mc.otm.config.ItemsConfig
-import ru.dbotthepony.mc.otm.core.collect.SupplierList
-import ru.dbotthepony.mc.otm.core.TranslatableComponent
 import ru.dbotthepony.mc.otm.core.addAll
 import ru.dbotthepony.mc.otm.core.asSupplierArray
+import ru.dbotthepony.mc.otm.core.collect.SupplierList
 import ru.dbotthepony.mc.otm.core.collect.SupplierMap
 import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.mc.otm.item.*
+import ru.dbotthepony.mc.otm.item.BatteryItem
+import ru.dbotthepony.mc.otm.item.ChestUpgraderItem
+import ru.dbotthepony.mc.otm.item.CrudeBatteryItem
+import ru.dbotthepony.mc.otm.item.EssenceCapsuleItem
+import ru.dbotthepony.mc.otm.item.EssenceServoItem
+import ru.dbotthepony.mc.otm.item.FluidCapsuleItem
+import ru.dbotthepony.mc.otm.item.FluidTankItem
+import ru.dbotthepony.mc.otm.item.GravitationalDisruptorItem
+import ru.dbotthepony.mc.otm.item.HealPillItem
+import ru.dbotthepony.mc.otm.item.MatteryItem
+import ru.dbotthepony.mc.otm.item.MinecartCargoCrateItem
+import ru.dbotthepony.mc.otm.item.PillItem
+import ru.dbotthepony.mc.otm.item.PillType
+import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem
+import ru.dbotthepony.mc.otm.item.ProceduralBatteryItem
+import ru.dbotthepony.mc.otm.item.QuantumBatteryItem
+import ru.dbotthepony.mc.otm.item.SimpleUpgrade
+import ru.dbotthepony.mc.otm.item.ZPMItem
+import ru.dbotthepony.mc.otm.item.addSimpleDescription
+import ru.dbotthepony.mc.otm.item.armor.PortableGravitationStabilizerItem
+import ru.dbotthepony.mc.otm.item.armor.SimpleTritaniumArmorItem
+import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem
 import ru.dbotthepony.mc.otm.item.exopack.ExopackProbeItem
 import ru.dbotthepony.mc.otm.item.exopack.ExopackSlotUpgradeItem
+import ru.dbotthepony.mc.otm.item.exopack.ExopackUpgradeItem
+import ru.dbotthepony.mc.otm.item.exopack.ProceduralExopackSlotUpgradeItem
 import ru.dbotthepony.mc.otm.item.matter.CreativePatternItem
 import ru.dbotthepony.mc.otm.item.matter.MatterCapacitorItem
 import ru.dbotthepony.mc.otm.item.matter.MatterDustItem
 import ru.dbotthepony.mc.otm.item.matter.PatternStorageItem
-import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem
 import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem
 import ru.dbotthepony.mc.otm.item.tool.MatteryAxeItem
-import ru.dbotthepony.mc.otm.item.armor.PortableGravitationStabilizerItem
-import ru.dbotthepony.mc.otm.item.armor.SimpleTritaniumArmorItem
-import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem
-import ru.dbotthepony.mc.otm.item.exopack.ExopackUpgradeItem
-import ru.dbotthepony.mc.otm.item.exopack.ProceduralExopackSlotUpgradeItem
-import ru.dbotthepony.mc.otm.item.weapon.PlasmaRifleItem
+import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem
 import java.util.function.Supplier
 
 object MItems {
 	private val DEFAULT_PROPERTIES = Properties()
-	private val registry = MDeferredRegister(ForgeRegistries.ITEMS)
+	private val registry = MDeferredRegister(BuiltInRegistries.ITEM)
 
 	private fun register(name: String, blocks: Map<DyeColor?, Block>, properties: Properties = DEFAULT_PROPERTIES): Map<DyeColor?, BlockItem> {
 		return registry.coloredWithBase(name) { color -> BlockItem(blocks[color]!!, properties) }
 	}
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registry.register(bus)
 	}
 
@@ -298,30 +322,23 @@ object MItems {
 
 	val FLUID_CAPSULE: FluidCapsuleItem by registry.register("fluid_capsule") { FluidCapsuleItem(ItemsConfig::FLUID_CAPSULE_CAPACITY) }
 	val FLUID_TANK: FluidTankItem by registry.register(MNames.FLUID_TANK) { FluidTankItem(MBlocks.FLUID_TANK, Item.Properties().stacksTo(1), ItemsConfig::FLUID_TANK_CAPACITY) }
-	val LIQUID_XP_BUCKET: BucketItem by registry.register("liquid_xp_bucket") { BucketItem(MFluids::LIQUID_XP, Item.Properties().stacksTo(1).rarity(Rarity.UNCOMMON)) }
+	val LIQUID_XP_BUCKET: BucketItem by registry.register("liquid_xp_bucket") { BucketItem(MFluids.LIQUID_XP, Item.Properties().stacksTo(1).rarity(Rarity.UNCOMMON)) }
 
-	val TRITANIUM_COMPONENT: ForgeTier = ForgeTier(
-		Tiers.IRON.level,
+	val TRITANIUM_COMPONENT: SimpleTier = SimpleTier(
+		BlockTags.INCORRECT_FOR_IRON_TOOL,
 		3072,
 		Tiers.IRON.speed * 1.1f,
 		3.5f,
 		16,
-		MBlockTags.REQUIRES_TRITANIUM_TOOL
 	) { Ingredient.of(TRITANIUM_INGOT) }
 
-	val TRITANIUM_COMPONENT_NAME = ResourceLocation(OverdriveThatMatters.MOD_ID, "tritanium")
-
-	init {
-		TierSortingRegistry.registerTier(TRITANIUM_COMPONENT, TRITANIUM_COMPONENT_NAME, listOf(Tiers.IRON), listOf(Tiers.DIAMOND))
-	}
-
 	private val TOOLS_PROPRTIES = Item.Properties()
 
-	val TRITANIUM_SWORD: SwordItem by registry.register(MNames.TRITANIUM_SWORD) { SwordItem(TRITANIUM_COMPONENT, 4, -2.7f, TOOLS_PROPRTIES) }
-	val TRITANIUM_SHOVEL: ShovelItem by registry.register(MNames.TRITANIUM_SHOVEL) { ShovelItem(TRITANIUM_COMPONENT, 1.5f, -2.4f, TOOLS_PROPRTIES) }
-	val TRITANIUM_AXE: MatteryAxeItem by registry.register(MNames.TRITANIUM_AXE) { MatteryAxeItem(TRITANIUM_COMPONENT, 8.5f, -3.4f, TOOLS_PROPRTIES) }
-	val TRITANIUM_PICKAXE: PickaxeItem by registry.register(MNames.TRITANIUM_PICKAXE) { PickaxeItem(TRITANIUM_COMPONENT, 2, -2.8f, TOOLS_PROPRTIES) }
-	val TRITANIUM_HOE: HoeItem by registry.register(MNames.TRITANIUM_HOE) { HoeItem(TRITANIUM_COMPONENT, 0, -3.4f, TOOLS_PROPRTIES) }
+	val TRITANIUM_SWORD: SwordItem by registry.register(MNames.TRITANIUM_SWORD) { SwordItem(TRITANIUM_COMPONENT, TOOLS_PROPRTIES) }
+	val TRITANIUM_SHOVEL: ShovelItem by registry.register(MNames.TRITANIUM_SHOVEL) { ShovelItem(TRITANIUM_COMPONENT, TOOLS_PROPRTIES) }
+	val TRITANIUM_AXE: MatteryAxeItem by registry.register(MNames.TRITANIUM_AXE) { MatteryAxeItem(TRITANIUM_COMPONENT, TOOLS_PROPRTIES) }
+	val TRITANIUM_PICKAXE: PickaxeItem by registry.register(MNames.TRITANIUM_PICKAXE) { PickaxeItem(TRITANIUM_COMPONENT, TOOLS_PROPRTIES) }
+	val TRITANIUM_HOE: HoeItem by registry.register(MNames.TRITANIUM_HOE) { HoeItem(TRITANIUM_COMPONENT, TOOLS_PROPRTIES) }
 	val TRITANIUM_SHEARS: ShearsItem by registry.register(MNames.TRITANIUM_SHEARS) { object : ShearsItem(Properties().durability(3072)) {
 		override fun isValidRepairItem(pToRepair: ItemStack, pRepair: ItemStack): Boolean {
 			return pRepair.`is`(MItemTags.TRITANIUM_INGOTS)
@@ -371,8 +388,6 @@ object MItems {
 
 	val ENERGY_SWORD: Item by registry.register(MNames.ENERGY_SWORD) { EnergySwordItem() }
 
-	val PLASMA_RIFLE: Item by registry.register(MNames.PLASMA_RIFLE) { PlasmaRifleItem() }
-
 	val BLACK_HOLE_SCANNER: Item by registry.register(MNames.BLACK_HOLE_SCANNER) { MatteryItem(DEFAULT_PROPERTIES).addSimpleDescription().addSimpleDescription("2") }
 
 	val GRAVITATION_FIELD_LIMITER: Item by registry.register(MNames.GRAVITATION_FIELD_LIMITER) { Item(DEFAULT_PROPERTIES) }
@@ -456,7 +471,7 @@ object MItems {
 	val PORTABLE_CONDENSATION_DRIVE: Item by registry.register(MNames.PORTABLE_CONDENSATION_DRIVE) { PortableCondensationDriveItem(4000) }
 	val PORTABLE_DENSE_CONDENSATION_DRIVE: Item by registry.register(MNames.PORTABLE_DENSE_CONDENSATION_DRIVE) { PortableCondensationDriveItem(25000) }
 
-	val NUTRIENT_PASTE: Item by registry.register(MNames.NUTRIENT_PASTE) { Item(Item.Properties().stacksTo(64).food(FoodProperties.Builder().meat().nutrition(8).saturationMod(0.9F).build())) }
+	val NUTRIENT_PASTE: Item by registry.register(MNames.NUTRIENT_PASTE) { Item(Properties().stacksTo(64).food(FoodProperties.Builder().nutrition(8).saturationModifier(0.9F).build())) }
 
 	val LABORATORY_LAMP: Item by registry.register(MNames.LABORATORY_LAMP) { BlockItem(MBlocks.LABORATORY_LAMP, DEFAULT_PROPERTIES) }
 	val LABORATORY_LAMP_INVERTED: Item by registry.register(MNames.LABORATORY_LAMP_INVERTED) { BlockItem(MBlocks.LABORATORY_LAMP_INVERTED, DEFAULT_PROPERTIES) }
@@ -562,9 +577,9 @@ object MItems {
 
 	object ExopackUpgrades {
 		val INVENTORY_UPGRADE_CREATIVE: ExopackSlotUpgradeItem by registry.register("exosuit_inventory_upgrade_creative") { ExopackSlotUpgradeItem(null, 27, Rarity.EPIC) }
-		val CRAFTING_UPGRADE: ExopackUpgradeItem by registry.register("exosuit_crafting_upgrade") { ExopackUpgradeItem(MatteryPlayerCapability.UpgradeType.CRAFTING, "crafting_upgrade", "crafting_upgraded") }
-		val SMELTING_UPGRADE: ExopackUpgradeItem by registry.register("exopack_smelting_upgrade") { ExopackUpgradeItem(MatteryPlayerCapability.UpgradeType.SMELTING, "smelting_upgrade", "smelting_installed") }
-		val ENDER_UPGRADE: ExopackUpgradeItem by registry.register("exopack_ender_upgrade") { ExopackUpgradeItem(MatteryPlayerCapability.UpgradeType.ENDER_ACCESS, "ender_access_upgrade", "ender_access_installed") }
+		val CRAFTING_UPGRADE: ExopackUpgradeItem by registry.register("exosuit_crafting_upgrade") { ExopackUpgradeItem(MatteryPlayer.UpgradeType.CRAFTING, "crafting_upgrade", "crafting_upgraded") }
+		val SMELTING_UPGRADE: ExopackUpgradeItem by registry.register("exopack_smelting_upgrade") { ExopackUpgradeItem(MatteryPlayer.UpgradeType.SMELTING, "smelting_upgrade", "smelting_installed") }
+		val ENDER_UPGRADE: ExopackUpgradeItem by registry.register("exopack_ender_upgrade") { ExopackUpgradeItem(MatteryPlayer.UpgradeType.ENDER_ACCESS, "ender_access_upgrade", "ender_access_installed") }
 
 		val INVENTORY_UPGRADES = SupplierList(8) {
 			registry.register("exosuit_inventory_upgrade_$it") { ExopackSlotUpgradeItem(18, Rarity.COMMON) }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MLootItemConditions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MLootItemConditions.kt
index 29a890a59..4b2f95d77 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MLootItemConditions.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MLootItemConditions.kt
@@ -1,10 +1,8 @@
 package ru.dbotthepony.mc.otm.registry
 
-import net.minecraft.core.Registry
 import net.minecraft.core.registries.Registries
 import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.data.SingletonCodec
 import ru.dbotthepony.mc.otm.data.condition.ChanceWithPlaytimeCondition
@@ -24,7 +22,7 @@ object MLootItemConditions {
 	val KILLED_BY_REAL_PLAYER_OR_INDIRECTLY: LootItemConditionType 	by registry.register("killed_by_real_player_or_indirectly") { LootItemConditionType(SingletonCodec(KilledByRealPlayerOrIndirectly)) }
 	val CHANCE: LootItemConditionType 								by registry.register("chance") { LootItemConditionType(ChanceCondition.CODEC) }
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registry.register(bus)
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt
index 6beedd44e..912826c41 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt
@@ -1,22 +1,22 @@
 package ru.dbotthepony.mc.otm.registry
 
-import net.minecraft.client.gui.screens.MenuScreens
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.world.flag.FeatureFlags
 import net.minecraft.world.inventory.MenuType
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent
 import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerBlockEntity
 import ru.dbotthepony.mc.otm.client.screen.decorative.CargoCrateScreen
 import ru.dbotthepony.mc.otm.client.screen.decorative.FluidTankScreen
 import ru.dbotthepony.mc.otm.client.screen.decorative.HoloSignScreen
 import ru.dbotthepony.mc.otm.client.screen.decorative.MinecartCargoCrateScreen
-import ru.dbotthepony.mc.otm.client.screen.matter.MatterReconstructorScreen
+import ru.dbotthepony.mc.otm.client.screen.decorative.PainterScreen
 import ru.dbotthepony.mc.otm.client.screen.matter.MatterBottlerScreen
 import ru.dbotthepony.mc.otm.client.screen.matter.MatterCapacitorBankScreen
 import ru.dbotthepony.mc.otm.client.screen.matter.MatterDecomposerScreen
 import ru.dbotthepony.mc.otm.client.screen.matter.MatterEntanglerScreen
 import ru.dbotthepony.mc.otm.client.screen.matter.MatterPanelScreen
+import ru.dbotthepony.mc.otm.client.screen.matter.MatterReconstructorScreen
 import ru.dbotthepony.mc.otm.client.screen.matter.MatterRecyclerScreen
 import ru.dbotthepony.mc.otm.client.screen.matter.MatterReplicatorScreen
 import ru.dbotthepony.mc.otm.client.screen.matter.MatterScannerScreen
@@ -27,29 +27,29 @@ import ru.dbotthepony.mc.otm.client.screen.storage.ItemMonitorScreen
 import ru.dbotthepony.mc.otm.client.screen.storage.StorageBusScreen
 import ru.dbotthepony.mc.otm.client.screen.storage.StorageImporterExporterScreen
 import ru.dbotthepony.mc.otm.client.screen.storage.StoragePowerSupplierScreen
+import ru.dbotthepony.mc.otm.client.screen.tech.AbstractProcessingMachineScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.AndroidChargerScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.AndroidStationScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.BatteryBankScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.ChemicalGeneratorScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.CobblerScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.EnergyCounterScreen
+import ru.dbotthepony.mc.otm.client.screen.tech.EnergyHatchScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.EnergyServoScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.EssenceStorageScreen
-import ru.dbotthepony.mc.otm.client.screen.decorative.PainterScreen
-import ru.dbotthepony.mc.otm.client.screen.tech.AbstractProcessingMachineScreen
-import ru.dbotthepony.mc.otm.client.screen.tech.EnergyHatchScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.ItemHatchScreen
 import ru.dbotthepony.mc.otm.client.screen.tech.MatterHatchScreen
 import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu
 import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu
 import ru.dbotthepony.mc.otm.menu.decorative.HoloSignMenu
 import ru.dbotthepony.mc.otm.menu.decorative.MinecartCargoCrateMenu
-import ru.dbotthepony.mc.otm.menu.matter.MatterReconstructorMenu
+import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu
 import ru.dbotthepony.mc.otm.menu.matter.MatterBottlerMenu
 import ru.dbotthepony.mc.otm.menu.matter.MatterCapacitorBankMenu
 import ru.dbotthepony.mc.otm.menu.matter.MatterDecomposerMenu
 import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu
 import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu
+import ru.dbotthepony.mc.otm.menu.matter.MatterReconstructorMenu
 import ru.dbotthepony.mc.otm.menu.matter.MatterRecyclerMenu
 import ru.dbotthepony.mc.otm.menu.matter.MatterReplicatorMenu
 import ru.dbotthepony.mc.otm.menu.matter.MatterScannerMenu
@@ -66,17 +66,16 @@ import ru.dbotthepony.mc.otm.menu.tech.BatteryBankMenu
 import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu
 import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu
 import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu
+import ru.dbotthepony.mc.otm.menu.tech.EnergyHatchMenu
 import ru.dbotthepony.mc.otm.menu.tech.EnergyServoMenu
 import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu
-import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu
-import ru.dbotthepony.mc.otm.menu.tech.EnergyHatchMenu
 import ru.dbotthepony.mc.otm.menu.tech.ItemHatchMenu
 import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu
 import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu
 import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu
 
 object MMenus {
-	private val registry = MDeferredRegister(ForgeRegistries.MENU_TYPES)
+	private val registry = MDeferredRegister(BuiltInRegistries.MENU)
 
 	val ANDROID_STATION by registry.register(MNames.ANDROID_STATION) { MenuType(::AndroidStationMenu, FeatureFlags.VANILLA_SET) }
 	val ANDROID_CHARGER by registry.register(MNames.ANDROID_CHARGER) { MenuType({ a, b -> AndroidChargerMenu(a, b, null as AndroidChargerBlockEntity?) }, FeatureFlags.VANILLA_SET) }
@@ -120,53 +119,51 @@ object MMenus {
 	val STORAGE_IMPORTER_EXPORTER by registry.register(MNames.STORAGE_IMPORTER) { MenuType(::StorageImporterExporterMenu, FeatureFlags.VANILLA_SET) }
 	val STORAGE_POWER_SUPPLIER by registry.register(MNames.STORAGE_POWER_SUPPLIER) { MenuType(::StoragePowerSupplierMenu, FeatureFlags.VANILLA_SET) }
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registry.register(bus)
-		bus.addListener(this::registerClient)
+		bus.addListener(this::registerScreens)
 	}
 
-	private fun registerClient(event: FMLClientSetupEvent) {
-		event.enqueueWork {
-			MenuScreens.register(ANDROID_STATION, ::AndroidStationScreen)
-			MenuScreens.register(ANDROID_CHARGER, ::AndroidChargerScreen)
-			MenuScreens.register(BATTERY_BANK, ::BatteryBankScreen)
-			MenuScreens.register(MATTER_DECOMPOSER, ::MatterDecomposerScreen)
-			MenuScreens.register(MATTER_CAPACITOR_BANK, ::MatterCapacitorBankScreen)
-			MenuScreens.register(PATTERN_STORAGE, ::PatternStorageScreen)
-			MenuScreens.register(MATTER_SCANNER, ::MatterScannerScreen)
-			MenuScreens.register(MATTER_PANEL, ::MatterPanelScreen)
-			MenuScreens.register(MATTER_REPLICATOR, ::MatterReplicatorScreen)
-			MenuScreens.register(MATTER_BOTTLER, ::MatterBottlerScreen)
-			MenuScreens.register(DRIVE_VIEWER, ::DriveViewerScreen)
-			MenuScreens.register(CARGO_CRATE, ::CargoCrateScreen)
-			MenuScreens.register(MINECART_CARGO_CRATE, ::MinecartCargoCrateScreen)
-			MenuScreens.register(DRIVE_RACK, ::DriveRackScreen)
-			MenuScreens.register(ITEM_MONITOR, ::ItemMonitorScreen)
-			MenuScreens.register(ENERGY_COUNTER, ::EnergyCounterScreen)
-			MenuScreens.register(CHEMICAL_GENERATOR, ::ChemicalGeneratorScreen)
-			MenuScreens.register(PLATE_PRESS, ::AbstractProcessingMachineScreen)
-			MenuScreens.register(TWIN_PLATE_PRESS, ::AbstractProcessingMachineScreen)
-			MenuScreens.register(MATTER_RECYCLER, ::MatterRecyclerScreen)
-			MenuScreens.register(STORAGE_BUS, ::StorageBusScreen)
-			MenuScreens.register(STORAGE_IMPORTER_EXPORTER, ::StorageImporterExporterScreen)
-			MenuScreens.register(STORAGE_POWER_SUPPLIER, ::StoragePowerSupplierScreen)
-			MenuScreens.register(ENERGY_SERVO, ::EnergyServoScreen)
-			MenuScreens.register(HOLO_SIGN, ::HoloSignScreen)
-			MenuScreens.register(COBBLESTONE_GENERATOR, ::CobblerScreen)
-			MenuScreens.register(ESSENCE_STORAGE, ::EssenceStorageScreen)
-			MenuScreens.register(ITEM_REPAIER, ::MatterReconstructorScreen)
-			MenuScreens.register(FLUID_TANK, ::FluidTankScreen)
-			MenuScreens.register(POWERED_FURNACE, ::AbstractProcessingMachineScreen)
-			MenuScreens.register(POWERED_BLAST_FURNACE, ::AbstractProcessingMachineScreen)
-			MenuScreens.register(POWERED_SMOKER, ::AbstractProcessingMachineScreen)
-			MenuScreens.register(PAINTER, ::PainterScreen)
-			MenuScreens.register(MATTER_ENTANGLER, ::MatterEntanglerScreen)
-			MenuScreens.register(ITEM_INPUT_HATCH, ::ItemHatchScreen)
-			MenuScreens.register(ITEM_OUTPUT_HATCH, ::ItemHatchScreen)
-			MenuScreens.register(MATTER_INPUT_HATCH, ::MatterHatchScreen)
-			MenuScreens.register(MATTER_OUTPUT_HATCH, ::MatterHatchScreen)
-			MenuScreens.register(ENERGY_INPUT_HATCH, ::EnergyHatchScreen)
-			MenuScreens.register(ENERGY_OUTPUT_HATCH, ::EnergyHatchScreen)
-		}
+	private fun registerScreens(event: RegisterMenuScreensEvent) {
+		event.register(ANDROID_STATION, ::AndroidStationScreen)
+		event.register(ANDROID_CHARGER, ::AndroidChargerScreen)
+		event.register(BATTERY_BANK, ::BatteryBankScreen)
+		event.register(MATTER_DECOMPOSER, ::MatterDecomposerScreen)
+		event.register(MATTER_CAPACITOR_BANK, ::MatterCapacitorBankScreen)
+		event.register(PATTERN_STORAGE, ::PatternStorageScreen)
+		event.register(MATTER_SCANNER, ::MatterScannerScreen)
+		event.register(MATTER_PANEL, ::MatterPanelScreen)
+		event.register(MATTER_REPLICATOR, ::MatterReplicatorScreen)
+		event.register(MATTER_BOTTLER, ::MatterBottlerScreen)
+		event.register(DRIVE_VIEWER, ::DriveViewerScreen)
+		event.register(CARGO_CRATE, ::CargoCrateScreen)
+		event.register(MINECART_CARGO_CRATE, ::MinecartCargoCrateScreen)
+		event.register(DRIVE_RACK, ::DriveRackScreen)
+		event.register(ITEM_MONITOR, ::ItemMonitorScreen)
+		event.register(ENERGY_COUNTER, ::EnergyCounterScreen)
+		event.register(CHEMICAL_GENERATOR, ::ChemicalGeneratorScreen)
+		event.register(PLATE_PRESS, ::AbstractProcessingMachineScreen)
+		event.register(TWIN_PLATE_PRESS, ::AbstractProcessingMachineScreen)
+		event.register(MATTER_RECYCLER, ::MatterRecyclerScreen)
+		event.register(STORAGE_BUS, ::StorageBusScreen)
+		event.register(STORAGE_IMPORTER_EXPORTER, ::StorageImporterExporterScreen)
+		event.register(STORAGE_POWER_SUPPLIER, ::StoragePowerSupplierScreen)
+		event.register(ENERGY_SERVO, ::EnergyServoScreen)
+		event.register(HOLO_SIGN, ::HoloSignScreen)
+		event.register(COBBLESTONE_GENERATOR, ::CobblerScreen)
+		event.register(ESSENCE_STORAGE, ::EssenceStorageScreen)
+		event.register(ITEM_REPAIER, ::MatterReconstructorScreen)
+		event.register(FLUID_TANK, ::FluidTankScreen)
+		event.register(POWERED_FURNACE, ::AbstractProcessingMachineScreen)
+		event.register(POWERED_BLAST_FURNACE, ::AbstractProcessingMachineScreen)
+		event.register(POWERED_SMOKER, ::AbstractProcessingMachineScreen)
+		event.register(PAINTER, ::PainterScreen)
+		event.register(MATTER_ENTANGLER, ::MatterEntanglerScreen)
+		event.register(ITEM_INPUT_HATCH, ::ItemHatchScreen)
+		event.register(ITEM_OUTPUT_HATCH, ::ItemHatchScreen)
+		event.register(MATTER_INPUT_HATCH, ::MatterHatchScreen)
+		event.register(MATTER_OUTPUT_HATCH, ::MatterHatchScreen)
+		event.register(ENERGY_INPUT_HATCH, ::EnergyHatchScreen)
+		event.register(ENERGY_OUTPUT_HATCH, ::EnergyHatchScreen)
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt
index ffaf11717..d884f51ee 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt
@@ -1,7 +1,7 @@
 package ru.dbotthepony.mc.otm.registry
 
-import net.minecraft.resources.ResourceLocation
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 object MNames {
 	const val LABORATORY_LAMP = "laboratory_lamp"
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt
index 6be2006db..379d2403f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt
@@ -1,12 +1,9 @@
 package ru.dbotthepony.mc.otm.registry
 
-import net.minecraft.resources.ResourceLocation
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.world.item.crafting.Recipe
 import net.minecraft.world.item.crafting.RecipeType
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.registries.RegistryObject
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.recipe.*
@@ -21,10 +18,10 @@ object MRecipes {
 		}
 	}
 
-	private val types = MDeferredRegister(ForgeRegistries.RECIPE_TYPES, OverdriveThatMatters.MOD_ID)
-	private val serializers = MDeferredRegister(ForgeRegistries.RECIPE_SERIALIZERS, OverdriveThatMatters.MOD_ID)
+	private val types = MDeferredRegister(BuiltInRegistries.RECIPE_TYPE, OverdriveThatMatters.MOD_ID)
+	private val serializers = MDeferredRegister(BuiltInRegistries.RECIPE_SERIALIZER, OverdriveThatMatters.MOD_ID)
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		types.register(bus)
 		serializers.register(bus)
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt
index ef9395342..9137cdb18 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt
@@ -7,9 +7,9 @@ import net.minecraft.advancements.CriteriaTriggers
 import net.minecraft.client.renderer.item.ItemProperties
 import net.minecraft.core.BlockPos
 import net.minecraft.core.cauldron.CauldronInteraction
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.core.registries.Registries
 import net.minecraft.nbt.CompoundTag
-import net.minecraft.resources.ResourceLocation
 import net.minecraft.world.entity.EntityType
 import net.minecraft.world.entity.ai.village.poi.PoiType
 import net.minecraft.world.entity.ai.village.poi.PoiTypes
@@ -24,46 +24,34 @@ import net.minecraft.world.level.block.state.BlockBehaviour
 import net.minecraft.world.level.block.state.BlockState
 import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
 import net.minecraft.world.level.material.MapColor
-import net.minecraftforge.api.distmarker.Dist
-import net.minecraftforge.client.event.RegisterColorHandlersEvent
-import net.minecraftforge.client.event.RegisterItemDecorationsEvent
-import net.minecraftforge.client.model.DynamicFluidContainerModel
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
-import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent
-import net.minecraftforge.fml.loading.FMLEnvironment
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.registries.NewRegistryEvent
-import net.minecraftforge.registries.RegisterEvent
+import net.neoforged.bus.api.IEventBus
+import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent
+import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent
+import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent
+import net.neoforged.neoforge.client.event.RegisterItemDecorationsEvent
+import net.neoforged.neoforge.client.model.DynamicFluidContainerModel
+import net.neoforged.neoforge.registries.NewRegistryEvent
+import net.neoforged.neoforge.registries.RegisterEvent
+import ru.dbotthepony.kommons.math.RGBAColor
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.android.AndroidResearchResult
-import ru.dbotthepony.mc.otm.android.AndroidResearchResults
 import ru.dbotthepony.mc.otm.android.AndroidFeatureType
-import ru.dbotthepony.mc.otm.android.AndroidResearchDescription
-import ru.dbotthepony.mc.otm.android.AndroidResearchDescriptions
 import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature
 import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature
 import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock
 import ru.dbotthepony.mc.otm.block.decorative.TritaniumPressurePlate
 import ru.dbotthepony.mc.otm.capability.matteryEnergy
 import ru.dbotthepony.mc.otm.client.MatteryGUI
-import ru.dbotthepony.mc.otm.compat.vanilla.MatteryChestMenu
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom
 import ru.dbotthepony.mc.otm.core.math.Decimal
-import ru.dbotthepony.kommons.math.RGBAColor
-import ru.dbotthepony.mc.otm.data.DecimalProvider
 import ru.dbotthepony.mc.otm.isClient
 import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem
-import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction
-import ru.dbotthepony.mc.otm.matter.IMatterFunction
 import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock
 import ru.dbotthepony.mc.otm.registry.objects.DecorativeBlock
 import ru.dbotthepony.mc.otm.registry.objects.IBlockItemRegistryAcceptor
 import ru.dbotthepony.mc.otm.registry.objects.StripedColoredDecorativeBlock
 import ru.dbotthepony.mc.otm.shapes.BlockShapes
-import ru.dbotthepony.mc.otm.storage.StorageStack
 import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger
-import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger
 import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger
 import ru.dbotthepony.mc.otm.triggers.AndroidTravelUnderwater
 import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger
@@ -79,6 +67,7 @@ import ru.dbotthepony.mc.otm.triggers.ExopackGainedSmeltingTrigger
 import ru.dbotthepony.mc.otm.triggers.ExopackObtainedTrigger
 import ru.dbotthepony.mc.otm.triggers.ExopackSlotsExpandedTrigger
 import ru.dbotthepony.mc.otm.triggers.FallDampenersSaveTrigger
+import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger
 import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger
 import ru.dbotthepony.mc.otm.triggers.NanobotsArmorTrigger
 import ru.dbotthepony.mc.otm.triggers.ShockwaveDamageMobTrigger
@@ -86,7 +75,7 @@ import ru.dbotthepony.mc.otm.triggers.ShockwaveTrigger
 import ru.dbotthepony.mc.otm.triggers.TakeItemOutOfReplicatorTrigger
 
 object MRegistry : IBlockItemRegistryAcceptor {
-	private val features = RegistryDelegate<AndroidFeatureType<*>>("android_features")
+	private val features = RegistryDelegate<AndroidFeatureType<*>>("android_features") { sync(true) }
 	val ANDROID_FEATURES by features
 	val ANDROID_FEATURES_LOCATION get() = features.location
 	val ANDROID_FEATURES_KEY get() = features.key
@@ -110,7 +99,7 @@ object MRegistry : IBlockItemRegistryAcceptor {
 		DyeColor.YELLOW,
 	)
 
-	private fun register(event: NewRegistryEvent) {
+	fun register(event: NewRegistryEvent) {
 		features.build(event)
 	}
 
@@ -155,16 +144,16 @@ object MRegistry : IBlockItemRegistryAcceptor {
 	val TRITANIUM_STAIRS = DecorativeBlock(MNames.TRITANIUM_STAIRS) {
 		StairBlock(
 			{ TRITANIUM_BLOCK.allBlocks[it]!!.defaultBlockState() },
-			BlockBehaviour.Properties.copy(TRITANIUM_BLOCK.allBlocks[it]!!)
+			BlockBehaviour.Properties.ofFullCopy(TRITANIUM_BLOCK.allBlocks[it]!!)
 		)
 	}.also { decorativeBlocks.add(it) }
 
 	val TRITANIUM_SLAB = DecorativeBlock(MNames.TRITANIUM_SLAB) {
-		SlabBlock(BlockBehaviour.Properties.copy(TRITANIUM_BLOCK.allBlocks[it]!!))
+		SlabBlock(BlockBehaviour.Properties.ofLegacyCopy(TRITANIUM_BLOCK.allBlocks[it]!!))
 	}.also { decorativeBlocks.add(it) }
 
 	val TRITANIUM_WALL = DecorativeBlock(MNames.TRITANIUM_WALL) {
-		WallBlock(BlockBehaviour.Properties.copy(TRITANIUM_BLOCK.allBlocks[it]!!))
+		WallBlock(BlockBehaviour.Properties.ofLegacyCopy(TRITANIUM_BLOCK.allBlocks[it]!!))
 	}.also { decorativeBlocks.add(it) }
 
 	val TRITANIUM_PRESSURE_PLATE = DecorativeBlock(MNames.TRITANIUM_PRESSURE_PLATE, ::TritaniumPressurePlate).also { decorativeBlocks.add(it) }
@@ -262,15 +251,15 @@ object MRegistry : IBlockItemRegistryAcceptor {
 	}).also { decorativeBlocks.add(it) }
 
 	val TRITANIUM_STRIPED_STAIRS = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_STAIRS, { colorA, colorB ->
-		StairBlock({ TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB).defaultBlockState() }, BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
+		StairBlock(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB).defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
 	}).also { decorativeBlocks.add(it) }
 
 	val TRITANIUM_STRIPED_SLAB = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_SLAB, { colorA, colorB ->
-		SlabBlock(BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
+		SlabBlock(BlockBehaviour.Properties.ofFullCopy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
 	}).also { decorativeBlocks.add(it) }
 
 	val TRITANIUM_STRIPED_WALL = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_WALL, { colorA, colorB ->
-		WallBlock(BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
+		WallBlock(BlockBehaviour.Properties.ofFullCopy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
 	}).also { decorativeBlocks.add(it) }
 
 	private fun registerEvent(event: RegisterEvent) {
@@ -300,36 +289,6 @@ object MRegistry : IBlockItemRegistryAcceptor {
 		if (isClient) bus.addListener(this::registerItemColorHandlers)
 		if (isClient) bus.addListener(this::registerItemDecorators)
 
-		MCreativeTabs.initialize(bus)
-
-		DecimalProvider.register(bus)
-		AndroidResearchDescription.register(bus)
-		AndroidResearchDescriptions.register(bus)
-		AndroidResearchResult.register(bus)
-		AndroidResearchResults.register(bus)
-
-		AbstractRegistryAction.register(bus)
-		IMatterFunction.register(bus)
-
-		MBlocks.register(bus)
-		MFluids.register(bus)
-		MBlockEntities.register(bus)
-		MEntityTypes.register(bus)
-		MMenus.register(bus)
-		MItems.register(bus)
-		AndroidFeatures.register(bus)
-		MSoundEvents.register(bus)
-		LootModifiers.register(bus)
-		MItemFunctionTypes.register(bus)
-		MLootItemConditions.register(bus)
-		MRecipes.register(bus)
-		StorageStack.register(bus)
-		MatteryChestMenu.register(bus)
-
-		if (FMLEnvironment.dist == Dist.CLIENT) {
-			MBlockColors.register(bus)
-		}
-
 		// call static constructors
 		NanobotsArmorFeature.Companion
 		EnderTeleporterFeature.Companion
@@ -381,13 +340,13 @@ object MRegistry : IBlockItemRegistryAcceptor {
 			}
 
 			ItemProperties.register(MItems.EXPLOSIVE_HAMMER, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_primed")) { stack, level, entity, _ ->
-				if (MItems.EXPLOSIVE_HAMMER.isPrimed(stack)) {
+				if (MItems.EXPLOSIVE_HAMMER.isPrimed(stack) || entity == null) {
 					1f
 				} else {
-					if ((entity?.useItemRemainingTicks ?: 0) <= 0) {
+					if (entity.useItemRemainingTicks <= 0) {
 						0f
 					} else {
-						(stack.useDuration - (entity?.useItemRemainingTicks ?: stack.useDuration)).toFloat() / stack.useDuration
+						(stack.getUseDuration(entity) - entity.useItemRemainingTicks).toFloat() / stack.getUseDuration(entity)
 					}
 				}
 			}
@@ -420,7 +379,7 @@ object MRegistry : IBlockItemRegistryAcceptor {
 	}
 
 	private fun registerItemDecorators(event: RegisterItemDecorationsEvent) {
-		ForgeRegistries.ITEMS.forEach {
+		BuiltInRegistries.ITEM.forEach {
 			if (it is ShieldItem) {
 				event.register(it, MatteryGUI::renderShieldCooldownOverlay)
 			}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MSoundEvents.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MSoundEvents.kt
index 56946d77c..83c32d073 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MSoundEvents.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MSoundEvents.kt
@@ -1,17 +1,13 @@
 package ru.dbotthepony.mc.otm.registry
 
-import net.minecraft.resources.ResourceLocation
+import net.minecraft.core.registries.BuiltInRegistries
 import net.minecraft.sounds.SoundEvent
-import net.minecraft.sounds.SoundEvents
-import net.minecraft.world.entity.EntityType
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.ForgeRegistries
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 object MSoundEvents {
-	private val registry: MDeferredRegister<SoundEvent> = MDeferredRegister(ForgeRegistries.SOUND_EVENTS, OverdriveThatMatters.MOD_ID)
+	private val registry: MDeferredRegister<SoundEvent> = MDeferredRegister(BuiltInRegistries.SOUND_EVENT, OverdriveThatMatters.MOD_ID)
 
 	// TODO: 1.19.3
 	private fun make(name: String): MDeferredRegister<SoundEvent>.Entry<SoundEvent> = registry.register(name) { SoundEvent.createVariableRangeEvent(ResourceLocation(OverdriveThatMatters.MOD_ID, name)) }
@@ -25,7 +21,7 @@ object MSoundEvents {
 	val ANDROID_SHOCKWAVE		by make("android.shockwave")
 	val ANDROID_PROJ_PARRY		by make("android.projectile_parry")
 
-	internal fun register(bus: IEventBus) {
+	fun register(bus: IEventBus) {
 		registry.register(bus)
 	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/RegistryDelegate.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/RegistryDelegate.kt
index 784b775c3..fc412a415 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/RegistryDelegate.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/RegistryDelegate.kt
@@ -2,20 +2,18 @@ package ru.dbotthepony.mc.otm.registry
 
 import net.minecraft.core.Registry
 import net.minecraft.resources.ResourceKey
-import net.minecraft.resources.ResourceLocation
-import net.minecraftforge.registries.ForgeRegistry
-import net.minecraftforge.registries.IForgeRegistry
-import net.minecraftforge.registries.NewRegistryEvent
-import net.minecraftforge.registries.RegistryBuilder
+import net.neoforged.neoforge.registries.NewRegistryEvent
+import net.neoforged.neoforge.registries.RegistryBuilder
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import java.util.function.Supplier
 import kotlin.properties.ReadOnlyProperty
 import kotlin.reflect.KProperty
 
-class RegistryDelegate<T>(key: String, private val configurator: RegistryBuilder<T>.() -> Unit = {}) : ReadOnlyProperty<Any, ForgeRegistry<T>>, Supplier<ForgeRegistry<T>>, Lazy<ForgeRegistry<T>> {
-	private var _value: Supplier<IForgeRegistry<T>?>? = null
+class RegistryDelegate<T>(key: String, private val configurator: RegistryBuilder<T>.() -> Unit = {}) : ReadOnlyProperty<Any, Registry<T>>, Supplier<Registry<T>>, Lazy<Registry<T>> {
+	private var _value: Registry<T>? = null
 
-	override val value: ForgeRegistry<T>
+	override val value: Registry<T>
 		get() = get()
 
 	override fun isInitialized(): Boolean {
@@ -25,12 +23,12 @@ class RegistryDelegate<T>(key: String, private val configurator: RegistryBuilder
 	val location = ResourceLocation(OverdriveThatMatters.MOD_ID, key)
 	val key: ResourceKey<Registry<T>> = ResourceKey.createRegistryKey(location)
 
-	override fun get(): ForgeRegistry<T> {
-		val supp = _value ?: throw IllegalStateException("Tried to access uninitialized registry $location")
-		return supp.get() as ForgeRegistry<T>? ?: throw IllegalStateException("Accessing registry $location too early")
+	override fun get(): Registry<T> {
+		val value = _value ?: throw IllegalStateException("Tried to access uninitialized registry $location")
+		return value
 	}
 
-	override fun getValue(thisRef: Any, property: KProperty<*>): ForgeRegistry<T> {
+	override fun getValue(thisRef: Any, property: KProperty<*>): Registry<T> {
 		return get()
 	}
 
@@ -39,9 +37,7 @@ class RegistryDelegate<T>(key: String, private val configurator: RegistryBuilder
 			throw IllegalStateException("Already built registry $location!")
 		}
 
-		_value = RegistryBuilder<T>().let {
-			it.setName(location)
-			// it.type = AndroidFeatureType::class.java
+		_value = RegistryBuilder(key).let {
 			configurator.invoke(it)
 			event.create(it)
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/ColoredDecorativeBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/ColoredDecorativeBlock.kt
index 4416da20d..1b1a52221 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/ColoredDecorativeBlock.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/ColoredDecorativeBlock.kt
@@ -5,8 +5,6 @@ import net.minecraft.world.item.DyeColor
 import net.minecraft.world.item.Item
 import net.minecraft.world.level.block.Block
 import net.minecraft.world.level.block.state.BlockBehaviour
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.RegistryObject
 import ru.dbotthepony.mc.otm.core.collect.SupplierMap
 import ru.dbotthepony.mc.otm.registry.MDeferredRegister
 import ru.dbotthepony.mc.otm.registry.MRegistry
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/StripedColoredDecorativeBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/StripedColoredDecorativeBlock.kt
index c0a68796d..63f2b57cb 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/StripedColoredDecorativeBlock.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/StripedColoredDecorativeBlock.kt
@@ -6,8 +6,6 @@ import net.minecraft.world.item.BlockItem
 import net.minecraft.world.item.DyeColor
 import net.minecraft.world.item.Item
 import net.minecraft.world.level.block.Block
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.RegistryObject
 import ru.dbotthepony.mc.otm.core.collect.SupplierList
 import ru.dbotthepony.mc.otm.core.collect.SupplierMap
 import ru.dbotthepony.mc.otm.registry.MDeferredRegister
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/saveddata/SavedCountingMap.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/saveddata/SavedCountingMap.kt
index 42263a437..087240065 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/saveddata/SavedCountingMap.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/saveddata/SavedCountingMap.kt
@@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.saveddata
 
 import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
 import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
+import net.minecraft.core.HolderLookup
 import net.minecraft.nbt.CompoundTag
 import net.minecraft.nbt.IntTag
 import net.minecraft.nbt.ListTag
@@ -84,7 +85,7 @@ class SavedCountingMap<T> private constructor(
 		return computeIfAbsent(nextIndex++)
 	}
 
-	override fun save(output: CompoundTag): CompoundTag {
+	override fun save(output: CompoundTag, registry: HolderLookup.Provider): CompoundTag {
 		output["map"] = ListTag().also {
 			for ((key, value) in this.map) {
 				val compound = CompoundTag()
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt
index c58cbf17f..a2afdabd3 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt
@@ -29,19 +29,19 @@ class ItemStorageStack private constructor(private val stack: ItemStack, count:
 	}
 
 	override fun equals(other: Any?): Boolean {
-		return other is ItemStorageStack && other.count == count && ItemStack.isSameItemSameTags(stack, other.stack)
+		return other is ItemStorageStack && other.count == count && ItemStack.isSameItemSameComponents(stack, other.stack)
 	}
 
 	override fun hashCode(): Int {
-		return Integer.rotateLeft(count.hashCode(), 12) xor (stack.item.hashCode() * 31 + stack.tag.hashCode())
+		return Integer.rotateLeft(count.hashCode(), 12) xor (stack.item.hashCode() * 31 + stack.components.hashCode())
 	}
 
 	override fun equalsWithoutCount(other: StorageStack<*>): Boolean {
-		return other is ItemStorageStack && ItemStack.isSameItemSameTags(stack, other.stack)
+		return other is ItemStorageStack && ItemStack.isSameItemSameComponents(stack, other.stack)
 	}
 
 	override fun hashCodeWithoutCount(): Int {
-		return stack.item.hashCode() * 31 + stack.tag.hashCode()
+		return stack.item.hashCode() * 31 + stack.components.hashCode()
 	}
 
 	override val isEmpty: Boolean = stack.isEmpty || super.isEmpty
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt
index 6b161f570..e2e28eb23 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt
@@ -1,17 +1,17 @@
 package ru.dbotthepony.mc.otm.storage
 
 import it.unimi.dsi.fastutil.Hash
-import net.minecraft.network.FriendlyByteBuf
+import net.minecraft.network.RegistryFriendlyByteBuf
 import net.minecraft.world.item.ItemStack
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.registries.DeferredRegister
-import ru.dbotthepony.kommons.util.getValue
+import net.neoforged.bus.api.IEventBus
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.core.getValue
 import ru.dbotthepony.mc.otm.core.math.Decimal
 import ru.dbotthepony.mc.otm.core.readBigInteger
+import ru.dbotthepony.mc.otm.core.readItem
 import ru.dbotthepony.mc.otm.core.writeBigInteger
-import ru.dbotthepony.mc.otm.core.writeItemType
+import ru.dbotthepony.mc.otm.core.writeItem
+import ru.dbotthepony.mc.otm.registry.MDeferredRegister
 import ru.dbotthepony.mc.otm.registry.RegistryDelegate
 import java.math.BigInteger
 
@@ -54,15 +54,15 @@ abstract class StorageStack<S : StorageStack<S>>(val count: BigInteger) {
 		return copy(newCount)
 	}
 
-	fun write(buff: FriendlyByteBuf) {
+	fun write(buff: RegistryFriendlyByteBuf) {
 		type.write(buff, this as S)
 	}
 
 	interface Type<T : StorageStack<*>> {
 		val empty: T
 
-		fun read(buff: FriendlyByteBuf): T
-		fun write(buff: FriendlyByteBuf, value: T)
+		fun read(buff: RegistryFriendlyByteBuf): T
+		fun write(buff: RegistryFriendlyByteBuf, value: T)
 
 		/**
 		 * If there is not enough energy for operation, it is completely cancelled
@@ -83,14 +83,14 @@ abstract class StorageStack<S : StorageStack<S>>(val count: BigInteger) {
 	class SimpleType<T : StorageStack<*>>(
 		val energyPerOperation: Decimal,
 		override val empty: T,
-		private val read: (FriendlyByteBuf) -> T,
-		private val write: (FriendlyByteBuf, T) -> Unit
+		private val read: (RegistryFriendlyByteBuf) -> T,
+		private val write: (RegistryFriendlyByteBuf, T) -> Unit
 	) : Type<T> {
-		override fun read(buff: FriendlyByteBuf): T {
+		override fun read(buff: RegistryFriendlyByteBuf): T {
 			return read.invoke(buff)
 		}
 
-		override fun write(buff: FriendlyByteBuf, value: T) {
+		override fun write(buff: RegistryFriendlyByteBuf, value: T) {
 			write.invoke(buff, value)
 		}
 
@@ -110,11 +110,11 @@ abstract class StorageStack<S : StorageStack<S>>(val count: BigInteger) {
 			return o?.hashCodeWithoutCount() ?: 0
 		}
 
-		private val delegate = RegistryDelegate<Type<*>>("stack_type") { disableSaving() }
+		private val delegate = RegistryDelegate<Type<*>>("stack_type") {}
 		val REGISTRY by delegate
 		val REGISTRY_KEY by delegate::key
 
-		private val registrar = DeferredRegister.create(REGISTRY_KEY, OverdriveThatMatters.MOD_ID)
+		private val registrar = MDeferredRegister(REGISTRY_KEY, OverdriveThatMatters.MOD_ID)
 
 		val ITEMS: Type<ItemStorageStack> by registrar.register("items") {
 			SimpleType(
@@ -130,7 +130,7 @@ abstract class StorageStack<S : StorageStack<S>>(val count: BigInteger) {
 			)
 		}
 
-		internal fun register(bus: IEventBus) {
+		fun register(bus: IEventBus) {
 			bus.addListener(delegate::build)
 			registrar.register(bus)
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt
index 800d22894..a761cc0bb 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt
@@ -7,6 +7,7 @@ import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.level.ServerPlayer
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
 import ru.dbotthepony.mc.otm.android.AndroidResearchType
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import java.util.*
 import java.util.function.Predicate
 
@@ -18,7 +19,7 @@ object AndroidResearchTrigger : MCriterionTrigger<AndroidResearchTrigger.Instanc
 	override val codec: Codec<Instance> = RecordCodecBuilder.create {
 		it.group(
 			ResourceLocation.CODEC.optionalFieldOf("research").forGetter(Instance::research),
-			playerPredicateCodec.forGetter(Instance::playerPredicate)
+			ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::playerPredicate)
 		).apply(it, ::Instance)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt
index 94d02e89d..c2039f212 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt
@@ -6,6 +6,7 @@ import net.minecraft.advancements.critereon.ContextAwarePredicate
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.level.ServerPlayer
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.data.minRange
 import java.util.*
 
@@ -17,7 +18,7 @@ object AndroidTravelUnderwater : MCriterionTrigger<AndroidTravelUnderwater.Insta
 	override val codec: Codec<Instance> = RecordCodecBuilder.create {
 		it.group(
 			Codec.DOUBLE.minRange(0.0).fieldOf("distanceToTravel").forGetter(Instance::distanceToTravel),
-			playerPredicateCodec.forGetter(Instance::playerPredicate)
+			ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::playerPredicate)
 		).apply(it, ::Instance)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt
index 1f8523669..98228bd13 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt
@@ -6,6 +6,7 @@ import net.minecraft.advancements.critereon.ContextAwarePredicate
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.level.ServerPlayer
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import java.util.*
 
 val ExopackObtainedTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_obtained"))
@@ -20,7 +21,7 @@ object ExopackSlotsExpandedTrigger : MCriterionTrigger<ExopackSlotsExpandedTrigg
 		it.group(
 			Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minGained", 0).forGetter(Instance::minGained),
 			Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minTotal", 0).forGetter(Instance::minTotal),
-			playerPredicateCodec.forGetter(Instance::playerPredicate)
+			ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::playerPredicate)
 		).apply(it, ::Instance)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt
index c5be0e2e5..5f5fbd5fc 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt
@@ -24,9 +24,9 @@ class HurtTrigger(id: ResourceLocation) : MCriterionTrigger<HurtTrigger.Instance
 
 	override val codec: Codec<Instance> = RecordCodecBuilder.create {
 		it.group(
-			predicateCodec.optionalFieldOf("predicate").forGetter(Instance::predicate),
+			ContextAwarePredicate.CODEC.optionalFieldOf("predicate").forGetter(Instance::predicate),
 			DamagePredicateCodec.optionalFieldOf("damagePredicate").forGetter(Instance::damagePredicate),
-			playerPredicateCodec.forGetter(Instance::playerPredicate)
+			ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::playerPredicate)
 		).apply(it, ::Instance)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt
index a7cd9c923..f7c716c17 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt
@@ -11,14 +11,14 @@ import java.util.*
 
 class ItemTrigger(id: ResourceLocation) : MCriterionTrigger<ItemTrigger.Instance>(id) {
 	fun trigger(player: ServerPlayer, item: ItemStack) {
-		trigger(player) { if (it.invert) !it.predicate.matches(item) else it.predicate.matches(item) }
+		trigger(player) { if (it.invert) !it.predicate.test(item) else it.predicate.test(item) }
 	}
 
 	override val codec: Codec<Instance> = RecordCodecBuilder.create {
 		it.group(
 			ItemPredicate.CODEC.fieldOf("predicate").forGetter(Instance::predicate),
 			Codec.BOOL.optionalFieldOf("invert", false).forGetter(Instance::invert),
-			playerPredicateCodec.forGetter(Instance::playerPredicate)
+			ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::playerPredicate)
 		).apply(it, ::Instance)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt
index 432303360..d0b374989 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt
@@ -2,16 +2,18 @@ package ru.dbotthepony.mc.otm.triggers
 
 import com.google.common.collect.ImmutableList
 import com.mojang.serialization.Codec
+import com.mojang.serialization.MapCodec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.advancements.critereon.*
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.level.ServerPlayer
 import net.minecraft.util.StringRepresentable
 import net.minecraft.world.entity.monster.ElderGuardian
-import net.minecraftforge.event.entity.living.LivingDeathEvent
+import net.neoforged.neoforge.event.entity.living.LivingDeathEvent
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
-import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
+import ru.dbotthepony.mc.otm.capability.MatteryPlayer
 import ru.dbotthepony.mc.otm.capability.matteryPlayer
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import ru.dbotthepony.mc.otm.data.SingletonCodec
 import ru.dbotthepony.mc.otm.registry.MRegistry
 import java.util.Optional
@@ -22,33 +24,33 @@ object KillAsAndroidTrigger : MCriterionTrigger<KillAsAndroidTrigger.Instance>(R
 
 	override val codec: Codec<Instance> = RecordCodecBuilder.create {
 		it.group(
-			predicateCodec.optionalFieldOf("entityPredicate").forGetter(Instance::predicate),
+			ContextAwarePredicate.CODEC.optionalFieldOf("entityPredicate").forGetter(Instance::predicate),
 			FEATURE_CODEC.fieldOf("featurePredicate").forGetter(Instance::featurePredicate),
-			playerPredicateCodec.forGetter(Instance::playerPredicate)
+			ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::playerPredicate)
 		).apply(it, ::Instance)
 	}
 
-	enum class PredicateType(codec: Lazy<Codec<out FeaturePredicate>>) : StringRepresentable {
+	enum class PredicateType(codec: Lazy<MapCodec<out FeaturePredicate>>) : StringRepresentable {
 		ALWAYS(lazy {
 			SingletonCodec(Always)
 		}),
 		HAS(lazy {
-			RecordCodecBuilder.create<Has> {
+			RecordCodecBuilder.mapCodec<Has> {
 				it.group(ResourceLocation.CODEC.fieldOf("name").forGetter(Has::name)).apply(it, ::Has)
 			}
 		}),
 		NOT(lazy {
-			RecordCodecBuilder.create<Not> {
+			RecordCodecBuilder.mapCodec<Not> {
 				it.group(FEATURE_CODEC.fieldOf("parent").forGetter(Not::parent)).apply(it, ::Not)
 			}
 		}),
 		AND(lazy {
-			RecordCodecBuilder.create<And> {
+			RecordCodecBuilder.mapCodec<And> {
 				it.group(FEATURE_CODEC.listOf().fieldOf("children").forGetter(And::children)).apply(it, ::And)
 			}
 		}),
 		OR(lazy {
-			RecordCodecBuilder.create<OrPredicate> {
+			RecordCodecBuilder.mapCodec<OrPredicate> {
 				it.group(FEATURE_CODEC.listOf().fieldOf("children").forGetter(OrPredicate::children)).apply(it, ::OrPredicate)
 			}
 		});
@@ -60,7 +62,7 @@ object KillAsAndroidTrigger : MCriterionTrigger<KillAsAndroidTrigger.Instance>(R
 		}
 	}
 
-	abstract class FeaturePredicate : Predicate<MatteryPlayerCapability> {
+	abstract class FeaturePredicate : Predicate<MatteryPlayer> {
 		abstract val type: PredicateType
 	}
 
@@ -68,18 +70,18 @@ object KillAsAndroidTrigger : MCriterionTrigger<KillAsAndroidTrigger.Instance>(R
 		override val type: PredicateType
 			get() = PredicateType.ALWAYS
 
-		override fun test(t: MatteryPlayerCapability): Boolean {
+		override fun test(t: MatteryPlayer): Boolean {
 			return true
 		}
 	}
 
 	class Has(val name: ResourceLocation) : FeaturePredicate() {
-		private val resolved by lazy { MRegistry.ANDROID_FEATURES.getValue(name) }
+		private val resolved by lazy { MRegistry.ANDROID_FEATURES.get(name) }
 
 		override val type: PredicateType
 			get() = PredicateType.HAS
 
-		override fun test(t: MatteryPlayerCapability): Boolean {
+		override fun test(t: MatteryPlayer): Boolean {
 			return t.hasFeature(resolved ?: return false)
 		}
 	}
@@ -88,7 +90,7 @@ object KillAsAndroidTrigger : MCriterionTrigger<KillAsAndroidTrigger.Instance>(R
 		override val type: PredicateType
 			get() = PredicateType.NOT
 
-		override fun test(t: MatteryPlayerCapability): Boolean {
+		override fun test(t: MatteryPlayer): Boolean {
 			return !parent.test(t)
 		}
 	}
@@ -99,7 +101,7 @@ object KillAsAndroidTrigger : MCriterionTrigger<KillAsAndroidTrigger.Instance>(R
 		override val type: PredicateType
 			get() = PredicateType.AND
 
-		override fun test(t: MatteryPlayerCapability): Boolean {
+		override fun test(t: MatteryPlayer): Boolean {
 			return children.all { it.test(t) }
 		}
 	}
@@ -110,7 +112,7 @@ object KillAsAndroidTrigger : MCriterionTrigger<KillAsAndroidTrigger.Instance>(R
 		override val type: PredicateType
 			get() = PredicateType.OR
 
-		override fun test(t: MatteryPlayerCapability): Boolean {
+		override fun test(t: MatteryPlayer): Boolean {
 			return children.any { it.test(t) }
 		}
 	}
@@ -126,7 +128,7 @@ object KillAsAndroidTrigger : MCriterionTrigger<KillAsAndroidTrigger.Instance>(R
 			val killer = event.source.entity
 
 			if (killer is ServerPlayer) {
-				val data = killer.matteryPlayer ?: return
+				val data = killer.matteryPlayer
 
 				if (data.isAndroid) {
 					val context = EntityPredicate.createContext(killer, event.entity)
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt
index d1c9b2370..6aee75154 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt
@@ -1,13 +1,6 @@
 package ru.dbotthepony.mc.otm.triggers
 
-import com.google.gson.JsonObject
-import com.google.gson.JsonParseException
-import com.mojang.datafixers.util.Pair
 import com.mojang.serialization.Codec
-import com.mojang.serialization.DataResult
-import com.mojang.serialization.DynamicOps
-import com.mojang.serialization.JsonOps
-import com.mojang.serialization.MapCodec
 import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
 import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
 import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
@@ -15,20 +8,17 @@ import net.minecraft.advancements.Criterion
 import net.minecraft.advancements.CriterionTrigger
 import net.minecraft.advancements.CriterionTriggerInstance
 import net.minecraft.advancements.critereon.ContextAwarePredicate
-import net.minecraft.advancements.critereon.DeserializationContext
+import net.minecraft.advancements.critereon.CriterionValidator
 import net.minecraft.advancements.critereon.EntityPredicate
 import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.PlayerAdvancements
 import net.minecraft.server.level.ServerPlayer
 import ru.dbotthepony.mc.otm.core.collect.filter
 import ru.dbotthepony.mc.otm.core.collect.toImmutableList
-import ru.dbotthepony.mc.otm.core.set
-import ru.dbotthepony.mc.otm.core.toJsonStrict
-import java.util.Optional
+import java.util.*
 import java.util.function.Predicate
 
-// allows to support both 1.20.1 and 1.20.2 with ease
-// and has slightly less memory footprint than vanilla SimpleCriterionTrigger
+// has slightly less memory footprint than vanilla SimpleCriterionTrigger
 abstract class MCriterionTrigger<T : MCriterionTrigger<T>.AbstractInstance>(val id: ResourceLocation) : CriterionTrigger<T> {
 	private val listeners = Reference2ObjectOpenHashMap<PlayerAdvancements, ObjectOpenHashSet<CriterionTrigger.Listener<T>>>()
 
@@ -46,13 +36,8 @@ abstract class MCriterionTrigger<T : MCriterionTrigger<T>.AbstractInstance>(val
 
 	protected abstract val codec: Codec<T>
 
-	override fun createInstance(data: JsonObject, context: DeserializationContext): T {
-		return try {
-			deserializationContext.get().addLast(context)
-			codec.decode(JsonOps.INSTANCE, data).getOrThrow(false) { throw JsonParseException("Failed to decode data: $it") }.first
-		} finally {
-			deserializationContext.get().removeLast()
-		}
+	final override fun codec(): Codec<T> {
+		return codec
 	}
 
 	fun trigger(player: ServerPlayer, predicate: Predicate<T> = Predicate { true }) {
@@ -70,36 +55,8 @@ abstract class MCriterionTrigger<T : MCriterionTrigger<T>.AbstractInstance>(val
 	abstract inner class AbstractInstance(val playerPredicate: Optional<ContextAwarePredicate>) : CriterionTriggerInstance {
 		fun criterion() = Criterion(this@MCriterionTrigger, this as T)
 
-		final override fun serializeToJson(): JsonObject {
-			return codec.toJsonStrict(this as T) as JsonObject
+		override fun validate(validator: CriterionValidator) {
+			validator.validateEntity(playerPredicate, ".player")
 		}
 	}
-
-	companion object {
-		protected val deserializationContext = object : ThreadLocal<ArrayDeque<DeserializationContext>>() {
-			override fun initialValue(): ArrayDeque<DeserializationContext> {
-				return ArrayDeque()
-			}
-		}
-
-		@JvmStatic
-		protected val predicateCodec: Codec<ContextAwarePredicate> = object : Codec<ContextAwarePredicate> {
-			override fun <T : Any?> encode(input: ContextAwarePredicate, ops: DynamicOps<T>, prefix: T): DataResult<T> {
-				return DataResult.success(JsonOps.INSTANCE.convertTo(ops, input.toJson()))
-			}
-
-			override fun <T : Any?> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<ContextAwarePredicate, T>> {
-				val context = deserializationContext.get().lastOrNull() ?: return DataResult.error { "Not current deserializing trigger instance" }
-
-				return try {
-					DataResult.success(Pair.of(EntityPredicate.fromJson(JsonObject().also { it["a"] = ops.convertTo(JsonOps.INSTANCE, input) }, "a", context).get(), ops.empty()))
-				} catch (err: Exception) {
-					DataResult.error { "Failed to deserialize ContextAwarePredicate: " + err.message }
-				}
-			}
-		}
-
-		@JvmStatic
-		protected val playerPredicateCodec: MapCodec<Optional<ContextAwarePredicate>> = predicateCodec.optionalFieldOf("player")
-	}
 }
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MatteryInventoryChangeTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MatteryInventoryChangeTrigger.kt
index c3064c2fa..cd2fccf4f 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MatteryInventoryChangeTrigger.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MatteryInventoryChangeTrigger.kt
@@ -11,7 +11,6 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
 import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
 import net.minecraft.advancements.CriteriaTriggers
 import net.minecraft.advancements.CriterionTrigger
-import net.minecraft.advancements.critereon.DeserializationContext
 import net.minecraft.advancements.critereon.InventoryChangeTrigger
 import net.minecraft.advancements.critereon.MinMaxBounds
 import net.minecraft.server.PlayerAdvancements
@@ -77,19 +76,19 @@ object MatteryInventoryChangeTrigger : CriterionTrigger<InventoryChangeTrigger.T
 	private val nodes = ArrayList<Node<*>>()
 
 	init {
-		nodes.add(Node(BoundsStrategy, { listOf(slotsOccupied) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsOccupied) }))
-		nodes.add(Node(BoundsStrategy, { listOf(slotsFull) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsFull) }))
-		nodes.add(Node(BoundsStrategy, { listOf(slotsEmpty) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsEmpty) }))
+		nodes.add(Node(BoundsStrategy, { listOf(slots.occupied) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsOccupied) }))
+		nodes.add(Node(BoundsStrategy, { listOf(slots.full) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsFull) }))
+		nodes.add(Node(BoundsStrategy, { listOf(slots.empty) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsEmpty) }))
 
 		nodes.add(Node(
 			DefaultStrategy,
-			{ predicates.iterator().flatMap { (it.items.map { it.map { it.value() } }.orElse(listOf(null))).iterator() }.toList() },
+			{ items.iterator().flatMap { (it.items.map { it.map { it.value() } }.orElse(listOf(null))).iterator() }.toList() },
 			{ v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v == null || item.item == v },
 			{ inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> mutableListOf(item.item) }))
 
 		nodes.add(Node(
 			DefaultStrategy,
-			{ predicates.map { it.tag.orElse(null) } },
+			{ items.map { it.tag.orElse(null) } },
 			{ v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v == null || item.`is`(v) },
 			{ inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> item.tags.collect(Collectors.toCollection(::ArrayList)) }))
 
@@ -97,7 +96,7 @@ object MatteryInventoryChangeTrigger : CriterionTrigger<InventoryChangeTrigger.T
 
 		nodes.add(Node(
 			BoundsStrategy,
-			{ predicates.map { it.durability } },
+			{ items.map { it.durability } },
 			{ v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.isAny || item.isDamageableItem && v.matches(item.maxDamage - item.damageValue) }))
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt
index 9c290969e..812e32188 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt
@@ -4,16 +4,16 @@ import com.mojang.serialization.Codec
 import com.mojang.serialization.codecs.RecordCodecBuilder
 import net.minecraft.advancements.critereon.ContextAwarePredicate
 import net.minecraft.advancements.critereon.MinMaxBounds.Doubles
-import net.minecraft.resources.ResourceLocation
 import net.minecraft.server.level.ServerPlayer
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 import java.util.*
 
 object NanobotsArmorTrigger : MCriterionTrigger<NanobotsArmorTrigger.Instance>(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor")) {
 	override val codec: Codec<Instance> = RecordCodecBuilder.create {
 		it.group(
 			Doubles.CODEC.fieldOf("predicate").forGetter(Instance::predicate),
-			playerPredicateCodec.forGetter(Instance::playerPredicate),
+			ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::playerPredicate),
 		).apply(it, ::Instance)
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt
index 18adae924..e014aeacb 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt
@@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.triggers
 
 import net.minecraft.resources.ResourceLocation
 import ru.dbotthepony.mc.otm.OverdriveThatMatters
+import ru.dbotthepony.mc.otm.core.ResourceLocation
 
 val BlackHoleTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "black_hole_pull"))
 val FallDampenersSaveTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "fall_dampeners_save"))
diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt
index 6ad9afd6d..4b51ffba2 100644
--- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt
+++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt
@@ -9,7 +9,9 @@ import java.util.*
 
 class SingletonTrigger(id: ResourceLocation) : MCriterionTrigger<SingletonTrigger.Instance>(id) {
 	override val codec: Codec<Instance> = RecordCodecBuilder.create {
-		it.group(playerPredicateCodec.forGetter(Instance::playerPredicate)).apply(it, ::Instance)
+		it.group(
+			ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::playerPredicate)
+		).apply(it, ::Instance)
 	}
 
 	val empty = Instance()
diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg
index 69270ee06..4da41e68e 100644
--- a/src/main/resources/META-INF/accesstransformer.cfg
+++ b/src/main/resources/META-INF/accesstransformer.cfg
@@ -1,174 +1,174 @@
 # Make Screen.title be public and easily modifiable
-public-f net.minecraft.client.gui.screens.Screen f_96539_ # title
-public net.minecraft.server.MinecraftServer f_129744_ # storageSource
+public-f net.minecraft.client.gui.screens.Screen title
+public net.minecraft.server.MinecraftServer storageSource
 
-public net.minecraft.world.entity.player.Inventory f_150070_ # SELECTION_SIZE
+public net.minecraft.world.entity.player.Inventory SELECTION_SIZE
 
 # for accessing and setting from MatteryScreen class
 public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169600_
 public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169605_
 public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169602_
 
-public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97732_ # menu
-public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169604_ # playerInventoryTitle
+public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen menu
+public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen playerInventoryTitle
 
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97706_ # clickedSlot
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97707_ # snapbackEnd
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97708_ # quickdropSlot
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97709_ # lastClickSlot
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97710_ # isSplittingStack
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97711_ # draggingItem
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97712_ # snapbackStartX
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97713_ # snapbackStartY
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97714_ # snapbackTime
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97715_ # snapbackItem
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97716_ # quickdropTime
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97717_ # quickCraftingType
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97718_ # quickCraftingButton
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97719_ # skipNextRelease
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97720_ # quickCraftingRemainder
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97721_ # lastClickTime
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97722_ # lastClickButton
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97723_ # doubleclick
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97724_ # lastQuickMoved
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen clickedSlot
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen snapbackEnd
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen quickdropSlot
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen lastClickSlot
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen isSplittingStack
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen draggingItem
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen snapbackStartX
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen snapbackStartY
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen snapbackTime
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen snapbackItem
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen quickdropTime
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen quickCraftingType
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen quickCraftingButton
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen skipNextRelease
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen quickCraftingRemainder
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen lastClickTime
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen lastClickButton
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen doubleclick
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen lastQuickMoved
 
-public net.minecraft.world.item.BlockItem f_150696_ # BLOCK_ENTITY_TAG
+public net.minecraft.world.item.BlockItem BLOCK_ENTITY_TAG
 
-public net.minecraft.client.gui.screens.InBedChatScreen f_263129_ # leaveBedButton
+public net.minecraft.client.gui.screens.InBedChatScreen leaveBedButton
 
-public net.minecraft.world.inventory.AbstractContainerMenu f_182405_ # stateId
+public net.minecraft.world.inventory.AbstractContainerMenu stateId
 
-public net.minecraft.world.inventory.CraftingMenu m_150546_(Lnet/minecraft/world/inventory/AbstractContainerMenu;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/inventory/CraftingContainer;Lnet/minecraft/world/inventory/ResultContainer;)V # slotChangedCraftingGrid
+public net.minecraft.world.inventory.CraftingMenu slotChangedCraftingGrid(Lnet/minecraft/world/inventory/AbstractContainerMenu;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/inventory/CraftingContainer;Lnet/minecraft/world/inventory/ResultContainer;)V
 
-public net.minecraft.server.level.ServerPlayer f_143380_ # containerListener
+public net.minecraft.server.level.ServerPlayer containerListener
 
 # for overriding from MatteryScreen class
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97762_(I)V # checkHotbarMouseClicked
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97818_()V # recalculateQuickCraftRemaining
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97744_(DD)Lnet/minecraft/world/inventory/Slot; # findSlot
-protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_280211_(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/item/ItemStack;IILjava/lang/String;)V # renderFloatingItem
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen checkHotbarMouseClicked(I)V
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen recalculateQuickCraftRemaining()V
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen findSlot(DD)Lnet/minecraft/world/inventory/Slot;
+protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen renderFloatingItem(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/item/ItemStack;IILjava/lang/String;)V
 
-protected net.minecraft.client.resources.TextureAtlasHolder f_118884_ # textureAtlas
+protected net.minecraft.client.resources.TextureAtlasHolder textureAtlas
 
-public net.minecraft.client.renderer.RenderStateShard f_110133_ # name
+public net.minecraft.client.renderer.RenderStateShard name
 
-public-f net.minecraft.world.inventory.Slot f_40220_ # x
-public-f net.minecraft.world.inventory.Slot f_40221_ # y
+public-f net.minecraft.world.inventory.Slot x
+public-f net.minecraft.world.inventory.Slot y
 
-public net.minecraft.client.renderer.LevelRenderer m_109782_(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/phys/shapes/VoxelShape;DDDFFFF)V # renderShape
+public net.minecraft.client.renderer.LevelRenderer renderShape(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/phys/shapes/VoxelShape;DDDFFFF)V
 
-public net.minecraft.client.renderer.RenderStateShard f_110135_ # ADDITIVE_TRANSPARENCY
-public net.minecraft.client.renderer.RenderStateShard f_173097_ # BLOCK_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_110146_ # BLOCK_SHEET
-public net.minecraft.client.renderer.RenderStateShard f_110145_ # BLOCK_SHEET_MIPPED
-public net.minecraft.client.renderer.RenderStateShard f_110128_ # CLOUDS_TARGET
-public net.minecraft.client.renderer.RenderStateShard f_110114_ # COLOR_DEPTH_WRITE
-public net.minecraft.client.renderer.RenderStateShard f_110115_ # COLOR_WRITE
-public net.minecraft.client.renderer.RenderStateShard f_110138_ # CRUMBLING_TRANSPARENCY
-public net.minecraft.client.renderer.RenderStateShard f_110158_ # CULL
-public net.minecraft.client.renderer.RenderStateShard f_110130_ # DEFAULT_LINE
-public net.minecraft.client.renderer.RenderStateShard f_110148_ # DEFAULT_TEXTURING
-public net.minecraft.client.renderer.RenderStateShard f_110116_ # DEPTH_WRITE
-public net.minecraft.client.renderer.RenderStateShard f_110151_ # ENTITY_GLINT_TEXTURING
-public net.minecraft.client.renderer.RenderStateShard f_110112_ # EQUAL_DEPTH_TEST
-public net.minecraft.client.renderer.RenderStateShard f_110150_ # GLINT_TEXTURING
-public net.minecraft.client.renderer.RenderStateShard f_110137_ # GLINT_TRANSPARENCY
-public net.minecraft.client.renderer.RenderStateShard f_110129_ # ITEM_ENTITY_TARGET
-public net.minecraft.client.renderer.RenderStateShard f_110113_ # LEQUAL_DEPTH_TEST
-public net.minecraft.client.renderer.RenderStateShard f_110152_ # LIGHTMAP
-public net.minecraft.client.renderer.RenderStateShard f_110136_ # LIGHTNING_TRANSPARENCY
-public net.minecraft.client.renderer.RenderStateShard f_110123_ # MAIN_TARGET
-public net.minecraft.client.renderer.RenderStateShard f_173098_ # NEW_ENTITY_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_110110_ # NO_CULL
-public net.minecraft.client.renderer.RenderStateShard f_110111_ # NO_DEPTH_TEST
-public net.minecraft.client.renderer.RenderStateShard f_110117_ # NO_LAYERING
-public net.minecraft.client.renderer.RenderStateShard f_110153_ # NO_LIGHTMAP
-public net.minecraft.client.renderer.RenderStateShard f_110155_ # NO_OVERLAY
-public net.minecraft.client.renderer.RenderStateShard f_173096_ # NO_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_110147_ # NO_TEXTURE
-public net.minecraft.client.renderer.RenderStateShard f_110134_ # NO_TRANSPARENCY
-public net.minecraft.client.renderer.RenderStateShard f_110124_ # OUTLINE_TARGET
-public net.minecraft.client.renderer.RenderStateShard f_110154_ # OVERLAY
-public net.minecraft.client.renderer.RenderStateShard f_110126_ # PARTICLES_TARGET
-public net.minecraft.client.renderer.RenderStateShard f_110118_ # POLYGON_OFFSET_LAYERING
-public net.minecraft.client.renderer.RenderStateShard f_173099_ # POSITION_COLOR_LIGHTMAP_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173104_ # POSITION_COLOR_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173103_ # POSITION_COLOR_TEX_LIGHTMAP_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173101_ # POSITION_COLOR_TEX_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173100_ # POSITION_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173102_ # POSITION_TEX_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173111_ # RENDERTYPE_ARMOR_CUTOUT_NO_CULL_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173079_ # RENDERTYPE_ARMOR_ENTITY_GLINT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173078_ # RENDERTYPE_ARMOR_GLINT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173068_ # RENDERTYPE_BEACON_BEAM_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173085_ # RENDERTYPE_CRUMBLING_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173106_ # RENDERTYPE_CUTOUT_MIPPED_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173107_ # RENDERTYPE_CUTOUT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173094_ # RENDERTYPE_END_GATEWAY_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173093_ # RENDERTYPE_END_PORTAL_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173074_ # RENDERTYPE_ENERGY_SWIRL_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173072_ # RENDERTYPE_ENTITY_ALPHA_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173114_ # RENDERTYPE_ENTITY_CUTOUT_NO_CULL_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173063_ # RENDERTYPE_ENTITY_CUTOUT_NO_CULL_Z_OFFSET_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173113_ # RENDERTYPE_ENTITY_CUTOUT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173069_ # RENDERTYPE_ENTITY_DECAL_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173084_ # RENDERTYPE_ENTITY_GLINT_DIRECT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173083_ # RENDERTYPE_ENTITY_GLINT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173070_ # RENDERTYPE_ENTITY_NO_OUTLINE_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173071_ # RENDERTYPE_ENTITY_SHADOW_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173067_ # RENDERTYPE_ENTITY_SMOOTH_CUTOUT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173112_ # RENDERTYPE_ENTITY_SOLID_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173065_ # RENDERTYPE_ENTITY_TRANSLUCENT_CULL_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_234323_ # RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173066_ # RENDERTYPE_ENTITY_TRANSLUCENT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173073_ # RENDERTYPE_EYES_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173082_ # RENDERTYPE_GLINT_DIRECT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173081_ # RENDERTYPE_GLINT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173080_ # RENDERTYPE_GLINT_TRANSLUCENT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173064_ # RENDERTYPE_ITEM_ENTITY_TRANSLUCENT_CULL_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173075_ # RENDERTYPE_LEASH_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173091_ # RENDERTYPE_LIGHTNING_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173095_ # RENDERTYPE_LINES_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173077_ # RENDERTYPE_OUTLINE_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173105_ # RENDERTYPE_SOLID_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173090_ # RENDERTYPE_TEXT_INTENSITY_SEE_THROUGH_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173087_ # RENDERTYPE_TEXT_INTENSITY_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173088_ # RENDERTYPE_TEXT_SEE_THROUGH_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173086_ # RENDERTYPE_TEXT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173109_ # RENDERTYPE_TRANSLUCENT_MOVING_BLOCK_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173110_ # RENDERTYPE_TRANSLUCENT_NO_CRUMBLING_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173108_ # RENDERTYPE_TRANSLUCENT_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173092_ # RENDERTYPE_TRIPWIRE_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_173076_ # RENDERTYPE_WATER_MASK_SHADER
-public net.minecraft.client.renderer.RenderStateShard f_110125_ # TRANSLUCENT_TARGET
-public net.minecraft.client.renderer.RenderStateShard f_110139_ # TRANSLUCENT_TRANSPARENCY
-public net.minecraft.client.renderer.RenderStateShard f_110119_ # VIEW_OFFSET_Z_LAYERING
-public net.minecraft.client.renderer.RenderStateShard f_173089_ # VIEW_SCALE_Z_EPSILON
-public net.minecraft.client.renderer.RenderStateShard f_110127_ # WEATHER_TARGET
+public net.minecraft.client.renderer.RenderStateShard ADDITIVE_TRANSPARENCY
+public net.minecraft.client.renderer.RenderStateShard BLOCK_SHADER
+public net.minecraft.client.renderer.RenderStateShard BLOCK_SHEET
+public net.minecraft.client.renderer.RenderStateShard BLOCK_SHEET_MIPPED
+public net.minecraft.client.renderer.RenderStateShard CLOUDS_TARGET
+public net.minecraft.client.renderer.RenderStateShard COLOR_DEPTH_WRITE
+public net.minecraft.client.renderer.RenderStateShard COLOR_WRITE
+public net.minecraft.client.renderer.RenderStateShard CRUMBLING_TRANSPARENCY
+public net.minecraft.client.renderer.RenderStateShard CULL
+public net.minecraft.client.renderer.RenderStateShard DEFAULT_LINE
+public net.minecraft.client.renderer.RenderStateShard DEFAULT_TEXTURING
+public net.minecraft.client.renderer.RenderStateShard DEPTH_WRITE
+public net.minecraft.client.renderer.RenderStateShard ENTITY_GLINT_TEXTURING
+public net.minecraft.client.renderer.RenderStateShard EQUAL_DEPTH_TEST
+public net.minecraft.client.renderer.RenderStateShard GLINT_TEXTURING
+public net.minecraft.client.renderer.RenderStateShard GLINT_TRANSPARENCY
+public net.minecraft.client.renderer.RenderStateShard ITEM_ENTITY_TARGET
+public net.minecraft.client.renderer.RenderStateShard LEQUAL_DEPTH_TEST
+public net.minecraft.client.renderer.RenderStateShard LIGHTMAP
+public net.minecraft.client.renderer.RenderStateShard LIGHTNING_TRANSPARENCY
+public net.minecraft.client.renderer.RenderStateShard MAIN_TARGET
+public net.minecraft.client.renderer.RenderStateShard NEW_ENTITY_SHADER
+public net.minecraft.client.renderer.RenderStateShard NO_CULL
+public net.minecraft.client.renderer.RenderStateShard NO_DEPTH_TEST
+public net.minecraft.client.renderer.RenderStateShard NO_LAYERING
+public net.minecraft.client.renderer.RenderStateShard NO_LIGHTMAP
+public net.minecraft.client.renderer.RenderStateShard NO_OVERLAY
+public net.minecraft.client.renderer.RenderStateShard NO_SHADER
+public net.minecraft.client.renderer.RenderStateShard NO_TEXTURE
+public net.minecraft.client.renderer.RenderStateShard NO_TRANSPARENCY
+public net.minecraft.client.renderer.RenderStateShard OUTLINE_TARGET
+public net.minecraft.client.renderer.RenderStateShard OVERLAY
+public net.minecraft.client.renderer.RenderStateShard PARTICLES_TARGET
+public net.minecraft.client.renderer.RenderStateShard POLYGON_OFFSET_LAYERING
+public net.minecraft.client.renderer.RenderStateShard POSITION_COLOR_LIGHTMAP_SHADER
+public net.minecraft.client.renderer.RenderStateShard POSITION_COLOR_SHADER
+public net.minecraft.client.renderer.RenderStateShard POSITION_COLOR_TEX_LIGHTMAP_SHADER
+public net.minecraft.client.renderer.RenderStateShard POSITION_COLOR_TEX_SHADER
+public net.minecraft.client.renderer.RenderStateShard POSITION_SHADER
+public net.minecraft.client.renderer.RenderStateShard POSITION_TEX_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ARMOR_CUTOUT_NO_CULL_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ARMOR_ENTITY_GLINT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ARMOR_GLINT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_BEACON_BEAM_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_CRUMBLING_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_CUTOUT_MIPPED_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_CUTOUT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_END_GATEWAY_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_END_PORTAL_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENERGY_SWIRL_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_ALPHA_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_CUTOUT_NO_CULL_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_CUTOUT_NO_CULL_Z_OFFSET_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_CUTOUT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_DECAL_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_GLINT_DIRECT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_GLINT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_NO_OUTLINE_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_SHADOW_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_SMOOTH_CUTOUT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_SOLID_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_TRANSLUCENT_CULL_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_TRANSLUCENT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_EYES_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_GLINT_DIRECT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_GLINT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_GLINT_TRANSLUCENT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ITEM_ENTITY_TRANSLUCENT_CULL_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_LEASH_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_LIGHTNING_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_LINES_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_OUTLINE_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_SOLID_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_TEXT_INTENSITY_SEE_THROUGH_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_TEXT_INTENSITY_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_TEXT_SEE_THROUGH_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_TEXT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_TRANSLUCENT_MOVING_BLOCK_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_TRANSLUCENT_NO_CRUMBLING_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_TRANSLUCENT_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_TRIPWIRE_SHADER
+public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_WATER_MASK_SHADER
+public net.minecraft.client.renderer.RenderStateShard TRANSLUCENT_TARGET
+public net.minecraft.client.renderer.RenderStateShard TRANSLUCENT_TRANSPARENCY
+public net.minecraft.client.renderer.RenderStateShard VIEW_OFFSET_Z_LAYERING
+public net.minecraft.client.renderer.RenderStateShard VIEW_SCALE_Z_EPSILON
+public net.minecraft.client.renderer.RenderStateShard WEATHER_TARGET
 
 public net.minecraft.client.renderer.RenderStateShard$LineStateShard
 
-public net.minecraft.world.inventory.AbstractContainerMenu m_38897_(Lnet/minecraft/world/inventory/Slot;)Lnet/minecraft/world/inventory/Slot; # addSlot
+public net.minecraft.world.inventory.AbstractContainerMenu addSlot(Lnet/minecraft/world/inventory/Slot;)Lnet/minecraft/world/inventory/Slot;
 
-public net.minecraft.client.renderer.FogRenderer f_109012_ # fogBlue
-public net.minecraft.client.renderer.FogRenderer f_109011_ # fogGreen
-public net.minecraft.client.renderer.FogRenderer f_109010_ # fogRed
+public net.minecraft.client.renderer.FogRenderer fogBlue
+public net.minecraft.client.renderer.FogRenderer fogGreen
+public net.minecraft.client.renderer.FogRenderer fogRed
 
-public net.minecraft.world.item.crafting.RecipeManager m_44054_(Lnet/minecraft/world/item/crafting/RecipeType;)Ljava/util/Map; # byType
+public net.minecraft.world.item.crafting.RecipeManager byType(Lnet/minecraft/world/item/crafting/RecipeType;)Ljava/util/Collection;
 
-public net.minecraft.world.item.crafting.SmithingTransformRecipe f_265888_ # base
-public net.minecraft.world.item.crafting.SmithingTransformRecipe f_265907_ # addition
+public net.minecraft.world.item.crafting.SmithingTransformRecipe base
+public net.minecraft.world.item.crafting.SmithingTransformRecipe addition
 
-public net.minecraft.world.entity.boss.wither.WitherBoss f_31432_ # TARGETING_CONDITIONS
-public-f net.minecraft.world.entity.boss.wither.WitherBoss f_31431_ # LIVING_ENTITY_SELECTOR
-public net.minecraft.world.entity.ai.targeting.TargetingConditions f_26879_ # selector
+public net.minecraft.world.entity.boss.wither.WitherBoss TARGETING_CONDITIONS
+public-f net.minecraft.world.entity.boss.wither.WitherBoss LIVING_ENTITY_SELECTOR
+public net.minecraft.world.entity.ai.targeting.TargetingConditions selector
 
-public net.minecraft.world.entity.ExperienceOrb f_147072_ # count
+public net.minecraft.world.entity.ExperienceOrb count # count
 
 public net.minecraft.advancements.critereon.InventoryChangeTrigger$TriggerInstance f_43179_
 public net.minecraft.advancements.critereon.InventoryChangeTrigger$TriggerInstance f_43178_
 public net.minecraft.advancements.critereon.InventoryChangeTrigger$TriggerInstance f_43177_
 public net.minecraft.advancements.critereon.InventoryChangeTrigger$TriggerInstance f_43176_
-#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger m_6467_(Lnet/minecraft/server/PlayerAdvancements;Lnet/minecraft/advancements/CriterionTrigger$Listener;)V # addPlayerListener
-#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger m_6468_(Lnet/minecraft/server/PlayerAdvancements;Lnet/minecraft/advancements/CriterionTrigger$Listener;)V # removePlayerListener
-#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger m_5656_(Lnet/minecraft/server/PlayerAdvancements;)V # removePlayerListeners
+#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger addPlayerListener(Lnet/minecraft/server/PlayerAdvancements;Lnet/minecraft/advancements/CriterionTrigger$Listener;)V
+#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger removePlayerListener(Lnet/minecraft/server/PlayerAdvancements;Lnet/minecraft/advancements/CriterionTrigger$Listener;)V
+#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger removePlayerListeners(Lnet/minecraft/server/PlayerAdvancements;)V
diff --git a/src/main/resources/overdrive_that_matters.ad_astra.mixins.json b/src/main/resources/overdrive_that_matters.ad_astra.mixins.json
deleted file mode 100644
index 2ee1936df..000000000
--- a/src/main/resources/overdrive_that_matters.ad_astra.mixins.json
+++ /dev/null
@@ -1,13 +0,0 @@
-
-{
-  "required": false,
-  "package": "ru.dbotthepony.mc.otm.mixin.compat.ad_astra",
-  "compatibilityLevel": "JAVA_17",
-  "minVersion": "0.8",
-  "refmap": "overdrive_that_matters.refmap.json",
-  "mixins": [
-    "EntityOxygenSystemMixin",
-    "EntityTemperatureSystemMixin",
-    "OxygenUtilsMixin"
-  ]
-}
diff --git a/src/main/resources/overdrive_that_matters.mixins.json b/src/main/resources/overdrive_that_matters.mixins.json
index 95ba2b017..4e22d4fd8 100644
--- a/src/main/resources/overdrive_that_matters.mixins.json
+++ b/src/main/resources/overdrive_that_matters.mixins.json
@@ -9,7 +9,6 @@
     "BlockEntityMixin",
     "EnchantmentHelperMixin",
     "FoodDataMixin",
-    "MixinPatchProjectileFinder",
     "MixinLivingEntity",
     "MixinAnvilBlock",
     "MixinInventory",