Add ToolTypeIndex struct (#1478)

## Description

This pull request adds `ToolTypeIndex` struct that behaves similarly to
`ModelIndex` and `BaseItemIndex` structs.

## Links

Related to: #1290 
Related to: #1060

---------

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
This commit is contained in:
Krzysztof Wiśniewski 2025-06-08 11:38:33 +02:00 committed by GitHub
parent cd4cd8e9b9
commit 9f0289551d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 49 deletions

View File

@ -15,6 +15,7 @@ const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
const Neighbor = main.chunk.Neighbor;
const BaseItemIndex = main.items.BaseItemIndex;
const ToolTypeIndex = main.items.ToolTypeIndex;
const Gamemode = main.game.Gamemode;
@ -1137,7 +1138,7 @@ pub const Command = struct { // MARK: Command
switch(self.inv.type) {
.normal, .creative, .crafting => {},
.workbench => {
writer.writeSlice(self.inv.type.workbench.id);
writer.writeSlice(self.inv.type.workbench.id());
},
}
}
@ -1180,7 +1181,7 @@ pub const Command = struct { // MARK: Command
};
const typ: Type = switch(typeEnum) {
inline .normal, .creative, .crafting => |tag| tag,
.workbench => .{.workbench = main.items.getToolTypeByID(reader.remaining) orelse return error.Invalid},
.workbench => .{.workbench = ToolTypeIndex.fromId(reader.remaining) orelse return error.Invalid},
};
Sync.ServerSide.createInventory(user.?, id, len, typ, source);
return .{
@ -1231,7 +1232,7 @@ pub const Command = struct { // MARK: Command
cmd.tryCraftingTo(allocator, self.source, self.dest, side, user);
return;
}
if(self.dest.inv.type == .workbench and self.dest.slot != 25 and self.dest.inv.type.workbench.slotInfos[self.dest.slot].disabled) return;
if(self.dest.inv.type == .workbench and self.dest.slot != 25 and self.dest.inv.type.workbench.slotInfos()[self.dest.slot].disabled) return;
if(self.dest.inv.type == .workbench and self.dest.slot == 25) {
if(self.source.ref().item == null and self.dest.ref().item != null) {
cmd.executeBaseOperation(allocator, .{.move = .{
@ -1288,7 +1289,7 @@ pub const Command = struct { // MARK: Command
std.debug.assert(self.source.inv.type == .normal);
if(self.dest.inv.type == .creative) return;
if(self.dest.inv.type == .crafting) return;
if(self.dest.inv.type == .workbench and (self.dest.slot == 25 or self.dest.inv.type.workbench.slotInfos[self.dest.slot].disabled)) return;
if(self.dest.inv.type == .workbench and (self.dest.slot == 25 or self.dest.inv.type.workbench.slotInfos()[self.dest.slot].disabled)) return;
if(self.dest.inv.type == .workbench and !canPutIntoWorkbench(self.source)) return;
const itemSource = self.source.ref().item orelse return;
if(self.dest.ref().item) |itemDest| {
@ -1342,7 +1343,7 @@ pub const Command = struct { // MARK: Command
cmd.tryCraftingTo(allocator, self.dest, self.source, side, user);
return;
}
if(self.source.inv.type == .workbench and self.source.slot != 25 and self.source.inv.type.workbench.slotInfos[self.source.slot].disabled) return;
if(self.source.inv.type == .workbench and self.source.slot != 25 and self.source.inv.type.workbench.slotInfos()[self.source.slot].disabled) return;
if(self.source.inv.type == .workbench and self.source.slot == 25) {
if(self.dest.ref().item == null and self.source.ref().item != null) {
cmd.executeBaseOperation(allocator, .{.move = .{
@ -1413,7 +1414,7 @@ pub const Command = struct { // MARK: Command
}
return;
}
if(self.source.inv.type == .workbench and self.source.slot != 25 and self.source.inv.type.workbench.slotInfos[self.source.slot].disabled) return;
if(self.source.inv.type == .workbench and self.source.slot != 25 and self.source.inv.type.workbench.slotInfos()[self.source.slot].disabled) return;
if(self.source.inv.type == .workbench and self.source.slot == 25) {
cmd.removeToolCraftingIngredients(allocator, self.source.inv, side);
}
@ -1449,7 +1450,7 @@ pub const Command = struct { // MARK: Command
amount: u16 = 0,
fn run(self: FillFromCreative, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User, mode: Gamemode) error{serverFailure}!void {
if(self.dest.inv.type == .workbench and (self.dest.slot == 25 or self.dest.inv.type.workbench.slotInfos[self.dest.slot].disabled)) return;
if(self.dest.inv.type == .workbench and (self.dest.slot == 25 or self.dest.inv.type.workbench.slotInfos()[self.dest.slot].disabled)) return;
if(side == .server and user != null and mode != .creative) return;
if(side == .client and mode != .creative) return;
@ -1847,7 +1848,7 @@ const Type = union(TypeEnum) {
normal: void,
creative: void,
crafting: void,
workbench: *const main.items.ToolType,
workbench: ToolTypeIndex,
pub fn shouldDepositToUserOnClose(self: Type) bool {
return self == .workbench;
@ -1905,7 +1906,7 @@ fn update(self: Inventory) void {
self._items[self._items.len - 1].deinit();
self._items[self._items.len - 1].clear();
var availableItems: [25]?BaseItemIndex = undefined;
const slotInfos = self.type.workbench.slotInfos;
const slotInfos = self.type.workbench.slotInfos();
for(0..25) |i| {
if(self._items[i].item != null and self._items[i].item.? == .baseItem) {

View File

@ -6,6 +6,8 @@ const BaseItem = items.BaseItem;
const Inventory = items.Inventory;
const Item = items.Item;
const Tool = items.Tool;
const ToolType = items.ToolType;
const ToolTypeIndex = items.ToolTypeIndex;
const Player = main.game.Player;
const Texture = main.graphics.Texture;
const Vec2f = main.vec.Vec2f;
@ -37,7 +39,7 @@ var inv: Inventory = undefined;
var itemSlots: [25]*ItemSlot = undefined;
var toolTypes: main.ListUnmanaged(*const main.items.ToolType) = .{};
var toolTypes: main.ListUnmanaged(ToolTypeIndex) = undefined;
var currentToolType: usize = 0;
var toolButton: *Button = undefined;
@ -47,7 +49,7 @@ var needsUpdate: bool = false;
fn toggleTool(_: usize) void {
currentToolType += 1;
currentToolType %= toolTypes.items.len;
toolButton.child.label.updateText(toolTypes.items[currentToolType].id);
toolButton.child.label.updateText(toolTypes.items[currentToolType].id());
needsUpdate = true;
}
@ -61,7 +63,7 @@ fn openInventory() void {
const row = HorizontalList.init();
for(0..5) |x| {
const index = x + y*5;
const slotInfo = toolTypes.items[currentToolType].slotInfos[index];
const slotInfo = toolTypes.items[currentToolType].slotInfos()[index];
const slot = ItemSlot.init(.{0, 0}, inv, @intCast(index), if(slotInfo.disabled) .invisible else if(slotInfo.optional) .immutable else .default, if(slotInfo.disabled) .immutable else .normal);
itemSlots[index] = slot;
row.add(slot);
@ -72,7 +74,7 @@ fn openInventory() void {
list.add(grid);
}
const verticalThing = VerticalList.init(.{0, 0}, 300, padding);
toolButton = Button.initText(.{8, 0}, 116, toolTypes.items[currentToolType].id, .{.callback = &toggleTool});
toolButton = Button.initText(.{8, 0}, 116, toolTypes.items[currentToolType].id(), .{.callback = &toggleTool});
verticalThing.add(toolButton);
const buttonHeight = verticalThing.size[1];
const craftingResultList = HorizontalList.init();
@ -119,15 +121,17 @@ pub fn render() void {
pub fn onOpen() void {
currentToolType = 0;
var iterator = main.items.toolTypeIterator();
toolTypes = .{};
var iterator = ToolTypeIndex.iterator();
while(iterator.next()) |toolType| {
toolTypes.append(main.globalAllocator, toolType);
}
openInventory();
}
pub fn onClose() void {
toolTypes.deinit(main.globalAllocator);
closeInventory();
toolTypes.clearAndFree(main.globalAllocator);
}

View File

@ -16,6 +16,7 @@ const Vec2i = vec.Vec2i;
const Vec3i = vec.Vec3i;
const Vec3f = vec.Vec3f;
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
const ListUnmanaged = main.ListUnmanaged;
const modifierList = @import("tool/modifiers/_list.zig");
const modifierRestrictionList = @import("tool/modifiers/restrictions/_list.zig");
@ -340,8 +341,8 @@ const TextureGenerator = struct { // MARK: TextureGenerator
const img = tool.image;
for(0..16) |x| {
for(0..16) |y| {
const source = tool.type.pixelSources[x][y];
const sourceOverlay = tool.type.pixelSourcesOverlay[x][y];
const source = tool.type.pixelSources()[x][y];
const sourceOverlay = tool.type.pixelSourcesOverlay()[x][y];
if(sourceOverlay < 25 and tool.craftingGrid[sourceOverlay] != null) {
tool.materialGrid[x][y] = tool.craftingGrid[sourceOverlay];
} else if(source < 25) {
@ -389,7 +390,7 @@ const ToolPhysics = struct { // MARK: ToolPhysics
}
var tempModifiers: main.List(Modifier) = .init(main.stackAllocator);
defer tempModifiers.deinit();
for(tool.type.properties) |property| {
for(tool.type.properties()) |property| {
var sum: f32 = 0;
var weight: f32 = 0;
for(0..25) |i| {
@ -504,6 +505,45 @@ const PropertyMatrix = struct { // MARK: PropertyMatrix
};
};
pub const ToolTypeIndex = packed struct {
index: u16,
const ToolTypeIterator = struct {
i: u16 = 0,
pub fn next(self: *ToolTypeIterator) ?ToolTypeIndex {
if(self.i >= toolTypeList.items.len) return null;
defer self.i += 1;
return ToolTypeIndex{.index = self.i};
}
};
pub fn iterator() ToolTypeIterator {
return .{};
}
pub fn fromId(_id: []const u8) ?ToolTypeIndex {
return toolTypeIdToIndex.get(_id);
}
pub fn id(self: ToolTypeIndex) []const u8 {
return toolTypeList.items[self.index].id;
}
pub fn blockTags(self: ToolTypeIndex) []const Tag {
return toolTypeList.items[self.index].blockTags;
}
pub fn properties(self: ToolTypeIndex) []const PropertyMatrix {
return toolTypeList.items[self.index].properties;
}
pub fn slotInfos(self: ToolTypeIndex) *const [25]SlotInfo {
return &toolTypeList.items[self.index].slotInfos;
}
pub fn pixelSources(self: ToolTypeIndex) *const [16][16]u8 {
return &toolTypeList.items[self.index].pixelSources;
}
pub fn pixelSourcesOverlay(self: ToolTypeIndex) *const [16][16]u8 {
return &toolTypeList.items[self.index].pixelSourcesOverlay;
}
};
pub const ToolType = struct { // MARK: ToolType
id: []const u8,
blockTags: []main.Tag,
@ -534,7 +574,7 @@ pub const Tool = struct { // MARK: Tool
image: graphics.Image,
texture: ?graphics.Texture,
seed: u32,
type: *const ToolType,
type: ToolTypeIndex,
damage: f32,
@ -600,7 +640,7 @@ pub const Tool = struct { // MARK: Tool
return result;
}
pub fn initFromCraftingGrid(craftingGrid: [25]?BaseItemIndex, seed: u32, typ: *const ToolType) *Tool {
pub fn initFromCraftingGrid(craftingGrid: [25]?BaseItemIndex, seed: u32, typ: ToolTypeIndex) *Tool {
const self = init();
self.seed = seed;
self.craftingGrid = craftingGrid;
@ -613,9 +653,9 @@ pub const Tool = struct { // MARK: Tool
}
pub fn initFromZon(zon: ZonElement) *Tool {
const self = initFromCraftingGrid(extractItemsFromZon(zon.getChild("grid")), zon.get(u32, "seed", 0), getToolTypeByID(zon.get([]const u8, "type", "cubyz:pickaxe")) orelse blk: {
const self = initFromCraftingGrid(extractItemsFromZon(zon.getChild("grid")), zon.get(u32, "seed", 0), ToolTypeIndex.fromId(zon.get([]const u8, "type", "cubyz:pickaxe")) orelse blk: {
std.log.err("Couldn't find tool with type {s}. Replacing it with cubyz:pickaxe", .{zon.get([]const u8, "type", "cubyz:pickaxe")});
break :blk getToolTypeByID("cubyz:pickaxe") orelse @panic("cubyz:pickaxe tool not found. Did you load the game with the correct assets?");
break :blk ToolTypeIndex.fromId("cubyz:pickaxe") orelse @panic("cubyz:pickaxe tool not found. Did you load the game with the correct assets?");
});
self.durability = zon.get(u32, "durability", std.math.lossyCast(u32, self.maxDurability));
return self;
@ -643,7 +683,7 @@ pub const Tool = struct { // MARK: Tool
zonObject.put("grid", zonArray);
zonObject.put("durability", self.durability);
zonObject.put("seed", self.seed);
zonObject.put("type", self.type.id);
zonObject.put("type", self.type.id());
return zonObject;
}
@ -685,7 +725,7 @@ pub const Tool = struct { // MARK: Tool
\\Damage: {d:.2}
\\Durability: {}/{}
, .{
self.type.id,
self.type.id(),
self.swingTime,
self.damage,
self.durability,
@ -708,7 +748,7 @@ pub const Tool = struct { // MARK: Tool
damage = modifier.changeBlockDamage(damage, block);
}
for(block.blockTags()) |blockTag| {
for(self.type.blockTags) |toolTag| {
for(self.type.blockTags()) |toolTag| {
if(toolTag == blockTag) return damage;
}
}
@ -874,7 +914,10 @@ pub const Recipe = struct { // MARK: Recipe
};
var arena: main.heap.NeverFailingArenaAllocator = undefined;
var toolTypes: std.StringHashMap(ToolType) = undefined;
var toolTypeList: ListUnmanaged(ToolType) = undefined;
var toolTypeIdToIndex: std.StringHashMapUnmanaged(ToolTypeIndex) = undefined;
var reverseIndices: std.StringHashMap(BaseItemIndex) = undefined;
var modifiers: std.StringHashMap(*const Modifier.VTable) = undefined;
var modifierRestrictions: std.StringHashMap(*const ModifierRestriction.VTable) = undefined;
@ -888,11 +931,7 @@ pub fn hasRegistered(id: []const u8) bool {
}
pub fn hasRegisteredTool(id: []const u8) bool {
return toolTypes.contains(id);
}
pub fn toolTypeIterator() std.StringHashMap(ToolType).ValueIterator {
return toolTypes.valueIterator();
return toolTypeIdToIndex.contains(id);
}
pub fn iterator() std.StringHashMap(BaseItemIndex).ValueIterator {
@ -905,7 +944,10 @@ pub fn recipes() []Recipe {
pub fn globalInit() void {
arena = .init(main.globalAllocator);
toolTypes = .init(arena.allocator().allocator);
toolTypeList = .{};
toolTypeIdToIndex = .{};
reverseIndices = .init(arena.allocator().allocator);
recipeList = .init(arena.allocator());
itemListSize = 0;
@ -982,7 +1024,7 @@ fn loadPixelSources(assetFolder: []const u8, id: []const u8, layerPostfix: []con
}
pub fn registerTool(assetFolder: []const u8, id: []const u8, zon: ZonElement) void {
if(toolTypes.contains(id)) {
if(toolTypeIdToIndex.contains(id)) {
std.log.err("Registered tool type with id {s} twice!", .{id});
}
var slotInfos: [25]SlotInfo = @splat(.{});
@ -1000,9 +1042,9 @@ pub fn registerTool(assetFolder: []const u8, id: []const u8, zon: ZonElement) vo
}
slotInfos[i].optional = zonDisabled.as(usize, 0) != 0;
}
var parameterMatrics: main.List(PropertyMatrix) = .init(arena.allocator());
var parameterMatrices: main.List(PropertyMatrix) = .init(arena.allocator());
for(zon.getChild("parameters").toSlice()) |paramZon| {
const val = parameterMatrics.addOne();
const val = parameterMatrices.addOne();
val.source = MaterialProperty.fromString(paramZon.get([]const u8, "source", "not specified"));
val.destination = ToolProperty.fromString(paramZon.get([]const u8, "destination", "not specified"));
val.resultScale = paramZon.get(f32, "factor", 1.0);
@ -1016,15 +1058,17 @@ pub fn registerTool(assetFolder: []const u8, id: []const u8, zon: ZonElement) vo
loadPixelSources(assetFolder, id, "", &pixelSources);
var pixelSourcesOverlay: [16][16]u8 = undefined;
loadPixelSources(assetFolder, id, "_overlay", &pixelSourcesOverlay);
const idDupe = arena.allocator().dupe(u8, id);
toolTypes.put(idDupe, .{
toolTypeList.append(arena.allocator(), .{
.id = idDupe,
.blockTags = Tag.loadTagsFromZon(arena.allocator(), zon.getChild("blockTags")),
.slotInfos = slotInfos,
.properties = parameterMatrics.toOwnedSlice(),
.properties = parameterMatrices.toOwnedSlice(),
.pixelSources = pixelSources,
.pixelSourcesOverlay = pixelSourcesOverlay,
}) catch unreachable;
});
toolTypeIdToIndex.put(arena.allocator().allocator, idDupe, .{.index = @intCast(toolTypeList.items.len - 1)}) catch unreachable;
std.log.debug("Registered tool: '{s}'", .{id});
}
@ -1071,7 +1115,8 @@ pub fn registerRecipes(zon: ZonElement) void {
}
pub fn reset() void {
toolTypes.clearAndFree();
toolTypeList.clearAndFree(arena.allocator());
toolTypeIdToIndex.clearAndFree(arena.allocator().allocator);
reverseIndices.clearAndFree();
for(recipeList.items) |recipe| {
if(recipe.cachedInventory) |inv| {
@ -1084,7 +1129,8 @@ pub fn reset() void {
}
pub fn deinit() void {
toolTypes.clearAndFree();
toolTypeList.deinit(arena.allocator());
toolTypeIdToIndex.deinit(arena.allocator().allocator);
reverseIndices.clearAndFree();
for(recipeList.items) |recipe| {
if(recipe.cachedInventory) |inv| {
@ -1097,12 +1143,3 @@ pub fn deinit() void {
arena.deinit();
Inventory.Sync.ClientSide.deinit();
}
pub fn getToolTypeByID(id: []const u8) ?*const ToolType {
if(toolTypes.getPtr(id)) |result| {
return result;
} else {
std.log.err("Couldn't find item {s}.", .{id});
return null;
}
}