diff --git a/.gitignore b/.gitignore index 4029c3d2..ee8b1af1 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ bin/ # fabric run/ +logs/ # github diff --git a/README.md b/README.md index 0f513cbe..3d3b770f 100644 --- a/README.md +++ b/README.md @@ -25,22 +25,27 @@ If you run into issues, contact your launcher's support. Before using any of these commands, make sure the seed has been configured using `/cconfig seedmapper Seed set `. ### Biome locating -Usage: `/sm:locate biome ` +Usage: `/sm:locate biome `. Locates a given biome closest to the player. All biomes in all dimensions are supported. ### Structure locating -Usage: `/sm:locate feature structure []{}` +Usage: `/sm:locate feature structure []{}`. Locates a given structure closest to the player. All structures in all dimensions are supported. However, due to limitations in the underlying library, some structures (in particular desert pyramids, jungle temples and woodland mansions) may result in occasional false positives. For more advanced querying you can also use piece and variant data to further restrict the search. For example, the following command will search for end cities with ships: `/sm:locate feature structure end_city[end_ship]`. +### Ore highlighting +Usage: `/sm:highlight block [chunks]`. + +Highlights the specified block in the world. All versions from 1.13 onwards are supported. Due to high dependence on the [`OCEAN_FLOOR_WG`](https://minecraft.wiki/w/Heightmap#OCEAN_FLOOR_WG) heightmap, coal, copper and emerald ore locations may be off. + ### Slime chunk locating -Usage: `/sm:locate feature slimechunk` +Usage: `/sm:locate feature slimechunk`. Locates a slime chunk closest to the player. This will always be accurate. ### Source mutation -Usage: `/sm:source (run)|(as )|(positioned )|(rotated )|(in )|(versioned )|(seeded )` +Usage: `/sm:source (run)|(as )|(positioned )|(rotated )|(in )|(versioned )|(seeded )`. Executes a given command from a modified source. For example, modifying the source's position will execute the command as if you were in that position. This command is really powerful, use it! diff --git a/gradle.properties b/gradle.properties index ef0b9789..3a15c933 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G org.gradle.parallel=true # Mod Properties -mod_version=2.4.0 +mod_version=2.5.0-beta.2 maven_group=dev.xpple archives_base_name=seedmapper diff --git a/includes.txt b/includes.txt index f7daef62..d4a1cf9d 100644 --- a/includes.txt +++ b/includes.txt @@ -270,11 +270,16 @@ --include-function isShallowOcean --include-function isSnowy +--include-constant ANCIENT_DEBRIS +--include-constant ANDESITE --include-constant Ancient_City +--include-constant AndesiteOre +--include-constant BASALT --include-constant BASE_FLOOR --include-constant BASE_ROOF --include-constant BF_APPROX --include-constant BF_FORCED_OCEAN +--include-constant BLACKSTONE --include-constant BRIDGE_CORRIDOR_ENTRANCE --include-constant BRIDGE_CROSSING --include-constant BRIDGE_END @@ -287,7 +292,13 @@ --include-constant BRIDGE_STRAIGHT --include-constant Bastion --include-constant Blacksmith +--include-constant BlackstoneOre +--include-constant BuriedDiamondOre +--include-constant BuriedLapisOre --include-constant Butcher +--include-constant CLAY +--include-constant COAL_ORE +--include-constant COPPER_ORE --include-constant CORRIDOR_CROSSING --include-constant CORRIDOR_NETHER_WART --include-constant CORRIDOR_STAIRS @@ -296,13 +307,29 @@ --include-constant CORRIDOR_TURN_RIGHT --include-constant CORRIDOR_T_CROSSING --include-constant Church +--include-constant ClayOre +--include-constant CoalOre +--include-constant CopperOre +--include-constant DEEPSLATE +--include-constant DIAMOND_ORE +--include-constant DIORITE +--include-constant DIRT +--include-constant DeepslateOre +--include-constant DeltasGoldOre +--include-constant DeltasQuartzOre --include-constant Desert_Pyramid --include-constant Desert_Well +--include-constant DiamondOre +--include-constant DioriteOre +--include-constant DirtOre +--include-constant EMERALD_ORE --include-constant END_CITY_PIECES_MAX --include-constant END_SHIP +--include-constant EmeraldOre --include-constant End_City --include-constant End_Gateway --include-constant End_Island +--include-constant ExtraGoldOre --include-constant FAT_TOWER_BASE --include-constant FAT_TOWER_MIDDLE --include-constant FAT_TOWER_TOP @@ -313,27 +340,63 @@ --include-constant FarmSmall --include-constant Feature --include-constant Fortress +--include-constant GOLD_ORE +--include-constant GRANITE +--include-constant GRAVEL --include-constant Geode +--include-constant GoldOre +--include-constant GraniteOre +--include-constant GravelOre --include-constant HOUSE_NUM --include-constant HouseLarge --include-constant HouseSmall +--include-constant IRON_ORE --include-constant Igloo +--include-constant IronOre --include-constant Jungle_Pyramid --include-constant Jungle_Temple +--include-constant LAPIS_ORE +--include-constant LapisOre +--include-constant LargeCopperOre +--include-constant LargeDebrisOre +--include-constant LargeDiamondOre --include-constant Library +--include-constant LowerAndesiteOre +--include-constant LowerCoalOre +--include-constant LowerDioriteOre +--include-constant LowerGoldOre +--include-constant LowerGraniteOre +--include-constant LowerRedstoneOre +--include-constant MAGMA_BLOCK --include-constant MASK48 +--include-constant MagmaOre --include-constant Mansion +--include-constant MediumDiamondOre +--include-constant MiddleIronOre --include-constant Mineshaft --include-constant Monument +--include-constant NETHERRACK +--include-constant NETHER_GOLD_ORE +--include-constant NETHER_QUARTZ_ORE +--include-constant NetherGoldOre +--include-constant NetherGravelOre +--include-constant NetherQuartzOre --include-constant Ocean_Ruin --include-constant Outpost --include-constant PIECE_COUNT +--include-constant REDSTONE_ORE +--include-constant RedstoneOre --include-constant Ruined_Portal --include-constant Ruined_Portal_N --include-constant SECOND_FLOOR_1 --include-constant SECOND_FLOOR_2 --include-constant SECOND_ROOF +--include-constant SOUL_SAND +--include-constant STONE --include-constant Shipwreck +--include-constant SmallDebrisOre +--include-constant SmallIronOre +--include-constant SoulSandOre --include-constant Swamp_Hut --include-constant THIRD_FLOOR_1 --include-constant THIRD_FLOOR_2 @@ -342,19 +405,34 @@ --include-constant TOWER_FLOOR --include-constant TOWER_PIECE --include-constant TOWER_TOP +--include-constant TUFF --include-constant Trail_Ruins --include-constant Treasure --include-constant Trial_Chambers +--include-constant TuffOre +--include-constant UpperAndesiteOre +--include-constant UpperCoalOre +--include-constant UpperDioriteOre +--include-constant UpperGraniteOre +--include-constant UpperIronOre --include-constant Village --include-constant WoodHut +--include-function appendPos3List --include-function canBiomeGenerate --include-function checkForBiomes --include-function checkForBiomesAtLayer --include-function checkForTemps +--include-function createPos3List --include-function estimateSpawn +--include-function freePos3List --include-function genPotential +--include-function generateBaseOrePosition +--include-function generateOrePositions +--include-function generateOres +--include-function generateVeinPart --include-function getAvailableBiomes --include-function getBiomeCenters +--include-function getBiomeForOreGen --include-function getBiomeParaExtremes --include-function getBiomeParaLimits --include-function getEndCityPieces @@ -366,6 +444,8 @@ --include-function getLinkedGatewayChunk --include-function getLinkedGatewayPos --include-function getMineshafts +--include-function getOreConfig +--include-function getOreYPos --include-function getParaDescent --include-function getParaRange --include-function getPossibleBiomesForLimits @@ -377,6 +457,7 @@ --include-function isEndChunkEmpty --include-function isViableEndCityTerrain --include-function isViableFeatureBiome +--include-function isViableOreBiome --include-function isViableStructurePos --include-function isViableStructureTerrain --include-function locateBiome @@ -386,9 +467,11 @@ --include-function setupBiomeFilter --include-struct BiomeFilter --include-struct EndIsland +--include-struct OreConfig --include-struct Piece --include-struct Pos --include-struct Pos3 +--include-struct Pos3List --include-struct StrongholdIter --include-struct StructureConfig --include-struct StructureVariant @@ -581,6 +664,7 @@ --include-typedef u32 --include-typedef u64 --include-typedef u8 +--include-struct RandomSource --include-struct Xoroshiro --include-function biome2str @@ -589,6 +673,7 @@ --include-function initBiomeTypeColors --include-function loadSavedSeeds --include-function mc2str +--include-function ore2str --include-function parseBiomeColors --include-function savePPM --include-function str2mc diff --git a/src/main/c b/src/main/c index 2e6c2643..56e57a2f 160000 --- a/src/main/c +++ b/src/main/c @@ -1 +1 @@ -Subproject commit 2e6c26438b253531c31b79df7b52be239d9e1ebe +Subproject commit 56e57a2fbc86079623fb659c4ef8010520c5b7f2 diff --git a/src/main/java/dev/xpple/seedmapper/SeedMapper.java b/src/main/java/dev/xpple/seedmapper/SeedMapper.java index 09ba0659..cdadfc12 100644 --- a/src/main/java/dev/xpple/seedmapper/SeedMapper.java +++ b/src/main/java/dev/xpple/seedmapper/SeedMapper.java @@ -5,10 +5,13 @@ import dev.xpple.seedmapper.command.arguments.SeedResolutionArgument; import dev.xpple.seedmapper.command.commands.BuildInfoCommand; import dev.xpple.seedmapper.command.commands.CheckSeedCommand; +import dev.xpple.seedmapper.command.commands.ClearCommand; +import dev.xpple.seedmapper.command.commands.HighlightCommand; import dev.xpple.seedmapper.command.commands.LocateCommand; import dev.xpple.seedmapper.command.commands.SourceCommand; import dev.xpple.seedmapper.config.Configs; import dev.xpple.seedmapper.config.SeedResolutionAdapter; +import dev.xpple.seedmapper.render.RenderManager; import dev.xpple.seedmapper.util.SeedDatabaseHelper; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -50,6 +53,7 @@ public void onInitializeClient() { SeedDatabaseHelper.fetchSeeds(); ClientCommandRegistrationCallback.EVENT.register(SeedMapper::registerCommands); + RenderManager.registerEvents(); } private static void registerCommands(CommandDispatcher dispatcher, CommandBuildContext context) { @@ -57,5 +61,7 @@ private static void registerCommands(CommandDispatcher Component.translatable("commands.exceptions.unknownDimension", arg)); public static final DynamicCommandExceptionType UNKNOWN_VERSION_EXCEPTION = new DynamicCommandExceptionType(arg -> Component.translatable("commands.exceptions.unknownVersion", arg)); public static final DynamicCommandExceptionType UNKNOWN_BIOME_EXCEPTION = new DynamicCommandExceptionType(arg -> Component.translatable("commands.exceptions.unknownBiome", arg)); @@ -18,6 +18,7 @@ private CommandExceptions() { public static final DynamicCommandExceptionType UNKNOWN_STRUCTURE_PIECE_EXCEPTION = new DynamicCommandExceptionType(arg -> Component.translatable("commands.exceptions.unknownStructurePiece", arg)); public static final DynamicCommandExceptionType UNKNOWN_VARIANT_KEY_EXCEPTION = new DynamicCommandExceptionType(arg -> Component.translatable("commands.exceptions.unknownVariantKey", arg)); public static final DynamicCommandExceptionType UNKNOWN_VARIANT_VALUE_EXCEPTION = new DynamicCommandExceptionType(arg -> Component.translatable("commands.exceptions.unknownVariantValue", arg)); + public static final DynamicCommandExceptionType UNKNOWN_BLOCK_EXCEPTION = new DynamicCommandExceptionType(arg -> Component.translatable("commands.exceptions.unknownOre", arg)); public static final SimpleCommandExceptionType NO_SEED_AVAILABLE_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.exceptions.noSeedAvailable")); public static final DynamicCommandExceptionType NO_BIOME_FOUND_EXCEPTION = new DynamicCommandExceptionType(arg -> Component.translatable("commands.exceptions.noBiomeFound", arg)); public static final DynamicCommandExceptionType NO_STRUCTURE_FOUND_EXCEPTION = new DynamicCommandExceptionType(arg -> Component.translatable("commands.exceptions.noStructureFound", arg)); diff --git a/src/main/java/dev/xpple/seedmapper/command/arguments/BlockArgument.java b/src/main/java/dev/xpple/seedmapper/command/arguments/BlockArgument.java new file mode 100644 index 00000000..d651609f --- /dev/null +++ b/src/main/java/dev/xpple/seedmapper/command/arguments/BlockArgument.java @@ -0,0 +1,84 @@ +package dev.xpple.seedmapper.command.arguments; + +import com.github.cubiomes.Cubiomes; +import com.google.common.collect.ImmutableMap; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.datafixers.util.Pair; +import dev.xpple.seedmapper.command.CommandExceptions; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.world.level.material.MapColor; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class BlockArgument implements ArgumentType> { + + private static final Collection EXAMPLES = Arrays.asList("diamond_ore", "gold_ore", "nether_quartz_ore"); + + public static final Map> BLOCKS = ImmutableMap.>builder() + .put("ancient_debris", Pair.of(Cubiomes.ANCIENT_DEBRIS(), MapColor.TERRACOTTA_BROWN.col)) + .put("andesite", Pair.of(Cubiomes.ANDESITE(), MapColor.STONE.col)) + .put("basalt", Pair.of(Cubiomes.BASALT(), MapColor.COLOR_BLACK.col)) + .put("blackstone", Pair.of(Cubiomes.BLACKSTONE(), MapColor.COLOR_BLACK.col)) + .put("clay", Pair.of(Cubiomes.CLAY(), MapColor.CLAY.col)) + .put("coal_ore", Pair.of(Cubiomes.COAL_ORE(), MapColor.COLOR_BLACK.col)) + .put("copper_ore", Pair.of(Cubiomes.COPPER_ORE(), MapColor.COLOR_ORANGE.col)) + .put("deepslate", Pair.of(Cubiomes.DEEPSLATE(), MapColor.DEEPSLATE.col)) + .put("diamond_ore", Pair.of(Cubiomes.DIAMOND_ORE(), MapColor.DIAMOND.col)) + .put("diorite", Pair.of(Cubiomes.DIORITE(), MapColor.QUARTZ.col)) + .put("dirt", Pair.of(Cubiomes.DIRT(), MapColor.DIRT.col)) + .put("emerald_ore", Pair.of(Cubiomes.EMERALD_ORE(), MapColor.EMERALD.col)) + .put("gold_ore", Pair.of(Cubiomes.GOLD_ORE(), MapColor.GOLD.col)) + .put("granite", Pair.of(Cubiomes.GRANITE(), MapColor.DIRT.col)) + .put("gravel", Pair.of(Cubiomes.GRAVEL(), MapColor.STONE.col)) + .put("iron_ore", Pair.of(Cubiomes.IRON_ORE(), MapColor.RAW_IRON.col)) + .put("lapis_ore", Pair.of(Cubiomes.LAPIS_ORE(), MapColor.LAPIS.col)) + .put("magma_block", Pair.of(Cubiomes.MAGMA_BLOCK(), MapColor.NETHER.col)) + .put("netherrack", Pair.of(Cubiomes.NETHERRACK(), MapColor.NETHER.col)) + .put("nether_gold_ore", Pair.of(Cubiomes.NETHER_GOLD_ORE(), MapColor.GOLD.col)) + .put("nether_quartz_ore", Pair.of(Cubiomes.NETHER_QUARTZ_ORE(), MapColor.QUARTZ.col)) + .put("redstone_ore", Pair.of(Cubiomes.REDSTONE_ORE(), MapColor.FIRE.col)) + .put("soul_sand", Pair.of(Cubiomes.SOUL_SAND(), MapColor.COLOR_BROWN.col)) + .put("stone", Pair.of(Cubiomes.STONE(), MapColor.STONE.col)) + .put("tuff", Pair.of(Cubiomes.TUFF(), MapColor.COLOR_GRAY.col)) + .build(); + + public static BlockArgument block() { + return new BlockArgument(); + } + + @SuppressWarnings("unchecked") + public static Pair getBlock(CommandContext context, String name) { + return (Pair) context.getArgument(name, Pair.class); + } + + @Override + public Pair parse(StringReader reader) throws CommandSyntaxException { + int cursor = reader.getCursor(); + String blockString = reader.readUnquotedString(); + Pair blockPair = BLOCKS.get(blockString); + if (blockPair == null) { + reader.setCursor(cursor); + throw CommandExceptions.UNKNOWN_BLOCK_EXCEPTION.create(blockString); + } + return blockPair; + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return SharedSuggestionProvider.suggest(BLOCKS.keySet(), builder); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } +} diff --git a/src/main/java/dev/xpple/seedmapper/command/commands/ClearCommand.java b/src/main/java/dev/xpple/seedmapper/command/commands/ClearCommand.java new file mode 100644 index 00000000..8338bd13 --- /dev/null +++ b/src/main/java/dev/xpple/seedmapper/command/commands/ClearCommand.java @@ -0,0 +1,23 @@ +package dev.xpple.seedmapper.command.commands; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import dev.xpple.seedmapper.command.CustomClientCommandSource; +import dev.xpple.seedmapper.render.RenderManager; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.network.chat.Component; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; + +public class ClearCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(literal("sm:clear") + .executes(ctx -> clear(CustomClientCommandSource.of(ctx.getSource())))); + } + + private static int clear(CustomClientCommandSource source) { + RenderManager.clear(); + source.sendFeedback(Component.translatable("command.clear.success")); + return Command.SINGLE_SUCCESS; + } +} diff --git a/src/main/java/dev/xpple/seedmapper/command/commands/HighlightCommand.java b/src/main/java/dev/xpple/seedmapper/command/commands/HighlightCommand.java new file mode 100644 index 00000000..c6dcf9a7 --- /dev/null +++ b/src/main/java/dev/xpple/seedmapper/command/commands/HighlightCommand.java @@ -0,0 +1,138 @@ +package dev.xpple.seedmapper.command.commands; + +import com.github.cubiomes.Cubiomes; +import com.github.cubiomes.Generator; +import com.github.cubiomes.OreConfig; +import com.github.cubiomes.Pos3; +import com.github.cubiomes.Pos3List; +import com.github.cubiomes.SurfaceNoise; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.datafixers.util.Pair; +import dev.xpple.seedmapper.command.CustomClientCommandSource; +import dev.xpple.seedmapper.config.Configs; +import dev.xpple.seedmapper.feature.OreTypes; +import dev.xpple.seedmapper.render.RenderManager; +import dev.xpple.seedmapper.util.SpiralLoop; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.mojang.brigadier.arguments.IntegerArgumentType.*; +import static dev.xpple.seedmapper.command.arguments.BlockArgument.*; +import static dev.xpple.seedmapper.thread.ThreadingHelper.*; +import static dev.xpple.seedmapper.util.ChatBuilder.*; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; + +public class HighlightCommand { + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(literal("sm:highlight") + .then(literal("block") + .then(argument("block", block()) + .executes(ctx -> highlightBlock(CustomClientCommandSource.of(ctx.getSource()), getBlock(ctx, "block"))) + .then(argument("chunks", integer(0, 20)) + .executes(ctx -> submit(() -> highlightBlock(CustomClientCommandSource.of(ctx.getSource()), getBlock(ctx, "block"), getInteger(ctx, "chunks")))))))); + } + + private static int highlightBlock(CustomClientCommandSource source, Pair blockPair) throws CommandSyntaxException { + return highlightBlock(source, blockPair, 0); + } + + private static int highlightBlock(CustomClientCommandSource source, Pair blockPair, int chunkRange) throws CommandSyntaxException { + int version = source.getVersion(); + int dimension = source.getDimension(); + long seed = source.getSeed().getSecond(); + try (Arena arena = Arena.ofConfined()) { + MemorySegment generator = Generator.allocate(arena); + Cubiomes.setupGenerator(generator, version, 0); + Cubiomes.applySeed(generator, dimension, seed); + MemorySegment surfaceNoise = SurfaceNoise.allocate(arena); + Cubiomes.initSurfaceNoise(surfaceNoise, dimension, seed); + + ChunkPos center = new ChunkPos(BlockPos.containing(source.getPosition())); + + int[] count = {0}; + SpiralLoop.spiral(center.x, center.z, chunkRange, (chunkX, chunkZ) -> { + LevelChunk chunk = source.getWorld().getChunkSource().getChunk(chunkX, chunkZ, ChunkStatus.FULL, false); + boolean doAirCheck = Configs.OreAirCheck && chunk != null; + Map generatedOres = new HashMap<>(); + // TODO: check biome at multiple altitudes (technically should check 3x3 square of chunks) + int biome = Cubiomes.getBiomeForOreGen(generator, chunkX, chunkZ); + OreTypes.ORE_TYPES.stream() + .filter(oreType -> Cubiomes.isViableOreBiome(version, oreType, biome) != 0) + .mapMulti((oreType, consumer) -> { + MemorySegment oreConfig = OreConfig.allocate(arena); + if (Cubiomes.getOreConfig(oreType, version, biome, oreConfig) != 0) { + consumer.accept(oreConfig); + } + }) + .sorted(Comparator.comparingInt(OreConfig::index)) + .forEachOrdered(oreConfig -> { + int oreBlock = OreConfig.oreBlock(oreConfig); + int numReplaceBlocks = OreConfig.numReplaceBlocks(oreConfig); + MemorySegment replaceBlocks = OreConfig.replaceBlocks(oreConfig); + MemorySegment pos3List = Cubiomes.generateOres(arena, generator, surfaceNoise, oreConfig, chunkX, chunkZ); + int size = Pos3List.size(pos3List); + MemorySegment pos3s = Pos3List.pos3s(pos3List); + for (int i = 0; i < size; i++) { + MemorySegment pos3 = Pos3.asSlice(pos3s, i); + BlockPos pos = new BlockPos(Pos3.x(pos3), Pos3.y(pos3), Pos3.z(pos3)); + if (doAirCheck && chunk.getBlockState(pos).isAir()) { + continue; + } + Integer previouslyGeneratedOre = generatedOres.get(pos); + if (previouslyGeneratedOre != null) { + boolean contains = false; + for (int j = 0; j < numReplaceBlocks; j++) { + int replaceBlock = replaceBlocks.getAtIndex(Cubiomes.C_INT, j); + if (replaceBlock == previouslyGeneratedOre) { + contains = true; + break; + } + } + if (!contains) { + continue; + } + } + generatedOres.put(pos, oreBlock); + } + Cubiomes.freePos3List(pos3List); + }); + + int block = blockPair.getFirst(); + int colour = blockPair.getSecond(); + List blockOres = generatedOres.entrySet().stream() + .filter(entry -> entry.getValue() == block) + .map(Map.Entry::getKey) + .toList(); + count[0] += blockOres.size(); + source.getClient().schedule(() -> { + RenderManager.drawBoxes(blockOres, colour); + source.sendFeedback(Component.translatable("command.highlight.chunkSuccess", accent(String.valueOf(blockOres.size())), copy( + hover( + accent("%d %d".formatted(chunkX, chunkZ)), + base(Component.translatable("chat.copy.click")) + ), + "%d %d".formatted(chunkX, chunkZ) + ))); + }); + + return false; + }); + + source.getClient().schedule(() -> source.sendFeedback(Component.translatable("command.highlight.success", accent(String.valueOf(count[0]))))); + return count[0]; + } + } +} diff --git a/src/main/java/dev/xpple/seedmapper/command/commands/LocateCommand.java b/src/main/java/dev/xpple/seedmapper/command/commands/LocateCommand.java index 3ec2a4bd..043c61c7 100644 --- a/src/main/java/dev/xpple/seedmapper/command/commands/LocateCommand.java +++ b/src/main/java/dev/xpple/seedmapper/command/commands/LocateCommand.java @@ -16,19 +16,14 @@ import dev.xpple.seedmapper.command.CustomClientCommandSource; import dev.xpple.seedmapper.feature.StructureChecks; import dev.xpple.seedmapper.feature.StructureVariantFeedbackHelper; -import dev.xpple.seedmapper.util.CheckedSupplier; import dev.xpple.seedmapper.util.SpiralLoop; import dev.xpple.seedmapper.util.TwoDTree; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.util.RandomSource; -import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.levelgen.WorldgenRandom; @@ -36,14 +31,12 @@ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.stream.IntStream; import static com.mojang.brigadier.arguments.BoolArgumentType.*; import static dev.xpple.seedmapper.command.arguments.BiomeArgument.*; import static dev.xpple.seedmapper.command.arguments.StructurePredicateArgument.*; +import static dev.xpple.seedmapper.thread.ThreadingHelper.*; import static dev.xpple.seedmapper.util.ChatBuilder.*; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; @@ -53,22 +46,6 @@ public class LocateCommand { private static final Long2ObjectMap cachedStrongholds = new Long2ObjectOpenHashMap<>(); - private static final ExecutorService locatingExecutor = Executors.newCachedThreadPool(); - private static Future currentTask = null; - - public static final Component STOP_TASK_COMPONENT = run(hover(format(Component.translatable("commands.exceptions.alreadyBusyLocating.stopTask"), ChatFormatting.UNDERLINE), base(Component.translatable("commands.exceptions.alreadyBusyLocating.clickToStop"))), () -> { - if (currentTask != null && !currentTask.isDone()) { - currentTask.cancel(true); - Minecraft.getInstance().player.displayClientMessage(Component.translatable("command.locate.taskStopped"), false); - } else { - Minecraft.getInstance().player.displayClientMessage(format(Component.translatable("command.locate.noTaskRunning"), ChatFormatting.RED), false); - } - }); - - static { - Runtime.getRuntime().addShutdownHook(new Thread(locatingExecutor::shutdownNow)); - } - public static void register(CommandDispatcher dispatcher) { dispatcher.register(literal("sm:locate") .then(literal("biome") @@ -88,25 +65,6 @@ public static void register(CommandDispatcher dispatc .executes(ctx -> submit(() -> locateSpawn(CustomClientCommandSource.of(ctx.getSource())))))); } - private static int submit(CheckedSupplier task) throws CommandSyntaxException { - if (currentTask != null && !currentTask.isDone()) { - throw CommandExceptions.ALREADY_BUSY_LOCATING_EXCEPTION.create(); - } - currentTask = locatingExecutor.submit(() -> { - try { - return task.get(); - } catch (CommandSyntaxException e) { - Player player = Minecraft.getInstance().player; - if (player != null) { - Minecraft.getInstance().schedule(() -> player.displayClientMessage(format((MutableComponent) e.getRawMessage(), ChatFormatting.RED), false)); - } - return 0; - } - }); - Minecraft.getInstance().player.displayClientMessage(Component.translatable("command.locate.taskStarted"), false); - return Command.SINGLE_SUCCESS; - } - private static int locateBiome(CustomClientCommandSource source, int biome) throws CommandSyntaxException { int dimension = source.getDimension(); if (Cubiomes.getDimension(biome) != dimension) { diff --git a/src/main/java/dev/xpple/seedmapper/config/Configs.java b/src/main/java/dev/xpple/seedmapper/config/Configs.java index fb40a863..d2b086b8 100644 --- a/src/main/java/dev/xpple/seedmapper/config/Configs.java +++ b/src/main/java/dev/xpple/seedmapper/config/Configs.java @@ -21,4 +21,7 @@ public static void addSavedSeed(long seed) { @Config public static SeedResolutionArgument.SeedResolution SeedResolutionOrder = new SeedResolutionArgument.SeedResolution(); + + @Config(comment = "Whether or not SeedMapper should use in-game air checks to invalidate ore positions.") + public static boolean OreAirCheck = true; } diff --git a/src/main/java/dev/xpple/seedmapper/feature/OreTypes.java b/src/main/java/dev/xpple/seedmapper/feature/OreTypes.java new file mode 100644 index 00000000..22905194 --- /dev/null +++ b/src/main/java/dev/xpple/seedmapper/feature/OreTypes.java @@ -0,0 +1,59 @@ +package dev.xpple.seedmapper.feature; + +import com.github.cubiomes.Cubiomes; +import com.google.common.collect.ImmutableSet; + +import java.util.Set; + +public final class OreTypes { + private OreTypes() { + } + + public static final Set ORE_TYPES = ImmutableSet.builder() + .add(Cubiomes.AndesiteOre()) + .add(Cubiomes.BlackstoneOre()) + .add(Cubiomes.BuriedDiamondOre()) + .add(Cubiomes.BuriedLapisOre()) + .add(Cubiomes.ClayOre()) + .add(Cubiomes.CoalOre()) + .add(Cubiomes.CopperOre()) + .add(Cubiomes.DeepslateOre()) + .add(Cubiomes.DeltasGoldOre()) + .add(Cubiomes.DeltasQuartzOre()) + .add(Cubiomes.DiamondOre()) + .add(Cubiomes.DioriteOre()) + .add(Cubiomes.DirtOre()) + .add(Cubiomes.EmeraldOre()) + .add(Cubiomes.ExtraGoldOre()) + .add(Cubiomes.GoldOre()) + .add(Cubiomes.GraniteOre()) + .add(Cubiomes.GravelOre()) + .add(Cubiomes.IronOre()) + .add(Cubiomes.LapisOre()) + .add(Cubiomes.LargeCopperOre()) + .add(Cubiomes.LargeDebrisOre()) + .add(Cubiomes.LargeDiamondOre()) + .add(Cubiomes.LowerAndesiteOre()) + .add(Cubiomes.LowerCoalOre()) + .add(Cubiomes.LowerDioriteOre()) + .add(Cubiomes.LowerGoldOre()) + .add(Cubiomes.LowerGraniteOre()) + .add(Cubiomes.LowerRedstoneOre()) + .add(Cubiomes.MagmaOre()) + .add(Cubiomes.MediumDiamondOre()) + .add(Cubiomes.MiddleIronOre()) + .add(Cubiomes.NetherGoldOre()) + .add(Cubiomes.NetherGravelOre()) + .add(Cubiomes.NetherQuartzOre()) + .add(Cubiomes.RedstoneOre()) + .add(Cubiomes.SmallDebrisOre()) + .add(Cubiomes.SmallIronOre()) + .add(Cubiomes.SoulSandOre()) + .add(Cubiomes.TuffOre()) + .add(Cubiomes.UpperAndesiteOre()) + .add(Cubiomes.UpperCoalOre()) + .add(Cubiomes.UpperDioriteOre()) + .add(Cubiomes.UpperGraniteOre()) + .add(Cubiomes.UpperIronOre()) + .build(); +} diff --git a/src/main/java/dev/xpple/seedmapper/mixin/ClientPacketListenerMixin.java b/src/main/java/dev/xpple/seedmapper/mixin/ClientPacketListenerMixin.java new file mode 100644 index 00000000..641e2f6e --- /dev/null +++ b/src/main/java/dev/xpple/seedmapper/mixin/ClientPacketListenerMixin.java @@ -0,0 +1,23 @@ +package dev.xpple.seedmapper.mixin; + +import dev.xpple.seedmapper.render.RenderManager; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.protocol.game.ClientboundLoginPacket; +import net.minecraft.network.protocol.game.ClientboundRespawnPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPacketListener.class) +public class ClientPacketListenerMixin { + @Inject(method = "handleLogin", at = @At("HEAD")) + private void onHandleLogin(ClientboundLoginPacket packet, CallbackInfo ci) { + RenderManager.clear(); + } + + @Inject(method = "handleRespawn", at = @At("HEAD")) + private void onHandleRespawn(ClientboundRespawnPacket packet, CallbackInfo ci) { + RenderManager.clear(); + } +} diff --git a/src/main/java/dev/xpple/seedmapper/render/Line.java b/src/main/java/dev/xpple/seedmapper/render/Line.java new file mode 100644 index 00000000..be6aa6ba --- /dev/null +++ b/src/main/java/dev/xpple/seedmapper/render/Line.java @@ -0,0 +1,18 @@ +package dev.xpple.seedmapper.render; + +import net.minecraft.world.phys.Vec3; + +public record Line(Vec3 start, Vec3 end, int colour) { + public Line { + // ensure consistent ordering, the ordering itself doesn't matter + if (start.hashCode() > end.hashCode()) { + Vec3 temp = start; + start = end; + end = temp; + } + } + + public Line offset(Vec3 offset) { + return new Line(this.start.add(offset), this.end.add(offset), this.colour); + } +} diff --git a/src/main/java/dev/xpple/seedmapper/render/NoDepthLayer.java b/src/main/java/dev/xpple/seedmapper/render/NoDepthLayer.java new file mode 100644 index 00000000..0a28618e --- /dev/null +++ b/src/main/java/dev/xpple/seedmapper/render/NoDepthLayer.java @@ -0,0 +1,25 @@ +package dev.xpple.seedmapper.render; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.platform.DepthTestFunction; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; + +import java.util.OptionalDouble; + +public final class NoDepthLayer { + private NoDepthLayer() { + } + + private static final RenderPipeline LINES_NO_DEPTH_PIPELINE = RenderPipelines.register( + RenderPipeline.builder(RenderPipelines.LINES_SNIPPET) + .withLocation(ResourceLocation.fromNamespaceAndPath("seedmapper", "pipeline/lines_no_depth")) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .build() + ); + public static final RenderType LINES_NO_DEPTH_LAYER = RenderType.create("seedmapper_no_depth", 3 * 512, LINES_NO_DEPTH_PIPELINE, RenderType.CompositeState.builder() + .setLayeringState(RenderType.VIEW_OFFSET_Z_LAYERING) + .setLineState(new RenderType.LineStateShard(OptionalDouble.of(2))) + .createCompositeState(false)); +} diff --git a/src/main/java/dev/xpple/seedmapper/render/RenderManager.java b/src/main/java/dev/xpple/seedmapper/render/RenderManager.java new file mode 100644 index 00000000..05ab5683 --- /dev/null +++ b/src/main/java/dev/xpple/seedmapper/render/RenderManager.java @@ -0,0 +1,93 @@ +package dev.xpple.seedmapper.render; + +import com.google.common.cache.CacheBuilder; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.core.BlockPos; +import net.minecraft.util.ARGB; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.phys.Vec3; + +import java.time.Duration; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public final class RenderManager { + + private RenderManager() { + } + + private static final Set lines = Collections.newSetFromMap(CacheBuilder.newBuilder().expireAfterWrite(Duration.ofMinutes(5)).build().asMap());; + + public static void drawBoxes(List posBatch, int colour) { + Set lines = new HashSet<>(); + + posBatch.forEach(pos -> { + Vec3 minPosition = new Vec3(pos); + Vec3 size = new Vec3(1, 1, 1); + addLine(new Line(minPosition, minPosition.add(size.x(), 0, 0), colour), lines); + addLine(new Line(minPosition, minPosition.add(0, size.y(), 0), colour), lines); + addLine(new Line(minPosition, minPosition.add(0, 0, size.z()), colour), lines); + addLine(new Line(minPosition.add(size.x(), 0, size.z()), minPosition.add(size.x(), 0, 0), colour), lines); + addLine(new Line(minPosition.add(size.x(), 0, size.z()), minPosition.add(size.x(), size.y(), size.z()), colour), lines); + addLine(new Line(minPosition.add(size.x(), 0, size.z()), minPosition.add(0, 0, size.z()), colour), lines); + addLine(new Line(minPosition.add(size.x(), size.y(), 0), minPosition.add(size.x(), 0, 0), colour), lines); + addLine(new Line(minPosition.add(size.x(), size.y(), 0), minPosition.add(0, size.y(), 0), colour), lines); + addLine(new Line(minPosition.add(size.x(), size.y(), 0), minPosition.add(size.x(), size.y(), size.z()), colour), lines); + addLine(new Line(minPosition.add(0, size.y(), size.z()), minPosition.add(0, 0, size.z()), colour), lines); + addLine(new Line(minPosition.add(0, size.y(), size.z()), minPosition.add(0, size.y(), 0), colour), lines); + addLine(new Line(minPosition.add(0, size.y(), size.z()), minPosition.add(size.x(), size.y(), size.z()), colour), lines); + }); + RenderManager.lines.addAll(lines); + } + + public static void clear() { + lines.clear(); + } + + private static void addLine(Line line, Set lines) { + if (!lines.add(line)) { + lines.remove(line); + } + } + + public static void registerEvents() { + WorldRenderEvents.AFTER_TRANSLUCENT.register(RenderManager::renderLines); + } + + private static void renderLines(WorldRenderContext context) { + lines.forEach(line -> { + ChunkPos chunkPos = new ChunkPos(BlockPos.containing(line.start())); + if (context.world().getChunk(chunkPos.x, chunkPos.z, ChunkStatus.FULL, false) == null) { + return; + } + Line relativeLine = line.offset(context.camera().getPosition().scale(-1)); + Vec3 start = relativeLine.start(); + Vec3 end = relativeLine.end(); + Vec3 normal = end.subtract(start).normalize(); + int colour = line.colour(); + float red = ARGB.redFloat(colour); + float green = ARGB.greenFloat(colour); + float blue = ARGB.blueFloat(colour); + + PoseStack stack = context.matrixStack(); + stack.pushPose(); + PoseStack.Pose pose = stack.last(); + VertexConsumer buffer = context.consumers().getBuffer(NoDepthLayer.LINES_NO_DEPTH_LAYER); + buffer + .addVertex(pose, (float) start.x, (float) start.y, (float) start.z) + .setColor(red, green, blue, 1.0F) + .setNormal(pose, (float) normal.x, (float) normal.y, (float) normal.z); + buffer + .addVertex(pose, (float) end.x, (float) end.y, (float) end.z) + .setColor(red, green, blue, 1.0F) + .setNormal(pose, (float) normal.x, (float) normal.y, (float) normal.z); + stack.popPose(); + }); + } +} diff --git a/src/main/java/dev/xpple/seedmapper/thread/ThreadingHelper.java b/src/main/java/dev/xpple/seedmapper/thread/ThreadingHelper.java new file mode 100644 index 00000000..aa979802 --- /dev/null +++ b/src/main/java/dev/xpple/seedmapper/thread/ThreadingHelper.java @@ -0,0 +1,57 @@ +package dev.xpple.seedmapper.thread; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.xpple.seedmapper.command.CommandExceptions; +import dev.xpple.seedmapper.util.CheckedSupplier; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.entity.player.Player; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import static dev.xpple.seedmapper.util.ChatBuilder.*; + +public final class ThreadingHelper { + private ThreadingHelper() { + } + + private static final ExecutorService locatingExecutor = Executors.newCachedThreadPool(); + private static Future currentTask = null; + + public static final Component STOP_TASK_COMPONENT = run(hover(format(Component.translatable("commands.exceptions.alreadyBusyLocating.stopTask"), ChatFormatting.UNDERLINE), base(Component.translatable("commands.exceptions.alreadyBusyLocating.clickToStop"))), () -> { + if (currentTask != null && !currentTask.isDone()) { + currentTask.cancel(true); + Minecraft.getInstance().player.displayClientMessage(Component.translatable("command.locate.taskStopped"), false); + } else { + Minecraft.getInstance().player.displayClientMessage(format(Component.translatable("command.locate.noTaskRunning"), ChatFormatting.RED), false); + } + }); + + static { + Runtime.getRuntime().addShutdownHook(new Thread(locatingExecutor::shutdownNow)); + } + + public static int submit(CheckedSupplier task) throws CommandSyntaxException { + if (currentTask != null && !currentTask.isDone()) { + throw CommandExceptions.ALREADY_BUSY_LOCATING_EXCEPTION.create(); + } + currentTask = locatingExecutor.submit(() -> { + try { + return task.get(); + } catch (CommandSyntaxException e) { + Player player = Minecraft.getInstance().player; + if (player != null) { + Minecraft.getInstance().schedule(() -> player.displayClientMessage(format((MutableComponent) e.getRawMessage(), ChatFormatting.RED), false)); + } + return 0; + } + }); + Minecraft.getInstance().player.displayClientMessage(Component.translatable("command.locate.taskStarted"), false); + return Command.SINGLE_SUCCESS; + } +} diff --git a/src/main/resources/assets/seedmapper/lang/en_us.json b/src/main/resources/assets/seedmapper/lang/en_us.json index 835ff692..7f2dd107 100644 --- a/src/main/resources/assets/seedmapper/lang/en_us.json +++ b/src/main/resources/assets/seedmapper/lang/en_us.json @@ -13,6 +13,7 @@ "commands.exceptions.unknownVariantKey": "Unknown structure variant key \"%s\".", "commands.exceptions.unknownVariantValue": "Unknown structure variant value \"%s\".", "commands.exceptions.noStructureFound": "Couldn't locate structure within %s blocks.", + "commands.exceptions.unknownOre": "Unknown block \"%s\".", "commands.exceptions.incompatibleParameters": "Incompatible parameters.", "commands.exceptions.unknownResolutionMethod": "Unknowing resolution method \"%s\".", @@ -58,5 +59,10 @@ "command.locate.feature.slimeChunk.copyChunk": "Click to copy chunk coordinates of this slime chunk", "command.locate.spawn.success": "Spawn point estimated to be at %s.", + "command.highlight.chunkSuccess": "Highlighted %s ores in chunk %s.", + "command.highlight.success": "Highlighted %s ores in total!", + + "command.clear.success": "Cleared all renders.", + "command.buildInfo.success": "Running SeedMapper on version %s (%s@{%s})." } diff --git a/src/main/resources/mixins.seedmapper.json b/src/main/resources/mixins.seedmapper.json index 98277138..7c7274b4 100644 --- a/src/main/resources/mixins.seedmapper.json +++ b/src/main/resources/mixins.seedmapper.json @@ -5,6 +5,7 @@ "mixins": [ ], "client": [ + "ClientPacketListenerMixin", "ScreenMixin" ], "injectors": {