Add rule to make the World Border update with ticks instead of real time (#1550)

This commit is contained in:
senseiwells authored and GitHub committed 2023-01-25 17:40:18 +01:00
1 parent 0e2f6df4d3
commit 009d971584
5 files changed
+242

No files matched your search

+44
View File
@@ -27,6 +27,8 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.StructureBlockEntity;
import net.minecraft.world.level.block.piston.PistonStructureResolver;
import net.minecraft.world.level.border.BorderStatus;
import net.minecraft.world.level.border.WorldBorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -1068,6 +1070,48 @@ public class CarpetSettings
)
public static int sculkSensorRange = 8;
/**
* Listener, we need to update world borders to change whether
* they are currently moving in real time or in game time.
*/
private static class WorldBorderValidator extends Validator<Boolean>
{
@Override
public Boolean validate(CommandSourceStack source, CarpetRule<Boolean> changingRule, Boolean newValue, String userInput)
{
if (changingRule.value() ^ newValue)
{
// Needed for the update
tickSyncedWorldBorders = newValue;
MinecraftServer server = CarpetServer.minecraft_server;
if (server == null)
{
return newValue;
}
for (ServerLevel level : server.getAllLevels())
{
WorldBorder worldBorder = level.getWorldBorder();
if (worldBorder.getStatus() != BorderStatus.STATIONARY)
{
double from = worldBorder.getSize();
double to = worldBorder.getLerpTarget();
long time = worldBorder.getLerpRemainingTime();
worldBorder.lerpSizeBetween(from, to, time);
}
}
}
return newValue;
}
}
@Rule(
desc = "Makes world borders move based on in game time instead of real time",
extra = "This has the effect that when the tick rate changes the world border speed also changes proportional to it",
category = FEATURE,
validate = WorldBorderValidator.class
)
public static boolean tickSyncedWorldBorders = false;
public enum FungusFixMode {
FALSE, RANDOM, ALL;
}
@@ -0,0 +1,25 @@
package carpet.mixins;
import carpet.CarpetSettings;
import carpet.patches.TickSyncedBorderExtent;
import net.minecraft.world.level.border.WorldBorder;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldBorder.class)
public class WorldBorder_tickMixin
{
@Shadow private WorldBorder.BorderExtent extent;
@Inject(method = "lerpSizeBetween", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/border/WorldBorder;getListeners()Ljava/util/List;"))
private void getExtent(double d, double e, long l, CallbackInfo ci)
{
if (d != e && CarpetSettings.tickSyncedWorldBorders)
{
this.extent = new TickSyncedBorderExtent((WorldBorder) (Object) this, l, d, e);
}
}
}
@@ -0,0 +1,168 @@
package carpet.patches;
import carpet.CarpetServer;
import carpet.helpers.TickSpeed;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Mth;
import net.minecraft.world.level.border.BorderChangeListener;
import net.minecraft.world.level.border.BorderStatus;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
/**
* This class is essentially a copy of {@link net.minecraft.world.level.border.WorldBorder.MovingBorderExtent}
* but instead of using real time to lerp the border
* this class uses the in game ticks.
*/
@SuppressWarnings("JavadocReference")
public class TickSyncedBorderExtent implements WorldBorder.BorderExtent
{
private final WorldBorder border;
private final long realDuration;
private final double tickDuration;
private final double from;
private final double to;
private int ticks;
public TickSyncedBorderExtent(WorldBorder border, long realDuration, double from, double to)
{
this.border = border;
this.realDuration = realDuration;
this.tickDuration = realDuration / 50.0;
this.from = from;
this.to = to;
this.ticks = 0;
}
@Override
public double getMinX()
{
int maxSize = this.border.getAbsoluteMaxSize();
return Mth.clamp(this.border.getCenterX() - this.getSize() / 2.0, -maxSize, maxSize);
}
@Override
public double getMaxX()
{
int maxSize = this.border.getAbsoluteMaxSize();
return Mth.clamp(this.border.getCenterX() + this.getSize() / 2.0, -maxSize, maxSize);
}
@Override
public double getMinZ()
{
int maxSize = this.border.getAbsoluteMaxSize();
return Mth.clamp(this.border.getCenterZ() - this.getSize() / 2.0, -maxSize, maxSize);
}
@Override
public double getMaxZ()
{
int maxSize = this.border.getAbsoluteMaxSize();
return Mth.clamp(this.border.getCenterZ() + this.getSize() / 2.0, -maxSize, maxSize);
}
@Override
public double getSize()
{
double progress = this.ticks / this.tickDuration;
return progress < 1.0 ? Mth.lerp(progress, this.from, this.to) : this.to;
}
@Override
public double getLerpSpeed()
{
return Math.abs(this.from - this.to) / this.realDuration;
}
@Override
public long getLerpRemainingTime()
{
// Rough estimation
MinecraftServer server = CarpetServer.minecraft_server;
double ms;
if (server == null)
{
ms = TickSpeed.mspt;
}
else
{
ms = Arrays.stream(server.tickTimes).average().orElseThrow(IllegalStateException::new) * 1.0E-6D;
}
double tps = 1_000.0D / Math.max((TickSpeed.time_warp_start_time != 0) ? 0.0 : TickSpeed.mspt, ms);
return (long) ((this.tickDuration - this.ticks) / tps * 1_000);
}
@Override
public double getLerpTarget()
{
return this.to;
}
@NotNull
@Override
public BorderStatus getStatus()
{
return this.to < this.from ? BorderStatus.SHRINKING : BorderStatus.GROWING;
}
@Override
public void onAbsoluteMaxSizeChange()
{
}
@Override
public void onCenterChange()
{
}
@NotNull
@Override
public WorldBorder.BorderExtent update()
{
if (this.ticks++ % 20 == 0)
{
// We need to update any listeners
// Most importantly those that send updates to the client
// This is because the client logic uses real time
// So if the tick speed has changed we need to tell the client
for (BorderChangeListener listener : this.border.getListeners())
{
// We do not want to update DelegateBorderChangeListener
// This updates borders in other dimensions
if (!(listener instanceof BorderChangeListener.DelegateBorderChangeListener))
{
listener.onBorderSizeLerping(this.border, this.from, this.to, this.realDuration);
}
}
}
return this.ticks >= this.tickDuration ? this.border.new StaticBorderExtent(this.to) : this;
}
@NotNull
@Override
public VoxelShape getCollisionShape()
{
return Shapes.join(
Shapes.INFINITY,
Shapes.box(
Math.floor(this.getMinX()),
Double.NEGATIVE_INFINITY,
Math.floor(this.getMinZ()),
Math.ceil(this.getMaxX()),
Double.POSITIVE_INFINITY,
Math.ceil(this.getMaxZ())
),
BooleanOp.ONLY_FIRST
);
}
}
+4
View File
@@ -4,7 +4,11 @@ accessible class net/minecraft/server/level/ChunkMap$DistanceManager
accessible class net/minecraft/server/level/ThreadedLevelLightEngine$TaskType
accessible class net/minecraft/world/item/crafting/Ingredient$Value
accessible class net/minecraft/world/level/lighting/BlockLightSectionStorage$BlockDataLayerStorageMap
accessible class net/minecraft/world/level/border/WorldBorder$BorderExtent
accessible class net/minecraft/world/level/border/WorldBorder$StaticBorderExtent
accessible class net/minecraft/server/MinecraftServer$ReloadableResources
accessible class net/minecraft/world/level/biome/Biome$ClimateSettings
accessible method net/minecraft/world/level/border/WorldBorder getListeners ()Ljava/util/List;
accessible field net/minecraft/world/level/block/state/BlockBehaviour UPDATE_SHAPE_ORDER [Lnet/minecraft/core/Direction;
+1
View File
@@ -38,6 +38,7 @@
"ChunkMap_tickMixin",
"DistanceManager_tickMixin",
"ServerFunctionManager_tickMixin",
"WorldBorder_tickMixin",
"ChunkMap_scarpetChunkCreationMixin",
"LevelEntityGetterAdapter_scarpetMixin",
"ChunkHolder_scarpetChunkCreationMixin",