Skip to content

Commit a5f2e9b

Browse files
committed
feat: enable injecting generated items directly into player inventory
1 parent de2f205 commit a5f2e9b

File tree

5 files changed

+97
-15
lines changed

5 files changed

+97
-15
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// Created by yechentide on 2026/02/06
3+
//
4+
5+
import Foundation
6+
7+
public enum ContainerInsertError: Error, Equatable, LocalizedError {
8+
case invalidPlayerNBT
9+
case noAvailableSlot
10+
}

Sources/CoreBedrock/Utils/ChestEntityGenerator.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
// MARK: Temporarily disabled until rollback is implemented
66

7+
// swiftlint:disable line_length
8+
79
//public enum ChestEntityGenerator {
810
// private static let capacity = 27
911
//
@@ -84,3 +86,5 @@
8486
// }
8587
// }
8688
//}
89+
90+
// swiftlint:enable line_length

Sources/CoreBedrock/Utils/ItemGenerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public enum ItemGenerator {
7373
}
7474

7575
if let blockMeta = meta.blockMeta {
76-
let blockMetaTag = try CompoundTag([
76+
let blockMetaTag = try CompoundTag(name: "Block", [
7777
StringTag(name: "name", meta.type),
7878
IntTag(name: "version", blockMeta.version),
7979
CompoundTag(name: "states", blockMeta.states),
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// Created by yechentide on 2026/02/06
3+
//
4+
5+
import Foundation
6+
7+
public struct ItemInjector {
8+
public static func giveItemToPlayer(
9+
item: CompoundTag, playerKey: Data, in world: LevelKeyValueStore
10+
) throws {
11+
let playerTag = try loadPlayerTag(from: world, key: playerKey)
12+
let inventory = try loadPlayerInventory(from: playerTag)
13+
14+
guard let slotIndex = firstAvailableSlot(in: inventory) else {
15+
throw ContainerInsertError.noAvailableSlot
16+
}
17+
18+
try place(item: item, into: inventory, at: slotIndex)
19+
20+
let newData = try playerTag.toData()
21+
try world.putData(newData, forKey: playerKey)
22+
}
23+
24+
private static func loadPlayerTag(
25+
from world: LevelKeyValueStore,
26+
key: Data
27+
) throws -> CompoundTag {
28+
let data = try world.data(forKey: key)
29+
let reader = CBTagReader(data: data)
30+
31+
guard let tag = try reader.readNext() as? CompoundTag else {
32+
throw ContainerInsertError.invalidPlayerNBT
33+
}
34+
return tag
35+
}
36+
37+
private static func loadPlayerInventory(
38+
from playerTag: CompoundTag
39+
) throws -> ListTag {
40+
guard let inventory = playerTag["Inventory"] as? ListTag else {
41+
throw ContainerInsertError.invalidPlayerNBT
42+
}
43+
return inventory
44+
}
45+
46+
private static func firstAvailableSlot(
47+
in inventory: ListTag
48+
) -> Int? {
49+
inventory.tags.firstIndex { slotTag in
50+
slotTag["Count"]?.stringValue == "0"
51+
}
52+
}
53+
54+
private static func place(
55+
item: CompoundTag,
56+
into inventory: ListTag,
57+
at index: Int
58+
) throws {
59+
_ = item.remove(forKey: "Slot")
60+
try item.append(ByteTag(name: "Slot", UInt8(index)))
61+
inventory.tags[index].parent = nil
62+
inventory.tags[index] = item
63+
}
64+
}

Sources/CoreBedrock/Utils/MapArtGenerator.swift

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,28 @@
33
//
44

55
import CoreGraphics
6+
import Foundation
67

78
public enum MapArtGenerator {
89
private static let tileSize = 128
910

10-
/// Generates map art from an image and places it in a shulker box chest at the specified location.
11+
/// Generates map art from an image, packs the maps into a shulker box,
12+
/// and inserts it into the specified player's inventory.
13+
///
1114
/// - Parameters:
12-
/// - database: The level database to store map data and chest
13-
/// - image: The source image to convert to map art
14-
/// - x: X coordinate for the chest placement
15-
/// - y: Y coordinate for the chest placement
16-
/// - z: Z coordinate for the chest placement
17-
/// - shulkerBoxName: Optional name for the shulker box containing maps
18-
/// - Throws: If image processing, map data generation, or placement fails
19-
public static func generateAndPlace(
15+
/// - database: The level database used to store generated map data
16+
/// - image: The source image to convert into map art
17+
/// - playerKey: The database key identifying the target player
18+
/// - shulkerBoxName: Optional custom name for the shulker box containing the maps
19+
///
20+
/// - Throws:
21+
/// - If map data generation or storage fails
22+
/// - If the player's inventory has no available slot
23+
/// - If the player's NBT data is invalid or cannot be parsed
24+
public static func generateAndGiveToPlayer(
2025
database: LevelKeyValueStore,
2126
image: CGImage,
22-
x: Int32,
23-
y: Int32,
24-
z: Int32,
27+
playerKey: Data,
2528
shulkerBoxName: String? = nil
2629
) throws {
2730
let (mapItems, mapDataDict) = try buildMapItemsAndData(image: image, database: database)
@@ -32,7 +35,7 @@ public enum MapArtGenerator {
3235
try database.putData(entryData, forKey: lvdbKey.data)
3336
}
3437
let shulkerBox = try ShulkerNestingPacker.pack(items: mapItems, rootName: shulkerBoxName)
35-
try ChestEntityGenerator.place(database: database, x: x, y: y, z: z, items: [shulkerBox])
38+
try ItemInjector.giveItemToPlayer(item: shulkerBox, playerKey: playerKey, in: database)
3639
} catch {
3740
// Rollback map data on failure
3841
for lvdbKey in mapDataDict.keys {
@@ -122,6 +125,7 @@ public enum MapArtGenerator {
122125
/// - Throws: If unable to allocate the requested number of IDs
123126
private static func allocateMapIDs(in database: LevelKeyValueStore, count: Int) throws -> [Int64] {
124127
let iter = try database.makeIterator()
128+
iter.moveToFirst()
125129
defer {
126130
iter.close()
127131
}
@@ -131,7 +135,7 @@ public enum MapArtGenerator {
131135
while ids.count < count {
132136
let keyData = LvDBKey.map(currentID).data
133137
iter.move(to: keyData)
134-
if iter.currentKey != keyData {
138+
if !(iter.isValid && iter.currentKey == keyData) {
135139
ids.append(currentID)
136140
}
137141
currentID += 1

0 commit comments

Comments
 (0)