mirror of
https://github.com/Samsuik/Sakura.git
synced 2025-12-30 04:09:09 +00:00
Rewrite local configuration api and expand "physics-version"
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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("<green>You are currently inside a area with a set local-config.");
|
||||
player.sendRichMessage("<green> - %.0f %.0f %.0f".formatted(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ()));
|
||||
player.sendRichMessage("<green> - %.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("<green>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("<green>Created a new area with size " + size);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tabComplete(final List<String> completions, final String[] args) throws IllegalArgumentException {
|
||||
completions.addAll(List.of("create", "delete"));
|
||||
}
|
||||
}
|
||||
@@ -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<LocalRegion> 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("<red>regions cannot overlap");
|
||||
} else {
|
||||
storageHandler.put(region, new LocalValueStorage());
|
||||
}
|
||||
}
|
||||
|
||||
if ("get".equalsIgnoreCase(args[0])) {
|
||||
player.sendRichMessage("<red>" + (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<String> list, String[] args) throws IllegalArgumentException {
|
||||
list.addAll(List.of("create", "get", "delete"));
|
||||
}
|
||||
}
|
||||
@@ -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 = "<dark_gray>(<light_purple>S</light_purple>) <white>This block has <gray><remaining></gray> of <gray><durability>";
|
||||
public String fpsSettingChange = "<dark_gray>(<light_purple>S</light_purple>) <gray><state> <yellow><name>";
|
||||
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;
|
||||
|
||||
@@ -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<GlobalConfigurati
|
||||
.defaultOptions(options -> options
|
||||
.header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER.apply(contextMap))
|
||||
.serializers(serializers -> serializers
|
||||
.register(new TypeToken<MinecraftMechanicsTarget>() {}, new MinecraftMechanicsTargetSerializer())
|
||||
.register(new TypeToken<Reference2IntMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2IntMap<?>>(Reference2IntOpenHashMap::new, Integer.TYPE))
|
||||
.register(new TypeToken<Reference2LongMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2LongMap<?>>(Reference2LongOpenHashMap::new, Long.TYPE))
|
||||
.register(new TypeToken<Table<?, ?, ?>>() {}, new TableSerializer())
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Block, DurableMaterial> 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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<LocalRegion, LocalValueStorage> storageMap = new Object2ObjectOpenHashMap<>();
|
||||
private final List<LocalRegion> largeRegions = new ObjectArrayList<>();
|
||||
private final Long2ObjectMap<List<LocalRegion>> smallRegions = new Long2ObjectOpenHashMap<>();
|
||||
private int regionExponent = 4;
|
||||
private final Long2ObjectMap<Pair<LocalValueConfig, TickExpiry>> 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<LocalRegion> locate(int x, int z) {
|
||||
int regionX = this.regionChunkCoord(x);
|
||||
int regionZ = this.regionChunkCoord(z);
|
||||
long regionPos = ChunkPos.asLong(regionX, regionZ);
|
||||
List<LocalRegion> 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<LocalRegion> 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<LocalRegion> 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<LocalRegion> 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<LocalValueConfig, TickExpiry> pair = this.chunkConfigCache.computeIfAbsent(chunkKey,
|
||||
k -> this.createLocalChunkConfigWithExpiry(position, gameTime));
|
||||
|
||||
pair.value().refresh(gameTime);
|
||||
return pair.key();
|
||||
}
|
||||
|
||||
private Pair<LocalValueConfig, TickExpiry> 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<LocalRegion> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<CachedLocalConfiguration> 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<BoundingBox> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<ConfigurationArea> AREA_BY_VOLUME = Comparator.comparingLong(ConfigurationArea::volume);
|
||||
|
||||
private final Map<ConfigurationArea, SealedConfigurationContainer> containers = new HashMap<>();
|
||||
private final List<ConfigurationArea> largeAreas = new ArrayList<>();
|
||||
private final Long2ObjectOpenHashMap<List<ConfigurationArea>> 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<ConfigurationArea> 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<ConfigurationArea> getAreas(final int x, final int y, final int z) {
|
||||
final long sectionKey = ConfigurationArea.sectionKey(x, y, z, this.sectionExponent);
|
||||
final List<ConfigurationArea> nearby = this.smallAreas.getOrDefault(sectionKey, Collections.emptyList());
|
||||
final List<ConfigurationArea> 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<ConfigurationArea> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<Block, DurableMaterial> 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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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<MinecraftMechanicsTarget> {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<T extends Entity> 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;
|
||||
}
|
||||
|
||||
@@ -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<PrimedTnt> {
|
||||
// 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
|
||||
|
||||
|
||||
@@ -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<BlockPos, DurableBlock> durableBlocks = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(1, TimeUnit.MINUTES)
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user