key, boolean returnDefault) {
- T val = (T) this.map.get(key);
- if (!returnDefault || val != null)
- return val;
- // update value
- this.set(key, val = key.defaultSupplier().get());
- return val;
- }
-}
diff --git a/sakura-api/src/main/java/me/samsuik/sakura/mechanics/MechanicVersion.java b/sakura-api/src/main/java/me/samsuik/sakura/mechanics/MechanicVersion.java
new file mode 100644
index 0000000..9b5f98f
--- /dev/null
+++ b/sakura-api/src/main/java/me/samsuik/sakura/mechanics/MechanicVersion.java
@@ -0,0 +1,39 @@
+package me.samsuik.sakura.mechanics;
+
+/**
+ * All post-1.8 Minecraft versions with changes to cannon mechanics.
+ *
+ * Versions are encoded as shorts see {@link MinecraftVersionEncoding}.
+ */
+public final class MechanicVersion {
+ public static final short LATEST = Short.MAX_VALUE;
+ public static final short LEGACY = Short.MIN_VALUE;
+ public static final short v1_8_2 = MinecraftVersionEncoding.v1xy(8, 2);
+ public static final short v1_9 = MinecraftVersionEncoding.v1xy(9, 0);
+ public static final short v1_10 = MinecraftVersionEncoding.v1xy(10, 0);
+ public static final short v1_11 = MinecraftVersionEncoding.v1xy(11, 0);
+ public static final short v1_12 = MinecraftVersionEncoding.v1xy(12, 0);
+ public static final short v1_13 = MinecraftVersionEncoding.v1xy(13, 0);
+ public static final short v1_14 = MinecraftVersionEncoding.v1xy(14, 0);
+ public static final short v1_16 = MinecraftVersionEncoding.v1xy(16, 0);
+ public static final short v1_17 = MinecraftVersionEncoding.v1xy(17, 0);
+ public static final short v1_18_2 = MinecraftVersionEncoding.v1xy(18, 2);
+ public static final short v1_19_3 = MinecraftVersionEncoding.v1xy(19, 3);
+ public static final short v1_20 = MinecraftVersionEncoding.v1xy(20, 0);
+ public static final short v1_21_2 = MinecraftVersionEncoding.v1xy(21, 2);
+ public static final short v1_21_5 = MinecraftVersionEncoding.v1xy(21, 5);
+ public static final short v1_21_6 = MinecraftVersionEncoding.v1xy(21, 6);
+
+ public static String name(final short version) {
+ if (version == LATEST) {
+ return "latest";
+ } else if (version == LEGACY) {
+ return "legacy";
+ }
+
+ final int significant = MinecraftVersionEncoding.significant(version);
+ final int major = MinecraftVersionEncoding.major(version);
+ final int minor = MinecraftVersionEncoding.minor(version);
+ return String.format("%d.%d.%d", significant, major, minor);
+ }
+}
diff --git a/sakura-api/src/main/java/me/samsuik/sakura/mechanics/MinecraftMechanicsTarget.java b/sakura-api/src/main/java/me/samsuik/sakura/mechanics/MinecraftMechanicsTarget.java
new file mode 100644
index 0000000..c65ea24
--- /dev/null
+++ b/sakura-api/src/main/java/me/samsuik/sakura/mechanics/MinecraftMechanicsTarget.java
@@ -0,0 +1,112 @@
+package me.samsuik.sakura.mechanics;
+
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+import java.util.Locale;
+
+/**
+ * The targeted Minecraft version and server type for cannon mechanics.
+ */
+@NullMarked
+public record MinecraftMechanicsTarget(short mechanicVersion, byte serverType) {
+ private static final MinecraftMechanicsTarget LATEST = new MinecraftMechanicsTarget(MechanicVersion.LATEST, ServerType.PAPER);
+ private static final MinecraftMechanicsTarget LEGACY = new MinecraftMechanicsTarget(MechanicVersion.v1_8_2, ServerType.PAPER);
+
+ public boolean isServerType(final byte type) {
+ return this.serverType == type;
+ }
+
+ public boolean is(final short version) {
+ return this.mechanicVersion == version;
+ }
+
+ public boolean before(final short version) {
+ return this.mechanicVersion < version;
+ }
+
+ public boolean after(final short version) {
+ return this.mechanicVersion > version;
+ }
+
+ public boolean atLeast(final short version) {
+ return this.mechanicVersion >= version;
+ }
+
+ public boolean atMost(final short version) {
+ return this.mechanicVersion <= version;
+ }
+
+ public boolean between(final short minVersion, final short maxVersion) {
+ return this.mechanicVersion >= minVersion && this.mechanicVersion < maxVersion;
+ }
+
+ public boolean betweenInclusive(final short minVersion, final short maxVersion) {
+ return this.mechanicVersion >= minVersion && this.mechanicVersion <= maxVersion;
+ }
+
+ public boolean isLegacy() {
+ return this.mechanicVersion == MechanicVersion.LEGACY;
+ }
+
+ public static MinecraftMechanicsTarget latest() {
+ return LATEST;
+ }
+
+ public static MinecraftMechanicsTarget legacy() {
+ return LEGACY;
+ }
+
+ public static MinecraftMechanicsTarget vanilla(final short mechanicVersion) {
+ return new MinecraftMechanicsTarget(mechanicVersion, ServerType.VANILLA);
+ }
+
+ public static MinecraftMechanicsTarget spigot(final short mechanicVersion) {
+ return new MinecraftMechanicsTarget(mechanicVersion, ServerType.SPIGOT);
+ }
+
+ public static MinecraftMechanicsTarget paper(final short mechanicVersion) {
+ return new MinecraftMechanicsTarget(mechanicVersion, ServerType.PAPER);
+ }
+
+ public static @Nullable MinecraftMechanicsTarget fromString(final String target) throws NumberFormatException {
+ // 1.21.8+paper 1.8.8+vanilla 12.2
+ final String[] parts = target.split("\\+");
+ final String serverPart = parts.length == 2 ? parts[1] : "";
+ final byte serverType = switch (serverPart.toLowerCase(Locale.ENGLISH)) {
+ case "vanilla" -> ServerType.VANILLA;
+ case "spigot" -> ServerType.SPIGOT;
+ default -> ServerType.PAPER;
+ };
+
+ if (parts.length == 0) {
+ return null;
+ }
+
+ final String[] version = parts[0].split("\\.");
+ if (version.length < 1) {
+ return null;
+ }
+
+ final short mechanicVersion;
+ if (version.length == 1) {
+ mechanicVersion = switch (version[0]) {
+ case "latest" -> MechanicVersion.LATEST;
+ case "legacy" -> MechanicVersion.LEGACY;
+ default -> 0;
+ };
+ } else {
+ // 21.1 -> 1.21.1
+ final int first = Integer.parseInt(version[0]);
+ final int second = Integer.parseInt(version[1]);
+ if (version.length == 3) {
+ final int third = Integer.parseInt(version[2]);
+ mechanicVersion = MinecraftVersionEncoding.encode(first, second, third);
+ } else {
+ mechanicVersion = MinecraftVersionEncoding.v1xy(first, second);
+ }
+ }
+
+ return new MinecraftMechanicsTarget(mechanicVersion, serverType);
+ }
+}
diff --git a/sakura-api/src/main/java/me/samsuik/sakura/mechanics/MinecraftVersionEncoding.java b/sakura-api/src/main/java/me/samsuik/sakura/mechanics/MinecraftVersionEncoding.java
new file mode 100644
index 0000000..214b631
--- /dev/null
+++ b/sakura-api/src/main/java/me/samsuik/sakura/mechanics/MinecraftVersionEncoding.java
@@ -0,0 +1,67 @@
+package me.samsuik.sakura.mechanics;
+
+/**
+ * Encode Minecraft versions into a short.
+ */
+public final class MinecraftVersionEncoding {
+ private static final int SIGNIFICANT_SHIFT = Short.SIZE - 4;
+ private static final int MAJOR_SHIFT = SIGNIFICANT_SHIFT - 6;
+ private static final int MINOR_SHIFT = MAJOR_SHIFT - 6;
+
+ /**
+ * Encodes a 1.x.y Minecraft version into a short.
+ *
+ * @param major the major version (x)
+ * @param minor the minor version (y)
+ * @return the encoded version as a short
+ */
+ public static short v1xy(final int major, final int minor) {
+ return encode(1, major, minor);
+ }
+
+ /**
+ * Encodes a Minecraft version into a short.
+ *
+ * @param significant the significant version
+ * @param major the major version
+ * @param minor the minor version
+ * @return the encoded version as a short
+ */
+ public static short encode(final int significant, final int major, final int minor) {
+ short encoded = 0;
+ encoded |= (short) (significant << SIGNIFICANT_SHIFT);
+ encoded |= (short) (major << MAJOR_SHIFT);
+ encoded |= (short) (minor << MINOR_SHIFT);
+ return encoded;
+ }
+
+ /**
+ * Decodes the significant version from an encoded version.
+ *
+ * @param version the encoded version
+ * @return the significant version
+ */
+ public static int significant(final short version) {
+ return (version >>> SIGNIFICANT_SHIFT) & 15;
+ }
+
+ /**
+ * Decodes the major version from an encoded version.
+ *
+ * @param version the encoded version
+ * @return the major version
+ */
+ public static int major(final short version) {
+ return (version >>> MAJOR_SHIFT) & 63;
+ }
+
+ /**
+ * Decodes the minor version from an encoded version.
+ *
+ * @param version the encoded version
+ * @return the minor version
+ */
+ public static int minor(final short version) {
+ return (version >>> MINOR_SHIFT) & 63;
+ }
+}
diff --git a/sakura-api/src/main/java/me/samsuik/sakura/mechanics/ServerType.java b/sakura-api/src/main/java/me/samsuik/sakura/mechanics/ServerType.java
new file mode 100644
index 0000000..90cd1d6
--- /dev/null
+++ b/sakura-api/src/main/java/me/samsuik/sakura/mechanics/ServerType.java
@@ -0,0 +1,22 @@
+package me.samsuik.sakura.mechanics;
+
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Types of server software that have different cannon mechanics.
+ */
+@NullMarked
+public final class ServerType {
+ public static final byte VANILLA = 0;
+ public static final byte SPIGOT = 1;
+ public static final byte PAPER = 2;
+
+ public static String name(final byte serverType) {
+ return switch (serverType) {
+ case 0 -> "vanilla";
+ case 1 -> "spigot";
+ case 2 -> "paper";
+ default -> "unknown";
+ };
+ }
+}
diff --git a/sakura-api/src/main/java/me/samsuik/sakura/physics/PhysicsVersion.java b/sakura-api/src/main/java/me/samsuik/sakura/physics/PhysicsVersion.java
deleted file mode 100644
index 2de50d6..0000000
--- a/sakura-api/src/main/java/me/samsuik/sakura/physics/PhysicsVersion.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package me.samsuik.sakura.physics;
-
-import org.jspecify.annotations.NullMarked;
-
-@NullMarked
-public enum PhysicsVersion {
- LEGACY("legacy", 1_0_0), // replicates patched 1.8.8 paper mechanics
- v1_8_2("1.8.2", 1_8_2), // vanilla mechanics
- v1_9("1.9", 1_9_0),
- v1_10("1.10", 1_10_0),
- v1_11("1.11", 1_11_0),
- v1_12("1.12", 1_12_0),
- v1_13("1.13", 1_13_0),
- v1_14("1.14", 1_14_0),
- v1_16("1.16", 1_16_0),
- v1_17("1.17", 1_17_0),
- v1_18_2("1.18.2", 1_18_2),
- v1_19_3("1.19.3", 1_19_3),
- v1_20("1.20", 1_20_0),
- v1_21_2("1.21.2", 1_21_2),
- v1_21_5("1.21.5", 1_21_5),
- v1_21_6("1.21.6", 1_21_6),
- LATEST("latest", 9_99_9); // latest version
-
- private final String friendlyName;
- private final int version;
-
- PhysicsVersion(String friendlyName, int version) {
- this.friendlyName = friendlyName;
- this.version = version;
- }
-
- public boolean isLegacy() {
- return this == LEGACY;
- }
-
- public boolean afterOrEqual(int version) {
- return this.version >= version;
- }
-
- public boolean before(int version) {
- return this.version < version;
- }
-
- public boolean is(int version) {
- return this.version == version;
- }
-
- public boolean isWithin(int min, int max) {
- return this.version >= min && this.version <= max;
- }
-
- public int getVersion() {
- return this.version;
- }
-
- public String getFriendlyName() {
- return this.friendlyName;
- }
-
- public static PhysicsVersion from(String string) {
- int parsedVersion = Integer.MIN_VALUE;
- try {
- String versionString = string.replace(".", "");
- parsedVersion = Integer.parseInt(versionString);
- } catch (NumberFormatException nfe) {
- // ignored
- }
- for (PhysicsVersion ver : values()) {
- if (ver.name().equalsIgnoreCase(string) || ver.getFriendlyName().equalsIgnoreCase(string) || ver.is(parsedVersion)) {
- return ver;
- }
- }
- return LATEST;
- }
-}
diff --git a/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneConfiguration.java b/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneConfiguration.java
new file mode 100644
index 0000000..5c6d063
--- /dev/null
+++ b/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneConfiguration.java
@@ -0,0 +1,16 @@
+package me.samsuik.sakura.redstone;
+
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Configuration for redstone behavior
+ *
+ * @param implementation the redstone implementation to use
+ * @param cache whether to cache redstone calculations
+ */
+@NullMarked
+public record RedstoneConfiguration(RedstoneImplementation implementation, boolean cache) {
+ public static RedstoneConfiguration withImplementation(final RedstoneImplementation implementation) {
+ return new RedstoneConfiguration(implementation, false);
+ }
+}
diff --git a/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneImplementation.java b/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneImplementation.java
index bfb5e9e..c9b7281 100644
--- a/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneImplementation.java
+++ b/sakura-api/src/main/java/me/samsuik/sakura/redstone/RedstoneImplementation.java
@@ -2,6 +2,9 @@ package me.samsuik.sakura.redstone;
import org.jspecify.annotations.NullMarked;
+/**
+ * The redstone implementation to use.
+ */
@NullMarked
public enum RedstoneImplementation {
VANILLA("vanilla"),
@@ -10,11 +13,11 @@ public enum RedstoneImplementation {
private final String friendlyName;
- RedstoneImplementation(String friendlyName) {
+ RedstoneImplementation(final String friendlyName) {
this.friendlyName = friendlyName;
}
- public String getFriendlyName() {
+ public final String getFriendlyName() {
return this.friendlyName;
}
}
diff --git a/sakura-server/minecraft-patches/features/0005-Add-redstone-implementation-api.patch b/sakura-server/minecraft-patches/features/0005-Add-redstone-implementation-api.patch
index dc3126e..66ba2a9 100644
--- a/sakura-server/minecraft-patches/features/0005-Add-redstone-implementation-api.patch
+++ b/sakura-server/minecraft-patches/features/0005-Add-redstone-implementation-api.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Add redstone implementation api
diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java
-index ddd70576d1551d77cbefb9d63bbbaf94b569b074..e76b6c44de16f4bf136bc9959f1eedae1492499a 100644
+index ddd70576d1551d77cbefb9d63bbbaf94b569b074..a9db955a90e0b44d3c85e39f2f7ae9ea4f68a316 100644
--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
+++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
@@ -275,7 +275,7 @@ public class RedStoneWireBlock extends Block {
@@ -13,7 +13,7 @@ index ddd70576d1551d77cbefb9d63bbbaf94b569b074..e76b6c44de16f4bf136bc9959f1eedae
*/
private void updateSurroundingRedstone(Level worldIn, BlockPos pos, BlockState state, @Nullable Orientation orientation, boolean blockAdded) {
- if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) {
-+ if (worldIn.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { // Sakura - redstone implementation api
++ if (worldIn.localConfig().at(pos).paperRedstoneImplementation() == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { // Sakura - redstone implementation api
// since 24w33a the source pos is no longer given, but instead an Orientation parameter
// when this is not null, it can be used to find the source pos, which the turbo uses
// to find the direction of information flow
@@ -22,7 +22,7 @@ index ddd70576d1551d77cbefb9d63bbbaf94b569b074..e76b6c44de16f4bf136bc9959f1eedae
if (!oldState.is(state.getBlock()) && !level.isClientSide) {
// Paper start - optimize redstone - replace call to updatePowerStrength
- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
-+ if (level.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api
++ if (level.localConfig().at(pos).paperRedstoneImplementation() == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api
level.getWireHandler().onWireAdded(pos, state); // Alternate Current
} else {
this.updateSurroundingRedstone(level, pos, state, null, true); // Vanilla/Eigencraft
@@ -31,7 +31,7 @@ index ddd70576d1551d77cbefb9d63bbbaf94b569b074..e76b6c44de16f4bf136bc9959f1eedae
// Paper start - optimize redstone - replace call to updatePowerStrength
- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
-+ if (level.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api
++ if (level.localConfig().at(pos).paperRedstoneImplementation() == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api
level.getWireHandler().onWireRemoved(pos, state); // Alternate Current
} else {
this.updateSurroundingRedstone(level, pos, state, null, false); // Vanilla/Eigencraft
@@ -40,7 +40,7 @@ index ddd70576d1551d77cbefb9d63bbbaf94b569b074..e76b6c44de16f4bf136bc9959f1eedae
// Paper start - optimize redstone (Alternate Current)
// Alternate Current handles breaking of redstone wires in the WireHandler.
- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
-+ if (level.localConfig().config(pos).redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api
++ if (level.localConfig().at(pos).paperRedstoneImplementation() == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { // Sakura - redstone implementation api
level.getWireHandler().onWireUpdated(pos, state, orientation);
} else
// Paper end - optimize redstone (Alternate Current)
diff --git a/sakura-server/minecraft-patches/features/0015-Explosion-Durable-Blocks.patch b/sakura-server/minecraft-patches/features/0015-Explosion-Durable-Blocks.patch
index cdea6ae..91b7b6f 100644
--- a/sakura-server/minecraft-patches/features/0015-Explosion-Durable-Blocks.patch
+++ b/sakura-server/minecraft-patches/features/0015-Explosion-Durable-Blocks.patch
@@ -5,29 +5,28 @@ Subject: [PATCH] Explosion Durable Blocks
diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java
-index 6db566adf2d0df1d26221eda04aa01738df6d3d2..a12068c3adbb147eeb41a093fcd2938cc2c34a15 100644
+index 6db566adf2d0df1d26221eda04aa01738df6d3d2..23c135a6355e920535734e946e5bd4d06f7f33b7 100644
--- a/net/minecraft/world/item/BlockItem.java
+++ b/net/minecraft/world/item/BlockItem.java
-@@ -38,8 +38,31 @@ public class BlockItem extends Item {
+@@ -38,8 +38,30 @@ public class BlockItem extends Item {
this.block = block;
}
+ // Sakura start - explosion durable blocks
-+ private void sendBlockDurabilityToPlayer(UseOnContext context) {
-+ Player player = context.getPlayer();
-+ BlockState state = context.getLevel().getBlockState(context.getClickedPos());
-+ Block block = state.getBlock();
-+ me.samsuik.sakura.explosion.durable.DurableMaterial material = context.getLevel().localConfig().config(context.getClickedPos()).durableMaterials.get(block);
++ private void sendBlockDurabilityToPlayer(final UseOnContext context) {
++ final Player player = context.getPlayer();
++ final Level level = context.getLevel();
++ final BlockPos clickedPos = context.getClickedPos();
++
++ // If the clicked block is a durable material then send the durability to the player
++ final BlockState state = level.getBlockState(clickedPos);
++ final Block clickedBlock = state.getBlock();
++ final me.samsuik.sakura.explosion.durable.DurableMaterial material = level.localConfig().at(clickedPos).durableMaterials.get(clickedBlock);
+
+ if (material != null) {
-+ int remaining = context.getLevel().durabilityManager.durability(context.getClickedPos(), material);
-+ int durability = material.durability();
-+
-+ player.getBukkitEntity().sendRichMessage(
-+ me.samsuik.sakura.configuration.GlobalConfiguration.get().messages.durableBlockInteraction,
-+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.unparsed("remaining", String.valueOf(remaining)),
-+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.unparsed("durability", String.valueOf(durability))
-+ );
++ final int remaining = context.getLevel().durabilityManager.durability(clickedPos, material);
++ final int durability = material.durability();
++ player.getBukkitEntity().sendMessage(me.samsuik.sakura.configuration.GlobalConfiguration.get().messages.durableBlockInteractionComponent(remaining, durability));
+ }
+ }
+
@@ -41,7 +40,7 @@ index 6db566adf2d0df1d26221eda04aa01738df6d3d2..a12068c3adbb147eeb41a093fcd2938c
return !interactionResult.consumesAction() && context.getItemInHand().has(DataComponents.CONSUMABLE)
? super.use(context.getLevel(), context.getPlayer(), context.getHand())
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 44849a074694a3f4438cf7f0672c78219859a20f..72537c6455ff0e38c98a83fd81c5afe17a0b387e 100644
+index 7a922d26952be2f19161053542fa3a4b1a3bdf80..68cc49c65c3098f0aeb2d6dfbe75262e1fff1681 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -830,6 +830,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl
@@ -53,7 +52,7 @@ index 44849a074694a3f4438cf7f0672c78219859a20f..72537c6455ff0e38c98a83fd81c5afe1
protected Level(
WritableLevelData levelData,
diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
-index d45582bdfc3fa837c5aa95f499183b60b877b7c2..341b162252efc4e65cba8a32b4c7936defede097 100644
+index dfe4058155a83ec213a37553996d2dbb9744881b..7f57531254031f7134fc6d72a5077aab5b48c7f0 100644
--- a/net/minecraft/world/level/ServerExplosion.java
+++ b/net/minecraft/world/level/ServerExplosion.java
@@ -131,7 +131,7 @@ public class ServerExplosion implements Explosion {
@@ -70,12 +69,12 @@ index d45582bdfc3fa837c5aa95f499183b60b877b7c2..341b162252efc4e65cba8a32b4c7936d
}
// Sakura end - specialised explosions
+ // Sakura start - explosion durable blocks
-+ private Optional calculateBlockResistance(BlockState blockState, FluidState fluidState, BlockPos pos) {
++ private Optional calculateBlockResistance(final BlockState blockState, final FluidState fluidState, final BlockPos pos) {
+ if (!blockState.isAir()) {
+ final Block block = blockState.getBlock();
-+ final me.samsuik.sakura.explosion.durable.DurableMaterial material = this.level.localConfig().config(pos).durableMaterials.get(block);
++ final me.samsuik.sakura.explosion.durable.DurableMaterial material = this.level.localConfig().at(pos).durableMaterials.get(block);
+
-+ if (material != null && material.resistance() >= 0.0f && pos.getY() > this.level.getMinY()) {
++ if (material != null && material.replaceBlastResistance() && pos.getY() > this.level.getMinY()) {
+ return Optional.of(material.resistance());
+ }
+ }
@@ -91,8 +90,8 @@ index d45582bdfc3fa837c5aa95f499183b60b877b7c2..341b162252efc4e65cba8a32b4c7936d
}
// CraftBukkit end
+ // Sakura start - explosion durable blocks
-+ final me.samsuik.sakura.explosion.durable.DurableMaterial material = this.level.localConfig().config(blockPos).durableMaterials.get(block);
-+ if (material != null && material.durability() >= 0) { // if durability is < 0 then only altar the blast resistance
++ final me.samsuik.sakura.explosion.durable.DurableMaterial material = this.level.localConfig().at(blockPos).durableMaterials.get(block);
++ if (material != null && material.applyDurability()) { // if durability is < 0 then only altar the blast resistance
+ if (material.onlyDamagedByTnt() && !(this.source instanceof PrimedTnt) || !this.level.durabilityManager.damage(blockPos, material)) {
+ continue;
+ }
diff --git a/sakura-server/minecraft-patches/features/0016-Destroy-Waterlogged-Blocks.patch b/sakura-server/minecraft-patches/features/0016-Destroy-Waterlogged-Blocks.patch
index fd5d773..2a4815d 100644
--- a/sakura-server/minecraft-patches/features/0016-Destroy-Waterlogged-Blocks.patch
+++ b/sakura-server/minecraft-patches/features/0016-Destroy-Waterlogged-Blocks.patch
@@ -5,11 +5,11 @@ Subject: [PATCH] Destroy Waterlogged Blocks
diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
-index 341b162252efc4e65cba8a32b4c7936defede097..9e5fe4314513b6364c51e60c1718ed942fd2dba9 100644
+index 7f57531254031f7134fc6d72a5077aab5b48c7f0..0664de8feb17805647b7de186bd4a536b17d5aa7 100644
--- a/net/minecraft/world/level/ServerExplosion.java
+++ b/net/minecraft/world/level/ServerExplosion.java
@@ -399,6 +399,11 @@ public class ServerExplosion implements Explosion {
- if (material != null && material.resistance() >= 0.0f && pos.getY() > this.level.getMinY()) {
+ if (material != null && material.replaceBlastResistance() && pos.getY() > this.level.getMinY()) {
return Optional.of(material.resistance());
}
+ // Sakura start - destroy water logged blocks
diff --git a/sakura-server/minecraft-patches/features/0017-Configure-cannon-physics.patch b/sakura-server/minecraft-patches/features/0017-Configure-cannon-physics.patch
index 6ee5cdb..64fd562 100644
--- a/sakura-server/minecraft-patches/features/0017-Configure-cannon-physics.patch
+++ b/sakura-server/minecraft-patches/features/0017-Configure-cannon-physics.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Configure cannon physics
diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
-index 548e3dc8e5fee004483b40a59e2502ba8b93e674..ac78d85e5f9a370eda7c8db8ea02198fb2b50464 100644
+index 0f61c8877694679f590cb79f4a34cf158b8ad610..bfb220f0808767fec561d9903955514729a7448f 100644
--- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
@@ -1774,6 +1774,13 @@ public final class CollisionUtil {
@@ -17,24 +17,21 @@ index 548e3dc8e5fee004483b40a59e2502ba8b93e674..ac78d85e5f9a370eda7c8db8ea02198f
+ }
+ public static Vec3 performAABBCollisions(final Vec3 moveVector, AABB axisalignedbb,
+ final List potentialCollisions,
-+ final me.samsuik.sakura.physics.PhysicsVersion physics) {
++ final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget) {
+ // Sakura end - configure cannon physics
double x = moveVector.x;
double y = moveVector.y;
double z = moveVector.z;
-@@ -1785,7 +1792,10 @@ public final class CollisionUtil {
+@@ -1785,7 +1792,7 @@ public final class CollisionUtil {
}
}
- final boolean xSmaller = Math.abs(x) < Math.abs(z);
-+ // Sakura start - configure cannon physics
-+ final boolean xSmaller = physics == null || physics.afterOrEqual(1_14_0) ? Math.abs(x) < Math.abs(z)
-+ : physics.isLegacy() && Math.abs(x) > Math.abs(z);
-+ // Sakura end - configure cannon physics
++ final boolean xSmaller = me.samsuik.sakura.mechanics.EntityBehaviour.prioritiseXFirst(x, z, mechanicsTarget); // Sakura - configure cannon physics
if (xSmaller && z != 0.0) {
z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions);
-@@ -1811,9 +1821,18 @@ public final class CollisionUtil {
+@@ -1811,9 +1818,18 @@ public final class CollisionUtil {
public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb,
final List voxels,
final List aabbs) {
@@ -45,312 +42,285 @@ index 548e3dc8e5fee004483b40a59e2502ba8b93e674..ac78d85e5f9a370eda7c8db8ea02198f
+ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb,
+ final List voxels,
+ final List aabbs,
-+ final me.samsuik.sakura.physics.PhysicsVersion physics) {
++ final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget) {
+ // Sakura end - configure cannon physics
if (voxels.isEmpty()) {
// fast track only AABBs
- return performAABBCollisions(moveVector, axisalignedbb, aabbs);
-+ return performAABBCollisions(moveVector, axisalignedbb, aabbs, physics); // Sakura - configure cannon physics
++ return performAABBCollisions(moveVector, axisalignedbb, aabbs, mechanicsTarget); // Sakura - configure cannon physics
}
double x = moveVector.x;
-@@ -1828,7 +1847,10 @@ public final class CollisionUtil {
+@@ -1828,7 +1844,7 @@ public final class CollisionUtil {
}
}
- final boolean xSmaller = Math.abs(x) < Math.abs(z);
-+ // Sakura start - configure cannon physics
-+ final boolean xSmaller = physics == null || physics.afterOrEqual(1_14_0) ? Math.abs(x) < Math.abs(z)
-+ : physics.isLegacy() && Math.abs(x) > Math.abs(z);
-+ // Sakura end - configure cannon physics
++ final boolean xSmaller = me.samsuik.sakura.mechanics.EntityBehaviour.prioritiseXFirst(x, z, mechanicsTarget); // Sakura - configure cannon physics
if (xSmaller && z != 0.0) {
z = performAABBCollisionsZ(axisalignedbb, z, aabbs);
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 2f80403bb400e88395df9d46a39c4adf69e479d7..751f8e3045dbb090f16f099097bf31b638df39d7 100644
+index c81cc505b211f230a816f2f5988b1aa793fa32db..a8ace187724ce0e26fd9b1d289ec1db4756b85b6 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
-@@ -587,6 +587,42 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -2,7 +2,6 @@ package net.minecraft.world.entity;
+
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.Lists;
+-import com.google.common.collect.Sets;
+ import com.google.common.collect.ImmutableList.Builder;
+ import com.mojang.logging.LogUtils;
+ import com.mojang.serialization.Codec;
+@@ -587,6 +586,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
}
// Sakura end - merge cannon entities
+ // Sakura start - configure cannon physics
-+ protected me.samsuik.sakura.physics.PhysicsVersion physics = me.samsuik.sakura.physics.PhysicsVersion.LATEST;
++ protected me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget = me.samsuik.sakura.mechanics.MinecraftMechanicsTarget.latest();
+
-+ public final me.samsuik.sakura.physics.PhysicsVersion physics() {
-+ return this.physics;
-+ }
-+
-+ private static void changeEntityPosition(final Entity entity, final Vec3 position, final Vec3 relativeMovement,
-+ final me.samsuik.sakura.physics.PhysicsVersion physics) {
-+ final Vec3 newPosition = position.add(relativeMovement);
-+ final Vec3 newEntityPosition;
-+ if (physics.is(1_21_5)) {
-+ newEntityPosition = manglePosition(position, relativeMovement);
-+ } else {
-+ newEntityPosition = newPosition;
-+ }
-+
-+ if (physics.afterOrEqual(1_21_5)) {
-+ entity.addMovementThisTick(new Entity.Movement(position, newPosition, true));
-+ }
-+
-+ entity.setPos(newEntityPosition);
-+ }
-+
-+ private static Vec3 manglePosition(final Vec3 position, final Vec3 relativeMovement) {
-+ Vec3 newPosition = position;
-+ for (final Direction.Axis axis : axisStepOrder(relativeMovement)) {
-+ final double movement = relativeMovement.get(axis);
-+ if (movement != 0.0) {
-+ newPosition = newPosition.relative(axis.getPositive(), movement);
-+ }
-+ }
-+
-+ return newPosition;
++ public final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget() {
++ return this.mechanicsTarget;
+ }
+ // Sakura end - configure cannon physics
public Entity(EntityType> entityType, Level level) {
this.type = entityType;
-@@ -1111,7 +1147,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1111,7 +1117,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
protected void checkSupportingBlock(boolean onGround, @Nullable Vec3 movement) {
- if (onGround) {
-+ if (onGround && this.physics.afterOrEqual(1_20_0)) { // Sakura - configure cannon physics
++ if (onGround && this.mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_20)) { // Sakura - configure cannon physics
AABB boundingBox = this.getBoundingBox();
AABB aabb = new AABB(boundingBox.minX, boundingBox.minY - 1.0E-6, boundingBox.minZ, boundingBox.maxX, boundingBox.minY, boundingBox.maxZ);
Optional optional = this.level.findSupportingBlock(this, aabb);
-@@ -1161,7 +1197,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1161,7 +1167,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (this.noPhysics) {
this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
} else {
- if (type == MoverType.PISTON) {
+ // Sakura start - configure cannon physics
-+ final me.samsuik.sakura.physics.PhysicsVersion physics = this.physics;
-+ if (type == MoverType.PISTON && physics.afterOrEqual(1_11_0)) {
++ final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget = this.mechanicsTarget;
++ if (type == MoverType.PISTON && mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_11)) {
+ // Sakura end - configure cannon physics
// Paper start - EAR 2
this.activatedTick = Math.max(this.activatedTick, MinecraftServer.currentTick + 20);
this.activatedImmunityTick = Math.max(this.activatedImmunityTick, MinecraftServer.currentTick + 20);
-@@ -1190,8 +1229,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1190,8 +1199,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
movement = this.maybeBackOffFromEdge(movement, type);
Vec3 vec3 = this.collide(movement);
double d = vec3.lengthSqr();
- if (d > 1.0E-7 || movement.lengthSqr() - d < 1.0E-7) {
- if (this.fallDistance != 0.0 && d >= 1.0 && !this.isFallingBlock) { // Sakura - optimise cannon entity movement
+ // Sakura start - configure cannon physics
-+ if (d > 1.0E-7 || physics.afterOrEqual(1_21_2) && movement.lengthSqr() - d < 1.0E-7 || physics.before(1_14_0)) {
-+ if (this.fallDistance != 0.0 && d >= 1.0 && !this.isFallingBlock && physics.afterOrEqual(1_18_2)) { // Sakura - optimise cannon entity movement
++ if (me.samsuik.sakura.mechanics.EntityBehaviour.canMoveEntity(d, movement, mechanicsTarget)) {
++ if (this.fallDistance != 0.0 && d >= 1.0 && !this.isFallingBlock && mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_18_2)) { // Sakura - optimise cannon entity movement
+ // Sakura end - configure cannon physics
BlockHitResult blockHitResult = this.level()
.clip(
new ClipContext(this.position(), this.position().add(vec3), ClipContext.Block.FALLDAMAGE_RESETTING, ClipContext.Fluid.WATER, this)
-@@ -1202,9 +1243,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1202,9 +1213,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
Vec3 vec31 = this.position();
- Vec3 vec32 = vec31.add(vec3);
- this.addMovementThisTick(new Entity.Movement(vec31, vec32, true));
- this.setPos(vec32);
-+ changeEntityPosition(this, vec31, vec3, physics); // Sakura - configure cannon physics
++ // Sakura - configure cannon physics
++ if (mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_21_6)) {
++ me.samsuik.sakura.mechanics.EntityBehaviour.pre1_21_6$changeEntityPosition(this, vec31, vec3, mechanicsTarget);
++ } else {
++ Vec3 vec32 = vec31.add(vec3);
++ this.addMovementThisTick(new Entity.Movement(vec31, vec32, true));
++ this.setPos(vec32);
++ }
++ // Sakura - configure cannon physics
}
profilerFiller.pop();
-@@ -1235,6 +1274,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1235,6 +1252,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
} else {
if (this.horizontalCollision) {
Vec3 deltaMovement = this.getDeltaMovement();
+ // Sakura start - configure cannon physics
-+ // SANITY: flag = movedX, flag1 = movedZ
-+ if (flag && flag1 && physics.isWithin(1_14_0, 1_18_1)) {
++ if (flag && flag1 && mechanicsTarget.between(me.samsuik.sakura.mechanics.MechanicVersion.v1_14, me.samsuik.sakura.mechanics.MechanicVersion.v1_18_2)) {
+ flag = false;
+ }
+ // Sakura end - configure cannon physics
this.setDeltaMovement(flag ? 0.0 : deltaMovement.x, deltaMovement.y, flag1 ? 0.0 : deltaMovement.z);
}
-@@ -1568,7 +1613,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1568,7 +1590,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
bb = currBoundingBox.expandTowards(movement.x, movement.y, movement.z);
}
this.collectCollisions(bb, voxelList, bbList, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER);
- return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, voxelList, bbList);
-+ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, voxelList, bbList, this.physics); // Sakura - configure cannon physics
++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, voxelList, bbList, this.mechanicsTarget); // Sakura - configure cannon physics
}
private Vec3 collideAxisScan(Vec3 movement, AABB currBoundingBox, List voxelList, List bbList) {
-@@ -1576,7 +1621,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1576,7 +1598,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
double y = movement.y;
double z = movement.z;
- boolean xSmaller = Math.abs(x) < Math.abs(z);
-+ // Sakura start - configure cannon physics
-+ boolean xSmaller = this.physics == null || this.physics.afterOrEqual(1_14_0) ? Math.abs(x) < Math.abs(z)
-+ : this.physics.isLegacy() && Math.abs(x) > Math.abs(z);
-+ // Sakura end - configure cannon physics
++ boolean xSmaller = me.samsuik.sakura.mechanics.EntityBehaviour.prioritiseXFirst(x, z, mechanicsTarget); // Sakura - configure cannon physics
if (y != 0.0) {
y = this.scanY(currBoundingBox, y, voxelList, bbList);
-@@ -1698,7 +1746,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1698,7 +1720,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER | this.getExtraCollisionFlags(), null // Sakura - load chunks on movement
);
potentialCollisionsBB.addAll(entityAABBs);
- final Vec3 collided = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currentBox, potentialCollisionsVoxel, potentialCollisionsBB);
-+ final Vec3 collided = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currentBox, potentialCollisionsVoxel, potentialCollisionsBB, this.physics); // Sakura - configure cannon physics
++ final Vec3 collided = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currentBox, potentialCollisionsVoxel, potentialCollisionsBB, this.mechanicsTarget); // Sakura - configure cannon physics
final boolean collidedX = collided.x != movement.x;
final boolean collidedY = collided.y != movement.y;
-@@ -1843,11 +1891,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1843,11 +1865,22 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
private void checkInsideBlocks(Vec3 vec3, Vec3 vec31, InsideBlockEffectApplier.StepBasedCollector stepBasedCollector, LongSet set) {
- AABB aabb = this.makeBoundingBox(vec31).deflate(1.0E-5F);
+ // Sakura start - configure cannon physics
-+ double margin = this.physics.afterOrEqual(1_21_2) ? 1.0E-5f : this.physics.afterOrEqual(1_19_3) ? 1.0E-7 : 0.001;
++ final double margin;
++ if (this.mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_21_2)) {
++ margin = 1.0e-5f;
++ } else if (this.mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_19_3)) {
++ margin = 1.0e-7f;
++ } else {
++ margin = 0.001;
++ }
+ AABB aabb = this.makeBoundingBox(vec31).deflate(margin);
+ // Sakura end - configure cannon physics
BlockGetter.forEachBlockIntersectedBetween(
vec3,
vec31,
aabb,
-+ this.physics, // Sakura - configure cannon physics
++ this.mechanicsTarget, // Sakura - configure cannon physics
(pos, index) -> {
if (!this.isAlive()) {
return false;
diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java
-index 0b4cb90a6877c2af4ef1eccd502b50c863bf74b0..fb3f28ee3b018462f2274e997d540029560df8d0 100644
+index 0b4cb90a6877c2af4ef1eccd502b50c863bf74b0..33e8073651099d6a3d82fe0886424d5aa3886c5d 100644
--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
-@@ -130,6 +130,43 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
+@@ -130,6 +130,25 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
return this.sakura_collide(movement);
}
// Sakura end - optimise cannon entity movement
+ // Sakura start - configure cannon physics
+ @Override
-+ public final double distanceToSqr(Vec3 vector) {
-+ if (!this.physics.isLegacy())
++ public final double distanceToSqr(final Vec3 vector) {
++ if (!this.mechanicsTarget.isLegacy()) {
+ return super.distanceToSqr(vector);
-+ double x = this.getX() - vector.x;
-+ double y = this.getEyeY() - vector.y;
-+ double z = this.getZ() - vector.z;
++ }
++ final double x = this.getX() - vector.x;
++ final double y = this.getEyeY() - vector.y;
++ final double z = this.getZ() - vector.z;
+ return x * x + y * y + z * z;
+ }
+
+ private BlockPos patchedBlockPosition() {
-+ // mitigate the floating point issue for sand breaking below y-0
-+ // 1.0e-12 allows tech that uses indirect collision clipping to still function
++ // Mitigates MC-261789 - falling blocks breaking when landing on a block below y 0
++ // This fix allows some tech that uses "indirect clipping" to still function.
++ // L stackers and midairs may still encounter issues if they shoot too high.
+ return BlockPos.containing(this.getX(), this.getY() + 1.0e-12, this.getZ());
+ }
-+
-+ private boolean isAbleToStackOnBlock() {
-+ BlockPos pos = BlockPos.containing(this.getX(), this.getY() - 0.001f, this.getZ());
-+ BlockState state = this.level().getBlockState(pos);
-+ return !FallingBlock.isFree(state);
-+ }
-+
-+ private void removeBlockOnFall(Block block) {
-+ BlockPos blockposition = this.blockPosition();
-+ BlockState blockstate = this.level().getBlockState(blockposition);
-+
-+ if (blockstate.is(block) && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState())) {
-+ this.level().removeBlock(blockposition, false);
-+ } else {
-+ if (blockstate.is(block)) {
-+ ((ServerLevel) this.level()).getChunkSource().blockChanged(blockposition);
-+ }
-+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN);
-+ }
-+ }
+ // Sakura end - configure cannon physics
public FallingBlockEntity(EntityType extends FallingBlockEntity> entityType, Level level) {
super(entityType, level);
-@@ -150,6 +187,10 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
+@@ -150,6 +169,10 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
this.yo = y;
this.zo = z;
this.setStartPos(this.blockPosition());
+ // Sakura start - configure cannon physics
-+ this.physics = level.localConfig().config(this.blockPosition()).physicsVersion;
-+ this.eyeHeight = this.physics.isLegacy() ? 0.49f : this.eyeHeight;
++ this.mechanicsTarget = level.localConfig().at(this.blockPosition()).mechanicsTarget;
++ this.eyeHeight = this.mechanicsTarget.isLegacy() ? 0.49f : this.eyeHeight;
+ // Sakura end - configure cannon physics
}
// Sakura start - falling block height parity api
-@@ -182,7 +223,11 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
+@@ -182,7 +205,11 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
blockState.hasProperty(BlockStateProperties.WATERLOGGED) ? blockState.setValue(BlockStateProperties.WATERLOGGED, false) : blockState
);
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(fallingBlockEntity, pos, blockState.getFluidState().createLegacyBlock())) return fallingBlockEntity; // CraftBukkit
- level.setBlock(pos, blockState.getFluidState().createLegacyBlock(), 3);
+ // Sakura start - configure cannon physics
-+ if (fallingBlockEntity.physics.afterOrEqual(1_18_2)) {
++ if (fallingBlockEntity.mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_18_2)) {
+ level.setBlock(pos, blockState.getFluidState().createLegacyBlock(), 3);
+ }
+ // Sakura end - configure cannon physics
level.addFreshEntity(fallingBlockEntity);
return fallingBlockEntity;
}
-@@ -226,7 +271,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
+@@ -226,7 +253,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
@Override
protected double getDefaultGravity() {
- return 0.04;
-+ return this.physics.before(1_14_0) ? 0.04f : 0.04; // Sakura - configure cannon physics
++ return this.mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_14) ? 0.04f : 0.04; // Sakura - configure cannon physics
}
@Override
-@@ -235,6 +280,12 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
+@@ -235,6 +262,12 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
} else {
Block block = this.blockState.getBlock();
+ // Sakura start - configure cannon physics
-+ final me.samsuik.sakura.physics.PhysicsVersion physics = this.physics;
-+ if (this.time == 0 && physics.before(1_18_2)) {
-+ this.removeBlockOnFall(block);
++ final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget = this.mechanicsTarget;
++ if (this.time == 0 && mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_18_2)) {
++ me.samsuik.sakura.mechanics.FallingBlockBehaviour.removeBlockOnFall(this, block);
+ }
+ // Sakura end - configure cannon physics
this.time++;
this.applyGravity();
this.move(MoverType.SELF, this.getDeltaMovement());
-@@ -249,8 +300,15 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
+@@ -249,8 +282,15 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
}
// Paper end - Configurable falling blocks height nerf
this.handlePortal();
+ // Sakura start - configure cannon physics
-+ if (physics.before(1_12_0)) {
++ if (mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_12)) {
+ this.setDeltaMovement(this.getDeltaMovement().scale(0.98F));
+ }
if (this.level() instanceof ServerLevel serverLevel && (this.isAlive() || this.forceTickAfterTeleportToDuplicate)) {
- BlockPos blockPos = this.blockPosition();
-+ // Patching the floating point issue on modern versions can break some cannons that rely on it.
-+ // However, it makes sense for legacy versions pre-1.17 before the world height change.
-+ BlockPos blockPos = physics.before(1_17_0) ? this.patchedBlockPosition() : this.blockPosition();
++ BlockPos blockPos = mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_17)
++ ? this.patchedBlockPosition() // Mitigate MC-261789
++ : this.blockPosition();
+ // Sakura end - configure cannon physics
boolean flag = this.level().sakuraConfig().cannons.sand.concreteSolidifyInWater && this.blockState.getBlock() instanceof ConcretePowderBlock; // Sakura - configure concrete solidifying in water
boolean flag1 = flag && this.level().getFluidState(blockPos).is(FluidTags.WATER);
double d = this.getDeltaMovement().lengthSqr();
-@@ -277,8 +335,11 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
+@@ -277,8 +317,13 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
}
} else {
BlockState blockState = this.level().getBlockState(blockPos);
- this.setDeltaMovement(this.getDeltaMovement().multiply(0.7, -0.5, 0.7));
- if (!blockState.is(Blocks.MOVING_PISTON)) {
+ // Sakura start - configure cannon physics
-+ final double friction = physics.before(1_14_0) ? 0.7f : 0.7;
++ final double friction = mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_14)
++ ? 0.7f
++ : 0.7;
+ this.setDeltaMovement(this.getDeltaMovement().multiply(friction, -0.5, friction));
-+ if (!blockState.is(Blocks.MOVING_PISTON) && (flag1 || !physics.isWithin(1_9_0, 1_12_0) || this.isAbleToStackOnBlock())) {
++ if (!blockState.is(Blocks.MOVING_PISTON) && (flag1 || me.samsuik.sakura.mechanics.FallingBlockBehaviour.isAbleToStackOnBlock(this, mechanicsTarget))) {
+ // Sakura end - configure cannon physics
if (!this.cancelDrop) {
boolean canBeReplaced = blockState.canBeReplaced(
new DirectionalPlaceContext(this.level(), blockPos, Direction.DOWN, ItemStack.EMPTY, Direction.UP)
-@@ -351,7 +412,12 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
+@@ -351,7 +396,14 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
}
}
- this.setDeltaMovement(this.getDeltaMovement().scale(0.98));
+ // Sakura start - configure cannon physics
-+ if (physics.afterOrEqual(1_12_0)) {
-+ final double drag = physics.before(1_14_0) ? 0.98f : 0.98;
++ if (mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_12)) {
++ final double drag = mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_14)
++ ? 0.98f
++ : 0.98;
+ this.setDeltaMovement(this.getDeltaMovement().scale(drag));
+ }
+ // Sakura end - configure cannon physics
@@ -358,67 +328,72 @@ index 0b4cb90a6877c2af4ef1eccd502b50c863bf74b0..fb3f28ee3b018462f2274e997d540029
}
diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java
-index 9a4b9be72e896ad4def7caa89716c707cfb5cc06..9d563a447d00b3ef53de48582d55abe4eb954182 100644
+index 9a4b9be72e896ad4def7caa89716c707cfb5cc06..c36d00d8d96f279dfd725df91a24a28894a06250 100644
--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
-@@ -88,6 +88,22 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
+@@ -88,6 +88,23 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
return this.sakura_collide(movement);
}
// Sakura end - optimise cannon entity movement
+ // Sakura start - configure cannon physics
+ @Override
+ public final double getEyeY() {
-+ return this.physics.isLegacy() ? super.getEyeY() : this.getY();
++ return this.mechanicsTarget.isLegacy() ? super.getEyeY() : this.getY();
+ }
+
+ @Override
-+ public final double distanceToSqr(net.minecraft.world.phys.Vec3 vector) {
-+ if (!this.physics.isLegacy())
++ public final double distanceToSqr(final net.minecraft.world.phys.Vec3 vector) {
++ if (!this.mechanicsTarget.isLegacy()) {
+ return super.distanceToSqr(vector);
-+ double x = this.getX() - vector.x;
-+ double y = this.getEyeY() - vector.y;
-+ double z = this.getZ() - vector.z;
++ }
++ final double x = this.getX() - vector.x;
++ final double y = this.getEyeY() - vector.y;
++ final double z = this.getZ() - vector.z;
+ return x * x + y * y + z * z;
+ }
+ // Sakura end - configure cannon physics
public PrimedTnt(EntityType extends PrimedTnt> entityType, Level level) {
super(entityType, level);
-@@ -113,6 +129,13 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
+@@ -113,6 +130,13 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
case Y -> this.setDeltaMovement(this.getDeltaMovement().multiply(0.0, 1.0, 0.0));
}
// Sakura end - configure cannon mechanics
+ // Sakura start - configure cannon physics
-+ this.physics = level.localConfig().config(this.blockPosition()).physicsVersion;
-+ this.eyeHeight = this.physics.isLegacy() ? 0.49f : this.eyeHeight;
-+ if (this.physics.isLegacy()) {
++ this.mechanicsTarget = level.localConfig().at(this.blockPosition()).mechanicsTarget;
++ this.eyeHeight = this.mechanicsTarget.isLegacy() ? 0.49f : this.eyeHeight;
++ if (this.mechanicsTarget.isLegacy()) {
+ this.setDeltaMovement(this.getDeltaMovement().multiply(0.0, 1.0, 0.0));
+ }
+ // Sakura end - configure cannon physics
}
// Sakura start - optimise tnt fluid state
-@@ -148,7 +171,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
+@@ -148,7 +172,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
@Override
protected double getDefaultGravity() {
- return 0.04;
-+ return this.physics.before(1_14_0) ? 0.04f : 0.04; // Sakura - configure cannon physics
++ return this.mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_14) ? 0.04f : 0.04; // Sakura - configure cannon physics
}
@Override
-@@ -164,14 +187,19 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
+@@ -164,14 +188,23 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
return;
}
// Paper end - Configurable TNT height nerf
- this.setDeltaMovement(this.getDeltaMovement().scale(0.98));
+ // Sakura start - configure cannon physics
-+ final me.samsuik.sakura.physics.PhysicsVersion physics = this.physics;
-+ final double drag = physics.before(1_14_0) ? 0.98f : 0.98;
++ final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget = this.mechanicsTarget;
++ final double drag = mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_14)
++ ? 0.98f
++ : 0.98;
+ this.setDeltaMovement(this.getDeltaMovement().scale(drag));
if (this.onGround()) {
- this.setDeltaMovement(this.getDeltaMovement().multiply(0.7, -0.5, 0.7));
-+ final double friction = physics.before(1_14_0) ? 0.7f : 0.7;
++ final double friction = mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_14)
++ ? 0.7f
++ : 0.7;
+ this.setDeltaMovement(this.getDeltaMovement().multiply(friction, -0.5, friction));
+ // Sakura end - configure cannon physics
}
@@ -426,15 +401,20 @@ index 9a4b9be72e896ad4def7caa89716c707cfb5cc06..9d563a447d00b3ef53de48582d55abe4
int i = this.getFuse() - 1;
this.setFuse(i);
- if (i <= 0) {
-+ if (physics.before(1_9_0) ? (i < 0) : (i <= 0)) { // Sakura - configure cannon physics
++ if (mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_9) ? (i < 0) : (i <= 0)) { // Sakura - configure cannon physics
// CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event
//this.discard();
this.tryToRespawnEntity(); // Sakura - merge cannon entities
-@@ -210,13 +238,14 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
+@@ -210,13 +243,19 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
return;
}
// CraftBukkit end
-+ final double explosionY = this.physics.before(1_10_0) ? this.getY() + (double) 0.49f : this.getY(0.0625D); // Sakura - configure cannon physics
++ // Sakura start - configure cannon physics
++ // Undocumented on the minecraft wiki but the explosion position did change in 1.10
++ final double explosionY = this.mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_10)
++ ? this.getY() + (double) 0.49f
++ : this.getY(0.0625D);
++ // Sakura end - configure cannon physics
this.level()
.explode(
this,
@@ -446,20 +426,20 @@ index 9a4b9be72e896ad4def7caa89716c707cfb5cc06..9d563a447d00b3ef53de48582d55abe4
this.getZ(),
event.getRadius(), // CraftBukkit
event.getFire(), // CraftBukkit
-@@ -307,7 +336,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
+@@ -307,7 +346,7 @@ public class PrimedTnt extends Entity implements TraceableEntity, me.samsuik.sak
// Paper start - Option to prevent TNT from moving in water
@Override
public boolean isPushedByFluid() {
- return !this.level().paperConfig().fixes.preventTntFromMovingInWater && this.level().sakuraConfig().cannons.mechanics.tntFlowsInWater && super.isPushedByFluid(); // Sakura - configure cannon mechanics
-+ return !this.level().paperConfig().fixes.preventTntFromMovingInWater && this.level().sakuraConfig().cannons.mechanics.tntFlowsInWater && !this.physics.isLegacy() && super.isPushedByFluid(); // Sakura - configure cannon physics // Sakura - configure cannon mechanics
++ return !this.level().paperConfig().fixes.preventTntFromMovingInWater && this.level().sakuraConfig().cannons.mechanics.tntFlowsInWater && !this.mechanicsTarget.isLegacy() && super.isPushedByFluid(); // Sakura - configure cannon physics // Sakura - configure cannon mechanics
}
// Paper end - Option to prevent TNT from moving in water
}
diff --git a/net/minecraft/world/level/BlockGetter.java b/net/minecraft/world/level/BlockGetter.java
-index 2146efa860d8323a88f3ad365c0cdb66de42154a..67c9393133f4509abf1bd352fbbc8e21dbb116e2 100644
+index 2146efa860d8323a88f3ad365c0cdb66de42154a..353ad0c1db2ee374ac5487539c77b2b067dc7c0a 100644
--- a/net/minecraft/world/level/BlockGetter.java
+++ b/net/minecraft/world/level/BlockGetter.java
-@@ -213,8 +213,14 @@ public interface BlockGetter extends LevelHeightAccessor {
+@@ -213,8 +213,20 @@ public interface BlockGetter extends LevelHeightAccessor {
}
static boolean forEachBlockIntersectedBetween(Vec3 from, Vec3 to, AABB boundingBox, BlockGetter.BlockStepVisitor visitor) {
@@ -467,55 +447,63 @@ index 2146efa860d8323a88f3ad365c0cdb66de42154a..67c9393133f4509abf1bd352fbbc8e21
+ return forEachBlockIntersectedBetween(from, to, boundingBox, null, visitor);
+ }
+
-+ static boolean forEachBlockIntersectedBetween(Vec3 from, Vec3 to, AABB boundingBox, me.samsuik.sakura.physics.PhysicsVersion physics, BlockGetter.BlockStepVisitor visitor) {
++ static boolean forEachBlockIntersectedBetween(
++ final Vec3 from,
++ final Vec3 to,
++ final AABB boundingBox,
++ final @Nullable me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget,
++ final BlockGetter.BlockStepVisitor visitor
++ ) {
Vec3 vec3 = to.subtract(from);
- if (vec3.lengthSqr() < Mth.square(0.99999F)) {
-+ if (physics != null && physics.before(1_21_2) || vec3.lengthSqr() < Mth.square(0.99999F)) {
++ if (vec3.lengthSqr() < Mth.square(0.99999F) || mechanicsTarget != null && mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_21_2)) {
+ // Sakura end - configure cannon physics
for (BlockPos blockPos : BlockPos.betweenClosed(boundingBox)) {
if (!visitor.visit(blockPos, 0)) {
return false;
diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
-index ad27005c70cfb48eba05a2cdc42d45d626774999..82ed7843e415595df83c3fb74876ed81d6e63db4 100644
+index 0664de8feb17805647b7de186bd4a536b17d5aa7..4d62f6bf353d2c23d0b8918b728b442bfb150628 100644
--- a/net/minecraft/world/level/ServerExplosion.java
+++ b/net/minecraft/world/level/ServerExplosion.java
@@ -409,6 +409,7 @@ public class ServerExplosion implements Explosion {
return this.damageCalculator.getBlockExplosionResistance(this, this.level, pos, blockState, fluidState);
}
// Sakura end - explosion durable blocks
-+ protected final me.samsuik.sakura.physics.PhysicsVersion physics; // Sakura - configure cannon physics
++ protected final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget; // Sakura - configure cannon physics
public ServerExplosion(
ServerLevel level,
@@ -433,6 +434,7 @@ public class ServerExplosion implements Explosion {
this.yield = Double.isFinite(this.yield) ? this.yield : 0; // Paper - Don't allow infinite default yields
// Paper end - add yield
- this.consistentRadius = level.localConfig().config(BlockPos.containing(this.center)).consistentRadius; // Sakura - consistent explosion radius
-+ this.physics = source != null ? source.physics() : level.localConfig().config(BlockPos.containing(this.center)).physicsVersion; // Sakura - configure cannon physics
+ this.consistentExplosionRadius = level.localConfig().at(this.center).consistentExplosionRadius; // Sakura - consistent explosion radius
++ this.mechanicsTarget = source != null ? source.mechanicsTarget() : level.localConfig().at(this.center).mechanicsTarget; // Sakura - configure cannon physics
}
private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
-@@ -462,8 +464,13 @@ public class ServerExplosion implements Explosion {
+@@ -462,8 +464,15 @@ public class ServerExplosion implements Explosion {
final float density = entity.level().densityCache.getKnownDensity(vec3);
if (density != me.samsuik.sakura.explosion.density.BlockDensityCache.UNKNOWN_DENSITY) {
hitResult = density != 0.0f ? net.minecraft.world.phys.HitResult.Type.MISS : net.minecraft.world.phys.HitResult.Type.BLOCK;
+ // Sakura start - configure cannon physics
-+ } else if (entity.physics().before(1_14_0)) {
-+ hitResult = me.samsuik.sakura.explosion.LegacyExplosionClipping.clipLegacy(entity.level(), vec3, explosionVector);
++ } else if (entity.mechanicsTarget().before(me.samsuik.sakura.mechanics.MechanicVersion.v1_14)) {
++ hitResult = me.samsuik.sakura.mechanics.LegacyExplosionBlockClipping.clip(entity.level(), vec3, explosionVector);
} else {
- hitResult = entity.level().clip(new ClipContext(vec3, explosionVector, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, entity)).getType();
-+ final ClipContext.Block blockContext = entity.physics().afterOrEqual(1_16_0) ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE;
++ final ClipContext.Block blockContext = entity.mechanicsTarget().atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_16)
++ ? ClipContext.Block.COLLIDER
++ : ClipContext.Block.OUTLINE;
+ hitResult = entity.level().clip(new ClipContext(vec3, explosionVector, blockContext, ClipContext.Fluid.NONE, entity)).getType();
+ // Sakura end - configure cannon physics
}
if (hitResult == HitResult.Type.MISS) {
// Sakura end - replace density cache
-@@ -561,6 +568,15 @@ public class ServerExplosion implements Explosion {
+@@ -561,6 +570,15 @@ public class ServerExplosion implements Explosion {
}
if (cachedBlock.outOfWorld) {
+ // Sakura start - configure cannon physics
-+ if (this.physics.before(1_17_0)) {
++ if (this.mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_17)) {
+ power -= 0.22500001F;
+ currX += incX;
+ currY += incY;
@@ -526,12 +514,12 @@ index ad27005c70cfb48eba05a2cdc42d45d626774999..82ed7843e415595df83c3fb74876ed81
break;
}
final BlockState iblockdata = cachedBlock.blockState;
-@@ -656,6 +672,12 @@ public class ServerExplosion implements Explosion {
+@@ -656,6 +674,12 @@ public class ServerExplosion implements Explosion {
double d2 = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - this.center.y;
double d3 = entity.getZ() - this.center.z;
double squareRoot = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);
+ // Sakura start - configure cannon physics
-+ if (this.physics.before(1_17_0)) {
++ if (this.mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_17)) {
+ d = (float) d;
+ squareRoot = (float) squareRoot;
+ }
@@ -539,7 +527,7 @@ index ad27005c70cfb48eba05a2cdc42d45d626774999..82ed7843e415595df83c3fb74876ed81
if (squareRoot != 0.0) {
d1 /= squareRoot;
d2 /= squareRoot;
-@@ -939,7 +961,7 @@ public class ServerExplosion implements Explosion {
+@@ -939,7 +963,7 @@ public class ServerExplosion implements Explosion {
// Sakura start - replace density cache
float blockDensity = this.level.densityCache.getDensity(vec3d, entity);
if (blockDensity == me.samsuik.sakura.explosion.density.BlockDensityCache.UNKNOWN_DENSITY) {
@@ -548,13 +536,13 @@ index ad27005c70cfb48eba05a2cdc42d45d626774999..82ed7843e415595df83c3fb74876ed81
this.level.densityCache.putDensity(vec3d, entity, blockDensity);
// Sakura end - replace density cache
}
-@@ -947,6 +969,16 @@ public class ServerExplosion implements Explosion {
+@@ -947,6 +971,16 @@ public class ServerExplosion implements Explosion {
return blockDensity;
}
+ // Sakura start - configure cannon physics
+ private float sakura_getSeenPercent(Vec3 vec3d, Entity entity) {
-+ if (this.physics.afterOrEqual(1_16_0)) {
++ if (this.mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_16)) {
+ return this.getSeenFraction(vec3d, entity, this.directMappedBlockCache, this.mutablePos); // Paper - collision optimisations
+ } else {
+ return getSeenPercent(vec3d, entity);
@@ -566,18 +554,25 @@ index ad27005c70cfb48eba05a2cdc42d45d626774999..82ed7843e415595df83c3fb74876ed81
private final Level world;
private final double posX, posY, posZ;
diff --git a/net/minecraft/world/level/block/FallingBlock.java b/net/minecraft/world/level/block/FallingBlock.java
-index 4fa238d1cd6b19f16c0d0a8a9a913e9e42debbed..4f9626be4e97483259f569af01aa05fc0860f87a 100644
+index 4fa238d1cd6b19f16c0d0a8a9a913e9e42debbed..235dbd5289165f9858727bee47a539e8d8b4eb15 100644
--- a/net/minecraft/world/level/block/FallingBlock.java
+++ b/net/minecraft/world/level/block/FallingBlock.java
-@@ -45,6 +45,15 @@ public abstract class FallingBlock extends Block implements Fallable {
+@@ -45,6 +45,22 @@ public abstract class FallingBlock extends Block implements Fallable {
return super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
}
+ // Sakura start - configure cannon physics
+ @Override
-+ public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, net.minecraft.world.level.redstone.Orientation wireOrientation, boolean notify) {
-+ if (world.localConfig().config(pos).physicsVersion.before(1_18_2)) {
-+ world.scheduleTick(pos, this, this.getDelayAfterPlace());
++ public void neighborChanged(
++ final BlockState state,
++ final Level level,
++ final BlockPos pos,
++ final Block sourceBlock,
++ final net.minecraft.world.level.redstone.Orientation wireOrientation,
++ final boolean notify
++ ) {
++ if (level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_18_2)) {
++ level.scheduleTick(pos, this, this.getDelayAfterPlace());
+ }
+ }
+ // Sakura end - configure cannon physics
@@ -586,28 +581,29 @@ index 4fa238d1cd6b19f16c0d0a8a9a913e9e42debbed..4f9626be4e97483259f569af01aa05fc
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (isFree(level.getBlockState(pos.below())) && pos.getY() >= level.getMinY()) {
diff --git a/net/minecraft/world/level/block/FenceGateBlock.java b/net/minecraft/world/level/block/FenceGateBlock.java
-index a5e686b90e532e3b656fca411936499c2b2020c7..0644841408cae93fe6175a7b9a01980fa9e5c140 100644
+index a5e686b90e532e3b656fca411936499c2b2020c7..5a699e581dd7bd89b14b4c27996740c93240ba80 100644
--- a/net/minecraft/world/level/block/FenceGateBlock.java
+++ b/net/minecraft/world/level/block/FenceGateBlock.java
-@@ -210,8 +210,14 @@ public class FenceGateBlock extends HorizontalDirectionalBlock {
+@@ -210,8 +210,15 @@ public class FenceGateBlock extends HorizontalDirectionalBlock {
hasNeighborSignal = eventRedstone.getNewCurrent() > 0;
}
// CraftBukkit end
- if (state.getValue(POWERED) != hasNeighborSignal) {
- level.setBlock(pos, state.setValue(POWERED, hasNeighborSignal).setValue(OPEN, hasNeighborSignal), 2);
+ // Sakura start - configure cannon physics
-+ final boolean legacy = level.localConfig().config(pos).physicsVersion.before(1_11_0);
++ final boolean pre1_10_0 = level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_10);
+ final boolean powered = state.getValue(POWERED);
-+ if (legacy ? (hasNeighborSignal || neighborBlock.defaultBlockState().isSignalSource()) : powered != hasNeighborSignal) {
-+ final boolean openGate = legacy && (hasNeighborSignal == powered || state.getValue(OPEN) != powered)
-+ ? state.getValue(OPEN) : hasNeighborSignal;
++ if (pre1_10_0 ? (hasNeighborSignal || neighborBlock.defaultBlockState().isSignalSource()) : powered != hasNeighborSignal) {
++ final boolean openGate = pre1_10_0 && (hasNeighborSignal == powered || state.getValue(OPEN) != powered)
++ ? state.getValue(OPEN)
++ : hasNeighborSignal;
+ level.setBlock(pos, state.setValue(POWERED, hasNeighborSignal).setValue(OPEN, openGate), 2);
+ // Sakura end - configure cannon physics
if (state.getValue(OPEN) != hasNeighborSignal) {
level.playSound(
null,
diff --git a/net/minecraft/world/level/block/HoneyBlock.java b/net/minecraft/world/level/block/HoneyBlock.java
-index a98c308c5febd458d6489174b94898cd4b9bae69..adc022ccfd3ad6e6372e25fe0b6a21c4d486b08c 100644
+index a98c308c5febd458d6489174b94898cd4b9bae69..53de53eaba377cc25204eb11dafbbf87fdde8aa2 100644
--- a/net/minecraft/world/level/block/HoneyBlock.java
+++ b/net/minecraft/world/level/block/HoneyBlock.java
@@ -71,11 +71,19 @@ public class HoneyBlock extends HalfTransparentBlock {
@@ -616,16 +612,16 @@ index a98c308c5febd458d6489174b94898cd4b9bae69..adc022ccfd3ad6e6372e25fe0b6a21c4
- private static double getOldDeltaY(double deltaY) {
+ // Sakura start - configure cannon physics
-+ private static double getOldDeltaY(double deltaY, Entity entity) {
-+ if (entity.physics().before(1_21_2)) {
++ private static double getOldDeltaY(final double deltaY, final Entity entity) {
++ if (entity.mechanicsTarget().before(me.samsuik.sakura.mechanics.MechanicVersion.v1_21_2)) {
+ return deltaY;
+ }
return deltaY / 0.98F + 0.08;
}
- private static double getNewDeltaY(double deltaY) {
-+ private static double getNewDeltaY(double deltaY, Entity entity) {
-+ if (entity.physics().before(1_21_2)) {
++ private static double getNewDeltaY(final double deltaY, final Entity entity) {
++ if (entity.mechanicsTarget().before(me.samsuik.sakura.mechanics.MechanicVersion.v1_21_2)) {
+ return deltaY;
+ }
+ // Sakura end - configure cannon physics
@@ -660,7 +656,7 @@ index a98c308c5febd458d6489174b94898cd4b9bae69..adc022ccfd3ad6e6372e25fe0b6a21c4
entity.resetFallDistance();
diff --git a/net/minecraft/world/level/block/LadderBlock.java b/net/minecraft/world/level/block/LadderBlock.java
-index f9c305de60a323b450a26c9d7de50a824492cf5a..d40879dc8b8d7366a7dfd5e67630c9472f23f68a 100644
+index f9c305de60a323b450a26c9d7de50a824492cf5a..8ff7887089d217464649142f72cc7f2e901f9dd7 100644
--- a/net/minecraft/world/level/block/LadderBlock.java
+++ b/net/minecraft/world/level/block/LadderBlock.java
@@ -33,6 +33,15 @@ public class LadderBlock extends Block implements SimpleWaterloggedBlock {
@@ -684,7 +680,7 @@ index f9c305de60a323b450a26c9d7de50a824492cf5a..d40879dc8b8d7366a7dfd5e67630c947
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
+ // Sakura start - configure cannon physics
-+ if (level instanceof net.minecraft.world.level.Level gameLevel && gameLevel.localConfig().config(pos).physicsVersion.before(1_9_0)) {
++ if (level instanceof net.minecraft.world.level.Level gameLevel && gameLevel.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_9)) {
+ return LEGACY_SHAPES.get(state.getValue(FACING));
+ }
+ // Sakura end - configure cannon physics
@@ -692,10 +688,10 @@ index f9c305de60a323b450a26c9d7de50a824492cf5a..d40879dc8b8d7366a7dfd5e67630c947
}
diff --git a/net/minecraft/world/level/block/LiquidBlock.java b/net/minecraft/world/level/block/LiquidBlock.java
-index 4dbbfa34c085fd9777de5b4a6bf48dedfe8603b8..1e8574d7900ffde16c2e1ee9f92a77c47c85af61 100644
+index 4dbbfa34c085fd9777de5b4a6bf48dedfe8603b8..e6091c252f252373481f46a3ef119ddc32baddf0 100644
--- a/net/minecraft/world/level/block/LiquidBlock.java
+++ b/net/minecraft/world/level/block/LiquidBlock.java
-@@ -195,7 +195,20 @@ public class LiquidBlock extends Block implements BucketPickup {
+@@ -195,7 +195,14 @@ public class LiquidBlock extends Block implements BucketPickup {
}
// Sakura end - configure fluid ticking outside the world border
if (level.getFluidState(blockPos).is(FluidTags.WATER)) {
@@ -703,37 +699,29 @@ index 4dbbfa34c085fd9777de5b4a6bf48dedfe8603b8..1e8574d7900ffde16c2e1ee9f92a77c4
+ // Sakura start - configure cannon physics
+ final FluidState fluidState = state.getFluidState();
+ final Block block = fluidState.isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE;
-+ if (block == Blocks.COBBLESTONE) {
-+ final me.samsuik.sakura.physics.PhysicsVersion physics = level.localConfig().config(pos).physicsVersion;
-+
-+ // SANITY: In legacy a patch by paper removes the fluid level condition from vanilla.
-+ if (physics.before(1_16_0) && !physics.isLegacy() &&
-+ (physics.before(1_13_0) || !(fluidState.getHeight(level, pos) >= 0.44444445f)) &&
-+ (physics.afterOrEqual(1_13_0) || FlowingFluid.getLegacyLevel(fluidState) > 4)) {
-+ return true;
-+ }
++ final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget = level.localConfig().at(pos).mechanicsTarget;
++ if (block == Blocks.COBBLESTONE && !me.samsuik.sakura.mechanics.LiquidBehaviour.canLiquidSolidify(level, pos, fluidState, mechanicsTarget)) {
++ return true;
+ }
+ // Sakura end - configure cannon physics
// CraftBukkit start
if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState(), 3)) {
this.fizz(level, pos);
diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java
-index e76b6c44de16f4bf136bc9959f1eedae1492499a..270c405a7384e3290b4eea58e0b231aa6235d85a 100644
+index a9db955a90e0b44d3c85e39f2f7ae9ea4f68a316..16f69f37bca2304ef032370c0ce991a682c345bc 100644
--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
+++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
-@@ -544,6 +544,10 @@ public class RedStoneWireBlock extends Block {
+@@ -542,7 +542,7 @@ public class RedStoneWireBlock extends Block {
+
+ @Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
- if (!player.getAbilities().mayBuild) {
+- if (!player.getAbilities().mayBuild) {
++ if (!player.getAbilities().mayBuild || level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_16)) { // Sakura - configure cannon physics
return InteractionResult.PASS;
-+ // Sakura start - configure cannon physics
-+ } else if (level.localConfig().config(pos).physicsVersion.before(1_16_0)) {
-+ return InteractionResult.PASS;
-+ // Sakura end - configure cannon physics
} else {
if (isCross(state) || isDot(state)) {
- BlockState blockState = isCross(state) ? this.defaultBlockState() : this.crossState;
diff --git a/net/minecraft/world/level/block/WaterlilyBlock.java b/net/minecraft/world/level/block/WaterlilyBlock.java
-index 3b3047aa1198754e64913634f76fdc015c1fe07d..0fe59a797f4ff1462a72492e6ffe32df50607756 100644
+index 3b3047aa1198754e64913634f76fdc015c1fe07d..f8e2472430e4e3b79b3459d1c2de88c434a53914 100644
--- a/net/minecraft/world/level/block/WaterlilyBlock.java
+++ b/net/minecraft/world/level/block/WaterlilyBlock.java
@@ -24,6 +24,15 @@ public class WaterlilyBlock extends VegetationBlock {
@@ -757,7 +745,7 @@ index 3b3047aa1198754e64913634f76fdc015c1fe07d..0fe59a797f4ff1462a72492e6ffe32df
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
+ // Sakura start - configure cannon physics
-+ if (level instanceof net.minecraft.world.level.Level gameLevel && gameLevel.localConfig().config(pos).physicsVersion.before(1_9_0)) {
++ if (level instanceof net.minecraft.world.level.Level gameLevel && gameLevel.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_9)) {
+ return LEGACY_SHAPE;
+ }
+ // Sakura end - configure cannon physics
@@ -765,7 +753,7 @@ index 3b3047aa1198754e64913634f76fdc015c1fe07d..0fe59a797f4ff1462a72492e6ffe32df
}
diff --git a/net/minecraft/world/level/block/piston/MovingPistonBlock.java b/net/minecraft/world/level/block/piston/MovingPistonBlock.java
-index 05bbc2e59384702439548a988e128a85f1adbe82..7d84bb8b2e155785152d58ed39af0c3328c30203 100644
+index 05bbc2e59384702439548a988e128a85f1adbe82..9c5ebb269fd434055de901bd058482b913f8c283 100644
--- a/net/minecraft/world/level/block/piston/MovingPistonBlock.java
+++ b/net/minecraft/world/level/block/piston/MovingPistonBlock.java
@@ -100,6 +100,16 @@ public class MovingPistonBlock extends BaseEntityBlock {
@@ -773,7 +761,7 @@ index 05bbc2e59384702439548a988e128a85f1adbe82..7d84bb8b2e155785152d58ed39af0c33
protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
PistonMovingBlockEntity blockEntity = this.getBlockEntity(level, pos);
+ // Sakura start - configure cannon physics
-+ if (blockEntity != null && level instanceof Level gameLevel && gameLevel.localConfig().config(pos).physicsVersion.before(1_9_0)) {
++ if (blockEntity != null && level instanceof Level gameLevel && gameLevel.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_9)) {
+ final VoxelShape shape = blockEntity.getCollisionShapeFromProgress(level, pos);
+ if (context.isAbove(shape, pos, false)) {
+ return shape;
@@ -786,7 +774,7 @@ index 05bbc2e59384702439548a988e128a85f1adbe82..7d84bb8b2e155785152d58ed39af0c33
}
diff --git a/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/net/minecraft/world/level/block/piston/PistonBaseBlock.java
-index c372c9f828f52af0d31cc9d20c00359fdb2a610a..3bf8045f77216d06683e749c35733969be5b37b3 100644
+index c372c9f828f52af0d31cc9d20c00359fdb2a610a..cae08bc784f76e248573e996675beebf02ba05b0 100644
--- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java
@@ -122,6 +122,11 @@ public class PistonBaseBlock extends DirectionalBlock {
@@ -794,95 +782,115 @@ index c372c9f828f52af0d31cc9d20c00359fdb2a610a..3bf8045f77216d06683e749c35733969
}
+ // Sakura start - configure cannon physics
-+ if (level.localConfig().config(pos).physicsVersion.before(1_9_0)) {
-+ level.setBlock(pos, state.setValue(PistonBaseBlock.EXTENDED, false), 18);
++ if (level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_9)) {
++ level.setBlock(pos, state.setValue(PistonBaseBlock.EXTENDED, false), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE);
+ }
+ // Sakura end - configure cannon physics
level.blockEvent(pos, this, i, direction.get3DDataValue());
}
}
diff --git a/net/minecraft/world/level/block/piston/PistonHeadBlock.java b/net/minecraft/world/level/block/piston/PistonHeadBlock.java
-index 6c789e56f21f01252c21786cfeb48d88485b5636..17ecdee7bd6a8369394115d6534c9d7e9a21e9ee 100644
+index 6c789e56f21f01252c21786cfeb48d88485b5636..e00611295c2b8e560e8ef7c78edbb6f952f6670c 100644
--- a/net/minecraft/world/level/block/piston/PistonHeadBlock.java
+++ b/net/minecraft/world/level/block/piston/PistonHeadBlock.java
-@@ -105,6 +105,11 @@ public class PistonHeadBlock extends DirectionalBlock {
+@@ -3,6 +3,8 @@ package net.minecraft.world.level.block.piston;
+ import com.mojang.serialization.MapCodec;
+ import java.util.Map;
+ import javax.annotation.Nullable;
++
++import me.samsuik.sakura.mechanics.MechanicVersion;
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.Direction;
+ import net.minecraft.server.level.ServerLevel;
+@@ -105,6 +107,11 @@ public class PistonHeadBlock extends DirectionalBlock {
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
BlockState blockState = level.getBlockState(pos.relative(state.getValue(FACING).getOpposite()));
+ // Sakura start - configure cannon physics
-+ if (level instanceof Level gameLevel && gameLevel.localConfig().config(pos).physicsVersion.before(1_9_0)) {
++ if (level instanceof Level gameLevel && gameLevel.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_9)) {
+ return this.isFittingBase(state, blockState);
+ }
+ // Sakura end - configure cannon physics
return this.isFittingBase(state, blockState) || blockState.is(Blocks.MOVING_PISTON) && blockState.getValue(FACING) == state.getValue(FACING);
}
-@@ -116,6 +121,10 @@ public class PistonHeadBlock extends DirectionalBlock {
+@@ -116,6 +123,10 @@ public class PistonHeadBlock extends DirectionalBlock {
neighborBlock,
ExperimentalRedstoneUtils.withFront(orientation, state.getValue(FACING).getOpposite())
);
+ // Sakura start - configure cannon physics
-+ } else if (level.localConfig().config(pos).physicsVersion.before(1_9_0)) {
++ } else if (level.localConfig().at(pos).mechanicsTarget.before(MechanicVersion.v1_9)) {
+ level.setBlock(pos, Blocks.AIR.defaultBlockState(), 19);
+ // Sakura end - configure cannon physics
}
}
diff --git a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
-index e87b685d4a2bc31a1d7a1a31881152abc37563ba..2d812099459b808bc0205e4108ef856a3dba6bbe 100644
+index e87b685d4a2bc31a1d7a1a31881152abc37563ba..583510a65df22926f21e0b7a98c124ae9e7b6454 100644
--- a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
+++ b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
-@@ -64,6 +64,150 @@ public class PistonMovingBlockEntity extends BlockEntity {
+@@ -64,6 +64,163 @@ public class PistonMovingBlockEntity extends BlockEntity {
this.isSourcePiston = isSourcePiston;
}
+ // Sakura start - configure cannon physics
-+ @javax.annotation.Nullable
-+ private AABB getBoundsFromProgress(BlockGetter level, BlockPos pos, BlockState state, float progress, Direction dir, boolean absolute) {
-+ if (!state.is(Blocks.MOVING_PISTON) && !state.isAir()) {
-+ VoxelShape shape = this.movedState.getCollisionShape(level, pos);
-+ // bounds on an empty shape causes an exception
-+ if (shape.isEmpty()) return null;
-+ if (absolute) shape = shape.move(pos.getX(), pos.getY(), pos.getZ());
-+ AABB bounds = shape.bounds();
-+
-+ double minX = bounds.minX;
-+ double minY = bounds.minY;
-+ double minZ = bounds.minZ;
-+ double maxX = bounds.maxX;
-+ double maxY = bounds.maxY;
-+ double maxZ = bounds.maxZ;
-+
-+ if (dir.getStepX() < 0) {
-+ minX -= (float) dir.getStepX() * progress;
-+ } else {
-+ maxX -= (float) dir.getStepX() * progress;
-+ }
-+
-+ if (dir.getStepY() < 0) {
-+ minY -= (float) dir.getStepY() * progress;
-+ } else {
-+ maxY -= (float) dir.getStepY() * progress;
-+ }
-+
-+ if (dir.getStepZ() < 0) {
-+ minZ -= (float) dir.getStepZ() * progress;
-+ } else {
-+ maxZ -= (float) dir.getStepZ() * progress;
-+ }
-+
-+ return this.fixZeroWidthBB(new AABB(minX, minY, minZ, maxX, maxY, maxZ), dir);
++ @org.jspecify.annotations.Nullable
++ private AABB getBoundsFromProgress(
++ final BlockGetter level,
++ final BlockPos pos,
++ final BlockState state,
++ final float progress,
++ final Direction dir,
++ final boolean absolute
++ ) {
++ if (state.is(Blocks.MOVING_PISTON) || state.isAir()) {
++ return null;
+ }
+
-+ return null;
++ VoxelShape shape = this.movedState.getCollisionShape(level, pos);
++ if (shape.isEmpty()) {
++ return null;
++ }
++
++ if (absolute) {
++ shape = shape.move(pos.getX(), pos.getY(), pos.getZ());
++ }
++
++ final AABB bounds = shape.bounds();
++ double minX = bounds.minX;
++ double minY = bounds.minY;
++ double minZ = bounds.minZ;
++ double maxX = bounds.maxX;
++ double maxY = bounds.maxY;
++ double maxZ = bounds.maxZ;
++
++ if (dir.getStepX() < 0) {
++ minX -= (float) dir.getStepX() * progress;
++ } else {
++ maxX -= (float) dir.getStepX() * progress;
++ }
++
++ if (dir.getStepY() < 0) {
++ minY -= (float) dir.getStepY() * progress;
++ } else {
++ maxY -= (float) dir.getStepY() * progress;
++ }
++
++ if (dir.getStepZ() < 0) {
++ minZ -= (float) dir.getStepZ() * progress;
++ } else {
++ maxZ -= (float) dir.getStepZ() * progress;
++ }
++
++ return this.fixZeroWidthBB(new AABB(minX, minY, minZ, maxX, maxY, maxZ), dir);
+ }
+
-+ private AABB fixZeroWidthBB(AABB bb, Direction dir) {
++ private AABB fixZeroWidthBB(AABB bb, final Direction dir) {
+ // Legacy behaviour relied on entities being able to collide with zero width shapes
+ // This is no longer possible, so we have to create a difference here for it to work
-+ double expandX = bb.getXsize() == 0.0 ? 1.0e-5 * dir.getStepX() : 0;
-+ double expandY = bb.getYsize() == 0.0 ? 1.0e-5 * dir.getStepY() : 0;
-+ double expandZ = bb.getZsize() == 0.0 ? 1.0e-5 * dir.getStepZ() : 0;
++ final double expandX = bb.getXsize() == 0.0 ? 1.0e-5 * dir.getStepX() : 0.0;
++ final double expandY = bb.getYsize() == 0.0 ? 1.0e-5 * dir.getStepY() : 0.0;
++ final double expandZ = bb.getZsize() == 0.0 ? 1.0e-5 * dir.getStepZ() : 0.0;
+
+ if (expandX != 0 || expandY != 0 || expandZ != 0) {
+ bb = bb.expandTowards(expandX, expandY, expandZ);
@@ -891,128 +899,130 @@ index e87b685d4a2bc31a1d7a1a31881152abc37563ba..2d812099459b808bc0205e4108ef856a
+ return bb;
+ }
+
-+ public final VoxelShape getCollisionShapeFromProgress(BlockGetter level, BlockPos pos) {
++ public final VoxelShape getCollisionShapeFromProgress(final BlockGetter level, final BlockPos pos) {
+ float progress = this.getProgress(0.0f);
-+
+ if (this.extending) {
+ progress = 1.0F - progress;
+ }
+
-+ AABB bb = this.getBoundsFromProgress(level, pos, this.movedState, progress, this.direction, false);
++ final AABB bb = this.getBoundsFromProgress(level, pos, this.movedState, progress, this.direction, false);
+ return bb == null ? Shapes.empty() : Shapes.create(bb);
+ }
+
-+ private void moveEntities(Level level, float f1) {
-+ float f = this.progress;
-+
++ private void moveEntities(final Level level, final float relativeProgress) {
++ float remaining = this.progress;
+ if (this.extending) {
-+ f = 1.0F - f;
++ remaining = 1.0F - remaining;
+ } else {
-+ --f;
++ --remaining;
+ }
+
-+ AABB bb = this.getBoundsFromProgress(level, this.worldPosition, this.movedState, f, this.direction, true);
-+
++ final Direction direction = this.direction;
++ final AABB bb = this.getBoundsFromProgress(level, this.worldPosition, this.movedState, remaining, direction, true);
+ if (bb == null || bb.getSize() == 0.0) {
+ return;
+ }
+
-+ List entities = level.getEntities(null, bb);
-+
-+ if (entities.isEmpty()) {
-+ return;
-+ }
-+
-+ for (Entity entity : entities) {
++ for (final Entity entity : level.getEntities(null, bb)) {
+ if (this.movedState.is(Blocks.SLIME_BLOCK) && this.extending) {
-+ Vec3 movement = entity.getDeltaMovement();
-+ double x = movement.x;
-+ double y = movement.y;
-+ double z = movement.z;
++ final Vec3 movement = entity.getDeltaMovement();
++ double moveX = movement.x;
++ double moveY = movement.y;
++ double moveZ = movement.z;
+
-+ switch (this.direction.getAxis()) {
-+ case X -> x = direction.getStepX();
-+ case Y -> y = direction.getStepY();
-+ case Z -> z = direction.getStepZ();
++ switch (direction.getAxis()) {
++ case X -> moveX = direction.getStepX();
++ case Y -> moveY = direction.getStepY();
++ case Z -> moveZ = direction.getStepZ();
+ }
+
-+ entity.setDeltaMovement(x, y, z);
++ entity.setDeltaMovement(moveX, moveY, moveZ);
+ } else {
-+ entity.move(MoverType.PISTON, new Vec3(f1 * (float) this.direction.getStepX(), f1 * (float) this.direction.getStepY(), f1 * (float) this.direction.getStepZ()));
++ final Vec3 pistonMovement = new Vec3(
++ relativeProgress * (float) direction.getStepX(),
++ relativeProgress * (float) direction.getStepY(),
++ relativeProgress * (float) direction.getStepZ()
++ );
++ entity.move(MoverType.PISTON, pistonMovement);
+ }
+ }
+ }
+
-+ private static void moveEntityByPistonFromDirection(Direction direction, Entity entity, AABB blockBB) {
-+ AABB entityBB = entity.getBoundingBox();
-+ double movX = 0.0;
-+ double movY = 0.0;
-+ double movZ = 0.0;
++ private static void moveEntityByPistonInDirection(final Direction direction, final Entity entity, final AABB movingBB) {
++ final AABB entityBB = entity.getBoundingBox();
++ double moveX = 0.0;
++ double moveY = 0.0;
++ double moveZ = 0.0;
+
+ switch (direction.getAxis()) {
+ case X -> {
+ if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
-+ movX = blockBB.maxX - entityBB.minX;
++ moveX = movingBB.maxX - entityBB.minX;
+ } else {
-+ movX = entityBB.maxX - blockBB.minX;
++ moveX = entityBB.maxX - movingBB.minX;
+ }
-+ movX += 0.01D;
++ moveX += 0.01D;
+ }
+ case Y -> {
+ if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
-+ movY = blockBB.maxY - entityBB.minY;
++ moveY = movingBB.maxY - entityBB.minY;
+ } else {
-+ movY = entityBB.maxY - blockBB.minY;
++ moveY = entityBB.maxY - movingBB.minY;
+ }
-+ movY += 0.01D;
++ moveY += 0.01D;
+ }
+ case Z -> {
+ if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
-+ movZ = blockBB.maxZ - entityBB.minZ;
++ moveZ = movingBB.maxZ - entityBB.minZ;
+ } else {
-+ movZ = entityBB.maxZ - blockBB.minZ;
++ moveZ = entityBB.maxZ - movingBB.minZ;
+ }
-+ movZ += 0.01D;
++ moveZ += 0.01D;
+ }
+ }
+
-+ entity.move(MoverType.PISTON, new Vec3(movX * direction.getStepX(), movY * direction.getStepY(), movZ * direction.getStepZ()));
++ final Vec3 pistonMovement = new Vec3(
++ moveX * direction.getStepX(),
++ moveY * direction.getStepY(),
++ moveZ * direction.getStepZ()
++ );
++ entity.move(MoverType.PISTON, pistonMovement);
+ }
+ // Sakura end - configure cannon physics
+
@Override
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
return this.saveCustomOnly(registries);
-@@ -168,6 +312,12 @@ public class PistonMovingBlockEntity extends BlockEntity {
+@@ -168,6 +325,12 @@ public class PistonMovingBlockEntity extends BlockEntity {
double d4 = 0.0;
+ // Sakura start - configure cannon physics
-+ if (entity.physics().before(1_11_0)) {
-+ moveEntityByPistonFromDirection(movementDirection, entity, aabb);
++ if (entity.mechanicsTarget().before(me.samsuik.sakura.mechanics.MechanicVersion.v1_11)) {
++ moveEntityByPistonInDirection(movementDirection, entity, aabb);
+ return;
+ }
+ // Sakura end - configure cannon physics
for (AABB aabb1 : list) {
AABB movementArea = PistonMath.getMovementArea(moveByPositionAndProgress(pos, aabb1, piston), movementDirection, d);
AABB boundingBox = entity.getBoundingBox();
-@@ -195,6 +345,11 @@ public class PistonMovingBlockEntity extends BlockEntity {
+@@ -195,6 +358,11 @@ public class PistonMovingBlockEntity extends BlockEntity {
NOCLIP.set(noClipDirection);
Vec3 vec3 = entity.position();
entity.move(MoverType.PISTON, new Vec3(progress * direction.getStepX(), progress * direction.getStepY(), progress * direction.getStepZ()));
+ // Sakura start - configure cannon physics
-+ if (entity.physics().before(1_21_5)) {
++ if (entity.mechanicsTarget().before(me.samsuik.sakura.mechanics.MechanicVersion.v1_21_5)) {
+ vec3 = entity.oldPosition();
+ }
+ // Sakura end - configure cannon physics
entity.applyEffectsFromBlocks(vec3, entity.position());
entity.removeLatestMovementRecording();
NOCLIP.set(null);
-@@ -307,12 +462,21 @@ public class PistonMovingBlockEntity extends BlockEntity {
+@@ -307,12 +475,21 @@ public class PistonMovingBlockEntity extends BlockEntity {
}
public static void tick(Level level, BlockPos pos, BlockState state, PistonMovingBlockEntity blockEntity) {
-+ final me.samsuik.sakura.physics.PhysicsVersion physics = level.localConfig().config(pos).physicsVersion; // Sakura - configure cannon physics
++ final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget = level.localConfig().at(pos).mechanicsTarget; // Sakura - configure cannon physics
blockEntity.lastTicked = level.getGameTime();
blockEntity.progressO = blockEntity.progress;
if (blockEntity.progressO >= 1.0F) {
@@ -1020,33 +1030,35 @@ index e87b685d4a2bc31a1d7a1a31881152abc37563ba..2d812099459b808bc0205e4108ef856a
blockEntity.deathTicks++;
} else {
+ // Sakura start - configure cannon physics
-+ if (physics.isWithin(1_9_0, 1_10_0)) {
++ if (mechanicsTarget.between(me.samsuik.sakura.mechanics.MechanicVersion.v1_9, me.samsuik.sakura.mechanics.MechanicVersion.v1_10)) {
+ moveCollidedEntities(level, pos, 1.0f, blockEntity);
+ moveStuckEntities(level, pos, 1.0f, blockEntity);
-+ } else if (physics.before(1_9_0)) {
++ } else if (mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_9)) {
+ blockEntity.moveEntities(level, 0.25f);
+ }
+ // Sakura end - configure cannon physics
level.removeBlockEntity(pos);
blockEntity.setRemoved();
if (level.getBlockState(pos).is(Blocks.MOVING_PISTON)) {
-@@ -334,12 +498,22 @@ public class PistonMovingBlockEntity extends BlockEntity {
+@@ -334,12 +511,22 @@ public class PistonMovingBlockEntity extends BlockEntity {
}
} else {
float f = blockEntity.progress + 0.5F;
+- moveCollidedEntities(level, pos, f, blockEntity);
+- moveStuckEntities(level, pos, f, blockEntity);
+ // Sakura start - configure cannon physics
-+ if (physics.afterOrEqual(1_11_0)) {
- moveCollidedEntities(level, pos, f, blockEntity);
- moveStuckEntities(level, pos, f, blockEntity);
++ if (mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_11)) {
++ moveCollidedEntities(level, pos, f, blockEntity);
++ moveStuckEntities(level, pos, f, blockEntity);
+ }
blockEntity.progress = f;
if (blockEntity.progress >= 1.0F) {
blockEntity.progress = 1.0F;
}
-+ if (physics.isWithin(1_9_0, 1_10_0)) {
++ if (mechanicsTarget.between(me.samsuik.sakura.mechanics.MechanicVersion.v1_9, me.samsuik.sakura.mechanics.MechanicVersion.v1_10)) {
+ moveCollidedEntities(level, pos, f, blockEntity);
+ moveStuckEntities(level, pos, f, blockEntity);
-+ } else if (blockEntity.extending && physics.before(1_9_0)) {
++ } else if (blockEntity.extending && mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_9)) {
+ blockEntity.moveEntities(level, blockEntity.progress - blockEntity.progressO + 0.0625f);
+ }
+ // Sakura end - configure cannon physics
@@ -1054,35 +1066,36 @@ index e87b685d4a2bc31a1d7a1a31881152abc37563ba..2d812099459b808bc0205e4108ef856a
}
diff --git a/net/minecraft/world/level/material/LavaFluid.java b/net/minecraft/world/level/material/LavaFluid.java
-index 48124dd5cc7461ffbcf741b1f0161ef9e1580158..34a40cf00c4337acd716358f7767aa81a936f1dc 100644
+index a5901d74f39216d770cf84b3ed3479c9195ec813..752099419e46070b42f9f2ecff1016d28a6ebf49 100644
--- a/net/minecraft/world/level/material/LavaFluid.java
+++ b/net/minecraft/world/level/material/LavaFluid.java
-@@ -184,7 +184,10 @@ public abstract class LavaFluid extends FlowingFluid {
+@@ -184,6 +184,11 @@ public abstract class LavaFluid extends FlowingFluid {
@Override
public boolean canBeReplacedWith(FluidState fluidState, BlockGetter blockReader, BlockPos pos, Fluid fluid, Direction direction) {
-- return fluidState.getHeight(blockReader, pos) >= 0.44444445F && fluid.is(FluidTags.WATER);
+ // Sakura start - configure cannon physics
-+ return fluidState.getHeight(blockReader, pos) >= 0.44444445F && fluid.is(FluidTags.WATER)
-+ && blockReader instanceof Level level && level.localConfig().config(pos).physicsVersion.afterOrEqual(1_13_0);
++ if (blockReader instanceof Level level && level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_13)) {
++ return false;
++ }
+ // Sakura end - configure cannon physics
+ return fluidState.getHeight(blockReader, pos) >= 0.44444445F && fluid.is(FluidTags.WATER);
}
- @Override
diff --git a/net/minecraft/world/level/material/WaterFluid.java b/net/minecraft/world/level/material/WaterFluid.java
-index 10e3c644e31650b0e1aad6349a83a763cf744ec8..62a51972df8edd1cc7f892376ba6e37eba1a301a 100644
+index 10e3c644e31650b0e1aad6349a83a763cf744ec8..9fc1c56dec5201c3b700992f88b258225ceb8581 100644
--- a/net/minecraft/world/level/material/WaterFluid.java
+++ b/net/minecraft/world/level/material/WaterFluid.java
-@@ -124,7 +124,12 @@ public abstract class WaterFluid extends FlowingFluid {
+@@ -124,7 +124,13 @@ public abstract class WaterFluid extends FlowingFluid {
@Override
public boolean canBeReplacedWith(FluidState fluidState, BlockGetter blockReader, BlockPos pos, Fluid fluid, Direction direction) {
- return direction == Direction.DOWN && !fluid.is(FluidTags.WATER);
+ // Sakura start - configure cannon physics
-+ if (direction == Direction.DOWN && !fluid.is(FluidTags.WATER) || !(blockReader instanceof Level level)) {
-+ return true;
-+ }
-+ return fluid.is(FluidTags.LAVA) && level.localConfig().config(pos).physicsVersion.before(1_13_0);
++ final boolean canReplace = direction == Direction.DOWN
++ || blockReader instanceof Level level
++ && level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_13);
++ // Before 1.13 lava could replace water
++ return canReplace && !fluid.is(FluidTags.WATER);
+ // Sakura end - configure cannon physics
}
diff --git a/sakura-server/minecraft-patches/features/0019-Collide-with-non-solid-blocks.patch b/sakura-server/minecraft-patches/features/0019-Collide-with-non-solid-blocks.patch
index 734da3d..d24e286 100644
--- a/sakura-server/minecraft-patches/features/0019-Collide-with-non-solid-blocks.patch
+++ b/sakura-server/minecraft-patches/features/0019-Collide-with-non-solid-blocks.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Collide with non-solid blocks
diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
-index 3eedfdf43748a61caad88ec847d9aed8bbec4b5b..2401cca1388298db8b5007c8332f738f793880e5 100644
+index bfb220f0808767fec561d9903955514729a7448f..7dddc1fda846c362c0f3d0cae89e15aba37abf5a 100644
--- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
-@@ -1908,6 +1908,7 @@ public final class CollisionUtil {
+@@ -1902,6 +1902,7 @@ public final class CollisionUtil {
public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2;
public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3;
public static final int COLLISION_FLAG_ADD_TICKET = 1 << 4; // Sakura - load chunks on movement
@@ -16,7 +16,7 @@ index 3eedfdf43748a61caad88ec847d9aed8bbec4b5b..2401cca1388298db8b5007c8332f738f
public static boolean getCollisionsForBlocksOrWorldBorder(final Level world, final Entity entity, final AABB aabb,
final List intoVoxel, final List intoAABB,
-@@ -1960,6 +1961,7 @@ public final class CollisionUtil {
+@@ -1954,6 +1955,7 @@ public final class CollisionUtil {
final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0;
final boolean addTicket = (collisionFlags & COLLISION_FLAG_ADD_TICKET) != 0; // Sakura - load chunks on movement
@@ -24,7 +24,7 @@ index 3eedfdf43748a61caad88ec847d9aed8bbec4b5b..2401cca1388298db8b5007c8332f738f
final ChunkSource chunkSource = world.getChunkSource();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
-@@ -2003,7 +2005,7 @@ public final class CollisionUtil {
+@@ -1997,7 +1999,7 @@ public final class CollisionUtil {
continue;
}
@@ -33,7 +33,7 @@ index 3eedfdf43748a61caad88ec847d9aed8bbec4b5b..2401cca1388298db8b5007c8332f738f
final int sectionAdjust = !hasSpecial ? 1 : 0;
final PalettedContainer blocks = section.states;
-@@ -2042,6 +2044,11 @@ public final class CollisionUtil {
+@@ -2036,6 +2038,11 @@ public final class CollisionUtil {
mutablePos.set(blockX, blockY, blockZ);
if (useEntityCollisionShape) {
blockCollision = collisionShape.getCollisionShape(blockData, world, mutablePos);
@@ -46,10 +46,10 @@ index 3eedfdf43748a61caad88ec847d9aed8bbec4b5b..2401cca1388298db8b5007c8332f738f
blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape);
}
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 751f8e3045dbb090f16f099097bf31b638df39d7..f8efa8d7ea4e79a4baac820f0042c2da7259a849 100644
+index a8ace187724ce0e26fd9b1d289ec1db4756b85b6..e63a24188e07f50fc4ef67ca87866063fbc51516 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
-@@ -548,6 +548,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -547,6 +547,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
flags |= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_ADD_TICKET;
}
diff --git a/sakura-server/minecraft-patches/features/0021-Legacy-lava-block-formation.patch b/sakura-server/minecraft-patches/features/0021-Legacy-lava-block-formation.patch
index 88f36a5..2479152 100644
--- a/sakura-server/minecraft-patches/features/0021-Legacy-lava-block-formation.patch
+++ b/sakura-server/minecraft-patches/features/0021-Legacy-lava-block-formation.patch
@@ -5,52 +5,73 @@ Subject: [PATCH] Legacy lava block formation
diff --git a/net/minecraft/world/level/block/LiquidBlock.java b/net/minecraft/world/level/block/LiquidBlock.java
-index 1e8574d7900ffde16c2e1ee9f92a77c47c85af61..e9895a986dffd2ca170916b3e11f88bf36adae50 100644
+index e6091c252f252373481f46a3ef119ddc32baddf0..cf624b29ed2b7ca2d6af24b3cac6bb316199d287 100644
--- a/net/minecraft/world/level/block/LiquidBlock.java
+++ b/net/minecraft/world/level/block/LiquidBlock.java
-@@ -199,7 +199,14 @@ public class LiquidBlock extends Block implements BucketPickup {
+@@ -198,7 +198,14 @@ public class LiquidBlock extends Block implements BucketPickup {
+ // Sakura start - configure cannon physics
final FluidState fluidState = state.getFluidState();
final Block block = fluidState.isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE;
- if (block == Blocks.COBBLESTONE) {
-- final me.samsuik.sakura.physics.PhysicsVersion physics = level.localConfig().config(pos).physicsVersion;
-+ // Sakura start - legacy lava block formation
-+ final me.samsuik.sakura.physics.PhysicsVersion physics;
-+ if (level.sakuraConfig().environment.blockGeneration.legacyBlockFormation) {
-+ physics = me.samsuik.sakura.physics.PhysicsVersion.v1_12;
-+ } else {
-+ physics = level.localConfig().config(pos).physicsVersion;
-+ }
-+ // Sakura end - legacy lava block formation
-
- // SANITY: In legacy a patch by paper removes the fluid level condition from vanilla.
- if (physics.before(1_16_0) && !physics.isLegacy() &&
+- final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget = level.localConfig().at(pos).mechanicsTarget;
++ // Sakura start - legacy lava block formation
++ final me.samsuik.sakura.mechanics.MinecraftMechanicsTarget mechanicsTarget;
++ if (level.sakuraConfig().environment.blockGeneration.legacyBlockFormation) {
++ mechanicsTarget = me.samsuik.sakura.mechanics.MinecraftMechanicsTarget.legacy();
++ } else {
++ mechanicsTarget = level.localConfig().at(pos).mechanicsTarget;
++ }
++ // Sakura end - legacy lava block formation
+ if (block == Blocks.COBBLESTONE && !me.samsuik.sakura.mechanics.LiquidBehaviour.canLiquidSolidify(level, pos, fluidState, mechanicsTarget)) {
+ return true;
+ }
diff --git a/net/minecraft/world/level/material/LavaFluid.java b/net/minecraft/world/level/material/LavaFluid.java
-index 34a40cf00c4337acd716358f7767aa81a936f1dc..ac052ccb2bfbd8a824b8f8d2ce8d55d8214b5fa9 100644
+index 752099419e46070b42f9f2ecff1016d28a6ebf49..b6f198a8ef365c0414d2560b36a9c7d13d86ccbf 100644
--- a/net/minecraft/world/level/material/LavaFluid.java
+++ b/net/minecraft/world/level/material/LavaFluid.java
-@@ -186,7 +186,8 @@ public abstract class LavaFluid extends FlowingFluid {
- public boolean canBeReplacedWith(FluidState fluidState, BlockGetter blockReader, BlockPos pos, Fluid fluid, Direction direction) {
- // Sakura start - configure cannon physics
- return fluidState.getHeight(blockReader, pos) >= 0.44444445F && fluid.is(FluidTags.WATER)
-- && blockReader instanceof Level level && level.localConfig().config(pos).physicsVersion.afterOrEqual(1_13_0);
-+ && blockReader instanceof Level level && level.localConfig().config(pos).physicsVersion.afterOrEqual(1_13_0)
-+ && !level.sakuraConfig().environment.blockGeneration.legacyBlockFormation; // Sakura - legacy lava block formation
- // Sakura end - configure cannon physics
- }
+@@ -184,9 +184,16 @@ public abstract class LavaFluid extends FlowingFluid {
+ @Override
+ public boolean canBeReplacedWith(FluidState fluidState, BlockGetter blockReader, BlockPos pos, Fluid fluid, Direction direction) {
+- // Sakura start - configure cannon physics
+- if (blockReader instanceof Level level && level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_13)) {
+- return false;
++ // Sakura start - configure cannon physics & legacy lava block formation
++ if (blockReader instanceof Level level) {
++ if (level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_13)) {
++ return false;
++ }
++
++ if (level.sakuraConfig().environment.blockGeneration.legacyBlockFormation) {
++ return false;
++ }
++ // Sakura end - legacy lava block formation
+ }
+ // Sakura end - configure cannon physics
+ return fluidState.getHeight(blockReader, pos) >= 0.44444445F && fluid.is(FluidTags.WATER);
diff --git a/net/minecraft/world/level/material/WaterFluid.java b/net/minecraft/world/level/material/WaterFluid.java
-index 62a51972df8edd1cc7f892376ba6e37eba1a301a..c8ebf065b250cc44fddd47c8622fb2110f2bfc0e 100644
+index 9fc1c56dec5201c3b700992f88b258225ceb8581..65fab5cb4af7e3357a1f65e89eb87a4c7fcd01a7 100644
--- a/net/minecraft/world/level/material/WaterFluid.java
+++ b/net/minecraft/world/level/material/WaterFluid.java
-@@ -128,7 +128,10 @@ public abstract class WaterFluid extends FlowingFluid {
- if (direction == Direction.DOWN && !fluid.is(FluidTags.WATER) || !(blockReader instanceof Level level)) {
- return true;
- }
-- return fluid.is(FluidTags.LAVA) && level.localConfig().config(pos).physicsVersion.before(1_13_0);
-+ // Sakura start - legacy lava block formation
-+ return fluid.is(FluidTags.LAVA) && (level.localConfig().config(pos).physicsVersion.before(1_13_0)
-+ || level.sakuraConfig().environment.blockGeneration.legacyBlockFormation);
-+ // Sakura end - legacy lava block formation
- // Sakura end - configure cannon physics
- }
+@@ -124,10 +124,18 @@ public abstract class WaterFluid extends FlowingFluid {
+ @Override
+ public boolean canBeReplacedWith(FluidState fluidState, BlockGetter blockReader, BlockPos pos, Fluid fluid, Direction direction) {
+- // Sakura start - configure cannon physics
+- final boolean canReplace = direction == Direction.DOWN
+- || blockReader instanceof Level level
+- && level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_13);
++ // Sakura start - configure cannon physics & legacy lava block formation
++ boolean canReplace = false;
++ if (direction == Direction.DOWN) {
++ canReplace = true;
++ } else if (blockReader instanceof Level level) {
++ if (level.localConfig().at(pos).mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_13)) {
++ canReplace = true;
++ } else if (level.sakuraConfig().environment.blockGeneration.legacyBlockFormation) {
++ canReplace = true;
++ }
++ }
++ // Sakura end - legacy lava block formation
+ // Before 1.13 lava could replace water
+ return canReplace && !fluid.is(FluidTags.WATER);
+ // Sakura end - configure cannon physics
diff --git a/sakura-server/minecraft-patches/features/0022-Add-entity-travel-distance-limits.patch b/sakura-server/minecraft-patches/features/0022-Add-entity-travel-distance-limits.patch
index 4324bb2..ecc8752 100644
--- a/sakura-server/minecraft-patches/features/0022-Add-entity-travel-distance-limits.patch
+++ b/sakura-server/minecraft-patches/features/0022-Add-entity-travel-distance-limits.patch
@@ -21,11 +21,11 @@ index bad69adfbc492d851a3542dc7f77884d9f933c8a..9250806b12171ae4f14d8dbc9dd3d947
} else {entity.inactiveTick();} // Paper - EAR 2
profilerFiller.pop();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index f8efa8d7ea4e79a4baac820f0042c2da7259a849..13b7652cd95b4411927d1e041b246d063fa80fae 100644
+index e63a24188e07f50fc4ef67ca87866063fbc51516..96f430c55c3cfe7791e25e4506f5fdf1ceb1b18b 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
-@@ -631,6 +631,19 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
- return newPosition;
+@@ -601,6 +601,19 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ return this.mechanicsTarget;
}
// Sakura end - configure cannon physics
+ // Sakura start - entity travel distance limits
@@ -44,7 +44,7 @@ index f8efa8d7ea4e79a4baac820f0042c2da7259a849..13b7652cd95b4411927d1e041b246d06
public Entity(EntityType> entityType, Level level) {
this.type = entityType;
-@@ -660,6 +673,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -630,6 +643,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.setPos(0.0, 0.0, 0.0);
this.eyeHeight = this.dimensions.eyeHeight();
this.despawnTime = level == null || type == EntityType.PLAYER ? -1 : level.paperConfig().entities.spawning.despawnTime.getOrDefault(type, io.papermc.paper.configuration.type.number.IntOr.Disabled.DISABLED).or(-1); // Paper - entity despawn time limit
diff --git a/sakura-server/minecraft-patches/features/0024-Configurable-left-shooting-and-adjusting-limits.patch b/sakura-server/minecraft-patches/features/0024-Configurable-left-shooting-and-adjusting-limits.patch
index 932a0a9..6a2cc65 100644
--- a/sakura-server/minecraft-patches/features/0024-Configurable-left-shooting-and-adjusting-limits.patch
+++ b/sakura-server/minecraft-patches/features/0024-Configurable-left-shooting-and-adjusting-limits.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Configurable left shooting and adjusting limits
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 13b7652cd95b4411927d1e041b246d063fa80fae..ee7da44161fee45ce3de26d7e990eab3a3f5ff6e 100644
+index 96f430c55c3cfe7791e25e4506f5fdf1ceb1b18b..2304e3e33edfce64b79001d2f70d731da3114d77 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
-@@ -644,6 +644,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -614,6 +614,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return Math.max(x, z) >= this.travelDistanceLimit;
}
// Sakura end - entity travel distance limits
@@ -55,7 +55,7 @@ index 13b7652cd95b4411927d1e041b246d063fa80fae..ee7da44161fee45ce3de26d7e990eab3
public Entity(EntityType> entityType, Level level) {
this.type = entityType;
-@@ -1656,6 +1696,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1630,6 +1670,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
if (xSmaller && z != 0.0) {
@@ -63,7 +63,7 @@ index 13b7652cd95b4411927d1e041b246d063fa80fae..ee7da44161fee45ce3de26d7e990eab3
z = this.scanZ(currBoundingBox, z, voxelList, bbList);
if (z != 0.0) {
currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetZ(currBoundingBox, z);
-@@ -1663,6 +1704,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1637,6 +1678,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
if (x != 0.0) {
@@ -76,10 +76,10 @@ index 13b7652cd95b4411927d1e041b246d063fa80fae..ee7da44161fee45ce3de26d7e990eab3
if (x != 0.0) {
currBoundingBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.offsetX(currBoundingBox, x);
diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java
-index fb3f28ee3b018462f2274e997d540029560df8d0..5a6e3769e3d9349927db1986256b3947ef30962e 100644
+index 33e8073651099d6a3d82fe0886424d5aa3886c5d..38047b725ffab925ac3dd97f1470e25db74e1226 100644
--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
-@@ -288,6 +288,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
+@@ -270,6 +270,7 @@ public class FallingBlockEntity extends Entity implements me.samsuik.sakura.enti
// Sakura end - configure cannon physics
this.time++;
this.applyGravity();
diff --git a/sakura-server/minecraft-patches/features/0026-Optimise-check-inside-blocks-and-traverse-blocks.patch b/sakura-server/minecraft-patches/features/0026-Optimise-check-inside-blocks-and-traverse-blocks.patch
index 9201c2f..6560167 100644
--- a/sakura-server/minecraft-patches/features/0026-Optimise-check-inside-blocks-and-traverse-blocks.patch
+++ b/sakura-server/minecraft-patches/features/0026-Optimise-check-inside-blocks-and-traverse-blocks.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Optimise check inside blocks and traverse blocks
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index ee7da44161fee45ce3de26d7e990eab3a3f5ff6e..b804964e620c49130c89d67e0371f06881b95eca 100644
+index 2304e3e33edfce64b79001d2f70d731da3114d77..d1688f01a9564b5ef4c9e905015a174550cab6ae 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
-@@ -1936,6 +1936,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1910,6 +1910,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
private void checkInsideBlocks(List movements, InsideBlockEffectApplier.StepBasedCollector stepBasedCollector) {
if (this.isAffectedByBlocks()) {
LongSet set = this.visitedBlocks;
@@ -16,7 +16,7 @@ index ee7da44161fee45ce3de26d7e990eab3a3f5ff6e..b804964e620c49130c89d67e0371f068
for (Entity.Movement movement : movements) {
Vec3 vec3 = movement.from;
-@@ -1945,12 +1946,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1919,12 +1920,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
double d = vec31.get(axis);
if (d != 0.0) {
Vec3 vec32 = vec3.relative(axis.getPositive(), d);
@@ -31,7 +31,7 @@ index ee7da44161fee45ce3de26d7e990eab3a3f5ff6e..b804964e620c49130c89d67e0371f068
}
}
-@@ -1958,7 +1959,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1932,7 +1933,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
}
@@ -41,9 +41,9 @@ index ee7da44161fee45ce3de26d7e990eab3a3f5ff6e..b804964e620c49130c89d67e0371f068
+ LongSet set, net.minecraft.world.level.chunk.ChunkAccess[] chunkCache) {
+ // Sakura end - optimise check inside blocks
// Sakura start - configure cannon physics
- double margin = this.physics.afterOrEqual(1_21_2) ? 1.0E-5f : this.physics.afterOrEqual(1_19_3) ? 1.0E-7 : 0.001;
- AABB aabb = this.makeBoundingBox(vec31).deflate(margin);
-@@ -1972,7 +1976,20 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ final double margin;
+ if (this.mechanicsTarget.atLeast(me.samsuik.sakura.mechanics.MechanicVersion.v1_21_2)) {
+@@ -1953,7 +1957,20 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (!this.isAlive()) {
return false;
} else {
@@ -66,19 +66,19 @@ index ee7da44161fee45ce3de26d7e990eab3a3f5ff6e..b804964e620c49130c89d67e0371f068
this.debugBlockIntersection(pos, false, false);
return true;
diff --git a/net/minecraft/world/level/BlockGetter.java b/net/minecraft/world/level/BlockGetter.java
-index 67c9393133f4509abf1bd352fbbc8e21dbb116e2..9e235b8bec7ed8da7c0cb099c47c3b23fbccb9b4 100644
+index 353ad0c1db2ee374ac5487539c77b2b067dc7c0a..1cdc68aab80390b5f1fb67c14c0a8aaa64f28250 100644
--- a/net/minecraft/world/level/BlockGetter.java
+++ b/net/minecraft/world/level/BlockGetter.java
-@@ -221,7 +221,7 @@ public interface BlockGetter extends LevelHeightAccessor {
+@@ -227,7 +227,7 @@ public interface BlockGetter extends LevelHeightAccessor {
Vec3 vec3 = to.subtract(from);
- if (physics != null && physics.before(1_21_2) || vec3.lengthSqr() < Mth.square(0.99999F)) {
+ if (vec3.lengthSqr() < Mth.square(0.99999F) || mechanicsTarget != null && mechanicsTarget.before(me.samsuik.sakura.mechanics.MechanicVersion.v1_21_2)) {
// Sakura end - configure cannon physics
- for (BlockPos blockPos : BlockPos.betweenClosed(boundingBox)) {
+ for (BlockPos blockPos : me.samsuik.sakura.utils.BlockPosIterator.iterable(boundingBox)) { // Sakura - optimise check inside blocks
if (!visitor.visit(blockPos, 0)) {
return false;
}
-@@ -229,6 +229,20 @@ public interface BlockGetter extends LevelHeightAccessor {
+@@ -235,6 +235,20 @@ public interface BlockGetter extends LevelHeightAccessor {
return true;
} else {
@@ -99,7 +99,7 @@ index 67c9393133f4509abf1bd352fbbc8e21dbb116e2..9e235b8bec7ed8da7c0cb099c47c3b23
LongSet set = new LongOpenHashSet();
Vec3 minPosition = boundingBox.getMinPosition();
Vec3 vec31 = minPosition.subtract(vec3);
-@@ -236,7 +250,7 @@ public interface BlockGetter extends LevelHeightAccessor {
+@@ -242,7 +256,7 @@ public interface BlockGetter extends LevelHeightAccessor {
if (i < 0) {
return false;
} else {
diff --git a/sakura-server/minecraft-patches/features/0029-Configure-breaking-blocks-outside-the-world-border.patch b/sakura-server/minecraft-patches/features/0029-Configure-breaking-blocks-outside-the-world-border.patch
index 2cd3fbe..8672632 100644
--- a/sakura-server/minecraft-patches/features/0029-Configure-breaking-blocks-outside-the-world-border.patch
+++ b/sakura-server/minecraft-patches/features/0029-Configure-breaking-blocks-outside-the-world-border.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Configure breaking blocks outside the world border
diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
-index 78b44fc3dda798d54656318b948c55dcb3b03ecb..d86f6f6a43584967b6e256be32c3144fbb2a326f 100644
+index 49dabe47bda4237df9799d3c673a40cab9f2d03e..cf8f4203c06030e36a5a5bfe210ba65582c204cb 100644
--- a/net/minecraft/world/level/ServerExplosion.java
+++ b/net/minecraft/world/level/ServerExplosion.java
-@@ -538,6 +538,11 @@ public class ServerExplosion implements Explosion {
+@@ -540,6 +540,11 @@ public class ServerExplosion implements Explosion {
return ret;
}
// Sakura end - optimise protected explosions
diff --git a/sakura-server/minecraft-patches/features/0030-Optimise-block-counting-for-cannon-entities.patch b/sakura-server/minecraft-patches/features/0030-Optimise-block-counting-for-cannon-entities.patch
index 64dca50..b2b117a 100644
--- a/sakura-server/minecraft-patches/features/0030-Optimise-block-counting-for-cannon-entities.patch
+++ b/sakura-server/minecraft-patches/features/0030-Optimise-block-counting-for-cannon-entities.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Optimise block counting for cannon entities
diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
-index 2401cca1388298db8b5007c8332f738f793880e5..f43bdff628ce94075137904ff9d714f128126e9d 100644
+index 7dddc1fda846c362c0f3d0cae89e15aba37abf5a..68b89ee60a5dcb5f38dfbda8dd3bbbf25f92f380 100644
--- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
-@@ -1943,6 +1943,7 @@ public final class CollisionUtil {
+@@ -1937,6 +1937,7 @@ public final class CollisionUtil {
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
final CollisionContext collisionShape = new LazyEntityCollisionContext(entity);
final boolean useEntityCollisionShape = LazyEntityCollisionContext.useEntityCollisionShape(world, entity);
@@ -16,7 +16,7 @@ index 2401cca1388298db8b5007c8332f738f793880e5..f43bdff628ce94075137904ff9d714f1
// special cases:
if (minBlockY > maxBlockY) {
-@@ -2007,15 +2008,19 @@ public final class CollisionUtil {
+@@ -2001,15 +2002,19 @@ public final class CollisionUtil {
final boolean hasSpecial = !fullBlocks && ((BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks(); // Sakura - collide with non-solid blocks
final int sectionAdjust = !hasSpecial ? 1 : 0;
@@ -42,7 +42,7 @@ index 2401cca1388298db8b5007c8332f738f793880e5..f43bdff628ce94075137904ff9d714f1
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
final int blockY = currY | (currChunkY << 4);
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index e894c404d58f95c8f54987739ad24b6a0b96dfc3..e77b59f8415c07ddb65a669423eaad9214abe6ab 100644
+index 1e8a69bfafebcf292140403227cc378a0b3f81a6..30b01591c9abc4dcb5d1916a782ab45af5db5daa 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -616,6 +616,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl
diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch
index 350878a..4e35835 100644
--- a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch
+++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch
@@ -19,12 +19,12 @@
+ return this.sakuraConfig;
+ }
+ // Sakura end - sakura configuration files
-+ // Sakura start - local config and property storage
-+ private final me.samsuik.sakura.configuration.local.LocalConfigManager localConfig = new me.samsuik.sakura.configuration.local.LocalConfigManager(this);
-+ public final me.samsuik.sakura.configuration.local.LocalConfigManager localConfig() {
++ // Sakura start - local config api
++ private final me.samsuik.sakura.configuration.local.LocalConfiguration localConfig = new me.samsuik.sakura.configuration.local.LocalConfiguration(this);
++ public final me.samsuik.sakura.configuration.local.LocalConfiguration localConfig() {
+ return this.localConfig;
+ }
-+ // Sakura end - local config and property storage
++ // Sakura end - local config api
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
private int tileTickPosition;
diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch
index f7f1cfa..eca08ac 100644
--- a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch
+++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch
@@ -4,7 +4,7 @@
public float yield;
// CraftBukkit end
public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source
-+ private final boolean consistentRadius; // Sakura - consistent explosion radius
++ private final boolean consistentExplosionRadius; // Sakura - consistent explosion radius
// Paper start - collisions optimisations
private static final double[] CACHED_RAYS;
static {
@@ -12,7 +12,7 @@
this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F;
this.yield = Double.isFinite(this.yield) ? this.yield : 0; // Paper - Don't allow infinite default yields
// Paper end - add yield
-+ this.consistentRadius = level.localConfig().config(BlockPos.containing(this.center)).consistentRadius; // Sakura - consistent explosion radius
++ this.consistentExplosionRadius = level.localConfig().at(this.center).consistentExplosionRadius; // Sakura - consistent explosion radius
}
private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
@@ -22,7 +22,7 @@
ray += 3;
-
- float power = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);
-+ float power = this.radius * (0.7F + (this.consistentRadius ? 0.7F : this.level.random.nextFloat()) * 0.6F); // Sakura - consistent explosion radius
++ float power = this.radius * (0.7F + (this.consistentExplosionRadius ? 0.7F : this.level.random.nextFloat()) * 0.6F); // Sakura - consistent explosion radius
do {
final int blockX = Mth.floor(currX);
diff --git a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch
index 7d180ef..5e5d11b 100644
--- a/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch
+++ b/sakura-server/minecraft-patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch
@@ -12,7 +12,7 @@
+ // Sakura start - lava flow speed api
+ @Override
+ public final int getTickDelay(Level world, BlockPos pos) {
-+ final int flowSpeed = world.localConfig().config(pos).lavaFlowSpeed;
++ final int flowSpeed = world.localConfig().at(pos).lavaFlowSpeed;
+ return flowSpeed >= 0 ? flowSpeed : this.getTickDelay(world);
+ }
+ // Sakura end - lava flow speed api
diff --git a/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch
index 73829ba..c5c2f88 100644
--- a/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch
+++ b/sakura-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch
@@ -4,12 +4,12 @@
).isValid();
}
// Paper end
-+ // Sakura start - local config and property storage
++ // Sakura start - local config api
+ @Override
-+ public final me.samsuik.sakura.local.storage.LocalStorageHandler getStorageHandler() {
++ public final me.samsuik.sakura.configuration.local.LocalConfigurationAccessor localConfig() {
+ return this.getHandle().localConfig();
+ }
-+ // Sakura end - local config and property storage
++ // Sakura end - local config api
private static final Random rand = new Random();
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java
index 7ee3ed3..18a0b9a 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/command/SakuraCommands.java
@@ -1,7 +1,7 @@
package me.samsuik.sakura.command;
import me.samsuik.sakura.command.subcommands.*;
-import me.samsuik.sakura.command.subcommands.debug.DebugLocalRegions;
+import me.samsuik.sakura.command.subcommands.debug.DebugLocalConfiguration;
import me.samsuik.sakura.command.subcommands.debug.DebugRedstoneCache;
import me.samsuik.sakura.player.visibility.VisibilityTypes;
import net.minecraft.server.MinecraftServer;
@@ -30,7 +30,7 @@ public final class SakuraCommands {
// "sakura" isn't a subcommand
COMMANDS.put("sakura", new SakuraCommand("sakura"));
DEBUG_COMMANDS.add(new DebugRedstoneCache("redstone-cache"));
- DEBUG_COMMANDS.add(new DebugLocalRegions("local-regions"));
+ DEBUG_COMMANDS.add(new DebugLocalConfiguration("local-regions"));
}
public static void registerCommands(MinecraftServer server) {
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/debug/DebugLocalConfiguration.java b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/debug/DebugLocalConfiguration.java
new file mode 100644
index 0000000..5cb2958
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/debug/DebugLocalConfiguration.java
@@ -0,0 +1,56 @@
+package me.samsuik.sakura.command.subcommands.debug;
+
+import me.samsuik.sakura.command.PlayerOnlySubCommand;
+import me.samsuik.sakura.configuration.local.ConfigurationContainer;
+import me.samsuik.sakura.configuration.local.LocalConfigurationAccessor;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.util.BoundingBox;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.List;
+
+@NullMarked
+public final class DebugLocalConfiguration extends PlayerOnlySubCommand {
+ private static final int DEFAULT_REGION_SIZE = 16;
+
+ public DebugLocalConfiguration(final String name) {
+ super(name);
+ }
+
+ @Override
+ public void execute(final Player player, final String[] args) {
+ final Location location = player.getLocation();
+ final LocalConfigurationAccessor localConfigurationAccessor = location.getWorld().localConfig();
+ final BoundingBox boundingBox = localConfigurationAccessor.getAreas(location).stream()
+ .findAny()
+ .orElse(null);
+
+ if (boundingBox != null) {
+ player.sendRichMessage("You are currently inside a area with a set local-config.");
+ player.sendRichMessage(" - %.0f %.0f %.0f".formatted(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ()));
+ player.sendRichMessage(" - %.0f %.0f %.0f".formatted(boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxX()));
+ }
+
+ if (args.length == 0) {
+ return;
+ }
+
+ if ("delete".equalsIgnoreCase(args[0]) && boundingBox != null) {
+ localConfigurationAccessor.remove(boundingBox);
+ player.sendRichMessage("Removed area");
+ }
+
+ if ("create".equalsIgnoreCase(args[0]) && args.length > 1) {
+ final int size = parseInt(args, 1).orElse(DEFAULT_REGION_SIZE);
+ final BoundingBox area = BoundingBox.of(location, size, size, size);
+ localConfigurationAccessor.set(area, ConfigurationContainer.sealedContainer());
+ player.sendRichMessage("Created a new area with size " + size);
+ }
+ }
+
+ @Override
+ public void tabComplete(final List completions, final String[] args) throws IllegalArgumentException {
+ completions.addAll(List.of("create", "delete"));
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/debug/DebugLocalRegions.java b/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/debug/DebugLocalRegions.java
deleted file mode 100644
index 87372f0..0000000
--- a/sakura-server/src/main/java/me/samsuik/sakura/command/subcommands/debug/DebugLocalRegions.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package me.samsuik.sakura.command.subcommands.debug;
-
-import me.samsuik.sakura.command.PlayerOnlySubCommand;
-import me.samsuik.sakura.local.LocalRegion;
-import me.samsuik.sakura.local.storage.LocalStorageHandler;
-import me.samsuik.sakura.local.storage.LocalValueStorage;
-import org.bukkit.Location;
-import org.bukkit.World;
-import org.bukkit.entity.Player;
-import org.jspecify.annotations.NullMarked;
-
-import java.util.List;
-import java.util.Optional;
-
-@NullMarked
-public final class DebugLocalRegions extends PlayerOnlySubCommand {
- private static final int DEFAULT_REGION_SIZE = 16;
-
- public DebugLocalRegions(String name) {
- super(name);
- }
-
- @Override
- public void execute(Player player, String[] args) {
- if (args.length == 0) {
- return;
- }
-
- final Location location = player.getLocation();
- final World world = location.getWorld();
- final LocalStorageHandler storageHandler = world.getStorageHandler();
- final int blockX = location.getBlockX();
- final int blockZ = location.getBlockZ();
- final Optional currentRegion = storageHandler.locate(blockX, blockZ);
-
- if ("create".equalsIgnoreCase(args[0]) && args.length > 1) {
- final int size = parseInt(args, 1).orElse(DEFAULT_REGION_SIZE);
- final LocalRegion region = LocalRegion.at(blockX, blockZ, size);
- if (currentRegion.isPresent()) {
- player.sendRichMessage("regions cannot overlap");
- } else {
- storageHandler.put(region, new LocalValueStorage());
- }
- }
-
- if ("get".equalsIgnoreCase(args[0])) {
- player.sendRichMessage("" + (currentRegion.isPresent() ? currentRegion.get() : "not inside of a region"));
- }
-
- if (currentRegion.isPresent()) {
- final LocalRegion region = currentRegion.get();
- if ("delete".equalsIgnoreCase(args[0])) {
- storageHandler.remove(region);
- }
- }
- }
-
- @Override
- public void tabComplete(List list, String[] args) throws IllegalArgumentException {
- list.addAll(List.of("create", "get", "delete"));
- }
-}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java
index e4aaa37..04a6716 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java
@@ -4,6 +4,9 @@ import com.mojang.logging.LogUtils;
import io.papermc.paper.configuration.Configuration;
import io.papermc.paper.configuration.ConfigurationPart;
import io.papermc.paper.configuration.type.number.IntOr;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Material;
import org.slf4j.Logger;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@@ -32,6 +35,14 @@ public final class GlobalConfiguration extends ConfigurationPart {
public String durableBlockInteraction = "(S) This block has of ";
public String fpsSettingChange = "(S) ";
public boolean tpsShowEntityAndChunkCount = true;
+
+ public Component durableBlockInteractionComponent(final int remaining, final int durability) {
+ return MiniMessage.miniMessage().deserialize(
+ GlobalConfiguration.get().messages.durableBlockInteraction,
+ Placeholder.unparsed("remaining", String.valueOf(remaining)),
+ Placeholder.unparsed("durability", String.valueOf(durability))
+ );
+ }
}
public Fps fps;
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java
index e559df5..915a2c1 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java
@@ -14,7 +14,9 @@ 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 me.samsuik.sakura.configuration.serializer.MinecraftMechanicsTargetSerializer;
import me.samsuik.sakura.configuration.transformation.ConfigurationTransformations;
+import me.samsuik.sakura.mechanics.MinecraftMechanicsTarget;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
@@ -141,6 +143,7 @@ public final class SakuraConfigurations extends Configurations options
.header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER.apply(contextMap))
.serializers(serializers -> serializers
+ .register(new TypeToken() {}, new MinecraftMechanicsTargetSerializer())
.register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2IntOpenHashMap::new, Integer.TYPE))
.register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2LongOpenHashMap::new, Long.TYPE))
.register(new TypeToken>() {}, new TableSerializer())
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java
index 3381e70..78e1ff3 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java
@@ -10,7 +10,7 @@ import io.papermc.paper.configuration.type.number.IntOr;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import me.samsuik.sakura.entity.merge.MergeLevel;
import me.samsuik.sakura.explosion.durable.DurableMaterial;
-import me.samsuik.sakura.physics.PhysicsVersion;
+import me.samsuik.sakura.mechanics.MinecraftMechanicsTarget;
import net.minecraft.Util;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
@@ -28,7 +28,7 @@ import java.util.Set;
public final class WorldConfiguration extends ConfigurationPart {
private static final Logger LOGGER = LogUtils.getClassLogger();
- static final int CURRENT_VERSION = 10; // (when you change the version, change the comment, so it conflicts on rebases): rename filter bad nbt from spawn eggs
+ static final int CURRENT_VERSION = 11; // (when you change the version, change the comment, so it conflicts on rebases): rename filter bad nbt from spawn eggs
private transient final ResourceLocation worldKey;
WorldConfiguration(ResourceLocation worldKey) {
@@ -115,7 +115,7 @@ public final class WorldConfiguration extends ConfigurationPart {
public TNTSpread tntSpread = TNTSpread.ALL;
public boolean tntFlowsInWater = true;
public boolean fallingBlockParity = false;
- public PhysicsVersion physicsVersion = PhysicsVersion.LATEST;
+ public MinecraftMechanicsTarget mechanicsTarget = MinecraftMechanicsTarget.latest();
public enum TNTSpread {
ALL, Y, NONE;
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/CachedLocalConfiguration.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/CachedLocalConfiguration.java
new file mode 100644
index 0000000..106a87f
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/CachedLocalConfiguration.java
@@ -0,0 +1,64 @@
+package me.samsuik.sakura.configuration.local;
+
+import io.papermc.paper.configuration.WorldConfiguration;
+import me.samsuik.sakura.explosion.durable.DurableMaterial;
+import me.samsuik.sakura.mechanics.MinecraftMechanicsTarget;
+import me.samsuik.sakura.redstone.RedstoneConfiguration;
+import me.samsuik.sakura.redstone.RedstoneImplementation;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import org.bukkit.craftbukkit.block.CraftBlockType;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@NullMarked
+public final class CachedLocalConfiguration {
+ public final long sectionKey;
+ public final MinecraftMechanicsTarget mechanicsTarget;
+ public final Map durableMaterials;
+ public final RedstoneConfiguration redstoneBehaviour;
+ public final boolean consistentExplosionRadius;
+ public final int lavaFlowSpeed;
+
+ public static CachedLocalConfiguration emptyConfiguration() {
+ return new CachedLocalConfiguration();
+ }
+
+ public CachedLocalConfiguration(final Level level, final ConfigurationContainer container, final long sectionKey) {
+ this.sectionKey = sectionKey;
+ this.mechanicsTarget = container.getOptional(ConfigurableKey.MECHANICS_TARGET)
+ .orElse(level.sakuraConfig().cannons.mechanics.mechanicsTarget);
+ this.durableMaterials = container.getOptional(ConfigurableKey.DURABLE_MATERIALS)
+ .map(sealedContainer -> sealedContainer.open().contents().entrySet().stream()
+ .map(entry -> Map.entry(CraftBlockType.bukkitToMinecraftNew(entry.getKey()), entry.getValue()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
+ .orElseGet(() -> level.sakuraConfig().cannons.explosion.durableMaterials);
+ this.redstoneBehaviour = container.getOptional(ConfigurableKey.REDSTONE_BEHAVIOUR)
+ .orElse(createDefaultRedstoneConfiguration(level));
+ this.consistentExplosionRadius = container.getOptional(ConfigurableKey.CONSISTENT_EXPLOSION_RADIUS)
+ .orElse(level.sakuraConfig().cannons.explosion.consistentRadius);
+ this.lavaFlowSpeed = container.getOptional(ConfigurableKey.LAVA_FLOW_SPEED)
+ .orElse(30);
+ }
+
+ private CachedLocalConfiguration() {
+ this.sectionKey = Long.MIN_VALUE;
+ this.mechanicsTarget = MinecraftMechanicsTarget.latest();
+ this.durableMaterials = Map.of();
+ this.redstoneBehaviour = new RedstoneConfiguration(RedstoneImplementation.VANILLA, false);
+ this.consistentExplosionRadius = false;
+ this.lavaFlowSpeed = 30;
+ }
+
+ public WorldConfiguration.Misc.RedstoneImplementation paperRedstoneImplementation() {
+ return WorldConfiguration.Misc.RedstoneImplementation.values()[this.redstoneBehaviour.implementation().ordinal()];
+ }
+
+ private static RedstoneConfiguration createDefaultRedstoneConfiguration(final Level level) {
+ final WorldConfiguration.Misc.RedstoneImplementation paperRedstoneImplementation = level.paperConfig().misc.redstoneImplementation;
+ final RedstoneImplementation sakuraRedstoneImplementation = RedstoneImplementation.values()[paperRedstoneImplementation.ordinal()];
+ return new RedstoneConfiguration(sakuraRedstoneImplementation, level.sakuraConfig().technical.redstone.redstoneCache);
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/ConfigurationArea.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/ConfigurationArea.java
new file mode 100644
index 0000000..da7aa27
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/ConfigurationArea.java
@@ -0,0 +1,66 @@
+package me.samsuik.sakura.configuration.local;
+
+import net.minecraft.util.Mth;
+import org.bukkit.util.BoundingBox;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.function.LongConsumer;
+
+@NullMarked
+public record ConfigurationArea(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
+ public ConfigurationArea(final BoundingBox boundingBox) {
+ this(
+ Mth.floor(boundingBox.getMinX()),
+ Mth.floor(boundingBox.getMinY()),
+ Mth.floor(boundingBox.getMinZ()),
+ Mth.floor(boundingBox.getMaxX()),
+ Mth.floor(boundingBox.getMaxY()),
+ Mth.floor(boundingBox.getMaxZ())
+ );
+ }
+
+ public BoundingBox asBoundingBox() {
+ return new BoundingBox(this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ);
+ }
+
+ public boolean contains(final int x, final int y, final int z) {
+ return x >= this.minX && x < this.maxX
+ && y >= this.minY && y < this.maxY
+ && z >= this.minZ && z < this.maxZ;
+ }
+
+ public long volume() {
+ return this.countSections(0);
+ }
+
+ public long countSections(final int sectionExponent) {
+ final int sectionsX = (maxX - minX >> sectionExponent) + 1;
+ final int sectionsY = (maxY - minY >> sectionExponent) + 1;
+ final int sectionsZ = (maxZ - minZ >> sectionExponent) + 1;
+ return (long) sectionsX * (long) sectionsY * (long) sectionsZ;
+ }
+
+ public static long sectionKey(final int x, final int y, final int z, final int sectionExponent) {
+ final int sectionX = x >> sectionExponent;
+ final int sectionY = y >> sectionExponent;
+ final int sectionZ = z >> sectionExponent;
+ return (long) sectionX << 40 | (long) sectionY << 20 | (long) sectionZ;
+ }
+
+ public void forEach(final int sectionExponent, final LongConsumer sectionConsumer) {
+ final int minSectionX = this.minX >> sectionExponent;
+ final int minSectionY = this.minY >> sectionExponent;
+ final int minSectionZ = this.minZ >> sectionExponent;
+ final int maxSectionX = this.maxX >> sectionExponent;
+ final int maxSectionY = this.maxY >> sectionExponent;
+ final int maxSectionZ = this.maxZ >> sectionExponent;
+
+ for (int x = minSectionX; x <= maxSectionX; x++) {
+ for (int y = minSectionY; y <= maxSectionY; y++) {
+ for (int z = minSectionZ; z <= maxSectionZ; z++) {
+ sectionConsumer.accept(sectionKey(x, y, z, 0));
+ }
+ }
+ }
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java
deleted file mode 100644
index 864bf4c..0000000
--- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java
+++ /dev/null
@@ -1,221 +0,0 @@
-package me.samsuik.sakura.configuration.local;
-
-import ca.spottedleaf.concurrentutil.function.BiLongObjectConsumer;
-import com.google.common.collect.Iterables;
-import it.unimi.dsi.fastutil.Pair;
-import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
-import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
-import it.unimi.dsi.fastutil.objects.ObjectArrayList;
-import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
-import me.samsuik.sakura.local.LocalRegion;
-import me.samsuik.sakura.local.storage.LocalStorageHandler;
-import me.samsuik.sakura.local.storage.LocalValueStorage;
-import me.samsuik.sakura.utils.TickExpiry;
-import net.minecraft.core.BlockPos;
-import net.minecraft.world.level.ChunkPos;
-import net.minecraft.world.level.Level;
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
-import java.util.*;
-
-public final class LocalConfigManager implements LocalStorageHandler {
- private static final long MASSIVE_REGION_SIZE = 0x10000000000L;
- private static final int SMALL_REGION_SIZE = 12;
- private static final int CONFIG_CACHE_EXPIRATION = 600;
-
- private final Map storageMap = new Object2ObjectOpenHashMap<>();
- private final List largeRegions = new ObjectArrayList<>();
- private final Long2ObjectMap> smallRegions = new Long2ObjectOpenHashMap<>();
- private int regionExponent = 4;
- private final Long2ObjectMap> chunkConfigCache = new Long2ObjectOpenHashMap<>();
- private final Level level;
- private long expirationTick = 0L;
-
- public LocalConfigManager(Level level) {
- this.level = level;
- }
-
- private int regionChunkCoord(int n) {
- return n >> this.regionExponent;
- }
-
- @Override
- public synchronized @NonNull Optional locate(int x, int z) {
- int regionX = this.regionChunkCoord(x);
- int regionZ = this.regionChunkCoord(z);
- long regionPos = ChunkPos.asLong(regionX, regionZ);
- List regions = this.smallRegions.getOrDefault(regionPos, List.of());
- for (LocalRegion region : Iterables.concat(regions, this.largeRegions)) {
- if (region.contains(x, z)) {
- return Optional.of(region);
- }
- }
- return Optional.empty();
- }
-
- @Override
- public synchronized @Nullable LocalValueStorage get(@NonNull LocalRegion region) {
- return this.storageMap.get(region);
- }
-
- @Override
- public synchronized boolean has(@NonNull LocalRegion region) {
- return this.storageMap.containsKey(region);
- }
-
- @Override
- public synchronized void put(@NonNull LocalRegion region, @NonNull LocalValueStorage storage) {
- boolean smallRegion = this.isSmallRegion(region);
- this.ensureRegionIsNotOverlapping(region, smallRegion);
-
- if (!smallRegion) {
- this.largeRegions.add(region);
-
- // The region exponent may be too small
- if ((this.largeRegions.size() & 15) == 0) {
- this.resizeRegions();
- }
- } else {
- this.forEachRegionChunks(region, this::addSmallRegion);
- }
-
- this.chunkConfigCache.clear();
- this.storageMap.put(region, storage);
- }
-
- @Override
- public synchronized void remove(@NonNull LocalRegion region) {
- this.forEachRegionChunks(region, (pos, r) -> {
- List regions = this.smallRegions.get(pos);
- if (regions != null) {
- regions.remove(region);
- if (regions.isEmpty()) {
- this.smallRegions.remove(pos);
- }
- }
- });
-
- this.chunkConfigCache.clear();
- this.storageMap.remove(region);
- this.largeRegions.remove(region);
- }
-
- private void addSmallRegion(long pos, LocalRegion region) {
- this.smallRegions.computeIfAbsent(pos, k -> new ArrayList<>())
- .add(region);
- }
-
- private void forEachRegionChunks(LocalRegion region, BiLongObjectConsumer chunkConsumer) {
- int exponent = this.regionExponent;
- int minX = region.minX() >> exponent;
- int minZ = region.minZ() >> exponent;
- int maxX = region.maxX() >> exponent;
- int maxZ = region.maxZ() >> exponent;
-
- for (int x = minX; x <= maxX; ++x) {
- for (int z = minZ; z <= maxZ; ++z) {
- chunkConsumer.accept(ChunkPos.asLong(x, z), region);
- }
- }
- }
-
- private void resizeRegions() {
- int newExponent = this.calculateRegionExponent();
- if (newExponent == this.regionExponent) {
- return; // nothing has changed
- }
-
- this.regionExponent = newExponent;
- this.largeRegions.clear();
- this.smallRegions.clear();
-
- for (LocalRegion region : this.storageMap.keySet()) {
- if (!this.isSmallRegion(region)) {
- this.largeRegions.add(region);
- } else {
- this.forEachRegionChunks(region, this::addSmallRegion);
- }
- }
- }
-
- private int calculateRegionExponent() {
- long totalRegionChunks = 0;
- for (LocalRegion region : this.storageMap.keySet()) {
- long chunks = regionChunks(region, 0);
- if (chunks >= MASSIVE_REGION_SIZE) {
- continue;
- }
- totalRegionChunks += chunks;
- }
- totalRegionChunks /= this.storageMap.size();
-
- int exponent = 4;
- while (true) {
- if ((totalRegionChunks >> exponent++) <= SMALL_REGION_SIZE / 2) {
- return exponent;
- }
- }
- }
-
- private boolean isSmallRegion(LocalRegion region) {
- return regionChunks(region, this.regionExponent) <= SMALL_REGION_SIZE;
- }
-
- private static long regionChunks(LocalRegion region, int exponent) {
- int sizeX = region.maxX() - region.minX() >> exponent;
- int sizeZ = region.maxZ() - region.minZ() >> exponent;
- return (long) (sizeX + 1) * (long) (sizeZ + 1);
- }
-
- @Override
- public synchronized @NonNull List regions() {
- return new ArrayList<>(this.storageMap.keySet());
- }
-
- public synchronized LocalValueConfig config(BlockPos position) {
- long gameTime = this.level.getGameTime();
- long ticks = gameTime - this.expirationTick;
- if (ticks >= CONFIG_CACHE_EXPIRATION / 3) {
- this.chunkConfigCache.values().removeIf(pair -> pair.value().isExpired(gameTime));
- this.expirationTick = gameTime;
- }
-
- long chunkKey = ChunkPos.asLong(position.getX() >> 4, position.getZ() >> 4);
- Pair pair = this.chunkConfigCache.computeIfAbsent(chunkKey,
- k -> this.createLocalChunkConfigWithExpiry(position, gameTime));
-
- pair.value().refresh(gameTime);
- return pair.key();
- }
-
- private Pair createLocalChunkConfigWithExpiry(BlockPos position, long gameTime) {
- // uses defaults from the sakura and paper config
- LocalValueConfig config = new LocalValueConfig(this.level);
- this.locate(position.getX(), position.getZ()).ifPresent(region -> {
- config.loadFromStorage(this.storageMap.get(region));
- });
-
- TickExpiry expiry = new TickExpiry(gameTime, CONFIG_CACHE_EXPIRATION);
- return Pair.of(config, expiry);
- }
-
- private void ensureRegionIsNotOverlapping(LocalRegion region, boolean smallRegion) {
- Set nearbyRegions = new ReferenceOpenHashSet<>();
- if (!smallRegion) {
- nearbyRegions.addAll(this.storageMap.keySet());
- } else {
- this.forEachRegionChunks(region, (pos, r) -> {
- nearbyRegions.addAll(this.smallRegions.getOrDefault(pos, List.of()));
- });
- }
-
- // Throw if any of the nearby regions are overlapping
- for (LocalRegion present : Iterables.concat(nearbyRegions, this.largeRegions)) {
- if (present != region && present.intersects(region)) {
- throw new OverlappingRegionException(present, region);
- }
- }
- }
-}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfiguration.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfiguration.java
new file mode 100644
index 0000000..d8edc89
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfiguration.java
@@ -0,0 +1,109 @@
+package me.samsuik.sakura.configuration.local;
+
+import io.papermc.paper.util.MCUtil;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import me.samsuik.sakura.configuration.local.ConfigurationContainer.SealedConfigurationContainer;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.util.BoundingBox;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+import java.util.*;
+
+@NullMarked
+public final class LocalConfiguration implements LocalConfigurationAccessor {
+ private static final CachedLocalConfiguration EMPTY_CONFIGURATION = CachedLocalConfiguration.emptyConfiguration();
+
+ private final LocalConfigurationContainers containers = new LocalConfigurationContainers();
+ private final Long2ObjectOpenHashMap cachedConfiguration = new Long2ObjectOpenHashMap<>();
+ private final CachedLocalConfiguration[] recentlyAccessed = new CachedLocalConfiguration[8];
+ private long lastGameTime;
+ private final Level level;
+
+ public LocalConfiguration(final Level level) {
+ this.level = level;
+ Arrays.fill(this.recentlyAccessed, EMPTY_CONFIGURATION);
+ }
+
+ @Override
+ public void set(final BoundingBox bb, final SealedConfigurationContainer container) {
+ final ConfigurationArea area = new ConfigurationArea(bb);
+ this.containers.add(area, container);
+
+ // As containers are immutable, we just need to clear the cache to provide an immediate update.
+ this.cachedConfiguration.clear();
+ Arrays.fill(this.recentlyAccessed, EMPTY_CONFIGURATION);
+ }
+
+ @Override
+ public @Nullable SealedConfigurationContainer remove(final BoundingBox bb) {
+ final ConfigurationArea area = new ConfigurationArea(bb);
+ final SealedConfigurationContainer container = this.containers.remove(area);
+
+ this.cachedConfiguration.clear();
+ Arrays.fill(this.recentlyAccessed, EMPTY_CONFIGURATION);
+ return container;
+ }
+
+ @Override
+ public @Nullable SealedConfigurationContainer get(final BoundingBox bb) {
+ return this.containers.get(new ConfigurationArea(bb));
+ }
+
+ @Override
+ public @Nullable ConfigurationContainer getContainer(final int x, final int y, final int z) {
+ return this.containers.getContainer(x, y, z);
+ }
+
+ @Override
+ public List getAreas(final int x, final int y, final int z) {
+ return this.containers.getAreas(x, y, z).stream()
+ .map(ConfigurationArea::asBoundingBox)
+ .toList();
+ }
+
+ public CachedLocalConfiguration at(final Vec3 vec3) {
+ return this.at(BlockPos.containing(vec3));
+ }
+
+ public CachedLocalConfiguration at(final BlockPos pos) {
+ // This is sometimes called off the main thread when loading/generating chunks
+ if (!MCUtil.isMainThread()) {
+ return EMPTY_CONFIGURATION;
+ }
+
+ final int x = pos.getX();
+ final int y = pos.getY();
+ final int z = pos.getZ();
+
+ final long sectionKey = ConfigurationArea.sectionKey(x, y, z, 2);
+ final int recentCacheIndex = ((x & 1) << 2) | ((y & 1) << 1) | (z & 1);
+ final CachedLocalConfiguration recentCache = recentlyAccessed[recentCacheIndex];
+
+ // Fast path if the local configuration was recently accessed
+ if (recentCache.sectionKey == sectionKey) {
+ return recentCache;
+ }
+
+ // Clear the cache every minute
+ final long gameTime = this.level.getGameTime();
+ if (gameTime - this.lastGameTime >= 60 * 20) {
+ this.cachedConfiguration.clear();
+ this.lastGameTime = gameTime;
+ }
+
+ // Get the local configuration from the cache, if that isn't possible then create one
+ CachedLocalConfiguration cache = this.cachedConfiguration.get(sectionKey);
+ //noinspection ConstantValue
+ if (cache == null) {
+ final ConfigurationContainer container = this.getContainer(x, y, z);
+ cache = new CachedLocalConfiguration(level, container, sectionKey);
+ this.cachedConfiguration.put(sectionKey, cache);
+ }
+
+ this.recentlyAccessed[recentCacheIndex] = cache;
+ return cache;
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigurationContainers.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigurationContainers.java
new file mode 100644
index 0000000..a9044c7
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigurationContainers.java
@@ -0,0 +1,144 @@
+package me.samsuik.sakura.configuration.local;
+
+import com.google.common.collect.Iterables;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+import java.util.*;
+
+import static me.samsuik.sakura.configuration.local.ConfigurationContainer.*;
+
+@NullMarked
+public final class LocalConfigurationContainers {
+ private static final long MASSIVE_REGION_SIZE = 0x180000000L;
+ private static final int LARGE_AREA_THRESHOLD = 6 * 6 * 6;
+ private static final Comparator AREA_BY_VOLUME = Comparator.comparingLong(ConfigurationArea::volume);
+
+ private final Map containers = new HashMap<>();
+ private final List largeAreas = new ArrayList<>();
+ private final Long2ObjectOpenHashMap> smallAreas = new Long2ObjectOpenHashMap<>();
+ private int sectionExponent = 4;
+ private int changes = 0;
+
+ private boolean isLargeArea(final ConfigurationArea area) {
+ return area.countSections(this.sectionExponent) > LARGE_AREA_THRESHOLD;
+ }
+
+ public void add(final ConfigurationArea area, final SealedConfigurationContainer container) {
+ final ConfigurationContainer presentContainer = this.containers.put(area, container);
+ if (presentContainer == null) {
+ if (this.isLargeArea(area)) {
+ this.largeAreas.add(area);
+ } else {
+ this.updateSections(area, false);
+ }
+
+ if ((changes++ & 15) == 0) {
+ this.resizeSections();
+ }
+ }
+ }
+
+ public @Nullable SealedConfigurationContainer remove(final ConfigurationArea area) {
+ final SealedConfigurationContainer container = this.containers.remove(area);
+ if (this.isLargeArea(area)) {
+ this.largeAreas.remove(area);
+ } else if (container != null) {
+ this.updateSections(area, true);
+ }
+
+ if ((changes++ & 15) == 0) {
+ this.resizeSections();
+ }
+
+ return container;
+ }
+
+ public @Nullable SealedConfigurationContainer get(final ConfigurationArea area) {
+ return this.containers.get(area);
+ }
+
+ public @Nullable ConfigurationContainer getContainer(final int x, final int y, final int z) {
+ final ConfigurationContainer newContainer = new ConfigurationContainer();
+ final List areas = this.getAreas(x, y, z);
+ areas.sort(AREA_BY_VOLUME); // sorted by size
+
+ for (final ConfigurationArea area : areas) {
+ if (area.contains(x, y, z)) {
+ newContainer.fillAbsentValues(this.containers.get(area));
+ }
+ }
+
+ return newContainer.contents().isEmpty() ? null : newContainer;
+ }
+
+ public List getAreas(final int x, final int y, final int z) {
+ final long sectionKey = ConfigurationArea.sectionKey(x, y, z, this.sectionExponent);
+ final List nearby = this.smallAreas.getOrDefault(sectionKey, Collections.emptyList());
+ final List foundAreas = new ArrayList<>();
+
+ for (final ConfigurationArea area : Iterables.concat(nearby, this.largeAreas)) {
+ if (area.contains(x, y, z)) {
+ foundAreas.add(area);
+ }
+ }
+
+ return foundAreas;
+ }
+
+ private int calculateNewSectionExponent() {
+ long totalSectionCount = 0;
+ int totalAreas = 0;
+ for (final ConfigurationArea area : this.containers.keySet()) {
+ final long sections = area.countSections(4);
+ if (sections < MASSIVE_REGION_SIZE) {
+ totalSectionCount += sections;
+ totalAreas++;
+ }
+ }
+
+ final long averageSectionCount = totalSectionCount / Math.max(totalAreas, 1);
+ for (int exponent = 4;; exponent++) {
+ if ((averageSectionCount >> exponent) < LARGE_AREA_THRESHOLD) {
+ return exponent;
+ }
+ }
+ }
+
+ private void resizeSections() {
+ final int newExponent = this.calculateNewSectionExponent();
+ if (newExponent == this.sectionExponent) {
+ return; // nothing has changed
+ }
+
+ this.sectionExponent = newExponent;
+ this.smallAreas.clear();
+ this.largeAreas.clear();
+
+ for (final ConfigurationArea area : this.containers.keySet()) {
+ if (this.isLargeArea(area)) {
+ this.largeAreas.add(area);
+ } else {
+ this.updateSections(area, false);
+ }
+ }
+ }
+
+ private void updateSections(final ConfigurationArea area, final boolean remove) {
+ area.forEach(this.sectionExponent, sectionKey -> {
+ if (remove) {
+ final List areas = this.smallAreas.get(sectionKey);
+ //noinspection ConstantValue
+ if (areas != null) {
+ areas.remove(area);
+ if (areas.isEmpty()) {
+ this.smallAreas.remove(sectionKey);
+ }
+ }
+ } else {
+ this.smallAreas.computeIfAbsent(sectionKey, k -> new ArrayList<>()).add(area);
+ }
+ });
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalValueConfig.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalValueConfig.java
deleted file mode 100644
index 1c442a9..0000000
--- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalValueConfig.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package me.samsuik.sakura.configuration.local;
-
-import io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation;
-import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
-import me.samsuik.sakura.explosion.durable.DurableMaterial;
-import me.samsuik.sakura.local.LocalValueKeys;
-import me.samsuik.sakura.local.storage.LocalValueStorage;
-import me.samsuik.sakura.physics.PhysicsVersion;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.level.block.Block;
-import org.bukkit.craftbukkit.util.CraftMagicNumbers;
-
-import java.util.Map;
-
-public final class LocalValueConfig {
- public Map durableMaterials;
- public RedstoneImplementation redstoneImplementation;
- public PhysicsVersion physicsVersion;
- public boolean consistentRadius;
- public boolean redstoneCache;
- public int lavaFlowSpeed = -1;
-
- LocalValueConfig(Level level) {
- this.durableMaterials = new Reference2ObjectOpenHashMap<>(level.sakuraConfig().cannons.explosion.durableMaterials);
- this.redstoneImplementation = level.paperConfig().misc.redstoneImplementation;
- this.physicsVersion = level.sakuraConfig().cannons.mechanics.physicsVersion;
- this.consistentRadius = level.sakuraConfig().cannons.explosion.consistentRadius;
- this.redstoneCache = level.sakuraConfig().technical.redstone.redstoneCache;
- }
-
- void loadFromStorage(LocalValueStorage storage) {
- storage.get(LocalValueKeys.DURABLE_MATERIALS).ifPresent(materials -> {
- materials.forEach((materialType, materialProperties) -> {
- Block nmsBlock = CraftMagicNumbers.getBlock(materialType);
- // temp, will be updated later
- DurableMaterial durableMaterial = new DurableMaterial(materialProperties.getKey(), materialProperties.getValue(), false);
- this.durableMaterials.put(nmsBlock, durableMaterial);
- });
- });
- storage.get(LocalValueKeys.REDSTONE_IMPLEMENTATION).ifPresent(implementation -> {
- this.redstoneImplementation = RedstoneImplementation.values()[implementation.ordinal()];
- });
- this.physicsVersion = storage.getOrDefault(LocalValueKeys.PHYSICS_VERSION, this.physicsVersion);
- this.consistentRadius = storage.getOrDefault(LocalValueKeys.CONSISTENT_EXPLOSION_RADIUS, this.consistentRadius);
- this.redstoneCache = storage.getOrDefault(LocalValueKeys.REDSTONE_CACHE, this.redstoneCache);
- this.lavaFlowSpeed = storage.getOrDefault(LocalValueKeys.LAVA_FLOW_SPEED, this.lavaFlowSpeed);
- }
-}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/OverlappingRegionException.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/OverlappingRegionException.java
deleted file mode 100644
index cd7e530..0000000
--- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/OverlappingRegionException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package me.samsuik.sakura.configuration.local;
-
-import me.samsuik.sakura.local.LocalRegion;
-
-public final class OverlappingRegionException extends RuntimeException {
- public OverlappingRegionException(LocalRegion presentRegion, LocalRegion region) {
- super("overlapping region (%s, %s)".formatted(presentRegion, region));
- }
-}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/serializer/MinecraftMechanicsTargetSerializer.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/serializer/MinecraftMechanicsTargetSerializer.java
new file mode 100644
index 0000000..a3fa11b
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/serializer/MinecraftMechanicsTargetSerializer.java
@@ -0,0 +1,50 @@
+package me.samsuik.sakura.configuration.serializer;
+
+import io.leangen.geantyref.TypeToken;
+import me.samsuik.sakura.mechanics.MechanicVersion;
+import me.samsuik.sakura.mechanics.MinecraftMechanicsTarget;
+import me.samsuik.sakura.mechanics.MinecraftVersionEncoding;
+import me.samsuik.sakura.mechanics.ServerType;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.NullMarked;
+import org.spongepowered.configurate.ConfigurationNode;
+import org.spongepowered.configurate.NodePath;
+import org.spongepowered.configurate.serialize.ScalarSerializer;
+import org.spongepowered.configurate.serialize.SerializationException;
+import org.spongepowered.configurate.serialize.TypeSerializer;
+
+import java.lang.reflect.Type;
+import java.util.function.Predicate;
+
+@NullMarked
+public final class MinecraftMechanicsTargetSerializer implements TypeSerializer {
+ private static final NodePath MECHANIC_VERSION = NodePath.path("mechanic-version");
+ private static final NodePath SERVER_TYPE = NodePath.path("server-type");
+
+ @Override
+ public MinecraftMechanicsTarget deserialize(final Type type, final ConfigurationNode root) throws SerializationException {
+ final String mechanicVersion = root.node(MECHANIC_VERSION).getString();
+ final String serverType = root.node(SERVER_TYPE).getString();
+ final MinecraftMechanicsTarget mechanicsTarget = MinecraftMechanicsTarget.fromString("%s+%s".formatted(mechanicVersion, serverType));
+
+ if (mechanicsTarget == null) {
+ throw new IllegalArgumentException("Unable to deserialize MinecraftMechanicsTarget (" + mechanicVersion + ", " + serverType + ")");
+ }
+
+ return mechanicsTarget;
+ }
+
+ @Override
+ public void serialize(
+ final Type type,
+ @Nullable MinecraftMechanicsTarget mechanicsTarget,
+ final ConfigurationNode root
+ ) throws SerializationException {
+ if (mechanicsTarget == null) {
+ mechanicsTarget = MinecraftMechanicsTarget.latest();
+ }
+
+ root.node(MECHANIC_VERSION).set(MechanicVersion.name(mechanicsTarget.mechanicVersion()));
+ root.node(SERVER_TYPE).set(ServerType.name(mechanicsTarget.serverType()));
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/ConfigurationTransformations.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/ConfigurationTransformations.java
index 30dfeaf..2f95f4e 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/ConfigurationTransformations.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/ConfigurationTransformations.java
@@ -29,6 +29,7 @@ public final class ConfigurationTransformations {
V8_RenameExplosionResistantItems.apply(versionedBuilder);
V9_RenameAllowNonTntBreakingDurableBlocks.apply(versionedBuilder);
V10_DurableMaterialOnlyDamagedByTnt.apply(versionedBuilder);
+ V11_RemovePhysicsVersion.apply(versionedBuilder);
// ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE
versionedBuilder.build().apply(node);
}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V11_RemovePhysicsVersion.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V11_RemovePhysicsVersion.java
new file mode 100644
index 0000000..3222f46
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/transformation/world/V11_RemovePhysicsVersion.java
@@ -0,0 +1,18 @@
+package me.samsuik.sakura.configuration.transformation.world;
+
+import org.spongepowered.configurate.NodePath;
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
+import org.spongepowered.configurate.transformation.TransformAction;
+
+import static org.spongepowered.configurate.NodePath.path;
+
+public final class V11_RemovePhysicsVersion {
+ private static final int VERSION = 11;
+ private static final NodePath PATH = path("cannons", "mechanics", "physics-version");
+
+ public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
+ builder.addVersion(VERSION, ConfigurationTransformation.builder()
+ .addAction(PATH, TransformAction.remove())
+ .build());
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/LegacyExplosionClipping.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/LegacyExplosionClipping.java
deleted file mode 100644
index 9b3ab13..0000000
--- a/sakura-server/src/main/java/me/samsuik/sakura/explosion/LegacyExplosionClipping.java
+++ /dev/null
@@ -1,179 +0,0 @@
-package me.samsuik.sakura.explosion;
-
-import net.minecraft.core.BlockPos;
-import net.minecraft.core.Direction;
-import net.minecraft.util.Mth;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.level.block.state.BlockState;
-import net.minecraft.world.level.chunk.LevelChunk;
-import net.minecraft.world.phys.AABB;
-import net.minecraft.world.phys.BlockHitResult;
-import net.minecraft.world.phys.Vec3;
-import net.minecraft.world.phys.shapes.VoxelShape;
-import org.jspecify.annotations.NullMarked;
-
-@NullMarked
-public final class LegacyExplosionClipping {
- public static BlockHitResult.Type clipLegacy(Level level, Vec3 from, Vec3 to) {
- int toX = Mth.floor(to.x);
- int toY = Mth.floor(to.y);
- int toZ = Mth.floor(to.z);
- int fromX = Mth.floor(from.x);
- int fromY = Mth.floor(from.y);
- int fromZ = Mth.floor(from.z);
-
- BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(fromX, fromY, fromZ);
- LevelChunk chunk = level.getChunkIfLoaded(fromX >> 4, fromZ >> 4);
- if (chunk == null) {
- return BlockHitResult.Type.MISS;
- }
-
- BlockState state = chunk.getBlockState(mutableBlockPos);
- VoxelShape shape = state.getShape(level, mutableBlockPos);
- for (AABB bb : shape.toAabbs()) {
- if (clip(bb, mutableBlockPos, from, to)) {
- return BlockHitResult.Type.BLOCK;
- }
- }
-
- for (int steps = 0; steps < 16; ++steps) {
- if (fromX == toX && fromY == toY && fromZ == toZ) {
- return BlockHitResult.Type.MISS;
- }
-
- boolean moveX = true;
- boolean moveY = true;
- boolean moveZ = true;
- double d0 = 999.0D;
- double d1 = 999.0D;
- double d2 = 999.0D;
-
- if (toX > fromX) {
- d0 = (double) fromX + 1.0D;
- } else if (toX < fromX) {
- d0 = (double) fromX + 0.0D;
- } else {
- moveX = false;
- }
-
- if (toY > fromY) {
- d1 = (double) fromY + 1.0D;
- } else if (toY < fromY) {
- d1 = (double) fromY + 0.0D;
- } else {
- moveY = false;
- }
-
- if (toZ > fromZ) {
- d2 = (double) fromZ + 1.0D;
- } else if (toZ < fromZ) {
- d2 = (double) fromZ + 0.0D;
- } else {
- moveZ = false;
- }
-
- double d3 = 999.0D;
- double d4 = 999.0D;
- double d5 = 999.0D;
- double d6 = to.x - from.x;
- double d7 = to.y - from.y;
- double d8 = to.z - from.z;
-
- if (moveX) d3 = (d0 - from.x) / d6;
- if (moveY) d4 = (d1 - from.y) / d7;
- if (moveZ) d5 = (d2 - from.z) / d8;
-
- if (d3 == -0.0D) d3 = -1.0E-4D;
- if (d4 == -0.0D) d4 = -1.0E-4D;
- if (d5 == -0.0D) d5 = -1.0E-4D;
-
- Direction moveDir;
- if (d3 < d4 && d3 < d5) {
- moveDir = toX > fromX ? Direction.WEST : Direction.EAST;
- from = new Vec3(d0, from.y + d7 * d3, from.z + d8 * d3);
- } else if (d4 < d5) {
- moveDir = toY > fromY ? Direction.DOWN : Direction.UP;
- from = new Vec3(from.x + d6 * d4, d1, from.z + d8 * d4);
- } else {
- moveDir = toZ > fromZ ? Direction.NORTH : Direction.SOUTH;
- from = new Vec3(from.x + d6 * d5, from.y + d7 * d5, d2);
- }
-
- fromX = Mth.floor(from.x) - (moveDir == Direction.EAST ? 1 : 0);
- fromY = Mth.floor(from.y) - (moveDir == Direction.UP ? 1 : 0);
- fromZ = Mth.floor(from.z) - (moveDir == Direction.SOUTH ? 1 : 0);
- mutableBlockPos.set(fromX, fromY, fromZ);
-
- int chunkX = fromX >> 4;
- int chunkZ = fromZ >> 4;
- if (chunkX != chunk.locX || chunkZ != chunk.locZ) {
- chunk = level.getChunkIfLoaded(chunkX, chunkZ);
- }
- if (chunk == null) {
- return BlockHitResult.Type.MISS;
- }
-
- state = chunk.getBlockState(mutableBlockPos);
- shape = state.getShape(level, mutableBlockPos);
- for (AABB bb : shape.toAabbs()) {
- if (clip(bb, mutableBlockPos, from, to)) {
- return BlockHitResult.Type.BLOCK;
- }
- }
- }
- return BlockHitResult.Type.MISS;
- }
-
- private static boolean clip(AABB bb, BlockPos pos, Vec3 from, Vec3 to) {
- from = from.subtract(pos.getX(), pos.getY(), pos.getZ());
- to = to.subtract(pos.getX(), pos.getY(), pos.getZ());
-
- double x = to.x - from.x;
- double y = to.y - from.y;
- double z = to.z - from.z;
-
- double minXd = clip(bb.minX, x, from.x);
- double minYd = clip(bb.minY, y, from.y);
- double minZd = clip(bb.minZ, z, from.z);
- double maxXd = clip(bb.maxX, x, from.x);
- double maxYd = clip(bb.maxY, y, from.y);
- double maxZd = clip(bb.maxZ, z, from.z);
-
- return clipX(from, bb, minXd, y, z) || clipY(from, bb, minYd, x, z) || clipZ(from, bb, minZd, x, y)
- || clipX(from, bb, maxXd, y, z) || clipY(from, bb, maxYd, x, z) || clipZ(from, bb, maxZd, x, y);
- }
-
- private static double clip(double bound, double axisD, double axisN) {
- if (axisD * axisD < 1.0000000116860974E-7D) {
- return -1.0;
- }
- return (bound - axisN) / axisD;
- }
-
- private static boolean clipX(Vec3 from, AABB bb, double n, double y, double z) {
- if (n < 0.0 || n > 1.0) {
- return false;
- }
- y = from.y + y * n;
- z = from.z + z * n;
- return y >= bb.minY && y <= bb.maxY && z >= bb.minZ && z <= bb.maxZ;
- }
-
- private static boolean clipY(Vec3 from, AABB bb, double n, double x, double z) {
- if (n < 0.0 || n > 1.0) {
- return false;
- }
- x = from.x + x * n;
- z = from.z + z * n;
- return x >= bb.minX && x <= bb.maxX && z >= bb.minZ && z <= bb.maxZ;
- }
-
- private static boolean clipZ(Vec3 from, AABB bb, double n, double x, double y) {
- if (n < 0.0 || n > 1.0) {
- return false;
- }
- x = from.x + x * n;
- y = from.y + y * n;
- return x >= bb.minX && x <= bb.maxX && y >= bb.minY && y <= bb.maxY;
- }
-}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/SpecialisedExplosion.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/SpecialisedExplosion.java
index 66c0e6e..6cc6e9e 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/explosion/SpecialisedExplosion.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/SpecialisedExplosion.java
@@ -4,6 +4,7 @@ import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import me.samsuik.sakura.mechanics.MechanicVersion;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
@@ -159,7 +160,7 @@ public abstract class SpecialisedExplosion extends ServerExplo
double z = entity.getZ() - pos.z;
double distance = Math.sqrt(x * x + y * y + z * z);
// Sakura start - configure cannon physics
- if (this.physics.before(1_17_0)) {
+ if (this.mechanicsTarget.before(MechanicVersion.v1_17)) {
distanceFromBottom = (float) distanceFromBottom;
distance = (float) distance;
}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/TntExplosion.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/TntExplosion.java
index 873ff96..630a206 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/explosion/TntExplosion.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/TntExplosion.java
@@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import me.samsuik.sakura.entity.EntityState;
import me.samsuik.sakura.entity.merge.MergeLevel;
import me.samsuik.sakura.entity.merge.MergeableEntity;
+import me.samsuik.sakura.mechanics.MechanicVersion;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
@@ -38,7 +39,7 @@ public final class TntExplosion extends SpecialisedExplosion {
// Sakura start - configure cannon physics
@Override
protected double getExplosionOffset() {
- return this.physics.before(1_10_0) ? (double) 0.49f : super.getExplosionOffset();
+ return this.mechanicsTarget.before(MechanicVersion.v1_10) ? (double) 0.49f : super.getExplosionOffset();
}
// Sakura end - configure cannon physics
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java
index e52cae2..d81dce7 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableBlockManager.java
@@ -3,9 +3,11 @@ package me.samsuik.sakura.explosion.durable;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.minecraft.core.BlockPos;
+import org.jspecify.annotations.NullMarked;
import java.util.concurrent.TimeUnit;
+@NullMarked
public final class DurableBlockManager {
private final Cache durableBlocks = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java b/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java
deleted file mode 100644
index c337091..0000000
--- a/sakura-server/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package me.samsuik.sakura.explosion.durable;
-
-import org.spongepowered.configurate.objectmapping.ConfigSerializable;
-import org.spongepowered.configurate.objectmapping.meta.Required;
-
-@ConfigSerializable
-public record DurableMaterial(int durability, float resistance, @Required boolean onlyDamagedByTnt) {
-}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/mechanics/EntityBehaviour.java b/sakura-server/src/main/java/me/samsuik/sakura/mechanics/EntityBehaviour.java
new file mode 100644
index 0000000..ab71cc8
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/mechanics/EntityBehaviour.java
@@ -0,0 +1,52 @@
+package me.samsuik.sakura.mechanics;
+
+import net.minecraft.core.Direction;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.Vec3;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+@NullMarked
+public final class EntityBehaviour {
+ public static void pre1_21_6$changeEntityPosition(
+ final Entity entity,
+ final Vec3 position,
+ final Vec3 relativeMovement,
+ final MinecraftMechanicsTarget mechanicsTarget
+ ) {
+ final Vec3 newPosition = position.add(relativeMovement);
+ final Vec3 newEntityPosition;
+ if (mechanicsTarget.is(MechanicVersion.v1_21_5)) {
+ newEntityPosition = manglePosition(position, relativeMovement);
+ entity.addMovementThisTick(new Entity.Movement(position, newPosition, true));
+ } else {
+ newEntityPosition = newPosition;
+ }
+
+ entity.setPos(newEntityPosition);
+ }
+
+ private static Vec3 manglePosition(final Vec3 position, final Vec3 relativeMovement) {
+ Vec3 newPosition = position;
+ for (final Direction.Axis axis : Entity.axisStepOrder(relativeMovement)) {
+ final double movement = relativeMovement.get(axis);
+ if (movement != 0.0) {
+ newPosition = newPosition.relative(axis.getPositive(), movement);
+ }
+ }
+
+ return newPosition;
+ }
+
+ public static boolean canMoveEntity(final double relativeMovementSqr, final Vec3 movement, final MinecraftMechanicsTarget mechanicsTarget) {
+ return relativeMovementSqr > 1.0E-7
+ || mechanicsTarget.atLeast(MechanicVersion.v1_21_2) && movement.lengthSqr() - relativeMovementSqr < 1.0E-7
+ || mechanicsTarget.before(MechanicVersion.v1_14);
+ }
+
+ public static boolean prioritiseXFirst(final double x, final double z, final @Nullable MinecraftMechanicsTarget mechanicsTarget) {
+ return mechanicsTarget == null || mechanicsTarget.atLeast(MechanicVersion.v1_14)
+ ? Math.abs(x) < Math.abs(z)
+ : mechanicsTarget.isLegacy() && Math.abs(x) > Math.abs(z);
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/mechanics/FallingBlockBehaviour.java b/sakura-server/src/main/java/me/samsuik/sakura/mechanics/FallingBlockBehaviour.java
new file mode 100644
index 0000000..6223b00
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/mechanics/FallingBlockBehaviour.java
@@ -0,0 +1,44 @@
+package me.samsuik.sakura.mechanics;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.item.FallingBlockEntity;
+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.FallingBlock;
+import net.minecraft.world.level.block.state.BlockState;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public final class FallingBlockBehaviour {
+ public static boolean isAbleToStackOnBlock(final FallingBlockEntity fallingBlock, final MinecraftMechanicsTarget mechanicsTarget) {
+ if (!mechanicsTarget.between(me.samsuik.sakura.mechanics.MechanicVersion.v1_9, me.samsuik.sakura.mechanics.MechanicVersion.v1_14)) {
+ return true;
+ }
+ // This is patched by default on Paper.
+ if (mechanicsTarget.isServerType(me.samsuik.sakura.mechanics.ServerType.PAPER)) {
+ return true;
+ }
+ // todo: Entity#getOnPos might be a good alternative to this
+ final BlockPos blockPos = BlockPos.containing(fallingBlock.getX(), fallingBlock.getY() - 0.001f, fallingBlock.getZ());
+ final BlockState state = fallingBlock.level().getBlockState(blockPos);
+ return !FallingBlock.isFree(state);
+ }
+
+ public static void removeBlockOnFall(final FallingBlockEntity fallingBlock, final Block block) {
+ final Level level = fallingBlock.level();
+ final BlockPos blockPos = fallingBlock.blockPosition();
+ final BlockState state = level.getBlockState(blockPos);
+
+ // todo: Do we need to call the event here? This event is already called in the fall method that spawns the falling block entity.
+ if (state.is(block) && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(fallingBlock, blockPos, Blocks.AIR.defaultBlockState())) {
+ level.removeBlock(blockPos, false);
+ } else {
+ if (state.is(block)) {
+ ((ServerLevel) level).getChunkSource().blockChanged(blockPos);
+ }
+ fallingBlock.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN);
+ }
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/mechanics/LegacyExplosionBlockClipping.java b/sakura-server/src/main/java/me/samsuik/sakura/mechanics/LegacyExplosionBlockClipping.java
new file mode 100644
index 0000000..2d69bd3
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/mechanics/LegacyExplosionBlockClipping.java
@@ -0,0 +1,182 @@
+package me.samsuik.sakura.mechanics;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.util.Mth;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.HitResult;
+import net.minecraft.world.phys.Vec3;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * A replica of the explosion raytrace code before it was replaced in Minecraft 1.14.
+ */
+@NullMarked
+public final class LegacyExplosionBlockClipping {
+ private static final double EPSILON = 1.0e-7f; // the precision loss is intentional
+
+ private Vec3 currentPos;
+ private final Vec3 endPos;
+ private final int toX;
+ private final int toY;
+ private final int toZ;
+
+ private LegacyExplosionBlockClipping(final Vec3 currentPos, final Vec3 endPos) {
+ this.currentPos = currentPos;
+ this.endPos = endPos;
+ this.toX = Mth.floor(endPos.x);
+ this.toY = Mth.floor(endPos.y);
+ this.toZ = Mth.floor(endPos.z);
+ }
+
+ public static BlockHitResult.Type clip(final Level level, final Vec3 currentPos, final Vec3 endPos) {
+ final LegacyExplosionBlockClipping clipDetection = new LegacyExplosionBlockClipping(currentPos, endPos);
+ final BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+ LevelChunk chunk = null;
+ int steps = 0;
+
+ do {
+ final int chunkX = Mth.floor(currentPos.x) >> 4;
+ final int chunkZ = Mth.floor(currentPos.z) >> 4;
+ if (chunk == null || chunkX != chunk.locX || chunkZ != chunk.locZ) {
+ chunk = level.getChunkIfLoaded(chunkX, chunkZ);
+ if (chunk == null) break;
+ }
+
+ final BlockState state = chunk.getBlockState(mutableBlockPos);
+ final VoxelShape shape = state.getShape(level, mutableBlockPos);
+ for (final AABB shapeBB : shape.toAabbs()) {
+ if (clip(shapeBB, mutableBlockPos, currentPos, endPos)) {
+ return HitResult.Type.BLOCK;
+ }
+ }
+ } while (++steps < 16 && clipDetection.next(mutableBlockPos));
+
+ return HitResult.Type.MISS;
+ }
+
+ private boolean next(final BlockPos.MutableBlockPos mutableBlockPos) {
+ final int currX = mutableBlockPos.getX();
+ final int currY = mutableBlockPos.getY();
+ final int currZ = mutableBlockPos.getZ();
+ final int toX = this.toX;
+ final int toY = this.toY;
+ final int toZ = this.toZ;
+
+ if (currX == toX && currY == toY && currZ == toZ) {
+ return false;
+ }
+
+ boolean moveX = true;
+ boolean moveY = true;
+ boolean moveZ = true;
+ double d0 = 999.0D;
+ double d1 = 999.0D;
+ double d2 = 999.0D;
+
+ if (toX > currX) {
+ d0 = (double) currX + 1.0D;
+ } else if (toX < currX) {
+ d0 = (double) currX + 0.0D;
+ } else {
+ moveX = false;
+ }
+
+ if (toY > currY) {
+ d1 = (double) currY + 1.0D;
+ } else if (toY < currY) {
+ d1 = (double) currY + 0.0D;
+ } else {
+ moveY = false;
+ }
+
+ if (toZ > currZ) {
+ d2 = (double) currZ + 1.0D;
+ } else if (toZ < currZ) {
+ d2 = (double) currZ + 0.0D;
+ } else {
+ moveZ = false;
+ }
+
+ double d3 = 999.0D;
+ double d4 = 999.0D;
+ double d5 = 999.0D;
+
+ final Vec3 currPos = this.currentPos;
+ final Vec3 endPos = this.endPos;
+ final double d6 = endPos.x - currPos.x;
+ final double d7 = endPos.y - currPos.y;
+ final double d8 = endPos.z - currPos.z;
+
+ if (moveX) d3 = (d0 - currPos.x) / d6;
+ if (moveY) d4 = (d1 - currPos.y) / d7;
+ if (moveZ) d5 = (d2 - currPos.z) / d8;
+
+ if (d3 == -0.0D) d3 = -1.0E-4D;
+ if (d4 == -0.0D) d4 = -1.0E-4D;
+ if (d5 == -0.0D) d5 = -1.0E-4D;
+
+ final Direction moveDir;
+ final Vec3 newCurrentPos;
+ if (d3 < d4 && d3 < d5) {
+ moveDir = toX > currX ? Direction.WEST : Direction.EAST;
+ newCurrentPos = new Vec3(d0, currPos.y + d7 * d3, currPos.z + d8 * d3);
+ } else if (d4 < d5) {
+ moveDir = toY > currY ? Direction.DOWN : Direction.UP;
+ newCurrentPos = new Vec3(currPos.x + d6 * d4, d1, currPos.z + d8 * d4);
+ } else {
+ moveDir = toZ > currZ ? Direction.NORTH : Direction.SOUTH;
+ newCurrentPos = new Vec3(currPos.x + d6 * d5, currPos.y + d7 * d5, d2);
+ }
+
+ mutableBlockPos.set(
+ Mth.floor(currPos.x) - (moveDir == Direction.EAST ? 1 : 0),
+ Mth.floor(currPos.y) - (moveDir == Direction.UP ? 1 : 0),
+ Mth.floor(currPos.z) - (moveDir == Direction.SOUTH ? 1 : 0)
+ );
+
+ this.currentPos = newCurrentPos;
+ return true;
+ }
+
+ private static boolean clip(final AABB bb, final BlockPos pos, final Vec3 from, final Vec3 to) {
+ final Vec3 origin = from.subtract(pos.getX(), pos.getY(), pos.getZ());
+ final Vec3 direction = to.subtract(pos.getX(), pos.getY(), pos.getZ()).subtract(origin);
+ double tmin = Double.NEGATIVE_INFINITY;
+ double tmax = Double.POSITIVE_INFINITY;
+
+ if (direction.x * direction.x >= EPSILON) {
+ final double t1 = (bb.minX - origin.x) / direction.x;
+ final double t2 = (bb.maxX - origin.x) / direction.x;
+ tmin = Math.max(tmin, Math.min(t1, t2));
+ tmax = Math.min(tmax, Math.max(t1, t2));
+ } else if (origin.x < bb.minX || origin.x > bb.maxX) {
+ return false;
+ }
+
+ if (direction.y * direction.y >= EPSILON) {
+ final double t1 = (bb.minY - origin.y) / direction.y;
+ final double t2 = (bb.maxY - origin.y) / direction.y;
+ tmin = Math.max(tmin, Math.min(t1, t2));
+ tmax = Math.min(tmax, Math.max(t1, t2));
+ } else if (origin.y < bb.minY || origin.y > bb.maxY) {
+ return false;
+ }
+
+ if (direction.z * direction.z >= EPSILON) {
+ double t1 = (bb.minZ - origin.z) / direction.z;
+ double t2 = (bb.maxZ - origin.z) / direction.z;
+ tmin = Math.max(tmin, Math.min(t1, t2));
+ tmax = Math.min(tmax, Math.max(t1, t2));
+ } else if (origin.z < bb.minZ || origin.z > bb.maxZ) {
+ return false;
+ }
+
+ return tmax >= tmin && tmax >= 0.0 && tmin <= 1.0;
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/mechanics/LiquidBehaviour.java b/sakura-server/src/main/java/me/samsuik/sakura/mechanics/LiquidBehaviour.java
new file mode 100644
index 0000000..317f793
--- /dev/null
+++ b/sakura-server/src/main/java/me/samsuik/sakura/mechanics/LiquidBehaviour.java
@@ -0,0 +1,34 @@
+package me.samsuik.sakura.mechanics;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.material.FlowingFluid;
+import net.minecraft.world.level.material.FluidState;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public final class LiquidBehaviour {
+ public static boolean canLiquidSolidify(
+ final Level level,
+ final BlockPos pos,
+ final FluidState fluidState,
+ final MinecraftMechanicsTarget mechanicsTarget
+ ) {
+ // In legacy-paper and versions since 1.16, liquids should always solidify.
+ if (mechanicsTarget.atLeast(MechanicVersion.v1_16) || mechanicsTarget.before(MechanicVersion.v1_10) && mechanicsTarget.isServerType(ServerType.PAPER)) {
+ return true;
+ }
+
+ // In 1.13 and later, liquids can only solidify if they occupy at least half of the block.
+ if (mechanicsTarget.atLeast(MechanicVersion.v1_13) && fluidState.getHeight(level, pos) >= 0.44444445f) {
+ return true;
+ }
+
+ // todo: not sure if this is necessary, this looks identical to the condition above.
+ if (mechanicsTarget.before(MechanicVersion.v1_13)) {
+ return FlowingFluid.getLegacyLevel(fluidState) < 4;
+ }
+
+ return true;
+ }
+}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java
index a7deeb4..479851c 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java
@@ -1,7 +1,7 @@
package me.samsuik.sakura.redstone;
import io.papermc.paper.configuration.WorldConfiguration;
-import me.samsuik.sakura.configuration.local.LocalValueConfig;
+import me.samsuik.sakura.configuration.local.CachedLocalConfiguration;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.redstone.Orientation;
@@ -13,10 +13,9 @@ public record RedstoneNetworkSource(WorldConfiguration.Misc.RedstoneImplementati
BlockPos position, @Nullable Orientation orientation,
int updateDepth, int newPower, int oldPower) {
- public static RedstoneNetworkSource createNetworkSource(Level level, LocalValueConfig localConfig, BlockPos pos,
+ public static RedstoneNetworkSource createNetworkSource(Level level, CachedLocalConfiguration localConfiguration, BlockPos pos,
@Nullable Orientation orientation, int newPower, int oldPower) {
- WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation = localConfig.redstoneImplementation;
int updateDepth = level.neighborUpdater.getUpdateDepth();
- return new RedstoneNetworkSource(redstoneImplementation, pos, orientation, updateDepth, newPower, oldPower);
+ return new RedstoneNetworkSource(localConfiguration.paperRedstoneImplementation(), pos, orientation, updateDepth, newPower, oldPower);
}
}
diff --git a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java
index 6a374b9..19da7b9 100644
--- a/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java
+++ b/sakura-server/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java
@@ -1,7 +1,7 @@
package me.samsuik.sakura.redstone;
import it.unimi.dsi.fastutil.objects.*;
-import me.samsuik.sakura.configuration.local.LocalValueConfig;
+import me.samsuik.sakura.configuration.local.CachedLocalConfiguration;
import me.samsuik.sakura.utils.TickExpiry;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -41,8 +41,8 @@ public final class RedstoneWireCache {
}
public boolean tryApplyFromCache(BlockPos pos, @Nullable Orientation orientation, int newPower, int oldPower) {
- LocalValueConfig localConfig = this.level.localConfig().config(pos);
- if (!localConfig.redstoneCache || this.isTrackingWireUpdates()) {
+ final CachedLocalConfiguration localConfiguration = this.level.localConfig().at(pos);
+ if (!localConfiguration.redstoneBehaviour.cache() || this.isTrackingWireUpdates()) {
return false;
}
@@ -51,7 +51,7 @@ public final class RedstoneWireCache {
return true;
}
- RedstoneNetworkSource networkSource = RedstoneNetworkSource.createNetworkSource(this.level, localConfig, pos, orientation, newPower, oldPower);
+ RedstoneNetworkSource networkSource = RedstoneNetworkSource.createNetworkSource(this.level, localConfiguration, pos, orientation, newPower, oldPower);
RedstoneNetwork network = this.networkCache.get(networkSource);
if (network != null) {
try {