1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
package moe.nea.firmament.features.inventory.storageoverlay
import java.util.SortedMap
import kotlinx.serialization.serializer
import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.Items
import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.SlotClickEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.customgui.customGui
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
object StorageOverlay : FirmamentFeature {
object Data : ProfileSpecificDataHolder<StorageData>(serializer(), "storage-data", ::StorageData)
override val identifier: String
get() = "storage-overlay"
object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val alwaysReplace by toggle("always-replace") { true }
val columns by integer("rows", 1, 10) { 3 }
val scrollSpeed by integer("scroll-speed", 1, 50) { 10 }
val inverseScroll by toggle("inverse-scroll") { false }
val padding by integer("padding", 1, 20) { 5 }
val margin by integer("margin", 1, 60) { 20 }
}
fun adjustScrollSpeed(amount: Double): Double {
return amount * TConfig.scrollSpeed * (if (TConfig.inverseScroll) 1 else -1)
}
override val config: TConfig
get() = TConfig
var lastStorageOverlay: StorageOverviewScreen? = null
var skipNextStorageOverlayBackflip = false
var currentHandler: StorageBackingHandle? = null
@Subscribe
fun onTick(event: TickEvent) {
rememberContent(currentHandler ?: return)
}
@Subscribe
fun onClick(event: SlotClickEvent) {
if (lastStorageOverlay != null && event.slot.inventory !is PlayerInventory && event.slot.index < 9
&& event.stack.item != Items.BLACK_STAINED_GLASS_PANE
) {
skipNextStorageOverlayBackflip = true
}
}
@Subscribe
fun onScreenChange(it: ScreenChangeEvent) {
if (it.old == null && it.new == null) return
val storageOverlayScreen = it.old as? StorageOverlayScreen
?: ((it.old as? HandledScreen<*>)?.customGui as? StorageOverlayCustom)?.overview
var storageOverviewScreen = it.old as? StorageOverviewScreen
val screen = it.new as? GenericContainerScreen
val oldHandler = currentHandler
currentHandler = StorageBackingHandle.fromScreen(screen)
rememberContent(currentHandler)
if (storageOverviewScreen != null && oldHandler is StorageBackingHandle.HasBackingScreen) {
val player = MC.player
assert(player != null)
player?.networkHandler?.sendPacket(CloseHandledScreenC2SPacket(oldHandler.handler.syncId))
if (player?.currentScreenHandler === oldHandler.handler) {
player.currentScreenHandler = player.playerScreenHandler
}
}
storageOverviewScreen = storageOverviewScreen ?: lastStorageOverlay
if (it.new == null && storageOverlayScreen != null && !storageOverlayScreen.isExiting) {
it.overrideScreen = storageOverlayScreen
return
}
if (storageOverviewScreen != null
&& !storageOverviewScreen.isClosing
&& (currentHandler is StorageBackingHandle.Overview || currentHandler == null)
) {
if (skipNextStorageOverlayBackflip) {
skipNextStorageOverlayBackflip = false
} else {
it.overrideScreen = storageOverviewScreen
lastStorageOverlay = null
}
return
}
screen ?: return
if (storageOverlayScreen?.isExiting == true) return
screen.customGui = StorageOverlayCustom(
currentHandler ?: return,
screen,
storageOverlayScreen ?: (if (TConfig.alwaysReplace) StorageOverlayScreen() else return))
}
fun rememberContent(handler: StorageBackingHandle?) {
handler ?: return
// TODO: Make all of these functions work on deltas / updates instead of the entire contents
val data = Data.data?.storageInventories ?: return
when (handler) {
is StorageBackingHandle.Overview -> rememberStorageOverview(handler, data)
is StorageBackingHandle.Page -> rememberPage(handler, data)
}
Data.markDirty()
}
private fun rememberStorageOverview(
handler: StorageBackingHandle.Overview,
data: SortedMap<StoragePageSlot, StorageData.StorageInventory>
) {
for ((index, stack) in handler.handler.stacks.withIndex()) {
// Ignore unloaded item stacks
if (stack.isEmpty) continue
val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue
val isEmpty = stack.item in StorageOverviewScreen.emptyStorageSlotItems
if (slot in data) {
if (isEmpty)
data.remove(slot)
continue
}
if (!isEmpty) {
data[slot] = StorageData.StorageInventory(slot.defaultName(), slot, null)
}
}
}
private fun rememberPage(
handler: StorageBackingHandle.Page,
data: SortedMap<StoragePageSlot, StorageData.StorageInventory>
) {
// TODO: FIXME: FIXME NOW: Definitely don't copy all of this every tick into persistence
val newStacks =
VirtualInventory(handler.handler.stacks.take(handler.handler.rows * 9).drop(9).map { it.copy() })
data.compute(handler.storagePageSlot) { slot, existingInventory ->
(existingInventory ?: StorageData.StorageInventory(
slot.defaultName(),
slot,
null
)).also {
it.inventory = newStacks
}
}
}
}
|