Skip to content

Commit 5571706

Browse files
authored
Feature: AutoEat and Building Life Maintainance (#147)
Adds a EatTask with smort behaviour and tons of settings used by both BuildTask and AutoEat.
1 parent 24870cb commit 5571706

File tree

21 files changed

+490
-95
lines changed

21 files changed

+490
-95
lines changed

src/main/java/com/lambda/mixin/render/DebugHudMixin.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
package com.lambda.mixin.render;
1919

20-
import com.lambda.task.RootTask;
2120
import com.lambda.util.DebugInfoHud;
2221
import net.minecraft.client.gui.hud.DebugHud;
2322
import org.spongepowered.asm.mixin.Mixin;
@@ -33,9 +32,4 @@ public class DebugHudMixin {
3332
private void onGetRightText(CallbackInfoReturnable<List<String>> cir) {
3433
DebugInfoHud.addDebugInfo(cir.getReturnValue());
3534
}
36-
37-
@Inject(method = "getLeftText", at = @At("TAIL"))
38-
private void onGetLeftText(CallbackInfoReturnable<List<String>> cir) {
39-
cir.getReturnValue().addAll(List.of(RootTask.INSTANCE.toString().split("\n")));
40-
}
4135
}

src/main/kotlin/com/lambda/config/groups/BreakSettings.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,16 @@ class BreakSettings(
8080
override val ignoredBlocks by c.setting("Ignored Blocks", allSigns, description = "Blocks that wont be broken", visibility = vis).group(groupPath, Group.General)
8181

8282
// Tool
83-
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)
83+
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)
8484
override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks", visibility = vis).group(groupPath, Group.General)
8585
override val forceFortunePickaxe by c.setting("Force Fortune Pickaxe", false, "Force fortune pickaxe when breaking blocks", visibility = vis).group(groupPath, Group.General)
8686
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)
87+
override val useWoodenTools by c.setting("Use Wooden Tools", true, "Use wooden tools when breaking blocks", visibility = vis).group(groupPath, Group.General)
88+
override val useStoneTools by c.setting("Use Stone Tools", true, "Use stone tools when breaking blocks", visibility = vis).group(groupPath, Group.General)
89+
override val useIronTools by c.setting("Use Iron Tools", true, "Use iron tools when breaking blocks", visibility = vis).group(groupPath, Group.General)
90+
override val useDiamondTools by c.setting("Use Diamond Tools", true, "Use diamond tools when breaking blocks", visibility = vis).group(groupPath, Group.General)
91+
override val useGoldTools by c.setting("Use Gold Tools", true, "Use gold tools when breaking blocks", visibility = vis).group(groupPath, Group.General)
92+
override val useNetheriteTools by c.setting("Use Netherite Tools", true, "Use netherite tools when breaking blocks", visibility = vis).group(groupPath, Group.General)
8793

