Skip to content

Commit e22f3c6

Browse files
committed
Add ContainerEvent.Transfer so transfers can be canceled if they conflict with other modules. This fixes HighwayTools and ToolSaver fighting over moving tools between hotbar and inventory.
1 parent 35c3e83 commit e22f3c6

File tree

6 files changed

+85
-42
lines changed

6 files changed

+85
-42
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2026 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.event.events
19+
20+
import com.lambda.event.callback.Cancellable
21+
import com.lambda.event.callback.ICancellable
22+
import com.lambda.interaction.material.container.MaterialContainer
23+
import net.minecraft.screen.slot.Slot
24+
25+
sealed class ContainerEvent {
26+
data class Transfer(
27+
val fromSlot: Slot,
28+
val toSlot: Slot,
29+
val from: MaterialContainer,
30+
val to: MaterialContainer
31+
) : ICancellable by Cancellable()
32+
}

src/main/kotlin/com/lambda/event/events/InventoryEvent.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package com.lambda.event.events
2020
import com.lambda.event.Event
2121
import com.lambda.event.callback.Cancellable
2222
import com.lambda.event.callback.ICancellable
23-
import com.lambda.interaction.managers.hotbar.HotbarRequest
2423
import net.minecraft.item.ItemStack
2524
import net.minecraft.screen.ScreenHandler
2625

@@ -84,9 +83,6 @@ sealed class InventoryEvent {
8483
) : Event
8584

