mirror of
https://github.com/BX-Team/DivineMC.git
synced 2025-12-19 14:59:25 +00:00
remove previous impl
This commit is contained in:
@@ -1,148 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com>
|
||||
Date: Thu, 10 Jul 2025 04:51:08 +0300
|
||||
Subject: [PATCH] Raytrace Entity Tracker
|
||||
|
||||
Original project: https://github.com/tr7zw/EntityCulling
|
||||
Original license: Custom License
|
||||
|
||||
Original project: https://github.com/LogisticsCraft/OcclusionCulling
|
||||
Original license: MIT
|
||||
|
||||
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||
index 56483d9e770e29526a36d4c8c5565092acb227c7..9db43874a94770d49ea5ff48a46c4657e4819e98 100644
|
||||
--- a/net/minecraft/world/entity/Entity.java
|
||||
+++ b/net/minecraft/world/entity/Entity.java
|
||||
@@ -147,7 +147,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
-public abstract class Entity implements SyncedDataHolder, DebugValueSource, Nameable, ItemOwner, EntityAccess, ScoreHolder, DataComponentGetter, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker
|
||||
+public abstract class Entity implements SyncedDataHolder, DebugValueSource, Nameable, ItemOwner, EntityAccess, ScoreHolder, DataComponentGetter, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity, dev.tr7zw.entityculling.versionless.access.Cullable { // Paper - rewrite chunk system // Paper - optimise entity tracker // DivineMC - Raytrace Entity Tracker
|
||||
public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur - Configurable entity base attributes
|
||||
// CraftBukkit start
|
||||
private static final int CURRENT_LEVEL = 2;
|
||||
@@ -5531,4 +5531,47 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name
|
||||
return false;
|
||||
}
|
||||
// Purpur end - Ridables
|
||||
+
|
||||
+ // DivineMC start - Raytrace Entity Tracker
|
||||
+ private long lasttime = 0;
|
||||
+ private boolean culled = false;
|
||||
+ private boolean outOfCamera = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public void setTimeout() {
|
||||
+ this.lasttime = System.currentTimeMillis() + 1000;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isForcedVisible() {
|
||||
+ return this.lasttime > System.currentTimeMillis();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setCulled(boolean value) {
|
||||
+ this.culled = value;
|
||||
+ if (!value) {
|
||||
+ setTimeout();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isCulled() {
|
||||
+ if (!org.bxteam.divinemc.config.DivineConfig.MiscCategory.retEnabled) return false;
|
||||
+
|
||||
+ return this.culled;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setOutOfCamera(boolean value) {
|
||||
+ this.outOfCamera = value;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isOutOfCamera() {
|
||||
+ if (!org.bxteam.divinemc.config.DivineConfig.MiscCategory.retEnabled) return false;
|
||||
+
|
||||
+ return this.outOfCamera;
|
||||
+ }
|
||||
+ // DivineMC end - Raytrace Entity Tracker
|
||||
}
|
||||
diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java
|
||||
index d9d45a3e7436d5a211e3f2b6810f76e0a22766e8..4c6ca2979a370346008b981e045625668269e380 100644
|
||||
--- a/net/minecraft/world/entity/EntityType.java
|
||||
+++ b/net/minecraft/world/entity/EntityType.java
|
||||
@@ -1202,6 +1202,7 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
|
||||
private final float spawnDimensionsScale;
|
||||
private final FeatureFlagSet requiredFeatures;
|
||||
private final boolean allowedInPeaceful;
|
||||
+ public boolean skipRaytracingCheck = false; // DivineMC - Raytrace Entity Tracker
|
||||
|
||||
private static <T extends Entity> EntityType<T> register(ResourceKey<EntityType<?>> key, EntityType.Builder<T> builder) {
|
||||
return Registry.register(BuiltInRegistries.ENTITY_TYPE, key, builder.build(key));
|
||||
diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java
|
||||
index 68942a2d15cfeddf1928a3ad270b1ce29685f8f0..149332116ff211c122b1dbf9b8010610174451db 100644
|
||||
--- a/net/minecraft/world/entity/player/Player.java
|
||||
+++ b/net/minecraft/world/entity/player/Player.java
|
||||
@@ -184,6 +184,25 @@ public abstract class Player extends Avatar implements ContainerUser {
|
||||
public int burpDelay = 0; // Purpur - Burp delay
|
||||
public boolean canPortalInstant = false; // Purpur - Add portal permission bypass
|
||||
public int sixRowEnderchestSlotCount = -1; // Purpur - Barrels and enderchests 6 rows
|
||||
+ // DivineMC start - Raytrace Entity Tracker
|
||||
+ public dev.tr7zw.entityculling.CullTask cullTask;
|
||||
+ {
|
||||
+ if (!org.bxteam.divinemc.config.DivineConfig.MiscCategory.retEnabled) {
|
||||
+ this.cullTask = null;
|
||||
+ } else {
|
||||
+ final com.logisticscraft.occlusionculling.OcclusionCullingInstance culling = new com.logisticscraft.occlusionculling.OcclusionCullingInstance(
|
||||
+ org.bxteam.divinemc.config.DivineConfig.MiscCategory.retTracingDistance,
|
||||
+ new dev.tr7zw.entityculling.DefaultChunkDataProvider(this.level())
|
||||
+ );
|
||||
+
|
||||
+ this.cullTask = new dev.tr7zw.entityculling.CullTask(
|
||||
+ culling, this,
|
||||
+ org.bxteam.divinemc.config.DivineConfig.MiscCategory.retHitboxLimit,
|
||||
+ org.bxteam.divinemc.config.DivineConfig.MiscCategory.retCheckIntervalMs
|
||||
+ );
|
||||
+ }
|
||||
+ }
|
||||
+ // DivineMC end - Raytrace Entity Tracker
|
||||
|
||||
// CraftBukkit start
|
||||
public boolean fauxSleeping;
|
||||
@@ -270,6 +289,25 @@ public abstract class Player extends Avatar implements ContainerUser {
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
+ // DivineMC start - Raytrace Entity Tracker
|
||||
+ if (!org.bxteam.divinemc.config.DivineConfig.MiscCategory.retEnabled) {
|
||||
+ if (this.cullTask != null) this.cullTask.signalStop();
|
||||
+ this.cullTask = null;
|
||||
+ } else {
|
||||
+ final com.logisticscraft.occlusionculling.OcclusionCullingInstance culling = new com.logisticscraft.occlusionculling.OcclusionCullingInstance(
|
||||
+ org.bxteam.divinemc.config.DivineConfig.MiscCategory.retTracingDistance,
|
||||
+ new dev.tr7zw.entityculling.DefaultChunkDataProvider(this.level())
|
||||
+ );
|
||||
+
|
||||
+ this.cullTask = new dev.tr7zw.entityculling.CullTask(
|
||||
+ culling, this,
|
||||
+ org.bxteam.divinemc.config.DivineConfig.MiscCategory.retHitboxLimit,
|
||||
+ org.bxteam.divinemc.config.DivineConfig.MiscCategory.retCheckIntervalMs
|
||||
+ );
|
||||
+ }
|
||||
+ if (this.cullTask != null) this.cullTask.setup();
|
||||
+ if (this.cullTask != null) this.cullTask.requestCullSignal();
|
||||
+ // DivineMC end - Raytrace Entity Tracker
|
||||
// Purpur start - Burp delay
|
||||
if (this.burpDelay > 0 && --this.burpDelay == 0) {
|
||||
this.level().playSound(null, getX(), getY(), getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 1.0F, this.level().random.nextFloat() * 0.1F + 0.9F);
|
||||
@@ -1315,6 +1353,7 @@ public abstract class Player extends Avatar implements ContainerUser {
|
||||
if (this.hasContainerOpen()) {
|
||||
this.doCloseContainer();
|
||||
}
|
||||
+ if (this.cullTask != null) this.cullTask.signalStop(); // DivineMC - Raytrace Entity Tracker
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,7 +102,7 @@ index 8c98c2593eec14a8a378041e94cf52b8fbfedc30..4788686f0bd8ab67700bf3687560ff4a
|
||||
|
||||
private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2
|
||||
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||
index 9db43874a94770d49ea5ff48a46c4657e4819e98..013261aff0b9ecfeb51f3baf597d7ee211a8a095 100644
|
||||
index 56483d9e770e29526a36d4c8c5565092acb227c7..5410bb67893ad01524d302b5d6c3952f3ffb7ee5 100644
|
||||
--- a/net/minecraft/world/entity/Entity.java
|
||||
+++ b/net/minecraft/world/entity/Entity.java
|
||||
@@ -1116,29 +1116,10 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name
|
||||
@@ -96,7 +96,7 @@ index 8e6f097b4d17aaaf8eccc16e11ce2bd01ad63322..ded99b157865f5bcfd64b3082c628a71
|
||||
|
||||
int getContainerSize();
|
||||
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||
index 013261aff0b9ecfeb51f3baf597d7ee211a8a095..ef9c0258d3f9371ddfc9931dce2c2584139e6752 100644
|
||||
index 5410bb67893ad01524d302b5d6c3952f3ffb7ee5..f6a599a4841d9598b6e1bf766e57d0505a363606 100644
|
||||
--- a/net/minecraft/world/entity/Entity.java
|
||||
+++ b/net/minecraft/world/entity/Entity.java
|
||||
@@ -5099,6 +5099,18 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name
|
||||
@@ -1,136 +0,0 @@
|
||||
package dev.tr7zw.entityculling;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import com.logisticscraft.occlusionculling.OcclusionCullingInstance;
|
||||
import com.logisticscraft.occlusionculling.util.Vec3d;
|
||||
import dev.tr7zw.entityculling.versionless.access.Cullable;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.bxteam.divinemc.config.DivineConfig;
|
||||
import org.bxteam.divinemc.util.NamedAgnosticThreadFactory;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CullTask implements Runnable {
|
||||
private volatile boolean requestCull = false;
|
||||
private volatile boolean scheduleNext = true;
|
||||
private volatile boolean inited = false;
|
||||
|
||||
private final OcclusionCullingInstance culling;
|
||||
private final Player checkTarget;
|
||||
|
||||
private final int hitboxLimit;
|
||||
|
||||
public long lastCheckedTime = 0;
|
||||
|
||||
private final Vec3d lastPos = new Vec3d(0, 0, 0);
|
||||
private final Vec3d aabbMin = new Vec3d(0, 0, 0);
|
||||
private final Vec3d aabbMax = new Vec3d(0, 0, 0);
|
||||
|
||||
private static final Executor backgroundWorker = Executors.newFixedThreadPool(DivineConfig.MiscCategory.retThreads, new NamedAgnosticThreadFactory<>("Raytrace Entity Tracker Thread", TickThread::new, DivineConfig.MiscCategory.retThreadsPriority));
|
||||
|
||||
private final Executor worker;
|
||||
|
||||
public CullTask(
|
||||
OcclusionCullingInstance culling,
|
||||
Player checkTarget,
|
||||
int hitboxLimit,
|
||||
long checkIntervalMs
|
||||
) {
|
||||
this.culling = culling;
|
||||
this.checkTarget = checkTarget;
|
||||
this.hitboxLimit = hitboxLimit;
|
||||
this.worker = CompletableFuture.delayedExecutor(checkIntervalMs, TimeUnit.MILLISECONDS, backgroundWorker);
|
||||
}
|
||||
|
||||
public void requestCullSignal() {
|
||||
this.requestCull = true;
|
||||
}
|
||||
|
||||
public void signalStop() {
|
||||
this.scheduleNext = false;
|
||||
}
|
||||
|
||||
public void setup() {
|
||||
if (!this.inited)
|
||||
this.inited = true;
|
||||
else
|
||||
return;
|
||||
this.worker.execute(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (this.checkTarget.tickCount > 10) {
|
||||
Vec3 cameraMC = this.checkTarget.getEyePosition(0);
|
||||
if (requestCull || !(cameraMC.x == lastPos.x && cameraMC.y == lastPos.y && cameraMC.z == lastPos.z)) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
requestCull = false;
|
||||
|
||||
lastPos.set(cameraMC.x, cameraMC.y, cameraMC.z);
|
||||
culling.resetCache();
|
||||
|
||||
cullEntities(cameraMC, lastPos);
|
||||
|
||||
lastCheckedTime = (System.currentTimeMillis() - start);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (this.scheduleNext) {
|
||||
this.worker.execute(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cullEntities(Vec3 cameraMC, Vec3d camera) {
|
||||
for (Entity entity : this.checkTarget.level().getEntities().getAll()) {
|
||||
if (!(entity instanceof Cullable cullable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.getType().skipRaytracingCheck) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cullable.isForcedVisible()) {
|
||||
if (entity.isCurrentlyGlowing() || isSkippableArmorstand(entity)) {
|
||||
cullable.setCulled(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entity.position().closerThan(cameraMC, DivineConfig.MiscCategory.retTracingDistance)) {
|
||||
cullable.setCulled(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
AABB boundingBox = entity.getBoundingBox();
|
||||
if (boundingBox.getXsize() > hitboxLimit || boundingBox.getYsize() > hitboxLimit
|
||||
|| boundingBox.getZsize() > hitboxLimit) {
|
||||
cullable.setCulled(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
aabbMin.set(boundingBox.minX, boundingBox.minY, boundingBox.minZ);
|
||||
aabbMax.set(boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ);
|
||||
|
||||
boolean visible = culling.isAABBVisible(aabbMin, aabbMax, camera);
|
||||
|
||||
cullable.setCulled(!visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSkippableArmorstand(Entity entity) {
|
||||
if (!DivineConfig.MiscCategory.retSkipMarkerArmorStands) return false;
|
||||
|
||||
return entity instanceof ArmorStand && entity.isInvisible();
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package dev.tr7zw.entityculling;
|
||||
|
||||
import com.logisticscraft.occlusionculling.DataProvider;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
|
||||
public class DefaultChunkDataProvider implements DataProvider {
|
||||
private final Level level;
|
||||
|
||||
public DefaultChunkDataProvider(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepareChunk(int chunkX, int chunkZ) {
|
||||
return this.level.getChunkIfLoaded(chunkX, chunkZ) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaqueFullCube(int x, int y, int z) {
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
|
||||
final ChunkAccess access = this.level.getChunkIfLoaded(pos);
|
||||
if (access == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.level.isOutsideBuildHeight(pos)) {
|
||||
return Blocks.VOID_AIR.defaultBlockState().isSolidRender();
|
||||
} else {
|
||||
return access.getBlockState(pos).isSolidRender();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
DataProvider.super.cleanup();
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package dev.tr7zw.entityculling.versionless.access;
|
||||
|
||||
public interface Cullable {
|
||||
public void setTimeout();
|
||||
|
||||
public boolean isForcedVisible();
|
||||
|
||||
public void setCulled(boolean value);
|
||||
|
||||
public boolean isCulled();
|
||||
|
||||
public void setOutOfCamera(boolean value);
|
||||
|
||||
public boolean isOutOfCamera();
|
||||
}
|
||||
@@ -630,18 +630,6 @@ public class DivineConfig {
|
||||
public static String logLevel = "WARN";
|
||||
public static boolean onlyLogThrown = true;
|
||||
|
||||
// Raytrace Entity Tracker
|
||||
@Experimental("Raytrace Entity Tracker")
|
||||
public static boolean retEnabled = false;
|
||||
public static int retThreads = 2;
|
||||
public static int retThreadsPriority = Thread.NORM_PRIORITY + 2;
|
||||
public static boolean retSkipMarkerArmorStands = true;
|
||||
public static int retCheckIntervalMs = 10;
|
||||
public static int retTracingDistance = 48;
|
||||
public static int retHitboxLimit = 50;
|
||||
public static List<String> retSkippedEntities = List.of();
|
||||
public static boolean retInvertSkippedEntities = false;
|
||||
|
||||
// Old features
|
||||
public static boolean copperBulb1gt = false;
|
||||
public static boolean crafter1gt = false;
|
||||
@@ -651,7 +639,6 @@ public class DivineConfig {
|
||||
lagCompensation();
|
||||
regionFileExtension();
|
||||
sentrySettings();
|
||||
ret();
|
||||
oldFeatures();
|
||||
}
|
||||
|
||||
@@ -724,47 +711,6 @@ public class DivineConfig {
|
||||
if (sentryDsn != null && !sentryDsn.isBlank()) gg.pufferfish.pufferfish.sentry.SentryManager.init(Level.getLevel(logLevel));
|
||||
}
|
||||
|
||||
private static void ret() {
|
||||
retEnabled = getBoolean(ConfigCategory.MISC.key("raytrace-entity-tracker.enabled"), retEnabled,
|
||||
"Raytrace Entity Tracker uses async ray-tracing to untrack entities players cannot see. Implementation of EntityCulling mod by tr7zw.");
|
||||
retThreads = getInt(ConfigCategory.MISC.key("raytrace-entity-tracker.threads"), retThreads,
|
||||
"The number of threads to use for raytrace entity tracker.");
|
||||
retThreadsPriority = getInt(ConfigCategory.MISC.key("raytrace-entity-tracker.threads-priority"), retThreadsPriority,
|
||||
"The priority of the threads used for raytrace entity tracker.",
|
||||
"0 - lowest, 10 - highest, 5 - normal");
|
||||
|
||||
retSkipMarkerArmorStands = getBoolean(ConfigCategory.MISC.key("raytrace-entity-tracker.skip-marker-armor-stands"), retSkipMarkerArmorStands,
|
||||
"Whether to skip tracing entities with marker armor stand");
|
||||
retCheckIntervalMs = getInt(ConfigCategory.MISC.key("raytrace-entity-tracker.check-interval-ms"), retCheckIntervalMs,
|
||||
"The interval in milliseconds between each trace.");
|
||||
retTracingDistance = getInt(ConfigCategory.MISC.key("raytrace-entity-tracker.tracing-distance"), retTracingDistance,
|
||||
"The distance in blocks to track entities in the raytrace entity tracker.");
|
||||
retHitboxLimit = getInt(ConfigCategory.MISC.key("raytrace-entity-tracker.hitbox-limit"), retHitboxLimit,
|
||||
"The maximum size of bounding box to trace.");
|
||||
|
||||
retSkippedEntities = getStringList(ConfigCategory.MISC.key("raytrace-entity-tracker.skipped-entities"), retSkippedEntities,
|
||||
"List of entity types to skip in raytrace entity tracker.");
|
||||
retInvertSkippedEntities = getBoolean(ConfigCategory.MISC.key("raytrace-entity-tracker.invert-skipped-entities"), retInvertSkippedEntities,
|
||||
"If true, the entities in the skipped list will be tracked, and all other entities will be untracked.",
|
||||
"If false, the entities in the skipped list will be untracked, and all other entities will be tracked.");
|
||||
|
||||
for (EntityType<?> entityType : BuiltInRegistries.ENTITY_TYPE) {
|
||||
entityType.skipRaytracingCheck = retInvertSkippedEntities;
|
||||
}
|
||||
|
||||
final String DEFAULT_PREFIX = ResourceLocation.DEFAULT_NAMESPACE + ResourceLocation.NAMESPACE_SEPARATOR;
|
||||
|
||||
for (String name : retSkippedEntities) {
|
||||
String lowerName = name.toLowerCase(Locale.ROOT);
|
||||
String typeId = lowerName.startsWith(DEFAULT_PREFIX) ? lowerName : DEFAULT_PREFIX + lowerName;
|
||||
|
||||
EntityType.byString(typeId).ifPresentOrElse(entityType ->
|
||||
entityType.skipRaytracingCheck = !retInvertSkippedEntities,
|
||||
() -> LOGGER.warn("Skipped unknown entity {} in {}", name, ConfigCategory.MISC.key("raytrace-entity-tracker.skipped-entities"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void oldFeatures() {
|
||||
copperBulb1gt = getBoolean(ConfigCategory.MISC.key("old-features.copper-bulb-1gt"), copperBulb1gt,
|
||||
"Whether to delay the copper lamp by 1 tick when the redstone signal changes.");
|
||||
|
||||
Reference in New Issue
Block a user