diff --git a/src/main/java/io/github/homchom/recode/game/SlotConversions.kt b/src/main/java/io/github/homchom/recode/game/SlotConversions.kt new file mode 100644 index 00000000..13fc8ae7 --- /dev/null +++ b/src/main/java/io/github/homchom/recode/game/SlotConversions.kt @@ -0,0 +1,120 @@ +package io.github.homchom.recode.game + +import io.github.homchom.recode.util.InvokableWrapper + +/** + * An index of a [net.minecraft.world.Container] slot, ordered by [order]. The order is stored in type + * parameter @T and can be changed, allowing for safe slot conversions. + * + * @see SlotOrder + */ +data class SlotIndex( + override val value: Int, + private val order: T +) : InvokableWrapper { + /** + * Converts the index to one in order [order]. + */ + fun reorder(order: S) = converted(value, this.order, order) + + companion object { + /** + * @return A [SlotIndex] with order [targetOrder] and an index equivalent to [value] in [sourceOrder]. + */ + fun converted(value: Int, sourceOrder: SlotOrder, targetOrder: S): SlotIndex { + val (order, subIndex) = sourceOrder.getOrNull(value) + ?: throw IndexOutOfBoundsException("Slot order $sourceOrder does not have index $value") + + val newIndex = targetOrder.indexOf(order, subIndex) + require(newIndex != -1) { + "Slot order $targetOrder does not have an index equivalent to $value in $sourceOrder" + } + + return SlotIndex(newIndex, targetOrder) + } + } +} + +/** + * A recursive total ordering of [net.minecraft.world.Container] indices with identity. + * + * @see SlotIndex + */ +sealed interface SlotOrder { // TODO: add unit tests + data object Hotbar : SlotOrder by slots(9) + data object UpperInventory : SlotOrder by slots(27) + data object Armor : SlotOrder by slots(4) + data object Offhand : SlotOrder by slots(1) + data object QuickCraft : SlotOrder by slots(5) + + data object InventoryItems : SlotOrder by slots(Hotbar, UpperInventory, Armor.reversed, Offhand) + data object InventoryMenu : SlotOrder by slots(QuickCraft, Armor, UpperInventory, Hotbar, Offhand) + + /** + * The identity of the order, used for equality checks. + */ + val id get() = this + + /** + * The number of slots present in this order. + */ + val size: Int + + /** + * @return The base [SlotOrder] found at [index], paired with the sub-index of [index] in it. Returns + * `null` if [index] is out of bounds. + */ + fun getOrNull(index: Int): Pair? + + /** + * @return The index of the given nested [order]'s [subIndex] in this order. + */ + fun indexOf(order: SlotOrder, subIndex: Int): Int +} + +private fun slots(size: Int) = SlotRange(size) +private fun slots(vararg compartments: SlotOrder) = SlotSequence(*compartments) +private val SlotOrder.reversed get() = ReversedSlotOrder(this) + +private class SlotRange(override val size: Int) : SlotOrder { + override fun getOrNull(index: Int) = + if (index in 0..? { + var mutIndex = index + for (compartment in compartments) { + compartment.getOrNull(mutIndex)?.let { return it } + mutIndex -= compartment.size + } + return null + } + + override fun indexOf(order: SlotOrder, subIndex: Int): Int { + var index = 0 + for (compartment in compartments) { + val nestedIndex = compartment.indexOf(order, subIndex) + if (nestedIndex != -1) return index + nestedIndex + index += compartment.size + } + return -1 + } +} + +private class ReversedSlotOrder(private val delegate: SlotOrder) : SlotOrder { + override val size get() = delegate.size + + override fun getOrNull(index: Int) = + delegate.getOrNull(size - 1 - index) + + override fun indexOf(order: SlotOrder, subIndex: Int) = + delegate.indexOf(order, order.size - 1 - subIndex) +} \ No newline at end of file diff --git a/src/main/java/io/github/homchom/recode/hypercube/state/Permissions.kt b/src/main/java/io/github/homchom/recode/hypercube/state/Permissions.kt index 8f8bcf4f..9d905984 100644 --- a/src/main/java/io/github/homchom/recode/hypercube/state/Permissions.kt +++ b/src/main/java/io/github/homchom/recode/hypercube/state/Permissions.kt @@ -3,6 +3,7 @@ package io.github.homchom.recode.hypercube.state class PermissionGroup(ranks: Iterable) { val ranks = ranks.toSet() - inline operator fun contains(rankPermission: P) where P : Rank, P : Comparable

= + inline operator fun contains(rankPermission: P) + where P : Rank, P : Comparable

= ranks.any { it is P && it >= rankPermission } } \ No newline at end of file diff --git a/src/main/java/io/github/homchom/recode/multiplayer/PlayerExtensions.kt b/src/main/java/io/github/homchom/recode/multiplayer/PlayerExtensions.kt index 92619da8..4e639313 100644 --- a/src/main/java/io/github/homchom/recode/multiplayer/PlayerExtensions.kt +++ b/src/main/java/io/github/homchom/recode/multiplayer/PlayerExtensions.kt @@ -1,5 +1,43 @@ package io.github.homchom.recode.multiplayer +import io.github.homchom.recode.game.SlotIndex +import io.github.homchom.recode.game.SlotOrder +import io.github.homchom.recode.mc +import net.minecraft.client.player.LocalPlayer +import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack -val Player.username: String get() = gameProfile.name \ No newline at end of file +val Player.username: String get() = gameProfile.name + +/** + * @return A [Sequence] of this [Inventory]'s items in the order specified by [SlotOrder.InventoryItems]. + */ +fun Inventory.asSequence() = sequenceOf(items, armor, offhand).flatten() + +/** + * Sets this player's creative [Inventory] [slot] to [stack] and synchronizes it with the server. + * + * @throws IllegalStateException if the player is not in creative mode. + */ +fun LocalPlayer.setItemAndSync(slot: SlotIndex, stack: ItemStack) = + setItemAndSync(slot, slot.reorder(SlotOrder.InventoryMenu), stack) + +/** + * Sets this player's creative [Inventory] [slot] to [stack] and synchronizes it with the server. + * + * @throws IllegalStateException if the player is not in creative mode. + */ +@JvmName("setItemAndSyncMenu") +fun LocalPlayer.setItemAndSync(slot: SlotIndex, stack: ItemStack) = + setItemAndSync(slot.reorder(SlotOrder.InventoryItems), slot, stack) + +private fun LocalPlayer.setItemAndSync( + itemsSlot: SlotIndex, + menuSlot: SlotIndex, + stack: ItemStack +) { + check(isCreative) { "Inventory items can only be set by the client in creative mode" } + inventory.setItem(itemsSlot(), stack) + mc.gameMode?.handleCreativeModeItemAdd(stack, menuSlot()) +} \ No newline at end of file