Skip to content

Commit

Permalink
Add 'kind' property to window areas to give certain areas meaning (e.…
Browse files Browse the repository at this point in the history
…g. crafting result). Prevent invalid interactions with armor slots and crafting result slots
  • Loading branch information
stackotter committed Jun 6, 2024
1 parent 264da59 commit ae0bfe5
Show file tree
Hide file tree
Showing 6 changed files with 341 additions and 220 deletions.
4 changes: 2 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ Not every version will be perfectly supported but I will try and have the most p
- [x] [Sky box](https://github.com/stackotter/delta-client/pull/188)
- [ ] Entities
- [x] Basic entity rendering (just coloured cubes)
- [ ] Render entity models
- [ ] Entity animations
- [x] Render entity models
- [ ] Block entities (e.g. chests)
- [ ] Item entities
- [ ] Entity animations
- [ ] GUI
- [x] Chat
- [x] F3-style stuff
Expand Down
9 changes: 6 additions & 3 deletions Sources/Core/Sources/ECS/Components/PlayerInventory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,24 @@ public class PlayerInventory: Component {
startIndex: 1,
width: 2,
height: 2,
position: Vec2i(98, 18)
position: Vec2i(98, 18),
kind: .smallCraftingRecipeInput
)

public static let craftingResultArea = WindowArea(
startIndex: 0,
width: 1,
height: 1,
position: Vec2i(154, 28)
position: Vec2i(154, 28),
kind: .recipeResult
)

public static let armorArea = WindowArea(
startIndex: 5,
width: 1,
height: 4,
position: Vec2i(8, 8)
position: Vec2i(8, 8),
kind: .armor
)

public static let offHandArea = WindowArea(
Expand Down
168 changes: 118 additions & 50 deletions Sources/Core/Sources/GUI/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,43 +46,96 @@ public class Window {
return rows
}

public func leftClick(_ slotIndex: Int, mouseStack: inout ItemStack?, connection: ServerConnection?) {
/// Gets the window area corresponding to the given slot and the position of the slot in the area, if any.
public func area(containing slotIndex: Int) -> (area: WindowArea, position: Vec2i)? {
for area in type.areas {
guard let position = area.position(ofWindowSlot: slotIndex) else {
continue
}
return (area, position)
}
return nil
}

public func leftClick(
_ slotIndex: Int, mouseStack: inout ItemStack?, connection: ServerConnection?
) {
guard let (area, slotPosition) = area(containing: slotIndex) else {
log.warning(
"No area of window of type '\(type.identifier)' contains the slot with index '\(slotIndex)'"
)
return
}

let clickedItem = slots[slotIndex]
if var slotStack = slots[slotIndex].stack,
var mouseStackCopy = mouseStack,
slotStack.itemId == mouseStackCopy.itemId
var mouseStackCopy = mouseStack,
slotStack.itemId == mouseStackCopy.itemId
{
guard
let item = RegistryStore.shared.itemRegistry.item(withId: slotStack.itemId)
else {
log.warning("Failed to get maximum stack size for item with id '\(slotStack.itemId)'")
return
}
let total = slotStack.count + mouseStackCopy.count
slotStack.count = min(total, item.maximumStackSize)
slots[slotIndex].stack = slotStack
if slotStack.count == total {
mouseStack = nil

// If clicking on a recipe result, take result if possible.
if area.kind == .recipeResult {
// Only take if we can take the whole result
if slotStack.count <= item.maximumStackSize - mouseStackCopy.count {
slots[slotIndex].stack = nil
mouseStackCopy.count += slotStack.count
mouseStack = mouseStackCopy
}
} else {
mouseStackCopy.count = total - slotStack.count
mouseStack = mouseStackCopy
let total = slotStack.count + mouseStackCopy.count
slotStack.count = min(total, item.maximumStackSize)
slots[slotIndex].stack = slotStack
if slotStack.count == total {
mouseStack = nil
} else {
mouseStackCopy.count = total - slotStack.count
mouseStack = mouseStackCopy
}
}
} else if area.kind != .recipeResult {
if area.kind == .armor, let mouseStackCopy = mouseStack {
guard
let item = RegistryStore.shared.itemRegistry.item(withId: mouseStackCopy.itemId)
else {
log.warning("Failed to get armor properties for item with id '\(mouseStackCopy.itemId)'")
return
}

// TODO: Allow heads and carved pumpkings to be warn (should be easy, just need an exhaustive
// list).
// Ensure that armor of the correct kind (boots etc) can be put in an armor slot
let isValid = item.properties?.armorProperties?.equipmentSlot.index == slotPosition.y
if isValid {
swap(&slots[slotIndex].stack, &mouseStack)
}
} else {
swap(&slots[slotIndex].stack, &mouseStack)
}
} else {
swap(&slots[slotIndex].stack, &mouseStack)
}

do {
try connection?.sendPacket(ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: .leftClick(slot: Int16(slotIndex)),
clickedItem: clickedItem
))
try connection?.sendPacket(
ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: .leftClick(slot: Int16(slotIndex)),
clickedItem: clickedItem
)
)
} catch {
log.warning("Failed to send click window packet for inventory left click: \(error)")
}
}

public func rightClick(_ slotIndex: Int, mouseStack: inout ItemStack?, connection: ServerConnection?) {
public func rightClick(
_ slotIndex: Int, mouseStack: inout ItemStack?, connection: ServerConnection?
) {
let clickedItem = slots[slotIndex]
if var stack = slots[slotIndex].stack, mouseStack == nil {
let total = stack.count
Expand All @@ -104,7 +157,7 @@ public class Window {
mouseStack = stack
}
} else if let slotStack = slots[slotIndex].stack,
slotStack.itemId == mouseStack?.itemId
slotStack.itemId == mouseStack?.itemId
{
slots[slotIndex].stack?.count += 1
mouseStack?.count -= 1
Expand All @@ -116,12 +169,13 @@ public class Window {
}

do {
try connection?.sendPacket(ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: .rightClick(slot: Int16(slotIndex)),
clickedItem: clickedItem
))
try connection?.sendPacket(
ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: .rightClick(slot: Int16(slotIndex)),
clickedItem: clickedItem
))
} catch {
log.warning("Failed to send click window packet for inventory right click: \(error)")
}
Expand All @@ -140,7 +194,9 @@ public class Window {
return false
}

let slotInputs: [Input] = [.slot1, .slot2, .slot3, .slot4, .slot5, .slot6, .slot7, .slot8, .slot9]
let slotInputs: [Input] = [
.slot1, .slot2, .slot3, .slot4, .slot5, .slot6, .slot7, .slot8, .slot9,
]

if input == .dropItem {
let dropWholeStack = inputState.keys.contains(where: \.isControl)
Expand All @@ -161,12 +217,13 @@ public class Window {
}

do {
try connection?.sendPacket(ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: .numberKey(slot: Int16(slotIndex), number: Int8(hotBarSlot)),
clickedItem: clickedItem
))
try connection?.sendPacket(
ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: .numberKey(slot: Int16(slotIndex), number: Int8(hotBarSlot)),
clickedItem: clickedItem
))
} catch {
log.warning("Failed to send click window packet for inventory right click: \(error)")
}
Expand All @@ -177,18 +234,26 @@ public class Window {
return true
}

public func close(mouseStack: inout ItemStack?, eventBus: EventBus, connection: ServerConnection?) throws {
public func close(mouseStack: inout ItemStack?, eventBus: EventBus, connection: ServerConnection?)
throws
{
mouseStack = nil
eventBus.dispatch(CaptureCursorEvent())
try connection?.sendPacket(CloseWindowServerboundPacket(windowId: UInt8(id)))
}

public func dropItemFromSlot(_ slotIndex: Int, mouseItemStack: ItemStack?, connection: ServerConnection?) {
dropFromSlot(slotIndex, wholeStack: false, mouseItemStack: mouseItemStack, connection: connection)
public func dropItemFromSlot(
_ slotIndex: Int, mouseItemStack: ItemStack?, connection: ServerConnection?
) {
dropFromSlot(
slotIndex, wholeStack: false, mouseItemStack: mouseItemStack, connection: connection)
}

public func dropStackFromSlot(_ slotIndex: Int, mouseItemStack: ItemStack?, connection: ServerConnection?) {
dropFromSlot(slotIndex, wholeStack: true, mouseItemStack: mouseItemStack, connection: connection)
public func dropStackFromSlot(
_ slotIndex: Int, mouseItemStack: ItemStack?, connection: ServerConnection?
) {
dropFromSlot(
slotIndex, wholeStack: true, mouseItemStack: mouseItemStack, connection: connection)
}

public func dropItemFromMouse(_ mouseStack: inout ItemStack?, connection: ServerConnection?) {
Expand Down Expand Up @@ -217,12 +282,13 @@ public class Window {
}

do {
try connection?.sendPacket(ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: wholeStack ? .leftClick(slot: nil) : .rightClick(slot: nil),
clickedItem: slot
))
try connection?.sendPacket(
ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: wholeStack ? .leftClick(slot: nil) : .rightClick(slot: nil),
clickedItem: slot
))
} catch {
log.warning("Failed to send click window packet for item drop: \(error)")
}
Expand All @@ -248,12 +314,14 @@ public class Window {
}

do {
try connection?.sendPacket(ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: wholeStack ? .dropStack(slot: Int16(slotIndex)) : .dropOne(slot: Int16(slotIndex)),
clickedItem: Slot(ItemStack(itemId: -1, itemCount: 1))
))
try connection?.sendPacket(
ClickWindowPacket(
windowId: UInt8(id),
actionId: generateActionId(),
action: wholeStack
? .dropStack(slot: Int16(slotIndex)) : .dropOne(slot: Int16(slotIndex)),
clickedItem: Slot(ItemStack(itemId: -1, itemCount: 1))
))
} catch {
log.warning("Failed to send click window packet for item drop: \(error)")
}
Expand Down
53 changes: 53 additions & 0 deletions Sources/Core/Sources/GUI/WindowArea.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,57 @@ public struct WindowArea {
public var height: Int
/// The position of the area within its window.
public var position: Vec2i
/// The kind of window area (determines its behaviour).
public var kind: Kind?

public init(
startIndex: Int,
width: Int,
height: Int,
position: Vec2i,
kind: WindowArea.Kind? = nil
) {
self.startIndex = startIndex
self.width = width
self.height = height
self.position = position
self.kind = kind
}

/// Gets the position of a slot (given as an index in the window's slots array)
/// if it lies within this area.
public func position(ofWindowSlot slotIndex: Int) -> Vec2i? {
let endIndex = startIndex + width * height
if slotIndex >= startIndex && slotIndex < endIndex {
let position = Vec2i(
(slotIndex - startIndex) % width,
(slotIndex - startIndex) / width
)
return position
}
return nil
}

public enum Kind {
/// A 9x3 area synced with the player's inventory.
case inventorySynced
/// A 9x1 area synced with the player's hotbar.
case hotbarSynced
/// A full 3x3 crafting recipe input area.
case fullCraftingRecipeInput
/// A small 2x2 crafting recipe input area.
case smallCraftingRecipeInput
/// A 1x1 heat recipe (e.g. furnace recipe) input area.
case heatRecipeInput
/// A 1x1 recipe result output area.
case recipeResult
/// A 1x1 heat recipe fuel input area (e.g. furnace fuel slot).
case heatRecipeFuel
/// The 1x1 anvil input slot on the left.
case firstAnvilInput
/// The 1x1 anvil input slot on the right.
case secondAnvilInput
/// The 1x4 armor area in the player inventory.
case armor
}
}
Loading

0 comments on commit ae0bfe5

Please sign in to comment.