Skip to content

Commit a1384c4

Browse files
committed
Move view position and selection from client to server (close #19)
1 parent b07f048 commit a1384c4

17 files changed

+487
-321
lines changed

Common/src/main/kotlin/gay/object/hexdebug/blocks/base/ContainerDataDelegate.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
package gay.`object`.hexdebug.blocks.base
22

3+
import gay.`object`.hexdebug.splicing.Selection
34
import net.minecraft.world.inventory.ContainerData
45
import kotlin.reflect.KProperty
56

7+
data class ContainerDataDelegate(
8+
val data: ContainerData,
9+
val index: Int,
10+
) {
11+
operator fun getValue(thisRef: Any, property: KProperty<*>): Int {
12+
return data.get(index)
13+
}
14+
15+
operator fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
16+
data.set(index, value)
17+
}
18+
}
19+
620
// DataSlot stores ints but serializes them as shorts??????????????????????????????????????????????
721
// TODO: idk if this works for negative values but i don't need it to be negative so idc
822
data class ContainerDataLongDelegate(
@@ -28,3 +42,21 @@ data class ContainerDataLongDelegate(
2842
data.set(index3, (value shr 48).toInt())
2943
}
3044
}
45+
46+
data class ContainerDataSelectionDelegate(
47+
val data: ContainerData,
48+
val fromIndex: Int,
49+
val toIndex: Int,
50+
) {
51+
operator fun getValue(thisRef: Any, property: KProperty<*>): Selection? {
52+
return Selection.fromRawIndices(
53+
from = data.get(fromIndex),
54+
to = data.get(toIndex),
55+
)
56+
}
57+
58+
operator fun setValue(thisRef: Any, property: KProperty<*>, value: Selection?) {
59+
data.set(fromIndex, value?.from ?: -1)
60+
data.set(toIndex, value?.to ?: -1)
61+
}
62+
}

Common/src/main/kotlin/gay/object/hexdebug/blocks/splicing/ClientSplicingTableContainer.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,26 @@ import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType
44
import at.petrak.hexcasting.api.casting.math.HexPattern
55
import gay.`object`.hexdebug.networking.msg.MsgSplicingTableActionC2S
66
import gay.`object`.hexdebug.networking.msg.MsgSplicingTableNewStaffPatternC2S
7+
import gay.`object`.hexdebug.networking.msg.MsgSplicingTableSelectIndexC2S
78
import gay.`object`.hexdebug.splicing.ISplicingTable
8-
import gay.`object`.hexdebug.splicing.Selection
99
import gay.`object`.hexdebug.splicing.SplicingTableAction
1010
import net.minecraft.server.level.ServerPlayer
1111
import net.minecraft.world.SimpleContainer
12-
import net.minecraft.world.item.ItemStack
1312

