Skip to content

Commit 117b403

Browse files
committed
WIP of container selections
1 parent 0a2d160 commit 117b403

File tree

10 files changed

+641
-537
lines changed

10 files changed

+641
-537
lines changed

common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ object TransferCommand : LambdaCommand(
4848
val selection = selectStack(count) {
4949
isItem(stack(ctx).value().item)
5050
}
51-
containerWithMaterial(selection).forEachIndexed { i, container ->
51+
selection.containerWithMaterial().forEachIndexed { i, container ->
5252
builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection))
5353
}
5454
builder.buildFuture()

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

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ import com.lambda.interaction.construction.result.BreakResult
2929
import com.lambda.interaction.construction.result.BuildResult
3030
import com.lambda.interaction.construction.result.PlaceResult
3131
import com.lambda.interaction.construction.verify.TargetState
32+
import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer
33+
import com.lambda.interaction.material.StackSelection.Companion.select
34+
import com.lambda.interaction.material.StackSelection.Companion.selectStack
35+
import com.lambda.interaction.material.container.ContainerManager
36+
import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial
37+
import com.lambda.interaction.material.container.ContainerManager.findContainerWithMaterial
38+
import com.lambda.interaction.material.container.MaterialContainer
3239
import com.lambda.interaction.request.rotation.Rotation.Companion.rotation
3340
import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo
3441
import com.lambda.interaction.request.rotation.RotationConfig
@@ -47,12 +54,13 @@ import com.lambda.util.BlockUtils.instantBreakable
4754
import com.lambda.util.BlockUtils.vecOf
4855
import com.lambda.util.Communication.warn
4956
import com.lambda.util.item.ItemStackUtils.equal
50-
import com.lambda.util.item.ItemUtils.findBestAvailableTool
57+
import com.lambda.util.item.ItemUtils.findBestToolsForBreaking
5158
import com.lambda.util.math.distSq
5259
import com.lambda.util.player.copyPlayer
5360
import com.lambda.util.world.raycast.RayCastUtils.blockResult
5461
import net.minecraft.block.OperatorBlock
5562
import net.minecraft.block.pattern.CachedBlockPosition
63+
import net.minecraft.enchantment.Enchantments
5664
import net.minecraft.item.BlockItem
5765
import net.minecraft.item.ItemPlacementContext
5866
import net.minecraft.item.ItemUsageContext
@@ -280,7 +288,7 @@ object BuildSimulator {
280288

281289
val blockHit = checkedResult.blockResult ?: return@forEach
282290
val hitBlock = blockState(blockHit.blockPos).block
283-
val shouldSneak = hitBlock::class in BlockUtils.interactionClasses
291+
val shouldSneak = hitBlock::class in BlockUtils.interactionBlocks
284292

285293
val primeDirection =
286294
(target as? TargetState.State)?.blockState?.getOrEmpty(Properties.HORIZONTAL_FACING)?.getOrNull()
@@ -373,15 +381,6 @@ object BuildSimulator {
373381
return acc
374382
}
375383

376-
/* The current selected item cant mine the block */
377-
Hand.entries.forEach {
378-
val stack = player.getStackInHand(it)
379-
if (stack.isEmpty) return@forEach
380-
if (stack.item.canMine(state, world, pos, player)) return@forEach
381-
acc.add(BreakResult.ItemCantMine(pos, state, stack.item, inventory))
382-
return acc
383-
}
384-
385384
val currentRotation = RotationManager.currentRotation
386385
val currentCast = currentRotation.rayCast(interact.interactReach, eye)
387386

@@ -412,7 +411,7 @@ object BuildSimulator {
412411
rotationRequest,
413412
state,
414413
targetState,
415-
player.inventory.selectedSlot,
414+
player.inventory.selectedSlot + 1,
416415
instantBreakable(state, pos),
417416
build
418417
)
@@ -426,6 +425,7 @@ object BuildSimulator {
426425
val reachSq = interact.interactReach.pow(2)
427426

428427
boxes.forEach { box ->
428+
// ToDo: Rewrite Rotation request system to allow support for all sim features and use the rotation finder
429429
scanSurfaces(box, Direction.entries.toSet(), interact.resolution) { side, vec ->
430430
if (eye distSq vec > reachSq) {
431431
misses.add(vec)
@@ -454,41 +454,62 @@ object BuildSimulator {
454454
return acc
455455
}
456456

457-
interact.pointSelection.select(validHits)?.let { checkedHit ->
458-
val blockHit = checkedHit.hit.blockResult ?: return@let
459-
460-
val breakContext = BreakContext(
461-
eye,
462-
blockHit,
463-
RotationRequest(lookAt(checkedHit.targetRotation, 0.001), rotation),
464-
state,
465-
targetState,
466-
player.inventory.selectedSlot,
467-
instantBreakable(state, pos),
468-
build
469-
)
470-
471-
/* player has a better tool for the job available */
472-
if (!player.isCreative) findBestAvailableTool(state).let { bestTools ->
473-
Hand.entries.map {
474-
player.getStackInHand(it)
475-
}.filter { stack ->
476-
bestTools.any { tool -> tool == stack.item }
477-
}.sortedByDescending {
478-
state.calcItemBlockBreakingDelta(player, world, pos, it)
479-
}.let { stackList ->
480-
if (stackList.isEmpty()) {
481-
acc.add(BuildResult.WrongItem(pos, breakContext, bestTools.first(), player.activeItem, inventory))
482-
return acc
483-
}
484-
breakContext.slotIndex = player.inventory.getSlotWithStack(stackList.first())
485-
acc.add(BreakResult.Break(pos, breakContext))
486-
return acc
487-
}
488-
}
457+
val bestHit = interact.pointSelection.select(validHits) ?: return acc
458+
val blockHit = bestHit.hit.blockResult ?: return acc
459+
val target = lookAt(bestHit.targetRotation, 0.001)
460+
val request = RotationRequest(target, rotation)
461+
val instant = instantBreakable(state, pos)
462+
val useSlotIndex = player.inventory.selectedSlot + 1
463+
464+
val breakContext = BreakContext(
465+
eye, blockHit, request, state, targetState, useSlotIndex, instant, build
466+
)
489467

468+
if (player.isCreative) {
490469
acc.add(BreakResult.Break(pos, breakContext))
470+
return acc
491471
}
472+
473+
val bestTools = findBestToolsForBreaking(state)
474+
475+
/* there is no good tool for the job */
476+
if (bestTools.isEmpty()) {
477+
/* The current selected item cant mine the block */
478+
Hand.entries.forEach {
479+
val stack = player.getStackInHand(it)
480+
if (stack.isEmpty) return@forEach
481+
if (stack.item.canMine(state, world, pos, player)) return@forEach
482+
acc.add(BreakResult.ItemCantMine(pos, state, stack.item, inventory))
483+
return acc
484+
}
485+
// ToDo: Switch to non destroyable item
486+
acc.add(BreakResult.Break(pos, breakContext))
487+
return acc
488+
}
489+
490+
val bestTool = bestTools.firstOrNull() ?: return acc
491+
val toolSelection = if (build.forceSilkTouch) {
492+
selectStack { isItem(bestTool) and hasEnchantment(Enchantments.SILK_TOUCH) }
493+
} else {
494+
bestTool.select()
495+
}
496+
val containerSelection = selectContainer {
497+
matches(toolSelection) and ofAnyType(MaterialContainer.Rank.OFF_HAND, MaterialContainer.Rank.HOTBAR)
498+
}
499+
val allContainersWithTools = toolSelection.containerWithMaterial(inventory, containerSelection)
500+
val matchingStacks = allContainersWithTools.associateWith { it.matchingStacks(toolSelection) }
501+
val bestDeltaTool = matchingStacks.mapValues { (_, stacks) ->
502+
stacks.associateWith { state.calcItemBlockBreakingDelta(player, world, pos, it) }
503+
.maxByOrNull { it.value }
504+
?.toPair()
505+
}.entries.maxByOrNull { it.value?.second ?: 0f }?.toPair() ?: return acc
506+
507+
if (bestDeltaTool.second == null) {
508+
acc.add(BuildResult.WrongItem(pos, breakContext, bestTools.first(), player.activeItem, inventory))
509+
return acc
510+
}
511+
512+
acc.add(BreakResult.Break(pos, breakContext))
492513
return acc
493514
}
494515
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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.interaction.material
19+
20+
import com.lambda.interaction.material.container.MaterialContainer
21+
22+
/**
23+
* ContainerSelection is a class that holds a predicate for matching MaterialContainers.
24+
* It can be combined using "and", "or", etc.
25+
*/
26+
class ContainerSelection {
27+
private var selector: (MaterialContainer) -> Boolean = { true }
28+
29+
/**
30+
* Tests whether the provided container matches this selection.
31+
*/
32+
@ContainerSelectionDsl
33+
fun matches(container: MaterialContainer): Boolean = selector(container)
34+
35+
/**
36+
* Returns a function that matches containers having at least one stack
37+
* which matches the given StackSelection.
38+
*/
39+
@ContainerSelectionDsl
40+
fun matches(stackSelection: StackSelection): (MaterialContainer) -> Boolean =
41+
{ container -> container.matchingStacks(stackSelection).isNotEmpty() }
42+
43+
/**
44+
* Returns a function that matches containers whose rank is any of the types provided.
45+
*/
46+
@ContainerSelectionDsl
47+
fun ofAnyType(vararg types: MaterialContainer.Rank): (MaterialContainer) -> Boolean =
48+
{ container -> types.contains(container.rank) }
49+
50+
/**
51+
* Returns a function that matches containers whose rank is not any of the types provided.
52+
*/
53+
@ContainerSelectionDsl
54+
fun noneOfType(vararg types: MaterialContainer.Rank): (MaterialContainer) -> Boolean =
55+
{ container -> !types.contains(container.rank) }
56+
57+
/**
58+
* Returns a function that combines two container predicates using logical AND.
59+
*/
60+
@ContainerSelectionDsl
61+
infix fun ((MaterialContainer) -> Boolean).and(other: (MaterialContainer) -> Boolean): (MaterialContainer) -> Boolean =
62+
{ container -> this(container) && other(container) }
63+
64+
/**
65+
* Returns a function that combines two container predicates using logical OR.
66+
*/
67+
@ContainerSelectionDsl
68+
infix fun ((MaterialContainer) -> Boolean).or(other: (MaterialContainer) -> Boolean): (MaterialContainer) -> Boolean =
69+
{ container -> this(container) || other(container) }
70+
71+
/**
72+
* Returns a function that negates the current selection predicate.
73+
*/
74+
@ContainerSelectionDsl
75+
fun ((MaterialContainer) -> Boolean).negate(): (MaterialContainer) -> Boolean =
76+
{ container -> !this(container) }
77+
78+
companion object {
79+
@DslMarker
80+
annotation class ContainerSelectionDsl
81+
82+
@ContainerSelectionDsl
83+
fun selectContainer(
84+
block: ContainerSelection.() -> (MaterialContainer) -> Boolean
85+
): ContainerSelection =
86+
ContainerSelection().apply { selector = block() }
87+
}
88+
}

common/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt

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

1818
package com.lambda.interaction.material
1919

20+
import com.lambda.interaction.material.ContainerSelection.Companion.ContainerSelectionDsl
2021
import com.lambda.util.BlockUtils.item
2122
import com.lambda.util.item.ItemStackUtils.shulkerBoxContents
2223
import net.minecraft.block.Block
@@ -103,6 +104,7 @@ class StackSelection {
103104
* @param item The [Item] to be matched.
104105
* @return A predicate that matches the [Item].
105106
*/
107+
@StackSelectionDsl
106108
fun isItem(item: Item): (ItemStack) -> Boolean {
107109
this.item = item
108110
return { it.item == item }
@@ -113,6 +115,7 @@ class StackSelection {
113115
* @param T The instance of [Item] to be matched.
114116
* @return A predicate that matches the [Item].
115117
*/
118+
@StackSelectionDsl
116119
inline fun <reified T : Item> isItem(): (ItemStack) -> Boolean {
117120
itemClass = T::class
118121
return { it.item is T }
@@ -123,6 +126,7 @@ class StackSelection {
123126
* @param block The [Block] to be matched.
124127
* @return A predicate that matches the [Block].
125128
*/
129+
@StackSelectionDsl
126130
fun isBlock(block: Block): (ItemStack) -> Boolean {
127131
item = block.item
128132
return { it.item == block.item }
@@ -133,6 +137,7 @@ class StackSelection {
133137
* @param stack The [ItemStack] to be matched.
134138
* @return A predicate that matches the [ItemStack].
135139
*/
140+
@StackSelectionDsl
136141
fun isItemStack(stack: ItemStack): (ItemStack) -> Boolean {
137142
this.itemStack = stack
138143
return { ItemStack.areEqual(it, stack) }
@@ -143,6 +148,7 @@ class StackSelection {
143148
* @param damage The damage value to be matched.
144149
* @return A predicate that matches the damage value.
145150
*/
151+
@StackSelectionDsl
146152
fun hasDamage(damage: Int): (ItemStack) -> Boolean {
147153
this.damage = damage
148154
return { it.damage == damage }
@@ -154,6 +160,7 @@ class StackSelection {
154160
* @param level The level to be matched (if -1 will look for any level above 0).
155161
* @return A predicate that matches the [Enchantment] and `level`.
156162
*/
163+
@StackSelectionDsl
157164
fun hasEnchantment(enchantment: Enchantment, level: Int = -1): (ItemStack) -> Boolean = {
158165
if (level < 0) {
159166
EnchantmentHelper.getLevel(enchantment, it) > 0
@@ -166,6 +173,7 @@ class StackSelection {
166173
* Returns the negation of the original predicate.
167174
* @return A new predicate that matches if the original predicate does not match.
168175
*/
176+
@StackSelectionDsl
169177
fun ((ItemStack) -> Boolean).not(): (ItemStack) -> Boolean {
170178
return { !this(it) }
171179
}
@@ -175,6 +183,7 @@ class StackSelection {
175183
* @param otherPredicate The second predicate.
176184
* @return A new predicate that matches if both inputs predicate match.
177185
*/
186+
@StackSelectionDsl
178187
infix fun ((ItemStack) -> Boolean).and(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean {
179188
return { this(it) && otherPredicate(it) }
180189
}
@@ -184,6 +193,7 @@ class StackSelection {
184193
* @param otherPredicate The second predicate.
185194
* @return A new predicate that matches if either input predicate matches.
186195
*/
196+
@StackSelectionDsl
187197
infix fun ((ItemStack) -> Boolean).or(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean {
188198
return { this(it) || otherPredicate(it) }
189199
}
@@ -197,6 +207,9 @@ class StackSelection {
197207
}
198208

199209
companion object {
210+
@DslMarker
211+
annotation class StackSelectionDsl
212+
200213
const val DEFAULT_AMOUNT = 1
201214
val FULL_SHULKERS: (ItemStack) -> Boolean = { stack ->
202215
stack.shulkerBoxContents.none { it.isEmpty }
@@ -206,8 +219,11 @@ class StackSelection {
206219
}
207220
val EVERYTHING: (ItemStack) -> Boolean = { true }
208221

222+
@ContainerSelectionDsl
209223
fun Item.select(): StackSelection = selectStack { isItem(this@select) }
224+
@ContainerSelectionDsl
210225
fun ItemStack.select(): StackSelection = selectStack { isItemStack(this@select) }
226+
@ContainerSelectionDsl
211227
fun ((ItemStack) -> Boolean).select() = selectStack { this@select }
212228

213229
/**
@@ -216,6 +232,7 @@ class StackSelection {
216232
* @param block The predicate to be used to select the items.
217233
* @return A [StackSelection] with the given parameters.
218234
*/
235+
@StackSelectionDsl
219236
fun selectStack(
220237
count: Int = DEFAULT_AMOUNT,
221238
inShulkerBox: Boolean = false,

0 commit comments

Comments
 (0)