diff --git a/src/main/java/com/lambda/mixin/render/DebugHudMixin.java b/src/main/java/com/lambda/mixin/render/DebugHudMixin.java index 46825717e..4b29a9e66 100644 --- a/src/main/java/com/lambda/mixin/render/DebugHudMixin.java +++ b/src/main/java/com/lambda/mixin/render/DebugHudMixin.java @@ -17,7 +17,6 @@ package com.lambda.mixin.render; -import com.lambda.task.RootTask; import com.lambda.util.DebugInfoHud; import net.minecraft.client.gui.hud.DebugHud; import org.spongepowered.asm.mixin.Mixin; @@ -33,9 +32,4 @@ public class DebugHudMixin { private void onGetRightText(CallbackInfoReturnable> cir) { DebugInfoHud.addDebugInfo(cir.getReturnValue()); } - - @Inject(method = "getLeftText", at = @At("TAIL")) - private void onGetLeftText(CallbackInfoReturnable> cir) { - cir.getReturnValue().addAll(List.of(RootTask.INSTANCE.toString().split("\n"))); - } } diff --git a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt index 03fc24a1e..c5edd44de 100644 --- a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt @@ -80,10 +80,16 @@ class BreakSettings( override val ignoredBlocks by c.setting("Ignored Blocks", allSigns, description = "Blocks that wont be broken", visibility = vis).group(groupPath, Group.General) // Tool - override val suitableToolsOnly by c.setting("Suitable Tools Only", false, "Places a restriction to only use tools suitable for the given block", visibility = vis).group(groupPath, Group.General) + override val suitableToolsOnly by c.setting("Suitable Tools Only", true, "Only use tools suitable for the given block (will get the item drop)", visibility = vis).group(groupPath, Group.General) override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks", visibility = vis).group(groupPath, Group.General) override val forceFortunePickaxe by c.setting("Force Fortune Pickaxe", false, "Force fortune pickaxe when breaking blocks", visibility = vis).group(groupPath, Group.General) override val minFortuneLevel by c.setting("Min Fortune Level", 1, 1..3, 1, "The minimum fortune level to use") { vis() && forceFortunePickaxe }.group(groupPath, Group.General) + override val useWoodenTools by c.setting("Use Wooden Tools", true, "Use wooden tools when breaking blocks", visibility = vis).group(groupPath, Group.General) + override val useStoneTools by c.setting("Use Stone Tools", true, "Use stone tools when breaking blocks", visibility = vis).group(groupPath, Group.General) + override val useIronTools by c.setting("Use Iron Tools", true, "Use iron tools when breaking blocks", visibility = vis).group(groupPath, Group.General) + override val useDiamondTools by c.setting("Use Diamond Tools", true, "Use diamond tools when breaking blocks", visibility = vis).group(groupPath, Group.General) + override val useGoldTools by c.setting("Use Gold Tools", true, "Use gold tools when breaking blocks", visibility = vis).group(groupPath, Group.General) + override val useNetheriteTools by c.setting("Use Netherite Tools", true, "Use netherite tools when breaking blocks", visibility = vis).group(groupPath, Group.General) // Cosmetics override val sounds by c.setting("Break Sounds", true, "Plays the breaking sounds", visibility = vis).group(groupPath, Group.Cosmetic) diff --git a/src/main/kotlin/com/lambda/config/groups/EatConfig.kt b/src/main/kotlin/com/lambda/config/groups/EatConfig.kt new file mode 100644 index 000000000..6088fda4d --- /dev/null +++ b/src/main/kotlin/com/lambda/config/groups/EatConfig.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.config.groups + +import com.lambda.context.SafeContext +import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.threading.runSafe +import com.lambda.util.Describable +import com.lambda.util.NamedEnum +import com.lambda.util.item.ItemUtils.nutrition +import net.minecraft.entity.effect.StatusEffects +import net.minecraft.item.Item +import net.minecraft.item.ItemStack + +interface EatConfig { + val eatOnHunger: Boolean + val minFoodLevel: Int + val nutritiousFood: List + val saturated: Saturation + + val eatOnFire: Boolean + val resistanceFood: List + + val eatOnDamage: Boolean + val minDamage: Int + val regenerationFood: List + + val selectionPriority: SelectionPriority + val ignoreBadFood: Boolean + val badFood: List + + enum class Saturation( + override val displayName: String, + override val description: String + ): NamedEnum, Describable { + EatSmart("Eat Smart", "Eats until the next food would exceed the hunger limit."), + EatUntilFull("Eat Until Full", "Eats food until the hunger bar is completely full. May waste some food."), + } + + enum class SelectionPriority( + val comparator: Comparator, + override val displayName: String, + override val description: String + ): NamedEnum, Describable { + LeastNutritious( + compareBy { it.item.nutrition }, + "Least Nutritious", + "Eats food items with the least nutritional value." + ), + MostNutritious( + compareByDescending { it.item.nutrition }, + "Most Nutritious", + "Eats food items with the most nutritional value." + ) + } + + enum class Reason(val message: (ItemStack) -> String) { + None({ "Waiting for reason to eat..." }), + Hunger({ "Eating ${it.item.name.string} due to Hunger" }), + Damage({ "Eating ${it.item.name.string} due to Damage" }), + Fire({ "Eating ${it.item.name.string} due to Fire" }); + + fun shouldEat() = this != None + + fun shouldKeepEating(config: EatConfig, stack: ItemStack?) = runSafe { + if (stack == null || stack.isEmpty) return@runSafe false + when(this@Reason) { + Hunger -> when(config.saturated) { + Saturation.EatSmart -> stack.item.nutrition + player.hungerManager.foodLevel <= 20 + Saturation.EatUntilFull -> player.hungerManager.isNotFull + } + Damage -> !player.hasStatusEffect(StatusEffects.REGENERATION) + Fire -> !player.hasStatusEffect(StatusEffects.FIRE_RESISTANCE) + None -> false + } + } ?: false + + fun selector(config: EatConfig) = selectStack(sorter = config.selectionPriority.comparator) { + when(this@Reason) { + None -> any() + Hunger -> isOneOfItems(config.nutritiousFood) + Damage -> isOneOfItems(config.regenerationFood) + Fire -> isOneOfItems(config.resistanceFood) + } and if (config.ignoreBadFood) isNoneOfItems(config.badFood) else any() + } + } + + companion object { + fun SafeContext.reasonEating(config: EatConfig) = when { + config.eatOnHunger && player.hungerManager.foodLevel <= config.minFoodLevel -> Reason.Hunger + config.eatOnDamage && player.health <= config.minDamage && !player.hasStatusEffect(StatusEffects.REGENERATION) -> Reason.Damage + config.eatOnFire && player.isOnFire && !player.hasStatusEffect(StatusEffects.FIRE_RESISTANCE) -> Reason.Fire + else -> Reason.None + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/groups/EatSettings.kt b/src/main/kotlin/com/lambda/config/groups/EatSettings.kt new file mode 100644 index 000000000..f4981a86a --- /dev/null +++ b/src/main/kotlin/com/lambda/config/groups/EatSettings.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.config.groups + +import com.lambda.config.Configurable +import com.lambda.util.NamedEnum +import net.minecraft.item.Item +import net.minecraft.item.Items + +class EatSettings( + c: Configurable, + baseGroup: NamedEnum, + vis: () -> Boolean = { true } +) : EatConfig { + val nutritiousFoodDefaults = listOf(Items.APPLE, Items.BAKED_POTATO, Items.BEEF, Items.BEETROOT, Items.BEETROOT_SOUP, Items.BREAD, Items.CARROT, Items.CHICKEN, Items.CHORUS_FRUIT, Items.COD, Items.COOKED_BEEF, Items.COOKED_CHICKEN, Items.COOKED_COD, Items.COOKED_MUTTON, Items.COOKED_PORKCHOP, Items.COOKED_RABBIT, Items.COOKED_SALMON, Items.COOKIE, Items.DRIED_KELP, Items.ENCHANTED_GOLDEN_APPLE, Items.GOLDEN_APPLE, Items.GOLDEN_CARROT, Items.HONEY_BOTTLE, Items.MELON_SLICE, Items.MUSHROOM_STEW, Items.MUTTON, Items.POISONOUS_POTATO, Items.PORKCHOP, Items.POTATO, Items.PUFFERFISH, Items.PUMPKIN_PIE, Items.RABBIT, Items.RABBIT_STEW, Items.ROTTEN_FLESH, Items.SALMON, Items.SPIDER_EYE, Items.SUSPICIOUS_STEW, Items.SWEET_BERRIES, Items.GLOW_BERRIES, Items.TROPICAL_FISH) + val resistanceFoodDefaults = listOf(Items.ENCHANTED_GOLDEN_APPLE) + val regenerationFoodDefaults = listOf(Items.ENCHANTED_GOLDEN_APPLE, Items.GOLDEN_APPLE) + val negativeFoodDefaults = listOf(Items.CHICKEN, Items.POISONOUS_POTATO, Items.PUFFERFISH, Items.ROTTEN_FLESH, Items.SPIDER_EYE) + + override val eatOnHunger by c.setting("Eat On Hunger", true, "Whether to eat when hungry", vis).group(baseGroup) + override val minFoodLevel by c.setting("Minimum Food Level", 6, 0..20, 1, "The minimum food level to eat food", " food level") { vis() && eatOnHunger }.group(baseGroup) + override val saturated by c.setting("Saturated", EatConfig.Saturation.EatSmart, "When to stop eating") { vis() && eatOnHunger }.group(baseGroup) + override val nutritiousFood by c.setting("Nutritious Food", nutritiousFoodDefaults, nutritiousFoodDefaults, "Items that are be considered nutritious") { vis() && eatOnHunger }.group(baseGroup) + override val selectionPriority by c.setting("Selection Priority", EatConfig.SelectionPriority.MostNutritious, "The priority for selecting food items") { vis() && eatOnHunger }.group(baseGroup) + override val eatOnFire by c.setting("Eat On Fire", true, "Whether to eat when on fire", vis).group(baseGroup) + override val resistanceFood by c.setting("Resistance Food", resistanceFoodDefaults, resistanceFoodDefaults, "Items that give Fire Resistance") { vis() && eatOnFire}.group(baseGroup) + override val eatOnDamage by c.setting("Eat On Damage", true, "Whether to eat when damaged", vis).group(baseGroup) + override val minDamage by c.setting("Minimum Damage", 10, 0..20, 1, "The minimum damage threshold to trigger eating") { vis() && eatOnDamage }.group(baseGroup) + override val regenerationFood by c.setting("Regeneration Food", regenerationFoodDefaults, regenerationFoodDefaults, "Items that give Regeneration") { vis() && eatOnDamage }.group(baseGroup) + override val ignoreBadFood by c.setting("Ignore Bad Food", true, "Whether to eat when the food is bad", vis).group(baseGroup) + override val badFood by c.setting("Bad Food", negativeFoodDefaults, negativeFoodDefaults, "Items that are considered bad food") { vis() && ignoreBadFood }.group(baseGroup) +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt index 1712db18c..bd00f9e5c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt @@ -92,7 +92,7 @@ abstract class BuildResult : ComparableResult, Nameable { } /** - * The player has no permission to interact with the block. (E.g.: Spectator mode) + * The player has no permission to interact with the block. (E.g.: Adventure mode) * @param blockPos The position of the block that is restricted. */ data class Restricted( @@ -204,14 +204,12 @@ abstract class BuildResult : ComparableResult, Nameable { override val pausesParent get() = true override fun resolve() = - neededSelection.let { selection -> - selection.transfer(MainHandContainer, inventory) - ?: MaterialContainer.AwaitItemTask( - "Couldn't find $neededSelection anywhere.", - selection, - inventory - ) - } + neededSelection.transfer(MainHandContainer, inventory) + ?: MaterialContainer.AwaitItemTask( + "Couldn't find $neededSelection anywhere.", + neededSelection, + inventory + ) override fun ShapeBuilder.buildRenderer() { box(blockPos, color, color) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 2784526bc..89aeec7c4 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -61,6 +61,7 @@ import com.lambda.util.BlockUtils.hasFluid import com.lambda.util.BlockUtils.instantBreakable import com.lambda.util.BlockUtils.isEmpty import com.lambda.util.BlockUtils.isNotEmpty +import com.lambda.util.Communication.info import com.lambda.util.Communication.warn import com.lambda.util.math.distSq import com.lambda.util.math.vec3d @@ -85,6 +86,13 @@ import net.minecraft.item.Item import net.minecraft.item.ItemPlacementContext import net.minecraft.item.ItemStack import net.minecraft.item.ItemUsageContext +import net.minecraft.item.Items +import net.minecraft.registry.tag.ItemTags.DIAMOND_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.GOLD_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.IRON_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.NETHERITE_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.STONE_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.WOODEN_TOOL_MATERIALS import net.minecraft.state.property.Properties import net.minecraft.util.Hand import net.minecraft.util.hit.BlockHitResult @@ -396,10 +404,10 @@ object BuildSimulator { if (!currentState.isReplaceable && !statePromoting) return acc preProcessing.sides.forEach { neighbor -> - val hitPos = if (!place.airPlace.isEnabled && (currentState.isEmpty || statePromoting)) - pos.offset(neighbor) - else pos + val hitPos = if (!place.airPlace.isEnabled && (currentState.isAir || statePromoting)) + pos.offset(neighbor) else pos val hitSide = neighbor.opposite + if (!world.worldBorder.contains(hitPos)) return@forEach val voxelShape = blockState(hitPos).getOutlineShape(world, hitPos).let { outlineShape -> if (!outlineShape.isEmpty || !place.airPlace.isEnabled) outlineShape @@ -644,7 +652,7 @@ object BuildSimulator { val state = blockState(pos) /* is a block that will be destroyed by breaking adjacent blocks */ - if (!breaking.breakWeakBlocks && state.block.hardness == 0f && state.isNotEmpty) { + if (!breaking.breakWeakBlocks && state.block.hardness == 0f && !state.isAir && state.isNotEmpty) { acc.add(BuildResult.Ignored(pos)) return acc } @@ -831,14 +839,25 @@ object BuildSimulator { state.calcItemBlockBreakingDelta(player, world, pos, it) } ) { - run { - if (breaking.suitableToolsOnly) isSuitableForBreaking(state) - else StackSelection.EVERYTHING - } and if (breaking.forceSilkTouch) { - hasEnchantment(Enchantments.AQUA_AFFINITY) - } else if (breaking.forceFortunePickaxe) { - hasEnchantment(Enchantments.FORTUNE, breaking.minFortuneLevel) - } else StackSelection.EVERYTHING + isTool() and if (breaking.suitableToolsOnly) { + isSuitableForBreaking(state) + } else any() and if (breaking.forceSilkTouch) { + hasEnchantment(Enchantments.SILK_TOUCH) + } else any() and if (breaking.forceFortunePickaxe) { + hasEnchantment(Enchantments.FORTUNE) + } else any() and if (!breaking.useWoodenTools) { + hasTag(WOODEN_TOOL_MATERIALS).not() + } else any() and if (!breaking.useStoneTools) { + hasTag(STONE_TOOL_MATERIALS).not() + } else any() and if (!breaking.useIronTools) { + hasTag(IRON_TOOL_MATERIALS).not() + } else any() and if (!breaking.useDiamondTools) { + hasTag(DIAMOND_TOOL_MATERIALS).not() + } else any() and if (!breaking.useGoldTools) { + hasTag(GOLD_TOOL_MATERIALS).not() + } else any() and if (!breaking.useNetheriteTools) { + hasTag(NETHERITE_TOOL_MATERIALS).not() + } else any() } val silentSwapSelection = selectContainer { @@ -851,7 +870,8 @@ object BuildSimulator { return acc } - val swapStack = swapCandidates.map { it.matchingStacks(stackSelection) } + val swapStack = swapCandidates + .map { it.matchingStacks(stackSelection) } .asSequence() .flatten() .let { containerStacks -> diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index cbc8b294a..8d1fe9a53 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -46,8 +46,7 @@ sealed class TargetState(val type: Type) : StateMatcher { pos: BlockPos, world: ClientWorld, ignoredProperties: Collection> - ) = - state.isEmpty + ) = state.isEmpty override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack = ItemStack.EMPTY @@ -63,8 +62,7 @@ sealed class TargetState(val type: Type) : StateMatcher { pos: BlockPos, world: ClientWorld, ignoredProperties: Collection> - ) = - state.isAir + ) = state.isAir override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack = ItemStack.EMPTY diff --git a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt index 9d6de926e..d8a3d957a 100644 --- a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt @@ -17,13 +17,18 @@ package com.lambda.interaction.material +import com.lambda.interaction.material.StackSelection.Companion.StackSelectionDsl import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemStackUtils.shulkerBoxContents import net.minecraft.block.Block import net.minecraft.block.BlockState +import net.minecraft.component.ComponentType +import net.minecraft.component.DataComponentTypes import net.minecraft.enchantment.Enchantment import net.minecraft.item.Item import net.minecraft.item.ItemStack +import net.minecraft.item.ToolMaterial +import net.minecraft.item.consume.UseAction import net.minecraft.registry.RegistryKey import net.minecraft.registry.tag.TagKey import net.minecraft.screen.slot.Slot @@ -32,6 +37,7 @@ import kotlin.reflect.KClass /** * [StackSelection] is a class that holds a predicate for matching [ItemStack]s. */ +@StackSelectionDsl class StackSelection { var selector: (ItemStack) -> Boolean = EVERYTHING var comparator: Comparator = NO_COMPARE @@ -53,6 +59,8 @@ class StackSelection { */ fun bestItemMatch(stacks: List): ItemStack? = stacks.minWithOrNull(comparator) + fun matches(stack: ItemStack): Boolean = filterStack(stack) + fun filterStack(stack: ItemStack) = if (inShulkerBox) stack.shulkerBoxContents.any { selector(it) } else selector(stack) @@ -65,34 +73,28 @@ class StackSelection { fun filterSlots(slots: List): List = slots.filter(::filterSlot).sortedWith { slot, slot2 -> comparator.compare(slot.stack, slot2.stack) } - @StackSelectionDsl fun > sortBy(selector: (ItemStack) -> R?): StackSelection = apply { comparator = compareBy(selector) } - @StackSelectionDsl fun > sortByDescending(selector: (ItemStack) -> R?): StackSelection = apply { comparator = compareByDescending(selector) } - @StackSelectionDsl fun > thenBy(selector: (ItemStack) -> R?): StackSelection = apply { check(comparator != NO_COMPARE) { "No comparator specified" } comparator = comparator.thenBy(selector) } - @StackSelectionDsl fun > thenByDescending(selector: (ItemStack) -> R?): StackSelection = apply { check(comparator != NO_COMPARE) { "No comparator specified" } comparator = comparator.thenByDescending(selector) } - @StackSelectionDsl fun sortWith(custom: Comparator): StackSelection = apply { comparator = custom } - @StackSelectionDsl fun reversed(): StackSelection = apply { comparator = comparator.reversed() } @@ -126,12 +128,14 @@ class StackSelection { } } + fun any() = EVERYTHING + fun none() = NOTHING + /** * [isItem] returns a predicate that matches a specific [Item]. * @param item The [Item] to be matched. * @return A predicate that matches the [Item]. */ - @StackSelectionDsl fun isItem(item: Item): (ItemStack) -> Boolean { this.item = item return { it.item == item } @@ -143,32 +147,35 @@ class StackSelection { * @param items The collection of `Item` instances to match against. * @return A predicate that checks if the `ItemStack`'s item is contained in the provided collection. */ - @StackSelectionDsl fun isOneOfItems(items: Collection): (ItemStack) -> Boolean = { it.item in items } + fun isNoneOfItems(items: Collection): (ItemStack) -> Boolean = isOneOfItems(items).not() + /** * Returns a predicate that checks if a given `ItemStack` exists within the provided collection of `ItemStack`s. * * @param stacks A collection of `ItemStack` instances to be checked against. * @return A predicate that evaluates to `true` if the given `ItemStack` is within the specified collection, otherwise `false`. */ - @StackSelectionDsl - fun isOneOfStacks(stacks: Collection): (ItemStack) -> Boolean = { it in stacks } + fun isOneOfStacks(stacks: Collection): (ItemStack) -> Boolean = stacks::contains - @StackSelectionDsl fun isSuitableForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { it.isSuitableFor(blockState) } - @StackSelectionDsl - fun isTag(tag: TagKey): (ItemStack) -> Boolean { - return { it.isIn(tag) } - } + fun hasTag(tag: TagKey): (ItemStack) -> Boolean = { it.isIn(tag) } + + fun hasUseAction(action: UseAction): (ItemStack) -> Boolean = { it.useAction == action } + + fun isTool(): (ItemStack) -> Boolean = hasComponent(DataComponentTypes.TOOL) + + fun isFood(): (ItemStack) -> Boolean = hasComponent(DataComponentTypes.FOOD) + + fun hasComponent(type: ComponentType<*>): (ItemStack) -> Boolean = { it.components.contains(type) } /** * [isItem] returns a predicate that matches a specific [Item] instance. * @param T The instance of [Item] to be matched. * @return A predicate that matches the [Item]. */ - @StackSelectionDsl inline fun isItem(): (ItemStack) -> Boolean { itemClass = T::class return { it.item is T } @@ -179,7 +186,6 @@ class StackSelection { * @param block The [Block] to be matched. * @return A predicate that matches the [Block]. */ - @StackSelectionDsl fun isBlock(block: Block): (ItemStack) -> Boolean { item = block.asItem() return { it.item == block.asItem() } @@ -190,7 +196,6 @@ class StackSelection { * @param stack The [ItemStack] to be matched. * @return A predicate that matches the [ItemStack]. */ - @StackSelectionDsl fun isItemStack(stack: ItemStack): (ItemStack) -> Boolean { this.itemStack = stack return { ItemStack.areEqual(it, stack) } @@ -201,7 +206,6 @@ class StackSelection { * @param damage The damage value to be matched. * @return A predicate that matches the damage value. */ - @StackSelectionDsl fun hasDamage(damage: Int): (ItemStack) -> Boolean { this.damage = damage return { it.damage == damage } @@ -213,7 +217,6 @@ class StackSelection { * @param level The level to be matched (if -1 will look for any level above 0). * @return A predicate that matches the [Enchantment] and `level`. */ - @StackSelectionDsl fun hasEnchantment(enchantment: RegistryKey, level: Int = -1): (ItemStack) -> Boolean = { if (level < 0) { it.getEnchantment(enchantment) > 0 @@ -226,7 +229,6 @@ class StackSelection { * Returns the negation of the original predicate. * @return A new predicate that matches if the original predicate does not match. */ - @StackSelectionDsl fun ((ItemStack) -> Boolean).not(): (ItemStack) -> Boolean { return { !this(it) } } @@ -236,7 +238,6 @@ class StackSelection { * @param otherPredicate The second predicate. * @return A new predicate that matches if both inputs predicate match. */ - @StackSelectionDsl infix fun ((ItemStack) -> Boolean).and(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean { return { this(it) && otherPredicate(it) } } @@ -246,7 +247,6 @@ class StackSelection { * @param otherPredicate The second predicate. * @return A new predicate that matches if either input predicate matches. */ - @StackSelectionDsl infix fun ((ItemStack) -> Boolean).or(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean { return { this(it) || otherPredicate(it) } } @@ -257,6 +257,13 @@ class StackSelection { itemClass?.let { append(it.simpleName) } itemStack?.let { append(it.name.string) } damage?.let { append(" with damage $it") } + when (selector) { + EVERYTHING -> append(" everything") + NOTHING -> append(" nothing") + else -> append(" custom predicate") + } + if (inShulkerBox) append(" in shulker box") + if (comparator != NO_COMPARE) append(" sorted by custom comparator") } companion object { diff --git a/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt b/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt index 85dfce04a..c022bc7da 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt @@ -110,18 +110,18 @@ object ContainerManager : Loadable { containerSelection: ContainerSelection = inventory.containerSelection, ): List = container() - .sortedWith(inventory.providerPriority.materialComparator(this)) .filter { it.materialAvailable(this) >= count } .filter { containerSelection.matches(it) } + .sortedWith(inventory.providerPriority.materialComparator(this)) fun containerWithSpace( selection: StackSelection, inventory: InventoryConfig = TaskFlowModule.inventory, ): List = container() - .sortedWith(inventory.providerPriority.spaceComparator(selection)) .filter { it.spaceAvailable(selection) >= selection.count } .filter { inventory.containerSelection.matches(it) } + .sortedWith(inventory.providerPriority.spaceComparator(selection)) fun findDisposable(inventory: InventoryConfig = TaskFlowModule.inventory) = container().find { container -> inventory.disposables.any { container.materialAvailable(it.asItem().select()) > 0 } diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt index 275944380..798d1357d 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt @@ -63,6 +63,13 @@ interface BreakConfig : RequestConfig { val forceFortunePickaxe: Boolean val minFortuneLevel: Int + val useWoodenTools: Boolean + val useStoneTools: Boolean + val useIronTools: Boolean + val useDiamondTools: Boolean + val useGoldTools: Boolean + val useNetheriteTools: Boolean + val sounds: Boolean val particles: Boolean val breakingTexture: Boolean diff --git a/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt b/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt index 944b343fe..80c7e92e2 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt @@ -18,6 +18,7 @@ package com.lambda.module.modules.client import com.lambda.config.groups.BuildSettings +import com.lambda.config.groups.EatSettings import com.lambda.config.groups.HotbarSettings import com.lambda.config.groups.InteractionSettings import com.lambda.config.groups.InventorySettings @@ -41,6 +42,7 @@ object TaskFlowModule : Module( Interaction("Interaction"), Inventory("Inventory"), Hotbar("Hotbar"), + Eat("Eat"), Debug("Debug") } @@ -49,6 +51,7 @@ object TaskFlowModule : Module( val interaction = InteractionSettings(this, Group.Interaction, InteractionMask.Both) val inventory = InventorySettings(this, Group.Inventory) val hotbar = HotbarSettings(this, Group.Hotbar) + val eat = EatSettings(this, Group.Eat) val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree").group(Group.Debug) val shrinkFactor by setting("Shrink Factor", 0.001, 0.0..1.0, 0.001).group(Group.Debug) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index f0ae41c75..69bb6b243 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -190,7 +190,7 @@ object Speed : Module( || player.isElytraFlying || player.isTouchingWater || player.isInLava - || player.hasVehicle()) return false + || player.isRiding) return false return when (mode) { Mode.GRIM_STRAFE -> !player.input.handledByBaritone diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt new file mode 100644 index 000000000..722947623 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.player + +import com.lambda.config.groups.EatConfig.Companion.reasonEating +import com.lambda.config.groups.EatSettings +import com.lambda.config.groups.InventorySettings +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.task.RootTask.run +import com.lambda.task.tasks.EatTask +import com.lambda.task.tasks.EatTask.Companion.eat +import com.lambda.util.NamedEnum + +object AutoEat : Module( + name = "AutoEat", + description = "Eats food when you are hungry", + tag = ModuleTag.PLAYER, +) { + private enum class Group(override val displayName: String) : NamedEnum { + Eating("Eating"), + Inventory("Inventory") + } + + private val eat = EatSettings(this, Group.Eating) + private val inventory = InventorySettings(this, Group.Inventory) + private var eatTask: EatTask? = null + + init { + listen { + val reason = reasonEating(eat) + if (eatTask != null || !reason.shouldEat()) return@listen + + val task = eat(eat, inventory) + task.finally { eatTask = null } + task.run() + eatTask = task + } + + onDisable { + eatTask?.cancel() + eatTask = null + } + } +} diff --git a/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index 67bc81251..38f3cf27c 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -18,6 +18,8 @@ package com.lambda.module.modules.player import com.lambda.config.groups.BuildSettings +import com.lambda.config.groups.EatSettings +import com.lambda.config.groups.HotbarSettings import com.lambda.config.groups.InteractionSettings import com.lambda.config.groups.InventorySettings import com.lambda.config.groups.RotationSettings @@ -59,8 +61,9 @@ object HighwayTools : Module( private val pavementMaterial by setting("Pavement Material", Blocks.OBSIDIAN, "Material to build the highway with") { pavement == Material.Block }.group(Group.Structure) private val floor by setting("Floor", Material.None, "Material for the floor").group(Group.Structure) private val floorMaterial by setting("Floor Material", Blocks.NETHERRACK, "Material to build the floor with") { floor == Material.Block }.group(Group.Structure) - private val walls by setting("Walls", Material.None, "Material for the walls").group(Group.Structure) - private val wallMaterial by setting("Wall Material", Blocks.NETHERRACK, "Material to build the walls with") { walls == Material.Block }.group(Group.Structure) + private val rightWall by setting("Right Wall", Material.None, "Build the right wall").group(Group.Structure) + private val leftWall by setting("Left Wall", Material.None, "Build the left wall").group(Group.Structure) + private val wallMaterial by setting("Wall Material", Blocks.NETHERRACK, "Material to build the walls with") { rightWall == Material.Block || leftWall == Material.Block }.group(Group.Structure) private val ceiling by setting("Ceiling", Material.None, "Material for the ceiling").group(Group.Structure) private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, "Material to build the ceiling with") { ceiling == Material.Block }.group(Group.Structure) private val distance by setting("Distance", -1, -1..1000000, 1, "Distance to build the highway/tunnel (negative for infinite)").group(Group.Structure) @@ -70,6 +73,8 @@ object HighwayTools : Module( private val rotation = RotationSettings(this, Group.Rotation) private val interact = InteractionSettings(this, Group.Interaction, InteractionMask.Block) private val inventory = InventorySettings(this, Group.Inventory) + private val hotbar = HotbarSettings(this, Group.Hotbar) + private val eat = EatSettings(this, Group.Eat) private var octant = EightWayDirection.NORTH private var distanceMoved = 0 @@ -100,6 +105,8 @@ object HighwayTools : Module( Rotation("Rotation"), Interaction("Interaction"), Inventory("Inventory"), + Hotbar("Hotbar"), + Eat("Eat") } init { @@ -140,6 +147,9 @@ object HighwayTools : Module( rotation = rotation, interact = interact, inventory = inventory, + hotbar = hotbar, + eat = eat, + lifeMaintenance = true, ).run() } @@ -215,26 +225,25 @@ object HighwayTools : Module( ).associateWith { target(ceiling, ceilingMaterial) } } - if (walls != Material.None) { - val wallElevation = rimHeight + if (pavement != Material.None) 1 else 0 - - // Left wall + val wallElevation = if (pavement != Material.None) rimHeight else 0 + if (pavement != Material.None) 1 else 0 + if (rightWall != Material.None) { structure += generateDirectionalTube( orthogonal, 1, height - wallElevation, -center + width, wallElevation, - ).associateWith { target(walls, wallMaterial) } + ).associateWith { target(rightWall, wallMaterial) } + } - // Right wall + if (leftWall != Material.None) { structure += generateDirectionalTube( orthogonal, 1, height - wallElevation, -center - 1, wallElevation, - ).associateWith { target(walls, wallMaterial) } + ).associateWith { target(leftWall, wallMaterial) } } if (floor != Material.None) { diff --git a/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt b/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt index c979b9263..1b45d0030 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt @@ -82,11 +82,10 @@ object BlockESP : Module( pos: BlockPos, sides: Int, ) = runSafe { - val shape = outlineShape(state, pos) val blockColor = blockColor(state, pos) - if (drawFaces) filled(shape, if (useBlockColor) blockColor else faceColor, sides) - if (drawOutlines) outline(shape, if (useBlockColor) blockColor else outlineColor, sides, outlineMode) + if (drawFaces) filled(pos, state, if (useBlockColor) blockColor else faceColor, sides) + if (drawOutlines) outline(pos, state, if (useBlockColor) blockColor else outlineColor, sides, outlineMode) } private fun rebuildMesh(ctx: SafeContext, from: Any, to: Any): Unit = esp.rebuild() diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index b86240929..dc2497a3f 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -23,6 +23,7 @@ import com.lambda.event.EventFlow.unsubscribe import com.lambda.event.Muteable import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.threading.runSafe import com.lambda.util.Communication.logError import com.lambda.util.Nameable @@ -126,6 +127,7 @@ abstract class Task : Nameable, Muteable { fun success(result: Result) { unsubscribe() state = State.COMPLETED + if (!TaskFlowModule.showAllEntries) parent?.subTasks?.remove(this) runSafe { executeNextTask(result) } diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 95d315179..b33d738ea 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -20,6 +20,8 @@ package com.lambda.task.tasks import baritone.api.pathing.goals.GoalBlock import com.lambda.Lambda.LOG import com.lambda.config.groups.BuildConfig +import com.lambda.config.groups.EatConfig +import com.lambda.config.groups.EatConfig.Companion.reasonEating import com.lambda.config.groups.InteractionConfig import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent @@ -51,6 +53,7 @@ import com.lambda.interaction.request.placing.PlaceRequest import com.lambda.interaction.request.rotating.RotationConfig import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task +import com.lambda.task.tasks.EatTask.Companion.eat import com.lambda.util.Formatting.string import com.lambda.util.extension.Structure import com.lambda.util.extension.inventorySlots @@ -60,15 +63,17 @@ import net.minecraft.entity.ItemEntity import net.minecraft.util.math.BlockPos import java.util.concurrent.ConcurrentLinkedQueue -class BuildTask @Ta5kBuilder constructor( +class BuildTask private constructor( private val blueprint: Blueprint, - private val finishOnDone: Boolean = true, - private val collectDrops: Boolean = TaskFlowModule.build.collectDrops, - private val build: BuildConfig = TaskFlowModule.build, - private val rotation: RotationConfig = TaskFlowModule.rotation, - private val interactionConfig: InteractionConfig = TaskFlowModule.interaction, - private val inventory: InventoryConfig = TaskFlowModule.inventory, - private val hotbar: HotbarConfig = TaskFlowModule.hotbar, + private val finishOnDone: Boolean, + private val collectDrops: Boolean, + private val build: BuildConfig, + private val rotation: RotationConfig, + private val interactionConfig: InteractionConfig, + private val inventory: InventoryConfig, + private val hotbar: HotbarConfig, + private val eat: EatConfig, + private val lifeMaintenance: Boolean, ) : Task() { override val name: String get() = "Building $blueprint with ${(breaks / (age / 20.0 + 0.001)).string} b/s ${(placements / (age / 20.0 + 0.001)).string} p/s" @@ -81,6 +86,7 @@ class BuildTask @Ta5kBuilder constructor( private var placements = 0 private var breaks = 0 private val dropsToCollect = mutableSetOf() + var eatTask: EatTask? = null private val onItemDrop: ((item: ItemEntity) -> Unit)? get() = if (collectDrops) { item -> @@ -93,6 +99,17 @@ class BuildTask @Ta5kBuilder constructor( init { listen { + when { + lifeMaintenance && eatTask == null && reasonEating(eat).shouldEat() -> { + eatTask = eat(eat) + eatTask?.finally { + eatTask = null + }?.execute(this@BuildTask) + return@listen + } + eatTask != null -> return@listen + } + if (blueprint is TickingBlueprint) { blueprint.tick() ?: failure("Failed to tick the ticking blueprint") } @@ -204,8 +221,7 @@ class BuildTask @Ta5kBuilder constructor( return@let true } - val noInventorySpace = player.hotbarAndStorage.none { it.isEmpty } - if (noInventorySpace) { + if (player.hotbarAndStorage.none { it.isEmpty }) { val stackToThrow = player.currentScreenHandler.inventorySlots.firstOrNull { it.stack.item.block in TaskFlowModule.inventory.disposables } ?: run { @@ -237,8 +253,11 @@ class BuildTask @Ta5kBuilder constructor( rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interaction, inventory: InventoryConfig = TaskFlowModule.inventory, + hotbar: HotbarConfig = TaskFlowModule.hotbar, + eat: EatConfig = TaskFlowModule.eat, + lifeMaintenance: Boolean = false, blueprint: () -> Blueprint, - ) = BuildTask(blueprint(), finishOnDone, collectDrops, build, rotation, interact, inventory) + ) = BuildTask(blueprint(), finishOnDone, collectDrops, build, rotation, interact, inventory, hotbar, eat, lifeMaintenance) @Ta5kBuilder fun Structure.build( @@ -248,7 +267,10 @@ class BuildTask @Ta5kBuilder constructor( rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interaction, inventory: InventoryConfig = TaskFlowModule.inventory, - ) = BuildTask(toBlueprint(), finishOnDone, collectDrops, build, rotation, interact, inventory) + hotbar: HotbarConfig = TaskFlowModule.hotbar, + eat: EatConfig = TaskFlowModule.eat, + lifeMaintenance: Boolean = false, + ) = BuildTask(toBlueprint(), finishOnDone, collectDrops, build, rotation, interact, inventory, hotbar, eat, lifeMaintenance) @Ta5kBuilder fun Blueprint.build( @@ -258,7 +280,10 @@ class BuildTask @Ta5kBuilder constructor( rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interaction, inventory: InventoryConfig = TaskFlowModule.inventory, - ) = BuildTask(this, finishOnDone, collectDrops, build, rotation, interact, inventory) + hotbar: HotbarConfig = TaskFlowModule.hotbar, + eat: EatConfig = TaskFlowModule.eat, + lifeMaintenance: Boolean = false, + ) = BuildTask(this, finishOnDone, collectDrops, build, rotation, interact, inventory, hotbar, eat, lifeMaintenance) @Ta5kBuilder fun breakAndCollectBlock( @@ -269,9 +294,12 @@ class BuildTask @Ta5kBuilder constructor( rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interaction, inventory: InventoryConfig = TaskFlowModule.inventory, + hotbar: HotbarConfig = TaskFlowModule.hotbar, + eat: EatConfig = TaskFlowModule.eat, + lifeMaintenance: Boolean = false, ) = BuildTask( blockPos.toStructure(TargetState.Air).toBlueprint(), - finishOnDone, collectDrops, build, rotation, interact, inventory + finishOnDone, collectDrops, build, rotation, interact, inventory, hotbar, eat, lifeMaintenance ) @Ta5kBuilder @@ -283,9 +311,12 @@ class BuildTask @Ta5kBuilder constructor( rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interaction, inventory: InventoryConfig = TaskFlowModule.inventory, + hotbar: HotbarConfig = TaskFlowModule.hotbar, + eat: EatConfig = TaskFlowModule.eat, + lifeMaintenance: Boolean = false, ) = BuildTask( blockPos.toStructure(TargetState.Air).toBlueprint(), - finishOnDone, collectDrops, build, rotation, interact, inventory + finishOnDone, collectDrops, build, rotation, interact, inventory, hotbar, eat, lifeMaintenance ) } } diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt new file mode 100644 index 000000000..51678c7f2 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.task.tasks + +import com.lambda.config.groups.EatConfig +import com.lambda.config.groups.EatConfig.Companion.reasonEating +import com.lambda.context.SafeContext +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.material.container.ContainerManager.transfer +import com.lambda.interaction.material.container.containers.MainHandContainer +import com.lambda.interaction.request.inventory.InventoryConfig +import com.lambda.module.modules.client.TaskFlowModule +import com.lambda.task.Task +import com.lambda.util.item.ItemUtils.nutrition +import net.minecraft.item.ItemStack +import net.minecraft.util.ActionResult +import net.minecraft.util.Hand + +class EatTask @Ta5kBuilder constructor( + val config: EatConfig, + val inventory: InventoryConfig +) : Task() { + override val name: String + get() = reason.message(eatStack ?: ItemStack.EMPTY) + + private var eatStack: ItemStack? = null + private var reason = EatConfig.Reason.None + private var holdingUse = false + + override fun SafeContext.onStart() { + reason = reasonEating(config) + } + + init { + listen { + if (holdingUse && !reason.shouldKeepEating(config, eatStack)) { + mc.options.useKey.isPressed = false + holdingUse = false + interaction.stopUsingItem(player) + success() + return@listen + } + + if (player.isUsingItem) { + if (!holdingUse) { + mc.options.useKey.isPressed = true + holdingUse = true + } + return@listen + } + + val foodFinder = reason.selector(config) + if (!foodFinder.matches(player.mainHandStack)) { + if (holdingUse) { + mc.options.useKey.isPressed = false + holdingUse = false + } + foodFinder.transfer(MainHandContainer, inventory) + ?.execute(this@EatTask) ?: failure("No food found") + return@listen + } + eatStack = player.mainHandStack + + (interaction.interactItem(player, Hand.MAIN_HAND) as? ActionResult.Success)?.let { + if (it.swingSource == ActionResult.SwingSource.CLIENT) player.swingHand(Hand.MAIN_HAND) + mc.gameRenderer.firstPersonRenderer.resetEquipProgress(Hand.MAIN_HAND) + mc.options.useKey.isPressed = true + holdingUse = true + } + } + } + + companion object { + @Ta5kBuilder + fun eat( + config: EatConfig = TaskFlowModule.eat, + inventory: InventoryConfig = TaskFlowModule.inventory, + ) = EatTask(config, inventory) + } +} diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt index 25e5969f0..9d06ba957 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt @@ -36,6 +36,7 @@ import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.BlockUtils.blockPos import com.lambda.util.BlockUtils.blockState import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.util.math.distSq import net.minecraft.block.ChestBlock import net.minecraft.entity.mob.ShulkerEntity import net.minecraft.item.ItemStack @@ -72,7 +73,7 @@ class PlaceContainer @Ta5kBuilder constructor( val containerPosition = options.filter { // ToDo: Check based on if we can move the player close enough rather than y level once the custom pathfinder is merged it.blockPos.y == player.blockPos.y - }.minOrNull()?.blockPos ?: run { + }.minByOrNull { it.blockPos distSq player.pos }?.blockPos ?: run { failure("Couldn't find a valid container placement position for ${startStack.name.string}") return@onStart } diff --git a/src/main/kotlin/com/lambda/util/BlockUtils.kt b/src/main/kotlin/com/lambda/util/BlockUtils.kt index 8f936ab13..0125fef78 100644 --- a/src/main/kotlin/com/lambda/util/BlockUtils.kt +++ b/src/main/kotlin/com/lambda/util/BlockUtils.kt @@ -256,23 +256,23 @@ object BlockUtils { player: PlayerEntity, world: BlockView, blockPos: BlockPos, - item: ItemStack + stack: ItemStack ): Float { val hardness = getHardness(world, blockPos) return if (hardness == -1.0f) 0.0f else { - val harvestMultiplier = if (item.canHarvest(this)) 30 else 100 - player.getItemBlockBreakingSpeed(this, item) / hardness / harvestMultiplier + val harvestMultiplier = if (stack.canHarvest(this)) 30 else 100 + player.getItemBlockBreakingSpeed(this, stack) / hardness / harvestMultiplier } } - fun ItemStack.canHarvest(blockState: BlockState) = - !blockState.isToolRequired || isSuitableFor(blockState) + fun ItemStack.canHarvest(state: BlockState) = + !state.isToolRequired || isSuitableFor(state) fun PlayerEntity.getItemBlockBreakingSpeed( - blockState: BlockState, + state: BlockState, item: ItemStack ): Float { - var speedMultiplier = item.getMiningSpeedMultiplier(blockState) + var speedMultiplier = item.getMiningSpeedMultiplier(state) if (speedMultiplier > 1.0f) { speedMultiplier += item.getEnchantment(Enchantments.EFFICIENCY).let { if (it > 0) (it * it) + 1 @@ -299,7 +299,7 @@ object BlockUtils { speedMultiplier *= getAttributeValue(EntityAttributes.BLOCK_BREAK_SPEED).toFloat() if (isSubmergedIn(FluidTags.WATER)) { getAttributeInstance(EntityAttributes.SUBMERGED_MINING_SPEED)?.let { speed -> - speedMultiplier *= speed.getValue().toFloat() + speedMultiplier *= speed.value.toFloat() } } diff --git a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt index da21632f5..2364191f4 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt @@ -19,6 +19,7 @@ package com.lambda.util.item import net.minecraft.block.Block import net.minecraft.block.Blocks +import net.minecraft.component.DataComponentTypes import net.minecraft.item.Item import net.minecraft.item.Items @@ -121,6 +122,8 @@ object ItemUtils { val Item.block: Block get() = Block.getBlockFromItem(this) + val Item.nutrition: Int get() = components.get(DataComponentTypes.FOOD)?.nutrition ?: 0 + fun Int.toItemCount(): String { if (this < 0) { return "Invalid input"