Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/main/java/com/lambda/mixin/render/DebugHudMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,9 +32,4 @@ public class DebugHudMixin {
private void onGetRightText(CallbackInfoReturnable<List<String>> cir) {
DebugInfoHud.addDebugInfo(cir.getReturnValue());
}

@Inject(method = "getLeftText", at = @At("TAIL"))
private void onGetLeftText(CallbackInfoReturnable<List<String>> cir) {
cir.getReturnValue().addAll(List.of(RootTask.INSTANCE.toString().split("\n")));
}
}
8 changes: 7 additions & 1 deletion src/main/kotlin/com/lambda/config/groups/BreakSettings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
111 changes: 111 additions & 0 deletions src/main/kotlin/com/lambda/config/groups/EatConfig.kt
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<Item>
val saturated: Saturation

val eatOnFire: Boolean
val resistanceFood: List<Item>

val eatOnDamage: Boolean
val minDamage: Int
val regenerationFood: List<Item>

val selectionPriority: SelectionPriority
val ignoreBadFood: Boolean
val badFood: List<Item>

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<ItemStack>,
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
}
}
}
47 changes: 47 additions & 0 deletions src/main/kotlin/com/lambda/config/groups/EatSettings.kt
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ abstract class BuildResult : ComparableResult<Rank>, 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(
Expand Down Expand Up @@ -204,14 +204,12 @@ abstract class BuildResult : ComparableResult<Rank>, 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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 ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ sealed class TargetState(val type: Type) : StateMatcher {
pos: BlockPos,
world: ClientWorld,
ignoredProperties: Collection<Property<*>>
) =
state.isEmpty
) = state.isEmpty

override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack =
ItemStack.EMPTY
Expand All @@ -63,8 +62,7 @@ sealed class TargetState(val type: Type) : StateMatcher {
pos: BlockPos,
world: ClientWorld,
ignoredProperties: Collection<Property<*>>
) =
state.isAir
) = state.isAir

override fun getStack(world: ClientWorld, pos: BlockPos, inventory: InventoryConfig): ItemStack =
ItemStack.EMPTY
Expand Down
Loading