8894
// Cosmetics
8995
override val sounds by c.setting("Break Sounds", true, "Plays the breaking sounds", visibility = vis).group(groupPath, Group.Cosmetic)
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.config.groups
19+
20+
import com.lambda.context.SafeContext
21+
import com.lambda.interaction.material.StackSelection.Companion.selectStack
22+
import com.lambda.threading.runSafe
23+
import com.lambda.util.Describable
24+
import com.lambda.util.NamedEnum
25+
import com.lambda.util.item.ItemUtils.nutrition
26+
import net.minecraft.entity.effect.StatusEffects
27+
import net.minecraft.item.Item
28+
import net.minecraft.item.ItemStack
29+
30+
interface EatConfig {
31+
val eatOnHunger: Boolean
32+
val minFoodLevel: Int
33+
val nutritiousFood: List<Item>
34+
val saturated: Saturation
35+
36+
val eatOnFire: Boolean
37+
val resistanceFood: List<Item>
38+
39+
val eatOnDamage: Boolean
40+
val minDamage: Int
41+
val regenerationFood: List<Item>
42+
43+
val selectionPriority: SelectionPriority
44+
val ignoreBadFood: Boolean
45+
val badFood: List<Item>
46+
47+
enum class Saturation(
48+
override val displayName: String,
49+
override val description: String
50+
): NamedEnum, Describable {
51+
EatSmart("Eat Smart", "Eats until the next food would exceed the hunger limit."),
52+
EatUntilFull("Eat Until Full", "Eats food until the hunger bar is completely full. May waste some food."),
53+
}
54+
55+
enum class SelectionPriority(
56+
val comparator: Comparator<ItemStack>,
57+
override val displayName: String,
58+
override val description: String
59+
): NamedEnum, Describable {
60+
LeastNutritious(
61+
compareBy { it.item.nutrition },
62+
"Least Nutritious",
63+
"Eats food items with the least nutritional value."
64+
),
65+
MostNutritious(
66+
compareByDescending { it.item.nutrition },
67+
"Most Nutritious",
68+
"Eats food items with the most nutritional value."
69+
)
70+
}
71+
72+
enum class Reason(val message: (ItemStack) -> String) {
73+
None({ "Waiting for reason to eat..." }),
74+
Hunger({ "Eating ${it.item.name.string} due to Hunger" }),
75+
Damage({ "Eating ${it.item.name.string} due to Damage" }),
76+
Fire({ "Eating ${it.item.name.string} due to Fire" });
77+
78+
fun shouldEat() = this != None
79+
80+
fun shouldKeepEating(config: EatConfig, stack: ItemStack?) = runSafe {
81+
if (stack == null || stack.isEmpty) return@runSafe false
82+
when(this@Reason) {
83+
Hunger -> when(config.saturated) {
84+
Saturation.EatSmart -> stack.item.nutrition + player.hungerManager.foodLevel <= 20
85+
Saturation.EatUntilFull -> player.hungerManager.isNotFull
86+
}
87+
Damage -> !player.hasStatusEffect(StatusEffects.REGENERATION)
88+
Fire -> !player.hasStatusEffect(StatusEffects.FIRE_RESISTANCE)
89+
None -> false
90+
}
91+
} ?: false
92+
93+
fun selector(config: EatConfig) = selectStack(sorter = config.selectionPriority.comparator) {
94+
when(this@Reason) {
95+
None -> any()
96+
Hunger -> isOneOfItems(config.nutritiousFood)
97+
Damage -> isOneOfItems(config.regenerationFood)
98+
Fire -> isOneOfItems(config.resistanceFood)
99+
} and if (config.ignoreBadFood) isNoneOfItems(config.badFood) else any()
100+
}
101+
}
102+
103+
companion object {
104+
fun SafeContext.reasonEating(config: EatConfig) = when {
105+
config.eatOnHunger && player.hungerManager.foodLevel <= config.minFoodLevel -> Reason.Hunger
106+
config.eatOnDamage && player.health <= config.minDamage && !player.hasStatusEffect(StatusEffects.REGENERATION) -> Reason.Damage
107+
config.eatOnFire && player.isOnFire && !player.hasStatusEffect(StatusEffects.FIRE_RESISTANCE) -> Reason.Fire
108+
else -> Reason.None
109+
}
110+
}
111+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.config.groups
19+
20+
import com.lambda.config.Configurable
21+
import com.lambda.util.NamedEnum
22+
import net.minecraft.item.Item
23+
import net.minecraft.item.Items
24+
25+
class EatSettings(
26+
c: Configurable,
27+
baseGroup: NamedEnum,
28+
vis: () -> Boolean = { true }
29+
) : EatConfig {
30+
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)
31+
val resistanceFoodDefaults = listOf(Items.ENCHANTED_GOLDEN_APPLE)
32+
val regenerationFoodDefaults = listOf(Items.ENCHANTED_GOLDEN_APPLE, Items.GOLDEN_APPLE)
33+
val negativeFoodDefaults = listOf(Items.CHICKEN, Items.POISONOUS_POTATO, Items.PUFFERFISH, Items.ROTTEN_FLESH, Items.SPIDER_EYE)
34+
35+
override val eatOnHunger by c.setting("Eat On Hunger", true, "Whether to eat when hungry", vis).group(baseGroup)
36+
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)
37+
override val saturated by c.setting("Saturated", EatConfig.Saturation.EatSmart, "When to stop eating") { vis() && eatOnHunger }.group(baseGroup)
38+
override val nutritiousFood by c.setting("Nutritious Food", nutritiousFoodDefaults, nutritiousFoodDefaults, "Items that are be considered nutritious") { vis() && eatOnHunger }.group(baseGroup)
39+
override val selectionPriority by c.setting("Selection Priority", EatConfig.SelectionPriority.MostNutritious, "The priority for selecting food items") { vis() && eatOnHunger }.group(baseGroup)
40+
override val eatOnFire by c.setting("Eat On Fire", true, "Whether to eat when on fire", vis).group(baseGroup)
41+
override val resistanceFood by c.setting("Resistance Food", resistanceFoodDefaults, resistanceFoodDefaults, "Items that give Fire Resistance") { vis() && eatOnFire}.group(baseGroup)
42+
override val eatOnDamage by c.setting("Eat On Damage", true, "Whether to eat when damaged", vis).group(baseGroup)
43+
override val minDamage by c.setting("Minimum Damage", 10, 0..20, 1, "The minimum damage threshold to trigger eating") { vis() && eatOnDamage }.group(baseGroup)
44+
override val regenerationFood by c.setting("Regeneration Food", regenerationFoodDefaults, regenerationFoodDefaults, "Items that give Regeneration") { vis() && eatOnDamage }.group(baseGroup)
45+
override val ignoreBadFood by c.setting("Ignore Bad Food", true, "Whether to eat when the food is bad", vis).group(baseGroup)
46+
override val badFood by c.setting("Bad Food", negativeFoodDefaults, negativeFoodDefaults, "Items that are considered bad food") { vis() && ignoreBadFood }.group(baseGroup)
47+
}