8685
abstract class HotbarSlot : Event {
87-
88-
data class Request(var request: HotbarRequest? = null) : HotbarSlot()
89-
9086
/**
9187
* Represents an event triggered when the client attempts to send slot update to the server.
9288
*

src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ package com.lambda.interaction.material.container
2020
import com.lambda.context.Automated
2121
import com.lambda.context.AutomatedSafeContext
2222
import com.lambda.context.SafeContext
23+
import com.lambda.event.EventFlow.post
24+
import com.lambda.event.events.ContainerEvent
2325
import com.lambda.interaction.managers.inventory.InventoryRequest
2426
import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest
2527
import com.lambda.interaction.material.StackSelection
@@ -133,6 +135,8 @@ abstract class MaterialContainer(
133135
with(automatedSafeContext) {
134136
val fromSlot = getSlot(stackSelection) ?: return false
135137
val toSlot = destination.getReplaceableSlot() ?: return false
138+
val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@MaterialContainer, destination)
139+
if (transferEvent.post().isCanceled()) return false
136140
return inventoryRequest {
137141
if (swapMethodPriority > destination.swapMethodPriority) transfer(fromSlot, toSlot)
138142
else with(destination) { transfer(toSlot, fromSlot) }

src/main/kotlin/com/lambda/module/modules/player/AutoArmor.kt

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,44 @@ object AutoArmor : Module(
5555
private val feetProtection by setting("Preferred Feet Protection", Protection.Protection)
5656
private val ignoreBinding by setting("Ignore Binding", true, "Ignores curse of binding armor pieces")
5757

58+
val sorter = compareByDescending<Slot> {
59+
if (it.stack.isDamageable && 1 - (it.stack.damage.toFloat() / it.stack.maxDamage) < minDurabilityPercentage.toFloat() / 100)
60+
-Double.MAX_VALUE
61+
else 0.0
62+
}.thenByDescending {
63+
if (elytraPriority) {
64+
if (it.stack.item == Items.ELYTRA) 1.0
65+
else 0.0
66+
} else 0.0
67+
}.thenByDescending {
68+
it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null)
69+
?.modifiers
70+
?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR }
71+
?.modifier?.value
72+
?: 0.0
73+
}.thenByDescending {
74+
it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null)
75+
?.modifiers
76+
?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS }
77+
?.modifier?.value
78+
?: 0.0
79+
}.thenByDescending {
80+
val stack = it.stack
81+
when {
82+
stack.isIn(ItemTags.FOOT_ARMOR) -> stack.getEnchantment(feetProtection.enchant)
83+
stack.isIn(ItemTags.LEG_ARMOR) -> stack.getEnchantment(legProtection.enchant)
84+
stack.isIn(ItemTags.CHEST_ARMOR) -> stack.getEnchantment(chestProtection.enchant)
85+
else -> stack.getEnchantment(headProtection.enchant)
86+
}
87+
}.thenByDescending { slot ->
88+
Protection.entries.fold(0) { acc, protection ->
89+
acc + slot.stack.getEnchantment(protection.enchant)
90+
}
91+
}.thenByDescending { slot ->
92+
slot.stack.getEnchantment(Enchantments.UNBREAKING) +
93+
slot.stack.getEnchantment(Enchantments.MENDING)
94+
}
95+
5896
init {
5997
setDefaultAutomationConfig {
6098
applyEdits {
@@ -65,44 +103,6 @@ object AutoArmor : Module(
65103
listen<TickEvent.Pre> {
66104
val armorSlots = player.armorSlots
67105

68-
val sorter = compareByDescending<Slot> {
69-
if (it.stack.isDamageable && 1 - (it.stack.damage.toFloat() / it.stack.maxDamage) < minDurabilityPercentage.toFloat() / 100)
70-
-Double.MAX_VALUE
71-
else 0.0
72-
}.thenByDescending {
73-
if (elytraPriority) {
74-
if (it.stack.item == Items.ELYTRA) 1.0
75-
else 0.0
76-
} else 0.0
77-
}.thenByDescending {
78-
it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null)
79-
?.modifiers
80-
?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR }
81-
?.modifier?.value
82-
?: 0.0
83-
}.thenByDescending {
84-
it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null)
85-
?.modifiers
86-
?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS }
87-
?.modifier?.value
88-
?: 0.0
89-
}.thenByDescending {
90-
val stack = it.stack
91-
when {
92-
stack.isIn(ItemTags.FOOT_ARMOR) -> stack.getEnchantment(feetProtection.enchant)
93-
stack.isIn(ItemTags.LEG_ARMOR) -> stack.getEnchantment(legProtection.enchant)
94-
stack.isIn(ItemTags.CHEST_ARMOR) -> stack.getEnchantment(chestProtection.enchant)
95-
else -> stack.getEnchantment(headProtection.enchant)
96-
}
97-
}.thenByDescending { slot ->
98-
Protection.entries.fold(0) { acc, protection ->
99-
acc + slot.stack.getEnchantment(protection.enchant)
100-
}
101-
}.thenByDescending { slot ->
102-
slot.stack.getEnchantment(Enchantments.UNBREAKING) +
103-
slot.stack.getEnchantment(Enchantments.MENDING)
104-
}
105-
106106
val swappable = player.hotbarAndInventorySlots
107107
.filter { it.stack.isEquipable && (!ignoreBinding || it.stack.getEnchantment(Enchantments.BINDING_CURSE) <= 0) }
108108
.sortedWith(sorter)

src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ package com.lambda.module.modules.player
1919

2020
import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig
2121
import com.lambda.config.applyEdits
22+
import com.lambda.event.events.ContainerEvent
2223
import com.lambda.event.events.TickEvent
2324
import com.lambda.event.listener.SafeListener.Companion.listen
2425
import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest
26+
import com.lambda.interaction.material.container.containers.HotbarContainer
2527
import com.lambda.module.Module
2628
import com.lambda.module.tag.ModuleTag
2729
import com.lambda.util.EnchantmentUtils.forEachEnchantment
@@ -89,6 +91,11 @@ object ToolSaver : Module(
8991
}
9092
}.submit()
9193
}
94+
95+
listen<ContainerEvent.Transfer> { event ->
96+
if (event.to is HotbarContainer && event.fromSlot.stack.isEndangered) event.cancel()
97+
else if (event.from is HotbarContainer && event.toSlot.stack.isEndangered) event.cancel()
98+
}
9299
}
93100

94101
private val ItemStack.isEndangered get() =

src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
package com.lambda.task.tasks
1919

2020
import com.lambda.context.Automated
21+
import com.lambda.event.EventFlow.post
22+
import com.lambda.event.events.ContainerEvent
2123
import com.lambda.event.events.TickEvent
2224
import com.lambda.event.listener.SafeListener.Companion.listen
2325
import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest
@@ -76,6 +78,8 @@ class ContainerTransferTask(
7678

7779
fromContainer.getSlot(stackSelection)?.let { fromSlot ->
7880
toContainer.getReplaceableSlot()?.let { toSlot ->
81+
val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, fromContainer, toContainer)
82+
if (transferEvent.post().isCanceled()) failure("Transfer prevented by an internal interruption")
7983
inventoryRequest {
8084
if (fromContainer.swapMethodPriority > toContainer.swapMethodPriority)
8185
with(fromContainer) { transfer(fromSlot, toSlot) }

0 commit comments

Comments
 (0)