diff --git a/src/main/java/com/redlimerl/speedrunigt/impl/CategoryRegistryImpl.java b/src/main/java/com/redlimerl/speedrunigt/impl/CategoryRegistryImpl.java index a65bc69ea..b5ceaf6d1 100644 --- a/src/main/java/com/redlimerl/speedrunigt/impl/CategoryRegistryImpl.java +++ b/src/main/java/com/redlimerl/speedrunigt/impl/CategoryRegistryImpl.java @@ -18,7 +18,7 @@ public Collection 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; diff --git a/src/main/java/com/redlimerl/speedrunigt/instance/TimerCommand.java b/src/main/java/com/redlimerl/speedrunigt/instance/TimerCommand.java index 0648826cd..aa94eef2f 100644 --- a/src/main/java/com/redlimerl/speedrunigt/instance/TimerCommand.java +++ b/src/main/java/com/redlimerl/speedrunigt/instance/TimerCommand.java @@ -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); diff --git a/src/main/java/com/redlimerl/speedrunigt/mixins/ClientPlayerEntityMixin.java b/src/main/java/com/redlimerl/speedrunigt/mixins/ClientPlayerEntityMixin.java index ca21b9a54..7bb2fa4fa 100644 --- a/src/main/java/com/redlimerl/speedrunigt/mixins/ClientPlayerEntityMixin.java +++ b/src/main/java/com/redlimerl/speedrunigt/mixins/ClientPlayerEntityMixin.java @@ -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") @@ -212,6 +212,7 @@ public void updateNausea(CallbackInfo ci) { this.portalTick = 0; } } + */ @Override public void changeLookDirection(double cursorDeltaX, double cursorDeltaY) { diff --git a/src/main/java/com/redlimerl/speedrunigt/mixins/ClientWorldMixin.java b/src/main/java/com/redlimerl/speedrunigt/mixins/ClientWorldMixin.java index 4113b96f2..db3d926e4 100644 --- a/src/main/java/com/redlimerl/speedrunigt/mixins/ClientWorldMixin.java +++ b/src/main/java/com/redlimerl/speedrunigt/mixins/ClientWorldMixin.java @@ -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; @@ -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 registryKey, RegistryKey registryKey2, DimensionType dimensionType, Supplier profiler, boolean bl, boolean bl2, long l) { super(mutableWorldProperties, registryKey, registryKey2, dimensionType, profiler, bl, bl2, l); } @@ -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) { @@ -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(); diff --git a/src/main/java/com/redlimerl/speedrunigt/mixins/command/ArgumentTypesMixin.java b/src/main/java/com/redlimerl/speedrunigt/mixins/command/ArgumentTypesMixin.java new file mode 100644 index 000000000..cbdfdee13 --- /dev/null +++ b/src/main/java/com/redlimerl/speedrunigt/mixins/command/ArgumentTypesMixin.java @@ -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 > void register(String id, Class class_, ArgumentSerializer argumentSerializer) { + } + + @Inject(method = "register()V", at = @At("TAIL")) + private static void registerRunCategoryArgumentType(CallbackInfo ci) { + register("speedrunigt:run_category", RunCategoryArgumentType.class, new ConstantArgumentSerializer<>(RunCategoryArgumentType::new)); + } +} diff --git a/src/main/java/com/redlimerl/speedrunigt/option/SpeedRunOptions.java b/src/main/java/com/redlimerl/speedrunigt/option/SpeedRunOptions.java index 0cb2464fa..834e3e018 100644 --- a/src/main/java/com/redlimerl/speedrunigt/option/SpeedRunOptions.java +++ b/src/main/java/com/redlimerl/speedrunigt/option/SpeedRunOptions.java @@ -187,7 +187,7 @@ public String valueToString(Boolean value) { }; public enum TimerSaveInterval { NONE, PAUSE, TICKS } - public static final OptionArgument TIMER_DATA_AUTO_SAVE = new OptionArgument(new Identifier(SpeedRunIGT.MOD_ID, "auto_save_interval"), TimerSaveInterval.PAUSE) { + public static final OptionArgument TIMER_DATA_AUTO_SAVE = new OptionArgument(new Identifier(SpeedRunIGT.MOD_ID, "auto_save_interval"), TimerSaveInterval.TICKS) { @Override public TimerSaveInterval valueFromString(String string) { return TimerSaveInterval.valueOf(string); diff --git a/src/main/java/com/redlimerl/speedrunigt/timer/category/RunCategories.java b/src/main/java/com/redlimerl/speedrunigt/timer/category/RunCategories.java index e002c1471..62314d038 100644 --- a/src/main/java/com/redlimerl/speedrunigt/timer/category/RunCategories.java +++ b/src/main/java/com/redlimerl/speedrunigt/timer/category/RunCategories.java @@ -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(); diff --git a/src/main/java/com/redlimerl/speedrunigt/timer/packet/packets/TimerStartPacket.java b/src/main/java/com/redlimerl/speedrunigt/timer/packet/packets/TimerStartPacket.java index 093475cdc..9cc1d6ab2 100644 --- a/src/main/java/com/redlimerl/speedrunigt/timer/packet/packets/TimerStartPacket.java +++ b/src/main/java/com/redlimerl/speedrunigt/timer/packet/packets/TimerStartPacket.java @@ -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); diff --git a/src/main/resources/assets/speedrunigt/lang/en_us.json b/src/main/resources/assets/speedrunigt/lang/en_us.json index a4614ba36..f3df5b805 100644 --- a/src/main/resources/assets/speedrunigt/lang/en_us.json +++ b/src/main/resources/assets/speedrunigt/lang/en_us.json @@ -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", diff --git a/src/main/resources/speedrunigt.mixins.json b/src/main/resources/speedrunigt.mixins.json index f5f97120b..0e17d6475 100644 --- a/src/main/resources/speedrunigt.mixins.json +++ b/src/main/resources/speedrunigt.mixins.json @@ -9,6 +9,7 @@ "ServerStatHandlerMixin", "ServerWorldMixin", "access.ServerStatHandlerAccessor", + "command.ArgumentTypesMixin", "command.CommandManagerMixin", "coop.PlayerManagerMixin", "coop.TimeCommandMixin",