diff --git a/README.md b/README.md index 7cdc0bc2..3f2f4423 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ An addon to Meteor Client that adds modules and commands that were too useless t - `.save-skin` - `.heads` - `.server` (Port scanning ported from [Cornos](https://github.com/cornos/Cornos/blob/master/src/main/java/me/zeroX150/cornos/features/command/impl/Scan.java)) +- `.seed` (taken from an [unmerged pr](https://github.com/MeteorDevelopment/meteor-client/pull/1300)) +- rewritten `.locate` (taken from an [unmerged pr](https://github.com/MeteorDevelopment/meteor-client/pull/1300)) - `.setblock` - `.teleport` - `.terrain-export` (Ported from [BleachHack](https://github.com/BleachDrinker420/BleachHack/blob/master/BleachHack-Fabric-1.17/src/main/java/bleach/hack/command/commands/CmdTerrain.java)) diff --git a/build.gradle b/build.gradle index f1517956..2d966fae 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,10 @@ dependencies { // You may need to force-disable transitiveness on them. modImplementation "com.github.MeteorDevelopment:meteor-client:master-SNAPSHOT" + + // Seed .locate features + modImplementation 'com.github.hube12:SEED:master-SNAPSHOT' + include 'com.github.hube12:SEED:master-SNAPSHOT' } processResources { diff --git a/src/main/java/cloudburst/rejects/MeteorRejectsAddon.java b/src/main/java/cloudburst/rejects/MeteorRejectsAddon.java index 7eee4ead..82a26e46 100644 --- a/src/main/java/cloudburst/rejects/MeteorRejectsAddon.java +++ b/src/main/java/cloudburst/rejects/MeteorRejectsAddon.java @@ -76,7 +76,9 @@ public void onInitialize() { commands.add(new GhostCommand()); commands.add(new GiveCommand()); commands.add(new SaveSkinCommand()); + commands.add(new SeedCommand()); commands.add(new HeadsCommand()); + // commands.add(new LocateCommand()); I wish it was that simple -_- commands.add(new ServerCommand()); commands.add(new SetBlockCommand()); commands.add(new TeleportCommand()); diff --git a/src/main/java/cloudburst/rejects/arguments/EnumArgumentType.java b/src/main/java/cloudburst/rejects/arguments/EnumArgumentType.java new file mode 100644 index 00000000..aa74f005 --- /dev/null +++ b/src/main/java/cloudburst/rejects/arguments/EnumArgumentType.java @@ -0,0 +1,56 @@ +package cloudburst.rejects.arguments; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +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.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import java.lang.reflect.InvocationTargetException; + +import net.minecraft.command.CommandSource; +import net.minecraft.text.LiteralText; + +public class EnumArgumentType> implements ArgumentType { + private static final DynamicCommandExceptionType NO_SUCH_TYPE = new DynamicCommandExceptionType(o -> + new LiteralText(o + " is not a valid argument.")); + + private T[] values; + + public EnumArgumentType(T defaultValue) { + super(); + + try { + values = (T[]) defaultValue.getClass().getMethod("values").invoke(null); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + e.printStackTrace(); + } + } + + public static > EnumArgumentType enumArgument(T defaultValue) { + return new EnumArgumentType(defaultValue); + } + + public static > T getEnum(CommandContext context, String name, T defaultValue) { + return (T) context.getArgument(name, defaultValue.getClass()); + } + + @Override + public T parse(StringReader reader) throws CommandSyntaxException { + String argument = reader.readString(); + for (T t : values) { + if (t.toString().equals(argument)) return t; + } + throw NO_SUCH_TYPE.create(argument); + } + + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return CommandSource.suggestMatching(Arrays.stream(values).map(T::toString), builder); + } +} diff --git a/src/main/java/cloudburst/rejects/commands/LocateCommand.java b/src/main/java/cloudburst/rejects/commands/LocateCommand.java new file mode 100644 index 00000000..59a2e22c --- /dev/null +++ b/src/main/java/cloudburst/rejects/commands/LocateCommand.java @@ -0,0 +1,194 @@ +package cloudburst.rejects.commands; + +import baritone.api.BaritoneAPI; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import meteordevelopment.meteorclient.MeteorClient; +import meteordevelopment.meteorclient.events.packets.PacketEvent; +import meteordevelopment.meteorclient.systems.commands.Command; +import cloudburst.rejects.arguments.EnumArgumentType; +import meteordevelopment.meteorclient.utils.Utils; +import meteordevelopment.meteorclient.utils.player.ChatUtils; +import meteordevelopment.meteorclient.utils.player.FindItemResult; +import meteordevelopment.meteorclient.utils.player.InvUtils; +import cloudburst.rejects.utils.WorldGenUtils; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.command.CommandSource; +import net.minecraft.entity.EntityType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket; +import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.BaseText; +import net.minecraft.text.LiteralText; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +import static com.mojang.brigadier.Command.SINGLE_SUCCESS; + +public class LocateCommand extends Command { + + private final static DynamicCommandExceptionType NOT_FOUND = new DynamicCommandExceptionType(o -> { + if (o instanceof WorldGenUtils.Feature) { + return new LiteralText(String.format( + "%s not found.", + Utils.nameToTitle(o.toString().replaceAll("_", "-"))) + ); + } + return new LiteralText("Not found."); + }); + + private Vec3d firstStart; + private Vec3d firstEnd; + private Vec3d secondStart; + private Vec3d secondEnd; + + public LocateCommand() { + super("locate", "Locates structures", "loc"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then(literal("lodestone").executes(ctx -> { + ItemStack stack = mc.player.getInventory().getMainHandStack(); + if (stack.getItem() != Items.COMPASS) { + error("You need to hold a lodestone compass"); + return SINGLE_SUCCESS; + } + NbtCompound tag = stack.getTag(); + if (tag == null) { + error("Couldn't get the NBT data. Are you holding a (highlight)lodestone(default) compass?"); + return SINGLE_SUCCESS; + } + NbtCompound nbt1 = tag.getCompound("LodestonePos"); + if (nbt1 == null) { + error("Couldn't get the NBT data. Are you holding a (highlight)lodestone(default) compass?"); + return SINGLE_SUCCESS; + } + + Vec3d coords = new Vec3d(nbt1.getDouble("X"),nbt1.getDouble("Y"),nbt1.getDouble("Z")); + BaseText text = new LiteralText("Lodestone located at "); + text.append(ChatUtils.formatCoords(coords)); + text.append("."); + info(text); + return SINGLE_SUCCESS; + })); + + builder.then(argument("feature", EnumArgumentType.enumArgument(WorldGenUtils.Feature.stronghold)).executes(ctx -> { + WorldGenUtils.Feature feature = EnumArgumentType.getEnum(ctx, "feature", WorldGenUtils.Feature.stronghold); + BlockPos pos = WorldGenUtils.locateFeature(feature, mc.player.getBlockPos()); + if (pos != null) { + BaseText text = new LiteralText(String.format( + "%s located at ", + Utils.nameToTitle(feature.toString().replaceAll("_", "-")) + )); + Vec3d coords = new Vec3d(pos.getX(), pos.getY(), pos.getZ()); + text.append(ChatUtils.formatCoords(coords)); + text.append("."); + info(text); + return SINGLE_SUCCESS; + } + if (feature == WorldGenUtils.Feature.stronghold) { + FindItemResult eye = InvUtils.findInHotbar(Items.ENDER_EYE); + if (eye.found()) { + BaritoneAPI.getProvider().getPrimaryBaritone().getCommandManager().execute("follow entity minecraft:eye_of_ender"); + firstStart = null; + firstEnd = null; + secondStart = null; + secondEnd = null; + MeteorClient.EVENT_BUS.subscribe(this); + info("Please throw the first Eye of Ender"); + } + } + throw NOT_FOUND.create(feature); + })); + + builder.then(literal("cancel").executes(s -> { + cancel(); + return SINGLE_SUCCESS; + })); + } + + private void cancel() { + warning("Locate canceled"); + MeteorClient.EVENT_BUS.unsubscribe(this); + } + + @EventHandler + private void onReadPacket(PacketEvent.Receive event) { + if (event.packet instanceof EntitySpawnS2CPacket) { + EntitySpawnS2CPacket packet = (EntitySpawnS2CPacket) event.packet; + if (packet.getEntityTypeId() == EntityType.EYE_OF_ENDER) { + firstPosition(packet.getX(),packet.getY(),packet.getZ()); + } + } + if (event.packet instanceof PlaySoundS2CPacket) { + PlaySoundS2CPacket packet = (PlaySoundS2CPacket) event.packet; + if (packet.getSound() == SoundEvents.ENTITY_ENDER_EYE_DEATH) { + lastPosition(packet.getX(), packet.getY(), packet.getZ()); + } + } + } + + private void firstPosition(double x, double y, double z) { + Vec3d pos = new Vec3d(x, y, z); + if (this.firstStart == null) { + this.firstStart = pos; + } + else { + this.secondStart = pos; + } + } + + private void lastPosition(double x, double y, double z) { + info("%s Eye of Ender's trajectory saved.", (this.firstEnd == null) ? "First" : "Second"); + Vec3d pos = new Vec3d(x, y, z); + if (this.firstEnd == null) { + this.firstEnd = pos; + info("Please throw the second Eye Of Ender from a different location."); + } + else { + this.secondEnd = pos; + findStronghold(); + } + } + + private void findStronghold() { + if (this.firstStart == null || this.firstEnd == null || this.secondStart == null || this.secondEnd == null) { + error("Missing position data"); + cancel(); + return; + } + final double[] start = new double[]{this.secondStart.x, this.secondStart.z, this.secondEnd.x, this.secondEnd.z}; + final double[] end = new double[]{this.firstStart.x, this.firstStart.z, this.firstEnd.x, this.firstEnd.z}; + final double[] intersection = calcIntersection(start, end); + if (Double.isNaN(intersection[0]) || Double.isNaN(intersection[1]) || Double.isInfinite(intersection[0]) || Double.isInfinite(intersection[1])) { + error("Lines are parallel"); + cancel(); + return; + } + BaritoneAPI.getProvider().getPrimaryBaritone().getCommandManager().execute("stop"); + MeteorClient.EVENT_BUS.unsubscribe(this); + Vec3d coords = new Vec3d(intersection[0],0,intersection[1]); + BaseText text = new LiteralText("Stronghold roughly located at "); + text.append(ChatUtils.formatCoords(coords)); + text.append("."); + info(text); + } + + private double[] calcIntersection(double[] line, double[] line2) { + final double a1 = line[3] - line[1]; + final double b1 = line[0] - line[2]; + final double c1 = a1 * line[0] + b1 * line[1]; + + final double a2 = line2[3] - line2[1]; + final double b2 = line2[0] - line2[2]; + final double c2 = a2 * line2[0] + b2 * line2[1]; + + final double delta = a1 * b2 - a2 * b1; + + return new double[]{(b2 * c1 - b1 * c2) / delta, (a1 * c2 - a2 * c1) / delta}; + } +} diff --git a/src/main/java/cloudburst/rejects/commands/SeedCommand.java b/src/main/java/cloudburst/rejects/commands/SeedCommand.java new file mode 100644 index 00000000..472ef15c --- /dev/null +++ b/src/main/java/cloudburst/rejects/commands/SeedCommand.java @@ -0,0 +1,68 @@ +package cloudburst.rejects.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; + +import net.minecraft.command.CommandSource; +import net.minecraft.text.BaseText; +import net.minecraft.text.LiteralText; + +import kaptainwutax.mcutils.version.MCVersion; +import meteordevelopment.meteorclient.systems.commands.Command; +import cloudburst.rejects.arguments.EnumArgumentType; +import cloudburst.rejects.utils.seeds.Seed; +import cloudburst.rejects.utils.seeds.Seeds; +import meteordevelopment.meteorclient.utils.Utils; + +import static com.mojang.brigadier.Command.SINGLE_SUCCESS; + +import com.mojang.brigadier.arguments.LongArgumentType; + +public class SeedCommand extends Command { + private final static SimpleCommandExceptionType NO_SEED = new SimpleCommandExceptionType(new LiteralText("No seed for current world saved.")); + + public SeedCommand() { + super("seed", "Get or set seed for the current world."); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.executes(ctx -> { + Seed seed = Seeds.get().getSeed(); + if (seed == null) throw NO_SEED.create(); + info(seed.toText()); + return SINGLE_SUCCESS; + }); + + builder.then(literal("list").executes(ctx -> { + Seeds.get().seeds.forEach((name, seed) -> { + BaseText text = new LiteralText(name + " "); + text.append(seed.toText()); + info(text); + }); + return SINGLE_SUCCESS; + })); + + builder.then(literal("delete").executes(ctx -> { + Seed seed = Seeds.get().getSeed(); + if (seed != null) { + BaseText text = new LiteralText("Deleted "); + text.append(seed.toText()); + info(text); + } + Seeds.get().seeds.remove(Utils.getWorldName()); + return SINGLE_SUCCESS; + })); + + builder.then(argument("seed", LongArgumentType.longArg()).executes(ctx -> { + Seeds.get().setSeed(LongArgumentType.getLong(ctx, "seed")); + return SINGLE_SUCCESS; + })); + + builder.then(argument("seed", LongArgumentType.longArg()).then(argument("version", EnumArgumentType.enumArgument(MCVersion.v1_17_1)).executes(ctx -> { + Seeds.get().setSeed(LongArgumentType.getLong(ctx, "seed"), EnumArgumentType.getEnum(ctx, "version", MCVersion.v1_17_1)); + return SINGLE_SUCCESS; + }))); + } + +} diff --git a/src/main/java/cloudburst/rejects/mixin/meteor/CommandsMixin.java b/src/main/java/cloudburst/rejects/mixin/meteor/CommandsMixin.java new file mode 100644 index 00000000..1af6b951 --- /dev/null +++ b/src/main/java/cloudburst/rejects/mixin/meteor/CommandsMixin.java @@ -0,0 +1,48 @@ +package cloudburst.rejects.mixin.meteor; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; + +import meteordevelopment.meteorclient.systems.commands.Command; +import meteordevelopment.meteorclient.systems.commands.Commands; +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.command.CommandSource; + +import java.util.List; +import java.util.Map; + + +@Mixin(Commands.class) +public class CommandsMixin { + @Shadow + @Final + private List commands; + + @Shadow + @Final + private Map, Command> commandInstances; + + + @Shadow + @Final + private CommandDispatcher DISPATCHER = new CommandDispatcher<>(); + + @Inject(method = "add", at=@At("HEAD"), remap = false, cancellable = true) + private void onAdd(Command cmd, CallbackInfo ci) { + if (cmd instanceof meteordevelopment.meteorclient.systems.commands.commands.LocateCommand) { + Command command = new cloudburst.rejects.commands.LocateCommand(); + commands.removeIf(command1 -> command1.getName().equals(command.getName())); + commandInstances.values().removeIf(command1 -> command1.getName().equals(command.getName())); + + command.registerTo(DISPATCHER); + commands.add(command); + commandInstances.put(command.getClass(), command); + ci.cancel(); + } + } +} diff --git a/src/main/java/cloudburst/rejects/utils/RejectsUtils.java b/src/main/java/cloudburst/rejects/utils/RejectsUtils.java index 7bb69210..6e0b35f1 100644 --- a/src/main/java/cloudburst/rejects/utils/RejectsUtils.java +++ b/src/main/java/cloudburst/rejects/utils/RejectsUtils.java @@ -5,12 +5,15 @@ import java.util.Timer; import java.util.TimerTask; +import cloudburst.rejects.utils.seeds.Seeds; + public class RejectsUtils { public static int CPS = 0; public static void init() { Runtime.getRuntime().addShutdownHook(new Thread(() -> { RejectsConfig.get().save(MeteorClient.FOLDER); + Seeds.get().save(MeteorClient.FOLDER); })); new Timer().scheduleAtFixedRate(newTimerTaskFromLambda(() -> CPS = 0), 0, 1000); diff --git a/src/main/java/cloudburst/rejects/utils/WorldGenUtils.java b/src/main/java/cloudburst/rejects/utils/WorldGenUtils.java new file mode 100644 index 00000000..93b9ddc1 --- /dev/null +++ b/src/main/java/cloudburst/rejects/utils/WorldGenUtils.java @@ -0,0 +1,313 @@ +package cloudburst.rejects.utils; + +import baritone.api.BaritoneAPI; +import kaptainwutax.biomeutils.source.BiomeSource; +import kaptainwutax.featureutils.misc.SlimeChunk; +import kaptainwutax.featureutils.structure.*; +import kaptainwutax.mcutils.rand.ChunkRand; +import kaptainwutax.mcutils.state.Dimension; +import kaptainwutax.mcutils.util.data.SpiralIterator; +import kaptainwutax.mcutils.util.pos.*; +import kaptainwutax.mcutils.version.MCVersion; +import kaptainwutax.terrainutils.TerrainGenerator; +import cloudburst.rejects.utils.seeds.Seed; +import cloudburst.rejects.utils.seeds.Seeds; +import meteordevelopment.meteorclient.utils.player.ChatUtils; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.entity.Entity; +import net.minecraft.entity.mob.*; +import net.minecraft.entity.passive.IronGolemEntity; +import net.minecraft.entity.passive.VillagerEntity; +import net.minecraft.entity.vehicle.ChestMinecartEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.*; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; + +import java.util.*; +import java.util.stream.StreamSupport; + +import static meteordevelopment.meteorclient.utils.Utils.mc; + +public class WorldGenUtils { + + private static final HashMap> FEATURE_BLOCKS = new HashMap<>(){{ + put(Feature.nether_fortress, Arrays.asList( + Blocks.NETHER_BRICKS, + Blocks.NETHER_BRICK_FENCE, + Blocks.NETHER_WART + )); + put(Feature.ocean_monument, Arrays.asList( + Blocks.PRISMARINE_BRICKS, + Blocks.SEA_LANTERN, + Blocks.DARK_PRISMARINE + )); + put(Feature.stronghold, Arrays.asList( + Blocks.END_PORTAL_FRAME, + Blocks.END_PORTAL + )); + put(Feature.end_city, Arrays.asList( + Blocks.PURPUR_BLOCK, + Blocks.PURPUR_PILLAR, + Blocks.PURPUR_STAIRS, + Blocks.END_ROD + )); + put(Feature.village, Arrays.asList( + Blocks.BELL, + Blocks.BREWING_STAND + )); + put(Feature.mineshaft, Collections.singletonList( + Blocks.RAIL + )); + }}; + + private static final HashMap>> FEATURE_ENTITIES = new HashMap<>(){{ + put(Feature.ocean_monument, Arrays.asList( + ElderGuardianEntity.class, + GuardianEntity.class + )); + put(Feature.nether_fortress, Arrays.asList( + BlazeEntity.class, + WitherSkeletonEntity.class + )); + put(Feature.mansion, Collections.singletonList( + EvokerEntity.class + )); + put(Feature.slime_chunk, Collections.singletonList( + SlimeEntity.class + )); + put(Feature.bastion_remnant, Collections.singletonList( + PiglinBruteEntity.class + )); + put(Feature.end_city, Collections.singletonList( + ShulkerEntity.class + )); + put(Feature.village, Arrays.asList( + VillagerEntity.class, + IronGolemEntity.class + )); + put(Feature.mineshaft, Collections.singletonList( + ChestMinecartEntity.class + )); + }}; + + public enum Feature { + buried_treasure, + mansion, + stronghold, + nether_fortress, + ocean_monument, + bastion_remnant, + end_city, + village, + mineshaft, + slime_chunk + } + + public static BlockPos locateFeature(Feature feature, BlockPos center) { + Seed seed = Seeds.get().getSeed(); + BlockPos pos = null; + if (seed != null) { + pos = locateFeature(seed, feature, center); + if (pos != null) return pos; + } + if (mc.player != null) { + ItemStack stack = mc.player.getStackInHand(Hand.MAIN_HAND); + if (stack.getItem() != Items.FILLED_MAP) + stack = mc.player.getStackInHand(Hand.OFF_HAND); + if (stack.getItem() == Items.FILLED_MAP) { + pos = locateFeatureMap(feature, stack); + if (pos != null) return pos; + } + } + pos = locateFeatureEntities(feature); + if (pos != null) return pos; + pos = locateFeatureBlocks(feature); + return pos; + } + + private static BlockPos locateFeatureMap(Feature feature, ItemStack stack) { + if (!isValidMap(feature, stack)) return null; + return getMapMarker(stack); + } + + private static BlockPos locateFeatureBlocks(Feature feature) { + List blocks = FEATURE_BLOCKS.get(feature); + if (blocks == null) return null; + List posList = BaritoneAPI + .getProvider() + .getWorldScanner() + .scanChunkRadius( + BaritoneAPI + .getProvider() + .getPrimaryBaritone() + .getPlayerContext(), + blocks,64,10,32); + if (posList.isEmpty()) return null; + if (posList.size() < 5) { + ChatUtils.warning("Locate", "Only %d block(s) found. This search might be a false positive.", posList.size()); + } + return posList.get(0); + } + + private static BlockPos locateFeatureEntities(Feature feature) { + List> entities = FEATURE_ENTITIES.get(feature); + if (entities == null) return null; + if (mc.world == null) return null; + for (Entity e: mc.world.getEntities()) { + for (Class clazz: entities) { + if (clazz.isInstance(e)) + return e.getBlockPos(); + } + } + return null; + } + + private static BlockPos locateFeature(Seed seed, Feature feature, BlockPos center) { + if (feature == Feature.slime_chunk) return locateSlimeChunk(seed, center); + return locateStructure(seed, feature, center); + } + + private static BlockPos locateSlimeChunk(Seed seed, BlockPos center) { + Dimension dimension = getDimension(Feature.slime_chunk); + MCVersion mcVersion = seed.version; + CPos centerChunk = new CPos(center.getX() >> 4, center.getZ() >> 4); + CPos slimeChunkPos = locateSlimeChunk(new SlimeChunk(mcVersion), centerChunk, 6400, seed.seed, new ChunkRand(), dimension); + if (slimeChunkPos == null) return null; + return toBlockPos(slimeChunkPos.toBlockPos()); + } + + private static CPos locateSlimeChunk(SlimeChunk slimeChunk, CPos centerChunk, int radius, long seed, ChunkRand rand, Dimension dimension) { + if (!slimeChunk.isValidDimension(dimension)) + return null; + SpiralIterator spiralIterator = new SpiralIterator<>(centerChunk, new CPos(radius, radius), (x, y, z) -> new CPos(x, z)); + for (CPos next : spiralIterator) { + SlimeChunk.Data data = slimeChunk.at(next.getX(), next.getZ(), true); + if (data.testStart(seed, rand)) { + return next; + } + } + return null; + } + + private static BlockPos locateStructure(Seed seed, Feature feature, BlockPos center) { + Dimension dimension = getDimension(feature); + MCVersion mcVersion = seed.version; + Structure structure = getStructure(feature, mcVersion); + if (structure == null) return null; + BiomeSource biomeSource = BiomeSource.of(dimension, mcVersion, seed.seed); + if (!structure.isValidDimension(biomeSource.getDimension())) + return null; + BPos structurePos = locateStructure(structure, new BPos(center.getX(), center.getY(), center.getZ()), 6400, new ChunkRand(), biomeSource, TerrainGenerator.of(biomeSource)); + if (structurePos == null) return null; + return toBlockPos(structurePos); + } + + private static BPos locateStructure(Structure structure, BPos center, int radius, ChunkRand chunkRand, BiomeSource source, TerrainGenerator terrainGenerator) { + if (structure instanceof RegionStructure regionStructure) { + int chunkInRegion = regionStructure.getSpacing(); + int regionSize = chunkInRegion * 16; + + final int border = 30_000_000; + SpiralIterator spiralIterator = new SpiralIterator<>(center.toRegionPos(regionSize), new BPos(-border, 0, -border).toRegionPos(regionSize), new BPos(border, 0, border).toRegionPos(regionSize), 1, (x, y, z) -> new RPos(x, z, regionSize)); + return StreamSupport.stream(spiralIterator.spliterator(), false) + .map(rPos -> regionStructure.getInRegion(source.getWorldSeed(), rPos.getX(), rPos.getZ(), chunkRand)) + .filter(Objects::nonNull) + .filter(cPos -> (regionStructure.canSpawn(cPos, source)) && (terrainGenerator == null || regionStructure.canGenerate(cPos, terrainGenerator))) + .findAny().map(cPos -> cPos.toBlockPos().add(9, 0, 9)).orElse(null); + } else { + if (structure instanceof Stronghold strongholdStructure) { + CPos currentChunkPos = center.toChunkPos(); + int squaredDistance = Integer.MAX_VALUE; + CPos closest = new CPos(0, 0); + for (CPos stronghold : strongholdStructure.getAllStarts(source, chunkRand)) { + int newSquaredDistance = (currentChunkPos.getX() - stronghold.getX()) * (currentChunkPos.getX() - stronghold.getX()) + (currentChunkPos.getZ() - stronghold.getZ()) * (currentChunkPos.getZ() - stronghold.getZ()); + if (newSquaredDistance < squaredDistance) { + squaredDistance = newSquaredDistance; + closest = stronghold; + } + } + BPos dimPos = closest.toBlockPos().add(9, 0, 9); + return new BPos(dimPos.getX(), 0, dimPos.getZ()); + } else if (structure instanceof Mineshaft mineshaft) { + SpiralIterator spiralIterator = new SpiralIterator<>(new CPos(center.getX() >> 4, center.getZ() >> 4), new CPos(radius, radius), (x, y, z) -> new CPos(x, z)); + + return StreamSupport.stream(spiralIterator.spliterator(), false) + .filter(cPos -> { + kaptainwutax.featureutils.Feature.Data data = mineshaft.at(cPos.getX(), cPos.getZ()); + return data.testStart(source.getWorldSeed(), chunkRand) && data.testBiome(source) && data.testGenerate(terrainGenerator); + }) + .findAny().map(cPos -> cPos.toBlockPos().add(9, 0, 9)).orElse(null); + } + } + return null; + } + + private static Dimension getDimension(Feature feature) { + switch (feature) { + case buried_treasure -> { return Dimension.OVERWORLD; } + case mansion -> { return Dimension.OVERWORLD; } + case stronghold -> { return Dimension.OVERWORLD; } + case nether_fortress -> { return Dimension.NETHER; } + case ocean_monument -> { return Dimension.OVERWORLD; } + case bastion_remnant -> { return Dimension.NETHER; } + case slime_chunk -> { return Dimension.OVERWORLD; } + case village -> { return Dimension.OVERWORLD; } + case mineshaft -> { return Dimension.OVERWORLD; } + case end_city -> { return Dimension.END; } + default -> { return Dimension.OVERWORLD; } + } + } + + private static Structure getStructure(Feature feature, MCVersion version) { + switch (feature) { + case buried_treasure -> { return new BuriedTreasure(version); } + case mansion -> { return new Mansion(version); } + case stronghold -> { return new Stronghold(version); } + case nether_fortress -> { return new Fortress(version); } + case ocean_monument -> { return new Monument(version); } + case bastion_remnant -> { return new BastionRemnant(version); } + case end_city -> { return new EndCity(version); } + case village -> { return new Village(version); } + case mineshaft -> { return new Mineshaft(version); } + default -> { return null;} + } + } + + private static BlockPos toBlockPos(BPos pos) { + return new BlockPos(pos.getX(), pos.getY(), pos.getZ()); + } + + private static boolean isValidMap(Feature feature, ItemStack stack) { + if (!stack.hasTag()) return false; + if (!stack.getTag().contains("display")) return false; + NbtCompound displayTag = stack.getTag().getCompound("display"); + if (!displayTag.contains("Name")) return false; + String nameTag = displayTag.getString("Name"); + if (!nameTag.contains("translate")) return false; + + if (feature == Feature.buried_treasure) { + return nameTag.contains("filled_map.buried_treasure"); + } else if (feature == Feature.ocean_monument) { + return nameTag.contains("filled_map.monument"); + } else if (feature == Feature.mansion) { + return nameTag.contains("filled_map.mansion"); + } + return false; + } + + private static BlockPos getMapMarker(ItemStack stack) { + if (!stack.hasTag()) return null; + if (!stack.getTag().contains("Decorations")) return null; + NbtList decorationsTag = stack.getTag().getList("Decorations", NbtElement.COMPOUND_TYPE); + if (decorationsTag.size() < 1) return null; + NbtCompound iconTag = decorationsTag.getCompound(0); + return new BlockPos( + (int)iconTag.getDouble("x"), + (int)iconTag.getDouble("y"), + (int)iconTag.getDouble("z") + ); + } +} diff --git a/src/main/java/cloudburst/rejects/utils/seeds/Seed.java b/src/main/java/cloudburst/rejects/utils/seeds/Seed.java new file mode 100644 index 00000000..6101d3b4 --- /dev/null +++ b/src/main/java/cloudburst/rejects/utils/seeds/Seed.java @@ -0,0 +1,53 @@ +package cloudburst.rejects.utils.seeds; + +import kaptainwutax.mcutils.version.MCVersion; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtLong; +import net.minecraft.nbt.NbtString; +import net.minecraft.text.*; +import net.minecraft.util.Formatting; + +public class Seed { + public final Long seed; + public final MCVersion version; + + public Seed(Long seed, MCVersion version) { + this.seed = seed; + this.version = version; + } + + public NbtCompound toTag() { + NbtCompound tag = new NbtCompound(); + tag.put("seed", NbtLong.of(seed)); + tag.put("version", NbtString.of(version.name())); + return tag; + } + + public static Seed fromTag(NbtCompound tag) { + return new Seed( + tag.getLong("seed"), + MCVersion.valueOf(tag.getString("version")) + ); + } + + public Text toText() { + BaseText text = new LiteralText(String.format("[%s%s%s] (%s)", + Formatting.GREEN, + seed.toString(), + Formatting.WHITE, + version.toString() + )); + text.setStyle(text.getStyle() + .withClickEvent(new ClickEvent( + ClickEvent.Action.COPY_TO_CLIPBOARD, + seed.toString() + )) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + new LiteralText("Copy to clipboard") + )) + ); + return text; + } +} \ No newline at end of file diff --git a/src/main/java/cloudburst/rejects/utils/seeds/Seeds.java b/src/main/java/cloudburst/rejects/utils/seeds/Seeds.java new file mode 100644 index 00000000..a80aabbd --- /dev/null +++ b/src/main/java/cloudburst/rejects/utils/seeds/Seeds.java @@ -0,0 +1,70 @@ +package cloudburst.rejects.utils.seeds; + +import java.util.HashMap; + +import net.minecraft.client.network.ServerInfo; +import net.minecraft.nbt.NbtCompound; + +import kaptainwutax.mcutils.version.MCVersion; +import meteordevelopment.meteorclient.MeteorClient; +import meteordevelopment.meteorclient.systems.System; +import meteordevelopment.meteorclient.utils.Utils; + +import static meteordevelopment.meteorclient.utils.Utils.mc; + +public class Seeds extends System { + private static final Seeds INSTANCE = new Seeds(); + + public HashMap seeds = new HashMap<>(); + + public Seeds() { + super("seeds"); + init(); + load(MeteorClient.FOLDER); + } + + public static Seeds get() { + return INSTANCE; + } + + public Seed getSeed() { + if (mc.isIntegratedServerRunning() && mc.getServer() != null) + return new Seed(mc.getServer().getOverworld().getSeed(), MCVersion.fromString(mc.getServer().getVersion())); + + return seeds.get(Utils.getWorldName()); + } + + public void setSeed(long seed, MCVersion version) { + if (mc.isIntegratedServerRunning()) return; + + seeds.put(Utils.getWorldName(), new Seed(seed, version)); + } + + public void setSeed(long seed) { + ServerInfo server = mc.getCurrentServerEntry(); + MCVersion ver = null; + if (server != null) + ver = MCVersion.fromString(server.version.asString()); + if (ver == null) + ver = MCVersion.latest(); + setSeed(seed, ver); + } + + @Override + public NbtCompound toTag() { + NbtCompound tag = new NbtCompound(); + seeds.forEach((key, seed) -> { + if (seed == null) return; + tag.put(key, seed.toTag()); + }); + return tag; + } + + @Override + public Seeds fromTag(NbtCompound tag) { + tag.getKeys().forEach(key -> { + seeds.put(key, Seed.fromTag(tag.getCompound(key))); + }); + return this; + } +} diff --git a/src/main/resources/meteor-rejects-meteor.mixins.json b/src/main/resources/meteor-rejects-meteor.mixins.json index 2ebe426e..02e26eda 100644 --- a/src/main/resources/meteor-rejects-meteor.mixins.json +++ b/src/main/resources/meteor-rejects-meteor.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_16", "client": [ "modules.NoRenderAccessor", + "CommandsMixin", "ConfigTabMixin", "GuiRendererAccessor", "HttpMixin"