Skip to content
Open
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
1 change: 1 addition & 0 deletions src/main/kotlin/com/lambda/config/blocks/BuildConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface BuildConfig {
val collectDrops: Boolean
val spleefEntities: Boolean
val cautionDoubleBlocks: Boolean
val stripLogs: Boolean
val maxPendingActions: Int
val actionTimeout: Int
val maxBuildDependencies: Int
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/com/lambda/config/blocks/BuildSettings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class BuildSettings(override val c: Config) : BuildConfig, ConfigBlock {
@Group(GENERAL_GROUP) override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks")
@Group(GENERAL_GROUP) override val spleefEntities by c.setting("Spleef Entities", false, "Breaks blocks beneath entities blocking placements to get them out of the way")
@Group(GENERAL_GROUP) override val cautionDoubleBlocks by c.setting("Caution Double Blocks", true, "Prevents spamming double blocks like doors, chests, etc when configured to interact more than once per tick")
@Group(GENERAL_GROUP) override val stripLogs by c.setting("Strip Logs", true, "Builds stripped blocks by placing the unstripped variant and stripping it with an axe when the stripped item isn't carried")
@Group(GENERAL_GROUP) override val maxPendingActions by c.setting("Max Pending Actions", 59, 1..60, 1, "The maximum count of pending interactions to allow before pausing future interactions")
@Group(GENERAL_GROUP) override val actionTimeout by c.setting("Action Timeout", 10, 1..30, 1, "Timeout for block breaks in ticks", unit = " ticks")
@Group(GENERAL_GROUP) override val maxBuildDependencies by c.setting("Max Sim Dependencies", 3, 0..10, 1, "Maximum dependency build results")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package com.lambda.interaction.construction.simulation.processing

import com.lambda.context.AutomatedSafeContext
import com.lambda.context.SafeContext
import net.minecraft.block.BlockState
import net.minecraft.util.math.BlockPos
Expand All @@ -27,6 +28,10 @@ import net.minecraft.util.math.BlockPos
* unnecessary to scan all of them, for example.
*/
interface StateProcessor {
/** Checked before [acceptsState], so a disabled processor still falls back to breaking. */
context(_: AutomatedSafeContext)
fun isEnabled(): Boolean = true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the isEnabled check is only overridden by StrippedStateProcessor so the check should probably be moved into the acceptsState for StrippedStateProcessor and the function removed.


fun acceptsState(state: BlockState, targetState: BlockState): Boolean

context(safeContext: SafeContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package com.lambda.interaction.construction.simulation.processing

import com.lambda.context.AutomatedSafeContext
import com.lambda.context.SafeContext
import com.lambda.core.Loadable
import com.lambda.interaction.construction.simulation.SimDsl
import com.lambda.interaction.construction.verify.TargetState
Expand Down Expand Up @@ -144,15 +143,19 @@ object ProcessorRegistry : Loadable {
return PreProcessingData(preProcessingInfo, pos)
}

context(safeContext: SafeContext)
context(_: AutomatedSafeContext)
private fun preProcess(pos: BlockPos, state: BlockState, targetState: BlockState, itemStack: ItemStack) =
PreProcessingInfoAccumulator(targetState, itemStack.item).run {
var stateProcessing = false
stateProcessors.forEach { processor ->
if (processor.acceptsState(state, targetState)) {
with(processor) { preProcess(state, targetState, pos) }
stateProcessing = true
if (!processor.acceptsState(state, targetState)) return@forEach

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldnt be altered

if (!processor.isEnabled()) {
// The verdict now hinges on a setting that can be toggled at any time.
noCaching()
return@forEach
}
with(processor) { preProcess(state, targetState, pos) }
stateProcessing = true
}
if (!omitInteraction) {
if (state.block != expectedState.block) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2026 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.interaction.construction.simulation.processing.preprocessors.state

import com.lambda.context.AutomatedSafeContext
import com.lambda.context.SafeContext
import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator
import com.lambda.interaction.construction.simulation.processing.StateProcessor
import com.lambda.util.item.ItemUtils
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.AxeItem
import net.minecraft.item.Item
import net.minecraft.state.property.Properties
import net.minecraft.util.math.BlockPos

@Suppress("unused")
object StrippedStateProcessor : StateProcessor {
// [AxeItem.STRIPPED_BLOCKS] maps unstripped to stripped, we need the other direction
private val unstrippedToStripped: Map<Block, Block> by lazy {
AxeItem.STRIPPED_BLOCKS.entries.associate { (from, to) -> to to from }
}

context(automatedSafeContext: AutomatedSafeContext)
override fun isEnabled() = automatedSafeContext.buildConfig.stripLogs

override fun acceptsState(state: BlockState, targetState: BlockState): Boolean {
val unstrippedVariant = unstrippedToStripped[targetState.block] ?: return false

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function as a whole can probably be simplified to an = function without a full block

return state.isReplaceable || state.block == unstrippedVariant
Comment thread
beanbag44 marked this conversation as resolved.
}

context(safeContext: SafeContext)
override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) {
val unstrippedVariant = unstrippedToStripped[targetState.block] ?: return
val player = safeContext.player

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for something like this, i try to avoid value sets and call with(safeContext) { }, wrapping the function in the safe context. If you change it to use that, i would change the function to = without the {} and then put the with on the next line


// Every branch below depends on the current inventory, so this must never be cached.
noCaching()

if (state.isReplaceable){

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing space before the {

Also, if you want the logic to mirror FlowerPotStateProcessor for parity, you could change this to state.block != unstrippedVariant and add a return in the block so you can handle the stripping logic after without indentation. The isReplaceable check is already handled in the acceptsState function

// Don't use unstripped logs if you already have stripped ones
if (player.carries(targetState.block.asItem())) return

val sourceItem = unstrippedVariant.asItem()
if (!player.carries(sourceItem)) return

setExpectedState(unstrippedVariant.withAxisOf(targetState))
setItem(sourceItem)
} else if (state.block == unstrippedVariant) {
val axe = player.findAxe() ?: return
setItem(axe)
setPlacing(false)
// InteractSim refuses to interact while sneaking, so ask for it explicitly.
setSneak(false)
}
}

private fun Block.withAxisOf(targetState: BlockState) = defaultState.let { placed ->
if (Properties.AXIS in placed && Properties.AXIS in targetState) {
placed.with(Properties.AXIS, targetState.get(Properties.AXIS))
} else placed
}

private fun PlayerEntity.findAxe(): Item? {
val held = mainHandStack.item
if (held in ItemUtils.axes) return held
return inventory.mainStacks.firstOrNull { it.item in ItemUtils.axes }?.item
}

private fun PlayerEntity.carries(item: Item) =
inventory.mainStacks.any { it.item == item }
}
1 change: 1 addition & 0 deletions src/main/resources/lambda.accesswidener
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ transitive-accessible method net/minecraft/item/DebugStickItem cycle (Lnet/minec
transitive-accessible field net/minecraft/structure/StructureTemplate blockInfoLists Ljava/util/List;
transitive-accessible method net/minecraft/item/BlockItem getPlacementState (Lnet/minecraft/item/ItemPlacementContext;)Lnet/minecraft/block/BlockState;
transitive-accessible method net/minecraft/block/AbstractBlock getPickStack (Lnet/minecraft/world/WorldView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Z)Lnet/minecraft/item/ItemStack;
transitive-accessible field net/minecraft/item/AxeItem STRIPPED_BLOCKS Ljava/util/Map;
transitive-accessible field net/minecraft/client/gui/screen/ingame/HandledScreen focusedSlot Lnet/minecraft/screen/slot/Slot;
transitive-accessible field net/minecraft/registry/SimpleRegistry frozen Z
transitive-accessible field net/minecraft/client/gui/screen/ingame/AbstractSignEditScreen messages [Ljava/lang/String;
Expand Down