diff --git a/gradle.properties b/gradle.properties index cb6a0861f..a404b27da 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,7 +27,7 @@ org.gradle.jvmargs=-Xmx2G simplewaypoints_version=1.0.0-alpha.4 seedfinding_core_version=1.210.0 seedfinding_biome_version=1.171.1 - seedfinding_feature_version=1.171.10 + seedfinding_feature_version=1.171.11 seedfinding_seed_version=1.171.2 latticg_version=1.07 mapping_io_version=0.7.1 diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index 5fd8593e1..8b8c0d89a 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -175,6 +175,7 @@ public static void registerCommands(CommandDispatcher // PlayerInfoCommand.register(dispatcher); PluginsCommand.register(dispatcher); PosCommand.register(dispatcher); + PredictBrushablesCommand.register(dispatcher); RelogCommand.register(dispatcher); RenderCommand.register(dispatcher); ReplyCommand.register(dispatcher); diff --git a/src/main/java/net/earthcomputer/clientcommands/command/ClientCommandHelper.java b/src/main/java/net/earthcomputer/clientcommands/command/ClientCommandHelper.java index dd2dce23c..fed8b5961 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/ClientCommandHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/ClientCommandHelper.java @@ -73,7 +73,7 @@ public static Component getGlowCoordsTextComponent(BlockPos pos) { } public static Component getGlowButtonTextComponent(BlockPos pos) { - return getCommandTextComponent(Component.translatable("commands.client.glow"), String.format("/cglow block %d %d %d 10", pos.getX(), pos.getY(), pos.getZ())); + return getCommandTextComponent(Component.translatable("commands.client.glow"), String.format("/cglow block %d %d %d 60", pos.getX(), pos.getY(), pos.getZ())); } public static Component getGlowButtonTextComponent(Entity entity) { diff --git a/src/main/java/net/earthcomputer/clientcommands/command/PredictBrushablesCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/PredictBrushablesCommand.java new file mode 100644 index 000000000..852fb31f4 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/PredictBrushablesCommand.java @@ -0,0 +1,169 @@ +package net.earthcomputer.clientcommands.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.seedfinding.mcfeature.loot.LootContext; +import com.seedfinding.mcfeature.loot.LootTable; +import com.seedfinding.mcfeature.loot.MCLootTables; +import com.seedfinding.mcfeature.loot.item.ItemStack; +import net.earthcomputer.clientcommands.task.RenderDistanceScanTask; +import net.earthcomputer.clientcommands.task.TaskManager; +import net.earthcomputer.clientcommands.util.SeedfindingUtil; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.SectionPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.tags.BiomeTags; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.alchemy.PotionContents; +import net.minecraft.world.item.component.ItemLore; +import net.minecraft.world.item.component.SuspiciousStewEffects; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.BrushableBlock; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static net.earthcomputer.clientcommands.command.ClientCommandHelper.*; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; + +public class PredictBrushablesCommand { + public static final Flag FLAG_KEEP_SEARCHING = Flag.ofFlag("keep-searching").build(); + + public static void register(CommandDispatcher dispatcher) { + var cpredictbrushables = dispatcher.register(literal("cpredictbrushables") + .executes(PredictBrushablesCommand::predictBrushables)); + FLAG_KEEP_SEARCHING.addToCommand(dispatcher, cpredictbrushables, ctx -> true); + } + + private static int predictBrushables(CommandContext ctx) throws CommandSyntaxException { + boolean keepSearching = getFlag(ctx, FLAG_KEEP_SEARCHING); + String taskName = TaskManager.addTask("cpredictbrushables", new PredictBrushablesTask(keepSearching)); + if (keepSearching) { + sendFeedback(Component.translatable("commands.cpredictbrushables.starting.keepSearching", getCommandTextComponent("commands.client.cancel", "/ctask stop " + taskName))); + } else { + sendFeedback(Component.translatable("commands.cpredictbrushables.starting")); + } + + return Command.SINGLE_SUCCESS; + } + + private static final class PredictBrushablesTask extends RenderDistanceScanTask { + private boolean found = false; + + PredictBrushablesTask(boolean keepSearching) { + super(keepSearching); + } + + @Override + protected void scanBlock(Entity cameraEntity, BlockPos pos) { + Minecraft minecraft = Minecraft.getInstance(); + LocalPlayer player = minecraft.player; + ClientLevel level = minecraft.level; + assert level != null; + BlockState blockState = level.getBlockState(pos); + + // best effort, may be inaccurate + Supplier lootTableSupplier; + long lootSeed; + + if (blockState.is(Blocks.SUSPICIOUS_SAND)) { + Holder biome = level.getBiome(pos); + if (biome.is(Biomes.DESERT)) { + // either desert well or desert pyramid, check for water + boolean hasWater = Stream.of(pos.atY(pos.getY() + 1), pos.atY(pos.getY() + 2)) + .map(level::getBlockState) + .anyMatch(s -> s.is(Blocks.WATER)); + lootTableSupplier = hasWater ? MCLootTables.DESERT_WELL_ARCHAEOLOGY : MCLootTables.DESERT_PYRAMID_ARCHAEOLOGY; + lootSeed = pos.asLong(); + } else if (biome.is(BiomeTags.HAS_OCEAN_RUIN_WARM)) { + lootTableSupplier = MCLootTables.OCEAN_RUIN_WARM_ARCHAEOLOGY; + RandomSource randomSource = RandomSource.create(Mth.getSeed(pos)); + lootSeed = randomSource.nextLong(); + } else { + return; + } + } else if (blockState.is(Blocks.SUSPICIOUS_GRAVEL)) { + Holder biome = level.getBiome(pos); + if (biome.is(BiomeTags.HAS_OCEAN_RUIN_COLD)) { + lootTableSupplier = MCLootTables.OCEAN_RUIN_COLD_ARCHAEOLOGY; + RandomSource randomSource = RandomSource.create(Mth.getSeed(pos)); + lootSeed = randomSource.nextLong(); + } else if (biome.is(BiomeTags.HAS_TRAIL_RUINS)) { + // TODO: check for rare or common loot + // Common loot is generated in TRAIL_RUINS_HOUSES_ARCHAEOLOGY, + // TRAIL_RUINS_ROADS_ARCHAEOLOGY and TRAIL_RUINS_TOWER_TOP_ARCHAEOLOGY + // Rare is generated in TRAIL_RUINS_HOUSES_ARCHAEOLOGY + // Ignored for now + return; + } else { + return; + } + } else { + return; + } + + LootContext context = new LootContext(lootSeed, SeedfindingUtil.getMCVersion()) + .withLuck((int) player.getLuck()); + + List items = lootTableSupplier.get().generate(context); + for (ItemStack item : items) { + var mcItemStack = SeedfindingUtil.fromSeedfindingItem(item, level.registryAccess()); + mcItemStack.set(DataComponents.LORE, getItemLore(mcItemStack)); + ClientCommandHelper.sendFeedback(Component.translatable("commands.cpredictbrushables.foundBrushableBlock", blockState.getBlock().getName(), ClientCommandHelper.getLookCoordsTextComponent(pos), mcItemStack.getDisplayName(), ClientCommandHelper.getGlowButtonTextComponent(pos))); + } + + found = true; + } + + // use lore to display effects + private static ItemLore getItemLore(net.minecraft.world.item.ItemStack itemStack) { + SuspiciousStewEffects effects = itemStack.get(DataComponents.SUSPICIOUS_STEW_EFFECTS); + if (effects == null) { + return ItemLore.EMPTY; + } + List components = new ArrayList<>(); + for (SuspiciousStewEffects.Entry entry : effects.effects()) { + MobEffectInstance mobEffectInstance = entry.createEffectInstance(); + MutableComponent description = PotionContents.getPotionDescription(mobEffectInstance.getEffect(), mobEffectInstance.getAmplifier()); + Component line = Component.translatable("commands.cpredictbrushables.stewEffect", description, entry.duration() / SharedConstants.TICKS_PER_SECOND); + components.add(line); + } + return new ItemLore(components); + } + + @Override + protected void onBlockStateUpdate(ClientLevel level, BlockPos pos, BlockState oldState, BlockState newState) { + } + + @Override + protected boolean canScanChunkSection(Entity cameraEntity, SectionPos pos) { + return hasBlockState(pos, s -> s.getBlock() instanceof BrushableBlock) && super.canScanChunkSection(cameraEntity, pos); + } + + @Override + public void onCompleted() { + super.onCompleted(); + if (!found) { + sendError(Component.translatable("commands.cpredictbrushables.notFound")); + } + } + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/util/SeedfindingUtil.java b/src/main/java/net/earthcomputer/clientcommands/util/SeedfindingUtil.java index 82bb40c14..68f3a0009 100644 --- a/src/main/java/net/earthcomputer/clientcommands/util/SeedfindingUtil.java +++ b/src/main/java/net/earthcomputer/clientcommands/util/SeedfindingUtil.java @@ -4,17 +4,23 @@ import com.google.common.collect.HashBiMap; import com.seedfinding.mcbiome.biome.Biomes; import com.seedfinding.mccore.version.MCVersion; +import com.seedfinding.mcfeature.loot.effect.Effect; +import com.seedfinding.mcfeature.loot.effect.Effects; import net.minecraft.Util; import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; +import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffects; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.SuspiciousStewEffects; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.Level; @@ -65,6 +71,40 @@ public class SeedfindingUtil { map.put(Enchantments.VANISHING_CURSE, "vanishing_curse"); }); + private static final BiMap, Effect> SEEDFINDING_EFFECTS = Util.make(HashBiMap.create(), map -> { + map.put(MobEffects.SPEED, Effects.MOVEMENT_SPEED); + map.put(MobEffects.SLOWNESS, Effects.MOVEMENT_SLOWDOWN); + map.put(MobEffects.HASTE, Effects.DIG_SPEED); + map.put(MobEffects.MINING_FATIGUE, Effects.DIG_SLOWDOWN); + map.put(MobEffects.STRENGTH, Effects.DAMAGE_BOOST); + map.put(MobEffects.INSTANT_HEALTH, Effects.HEAL); + map.put(MobEffects.INSTANT_DAMAGE, Effects.HARM); + map.put(MobEffects.JUMP_BOOST, Effects.JUMP); + map.put(MobEffects.NAUSEA, Effects.CONFUSION); + map.put(MobEffects.REGENERATION, Effects.REGENERATION); + map.put(MobEffects.RESISTANCE, Effects.DAMAGE_RESISTANCE); + map.put(MobEffects.FIRE_RESISTANCE, Effects.FIRE_RESISTANCE); + map.put(MobEffects.WATER_BREATHING, Effects.WATER_BREATHING); + map.put(MobEffects.INVISIBILITY, Effects.INVISIBILITY); + map.put(MobEffects.BLINDNESS, Effects.BLINDNESS); + map.put(MobEffects.NIGHT_VISION, Effects.NIGHT_VISION); + map.put(MobEffects.HUNGER, Effects.HUNGER); + map.put(MobEffects.WEAKNESS, Effects.WEAKNESS); + map.put(MobEffects.POISON, Effects.POISON); + map.put(MobEffects.WITHER, Effects.WITHER); + map.put(MobEffects.HEALTH_BOOST, Effects.HEALTH_BOOST); + map.put(MobEffects.ABSORPTION, Effects.ABSORPTION); + map.put(MobEffects.SATURATION, Effects.SATURATION); + map.put(MobEffects.GLOWING, Effects.GLOWING); + map.put(MobEffects.LEVITATION, Effects.LEVITATION); + map.put(MobEffects.LUCK, Effects.LUCK); + map.put(MobEffects.UNLUCK, Effects.UNLUCK); + map.put(MobEffects.SLOW_FALLING, Effects.SLOW_FALLING); + map.put(MobEffects.CONDUIT_POWER, Effects.CONDUIT_POWER); + map.put(MobEffects.DOLPHINS_GRACE, Effects.DOLPHINS_GRACE); + map.put(MobEffects.BAD_OMEN, Effects.BAD_OMEN); + }); + private SeedfindingUtil() { } @@ -101,6 +141,12 @@ public static ItemStack fromSeedfindingItem(com.seedfinding.mcfeature.loot.item. ret.enchant(enchantment, enchAndLevel.getSecond()); }); } + + for (var effectAndDuration : stack.getItem().getEffects()) { + Holder effectHolder = Objects.requireNonNull(SEEDFINDING_EFFECTS.inverse().get(effectAndDuration.getFirst()), () -> "missing seedfinding effect " + effectAndDuration.getFirst()); + SuspiciousStewEffects.Entry entry = new SuspiciousStewEffects.Entry(effectHolder, effectAndDuration.getSecond()); + ret.update(DataComponents.SUSPICIOUS_STEW_EFFECTS, SuspiciousStewEffects.EMPTY, entry, SuspiciousStewEffects::withEffectAdded); + } return ret; } diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index bf836e5f3..8a09bc854 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -234,6 +234,12 @@ "commands.cpos.level.the_end": "the End", "commands.cpos.level.the_nether": "the Nether", + "commands.cpredictbrushables.foundBrushableBlock": "Found %s at %s with item %s %s", + "commands.cpredictbrushables.notFound": "No brushable blocks found", + "commands.cpredictbrushables.starting": "Searching for brushable blocks", + "commands.cpredictbrushables.starting.keepSearching": "Infinitely searching for brushable blocks [%s]", + "commands.cpredictbrushables.stewEffect": "%s (%s seconds)", + "commands.crelog.failed": "Failed to relog", "commands.crender.entities.success": "Entity rendering rules have been updated",