src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ abstract class BuildResult : ComparableResult<Rank>, Nameable {
9292
}
9393

9494
/**
95-
* The player has no permission to interact with the block. (E.g.: Spectator mode)
95+
* The player has no permission to interact with the block. (E.g.: Adventure mode)
9696
* @param blockPos The position of the block that is restricted.
9797
*/
9898
data class Restricted(
@@ -204,14 +204,12 @@ abstract class BuildResult : ComparableResult<Rank>, Nameable {
204204
override val pausesParent get() = true
205205

206206
override fun resolve() =
207-
neededSelection.let { selection ->
208-
selection.transfer(MainHandContainer, inventory)
209-
?: MaterialContainer.AwaitItemTask(
210-
"Couldn't find $neededSelection anywhere.",
211-
selection,
212-
inventory
213-
)
214-
}
207+
neededSelection.transfer(MainHandContainer, inventory)
208+
?: MaterialContainer.AwaitItemTask(
209+
"Couldn't find $neededSelection anywhere.",
210+
neededSelection,
211+
inventory
212+
)
215213

216214
override fun ShapeBuilder.buildRenderer() {
217215
box(blockPos, color, color)

src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import com.lambda.util.BlockUtils.hasFluid
6161
import com.lambda.util.BlockUtils.instantBreakable
6262
import com.lambda.util.BlockUtils.isEmpty
6363
import com.lambda.util.BlockUtils.isNotEmpty
64+
import com.lambda.util.Communication.info
6465
import com.lambda.util.Communication.warn
6566
import com.lambda.util.math.distSq
6667
import com.lambda.util.math.vec3d
@@ -85,6 +86,13 @@ import net.minecraft.item.Item
8586
import net.minecraft.item.ItemPlacementContext
8687
import net.minecraft.item.ItemStack
8788
import net.minecraft.item.ItemUsageContext
89+
import net.minecraft.item.Items
90+
import net.minecraft.registry.tag.ItemTags.DIAMOND_TOOL_MATERIALS
91+
import net.minecraft.registry.tag.ItemTags.GOLD_TOOL_MATERIALS
92+
import net.minecraft.registry.tag.ItemTags.IRON_TOOL_MATERIALS
93+
import net.minecraft.registry.tag.ItemTags.NETHERITE_TOOL_MATERIALS
94+
import net.minecraft.registry.tag.ItemTags.STONE_TOOL_MATERIALS
95+
import net.minecraft.registry.tag.ItemTags.WOODEN_TOOL_MATERIALS
8896
import net.minecraft.state.property.Properties
8997
import net.minecraft.util.Hand
9098
import net.minecraft.util.hit.BlockHitResult
@@ -396,10 +404,10 @@ object BuildSimulator {
396404
if (!currentState.isReplaceable && !statePromoting) return acc
397405

398406
preProcessing.sides.forEach { neighbor ->
399-
val hitPos = if (!place.airPlace.isEnabled && (currentState.isEmpty || statePromoting))
400-
pos.offset(neighbor)
401-
else pos
407+
val hitPos = if (!place.airPlace.isEnabled && (currentState.isAir || statePromoting))
408+
pos.offset(neighbor) else pos
402409
val hitSide = neighbor.opposite
410+
if (!world.worldBorder.contains(hitPos)) return@forEach
403411

404412
val voxelShape = blockState(hitPos).getOutlineShape(world, hitPos).let { outlineShape ->
405413
if (!outlineShape.isEmpty || !place.airPlace.isEnabled) outlineShape
@@ -644,7 +652,7 @@ object BuildSimulator {
644652
val state = blockState(pos)
645653

646654
/* is a block that will be destroyed by breaking adjacent blocks */
647-
if (!breaking.breakWeakBlocks && state.block.hardness == 0f && state.isNotEmpty) {
655+
if (!breaking.breakWeakBlocks && state.block.hardness == 0f && !state.isAir && state.isNotEmpty) {
648656
acc.add(BuildResult.Ignored(pos))
649657
return acc
650658
}
@@ -831,14 +839,25 @@ object BuildSimulator {
831839
state.calcItemBlockBreakingDelta(player, world, pos, it)
832840
}
833841
) {
834-
run {
835-
if (breaking.suitableToolsOnly) isSuitableForBreaking(state)
836-
else StackSelection.EVERYTHING
837-
} and if (breaking.forceSilkTouch) {
838-
hasEnchantment(Enchantments.AQUA_AFFINITY)
839-
} else if (breaking.forceFortunePickaxe) {
840-
hasEnchantment(Enchantments.FORTUNE, breaking.minFortuneLevel)
841-
} else StackSelection.EVERYTHING
842+
isTool() and if (breaking.suitableToolsOnly) {
843+
isSuitableForBreaking(state)
844+
} else any() and if (breaking.forceSilkTouch) {
845+
hasEnchantment(Enchantments.SILK_TOUCH)
846+
} else any() and if (breaking.forceFortunePickaxe) {
847+
hasEnchantment(Enchantments.FORTUNE)
848+
} else any() and if (!breaking.useWoodenTools) {
849+
hasTag(WOODEN_TOOL_MATERIALS).not()
850+
} else any() and if (!breaking.useStoneTools) {
851+
hasTag(STONE_TOOL_MATERIALS).not()
852+
} else any() and if (!breaking.useIronTools) {
853+
hasTag(IRON_TOOL_MATERIALS).not()
854+
} else any() and if (!breaking.useDiamondTools) {
855+
hasTag(DIAMOND_TOOL_MATERIALS).not()
856+
} else any() and if (!breaking.useGoldTools) {
857+
hasTag(GOLD_TOOL_MATERIALS).not()
858+
} else any() and if (!breaking.useNetheriteTools) {
859+
hasTag(NETHERITE_TOOL_MATERIALS).not()
860+
} else any()
842861
}
843862

844863
val silentSwapSelection = selectContainer {
@@ -851,7 +870,8 @@ object BuildSimulator {
851870
return acc
852871
}
853872

854-
val swapStack = swapCandidates.map { it.matchingStacks(stackSelection) }
873+
val swapStack = swapCandidates
874+
.map { it.matchingStacks(stackSelection) }
855875
.asSequence()
856876
.flatten()
857877
.let { containerStacks ->

src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ sealed class TargetState(val type: Type) : StateMatcher {
4646
pos: BlockPos,
4747
world: ClientWorld,
4848
ignoredProperties: Collection<Property<*>>
49-
) =
50-
state.isEmpty
49+
) = state.isEmpty
5150

5251
override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack =
5352
ItemStack.EMPTY
@@ -63,8 +62,7 @@ sealed class TargetState(val type: Type) : StateMatcher {
6362
pos: BlockPos,
6463
world: ClientWorld,
6564
ignoredProperties: Collection<Property<*>>
66-
) =
67-
state.isAir
65+
) = state.isAir
6866

6967
override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack =
7068
ItemStack.EMPTY

0 commit comments

Comments
 (0)