diff --git a/build-data/sakura.at b/build-data/sakura.at index 242c7da..b5ef3a5 100644 --- a/build-data/sakura.at +++ b/build-data/sakura.at @@ -23,3 +23,6 @@ public net.minecraft.world.level.Level neighborUpdater public net.minecraft.world.level.block.RedStoneWireBlock turbo public net.minecraft.world.level.entity.EntityTickList entities public net.minecraft.world.level.material.FlowingFluid getLegacyLevel(Lnet/minecraft/world/level/material/FluidState;)I +public net.minecraft.world.entity.Entity axisStepOrder(Lnet/minecraft/world/phys/Vec3;)Ljava/lang/Iterable; +public net.minecraft.world.entity.Entity$Movement +public net.minecraft.world.entity.Entity addMovementThisTick(Lnet/minecraft/world/entity/Entity$Movement;)V \ No newline at end of file diff --git a/sakura-api/build.gradle.kts.patch b/sakura-api/build.gradle.kts.patch index 2b27b97..4cae1f9 100644 --- a/sakura-api/build.gradle.kts.patch +++ b/sakura-api/build.gradle.kts.patch @@ -1,5 +1,13 @@ --- a/paper-api/build.gradle.kts +++ b/paper-api/build.gradle.kts +@@ -68,6 +_,7 @@ + api("org.apache.maven:maven-resolver-provider:3.9.6") // make API dependency for Paper Plugins + implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") + implementation("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") ++ compileOnly("org.spongepowered:configurate-yaml:4.2.0") + + // Annotations - Slowly migrate to jspecify + val annotations = "org.jetbrains:annotations:$annotationsVersion" @@ -90,7 +_,7 @@ testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/sakura-api/paper-patches/files/src/main/java/org/bukkit/World.java.patch b/sakura-api/paper-patches/files/src/main/java/org/bukkit/World.java.patch index 1fded9a..f9f8675 100644 --- a/sakura-api/paper-patches/files/src/main/java/org/bukkit/World.java.patch +++ b/sakura-api/paper-patches/files/src/main/java/org/bukkit/World.java.patch @@ -1,13 +1,14 @@ --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -205,6 +_,10 @@ - return new Location(this, x, y, z); - } - // Paper end -+ // Sakura start -+ @NotNull -+ me.samsuik.sakura.local.storage.LocalStorageHandler getStorageHandler(); -+ // Sakura end +@@ -4387,6 +_,11 @@ + void setSendViewDistance(int viewDistance); + // Paper end - view distance api ++ // Sakura start - local config api ++ @NotNull ++ me.samsuik.sakura.configuration.local.LocalConfigurationAccessor localConfig(); ++ // Sakura end - local config api ++ /** - * Gets the highest non-empty (impassable) block at the given coordinates. + * Gets all generated structures that intersect the chunk at the given + * coordinates.
diff --git a/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/ConfigurableKey.java b/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/ConfigurableKey.java new file mode 100644 index 0000000..f88ac6a --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/ConfigurableKey.java @@ -0,0 +1,37 @@ +package me.samsuik.sakura.configuration.local; + +import me.samsuik.sakura.explosion.durable.DurableMaterialsContainer.SealedDurableMaterialsContainer; +import me.samsuik.sakura.mechanics.MinecraftMechanicsTarget; +import me.samsuik.sakura.redstone.RedstoneConfiguration; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * A key for a configurable value. + */ +@NullMarked +public record ConfigurableKey(Class expectedType) { + public static final ConfigurableKey MECHANICS_TARGET = new ConfigurableKey<>(MinecraftMechanicsTarget.class); + public static final ConfigurableKey DURABLE_MATERIALS = new ConfigurableKey<>(SealedDurableMaterialsContainer.class); + public static final ConfigurableKey REDSTONE_BEHAVIOUR = new ConfigurableKey<>(RedstoneConfiguration.class); + public static final ConfigurableKey CONSISTENT_EXPLOSION_RADIUS = new ConfigurableKey<>(Boolean.class); + public static final ConfigurableKey LAVA_FLOW_SPEED = new ConfigurableKey<>(Integer.class); + + public T validate(@Nullable final Object value) { + final T casted = this.conform(value); + if (casted == null) { + throw new IllegalArgumentException("Value cannot be null for key " + this); + } + return casted; + } + + public @Nullable T conform(@Nullable final Object value) { + if (value == null) { + return null; + } + if (!this.expectedType.isInstance(value)) { + throw new IllegalArgumentException("Expected type " + this.expectedType.getName() + " but got " + value.getClass().getName()); + } + return this.expectedType.cast(value); + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/ConfigurationContainer.java b/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/ConfigurationContainer.java new file mode 100644 index 0000000..6ed7329 --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/ConfigurationContainer.java @@ -0,0 +1,106 @@ +package me.samsuik.sakura.configuration.local; + +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A container for configuration values. + */ +@NullMarked +public sealed class ConfigurationContainer implements Container, Object> { + private final IdentityHashMap, Object> values = new IdentityHashMap<>(); + + public static SealedConfigurationContainer sealedContainer(final Object... contents) { + if (contents.length % 2 != 0) { + throw new IllegalArgumentException("Expected an even number of contents, got " + contents.length); + } + final IdentityHashMap, Object> values = new IdentityHashMap<>(); + for (int index = 0; index < contents.length; index += 2) { + final Object key = contents[index]; + final Object value = contents[index + 1]; + if (!(key instanceof ConfigurableKey configurableKey)) { + throw new IllegalArgumentException("Key at index " + index + " must be of type ConfigurableKey"); + } + values.put(configurableKey, configurableKey.validate(value)); + } + return new SealedConfigurationContainer(values); + } + + private ConfigurationContainer(final IdentityHashMap, Object> values) { + this.values.putAll(values); + } + + public ConfigurationContainer() {} + + public @Nullable V set(final ConfigurableKey key, final V value) { + Preconditions.checkNotNull(value, "Value cannot be null"); + return key.conform(this.values.put(key, value)); + } + + public @Nullable V remove(final ConfigurableKey key) { + return key.conform(this.values.remove(key)); + } + + public final @Nullable V get(final ConfigurableKey key) { + return key.conform(this.values.get(key)); + } + + public final Optional getOptional(final ConfigurableKey key) { + return Optional.ofNullable(this.get(key)); + } + + @ApiStatus.Internal + public final void fillAbsentValues(final ConfigurationContainer container) { + for (final Map.Entry, Object> entry : container.values.entrySet()) { + this.values.putIfAbsent(entry.getKey(), entry.getValue()); + } + } + + public void clear() { + this.values.clear(); + } + + @Override + public final Map, Object> contents() { + return Map.copyOf(this.values); + } + + public final ConfigurationContainer open() { + return this instanceof SealedConfigurationContainer + ? new ConfigurationContainer(this.values) + : this; + } + + public final SealedConfigurationContainer seal() { + return this instanceof SealedConfigurationContainer sealed + ? sealed + : new SealedConfigurationContainer(this.values); + } + + public static final class SealedConfigurationContainer extends ConfigurationContainer { + private SealedConfigurationContainer(final IdentityHashMap, Object> values) { + super(values); + } + + @Override + public V set(final ConfigurableKey key, final V value) { + throw new UnsupportedOperationException("Container is sealed"); + } + + @Override + public V remove(final ConfigurableKey key) { + throw new UnsupportedOperationException("Container is sealed"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Container is sealed"); + } + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/Container.java b/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/Container.java new file mode 100644 index 0000000..358cd91 --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/Container.java @@ -0,0 +1,14 @@ +package me.samsuik.sakura.configuration.local; + +import org.jspecify.annotations.NullMarked; + +import java.util.Map; + +@NullMarked +public interface Container { + Map contents(); + + Container open(); + + Container seal(); +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigurationAccessor.java b/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigurationAccessor.java new file mode 100644 index 0000000..e27062d --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigurationAccessor.java @@ -0,0 +1,75 @@ +package me.samsuik.sakura.configuration.local; + +import io.papermc.paper.math.Position; +import me.samsuik.sakura.configuration.local.ConfigurationContainer.SealedConfigurationContainer; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +/** + * An accessor for local configuration containers. + */ +@NullMarked +public interface LocalConfigurationAccessor { + default void set(final BoundingBox area, final ConfigurableKey key, final V value) { + final ConfigurationContainer container = this.get(area); + final ConfigurationContainer newContainer = container != null + ? container.open() + : new ConfigurationContainer(); + newContainer.set(key, value); + this.set(area, newContainer.seal()); + } + + default void remove(final BoundingBox area, final ConfigurableKey key) { + final ConfigurationContainer container = this.get(area); + if (container != null) { + final ConfigurationContainer newContainer = container.open(); + newContainer.remove(key); + this.set(area, newContainer.seal()); + } + } + + default @Nullable T get(final BoundingBox area, final ConfigurableKey key) { + final ConfigurationContainer container = this.get(area); + return container == null ? null : container.get(key); + } + + void set(final BoundingBox area, final SealedConfigurationContainer container); + + @Nullable SealedConfigurationContainer remove(final BoundingBox area); + + @Nullable SealedConfigurationContainer get(final BoundingBox area); + + default @Nullable T getValue(final Vector vector, final ConfigurableKey key) { + final ConfigurationContainer container = this.getContainer(vector); + return container != null ? container.get(key) : null; + } + + default @Nullable T getValue(final Position position, final ConfigurableKey key) { + final ConfigurationContainer container = this.getContainer(position); + return container != null ? container.get(key) : null; + } + + default @Nullable ConfigurationContainer getContainer(final Vector vector) { + return this.getContainer(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); + } + + default @Nullable ConfigurationContainer getContainer(final Position position) { + return this.getContainer(position.blockX(), position.blockY(), position.blockZ()); + } + + @Nullable ConfigurationContainer getContainer(final int x, final int y, final int z); + + default List getAreas(final Vector vector) { + return this.getAreas(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); + } + + default List getAreas(final Position position) { + return this.getAreas(position.blockX(), position.blockY(), position.blockZ()); + } + + List getAreas(final int x, final int y, final int z); +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java b/sakura-api/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java new file mode 100644 index 0000000..9838d9f --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java @@ -0,0 +1,40 @@ +package me.samsuik.sakura.explosion.durable; + +import org.jspecify.annotations.NullMarked; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@NullMarked +@ConfigSerializable +public record DurableMaterial(int durability, float resistance, boolean onlyDamagedByTnt) { + public DurableMaterial(final int durability, final float resistance) { + this(durability, resistance, true); + } + + public boolean replaceBlastResistance() { + return this.resistance >= 0.0f; + } + + public boolean applyDurability() { + return this.durability >= 0; + } + + public static DurableMaterial durability(final int durability) { + return new DurableMaterial(durability, 0.0f); + } + + public static DurableMaterial resistance(final float resistance) { + return new DurableMaterial(1, resistance); + } + + public static DurableMaterial likeSand(final int durability) { + return new DurableMaterial(durability, 3.0f); + } + + public static DurableMaterial likeCobblestone(final int durability) { + return new DurableMaterial(durability, 6.0f); + } + + public static DurableMaterial likeEndstone(final int durability) { + return new DurableMaterial(durability, 9.0f); + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterialsContainer.java b/sakura-api/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterialsContainer.java new file mode 100644 index 0000000..6fa6ae6 --- /dev/null +++ b/sakura-api/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterialsContainer.java @@ -0,0 +1,123 @@ +package me.samsuik.sakura.explosion.durable; + +import com.google.common.base.Preconditions; +import me.samsuik.sakura.configuration.local.Container; +import org.bukkit.Material; +import org.bukkit.block.BlockType; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * A container for durable materials. + */ +@NullMarked +public sealed class DurableMaterialsContainer implements Container { + private final Map materials = new IdentityHashMap<>(); + + public static SealedDurableMaterialsContainer sealedContainer(final Object... contents) { + if (contents.length % 2 != 0) { + throw new IllegalArgumentException("Expected an even number of contents, got " + contents.length); + } + final Map materials = new IdentityHashMap<>(); + for (int index = 0; index < contents.length; index += 2) { + Object key = contents[index]; + Object value = contents[index + 1]; + if (key instanceof Material bukkitMaterial) { + key = blockTypeFromBukkitMaterial(bukkitMaterial); + } + if (!(key instanceof BlockType blockType)) { + throw new IllegalArgumentException("Key at index " + index + " must be of type BlockType or Material"); + } + if (!(value instanceof DurableMaterial material)) { + throw new IllegalArgumentException("Value at index " + (index + 1) + " must be of type DurableMaterial"); + } + materials.put(blockType, material); + } + return new SealedDurableMaterialsContainer(materials); + } + + private DurableMaterialsContainer(final Map materials) { + this.materials.putAll(materials); + } + + public DurableMaterialsContainer() {} + + private static BlockType blockTypeFromBukkitMaterial(final Material bukkitMaterial) { + final BlockType blockType = bukkitMaterial.asBlockType(); + Preconditions.checkNotNull(blockType, "Material " + bukkitMaterial + " is not a block"); + return blockType; + } + + @Deprecated + public final @Nullable DurableMaterial set(final Material bukkitMaterial, final DurableMaterial material) { + return this.set(blockTypeFromBukkitMaterial(bukkitMaterial), material); + } + + @Deprecated + public final @Nullable DurableMaterial remove(final Material bukkitMaterial) { + return this.remove(blockTypeFromBukkitMaterial(bukkitMaterial)); + } + + @Deprecated + public final @Nullable DurableMaterial get(final Material bukkitMaterial) { + return this.get(blockTypeFromBukkitMaterial(bukkitMaterial)); + } + + public @Nullable DurableMaterial set(final BlockType blockType, final DurableMaterial material) { + Preconditions.checkNotNull(material, "Material cannot be null"); + return this.materials.put(blockType, material); + } + + public @Nullable DurableMaterial remove(final BlockType blockType) { + return this.materials.remove(blockType); + } + + public final @Nullable DurableMaterial get(final BlockType blockType) { + return this.materials.get(blockType); + } + + public void clear() { + this.materials.clear(); + } + + @Override + public final Map contents() { + return Map.copyOf(this.materials); + } + + public final DurableMaterialsContainer open() { + return this instanceof SealedDurableMaterialsContainer + ? new DurableMaterialsContainer(this.materials) + : this; + } + + public final SealedDurableMaterialsContainer seal() { + return this instanceof SealedDurableMaterialsContainer sealed + ? sealed + : new SealedDurableMaterialsContainer(this.materials); + } + + public static final class SealedDurableMaterialsContainer extends DurableMaterialsContainer { + private SealedDurableMaterialsContainer(final Map materials) { + super(materials); + } + + @Override + public DurableMaterial set(final BlockType key, final DurableMaterial value) { + throw new UnsupportedOperationException("Container is sealed"); + } + + @Override + public DurableMaterial remove(final BlockType key) { + throw new UnsupportedOperationException("Container is sealed"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Container is sealed"); + } + } +} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java deleted file mode 100644 index ab9efc5..0000000 --- a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java +++ /dev/null @@ -1,47 +0,0 @@ -package me.samsuik.sakura.local; - -import io.papermc.paper.math.Position; -import org.bukkit.util.BoundingBox; -import org.bukkit.util.Vector; -import org.jspecify.annotations.NullMarked; - -@NullMarked -@Deprecated(forRemoval = true) -public record LocalRegion(int minX, int minZ, int maxX, int maxZ) { - public static LocalRegion from(BoundingBox boundingBox) { - return of(boundingBox.getMin(), boundingBox.getMax()); - } - - public static LocalRegion of(Vector min, Vector max) { - return of(min.getBlockX(), min.getBlockZ(), max.getBlockX(), max.getBlockZ()); - } - - public static LocalRegion of(Position min, Position max) { - return of(min.blockX(), min.blockZ(), max.blockX(), max.blockZ()); - } - - public static LocalRegion of(int minX, int minZ, int maxX, int maxZ) { - return new LocalRegion( - Math.min(minX, maxX), Math.min(minZ, maxZ), - Math.max(minX, maxX), Math.max(minZ, maxZ) - ); - } - - public static LocalRegion at(int x, int z, int radius) { - return new LocalRegion(x-radius, z-radius, x+radius, z+radius); - } - - public boolean intersects(LocalRegion region) { - return (this.minX <= region.minX() && this.maxX >= region.minX() || this.maxX >= region.maxX() && this.minX < region.maxX()) - && (this.minZ <= region.minZ() && this.maxZ >= region.minZ() || this.maxZ >= region.maxZ() && this.minZ < region.maxZ()); - } - - public boolean contains(LocalRegion region) { - return this.minX < region.minX() && this.maxX > region.maxX() - && this.maxZ < region.minZ() && this.maxZ > region.maxZ(); - } - - public boolean contains(int x, int z) { - return this.minX <= x && this.maxX >= x && this.minZ <= z && this.maxZ >= z; - } -} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKey.java b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKey.java deleted file mode 100644 index caa5459..0000000 --- a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKey.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.samsuik.sakura.local; - -import org.bukkit.NamespacedKey; -import org.jspecify.annotations.NullMarked; - -import java.util.function.Supplier; - -@NullMarked -@Deprecated(forRemoval = true) -public record LocalValueKey(NamespacedKey key, Supplier defaultSupplier) { - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || this.getClass() != o.getClass()) return false; - - LocalValueKey that = (LocalValueKey) o; - return this.key.equals(that.key); - } - - @Override - public int hashCode() { - return this.key.hashCode(); - } -} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKeys.java b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKeys.java deleted file mode 100644 index 88a62f4..0000000 --- a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalValueKeys.java +++ /dev/null @@ -1,26 +0,0 @@ -package me.samsuik.sakura.local; - -import me.samsuik.sakura.physics.PhysicsVersion; -import me.samsuik.sakura.redstone.RedstoneImplementation; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -@Deprecated(forRemoval = true) -public final class LocalValueKeys { - private static final String NAMESPACE = "sakura"; - - public static final LocalValueKey PHYSICS_VERSION = create("physics-version", () -> PhysicsVersion.LATEST); - public static final LocalValueKey>> DURABLE_MATERIALS = create("durable-materials", HashMap::new); - public static final LocalValueKey REDSTONE_IMPLEMENTATION = create("redstone-implementation", () -> RedstoneImplementation.VANILLA); - public static final LocalValueKey CONSISTENT_EXPLOSION_RADIUS = create("consistent-radius", () -> false); - public static final LocalValueKey REDSTONE_CACHE = create("redstone-cache", () -> false); - public static final LocalValueKey LAVA_FLOW_SPEED = create("lava-flow-speed", () -> -1); - - private static LocalValueKey create(String key, Supplier supplier) { - return new LocalValueKey<>(new NamespacedKey(NAMESPACE, key), supplier); - } -} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalStorageHandler.java b/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalStorageHandler.java deleted file mode 100644 index 43a51b4..0000000 --- a/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalStorageHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.samsuik.sakura.local.storage; - -import me.samsuik.sakura.local.LocalRegion; -import org.bukkit.Location; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.util.List; -import java.util.Optional; - -@Deprecated(forRemoval = true) -public interface LocalStorageHandler { - default @NonNull Optional locate(@NonNull Location location) { - return this.locate(location.blockX(), location.blockZ()); - } - - @NonNull Optional locate(int x, int z); - - @Nullable LocalValueStorage get(@NonNull LocalRegion region); - - boolean has(@NonNull LocalRegion region); - - void put(@NonNull LocalRegion region, @NonNull LocalValueStorage storage); - - void remove(@NonNull LocalRegion region); - - @NonNull List regions(); -} diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalValueStorage.java b/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalValueStorage.java deleted file mode 100644 index dc9ff6e..0000000 --- a/sakura-api/src/main/java/me/samsuik/sakura/local/storage/LocalValueStorage.java +++ /dev/null @@ -1,51 +0,0 @@ -package me.samsuik.sakura.local.storage; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import me.samsuik.sakura.local.LocalValueKey; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -import java.util.Map; -import java.util.Optional; - -@NullMarked -@Deprecated(forRemoval = true) -@SuppressWarnings("unchecked") -public final class LocalValueStorage { - private final Map, Object> map = new Object2ObjectOpenHashMap<>(); - - public void set(LocalValueKey key, T insert) { - this.map.put(key, insert); - } - - public void remove(LocalValueKey key) { - this.map.remove(key); - } - - public Optional get(LocalValueKey key) { - T value = (T) this.map.get(key); - return Optional.ofNullable(value); - } - - public T getOrDefault(LocalValueKey key, T def) { - return (T) this.map.getOrDefault(key, def); - } - - public boolean exists(LocalValueKey key) { - return this.map.containsKey(key); - } - - @Nullable - public T value(LocalValueKey key) { - return (T) this.map.get(key); - } - - public T value(LocalValueKey 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 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 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 {