From 5eb4ada324f51ce4d9dedc6e2fb8cacc39bc4e01 Mon Sep 17 00:00:00 2001 From: Jake Potrebic <jake.m.potrebic@gmail.com> Date: Sat, 8 Oct 2022 23:52:09 -0700 Subject: [PATCH] Fix configs that relied on outdated min/max y levels (#6986) --- ...nfigurable-top-of-nether-void-damage.patch | 29 ++- ...ck-and-tnt-entities-at-the-specified.patch | 8 +- patches/server/Fix-sand-duping.patch | 2 +- .../server/Optimise-general-POI-access.patch | 4 +- patches/server/Paper-config-files.patch | 179 +++++++++++++----- 5 files changed, 170 insertions(+), 52 deletions(-) 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; + } + } + }