diff --git a/.github/workflows/build-gradle.yml b/.github/workflows/build-gradle.yml
index d55c9a4..e02e70d 100644
--- a/.github/workflows/build-gradle.yml
+++ b/.github/workflows/build-gradle.yml
@@ -5,35 +5,20 @@ on: [ push, pull_request ]
 jobs:
   build:
     runs-on: ubuntu-latest
-
     steps:
-      - name: Checkout latest commit
-        uses: actions/checkout@v3
+      - uses: actions/checkout@v2
 
       - name: Set up JDK 17
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v2
         with:
-          distribution: temurin
+          distribution: 'temurin'
           java-version: 17
 
-      - name: Restore Gradle cache
-        uses: actions/cache@v3
-        with:
-          path: |
-            .gradle
-            ~/.gradle/caches
-            ~/.gradle/wrapper
-          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
-          restore-keys: ${{ runner.os }}-gradle
-
-      - name: Validate Gradle wrapper
-        uses: gradle/wrapper-validation-action@v1
-
       - name: Build with Gradle
         run: ./gradlew build
 
       - name: Upload build artifacts
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v2
         with:
           name: build-artifacts
           path: ./build/libs
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index b1ac613..07b4fe3 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -8,45 +8,20 @@ on:
 jobs:
   build:
     runs-on: ubuntu-latest
-
     steps:
-      - name: Checkout latest commit
-        uses: actions/checkout@v3
+      - uses: actions/checkout@v2
 
       - name: Set up JDK 17
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v2
         with:
-          distribution: temurin
+          distribution: 'temurin'
           java-version: 17
 
-      - name: Restore Gradle cache
-        uses: actions/cache@v3
+      - name: Build with Gradle
+        run: ./gradlew build
+
+      - name: Upload build artifacts
+        uses: AButler/upload-release-assets@v2.0
         with:
-          path: |
-            .gradle
-            ~/.gradle/caches
-            ~/.gradle/wrapper
-          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
-          restore-keys: ${{ runner.os }}-gradle
-
-      - name: Validate Gradle wrapper
-        uses: gradle/wrapper-validation-action@v1
-
-      - name: Build and publish with Gradle
-        run: ./gradlew build publish
-        env:
-          MAVEN_URL: ${{ secrets.MAVEN_URL }}
-          MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
-          MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
-
-      - name: Upload artifacts to Modrinth and GitHub
-        uses: Kir-Antipov/mc-publish@v3
-        with:
-          modrinth-id: 5AgJnN8I
-          modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
-
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-
-          files: build/libs/!(*-sources).jar
-
-          loaders: fabric quilt
+          files: 'build/libs/!(*-@(dev|sources)).jar'
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index 456c93c..4f5744f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Cauldron Dyeing
 ![GitHub license](https://img.shields.io/github/license/tibinonest/cauldron-dyeing)
-![GitHub actions](https://img.shields.io/github/actions/workflow/status/tibinonest/cauldron-dyeing/build-gradle.yml?branch=1.19.3)
+![GitHub actions](https://img.shields.io/github/workflow/status/tibinonest/cauldron-dyeing/gradle-ci)
 ![GitHub release](https://img.shields.io/github/v/release/tibinonest/cauldron-dyeing?display_name=tag&include_prereleases&sort=semver)
 
 Bring Bedrock's cauldron-based armor dyeing to Java Edition.
@@ -12,20 +12,19 @@ The usage is the same as in Bedrock, right-clicking on a water cauldron with dye
 When you right-click on the dyed water cauldron with a dyeable item, it will add the current color in the cauldron to the item.
 Right-clicking on an un-dyed water cauldron with a dyed item to remove its color will still work.
 
-![dyed water](https://cdn.tibinonest.me/images/dyed-water.jpg)
+![dyed water](https://imgur.com/Wn2Q27Y.png)
 
 ## Usage
-Cauldron Dyeing requires Fabric or Quilt loader and [FAPI](https://modrinth.com/mod/fabric-api) or [QFAPI](https://modrinth.com/mod/qsl).
-The latest release is available on the [Modrinth](https://modrinth.com/mod/cauldron-dyeing) or [Github releases](https://github.com/TibiNonEst/cauldron-dyeing/releases).
-To install, just drag the mod jar into your mods folder along with either FAPI or QFAPI.
+Fabric Loader version 12.x or newer is required to install this mod. The latest release is available on the [Modrinth](https://modrinth.com/mod/cauldron-dyeing) or [Github releases](https://github.com/TibiNonEst/cauldron-dyeing/releases). To install, just drag the mod jar into your mods folder.
 
 ### Building
 Building the mod requires JDK 17+ on your system. To build, simply run `./gradlew build` and the artifacts will be available in `build/libs`.
 
 ## Compatibility
-Cauldron Dyeing is ensured to be compatible with (Quilted) Fabric API, CaffeineMC mods, and Iris, if any incompatibility is found please report it on the [issue tracker](https://github.com/TibiNonEst/cauldron-dyeing/issues).
 
-**Note:** The current method of displaying dyed water is not compatible with shaders that customize water color such as Complementary's Realistic-ish and RTX-ish settings.
+Cauldron Dyeing is ensured to be compatible with Fabric API, CaffineMC mods, and Iris, if any incompatibility is found please report it on the [issue tracker](https://github.com/TibiNonEst/cauldron-dyeing/issues).
+
+**Note:** The current method of displaying dyed water is not compatible with shaders that customize water color, such as Complementary.
 
 ## Issues
 Report any bugs, crashes, or other issues that may arise on the [issue tracker](https://github.com/TibiNonEst/cauldron-dyeing/issues).
diff --git a/build.gradle b/build.gradle
index bf22eb2..07a45ab 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,93 +1,85 @@
 plugins {
-    alias(libs.plugins.fabric.loom)
-    alias(libs.plugins.grgit)
-    id "maven-publish"
+    id 'fabric-loom' version '0.10.+'
+    id 'io.github.juuxel.loom-quiltflower-mini' version '1.2.1'
+    id 'org.ajoberstar.grgit' version '4.1.0'
 }
 
+sourceCompatibility = '17'
+targetCompatibility = '17'
+
 archivesBaseName = project.archives_base_name
-version = "${project.mod_version}${getMetadata()}"
+version = "${project.mod_version}${getVersionMetadata()}"
 group = project.maven_group
 
 repositories {
     maven {
-        name = "Quilt"
-        url = "https://maven.quiltmc.org/repository/release"
+        name = "Modrinth"
+        url = "https://api.modrinth.com/maven"
+        content {
+            includeGroup "maven.modrinth"
+        }
     }
 }
 
+var apiModules = [
+        "fabric-api-base",
+        "fabric-tool-attribute-api-v1",
+        "fabric-mining-level-api-v1",
+        "fabric-resource-loader-v0",
+        "fabric-tag-extensions-v0",
+        "fabric-object-builder-api-v1"
+]
+
 dependencies {
-    minecraft libs.minecraft
-    mappings variantOf(libs.quilt.mappings) { classifier "intermediary-v2" }
-    modImplementation libs.fabric.loader
+    // To change the versions see the gradle.properties file
+    minecraft "com.mojang:minecraft:${project.minecraft_version}"
+    mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
+    modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
 
-    modImplementation libs.fabric.api
-}
+    // Fabric API
+    apiModules.forEach {
+        include modImplementation(fabricApi.module(it, project.fabric_version))
+    }
 
-loom {
-    accessWidenerPath.set file("src/main/resources/cauldron-dyeing.accesswidener")
+    // Sodium
+    modCompileOnly "maven.modrinth:sodium:${project.sodium_version}"
 }
 
 processResources {
-    inputs.property "version", project.version
-    filteringCharset "UTF-8"
+    inputs.property 'version', project.version
+    filteringCharset 'UTF-8'
 
-    filesMatching("fabric.mod.json") {
-        expand "version": project.version
+    filesMatching('fabric.mod.json') {
+        expand 'version': project.version
     }
 }
 
 tasks.withType(JavaCompile) {
-    it.options.encoding = "UTF-8"
-    // Minecraft 1.18 (1.18-pre2) upwards uses Java 17.
-    it.options.release = 17
+    it.options.encoding = 'UTF-8'
 }
 
 java {
-    // Still required by IDEs such as Eclipse and Visual Studio Code
-    sourceCompatibility = JavaVersion.VERSION_17
-    targetCompatibility = JavaVersion.VERSION_17
-
     withSourcesJar()
 }
 
 jar {
-    from("LICENSE") {
+    from('LICENSE') {
         rename { "${it}_${project.archivesBaseName}"}
     }
 }
 
-publishing {
-    publications {
-        mavenJava(MavenPublication) {
-            from components.java
-        }
-    }
-
-    repositories {
-        maven {
-            url = System.getenv("MAVEN_URL")
-            credentials {
-                username = System.getenv("MAVEN_USERNAME")
-                password = System.getenv("MAVEN_PASSWORD")
-            }
-        }
-    }
-}
-
-def getMetadata() {
+def getVersionMetadata() {
     def build_id = System.getenv("GITHUB_RUN_NUMBER")
     def workflow_id = System.getenv("GITHUB_WORKFLOW")
 
-    def metadata = ""
-
-    // Release builds only
+    // CI builds only
     if (workflow_id == "build-release") {
-        return metadata
+        return "+${project.minecraft_version}"
+    } else if (build_id != null) {
+        return "+build.${build_id}"
     }
 
-    if (build_id != null) {
-        metadata += "+build.${build_id}"
-    } else if (grgit != null) {
+    if (grgit != null) {
         def head = grgit.head()
         def id = head.abbreviatedId
 
@@ -96,8 +88,6 @@ def getMetadata() {
             id += "-dirty"
         }
 
-        metadata += "+git.${id}"
+        return "+rev.${id}"
     }
-
-    return metadata
 }
diff --git a/gradle.properties b/gradle.properties
index aa96e3d..465959e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,10 +1,17 @@
-# Gradle Properties
-org.gradle.jvmargs=-Xmx2G
-org.gradle.parallel=true
+# Done to increase the memory available to gradle.
+org.gradle.jvmargs=-Xmx1G
+
+# Fabric Properties
+# check these on https://modmuss50.me/fabric.html
+minecraft_version=1.18.1
+yarn_mappings=1.18.1+build.18
+loader_version=0.12.12
+fabric_version=0.46.0+1.18
 
 # Mod Properties
-mod_version=1.0.12
+mod_version=1.0.0
 maven_group=me.tibinonest.mods
-archives_base_name=cauldron-dyeing
+archives_base_name=cauldron-dying
 
-# Dependencies are managed at gradle/libs.versions.toml
+# Dependencies
+sodium_version=mc1.18.1-0.4.0-alpha6
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
deleted file mode 100644
index 455bab0..0000000
--- a/gradle/libs.versions.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[versions]
-minecraft = "1.20.3"
-quilt_mappings = "1.20.3+build.3"
-fabric_loader = "0.15.3"
-
-fabric_api = "0.91.1+1.20.3"
-
-fabric_loom = "1.4.+"
-grgit = "5.2.+"
-
-[libraries]
-minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" }
-quilt_mappings = { module = "org.quiltmc:quilt-mappings", version.ref = "quilt_mappings" }
-fabric_loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric_loader" }
-
-fabric_api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric_api" }
-
-[plugins]
-fabric_loom = { id = "fabric-loom", version.ref = "fabric_loom" }
-grgit = { id = "org.ajoberstar.grgit", version.ref = "grgit" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index d64cd49..7454180 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 1af9e09..2e6e589 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
-networkTimeout=10000
-validateDistributionUrl=true
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 1aa94a4..1b6c787 100755
--- a/gradlew
+++ b/gradlew
@@ -55,7 +55,7 @@
 #       Darwin, MinGW, and NonStop.
 #
 #   (3) This script is generated from the Groovy template
-#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 #       within the Gradle project.
 #
 #       You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,11 +80,13 @@ do
     esac
 done
 
-# This is normally unused
-# shellcheck disable=SC2034
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
 APP_BASE_NAME=${0##*/}
-# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD=maximum
@@ -131,29 +133,22 @@ location of your Java installation."
     fi
 else
     JAVACMD=java
-    if ! command -v java >/dev/null 2>&1
-    then
-        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 
 Please set the JAVA_HOME variable in your environment to match the
 location of your Java installation."
-    fi
 fi
 
 # Increase the maximum file descriptors if we can.
 if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
     case $MAX_FD in #(
       max*)
-        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC2039,SC3045
         MAX_FD=$( ulimit -H -n ) ||
             warn "Could not query maximum file descriptor limit"
     esac
     case $MAX_FD in  #(
       '' | soft) :;; #(
       *)
-        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC2039,SC3045
         ulimit -n "$MAX_FD" ||
             warn "Could not set maximum file descriptor limit to $MAX_FD"
     esac
@@ -198,15 +193,11 @@ if "$cygwin" || "$msys" ; then
     done
 fi
 
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Collect all arguments for the java command:
-#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
-#     and any embedded shellness will be escaped.
-#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
-#     treated as '${Hostname}' itself on the command line.
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
 
 set -- \
         "-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -214,12 +205,6 @@ set -- \
         org.gradle.wrapper.GradleWrapperMain \
         "$@"
 
-# Stop when "xargs" is not available.
-if ! command -v xargs >/dev/null 2>&1
-then
-    die "xargs is not available"
-fi
-
 # Use "xargs" to parse quoted args.
 #
 # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/gradlew.bat b/gradlew.bat
index 93e3f59..107acd3 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
 @rem limitations under the License.
 @rem
 
-@if "%DEBUG%"=="" @echo off
+@if "%DEBUG%" == "" @echo off
 @rem ##########################################################################
 @rem
 @rem  Gradle startup script for Windows
@@ -25,8 +25,7 @@
 if "%OS%"=="Windows_NT" setlocal
 
 set DIRNAME=%~dp0
-if "%DIRNAME%"=="" set DIRNAME=.
-@rem This is normally unused
+if "%DIRNAME%" == "" set DIRNAME=.
 set APP_BASE_NAME=%~n0
 set APP_HOME=%DIRNAME%
 
@@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
 
 set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
-if %ERRORLEVEL% equ 0 goto execute
+if "%ERRORLEVEL%" == "0" goto execute
 
 echo.
 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 
 :end
 @rem End local scope for the variables with windows NT shell
-if %ERRORLEVEL% equ 0 goto mainEnd
+if "%ERRORLEVEL%"=="0" goto mainEnd
 
 :fail
 rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
 rem the _cmd.exe /c_ return code!
-set EXIT_CODE=%ERRORLEVEL%
-if %EXIT_CODE% equ 0 set EXIT_CODE=1
-if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
-exit /b %EXIT_CODE%
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
 
 :mainEnd
 if "%OS%"=="Windows_NT" endlocal
diff --git a/settings.gradle b/settings.gradle
index 6908847..d6e33e2 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,13 +1,13 @@
 pluginManagement {
     repositories {
-        maven {
-            name = 'Quilt'
-            url = 'https://maven.quiltmc.org/repository/release'
-        }
         maven {
             name = 'Fabric'
             url = 'https://maven.fabricmc.net/'
         }
+        maven {
+            name = 'Cotton'
+            url = 'https://server.bbkr.space/artifactory/libs-release/'
+        }
         gradlePluginPortal()
     }
 }
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/CauldronDyeing.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/CauldronDyeing.java
index a466188..a157b4c 100644
--- a/src/main/java/me/tibinonest/mods/cauldron_dyeing/CauldronDyeing.java
+++ b/src/main/java/me/tibinonest/mods/cauldron_dyeing/CauldronDyeing.java
@@ -8,31 +8,18 @@ import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityT
 import net.minecraft.block.Blocks;
 import net.minecraft.block.entity.BlockEntityType;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.color.world.BiomeColors;
-import net.minecraft.registry.Registries;
-import net.minecraft.registry.Registry;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.BlockRenderView;
+import net.minecraft.util.registry.Registry;
 
 public class CauldronDyeing implements ModInitializer {
     public static final String MOD_ID = "cauldron_dyeing";
 
-    public static final BlockEntityType<WaterCauldronBlockEntity> WATER_CAULDRON_BLOCK_ENTITY = FabricBlockEntityTypeBuilder.create(WaterCauldronBlockEntity::new, Blocks.WATER_CAULDRON).build();
+    public static BlockEntityType<WaterCauldronBlockEntity> WATER_CAULDRON_BLOCK_ENTITY = FabricBlockEntityTypeBuilder.create(WaterCauldronBlockEntity::new, Blocks.WATER_CAULDRON).build();
 
     @Override
     public void onInitialize() {
-        Registry.register(Registries.BLOCK_ENTITY_TYPE, new Identifier(MOD_ID, "water_cauldron_entity"), WATER_CAULDRON_BLOCK_ENTITY);
-    }
-
-    @Environment(EnvType.CLIENT)
-    public static int getColor(BlockRenderView world, BlockPos pos) {
-        var blockEntity = world.getBlockEntity(pos);
-        if (blockEntity instanceof WaterCauldronBlockEntity waterCauldron && waterCauldron.getColor() != -1) {
-            return waterCauldron.getColor();
-        }
-
-        return BiomeColors.getWaterColor(world, pos);
+        Registry.register(Registry.BLOCK_ENTITY_TYPE, new Identifier(MOD_ID, "water_cauldron_entity"), WATER_CAULDRON_BLOCK_ENTITY);
     }
 
     @Environment(EnvType.CLIENT)
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/block/WaterCauldronBlock.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/block/WaterCauldronBlock.java
new file mode 100644
index 0000000..9ca627d
--- /dev/null
+++ b/src/main/java/me/tibinonest/mods/cauldron_dyeing/block/WaterCauldronBlock.java
@@ -0,0 +1,24 @@
+package me.tibinonest.mods.cauldron_dyeing.block;
+
+import net.minecraft.block.BlockEntityProvider;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.LeveledCauldronBlock;
+import net.minecraft.block.cauldron.CauldronBehavior;
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.item.Item;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.biome.Biome;
+
+import java.util.Map;
+import java.util.function.Predicate;
+
+public class WaterCauldronBlock extends LeveledCauldronBlock implements BlockEntityProvider {
+    public WaterCauldronBlock(Settings settings, Predicate<Biome.Precipitation> precipitationPredicate, Map<Item, CauldronBehavior> behaviorMap) {
+        super(settings, precipitationPredicate, behaviorMap);
+    }
+
+    @Override
+    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
+        return new WaterCauldronBlockEntity(pos, state);
+    }
+}
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/block/WaterCauldronBlockEntity.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/block/WaterCauldronBlockEntity.java
index 5e6678f..b5d012e 100644
--- a/src/main/java/me/tibinonest/mods/cauldron_dyeing/block/WaterCauldronBlockEntity.java
+++ b/src/main/java/me/tibinonest/mods/cauldron_dyeing/block/WaterCauldronBlockEntity.java
@@ -5,22 +5,20 @@ import me.tibinonest.mods.cauldron_dyeing.CauldronDyeing;
 import net.minecraft.block.BlockState;
 import net.minecraft.block.entity.BlockEntity;
 import net.minecraft.nbt.NbtCompound;
+import net.minecraft.network.Packet;
 import net.minecraft.network.listener.ClientPlayPacketListener;
-import net.minecraft.network.packet.Packet;
 import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
 import net.minecraft.server.world.ServerWorld;
 import net.minecraft.util.DyeColor;
 import net.minecraft.util.math.BlockPos;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Arrays;
-
 public class WaterCauldronBlockEntity extends BlockEntity {
-    private static final int[] NULL_COLOR = new int[] {-1, -1, -1};
-    private int[] color = NULL_COLOR;
+    private int[] color;
 
     public WaterCauldronBlockEntity(BlockPos pos, BlockState state) {
         super(CauldronDyeing.WATER_CAULDRON_BLOCK_ENTITY, pos, state);
+        color = new int[]{-1, -1, -1};
     }
 
     // Formula taken from https://minecraft.fandom.com/wiki/Dye#Dyeing_armor and DyeableItem#blendAndSetColor
@@ -32,7 +30,7 @@ public class WaterCauldronBlockEntity extends BlockEntity {
         newColor[1] = (int) (colorComponents[1] * 255.0f);
         newColor[2] = (int) (colorComponents[2] * 255.0f);
 
-        if (!Arrays.equals(color, NULL_COLOR)) {
+        if (hasColor()) {
             var avgColor = new int[3];
             avgColor[0] = (color[0] + newColor[0]) / 2;
             avgColor[1] = (color[1] + newColor[1]) / 2;
@@ -53,12 +51,16 @@ public class WaterCauldronBlockEntity extends BlockEntity {
         markDirty();
     }
 
+    public boolean hasColor() {
+        return color.length == 3 && color[0] != -1 && color[1] != -1 && color[2] != -1;
+    }
+
     public int getColor() {
-        return !Arrays.equals(color, NULL_COLOR) ? (color[0] << 16) + (color[1] << 8) + color[2] : -1;
+        return hasColor() ? (color[0] << 16) + (color[1] << 8) + color[2] : -1;
     }
 
     public void resetColor() {
-        color = NULL_COLOR;
+        color = new int[]{-1, -1, -1};
         markDirty();
     }
 
@@ -76,14 +78,16 @@ public class WaterCauldronBlockEntity extends BlockEntity {
     }
 
     @Override
-    public NbtCompound toSyncedNbt() {
-        return toNbt();
+    public NbtCompound toInitialChunkDataNbt() {
+        var nbt = new NbtCompound();
+        nbt.putIntArray("color", color);
+        return nbt;
     }
 
     @Nullable
     @Override
     public Packet<ClientPlayPacketListener> toUpdatePacket() {
-        return BlockEntityUpdateS2CPacket.of(this);
+        return BlockEntityUpdateS2CPacket.create(this);
     }
 
     @Override
@@ -91,7 +95,7 @@ public class WaterCauldronBlockEntity extends BlockEntity {
         if (world != null) {
             if (world.isClient()) {
                 CauldronDyeing.rebuildBlock(pos);
-            } else if (world instanceof ServerWorld) {
+            } else {
                 ((ServerWorld) world).getChunkManager().markForUpdate(pos);
             }
             super.markDirty();
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/AbstractCauldronBlockMixin.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/AbstractCauldronBlockMixin.java
deleted file mode 100644
index 47faca6..0000000
--- a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/AbstractCauldronBlockMixin.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package me.tibinonest.mods.cauldron_dyeing.mixin;
-
-import me.tibinonest.mods.cauldron_dyeing.block.WaterCauldronBlockEntity;
-import net.minecraft.block.BlockEntityProvider;
-import net.minecraft.block.BlockState;
-import net.minecraft.block.cauldron.AbstractCauldronBlock;
-import net.minecraft.block.entity.BlockEntity;
-import net.minecraft.fluid.Fluids;
-import net.minecraft.util.math.BlockPos;
-import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Mixin;
-
-@Mixin(AbstractCauldronBlock.class)
-public class AbstractCauldronBlockMixin implements BlockEntityProvider {
-    @Nullable
-    @Override
-    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
-        if (state.getBlock() instanceof AbstractCauldronBlock cauldron && cauldron.canBeFilledByDripstone(Fluids.WATER)) {
-            return new WaterCauldronBlockEntity(pos, state);
-        }
-
-        return null;
-    }
-}
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/BiomeColorsMixin.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/BiomeColorsMixin.java
new file mode 100644
index 0000000..f46ce94
--- /dev/null
+++ b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/BiomeColorsMixin.java
@@ -0,0 +1,22 @@
+package me.tibinonest.mods.cauldron_dyeing.mixin;
+
+import me.tibinonest.mods.cauldron_dyeing.block.WaterCauldronBlockEntity;
+import net.minecraft.client.color.world.BiomeColors;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.BlockRenderView;
+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;
+
+@Mixin(BiomeColors.class)
+public class BiomeColorsMixin {
+    @Inject(method = "getWaterColor", at = @At("RETURN"), cancellable = true)
+    private static void modifyWaterColor(BlockRenderView world, BlockPos pos, CallbackInfoReturnable<Integer> cir) {
+        var blockEntity = world.getBlockEntity(pos);
+
+        if (blockEntity instanceof WaterCauldronBlockEntity waterCauldron && waterCauldron.getColor() != -1) {
+            cir.setReturnValue(waterCauldron.getColor());
+        }
+    }
+}
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/BlockColorsMixin.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/BlockColorsMixin.java
deleted file mode 100644
index 9e98780..0000000
--- a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/BlockColorsMixin.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package me.tibinonest.mods.cauldron_dyeing.mixin;
-
-import me.tibinonest.mods.cauldron_dyeing.CauldronDyeing;
-import net.minecraft.block.Blocks;
-import net.minecraft.client.color.block.BlockColors;
-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 org.spongepowered.asm.mixin.injection.callback.LocalCapture;
-
-@Mixin(BlockColors.class)
-public class BlockColorsMixin {
-    @Inject(method = "create", at = @At("TAIL"), locals = LocalCapture.CAPTURE_FAILHARD)
-    private static void cauldron_dyeing$modifyCreate(CallbackInfoReturnable<BlockColors> cir, BlockColors blockColors) {
-        blockColors.registerColorProvider((state, world, pos, tintIndex) -> world != null && pos != null ? CauldronDyeing.getColor(world, pos) : -1, Blocks.WATER_CAULDRON);
-    }
-}
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/BlocksMixin.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/BlocksMixin.java
new file mode 100644
index 0000000..c5a6df1
--- /dev/null
+++ b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/BlocksMixin.java
@@ -0,0 +1,19 @@
+package me.tibinonest.mods.cauldron_dyeing.mixin;
+
+import me.tibinonest.mods.cauldron_dyeing.block.WaterCauldronBlock;
+import net.minecraft.block.AbstractBlock;
+import net.minecraft.block.Block;
+import net.minecraft.block.Blocks;
+import net.minecraft.block.cauldron.CauldronBehavior;
+import net.minecraft.util.registry.Registry;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(Blocks.class)
+public class BlocksMixin {
+    @Redirect(method = "<clinit>", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Blocks;register(Ljava/lang/String;Lnet/minecraft/block/Block;)Lnet/minecraft/block/Block;", ordinal = 271))
+    private static Block redirectWaterCauldron(String id, Block block) {
+        return Registry.register(Registry.BLOCK, id, new WaterCauldronBlock(AbstractBlock.Settings.copy(Blocks.CAULDRON), WaterCauldronBlock.RAIN_PREDICATE, CauldronBehavior.WATER_CAULDRON_BEHAVIOR));
+    }
+}
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/CauldronBehaviorMixin.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/CauldronBehaviorMixin.java
index aff0684..01661dd 100644
--- a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/CauldronBehaviorMixin.java
+++ b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/CauldronBehaviorMixin.java
@@ -3,8 +3,8 @@ package me.tibinonest.mods.cauldron_dyeing.mixin;
 import me.tibinonest.mods.cauldron_dyeing.block.CauldronBehaviorExtended;
 import me.tibinonest.mods.cauldron_dyeing.block.WaterCauldronBlockEntity;
 import net.minecraft.block.BlockState;
+import net.minecraft.block.LeveledCauldronBlock;
 import net.minecraft.block.cauldron.CauldronBehavior;
-import net.minecraft.block.cauldron.LeveledCauldronBlock;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.DyeItem;
 import net.minecraft.item.DyeableItem;
@@ -25,9 +25,8 @@ import java.util.Map;
 
 @Mixin(CauldronBehavior.class)
 public interface CauldronBehaviorMixin {
-    @SuppressWarnings("target")
     @Inject(method = "method_32209", at = @At("HEAD"), cancellable = true)
-    private static void cauldron_dyeing$injectCleanDyeableItem(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, ItemStack stack, CallbackInfoReturnable<ActionResult> cir) {
+    private static void injectCleanDyeableItem(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, ItemStack stack, CallbackInfoReturnable<ActionResult> cir) {
         var blockEntity = world.getBlockEntity(pos);
         var item = stack.getItem();
 
@@ -42,9 +41,8 @@ public interface CauldronBehaviorMixin {
         }
     }
 
-    @SuppressWarnings("target")
-    @Inject(method = "method_32217", at = @At("TAIL"))
-    private static void cauldron_dyeing$injectFillWithWater(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, ItemStack stack, CallbackInfoReturnable<ActionResult> cir) {
+    @Inject(method = "method_32217", at = @At(value = "TAIL"))
+    private static void injectFillWithWater(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, ItemStack stack, CallbackInfoReturnable<ActionResult> cir) {
         var blockEntity = world.getBlockEntity(pos);
         if (blockEntity instanceof WaterCauldronBlockEntity waterCauldron) {
             waterCauldron.resetColor();
@@ -52,9 +50,10 @@ public interface CauldronBehaviorMixin {
     }
 
     @Inject(method = "registerBehavior", at = @At("TAIL"))
-    private static void cauldron_dyeing$injectRegisterBehavior(CallbackInfo ci) {
-        for (Map.Entry<DyeColor, DyeItem> dyeItem : DyeItem.DYES.entrySet()) {
-            CauldronBehavior.WATER_CAULDRON_BEHAVIOR.map().put(dyeItem.getValue(), CauldronBehaviorExtended.DYE_WATER);
+    private static void injectRegisterBehavior(CallbackInfo ci) {
+        var dyeMap = DyeItemAccessor.getDyeMap();
+        for (Map.Entry<DyeColor, DyeItem> dyeItem : dyeMap.entrySet()) {
+            CauldronBehavior.WATER_CAULDRON_BEHAVIOR.put(dyeItem.getValue(), CauldronBehaviorExtended.DYE_WATER);
         }
     }
 }
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/DyeItemAccessor.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/DyeItemAccessor.java
new file mode 100644
index 0000000..f470fef
--- /dev/null
+++ b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/DyeItemAccessor.java
@@ -0,0 +1,16 @@
+package me.tibinonest.mods.cauldron_dyeing.mixin;
+
+import net.minecraft.item.DyeItem;
+import net.minecraft.util.DyeColor;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import java.util.Map;
+
+@Mixin(DyeItem.class)
+public interface DyeItemAccessor {
+    @Accessor("DYES")
+    static Map<DyeColor, DyeItem> getDyeMap() {
+        throw new AssertionError();
+    }
+}
diff --git a/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/sodium/SodiumBlockRendererMixin.java b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/sodium/SodiumBlockRendererMixin.java
new file mode 100644
index 0000000..b13e28e
--- /dev/null
+++ b/src/main/java/me/tibinonest/mods/cauldron_dyeing/mixin/sodium/SodiumBlockRendererMixin.java
@@ -0,0 +1,28 @@
+package me.tibinonest.mods.cauldron_dyeing.mixin.sodium;
+
+import me.jellysquid.mods.sodium.client.model.quad.ModelQuadView;
+import me.jellysquid.mods.sodium.client.model.quad.blender.ColorBlender;
+import me.jellysquid.mods.sodium.client.model.quad.blender.ColorSampler;
+import me.jellysquid.mods.sodium.client.model.quad.blender.FlatColorBlender;
+import me.jellysquid.mods.sodium.client.render.pipeline.BlockRenderer;
+import me.tibinonest.mods.cauldron_dyeing.block.WaterCauldronBlockEntity;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.BlockRenderView;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(BlockRenderer.class)
+public class SodiumBlockRendererMixin {
+    @Redirect(method = "renderQuad", at = @At(value = "INVOKE", target = "Lme/jellysquid/mods/sodium/client/model/quad/blender/ColorBlender;getColors(Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/util/math/BlockPos;Lme/jellysquid/mods/sodium/client/model/quad/ModelQuadView;Lme/jellysquid/mods/sodium/client/model/quad/blender/ColorSampler;Ljava/lang/Object;)[I"))
+    private <T> int[] redirectGetColors(ColorBlender instance, BlockRenderView world, BlockPos pos, ModelQuadView quad, ColorSampler<T> sampler, T state) {
+        var blockEntity = world.getBlockEntity(pos);
+
+        if (blockEntity instanceof WaterCauldronBlockEntity) {
+            var flatBiomeColorBlender = new FlatColorBlender();
+            return flatBiomeColorBlender.getColors(world, pos, quad, sampler, state);
+        }
+
+        return instance.getColors(world, pos, quad, sampler, state);
+    }
+}
diff --git a/src/main/resources/assets/cauldron_dyeing/icon.png b/src/main/resources/assets/cauldron_dyeing/icon.png
index 915488c..3550e2f 100644
Binary files a/src/main/resources/assets/cauldron_dyeing/icon.png and b/src/main/resources/assets/cauldron_dyeing/icon.png differ
diff --git a/src/main/resources/cauldron-dyeing.accesswidener b/src/main/resources/cauldron-dyeing.accesswidener
deleted file mode 100644
index d48d1c3..0000000
--- a/src/main/resources/cauldron-dyeing.accesswidener
+++ /dev/null
@@ -1,5 +0,0 @@
-accessWidener v1 named
-
-accessible method net/minecraft/block/cauldron/AbstractCauldronBlock canBeFilledByDripstone (Lnet/minecraft/fluid/Fluid;)Z
-
-accessible field net/minecraft/item/DyeItem DYES Ljava/util/Map;
diff --git a/src/main/resources/cauldron-dyeing.compat.sodium.mixins.json b/src/main/resources/cauldron-dyeing.compat.sodium.mixins.json
new file mode 100644
index 0000000..597c520
--- /dev/null
+++ b/src/main/resources/cauldron-dyeing.compat.sodium.mixins.json
@@ -0,0 +1,11 @@
+{
+  "package": "me.tibinonest.mods.cauldron_dyeing.mixin.sodium",
+  "required": false,
+  "compatibilityLevel": "JAVA_17",
+  "client": [
+    "SodiumBlockRendererMixin"
+  ],
+  "injectors": {
+    "defaultRequire": 1
+  }
+}
diff --git a/src/main/resources/cauldron-dyeing.mixins.json b/src/main/resources/cauldron-dyeing.mixins.json
index b424eb0..57a9028 100644
--- a/src/main/resources/cauldron-dyeing.mixins.json
+++ b/src/main/resources/cauldron-dyeing.mixins.json
@@ -3,11 +3,12 @@
   "required": true,
   "compatibilityLevel": "JAVA_17",
   "mixins": [
-    "AbstractCauldronBlockMixin",
-    "CauldronBehaviorMixin"
+    "BlocksMixin",
+    "CauldronBehaviorMixin",
+    "DyeItemAccessor"
   ],
   "client": [
-    "BlockColorsMixin"
+    "BiomeColorsMixin"
   ],
   "injectors": {
     "defaultRequire": 1
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 2f7e512..4a9b8ef 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -19,12 +19,12 @@
       "me.tibinonest.mods.cauldron_dyeing.CauldronDyeing"
     ]
   },
-  "accessWidener": "cauldron-dyeing.accesswidener",
   "mixins": [
-    "cauldron-dyeing.mixins.json"
+    "cauldron-dyeing.mixins.json",
+    "cauldron-dyeing.compat.sodium.mixins.json"
   ],
   "depends": {
-    "minecraft": ">=1.20.3",
+    "minecraft": ">=1.18",
     "fabricloader": ">=0.12.0",
     "fabric-object-builder-api-v1": ">=0.1"
   }