1413
class ClientSplicingTableContainer : SimpleContainer(SplicingTableItemSlot.container_size), ISplicingTable {
1514
override fun getClientView() = null
16-
override fun listStackChanged(stack: ItemStack) {}
17-
override fun clipboardStackChanged(stack: ItemStack) {}
1815

1916
/** Called on the client. */
20-
override fun runAction(action: SplicingTableAction, player: ServerPlayer?, selection: Selection?) = selection.also {
21-
MsgSplicingTableActionC2S(action, it).sendToServer()
17+
override fun runAction(action: SplicingTableAction, player: ServerPlayer?) {
18+
MsgSplicingTableActionC2S(action).sendToServer()
2219
}
2320

24-
override fun drawPattern(player: ServerPlayer?, pattern: HexPattern, index: Int, selection: Selection?): Pair<Selection?, ResolvedPatternType> {
25-
MsgSplicingTableNewStaffPatternC2S(pattern, index, selection).sendToServer()
26-
return selection to ResolvedPatternType.UNRESOLVED
21+
override fun drawPattern(player: ServerPlayer?, pattern: HexPattern, index: Int): ResolvedPatternType {
22+
MsgSplicingTableNewStaffPatternC2S(pattern, index).sendToServer()
23+
return ResolvedPatternType.UNRESOLVED
24+
}
25+
26+
override fun selectIndex(player: ServerPlayer?, index: Int, hasShiftDown: Boolean, isIota: Boolean) {
27+
MsgSplicingTableSelectIndexC2S(index, hasShiftDown = hasShiftDown, isIota = isIota).sendToServer()
2728
}
2829
}

Common/src/main/kotlin/gay/object/hexdebug/blocks/splicing/SplicingTableBlockEntity.kt

Lines changed: 136 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import at.petrak.hexcasting.api.casting.iota.IotaType
55
import at.petrak.hexcasting.api.casting.iota.PatternIota
66
import at.petrak.hexcasting.api.casting.math.HexPattern
77
import at.petrak.hexcasting.api.utils.extractMedia
8+
import at.petrak.hexcasting.api.utils.getInt
89
import at.petrak.hexcasting.xplat.IXplatAbstractions
910
import gay.`object`.hexdebug.blocks.base.BaseContainer
11+
import gay.`object`.hexdebug.blocks.base.ContainerDataDelegate
1012
import gay.`object`.hexdebug.blocks.base.ContainerDataLongDelegate
13+
import gay.`object`.hexdebug.blocks.base.ContainerDataSelectionDelegate
1114
import gay.`object`.hexdebug.casting.eval.FakeCastEnv
1215
import gay.`object`.hexdebug.config.HexDebugConfig
1316
import gay.`object`.hexdebug.gui.splicing.SplicingTableMenu
@@ -50,20 +53,40 @@ class SplicingTableBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(
5053
index3 = SplicingTableDataSlot.MEDIA_3.index,
5154
)
5255

56+
private var selection by ContainerDataSelectionDelegate(
57+
containerData,
58+
fromIndex = SplicingTableDataSlot.SELECTION_FROM.index,
59+
toIndex = SplicingTableDataSlot.SELECTION_TO.index,
60+
)
61+
62+
private var viewStartIndex by ContainerDataDelegate(
63+
containerData,
64+
index = SplicingTableDataSlot.VIEW_START_INDEX.index,
65+
)
66+
5367
val analogOutputSignal get() = if (!listStack.isEmpty) 15 else 0
5468

69+
// TODO: save?
5570
private val undoStack = UndoStack()
5671

5772
override fun load(tag: CompoundTag) {
5873
super.load(tag)
5974
ContainerHelper.loadAllItems(tag, stacks)
6075
media = tag.getLong("media")
76+
selection = Selection.fromRawIndices(
77+
from = tag.getInt("selectionFrom", -1),
78+
to = tag.getInt("selectionTo", -1),
79+
)
80+
viewStartIndex = tag.getInt("viewStartIndex")
6181
}
6282

6383
override fun saveAdditional(tag: CompoundTag) {
6484
super.saveAdditional(tag)
6585
ContainerHelper.saveAllItems(tag, stacks)
6686
tag.putLong("media", media)
87+
tag.putInt("selectionFrom", selection?.from ?: -1)
88+
tag.putInt("selectionTo", selection?.to ?: -1)
89+
tag.putInt("viewStartIndex", viewStartIndex)
6790
}
6891

6992
override fun createMenu(i: Int, inventory: Inventory, player: Player) =
@@ -77,18 +100,29 @@ class SplicingTableBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(
77100
}
78101

79102
/** Only returns null if it fails to convert `this.level` to [ServerLevel]. */
80-
private fun getData(player: ServerPlayer?, selection: Selection?): SplicingTableData? {
103+
private fun getData(player: ServerPlayer?): SplicingTableData? {
81104
return SplicingTableData(
82105
player = player,
83106
level = level as? ServerLevel ?: return null,
84107
undoStack = undoStack,
85108
selection = selection,
109+
viewStartIndex = viewStartIndex,
86110
listHolder = IXplatAbstractions.INSTANCE.findDataHolder(listStack),
87111
clipboardHolder = IXplatAbstractions.INSTANCE.findDataHolder(clipboardStack),
88112
)
89113
}
90114

91-
override fun getClientView() = getData(null, null)?.run {
115+
private fun setupUndoStack(data: SplicingTableData) {
116+
if (undoStack.size == 0) {
117+
data.pushUndoState(
118+
list = Some(data.list.orEmpty()),
119+
clipboard = Some(data.clipboard),
120+
selection = Some(selection),
121+
)
122+
}
123+
}
124+
125+
override fun getClientView() = getData(null)?.run {
92126
val env = FakeCastEnv(level)
93127
SplicingTableClientView(
94128
list = list?.map { IotaClientView(it, env) },
@@ -101,9 +135,11 @@ class SplicingTableBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(
101135
}
102136

103137
override fun listStackChanged(stack: ItemStack) {
104-
// when the list item is removed, clear the undo stack
138+
// when the list item is removed, clear the undo stack, selection, and view index
105139
if (stack.isEmpty) {
106140
undoStack.clear()
141+
selection = null
142+
viewStartIndex = 0
107143
}
108144
}
109145

@@ -119,65 +155,123 @@ class SplicingTableBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(
119155
}
120156
}
121157

122-
override fun runAction(action: SplicingTableAction, player: ServerPlayer?, selection: Selection?): Selection? {
123-
if (media < mediaCost) return selection
124-
val data = getData(player, selection) ?: return selection
125-
if (undoStack.size == 0) {
126-
data.pushUndoState(
127-
list = Some(data.list.orEmpty()),
128-
clipboard = Some(data.clipboard),
129-
selection = Some(selection),
130-
)
131-
}
132-
return convertAndRun(action.value, data)
158+
override fun runAction(action: SplicingTableAction, player: ServerPlayer?) {
159+
if (media < mediaCost) return
160+
val data = getData(player) ?: return
161+
setupUndoStack(data)
162+
convertAndRun(action.value, data)
133163
}
134164

135165
private fun <T : SplicingTableData> convertAndRun(
136166
actionValue: SplicingTableAction.Value<T>,
137167
data: SplicingTableData,
138-
): Selection? {
139-
val converted = actionValue.convert(data) ?: return data.selection
140-
return actionValue.run(converted).also { consumeMedia(converted) }
141-
}
142-
143-
override fun drawPattern(
144-
player: ServerPlayer?,
145-
pattern: HexPattern,
146-
index: Int,
147-
selection: Selection?
148-
): Pair<Selection?, ResolvedPatternType> {
149-
val errorResult = selection to ResolvedPatternType.ERRORED
150-
if (media < mediaCost) return errorResult
151-
val data = getData(player, selection)
168+
) {
169+
val converted = actionValue.convert(data) ?: return
170+
actionValue.run(converted)
171+
postRunAction(converted)
172+
}
173+
174+
override fun drawPattern(player: ServerPlayer?, pattern: HexPattern, index: Int): ResolvedPatternType {
175+
if (media < mediaCost) return ResolvedPatternType.ERRORED
176+
val data = getData(player)
152177
?.let(ReadWriteList::convertOrNull)
153-
?: return errorResult
154-
if (undoStack.size == 0) {
155-
data.pushUndoState(
156-
list = Some(data.list),
157-
clipboard = Some(data.clipboard),
158-
selection = Some(selection),
159-
)
160-
}
161-
return drawPattern(pattern, data).also { consumeMedia(data) }
178+
?: return ResolvedPatternType.ERRORED
179+
setupUndoStack(data)
180+
val result = drawPattern(pattern, data)
181+
postRunAction(data)
182+
return result
162183
}
163184

164185
private fun drawPattern(pattern: HexPattern, data: ReadWriteList) = data.run {
165-
selection.mutableSubList(list).apply {
186+
typedSelection.mutableSubList(list).apply {
166187
clear()
167188
add(PatternIota(pattern))
168189
}
169190
if (writeList(list)) {
170191
shouldConsumeMedia = true
192+
selection = Selection.edge(typedSelection.start + 1)
171193
pushUndoState(
172194
list = Some(list),
173-
selection = Some(Selection.edge(selection.start + 1)),
174-
) to ResolvedPatternType.ESCAPED
195+
selection = Some(selection),
196+
)
197+
ResolvedPatternType.ESCAPED
198+
} else {
199+
ResolvedPatternType.ERRORED
200+
}
201+
}
202+
203+
override fun selectIndex(player: ServerPlayer?, index: Int, hasShiftDown: Boolean, isIota: Boolean) {
204+
// scuffed: we don't really need the player here, but the code assumes it's present
205+
val data = getData(player)
206+
?.let(ReadList::convertOrNull)
207+
?: return
208+
209+
if (isIota) {
210+
selectIota(data, index, hasShiftDown)
175211
} else {
176-
selection to ResolvedPatternType.ERRORED
212+
selectEdge(data, index, hasShiftDown)
177213
}
178214
}
179215

180-
private fun consumeMedia(data: SplicingTableData) {
216+
private fun selectIota(data: ReadList, index: Int, hasShiftDown: Boolean) {
217+
if (!data.isInRange(index)) return
218+
219+
val selection = selection
220+
this.selection = if (isOnlyIotaSelected(index)) {
221+
null
222+
} else if (hasShiftDown && selection != null) {
223+
if (selection is Selection.Edge && index < selection.from) {
224+
Selection.of(selection.from - 1, index)
225+
} else {
226+
Selection.of(selection.from, index)
227+
}
228+
} else {
229+
Selection.withSize(index, 1)
230+
}
231+
}
232+
233+
private fun selectEdge(data: ReadList, index: Int, hasShiftDown: Boolean) {
234+
if (!isEdgeInRange(data, index)) return
235+
236+
val selection = selection
237+
this.selection = if (isEdgeSelected(index)) {
238+
null
239+
} else if (hasShiftDown && selection != null) {
240+
if (selection is Selection.Edge && index < selection.from) {
241+
Selection.of(selection.from - 1, index)
242+
} else if (index > selection.from) {
243+
Selection.of(selection.from, index - 1)
244+
} else {
245+
Selection.of(selection.from, index)
246+
}
247+
} else {
248+
Selection.edge(index)
249+
}
250+
}
251+
252+
private fun isOnlyIotaSelected(index: Int) =
253+
selection?.let { it.size == 1 && it.from == index } ?: false
254+
255+
private fun isEdgeSelected(index: Int) =
256+
selection?.let { it.start == index && it.end == null } ?: false
257+
258+
private fun isEdgeInRange(data: ReadList, index: Int) =
259+
// cell to the right is in range
260+
data.isInRange(index)
261+
// cell to the left is in range
262+
|| data.isInRange(index - 1)
263+
// allow selecting leftmost edge of empty list
264+
|| (index == 0 && data.list.size == 0)
265+
266+
private fun postRunAction(data: SplicingTableData) {
267+
selection = data.selection
268+
269+
// if there is no list, or the list is too short to fill the screen, set the start index to 0
270+
// otherwise, clamp the start index such that the end index stays in range
271+
val lastIndex = data.list?.lastIndex ?: 0
272+
val maxStartIndex = lastIndex - VIEW_END_INDEX_OFFSET
273+
viewStartIndex = if (maxStartIndex > 0) data.viewStartIndex.coerceIn(0, maxStartIndex) else 0
274+
181275
if (data.shouldConsumeMedia) {
182276
media = (media - mediaCost).coerceIn(0, maxMedia)
183277
refillMedia()

Common/src/main/kotlin/gay/object/hexdebug/blocks/splicing/SplicingTableDataSlot.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ enum class SplicingTableDataSlot {
66
MEDIA_0,
77
MEDIA_1,
88
MEDIA_2,
9-
MEDIA_3;
9+
MEDIA_3,
10+
SELECTION_FROM,
11+
SELECTION_TO,
12+
VIEW_START_INDEX;
1013

1114
val index = ordinal
1215

Common/src/main/kotlin/gay/object/hexdebug/gui/splicing/SplicingTableMenu.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package gay.`object`.hexdebug.gui.splicing
22

33
import at.petrak.hexcasting.api.mod.HexTags
44
import at.petrak.hexcasting.api.utils.isMediaItem
5+
import gay.`object`.hexdebug.blocks.base.ContainerDataDelegate
56
import gay.`object`.hexdebug.blocks.base.ContainerDataLongDelegate
7+
import gay.`object`.hexdebug.blocks.base.ContainerDataSelectionDelegate
68
import gay.`object`.hexdebug.blocks.splicing.ClientSplicingTableContainer
79
import gay.`object`.hexdebug.blocks.splicing.SplicingTableDataSlot
810
import gay.`object`.hexdebug.blocks.splicing.SplicingTableItemSlot
@@ -49,6 +51,17 @@ class SplicingTableMenu(
4951
index3 = SplicingTableDataSlot.MEDIA_3.index,
5052
)
5153

54+
val selection by ContainerDataSelectionDelegate(
55+
data,
56+
fromIndex = SplicingTableDataSlot.SELECTION_FROM.index,
57+
toIndex = SplicingTableDataSlot.SELECTION_TO.index,
58+
)
59+
60+
val viewStartIndex by ContainerDataDelegate(
61+
data,
62+
index = SplicingTableDataSlot.VIEW_START_INDEX.index,
63+
)
64+
5265
var clientView = SplicingTableClientView.empty()
5366

5467
val mediaSlot: Slot
@@ -89,6 +102,7 @@ class SplicingTableMenu(
89102

90103
addDataSlots(data)
91104

105+
// note: it seems like this ONLY runs on the server?
92106
addSlotListener(object : ContainerListener {
93107
override fun slotChanged(menu: AbstractContainerMenu, index: Int, stack: ItemStack) {
94108
when (index) {

0 commit comments

Comments
 (0)