9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-23 17:09:29 +00:00

Merge branch 'ver/1.21.4' into dev/1.21.5

This commit is contained in:
Dreeam
2025-06-03 05:53:33 +08:00
56 changed files with 3006 additions and 1421 deletions

View File

@@ -18,7 +18,6 @@ public class AsyncGoalExecutor {
protected final SpscIntQueue queue;
protected final SpscIntQueue wake;
protected final IntArrayList submit;
private final AsyncGoalThread thread;
private final ServerLevel world;
private long midTickCount = 0L;
@@ -27,7 +26,6 @@ public class AsyncGoalExecutor {
this.queue = new SpscIntQueue(AsyncTargetFinding.queueSize);
this.wake = new SpscIntQueue(AsyncTargetFinding.queueSize);
this.submit = new IntArrayList();
this.thread = thread;
}
boolean wake(int id) {
@@ -46,7 +44,18 @@ public class AsyncGoalExecutor {
public final void tick() {
batchSubmit();
LockSupport.unpark(thread);
while (true) {
OptionalInt result = this.wake.recv();
if (result.isEmpty()) {
break;
}
int id = result.getAsInt();
if (poll(id) && !this.queue.send(id)) {
do {
wake(id);
} while (poll(id));
}
}
}
private void batchSubmit() {
@@ -67,18 +76,6 @@ public class AsyncGoalExecutor {
}
public final void midTick() {
while (true) {
OptionalInt result = this.wake.recv();
if (result.isEmpty()) {
break;
}
int id = result.getAsInt();
if (poll(id) && !this.queue.send(id)) {
do {
wake(id);
} while (poll(id));
}
}
if (AsyncTargetFinding.threshold <= 0L || (midTickCount % AsyncTargetFinding.threshold) == 0L) {
batchSubmit();
}

View File

@@ -1,5 +1,6 @@
package org.dreeam.leaf.async.chunk;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import net.minecraft.Util;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -14,13 +15,13 @@ public class AsyncChunkSend {
public static final ExecutorService POOL = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
new com.google.common.util.concurrent.ThreadFactoryBuilder()
.setPriority(Thread.NORM_PRIORITY - 2)
new ThreadFactoryBuilder()
.setPriority(Thread.NORM_PRIORITY)
.setNameFormat("Leaf Async Chunk Send Thread")
.setUncaughtExceptionHandler(Util::onThreadException)
.setThreadFactory(AsyncChunkSendThread::new)
.build(),
new ThreadPoolExecutor.DiscardPolicy()
new ThreadPoolExecutor.CallerRunsPolicy()
);
public static final Logger LOGGER = LogManager.getLogger("Leaf Async Chunk Send");
}

View File

@@ -5,6 +5,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.pathfinder.Path;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dreeam.leaf.config.modules.async.AsyncPathfinding;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -29,8 +30,8 @@ public class AsyncPathProcessor {
private static long lastWarnMillis = System.currentTimeMillis();
private static final ThreadPoolExecutor pathProcessingExecutor = new ThreadPoolExecutor(
1,
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads,
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,
AsyncPathfinding.asyncPathfindingMaxThreads,
AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,
getQueueImpl(),
new ThreadFactoryBuilder()
.setNameFormat(THREAD_PREFIX + " Thread - %d")
@@ -44,7 +45,7 @@ public class AsyncPathProcessor {
public void rejectedExecution(Runnable rejectedTask, ThreadPoolExecutor executor) {
BlockingQueue<Runnable> workQueue = executor.getQueue();
if (!executor.isShutdown()) {
switch (org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingRejectPolicy) {
switch (AsyncPathfinding.asyncPathfindingRejectPolicy) {
case FLUSH_ALL -> {
if (!workQueue.isEmpty()) {
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
@@ -98,7 +99,7 @@ public class AsyncPathProcessor {
}
private static BlockingQueue<Runnable> getQueueImpl() {
final int queueCapacity = org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingQueueSize;
final int queueCapacity = AsyncPathfinding.asyncPathfindingQueueSize;
return new LinkedBlockingQueue<>(queueCapacity);
}

View File

@@ -32,15 +32,6 @@ public class MultithreadedTracker {
private static long lastWarnMillis = System.currentTimeMillis();
private static ThreadPoolExecutor TRACKER_EXECUTOR = null;
private record SendChanges(Object[] entities, int size) implements Runnable {
@Override
public void run() {
for (int i = 0; i < size; i++) {
((ServerEntity) entities[i]).sendDirtyEntityData();
}
}
}
private MultithreadedTracker() {
}
@@ -80,7 +71,6 @@ public class MultithreadedTracker {
// Move tracking to off-main
TRACKER_EXECUTOR.execute(() -> {
ReferenceArrayList<ServerEntity> sendDirty = new ReferenceArrayList<>();
for (final Entity entity : trackerEntitiesRaw) {
if (entity == null) continue;
@@ -88,19 +78,12 @@ public class MultithreadedTracker {
if (tracker == null) continue;
// Don't Parallel Tick Tracker of Entity
synchronized (tracker.sync) {
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
synchronized (tracker) {
var trackedChunk = nearbyPlayers.getChunk(entity.chunkPosition());
tracker.moonrise$tick(trackedChunk);
tracker.serverEntity.sendChanges();
if (tracker.serverEntity.wantSendDirtyEntityData) {
tracker.serverEntity.wantSendDirtyEntityData = false;
sendDirty.add(tracker.serverEntity);
}
}
}
if (!sendDirty.isEmpty()) {
level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size()));
}
});
}
@@ -121,7 +104,7 @@ public class MultithreadedTracker {
if (tracker == null) continue;
synchronized (tracker.sync) {
synchronized (tracker) {
tickTask[index] = tracker.leafTickCompact(nearbyPlayers.getChunk(entity.chunkPosition()));
sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
}
@@ -140,22 +123,6 @@ public class MultithreadedTracker {
sendChanges.run();
}
ReferenceArrayList<ServerEntity> sendDirty = new ReferenceArrayList<>();
for (final Entity entity : trackerEntitiesRaw) {
if (entity == null) continue;
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
if (tracker == null) continue;
if (tracker.serverEntity.wantSendDirtyEntityData) {
tracker.serverEntity.wantSendDirtyEntityData = false;
sendDirty.add(tracker.serverEntity);
}
}
if (!sendDirty.isEmpty()) {
level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size()));
}
});
}

View File

@@ -227,8 +227,12 @@ public class LeafConfig {
extraConfigs.addAll(Arrays.asList(existing.split(",")));
}
// Use same way in spark's BukkitServerConfigProvider#getNestedFiles to get all world configs
// It may spam in the spark profiler, but it's ok, since spark uses YamlConfigParser.INSTANCE to
// get configs defined in extra config flag instead of using SplitYamlConfigParser.INSTANCE
for (World world : Bukkit.getWorlds()) {
extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config
Path galeWorldFolder = world.getWorldFolder().toPath().resolve("gale-world.yml");
extraConfigs.add(galeWorldFolder.toString().replace("\\", "/").replace("./", "")); // Gale world config
}
return extraConfigs;

View File

@@ -8,15 +8,18 @@ import org.dreeam.leaf.config.annotations.Experimental;
public class SparklyPaperParallelWorldTicking extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-tracking";
} // TODO: Correct config key when stable
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-ticking";
}
@Experimental
public static boolean enabled = false;
public static int threads = 8;
public static boolean logContainerCreationStacktraces = false;
public static boolean disableHardThrow = false;
@Deprecated
public static boolean runAsyncTasksSync = false;
// STRICT, BUFFERED, DISABLED
public static String asyncUnsafeReadHandling = "BUFFERED";
@Override
public void onLoaded() {
@@ -34,15 +37,30 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules {
} else {
threads = 0;
}
logContainerCreationStacktraces = config.getBoolean(getBasePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces);
logContainerCreationStacktraces = enabled && logContainerCreationStacktraces;
disableHardThrow = config.getBoolean(getBasePath() + ".disable-hard-throw", disableHardThrow);
disableHardThrow = enabled && disableHardThrow;
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", runAsyncTasksSync);
runAsyncTasksSync = enabled && runAsyncTasksSync;
asyncUnsafeReadHandling = config.getString(getBasePath() + ".async-unsafe-read-handling", asyncUnsafeReadHandling).toUpperCase();
if (!asyncUnsafeReadHandling.equals("STRICT") && !asyncUnsafeReadHandling.equals("BUFFERED") && !asyncUnsafeReadHandling.equals("DISABLED")) {
LeafConfig.LOGGER.warn("Invalid value for {}.async-unsafe-read-handling: {}, fallback to STRICT.", getBasePath(), asyncUnsafeReadHandling);
asyncUnsafeReadHandling = "STRICT";
}
if (!enabled) {
asyncUnsafeReadHandling = "DISABLED";
}
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", false); // Default to false now
if (runAsyncTasksSync) {
LeafConfig.LOGGER.warn("The setting '{}.run-async-tasks-sync' is deprecated. Use 'async-unsafe-read-handling: STRICT' for similar safety checks or 'BUFFERED' for buffered reads.", getBasePath());
}
if (enabled) {
LeafConfig.LOGGER.info("Using {} threads for Parallel World Ticking", threads);
}
runAsyncTasksSync = enabled && runAsyncTasksSync; // Auto-disable if main feature is off
}
}

View File

@@ -0,0 +1,18 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class OptimizedPoweredRails extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".optimized-powered-rails";
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config().getBoolean(getBasePath(), enabled);
}
}

View File

@@ -11,7 +11,7 @@ import java.util.*;
import java.util.AbstractMap.SimpleEntry;
// fast array backend map with O(1) get & put & remove
public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, AttributeInstance>, Cloneable {
public final class AttributeInstanceArrayMap implements Map<Holder<Attribute>, AttributeInstance>, Cloneable {
private int size = 0;
private transient AttributeInstance[] a = new AttributeInstance[32];
@@ -46,17 +46,17 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
}
@Override
public final int size() {
public int size() {
return size;
}
@Override
public final boolean isEmpty() {
public boolean isEmpty() {
return size == 0;
}
@Override
public final boolean containsKey(Object key) {
public boolean containsKey(Object key) {
if (key instanceof Holder<?> holder && holder.value() instanceof Attribute attribute) {
int uid = attribute.uid;
return uid >= 0 && uid < a.length && a[uid] != null;
@@ -65,22 +65,22 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
}
@Override
public final boolean containsValue(Object value) {
for (final AttributeInstance instance : a) {
if (Objects.equals(value, instance)) {
return true;
}
}
return false;
public boolean containsValue(Object value) {
return value instanceof AttributeInstance val && Objects.equals(getInstance(val.getAttribute().value().uid), val);
}
@Override
public final AttributeInstance get(Object key) {
public AttributeInstance get(Object key) {
return key instanceof Holder<?> holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null;
}
@Nullable
public AttributeInstance getInstance(int key) {
return a[key];
}
@Override
public final AttributeInstance put(@NotNull Holder<Attribute> key, AttributeInstance value) {
public AttributeInstance put(@NotNull Holder<Attribute> key, AttributeInstance value) {
int uid = key.value().uid;
AttributeInstance prev = a[uid];
setByIndex(uid, value);
@@ -88,7 +88,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
}
@Override
public final AttributeInstance remove(Object key) {
public AttributeInstance remove(Object key) {
if (!(key instanceof Holder<?> holder) || !(holder.value() instanceof Attribute attribute)) return null;
int uid = attribute.uid;
AttributeInstance prev = a[uid];
@@ -97,7 +97,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
}
@Override
public final void putAll(@NotNull Map<? extends Holder<Attribute>, ? extends AttributeInstance> m) {
public void putAll(@NotNull Map<? extends Holder<Attribute>, ? extends AttributeInstance> m) {
for (AttributeInstance e : m.values()) {
if (e != null) {
setByIndex(e.getAttribute().value().uid, e);
@@ -106,13 +106,13 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
}
@Override
public final void clear() {
public void clear() {
Arrays.fill(a, null);
size = 0;
}
@Override
public final @NotNull Set<Holder<Attribute>> keySet() {
public @NotNull Set<Holder<Attribute>> keySet() {
if (keys == null) {
keys = new KeySet();
}
@@ -120,7 +120,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
}
@Override
public final @NotNull Collection<AttributeInstance> values() {
public @NotNull Collection<AttributeInstance> values() {
if (values == null) {
values = new Values();
}
@@ -128,7 +128,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
}
@Override
public final @NotNull Set<Entry<Holder<Attribute>, AttributeInstance>> entrySet() {
public @NotNull Set<Entry<Holder<Attribute>, AttributeInstance>> entrySet() {
if (entries == null) {
entries = new EntrySet();
}
@@ -136,13 +136,23 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
}
@Override
public final boolean equals(Object o) {
if (!(o instanceof AttributeInstanceArrayMap that)) return false;
return size == that.size && Arrays.equals(a, that.a);
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Map<?, ?> s)) return false;
if (s.size() != size()) return false;
if (o instanceof AttributeInstanceArrayMap that) {
return Arrays.equals(a, that.a);
}
for (Entry<?, ?> e : s.entrySet()) {
if (!Objects.equals(get(e.getKey()), e.getValue())) {
return false;
}
}
return true;
}
@Override
public final int hashCode() {
public int hashCode() {
return Arrays.hashCode(a);
}
@@ -192,7 +202,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
if (!hasNext()) throw new NoSuchElementException();
currentIndex = nextIndex;
nextIndex = findNextOccupied(nextIndex + 1);
return BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(currentIndex);
return BuiltInRegistries.ATTRIBUTE.get(currentIndex).orElseThrow();
}
@Override
@@ -279,7 +289,7 @@ public class AttributeInstanceArrayMap implements Map<Holder<Attribute>, Attribu
public Entry<Holder<Attribute>, AttributeInstance> next() {
if (!hasNext()) throw new NoSuchElementException();
currentIndex = nextIndex;
Holder<Attribute> key = BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(nextIndex);
Holder<Attribute> key = BuiltInRegistries.ATTRIBUTE.get(nextIndex).orElseThrow();
AttributeInstance value = a[nextIndex];
nextIndex = findNextOccupied(nextIndex + 1);
return new SimpleEntry<>(key, value) {

View File

@@ -0,0 +1,113 @@
package org.dreeam.leaf.util.map;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Array;
import java.util.*;
public final class AttributeInstanceSet extends AbstractCollection<AttributeInstance> implements Set<AttributeInstance> {
public final IntSet inner;
public final AttributeInstanceArrayMap map;
public AttributeInstanceSet(AttributeInstanceArrayMap map) {
this.map = map;
inner = new IntArraySet();
}
@Override
public boolean add(AttributeInstance instance) {
return inner.add(instance.getAttribute().value().uid);
}
@Override
public boolean remove(Object o) {
return o instanceof AttributeInstance instance && inner.remove(instance.getAttribute().value().uid);
}
@Override
public @NotNull Iterator<AttributeInstance> iterator() {
return new CloneIterator(inner.toIntArray(), map);
}
@Override
public int size() {
return inner.size();
}
@Override
public boolean isEmpty() {
return inner.isEmpty();
}
@Override
public void clear() {
inner.clear();
}
@Override
public boolean contains(Object o) {
if (o instanceof AttributeInstance instance) {
return inner.contains(instance.getAttribute().value().uid);
}
return false;
}
@Override
public AttributeInstance @NotNull [] toArray() {
int[] innerClone = inner.toIntArray();
AttributeInstance[] arr = new AttributeInstance[innerClone.length];
for (int i = 0; i < arr.length; i++) {
arr[i] = map.getInstance(innerClone[i]);
}
return arr;
}
@SuppressWarnings({"unchecked"})
@Override
public <T> T @NotNull [] toArray(T[] a) {
if (a == null || (a.getClass() == AttributeInstance[].class && a.length == 0)) {
return (T[]) toArray();
}
if (a.length < size()) {
a = (T[]) Array.newInstance(a.getClass().getComponentType(), size());
}
System.arraycopy((T[]) toArray(), 0, a, 0, size());
if (a.length > size()) {
a[size()] = null;
}
return a;
}
static class CloneIterator implements Iterator<AttributeInstance> {
private final int[] array;
private int index = 0;
private final AttributeInstanceArrayMap map;
CloneIterator(int[] array, AttributeInstanceArrayMap map) {
this.array = array;
this.map = map;
}
@Override
public boolean hasNext() {
return index < array.length;
}
@Override
public AttributeInstance next() {
if (!hasNext()) throw new NoSuchElementException();
return map.getInstance(array[index++]);
}
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Set<?> s)) return false;
if (s.size() != size()) return false;
return containsAll(s);
}
}

View File

@@ -1,4 +1,4 @@
package org.dreeam.leaf.util.biome;
package org.dreeam.leaf.world.biome;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;

View File

@@ -0,0 +1,333 @@
package org.dreeam.leaf.world.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.PoweredRailBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.RailShape;
import java.util.HashMap;
import static net.minecraft.world.level.block.Block.*;
import static net.minecraft.world.level.block.PoweredRailBlock.POWERED;
import static net.minecraft.world.level.block.PoweredRailBlock.SHAPE;
public class OptimizedPoweredRails {
private static final Direction[] EAST_WEST_DIR = new Direction[]{Direction.WEST, Direction.EAST};
private static final Direction[] NORTH_SOUTH_DIR = new Direction[]{Direction.SOUTH, Direction.NORTH};
private static final int UPDATE_FORCE_PLACE = UPDATE_MOVE_BY_PISTON | UPDATE_KNOWN_SHAPE | UPDATE_CLIENTS;
private static int RAIL_POWER_LIMIT = 8;
private static void giveShapeUpdate(Level level, BlockState state, BlockPos pos, BlockPos fromPos, Direction direction) {
BlockState oldState = level.getBlockState(pos);
Block.updateOrDestroy(
oldState,
oldState.updateShape(level, level, pos, direction.getOpposite(), fromPos, state, level.random),
level,
pos,
UPDATE_CLIENTS & -34,
0
);
}
public static int getRailPowerLimit() {
return RAIL_POWER_LIMIT;
}
public static void setRailPowerLimit(int powerLimit) {
RAIL_POWER_LIMIT = powerLimit;
}
public static void updateState(PoweredRailBlock self, BlockState state, Level level, BlockPos pos) {
boolean shouldBePowered = level.hasNeighborSignal(pos) ||
self.findPoweredRailSignal(level, pos, state, true, 0) ||
self.findPoweredRailSignal(level, pos, state, false, 0);
if (shouldBePowered != state.getValue(POWERED)) {
RailShape railShape = state.getValue(SHAPE);
if (railShape.isSlope()) {
level.setBlock(pos, state.setValue(POWERED, shouldBePowered), 3);
level.updateNeighborsAtExceptFromFacing(pos.below(), self, Direction.UP, null);
level.updateNeighborsAtExceptFromFacing(pos.above(), self, Direction.DOWN, null); // isSlope
} else if (shouldBePowered) {
powerLane(self, level, pos, state, railShape);
} else {
dePowerLane(self, level, pos, state, railShape);
}
}
}
private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level world, BlockPos pos,
boolean bl, int distance, RailShape shape,
HashMap<BlockPos, Boolean> checkedPos) {
BlockState blockState = world.getBlockState(pos);
boolean speedCheck = checkedPos.containsKey(pos) && checkedPos.get(pos);
if (speedCheck) {
return world.hasNeighborSignal(pos) ||
findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos);
} else {
if (blockState.is(self)) {
RailShape railShape = blockState.getValue(SHAPE);
if (shape == RailShape.EAST_WEST && (
railShape == RailShape.NORTH_SOUTH ||
railShape == RailShape.ASCENDING_NORTH ||
railShape == RailShape.ASCENDING_SOUTH
) || shape == RailShape.NORTH_SOUTH && (
railShape == RailShape.EAST_WEST ||
railShape == RailShape.ASCENDING_EAST ||
railShape == RailShape.ASCENDING_WEST
)) {
return false;
} else if (blockState.getValue(POWERED)) {
return world.hasNeighborSignal(pos) ||
findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos);
} else {
return false;
}
}
return false;
}
}
private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level level,
BlockPos pos, BlockState state, boolean bl, int distance,
HashMap<BlockPos, Boolean> checkedPos) {
if (distance >= RAIL_POWER_LIMIT - 1) return false;
int i = pos.getX();
int j = pos.getY();
int k = pos.getZ();
boolean bl2 = true;
RailShape railShape = state.getValue(SHAPE);
switch (railShape.ordinal()) {
case 0 -> {
if (bl) ++k;
else --k;
}
case 1 -> {
if (bl) --i;
else ++i;
}
case 2 -> {
if (bl) {
--i;
} else {
++i;
++j;
bl2 = false;
}
railShape = RailShape.EAST_WEST;
}
case 3 -> {
if (bl) {
--i;
++j;
bl2 = false;
} else {
++i;
}
railShape = RailShape.EAST_WEST;
}
case 4 -> {
if (bl) {
++k;
} else {
--k;
++j;
bl2 = false;
}
railShape = RailShape.NORTH_SOUTH;
}
case 5 -> {
if (bl) {
++k;
++j;
bl2 = false;
} else {
--k;
}
railShape = RailShape.NORTH_SOUTH;
}
}
return findPoweredRailSignalFaster(
self, level, new BlockPos(i, j, k),
bl, distance, railShape, checkedPos
) ||
(bl2 && findPoweredRailSignalFaster(
self, level, new BlockPos(i, j - 1, k),
bl, distance, railShape, checkedPos
));
}
private static void powerLane(PoweredRailBlock self, Level world, BlockPos pos,
BlockState mainState, RailShape railShape) {
world.setBlock(pos, mainState.setValue(POWERED, true), UPDATE_FORCE_PLACE);
HashMap<BlockPos, Boolean> checkedPos = new HashMap<>();
checkedPos.put(pos, true);
int[] count = new int[2];
if (railShape == RailShape.NORTH_SOUTH) { // Order: +z, -z
for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) {
setRailPositionsPower(self, world, pos, checkedPos, count, i, NORTH_SOUTH_DIR[i]);
}
updateRails(self, false, world, pos, mainState, count);
} else if (railShape == RailShape.EAST_WEST) { // Order: -x, +x
for (int i = 0; i < EAST_WEST_DIR.length; ++i) {
setRailPositionsPower(self, world, pos, checkedPos, count, i, EAST_WEST_DIR[i]);
}
updateRails(self, true, world, pos, mainState, count);
}
}
private static void dePowerLane(PoweredRailBlock self, Level world, BlockPos pos,
BlockState mainState, RailShape railShape) {
world.setBlock(pos, mainState.setValue(POWERED, false), UPDATE_FORCE_PLACE);
int[] count = new int[2];
if (railShape == RailShape.NORTH_SOUTH) { // Order: +z, -z
for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) {
setRailPositionsDePower(self, world, pos, count, i, NORTH_SOUTH_DIR[i]);
}
updateRails(self, false, world, pos, mainState, count);
} else if (railShape == RailShape.EAST_WEST) { // Order: -x, +x
for (int i = 0; i < EAST_WEST_DIR.length; ++i) {
setRailPositionsDePower(self, world, pos, count, i, EAST_WEST_DIR[i]);
}
updateRails(self, true, world, pos, mainState, count);
}
}
private static void setRailPositionsPower(PoweredRailBlock self, Level world, BlockPos pos,
HashMap<BlockPos, Boolean> checkedPos, int[] count, int i, Direction dir) {
for (int z = 1; z < RAIL_POWER_LIMIT; z++) {
BlockPos newPos = pos.relative(dir, z);
BlockState state = world.getBlockState(newPos);
if (checkedPos.containsKey(newPos)) {
if (!checkedPos.get(newPos)) break;
count[i]++;
} else if (!state.is(self) || state.getValue(POWERED) || !(
world.hasNeighborSignal(newPos) ||
findPoweredRailSignalFaster(self, world, newPos, state, true, 0, checkedPos) ||
findPoweredRailSignalFaster(self, world, newPos, state, false, 0, checkedPos)
)) {
checkedPos.put(newPos, false);
break;
} else {
checkedPos.put(newPos, true);
world.setBlock(newPos, state.setValue(POWERED, true), UPDATE_FORCE_PLACE);
count[i]++;
}
}
}
private static void setRailPositionsDePower(PoweredRailBlock self, Level world, BlockPos pos,
int[] count, int i, Direction dir) {
for (int z = 1; z < RAIL_POWER_LIMIT; z++) {
BlockPos newPos = pos.relative(dir, z);
BlockState state = world.getBlockState(newPos);
if (!state.is(self) || !state.getValue(POWERED) || world.hasNeighborSignal(newPos) ||
self.findPoweredRailSignal(world, newPos, state, true, 0) ||
self.findPoweredRailSignal(world, newPos, state, false, 0)) break;
world.setBlock(newPos, state.setValue(POWERED, false), UPDATE_FORCE_PLACE);
count[i]++;
}
}
private static void shapeUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, BlockState mainState,
int endPos, Direction direction, int currentPos, BlockPos blockPos) {
if (currentPos == endPos) {
BlockPos newPos = pos.relative(direction, currentPos + 1);
giveShapeUpdate(world, mainState, newPos, pos, direction);
BlockState state = world.getBlockState(blockPos);
if (state.is(self) && state.getValue(SHAPE).isSlope()) giveShapeUpdate(world, mainState, newPos.above(), pos, direction);
}
}
private static void neighborUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, int endPos,
Direction direction, Block block, int currentPos, BlockPos blockPos) {
if (currentPos == endPos) {
BlockPos newPos = pos.relative(direction, currentPos + 1);
world.neighborChanged(newPos, block, null);
BlockState state = world.getBlockState(blockPos);
if (state.is(self) && state.getValue(SHAPE).isSlope()) world.neighborChanged(newPos.above(), block, null);
}
}
private static void updateRailsSectionEastWestShape(PoweredRailBlock self, Level world, BlockPos pos,
int c, BlockState mainState, Direction dir,
int[] count, int countAmt) {
BlockPos pos1 = pos.relative(dir, c);
if (c == 0 && count[1] == 0) giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite());
shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1);
giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN);
giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP);
giveShapeUpdate(world, mainState, pos1.north(), pos, Direction.NORTH);
giveShapeUpdate(world, mainState, pos1.south(), pos, Direction.SOUTH);
}
private static void updateRailsSectionNorthSouthShape(PoweredRailBlock self, Level world, BlockPos pos,
int c, BlockState mainState, Direction dir,
int[] count, int countAmt) {
BlockPos pos1 = pos.relative(dir, c);
giveShapeUpdate(world, mainState, pos1.west(), pos, Direction.WEST);
giveShapeUpdate(world, mainState, pos1.east(), pos, Direction.EAST);
giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN);
giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP);
shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1);
if (c == 0 && count[1] == 0) giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite());
}
private static void updateRails(PoweredRailBlock self, boolean eastWest, Level world,
BlockPos pos, BlockState mainState, int[] count) {
if (eastWest) {
for (int i = 0; i < EAST_WEST_DIR.length; ++i) {
int countAmt = count[i];
if (i == 1 && countAmt == 0) continue;
Direction dir = EAST_WEST_DIR[i];
Block block = mainState.getBlock();
for (int c = countAmt; c >= i; c--) {
BlockPos p = pos.relative(dir, c);
if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, null);
neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p);
world.neighborChanged(p.below(), block, null);
world.neighborChanged(p.above(), block, null);
world.neighborChanged(p.north(), block, null);
world.neighborChanged(p.south(), block, null);
BlockPos pos2 = pos.relative(dir, c).below();
world.neighborChanged(pos2.below(), block, null);
world.neighborChanged(pos2.north(), block, null);
world.neighborChanged(pos2.south(), block, null);
if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, null);
if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()).below(), block, null);
}
for (int c = countAmt; c >= i; c--)
updateRailsSectionEastWestShape(self, world, pos, c, mainState, dir, count, countAmt);
}
} else {
for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) {
int countAmt = count[i];
if (i == 1 && countAmt == 0) continue;
Direction dir = NORTH_SOUTH_DIR[i];
Block block = mainState.getBlock();
for (int c = countAmt; c >= i; c--) {
BlockPos p = pos.relative(dir, c);
world.neighborChanged(p.west(), block, null);
world.neighborChanged(p.east(), block, null);
world.neighborChanged(p.below(), block, null);
world.neighborChanged(p.above(), block, null);
neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p);
if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, null);
BlockPos pos2 = pos.relative(dir, c).below();
world.neighborChanged(pos2.west(), block, null);
world.neighborChanged(pos2.east(), block, null);
world.neighborChanged(pos2.below(), block, null);
if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, null);
if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()).below(), block, null);
}
for (int c = countAmt; c >= i; c--)
updateRailsSectionNorthSouthShape(self, world, pos, c, mainState, dir, count, countAmt);
}
}
}
}