Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public Collection<RunCategory> registerCategories() {
list.add(KILL_ALL_BOSSES);list.add(KILL_WITHER);list.add(KILL_ELDER_GUARDIAN);
list.add(HOW_DID_WE_GET_HERE);list.add(HERO_OF_VILLAGE);list.add(ARBALISTIC);list.add(COVER_ME_IN_DEBRIS);
list.add(ENTER_NETHER);list.add(ENTER_END);
list.add(MINE_A_CHUNK);
list.add(MINE_A_CHUNK);list.add(MINE_A_CHUNK_SF);
list.add(HIGH);
list.add(ALL_SWORDS);list.add(ALL_MINERALS);list.add(FULL_IA_15_LVL);list.add(ALL_WORKSTATIONS);list.add(FULL_INV);list.add(STACK_OF_LIME_WOOL);
return list;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ private static int setVisible(ServerCommandSource source, boolean visible) {
}

private static int startTimer(ServerCommandSource source, RunCategory runCategory, boolean instantStart) {
boolean coop = InGameTimer.getInstance().isCoop();
InGameTimer.start(InGameTimer.getInstance().getWorldName(), RunType.OLD_WORLD);
InGameTimer.getInstance().setCoop(coop);
InGameTimer.getInstance().setCategory(runCategory, true);
if (instantStart) InGameTimer.getInstance().setPause(false, "instant start");
source.sendFeedback(new LiteralText("Timer is started" + (instantStart ? " instantly" : "") + " with " + runCategory.getText().getString() + " category"), true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ private void onMove(MovementType movementType, Vec3d vec3d, CallbackInfo ci) {
}
}


/*
private Long latestPortalEnter = null;
private int portalTick = 0;
@Inject(at = @At("HEAD"), method = "tick")
Expand All @@ -212,6 +212,7 @@ public void updateNausea(CallbackInfo ci) {
this.portalTick = 0;
}
}
*/

@Override
public void changeLookDirection(double cursorDeltaX, double cursorDeltaY) {
Expand Down
130 changes: 67 additions & 63 deletions src/main/java/com/redlimerl/speedrunigt/mixins/ClientWorldMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import com.redlimerl.speedrunigt.timer.InGameTimer;
import com.redlimerl.speedrunigt.timer.category.RunCategories;
import com.redlimerl.speedrunigt.timer.category.RunCategory;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.profiler.Profiler;
Expand All @@ -12,28 +12,19 @@
import net.minecraft.world.MutableWorldProperties;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.EmptyChunk;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.gen.chunk.FlatChunkGenerator;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import java.util.Arrays;
import java.util.function.Supplier;

@Mixin(ClientWorld.class)
public abstract class ClientWorldMixin extends World {
@Shadow
public abstract ClientWorld.Properties getLevelProperties();

@Shadow
@Final
private MinecraftClient client;

protected ClientWorldMixin(MutableWorldProperties mutableWorldProperties, RegistryKey<World> registryKey, RegistryKey<DimensionType> registryKey2, DimensionType dimensionType, Supplier<Profiler> profiler, boolean bl, boolean bl2, long l) {
super(mutableWorldProperties, registryKey, registryKey2, dimensionType, profiler, bl, bl2, l);
}
Expand All @@ -43,75 +34,87 @@ public void onTick(CallbackInfo ci) {
InGameTimer.getInstance().tick();
}

@Unique
private final int[][] heightmapAccumulator = new int[16 * 3][16 * 3];

@Unique
private final BlockPos.Mutable mutable = new BlockPos.Mutable();

@Inject(method = "updateListeners", at = @At("TAIL"))
public void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState newState, int flags, CallbackInfo ci) {
// TODO: doesn't support nether or overworld caves
if (this.getDimension().hasCeiling()) {
RunCategory category = InGameTimer.getInstance().getCategory();
if (category != RunCategories.MINE_A_CHUNK && category != RunCategories.MINE_A_CHUNK_SF) {
return;
}

InGameTimer timer = InGameTimer.getInstance();
if (timer.getCategory() == RunCategories.MINE_A_CHUNK) {
int chunkX = pos.getX() >> 4;
int chunkZ = pos.getZ() >> 4;
int chunkX = pos.getX() >> 4;
int chunkZ = pos.getZ() >> 4;

for (int i = -1; i < 2; ++i) {
for (int j = -1; j < 2; ++j) {
// if all the chunks aren't loaded (and chunks are given as EmptyChunks), it will break because the heightmap is reported as all 0's
if (getChunk(chunkX + i, chunkZ + j) instanceof EmptyChunk) {
return;
}
for (int i = -1; i < 2; ++i) {
for (int j = -1; j < 2; ++j) {
// if all the chunks aren't loaded, it will break because the heightmap is reported as all 0's
if (getChunk(chunkX + i, chunkZ + j, ChunkStatus.FULL, false) == null) {
return;
}
}
}

// checks for 16 x 16 squares that have blocks only at or below bedrock in the square from the chunks -1, -1 to 1, 1 relative to the chunk the block update was in
// algorithm from https://stackoverflow.com/a/17790267
for (int[] arr : heightmapAccumulator) {
Arrays.fill(arr, 0);
}

// checks for 16 x 16 squares that have blocks only at or below bedrock in the square from the chunks -1, -1 to 1, 1 relative to the chunk the block update was in
// algorithm from https://stackoverflow.com/a/17790267
int[][] heightmapAccumulator = new int[16 * 3][16 * 3];
boolean hasCeiling = this.getDimension().hasCeiling();
int firstNonBedrockLayer = category == RunCategories.MINE_A_CHUNK ? 5 : (category == RunCategories.MINE_A_CHUNK_SF ? 1 : -1);
assert firstNonBedrockLayer != -1;
int lastNonBedrockLayer = this.getDimensionHeight() - 6;

for (int x = 0; x < 16 * 3; ++x) {
for (int z = 0; z < 16 * 3; ++z) {
// convert the 0 to 47 x and z counters in to -1 to 1 chunk offsets
Chunk chunk = this.getChunk(chunkX + (x >> 4) - 1, chunkZ + (z >> 4) - 1);
int height = chunk.getHeightmap(Heightmap.Type.WORLD_SURFACE).get(mod(x, 16), mod(z, 16));
if (height <= getBedrockMaxHeight()) {
if (x == 0 || z == 0) {
// special case for first row and column, no previous work to check
heightmapAccumulator[x][z] = 1;
continue;
}
// calculate the max value the next square is allowed to be using the bounds of the previous adjacent ones
int currentSquareLevel = Math.min(Math.min(heightmapAccumulator[x - 1][z], heightmapAccumulator[x][z - 1]), heightmapAccumulator[x - 1][z - 1]) + 1;
// if we hit 16 on the square level, we've found a large enough area
if (currentSquareLevel == 16) {
InGameTimer.complete();
return;
for (int x = 0; x < 16 * 3; ++x) {
for (int z = 0; z < 16 * 3; ++z) {
boolean columnClear = true;
Chunk chunk = this.getChunk(chunkX + (x >> 4) - 1, chunkZ + (z >> 4) - 1, ChunkStatus.FULL, false);
assert chunk != null; // already checked at the start
if (!hasCeiling) {
// calculate chunk coordinates
columnClear = chunk.getHeightmap(Heightmap.Type.WORLD_SURFACE).get(x & 15, z & 15) <= firstNonBedrockLayer;
} else {
// check the column manually, use mutable for less churn
// calculate world coordinates
mutable.set((chunkX << 4) - 16 + x, 0, (chunkZ << 4) - 16 + z);
for (int y = firstNonBedrockLayer; y <= lastNonBedrockLayer; y++) {
mutable.setY(y);
if (!chunk.getBlockState(mutable).isAir()) {
columnClear = false;
break;
}
// otherwise, just assign the value to it's place in the matrix
heightmapAccumulator[x][z] = currentSquareLevel;
} else {
heightmapAccumulator[x][z] = 0;
}
}
if (!columnClear) {
heightmapAccumulator[x][z] = 0;
continue;
}
if (x == 0 || z == 0) {
// special case for first row and column, no previous work to check
heightmapAccumulator[x][z] = 1;
continue;
}
// calculate the max value the next square is allowed to be using the bounds of the previous adjacent ones
int currentSquareLevel = Math.min(Math.min(heightmapAccumulator[x - 1][z], heightmapAccumulator[x][z - 1]), heightmapAccumulator[x - 1][z - 1]) + 1;
// if we hit 16 on the square level, we've found a large enough area
if (currentSquareLevel == 16) {
InGameTimer.complete();
return;
}
// otherwise, just assign the value to it's place in the matrix
heightmapAccumulator[x][z] = currentSquareLevel;
}
}
}

// handles negative numbers correctly, like python
@Unique
private static int mod(int divisor, int dividend) {
return ((divisor % dividend) + dividend) % dividend;
}

@Unique
private int getBedrockMaxHeight() {
if (this.client.isIntegratedServerRunning() && this.client.getServer().getWorld((this.getRegistryKey())).getChunkManager().getChunkGenerator() instanceof FlatChunkGenerator) {
return 1;
}
return 5;
}

// for debugging purposes
@Unique
@SuppressWarnings("unused")
private void printHeightmapAccumulator(int[][] heightmap) {
for (int[] row : heightmap) {
for (int height : row) {
Expand All @@ -123,11 +126,12 @@ private void printHeightmapAccumulator(int[][] heightmap) {
}

@Unique
@SuppressWarnings("unused")
private void printHeightmap(int chunkX, int chunkZ) {
for (int x = 0; x < 16 * 3; ++x) {
for (int z = 0; z < 16 * 3; ++z) {
Chunk chunk = this.getChunk(chunkX + (x >> 4) - 1, chunkZ + (z >> 4) - 1);
int height = chunk.getHeightmap(Heightmap.Type.WORLD_SURFACE).get(mod(x, 16), mod(z, 16));
int height = chunk.getHeightmap(Heightmap.Type.WORLD_SURFACE).get(x & 15, z & 15);
System.out.printf("%02d ", height);
}
System.out.println();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.redlimerl.speedrunigt.mixins.command;

import com.mojang.brigadier.arguments.ArgumentType;
import com.redlimerl.speedrunigt.timer.category.RunCategoryArgumentType;
import net.minecraft.command.arguments.ArgumentTypes;
import net.minecraft.command.arguments.serialize.ArgumentSerializer;
import net.minecraft.command.arguments.serialize.ConstantArgumentSerializer;
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(ArgumentTypes.class)
public abstract class ArgumentTypesMixin {
@Shadow
public static <T extends ArgumentType<?>> void register(String id, Class<T> class_, ArgumentSerializer<T> argumentSerializer) {
}

@Inject(method = "register()V", at = @At("TAIL"))
private static void registerRunCategoryArgumentType(CallbackInfo ci) {
register("speedrunigt:run_category", RunCategoryArgumentType.class, new ConstantArgumentSerializer<>(RunCategoryArgumentType::new));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public String valueToString(Boolean value) {
};

public enum TimerSaveInterval { NONE, PAUSE, TICKS }
public static final OptionArgument<TimerSaveInterval> TIMER_DATA_AUTO_SAVE = new OptionArgument<TimerSaveInterval>(new Identifier(SpeedRunIGT.MOD_ID, "auto_save_interval"), TimerSaveInterval.PAUSE) {
public static final OptionArgument<TimerSaveInterval> TIMER_DATA_AUTO_SAVE = new OptionArgument<TimerSaveInterval>(new Identifier(SpeedRunIGT.MOD_ID, "auto_save_interval"), TimerSaveInterval.TICKS) {
@Override
public TimerSaveInterval valueFromString(String string) {
return TimerSaveInterval.valueOf(string);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class RunCategories {
.setCanSegment(true).build();
public static AllBlocksRunCategory ALL_BLOCKS = new AllBlocksRunCategory();
public static RunCategory MINE_A_CHUNK = new RunCategory("MINE_A_CHUNK","mcce#Mine_a_Chunk");
public static RunCategory MINE_A_CHUNK_SF = new RunCategory("MINE_A_CHUNK_SF","mc_juice#Mine_a_Chunk_Superflat");

public static void checkAllBossesCompleted() {
InGameTimer timer = InGameTimer.getInstance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public void timerInit(TimerPacketBuf buf, boolean isIntegrated) {
InGameTimer.getInstance().setStartTime(startTime);
InGameTimer.getInstance().setCategory(category, false);
}
InGameTimer.getInstance().setPause(false, "coop start");
InGameTimer.getInstance().setCoop(true);
InGameTimer.getInstance().setServerIntegrated(isIntegrated);

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/assets/speedrunigt/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"speedrunigt.option.timer_category.pogloot_quater": "Quater",
"speedrunigt.option.timer_category.all_portals": "All Portals",
"speedrunigt.option.timer_category.mine_a_chunk": "Mine a Chunk",
"speedrunigt.option.timer_category.mine_a_chunk_sf": "Mine a Chunk Superflat",
"speedrunigt.option.hide_timer_in_options": "Hide Timer in Options",
"speedrunigt.option.hide_timer_in_debugs": "Hide Timer in F3",
"speedrunigt.option.debug_mode": "Debug Mode",
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/speedrunigt.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"ServerStatHandlerMixin",
"ServerWorldMixin",
"access.ServerStatHandlerAccessor",
"command.ArgumentTypesMixin",
"command.CommandManagerMixin",
"coop.PlayerManagerMixin",
"coop.TimeCommandMixin",
Expand Down