diff --git a/patches/server/Configurable-top-of-nether-void-damage.patch b/patches/server/Configurable-top-of-nether-void-damage.patch
index 460465bf45..e4d21e14ed 100644
--- a/patches/server/Configurable-top-of-nether-void-damage.patch
+++ b/patches/server/Configurable-top-of-nether-void-damage.patch
@@ -3,6 +3,7 @@ From: Zach Brown <1254957+zachbr@users.noreply.github.com>
 Date: Tue, 1 Mar 2016 23:58:50 -0600
 Subject: [PATCH] Configurable top of nether void damage
 
+Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
 
 diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
@@ -15,10 +16,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -        if (this.getY() < (double) (this.level.getMinBuildHeight() - 64)) {
 +        // Paper start - Configurable nether ceiling damage
 +        if (this.getY() < (double) (this.level.getMinBuildHeight() - 64) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER
-+            && level.paperConfig().environment.netherCeilingVoidDamageHeight > 0
-+            && this.getY() >= this.level.paperConfig().environment.netherCeilingVoidDamageHeight
++            && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v)
 +            && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) {
 +            // Paper end
              this.outOfWorld();
          }
  
+diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
++++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
+@@ -0,0 +0,0 @@ public class PortalForcer {
+         Optional<PoiRecord> optional = villageplace.getInSquare((holder) -> {
+             return holder.is(PoiTypes.NETHER_PORTAL);
+         }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> {
+-            return worldborder.isWithinBounds(villageplacerecord.getPos());
++            return worldborder.isWithinBounds(villageplacerecord.getPos()) && !this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> villageplacerecord.getPos().getY() >= v); // Paper - don't teleport into void damage
+         }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error
+             return villageplacerecord.getPos().distSqr(blockposition);
+         }).thenComparingInt((villageplacerecord) -> {
+@@ -0,0 +0,0 @@ public class PortalForcer {
+         BlockPos blockposition2 = null;
+         WorldBorder worldborder = this.level.getWorldBorder();
+         int i = Math.min(this.level.getMaxBuildHeight(), this.level.getMinBuildHeight() + this.level.getLogicalHeight()) - 1;
++        // Paper start - if ceiling void damage is enabled, make sure the max height doesn't exceed the void damage height
++        if (this.level.paperConfig().environment.netherCeilingVoidDamageHeight.enabled()) {
++            i = Math.min(i, this.level.paperConfig().environment.netherCeilingVoidDamageHeight.intValue() - 1);
++        }
++        // Paper end
+         BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable();
+         Iterator iterator = BlockPos.spiralAround(blockposition, createRadius, Direction.EAST, Direction.SOUTH).iterator(); // CraftBukkit
+ 
diff --git a/patches/server/Drop-falling-block-and-tnt-entities-at-the-specified.patch b/patches/server/Drop-falling-block-and-tnt-entities-at-the-specified.patch
index 6043636ba6..0b8acadb65 100644
--- a/patches/server/Drop-falling-block-and-tnt-entities-at-the-specified.patch
+++ b/patches/server/Drop-falling-block-and-tnt-entities-at-the-specified.patch
@@ -3,7 +3,7 @@ From: Byteflux <byte@byteflux.net>
 Date: Tue, 1 Mar 2016 14:14:15 -0600
 Subject: [PATCH] Drop falling block and tnt entities at the specified height
 
-* Dec 2, 2020 Added tnt nerf for tnt minecarts - Machine_Maker
+Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
 
 diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
@@ -15,7 +15,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              this.move(MoverType.SELF, this.getDeltaMovement());
 +
 +            // Paper start - Configurable EntityFallingBlock height nerf
-+            if (this.level.paperConfig().fixes.fallingBlockHeightNerf != 0 && this.getY() > this.level.paperConfig().fixes.fallingBlockHeightNerf) {
++            if (this.level.paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) {
 +                if (this.dropItem && this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
 +                    this.spawnAtLocation(block);
 +                }
@@ -36,7 +36,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          this.move(MoverType.SELF, this.getDeltaMovement());
 +        // Paper start - Configurable TNT entity height nerf
-+        if (this.level.paperConfig().fixes.tntEntityHeightNerf != 0 && this.getY() > this.level.paperConfig().fixes.tntEntityHeightNerf) {
++        if (this.level.paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) {
 +            this.discard();
 +            return;
 +        }
@@ -53,7 +53,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          super.tick();
          if (this.fuse > 0) {
 +            // Paper start - Configurable TNT entity height nerf
-+            if (this.level.paperConfig().fixes.tntEntityHeightNerf != 0 && this.getY() > this.level.paperConfig().fixes.tntEntityHeightNerf) {
++            if (this.level.paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) {
 +                this.discard();
 +                return;
 +            }
diff --git a/patches/server/Fix-sand-duping.patch b/patches/server/Fix-sand-duping.patch
index 0a3a7cb42f..7087d5f9fa 100644
--- a/patches/server/Fix-sand-duping.patch
+++ b/patches/server/Fix-sand-duping.patch
@@ -33,5 +33,5 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            // Paper end - fix sand duping
 +
              // Paper start - Configurable EntityFallingBlock height nerf
-             if (this.level.paperConfig().fixes.fallingBlockHeightNerf != 0 && this.getY() > this.level.paperConfig().fixes.fallingBlockHeightNerf) {
+             if (this.level.paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) {
                  if (this.dropItem && this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
diff --git a/patches/server/Optimise-general-POI-access.patch b/patches/server/Optimise-general-POI-access.patch
index aecef814bc..d8a938df1b 100644
--- a/patches/server/Optimise-general-POI-access.patch
+++ b/patches/server/Optimise-general-POI-access.patch
@@ -1004,7 +1004,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -        Optional<PoiRecord> optional = villageplace.getInSquare((holder) -> {
 -            return holder.is(PoiTypes.NETHER_PORTAL);
 -        }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> {
--            return worldborder.isWithinBounds(villageplacerecord.getPos());
+-            return worldborder.isWithinBounds(villageplacerecord.getPos()) && !this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> villageplacerecord.getPos().getY() >= v); // Paper - don't teleport into void damage
 -        }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error
 -            return villageplacerecord.getPos().distSqr(blockposition);
 -        }).thenComparingInt((villageplacerecord) -> {
@@ -1025,7 +1025,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    // why would we generate the chunk?
 +                    return false;
 +                }
-+                if (!worldborder.isWithinBounds(pos)) {
++                if (!worldborder.isWithinBounds(pos) || this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v)) { // Paper - don't teleport into void damage
 +                    return false;
 +                }
 +                return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
diff --git a/patches/server/Paper-config-files.patch b/patches/server/Paper-config-files.patch
index b1bb4bd32a..5b2d1b8810 100644
--- a/patches/server/Paper-config-files.patch
+++ b/patches/server/Paper-config-files.patch
@@ -947,11 +947,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import io.papermc.paper.configuration.transformation.global.LegacyPaperConfig;
 +import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
 +import io.papermc.paper.configuration.transformation.world.LegacyPaperWorldConfig;
++import io.papermc.paper.configuration.transformation.world.ZeroWorldHeight;
 +import io.papermc.paper.configuration.type.BooleanOrDefault;
 +import io.papermc.paper.configuration.type.DoubleOrDefault;
 +import io.papermc.paper.configuration.type.Duration;
 +import io.papermc.paper.configuration.type.EngineMode;
-+import io.papermc.paper.configuration.type.IntOrDefault;
++import io.papermc.paper.configuration.type.IntOr;
 +import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer;
 +import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -965,7 +966,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import net.minecraft.world.item.Item;
 +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
 +import org.apache.commons.lang3.RandomStringUtils;
-+import org.bukkit.command.Command;
 +import org.bukkit.configuration.ConfigurationSection;
 +import org.bukkit.configuration.file.YamlConfiguration;
 +import org.jetbrains.annotations.VisibleForTesting;
@@ -988,9 +988,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import java.nio.file.Files;
 +import java.nio.file.Path;
 +import java.nio.file.StandardCopyOption;
-+import java.util.HashMap;
 +import java.util.List;
-+import java.util.Map;
 +import java.util.function.Function;
 +import java.util.function.Supplier;
 +
@@ -1135,7 +1133,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .register(new TypeToken<Reference2LongMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2LongMap<?>>(Reference2LongOpenHashMap::new, Long.TYPE))
 +                    .register(new TypeToken<Table<?, ?, ?>>() {}, new TableSerializer())
 +                    .register(new StringRepresentableSerializer())
-+                    .register(IntOrDefault.SERIALIZER)
++                    .register(IntOr.Default.SERIALIZER)
++                    .register(IntOr.Disabled.SERIALIZER)
 +                    .register(DoubleOrDefault.SERIALIZER)
 +                    .register(BooleanOrDefault.SERIALIZER)
 +                    .register(Duration.SERIALIZER)
@@ -1162,7 +1161,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            builder.addAction(path, TransformAction.remove());
 +        }
 +        builder.build().apply(node);
-+        // ADD FUTURE TRANSFORMS HERE
++
++        final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder();
++        ZeroWorldHeight.apply(versionedBuilder);
++        // ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE
++        versionedBuilder.build().apply(node);
 +    }
 +
 +    @Override
@@ -1171,8 +1174,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        for (NodePath path : RemovedConfigurations.REMOVED_GLOBAL_PATHS) {
 +            builder.addAction(path, TransformAction.remove());
 +        }
++        // ADD FUTURE TRANSFORMS TO builder HERE
 +        builder.build().apply(node);
-+        // ADD FUTURE TRANSFORMS HERE
 +    }
 +
 +    private static final List<Transformations.DefaultsAware> DEFAULT_AWARE_TRANSFORMATIONS = List.of(FeatureSeedsGeneration::apply);
@@ -1449,13 +1452,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import io.papermc.paper.configuration.type.DoubleOrDefault;
 +import io.papermc.paper.configuration.type.Duration;
 +import io.papermc.paper.configuration.type.EngineMode;
-+import io.papermc.paper.configuration.type.IntOrDefault;
++import io.papermc.paper.configuration.type.IntOr;
 +import io.papermc.paper.configuration.type.fallback.ArrowDespawnRate;
 +import io.papermc.paper.configuration.type.fallback.AutosavePeriod;
 +import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 +import it.unimi.dsi.fastutil.objects.Reference2LongMap;
 +import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
++import java.util.Arrays;
++import java.util.List;
++import java.util.Map;
++import java.util.function.Function;
++import java.util.stream.Collectors;
 +import net.minecraft.Util;
 +import net.minecraft.core.Holder;
 +import net.minecraft.core.Registry;
@@ -1475,16 +1483,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import org.spongepowered.configurate.objectmapping.meta.Required;
 +import org.spongepowered.configurate.objectmapping.meta.Setting;
 +
-+import java.util.Arrays;
-+import java.util.List;
-+import java.util.Map;
-+import java.util.function.Function;
-+import java.util.stream.Collectors;
-+
 +@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
 +public class WorldConfiguration extends ConfigurationPart {
 +    private static final Logger LOGGER = LogUtils.getLogger();
-+    static final int CURRENT_VERSION = 28;
++    static final int CURRENT_VERSION = 29; // (when you change the version, change the comment, so it conflicts on rebases): zero height fixes
 +
 +    private transient final SpigotWorldConfig spigotConfig;
 +    private transient final ResourceLocation worldKey;
@@ -1575,8 +1577,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            public WaterAnimalSpawnHeight wateranimalSpawnHeight;
 +
 +            public class WaterAnimalSpawnHeight extends ConfigurationPart {
-+                public IntOrDefault maximum = IntOrDefault.USE_DEFAULT;
-+                public IntOrDefault minimum = IntOrDefault.USE_DEFAULT;
++                public IntOr.Default maximum = IntOr.Default.USE_DEFAULT;
++                public IntOr.Default minimum = IntOr.Default.USE_DEFAULT;
 +            }
 +
 +            public SlimeSpawnHeight slimeSpawnHeight;
@@ -1775,7 +1777,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        public int portalCreateRadius = 16;
 +        public boolean portalSearchVanillaDimensionScaling = true;
 +        public boolean disableTeleportationSuffocationCheck = false;
-+        public int netherCeilingVoidDamageHeight = 0;
++        public IntOr.Disabled netherCeilingVoidDamageHeight = IntOr.Disabled.DISABLED;
 +    }
 +
 +    public Spawn spawn;
@@ -1802,8 +1804,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        public boolean preventTntFromMovingInWater = false;
 +        public boolean splitOverstackedLoot = true;
 +        public boolean fixCuringZombieVillagerDiscountExploit = true;
-+        public int fallingBlockHeightNerf = 0;
-+        public int tntEntityHeightNerf = 0;
++        public IntOr.Disabled fallingBlockHeightNerf = IntOr.Disabled.DISABLED;
++        public IntOr.Disabled tntEntityHeightNerf = IntOr.Disabled.DISABLED;
 +    }
 +
 +    public UnsupportedSettings unsupportedSettings;
@@ -2916,10 +2918,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 @@ -0,0 +0,0 @@
 +package io.papermc.paper.configuration.transformation;
 +
++import io.papermc.paper.configuration.Configuration;
 +import io.papermc.paper.configuration.Configurations;
 +import org.spongepowered.configurate.ConfigurationNode;
 +import org.spongepowered.configurate.NodePath;
 +import org.spongepowered.configurate.transformation.ConfigurationTransformation;
++import org.spongepowered.configurate.transformation.TransformAction;
 +
 +import static org.spongepowered.configurate.NodePath.path;
 +
@@ -2944,6 +2948,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    public static ConfigurationTransformation.VersionedBuilder versionedBuilder() {
++        return ConfigurationTransformation.versionedBuilder().versionKey(Configuration.VERSION_FIELD);
++    }
++
 +    @FunctionalInterface
 +    public interface DefaultsAware {
 +        void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap, final ConfigurationNode defaultsNode);
@@ -3581,6 +3589,58 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        moveFromRootAndRename(builder, path("game-mechanics", oldKey), newKey, parents);
 +    }
 +}
+diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/ZeroWorldHeight.java b/src/main/java/io/papermc/paper/configuration/transformation/world/ZeroWorldHeight.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/configuration/transformation/world/ZeroWorldHeight.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.configuration.transformation.world;
++
++import io.papermc.paper.configuration.type.IntOr;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.spongepowered.configurate.ConfigurateException;
++import org.spongepowered.configurate.ConfigurationNode;
++import org.spongepowered.configurate.NodePath;
++import org.spongepowered.configurate.transformation.ConfigurationTransformation;
++import org.spongepowered.configurate.transformation.TransformAction;
++
++import static org.spongepowered.configurate.NodePath.path;
++
++/**
++ * Several configurations that set a y-level used '0' as the "disabled" value.
++ * Since 0 is now a valid value, they need to be updated.
++ */
++public class ZeroWorldHeight implements TransformAction {
++
++    private static final int VERSION = 29;
++
++    private static final String FIXES_KEY = "fixes";
++    private static final String FALLING_BLOCK_HEIGHT_NERF_KEY = "falling-block-height-nerf";
++    private static final String TNT_ENTITY_HEIGHT_NERF_KEY = "tnt-entity-height-nerf";
++
++    private static final String ENVIRONMENT_KEY = "environment";
++    private static final String NETHER_CEILING_VOID_DAMAGE_HEIGHT_KEY = "nether-ceiling-void-damage-height";
++
++    private static final ZeroWorldHeight INSTANCE = new ZeroWorldHeight();
++
++    public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
++        final ConfigurationTransformation transformation = ConfigurationTransformation.builder()
++            .addAction(path(FIXES_KEY, FALLING_BLOCK_HEIGHT_NERF_KEY), INSTANCE)
++            .addAction(path(FIXES_KEY, TNT_ENTITY_HEIGHT_NERF_KEY), INSTANCE)
++            .addAction(path(ENVIRONMENT_KEY, NETHER_CEILING_VOID_DAMAGE_HEIGHT_KEY), INSTANCE)
++            .build();
++        builder.addVersion(VERSION, transformation);
++    }
++
++    @Override
++    public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException {
++        if (value.getInt() == 0) {
++            value.set(IntOr.Disabled.DISABLED);
++        }
++        return null;
++    }
++}
 diff --git a/src/main/java/io/papermc/paper/configuration/type/BooleanOrDefault.java b/src/main/java/io/papermc/paper/configuration/type/BooleanOrDefault.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -3857,64 +3917,97 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return description;
 +    }
 +}
-diff --git a/src/main/java/io/papermc/paper/configuration/type/IntOrDefault.java b/src/main/java/io/papermc/paper/configuration/type/IntOrDefault.java
+diff --git a/src/main/java/io/papermc/paper/configuration/type/IntOr.java b/src/main/java/io/papermc/paper/configuration/type/IntOr.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
 --- /dev/null
-+++ b/src/main/java/io/papermc/paper/configuration/type/IntOrDefault.java
++++ b/src/main/java/io/papermc/paper/configuration/type/IntOr.java
 @@ -0,0 +0,0 @@
 +package io.papermc.paper.configuration.type;
 +
 +import com.mojang.logging.LogUtils;
++import java.lang.reflect.Type;
++import java.util.OptionalInt;
++import java.util.function.Function;
++import java.util.function.IntPredicate;
++import java.util.function.Predicate;
 +import org.apache.commons.lang3.math.NumberUtils;
 +import org.slf4j.Logger;
 +import org.spongepowered.configurate.serialize.ScalarSerializer;
 +import org.spongepowered.configurate.serialize.SerializationException;
 +
-+import java.lang.reflect.Type;
-+import java.util.OptionalInt;
-+import java.util.function.Predicate;
++public interface IntOr {
 +
-+public record IntOrDefault(OptionalInt value) {
-+    private static final String DEFAULT_VALUE = "default";
-+    private static final Logger LOGGER = LogUtils.getLogger();
-+    public static final IntOrDefault USE_DEFAULT = new IntOrDefault(OptionalInt.empty());
-+    public static final ScalarSerializer<IntOrDefault> SERIALIZER = new Serializer();
++    Logger LOGGER = LogUtils.getLogger();
 +
-+    public int or(final int fallback) {
-+        return this.value.orElse(fallback);
++    default int or(final int fallback) {
++        return this.value().orElse(fallback);
 +    }
 +
-+    private static final class Serializer extends ScalarSerializer<IntOrDefault> {
-+        Serializer() {
-+            super(IntOrDefault.class);
++    OptionalInt value();
++
++    default int intValue() {
++        return this.value().orElseThrow();
++    }
++
++    record Default(OptionalInt value) implements IntOr {
++        private static final String DEFAULT_VALUE = "default";
++        public static final Default USE_DEFAULT = new Default(OptionalInt.empty());
++        public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT);
++    }
++
++    record Disabled(OptionalInt value) implements IntOr {
++        private static final String DISABLED_VALUE = "disabled";
++        public static final Disabled DISABLED = new Disabled(OptionalInt.empty());
++        public static final ScalarSerializer<Disabled> SERIALIZER = new Serializer<>(Disabled.class, Disabled::new, DISABLED_VALUE, DISABLED);
++
++        public boolean test(IntPredicate predicate) {
++            return this.value.isPresent() && predicate.test(this.value.getAsInt());
++        }
++
++        public boolean enabled() {
++            return this.value.isPresent();
++        }
++    }
++
++    final class Serializer<T extends IntOr> extends ScalarSerializer<T> {
++
++        private final Function<OptionalInt, T> creator;
++        private final String otherSerializedValue;
++        private final T otherValue;
++
++        public Serializer(Class<T> classOfT, Function<OptionalInt, T> creator, String otherSerializedValue, T otherValue) {
++            super(classOfT);
++            this.creator = creator;
++            this.otherSerializedValue = otherSerializedValue;
++            this.otherValue = otherValue;
 +        }
 +
 +        @Override
-+        public IntOrDefault deserialize(final Type type, final Object obj) throws SerializationException {
++        public T deserialize(Type type, Object obj) throws SerializationException {
 +            if (obj instanceof String string) {
-+                if (DEFAULT_VALUE.equalsIgnoreCase(string)) {
-+                    return USE_DEFAULT;
++                if (this.otherSerializedValue.equalsIgnoreCase(string)) {
++                    return this.otherValue;
 +                }
 +                if (NumberUtils.isParsable(string)) {
-+                    return new IntOrDefault(OptionalInt.of(Integer.parseInt(string)));
++                    return this.creator.apply(OptionalInt.of(Integer.parseInt(string)));
 +                }
 +            } else if (obj instanceof Number num) {
 +                if (num.intValue() != num.doubleValue() || num.intValue() != num.longValue()) {
 +                    LOGGER.error("{} cannot be converted to an integer without losing information", num);
 +                }
-+                return new IntOrDefault(OptionalInt.of(num.intValue()));
++                return this.creator.apply(OptionalInt.of(num.intValue()));
 +            }
-+            throw new SerializationException(obj + "(" + type + ") is not a integer or '" + DEFAULT_VALUE + "'");
++            throw new SerializationException(obj + "(" + type + ") is not a integer or '" + this.otherSerializedValue + "'");
 +        }
 +
 +        @Override
-+        protected Object serialize(final IntOrDefault item, final Predicate<Class<?>> typeSupported) {
++        protected Object serialize(T item, Predicate<Class<?>> typeSupported) {
 +            final OptionalInt value = item.value();
 +            if (value.isPresent()) {
 +                return value.getAsInt();
 +            } else {
-+                return DEFAULT_VALUE;
++                return this.otherSerializedValue;
 +            }
 +        }
 +    }