mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 11:17:05 -04:00
Add inventory crafting.
This commit is contained in:
parent
9fdfa04734
commit
da8f6ac9cb
@ -1,33 +1,20 @@
|
|||||||
L = cubyz:oak_log
|
L = cubyz:oak_log
|
||||||
T = cubyz:oak_top
|
T = cubyz:oak_top
|
||||||
P = cubyz:oak_planks
|
P = cubyz:oak_planks
|
||||||
S = cubyz:stick
|
|
||||||
C = cubyz:coal
|
C = cubyz:coal
|
||||||
|
|
||||||
shapeless
|
|
||||||
L
|
L
|
||||||
result 4*P
|
result 4*P
|
||||||
|
|
||||||
shapeless
|
|
||||||
T
|
T
|
||||||
result 3*P
|
result 3*P
|
||||||
|
|
||||||
shaped
|
4*P
|
||||||
P P
|
|
||||||
P P
|
|
||||||
result cubyz:workbench
|
result cubyz:workbench
|
||||||
|
|
||||||
shaped
|
|
||||||
P
|
|
||||||
P
|
|
||||||
result 4*S
|
|
||||||
|
|
||||||
shaped
|
|
||||||
C
|
C
|
||||||
S
|
P
|
||||||
result 8*cubyz:torch
|
result 8*cubyz:torch
|
||||||
|
|
||||||
shaped
|
P
|
||||||
S P S
|
result 2*cubyz:oak_fence
|
||||||
S P S
|
|
||||||
result 6*cubyz:oak_fence
|
|
BIN
assets/cubyz/ui/inventory/crafting_arrow.png
Normal file
BIN
assets/cubyz/ui/inventory/crafting_arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 588 B |
BIN
assets/cubyz/ui/inventory/crafting_result_slot.png
Normal file
BIN
assets/cubyz/ui/inventory/crafting_result_slot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 696 B |
BIN
assets/cubyz/ui/inventory/immutable_slot.png
Normal file
BIN
assets/cubyz/ui/inventory/immutable_slot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 696 B |
165
src/assets.zig
165
src/assets.zig
@ -42,8 +42,30 @@ pub fn readAllJsonFilesInAddons(externalAllocator: Allocator, addons: std.ArrayL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Reads text files recursively from all subfolders.
|
||||||
|
pub fn readAllFilesInAddons(externalAllocator: Allocator, addons: std.ArrayList(std.fs.Dir), subPath: []const u8, output: *std.ArrayList([]const u8)) !void {
|
||||||
|
for(addons.items) |addon| {
|
||||||
|
var dir: std.fs.IterableDir = addon.openIterableDir(subPath, .{}) catch |err| {
|
||||||
|
if(err == error.FileNotFound) continue;
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
defer dir.close();
|
||||||
|
|
||||||
pub fn readAssets(externalAllocator: Allocator, assetPath: []const u8, blocks: *std.StringHashMap(JsonElement), items: *std.StringHashMap(JsonElement), biomes: *std.StringHashMap(JsonElement)) !void {
|
var walker = try dir.walk(main.threadAllocator);
|
||||||
|
defer walker.deinit();
|
||||||
|
|
||||||
|
while(try walker.next()) |entry| {
|
||||||
|
if(entry.kind == .File) {
|
||||||
|
var file = try dir.dir.openFile(entry.path, .{});
|
||||||
|
defer file.close();
|
||||||
|
const string = try file.readToEndAlloc(externalAllocator, std.math.maxInt(usize));
|
||||||
|
try output.append(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readAssets(externalAllocator: Allocator, assetPath: []const u8, blocks: *std.StringHashMap(JsonElement), items: *std.StringHashMap(JsonElement), biomes: *std.StringHashMap(JsonElement), recipes: *std.ArrayList([]const u8)) !void {
|
||||||
var addons = std.ArrayList(std.fs.Dir).init(main.threadAllocator);
|
var addons = std.ArrayList(std.fs.Dir).init(main.threadAllocator);
|
||||||
defer addons.deinit();
|
defer addons.deinit();
|
||||||
var addonNames = std.ArrayList([]const u8).init(main.threadAllocator);
|
var addonNames = std.ArrayList([]const u8).init(main.threadAllocator);
|
||||||
@ -68,6 +90,7 @@ pub fn readAssets(externalAllocator: Allocator, assetPath: []const u8, blocks: *
|
|||||||
try readAllJsonFilesInAddons(externalAllocator, addons, addonNames, "blocks", blocks);
|
try readAllJsonFilesInAddons(externalAllocator, addons, addonNames, "blocks", blocks);
|
||||||
try readAllJsonFilesInAddons(externalAllocator, addons, addonNames, "items", items);
|
try readAllJsonFilesInAddons(externalAllocator, addons, addonNames, "items", items);
|
||||||
try readAllJsonFilesInAddons(externalAllocator, addons, addonNames, "biomes", biomes);
|
try readAllJsonFilesInAddons(externalAllocator, addons, addonNames, "biomes", biomes);
|
||||||
|
try readAllFilesInAddons(externalAllocator, addons, "recipes", recipes);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() !void {
|
pub fn init() !void {
|
||||||
@ -78,7 +101,7 @@ pub fn init() !void {
|
|||||||
commonBiomes = std.StringHashMap(JsonElement).init(arenaAllocator);
|
commonBiomes = std.StringHashMap(JsonElement).init(arenaAllocator);
|
||||||
commonRecipes = std.ArrayList([]const u8).init(arenaAllocator);
|
commonRecipes = std.ArrayList([]const u8).init(arenaAllocator);
|
||||||
|
|
||||||
try readAssets(arenaAllocator, "assets/", &commonBlocks, &commonItems, &commonBiomes);
|
try readAssets(arenaAllocator, "assets/", &commonBlocks, &commonItems, &commonBiomes, &commonRecipes);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn registerItem(assetFolder: []const u8, id: []const u8, json: JsonElement) !*items_zig.BaseItem {
|
fn registerItem(assetFolder: []const u8, id: []const u8, json: JsonElement) !*items_zig.BaseItem {
|
||||||
@ -120,6 +143,10 @@ fn registerBlock(assetFolder: []const u8, id: []const u8, json: JsonElement) !vo
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn registerRecipesFromFile(file: []const u8) !void {
|
||||||
|
try items_zig.registerRecipes(file);
|
||||||
|
}
|
||||||
|
|
||||||
pub const BlockPalette = struct {
|
pub const BlockPalette = struct {
|
||||||
palette: std.ArrayList([]const u8),
|
palette: std.ArrayList([]const u8),
|
||||||
pub fn init(allocator: Allocator, json: JsonElement) !*BlockPalette {
|
pub fn init(allocator: Allocator, json: JsonElement) !*BlockPalette {
|
||||||
@ -181,8 +208,11 @@ pub fn loadWorldAssets(assetFolder: []const u8, palette: *BlockPalette) !void {
|
|||||||
defer items.clearAndFree();
|
defer items.clearAndFree();
|
||||||
var biomes = try commonBiomes.cloneWithAllocator(main.threadAllocator);
|
var biomes = try commonBiomes.cloneWithAllocator(main.threadAllocator);
|
||||||
defer biomes.clearAndFree();
|
defer biomes.clearAndFree();
|
||||||
|
var recipes = std.ArrayList([]const u8).init(main.threadAllocator);
|
||||||
|
try recipes.appendSlice(commonRecipes.items);
|
||||||
|
defer recipes.clearAndFree();
|
||||||
|
|
||||||
try readAssets(arenaAllocator, assetFolder, &blocks, &items, &biomes);
|
try readAssets(arenaAllocator, assetFolder, &blocks, &items, &biomes, &recipes);
|
||||||
|
|
||||||
// blocks:
|
// blocks:
|
||||||
var block: u32 = 0;
|
var block: u32 = 0;
|
||||||
@ -218,6 +248,10 @@ pub fn loadWorldAssets(assetFolder: []const u8, palette: *BlockPalette) !void {
|
|||||||
// block drops:
|
// block drops:
|
||||||
try blocks_zig.registerBlockDrops(blocks);
|
try blocks_zig.registerBlockDrops(blocks);
|
||||||
|
|
||||||
|
for(recipes.items) |recipe| {
|
||||||
|
try registerRecipesFromFile(recipe);
|
||||||
|
}
|
||||||
|
|
||||||
// public void registerBlocks(Registry<DataOrientedRegistry> registries, NoIDRegistry<Ore> oreRegistry, BlockPalette palette) {
|
// public void registerBlocks(Registry<DataOrientedRegistry> registries, NoIDRegistry<Ore> oreRegistry, BlockPalette palette) {
|
||||||
// HashMap<Resource, JsonObject> perWorldBlocks = new HashMap<>(commonBlocks);
|
// HashMap<Resource, JsonObject> perWorldBlocks = new HashMap<>(commonBlocks);
|
||||||
// readAllJsonObjects("blocks", (json, id) -> {
|
// readAllJsonObjects("blocks", (json, id) -> {
|
||||||
@ -406,129 +440,4 @@ pub fn deinit() void {
|
|||||||
// missingDropsItem.clear();
|
// missingDropsItem.clear();
|
||||||
// missingDropsAmount.clear();
|
// missingDropsAmount.clear();
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// public void readRecipes(ArrayList<String[]> recipesList) {
|
|
||||||
// SimpleList<String> lines = new SimpleList<>(new String[1024]);
|
|
||||||
// for (File addon : addons) {
|
|
||||||
// File recipes = new File(addon, "recipes");
|
|
||||||
// if (recipes.exists()) {
|
|
||||||
// for (File file : recipes.listFiles()) {
|
|
||||||
// if (file.isDirectory()) continue;
|
|
||||||
// lines.clear();
|
|
||||||
// try {
|
|
||||||
// BufferedReader buf = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8));
|
|
||||||
// String line;
|
|
||||||
// while ((line = buf.readLine())!= null) {
|
|
||||||
// line = line.replaceAll("//.*", ""); // Ignore comments with "//".
|
|
||||||
// line = line.trim(); // Remove whitespaces before and after the word starts.
|
|
||||||
// if (line.isEmpty()) continue;
|
|
||||||
// lines.add(line);
|
|
||||||
// }
|
|
||||||
// buf.close();
|
|
||||||
// } catch(IOException e) {
|
|
||||||
// Logger.error(e);
|
|
||||||
// }
|
|
||||||
// recipesList.add(lines.toArray());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void registerRecipe(String[] recipe, NoIDRegistry<Recipe> recipeRegistry, Registry<Item> itemRegistry) {
|
|
||||||
// HashMap<String, Item> shortCuts = new HashMap<>();
|
|
||||||
// ArrayList<Item> items = new ArrayList<>();
|
|
||||||
// IntSimpleList itemsPerRow = new IntSimpleList(8);
|
|
||||||
// boolean shaped = false;
|
|
||||||
// boolean startedRecipe = false;
|
|
||||||
// for(int i = 0; i < recipe.length; i++) {
|
|
||||||
// String line = recipe[i];
|
|
||||||
// // shortcuts:
|
|
||||||
// if (line.contains("=")) {
|
|
||||||
// String[] parts = line.split("=");
|
|
||||||
// Item item = itemRegistry.getByID(parts[1].replaceAll("\\s", ""));
|
|
||||||
// if (item == null) {
|
|
||||||
// Logger.warning("Skipping unknown item \"" + parts[1].replaceAll("\\s", "") + "\" in recipe parsing.");
|
|
||||||
// } else {
|
|
||||||
// shortCuts.put(parts[0].replaceAll("\\s", ""), itemRegistry.getByID(parts[1].replaceAll("\\s", ""))); // Remove all whitespaces, wherever they might be. Not necessarily the most robust way, but it should work.
|
|
||||||
// }
|
|
||||||
// } else if (line.startsWith("shaped")) {
|
|
||||||
// // Start of a shaped pattern
|
|
||||||
// shaped = true;
|
|
||||||
// startedRecipe = true;
|
|
||||||
// items.clear();
|
|
||||||
// itemsPerRow.clear();
|
|
||||||
// } else if (line.startsWith("shapeless")) {
|
|
||||||
// // Start of a shapeless pattern
|
|
||||||
// shaped = false;
|
|
||||||
// startedRecipe = true;
|
|
||||||
// items.clear();
|
|
||||||
// itemsPerRow.clear();
|
|
||||||
// } else if (line.startsWith("result") && startedRecipe && !itemsPerRow.isEmpty()) {
|
|
||||||
// // Parse the result, which is made up of `amount*shortcut`.
|
|
||||||
// startedRecipe = false;
|
|
||||||
// String result = line.substring(6).replaceAll("\\s", ""); // Remove "result" and all space-likes.
|
|
||||||
// int number = 1;
|
|
||||||
// if (result.contains("*")) {
|
|
||||||
// String[] parts = result.split("\\*");
|
|
||||||
// result = parts[1];
|
|
||||||
// number = Integer.parseInt(parts[0]);
|
|
||||||
// }
|
|
||||||
// Item item;
|
|
||||||
// if (shortCuts.containsKey(result)) {
|
|
||||||
// item = shortCuts.get(result);
|
|
||||||
// } else {
|
|
||||||
// item = itemRegistry.getByID(result);
|
|
||||||
// }
|
|
||||||
// if (item == null) {
|
|
||||||
// Logger.warning("Skipping recipe with unknown item \"" + result + "\" in recipe parsing.");
|
|
||||||
// } else {
|
|
||||||
// if (shaped) {
|
|
||||||
// int x = CubyzMath.max(itemsPerRow);
|
|
||||||
// int y = itemsPerRow.size;
|
|
||||||
// Item[] array = new Item[x*y];
|
|
||||||
// int index = 0;
|
|
||||||
// for(int iy = 0; iy < itemsPerRow.size; iy++) {
|
|
||||||
// for(int ix = 0; ix < itemsPerRow.array[iy]; ix++) {
|
|
||||||
// array[iy*x + ix] = items.get(index);
|
|
||||||
// index++;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// recipeRegistry.register(new Recipe(x, y, array, number, item));
|
|
||||||
// } else {
|
|
||||||
// recipeRegistry.register(new Recipe(items.toArray(new Item[0]), number, item));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else if (startedRecipe) {
|
|
||||||
// // Parse the actual recipe:
|
|
||||||
// String[] words = line.split("\\s+"); // Split into sections that are divided by any number of whitespace characters.
|
|
||||||
// itemsPerRow.add(words.length);
|
|
||||||
// for(int j = 0; j < words.length; j++) {
|
|
||||||
// Item item;
|
|
||||||
// if (words[j].equals("0")) {
|
|
||||||
// item = null;
|
|
||||||
// } else if (shortCuts.containsKey(words[j])) {
|
|
||||||
// item = shortCuts.get(words[j]);
|
|
||||||
// } else {
|
|
||||||
// item = itemRegistry.getByID(words[j]);
|
|
||||||
// if (item == null) {
|
|
||||||
// startedRecipe = false; // Skip unknown recipes.
|
|
||||||
// Logger.warning("Skipping recipe with unknown item \"" + words[j] + "\" in recipe parsing.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// items.add(item);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public void registerRecipes(NoIDRegistry<Recipe> recipeRegistry, Registry<Item> itemRegistry) {
|
|
||||||
// for(String[] recipe : commonRecipes) {
|
|
||||||
// registerRecipe(recipe, recipeRegistry, itemRegistry);
|
|
||||||
// }
|
|
||||||
// ArrayList<String[]> worldSpecificRecipes = new ArrayList<>();
|
|
||||||
// readRecipes(worldSpecificRecipes);
|
|
||||||
// for(String[] recipe : worldSpecificRecipes) {
|
|
||||||
// registerRecipe(recipe, recipeRegistry, itemRegistry);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
//}
|
110
src/gui/components/CraftingResultSlot.zig
Normal file
110
src/gui/components/CraftingResultSlot.zig
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const main = @import("root");
|
||||||
|
const ItemStack = main.items.ItemStack;
|
||||||
|
const graphics = main.graphics;
|
||||||
|
const draw = graphics.draw;
|
||||||
|
const Texture = graphics.Texture;
|
||||||
|
const TextBuffer = graphics.TextBuffer;
|
||||||
|
const vec = main.vec;
|
||||||
|
const Vec2f = vec.Vec2f;
|
||||||
|
|
||||||
|
const gui = @import("../gui.zig");
|
||||||
|
const GuiComponent = gui.GuiComponent;
|
||||||
|
|
||||||
|
const CraftingResultSlot = @This();
|
||||||
|
|
||||||
|
var texture: Texture = undefined;
|
||||||
|
const border: f32 = 3;
|
||||||
|
|
||||||
|
pos: Vec2f,
|
||||||
|
size: Vec2f = .{32 + 2*border, 32 + 2*border},
|
||||||
|
itemStack: ItemStack,
|
||||||
|
text: TextBuffer,
|
||||||
|
textSize: Vec2f = .{0, 0},
|
||||||
|
onTake: *const fn(usize) bool,
|
||||||
|
userData: usize,
|
||||||
|
hovered: bool = false,
|
||||||
|
pressed: bool = false,
|
||||||
|
|
||||||
|
pub fn __init() !void {
|
||||||
|
texture = try Texture.initFromFile("assets/cubyz/ui/inventory/crafting_result_slot.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __deinit() void {
|
||||||
|
texture.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(pos: Vec2f, itemStack: ItemStack, onTake: *const fn(usize) bool, userData: usize) Allocator.Error!*CraftingResultSlot {
|
||||||
|
std.debug.assert(itemStack.item != null);
|
||||||
|
const self = try gui.allocator.create(CraftingResultSlot);
|
||||||
|
var buf: [16]u8 = undefined;
|
||||||
|
self.* = CraftingResultSlot {
|
||||||
|
.itemStack = itemStack,
|
||||||
|
.pos = pos,
|
||||||
|
.text = try TextBuffer.init(gui.allocator, std.fmt.bufPrint(&buf, "{}", .{self.itemStack.amount}) catch "∞", .{}, false, .right),
|
||||||
|
.onTake = onTake,
|
||||||
|
.userData = userData,
|
||||||
|
};
|
||||||
|
self.textSize = try self.text.calculateLineBreaks(8, self.size[0] - 2*border);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const CraftingResultSlot) void {
|
||||||
|
self.text.deinit();
|
||||||
|
gui.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toComponent(self: *CraftingResultSlot) GuiComponent {
|
||||||
|
return GuiComponent{
|
||||||
|
.craftingResultSlot = self
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateHovered(self: *CraftingResultSlot, _: Vec2f) void {
|
||||||
|
self.hovered = true;
|
||||||
|
gui.hoveredCraftingSlot = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mainButtonPressed(self: *CraftingResultSlot, _: Vec2f) void {
|
||||||
|
self.pressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mainButtonReleased(self: *CraftingResultSlot, mousePosition: Vec2f) void {
|
||||||
|
if(self.pressed) {
|
||||||
|
self.pressed = false;
|
||||||
|
if(GuiComponent.contains(self.pos, self.size, mousePosition)) {
|
||||||
|
if(gui.inventory.carriedItemStack.item == null or std.meta.eql(self.itemStack.item, gui.inventory.carriedItemStack.item)) {
|
||||||
|
if(std.math.add(u16, gui.inventory.carriedItemStack.amount, self.itemStack.amount) catch null) |nextAmount| if(nextAmount <= self.itemStack.item.?.stackSize()) {
|
||||||
|
if(self.onTake(self.userData)) {
|
||||||
|
gui.inventory.carriedItemStack.item = self.itemStack.item;
|
||||||
|
gui.inventory.carriedItemStack.amount += self.itemStack.amount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(self: *CraftingResultSlot, _: Vec2f) !void {
|
||||||
|
draw.setColor(0xffffffff);
|
||||||
|
texture.bindTo(0);
|
||||||
|
draw.boundImage(self.pos, self.size);
|
||||||
|
if(self.itemStack.item) |item| {
|
||||||
|
const itemTexture = try item.getTexture();
|
||||||
|
itemTexture.bindTo(0);
|
||||||
|
draw.boundImage(self.pos + @splat(2, border), self.size - @splat(2, 2*border));
|
||||||
|
if(self.itemStack.amount != 1) {
|
||||||
|
try self.text.render(self.pos[0] + self.size[0] - self.textSize[0] - border, self.pos[1] + self.size[1] - self.textSize[1] - border, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(self.pressed) {
|
||||||
|
draw.setColor(0x80808080);
|
||||||
|
draw.rect(self.pos, self.size);
|
||||||
|
} else if(self.hovered) {
|
||||||
|
self.hovered = false;
|
||||||
|
draw.setColor(0x300000ff);
|
||||||
|
draw.rect(self.pos, self.size);
|
||||||
|
}
|
||||||
|
}
|
48
src/gui/components/Icon.zig
Normal file
48
src/gui/components/Icon.zig
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const main = @import("root");
|
||||||
|
const graphics = main.graphics;
|
||||||
|
const draw = graphics.draw;
|
||||||
|
const Texture = graphics.Texture;
|
||||||
|
const vec = main.vec;
|
||||||
|
const Vec2f = vec.Vec2f;
|
||||||
|
|
||||||
|
const gui = @import("../gui.zig");
|
||||||
|
const GuiComponent = gui.GuiComponent;
|
||||||
|
|
||||||
|
const Icon = @This();
|
||||||
|
|
||||||
|
const fontSize: f32 = 16;
|
||||||
|
|
||||||
|
pos: Vec2f,
|
||||||
|
size: Vec2f,
|
||||||
|
texture: Texture,
|
||||||
|
|
||||||
|
pub fn init(pos: Vec2f, size: Vec2f, texture: Texture) Allocator.Error!*Icon {
|
||||||
|
const self = try gui.allocator.create(Icon);
|
||||||
|
self.* = Icon {
|
||||||
|
.texture = texture,
|
||||||
|
.pos = pos,
|
||||||
|
.size = size,
|
||||||
|
};
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const Icon) void {
|
||||||
|
gui.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toComponent(self: *Icon) GuiComponent {
|
||||||
|
return GuiComponent {
|
||||||
|
.icon = self
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateTexture(self: *Icon, newTexture: Texture) !void {
|
||||||
|
self.texture = newTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(self: *Icon, _: Vec2f) !void {
|
||||||
|
self.texture.render(self.pos, self.size);
|
||||||
|
}
|
70
src/gui/components/ImmutableItemSlot.zig
Normal file
70
src/gui/components/ImmutableItemSlot.zig
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const main = @import("root");
|
||||||
|
const BaseItem = main.items.BaseItem;
|
||||||
|
const graphics = main.graphics;
|
||||||
|
const draw = graphics.draw;
|
||||||
|
const Texture = graphics.Texture;
|
||||||
|
const TextBuffer = graphics.TextBuffer;
|
||||||
|
const vec = main.vec;
|
||||||
|
const Vec2f = vec.Vec2f;
|
||||||
|
|
||||||
|
const gui = @import("../gui.zig");
|
||||||
|
const GuiComponent = gui.GuiComponent;
|
||||||
|
|
||||||
|
const ImmutableItemSlot = @This();
|
||||||
|
|
||||||
|
var texture: Texture = undefined;
|
||||||
|
const border: f32 = 3;
|
||||||
|
|
||||||
|
pos: Vec2f,
|
||||||
|
size: Vec2f = .{32 + 2*border, 32 + 2*border},
|
||||||
|
item: *BaseItem,
|
||||||
|
amount: u32,
|
||||||
|
text: TextBuffer,
|
||||||
|
textSize: Vec2f = .{0, 0},
|
||||||
|
|
||||||
|
pub fn __init() !void {
|
||||||
|
texture = try Texture.initFromFile("assets/cubyz/ui/inventory/immutable_slot.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __deinit() void {
|
||||||
|
texture.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(pos: Vec2f, item: *BaseItem, amount: u32) Allocator.Error!*ImmutableItemSlot {
|
||||||
|
const self = try gui.allocator.create(ImmutableItemSlot);
|
||||||
|
var buf: [16]u8 = undefined;
|
||||||
|
self.* = ImmutableItemSlot {
|
||||||
|
.item = item,
|
||||||
|
.amount = amount,
|
||||||
|
.pos = pos,
|
||||||
|
.text = try TextBuffer.init(gui.allocator, std.fmt.bufPrint(&buf, "{}", .{amount}) catch "∞", .{}, false, .right),
|
||||||
|
};
|
||||||
|
self.textSize = try self.text.calculateLineBreaks(8, self.size[0] - 2*border);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const ImmutableItemSlot) void {
|
||||||
|
self.text.deinit();
|
||||||
|
gui.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toComponent(self: *ImmutableItemSlot) GuiComponent {
|
||||||
|
return GuiComponent{
|
||||||
|
.immutableItemSlot = self
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(self: *ImmutableItemSlot, _: Vec2f) !void {
|
||||||
|
draw.setColor(0xffffffff);
|
||||||
|
texture.bindTo(0);
|
||||||
|
draw.boundImage(self.pos, self.size);
|
||||||
|
const itemTexture = try self.item.getTexture();
|
||||||
|
itemTexture.bindTo(0);
|
||||||
|
draw.boundImage(self.pos + @splat(2, border), self.size - @splat(2, 2*border));
|
||||||
|
if(self.amount != 1) {
|
||||||
|
try self.text.render(self.pos[0] + self.size[0] - self.textSize[0] - border, self.pos[1] + self.size[1] - self.textSize[1] - border, 8);
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,8 @@ const Vec2f = vec.Vec2f;
|
|||||||
|
|
||||||
const Button = @import("components/Button.zig");
|
const Button = @import("components/Button.zig");
|
||||||
const CheckBox = @import("components/CheckBox.zig");
|
const CheckBox = @import("components/CheckBox.zig");
|
||||||
|
const CraftingResultSlot = @import("components/CraftingResultSlot.zig");
|
||||||
|
const ImmutableItemSlot = @import("components/ImmutableItemSlot.zig");
|
||||||
const ItemSlot = @import("components/ItemSlot.zig");
|
const ItemSlot = @import("components/ItemSlot.zig");
|
||||||
const ScrollBar = @import("components/ScrollBar.zig");
|
const ScrollBar = @import("components/ScrollBar.zig");
|
||||||
const Slider = @import("components/Slider.zig");
|
const Slider = @import("components/Slider.zig");
|
||||||
@ -25,12 +27,14 @@ var hudWindows: std.ArrayList(*GuiWindow) = undefined;
|
|||||||
pub var openWindows: std.ArrayList(*GuiWindow) = undefined;
|
pub var openWindows: std.ArrayList(*GuiWindow) = undefined;
|
||||||
pub var selectedWindow: ?*GuiWindow = null; // TODO: Make private.
|
pub var selectedWindow: ?*GuiWindow = null; // TODO: Make private.
|
||||||
pub var selectedTextInput: ?*TextInput = null;
|
pub var selectedTextInput: ?*TextInput = null;
|
||||||
|
var hoveredAWindow: bool = false;
|
||||||
|
|
||||||
pub var allocator: Allocator = undefined;
|
pub var allocator: Allocator = undefined;
|
||||||
|
|
||||||
pub var scale: f32 = undefined;
|
pub var scale: f32 = undefined;
|
||||||
|
|
||||||
pub var hoveredItemSlot: ?*ItemSlot = null;
|
pub var hoveredItemSlot: ?*ItemSlot = null;
|
||||||
|
pub var hoveredCraftingSlot: ?*CraftingResultSlot = null;
|
||||||
|
|
||||||
pub fn init(_allocator: Allocator) !void {
|
pub fn init(_allocator: Allocator) !void {
|
||||||
allocator = _allocator;
|
allocator = _allocator;
|
||||||
@ -53,6 +57,8 @@ pub fn init(_allocator: Allocator) !void {
|
|||||||
try GuiWindow.__init();
|
try GuiWindow.__init();
|
||||||
try Button.__init();
|
try Button.__init();
|
||||||
try CheckBox.__init();
|
try CheckBox.__init();
|
||||||
|
try CraftingResultSlot.__init();
|
||||||
|
try ImmutableItemSlot.__init();
|
||||||
try ItemSlot.__init();
|
try ItemSlot.__init();
|
||||||
try ScrollBar.__init();
|
try ScrollBar.__init();
|
||||||
try Slider.__init();
|
try Slider.__init();
|
||||||
@ -74,6 +80,8 @@ pub fn deinit() void {
|
|||||||
GuiWindow.__deinit();
|
GuiWindow.__deinit();
|
||||||
Button.__deinit();
|
Button.__deinit();
|
||||||
CheckBox.__deinit();
|
CheckBox.__deinit();
|
||||||
|
CraftingResultSlot.__deinit();
|
||||||
|
ImmutableItemSlot.__deinit();
|
||||||
ItemSlot.__deinit();
|
ItemSlot.__deinit();
|
||||||
ScrollBar.__deinit();
|
ScrollBar.__deinit();
|
||||||
Slider.__deinit();
|
Slider.__deinit();
|
||||||
@ -352,7 +360,12 @@ pub fn mainButtonPressed() void {
|
|||||||
inventory.update() catch |err| {
|
inventory.update() catch |err| {
|
||||||
std.log.err("Encountered error while updating inventory: {s}", .{@errorName(err)});
|
std.log.err("Encountered error while updating inventory: {s}", .{@errorName(err)});
|
||||||
};
|
};
|
||||||
if(inventory.carriedItemStack.amount != 0) return;
|
if(inventory.carriedItemStack.amount != 0) {
|
||||||
|
if(hoveredCraftingSlot) |hovered| {
|
||||||
|
hovered.mainButtonPressed(undefined);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
selectedWindow = null;
|
selectedWindow = null;
|
||||||
selectedTextInput = null;
|
selectedTextInput = null;
|
||||||
var selectedI: usize = 0;
|
var selectedI: usize = 0;
|
||||||
@ -423,17 +436,20 @@ pub fn updateWindowPositions() void {
|
|||||||
|
|
||||||
pub fn updateAndRenderGui() !void {
|
pub fn updateAndRenderGui() !void {
|
||||||
const mousePos = main.Window.getMousePosition()/@splat(2, scale);
|
const mousePos = main.Window.getMousePosition()/@splat(2, scale);
|
||||||
|
hoveredAWindow = false;
|
||||||
if(!main.Window.grabbed) {
|
if(!main.Window.grabbed) {
|
||||||
if(selectedWindow) |selected| {
|
if(selectedWindow) |selected| {
|
||||||
try selected.updateSelected(mousePos);
|
try selected.updateSelected(mousePos);
|
||||||
}
|
}
|
||||||
hoveredItemSlot = null;
|
hoveredItemSlot = null;
|
||||||
|
hoveredCraftingSlot = null;
|
||||||
var i: usize = openWindows.items.len;
|
var i: usize = openWindows.items.len;
|
||||||
while(i != 0) {
|
while(i != 0) {
|
||||||
i -= 1;
|
i -= 1;
|
||||||
const window: *GuiWindow = openWindows.items[i];
|
const window: *GuiWindow = openWindows.items[i];
|
||||||
if(GuiComponent.contains(window.pos, window.size, mousePos)) {
|
if(GuiComponent.contains(window.pos, window.size, mousePos)) {
|
||||||
try window.updateHovered(mousePos);
|
try window.updateHovered(mousePos);
|
||||||
|
hoveredAWindow = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,9 +466,9 @@ pub fn updateAndRenderGui() !void {
|
|||||||
try inventory.render(mousePos);
|
try inventory.render(mousePos);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inventory = struct {
|
pub const inventory = struct {
|
||||||
const ItemStack = main.items.ItemStack;
|
const ItemStack = main.items.ItemStack;
|
||||||
var carriedItemStack: ItemStack = .{.item = null, .amount = 0};
|
pub var carriedItemStack: ItemStack = .{.item = null, .amount = 0};
|
||||||
var carriedItemSlot: *ItemSlot = undefined;
|
var carriedItemSlot: *ItemSlot = undefined;
|
||||||
var deliveredItemStacks: std.ArrayList(*ItemStack) = undefined;
|
var deliveredItemStacks: std.ArrayList(*ItemStack) = undefined;
|
||||||
var deliveredItemStacksOldAmount: std.ArrayList(u16) = undefined;
|
var deliveredItemStacksOldAmount: std.ArrayList(u16) = undefined;
|
||||||
@ -547,7 +563,7 @@ const inventory = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if(!hoveredAWindow) {
|
||||||
if(leftClick or carriedItemStack.amount == 1) {
|
if(leftClick or carriedItemStack.amount == 1) {
|
||||||
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, carriedItemStack, vec.floatCast(f32, main.game.Player.getPosBlocking()), main.game.camera.direction, 20) catch |err| {
|
main.network.Protocols.genericUpdate.itemStackDrop(main.game.world.?.conn, carriedItemStack, vec.floatCast(f32, main.game.Player.getPosBlocking()), main.game.camera.direction, 20) catch |err| {
|
||||||
std.log.err("Error while dropping itemStack: {s}", .{@errorName(err)});
|
std.log.err("Error while dropping itemStack: {s}", .{@errorName(err)});
|
||||||
|
@ -8,19 +8,25 @@ pub const GuiComponent = union(enum) {
|
|||||||
|
|
||||||
pub const Button = @import("components/Button.zig");
|
pub const Button = @import("components/Button.zig");
|
||||||
pub const CheckBox = @import("components/CheckBox.zig");
|
pub const CheckBox = @import("components/CheckBox.zig");
|
||||||
|
pub const CraftingResultSlot = @import("components/CraftingResultSlot.zig");
|
||||||
pub const HorizontalList = @import("components/HorizontalList.zig");
|
pub const HorizontalList = @import("components/HorizontalList.zig");
|
||||||
|
pub const Icon = @import("components/Icon.zig");
|
||||||
|
pub const ImmutableItemSlot = @import("components/ImmutableItemSlot.zig");
|
||||||
pub const ItemSlot = @import("components/ItemSlot.zig");
|
pub const ItemSlot = @import("components/ItemSlot.zig");
|
||||||
pub const Label = @import("components/Label.zig");
|
pub const Label = @import("components/Label.zig");
|
||||||
pub const MutexComponent = @import("components/MutexComponent.zig");
|
pub const MutexComponent = @import("components/MutexComponent.zig");
|
||||||
pub const Slider = @import("components/Slider.zig");
|
|
||||||
pub const ScrollBar = @import("components/ScrollBar.zig");
|
pub const ScrollBar = @import("components/ScrollBar.zig");
|
||||||
|
pub const Slider = @import("components/Slider.zig");
|
||||||
pub const TextInput = @import("components/TextInput.zig");
|
pub const TextInput = @import("components/TextInput.zig");
|
||||||
pub const VerticalList = @import("components/VerticalList.zig");
|
pub const VerticalList = @import("components/VerticalList.zig");
|
||||||
|
|
||||||
|
|
||||||
button: *Button,
|
button: *Button,
|
||||||
checkBox: *CheckBox,
|
checkBox: *CheckBox,
|
||||||
|
craftingResultSlot: *CraftingResultSlot,
|
||||||
horizontalList: *HorizontalList,
|
horizontalList: *HorizontalList,
|
||||||
|
icon: *Icon,
|
||||||
|
immutableItemSlot: *ImmutableItemSlot,
|
||||||
itemSlot: *ItemSlot,
|
itemSlot: *ItemSlot,
|
||||||
label: *Label,
|
label: *Label,
|
||||||
mutexComponent: *MutexComponent,
|
mutexComponent: *MutexComponent,
|
||||||
|
@ -8,6 +8,7 @@ pub const graphics = @import("graphics.zig");
|
|||||||
pub const healthbar = @import("healthbar.zig");
|
pub const healthbar = @import("healthbar.zig");
|
||||||
pub const hotbar = @import("hotbar.zig");
|
pub const hotbar = @import("hotbar.zig");
|
||||||
pub const inventory = @import("inventory.zig");
|
pub const inventory = @import("inventory.zig");
|
||||||
|
pub const inventory_crafting = @import("inventory_crafting.zig");
|
||||||
pub const main = @import("main.zig");
|
pub const main = @import("main.zig");
|
||||||
pub const multiplayer = @import("multiplayer.zig");
|
pub const multiplayer = @import("multiplayer.zig");
|
||||||
pub const settings = @import("settings.zig");
|
pub const settings = @import("settings.zig");
|
||||||
|
@ -8,6 +8,7 @@ const Vec2f = main.vec.Vec2f;
|
|||||||
const gui = @import("../gui.zig");
|
const gui = @import("../gui.zig");
|
||||||
const GuiComponent = gui.GuiComponent;
|
const GuiComponent = gui.GuiComponent;
|
||||||
const GuiWindow = gui.GuiWindow;
|
const GuiWindow = gui.GuiWindow;
|
||||||
|
const Button = GuiComponent.Button;
|
||||||
const HorizontalList = GuiComponent.HorizontalList;
|
const HorizontalList = GuiComponent.HorizontalList;
|
||||||
const VerticalList = GuiComponent.VerticalList;
|
const VerticalList = GuiComponent.VerticalList;
|
||||||
const ItemSlot = GuiComponent.ItemSlot;
|
const ItemSlot = GuiComponent.ItemSlot;
|
||||||
@ -19,15 +20,20 @@ pub var window = GuiWindow {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const padding: f32 = 8;
|
const padding: f32 = 8;
|
||||||
|
|
||||||
pub fn onOpen() Allocator.Error!void {
|
pub fn onOpen() Allocator.Error!void {
|
||||||
var list = try VerticalList.init(.{padding, padding + 16}, 300, 0);
|
var list = try VerticalList.init(.{padding, padding + 16}, 300, 0);
|
||||||
// TODO: Crafting.
|
// Some miscellanious slots and buttons:
|
||||||
|
// TODO: armor slots, backpack slot + stack-based backpack inventory, other items maybe?
|
||||||
|
{
|
||||||
|
var row = try HorizontalList.init();
|
||||||
|
try row.add(try Button.init(.{0, 0}, 64, "Crafting", gui.openWindowFunction("cubyz:inventory_crafting"))); // TODO: Replace the text with an icon
|
||||||
|
try list.add(row);
|
||||||
|
}
|
||||||
// Inventory:
|
// Inventory:
|
||||||
for(1..4) |y| {
|
for(1..4) |y| {
|
||||||
var row = try HorizontalList.init();
|
var row = try HorizontalList.init();
|
||||||
for(0..8) |x| {
|
for(0..8) |x| {
|
||||||
try row.add(try ItemSlot.init(.{0, 0}, &Player.inventory__SEND_CHANGES_TO_SERVER.items[y*8 + x]));
|
try row.add(try ItemSlot.init(.{0, 0}, &Player.inventory__SEND_CHANGES_TO_SERVER.items[y*8 + x])); // TODO: Update server
|
||||||
}
|
}
|
||||||
try list.add(row);
|
try list.add(row);
|
||||||
}
|
}
|
||||||
|
173
src/gui/windows/inventory_crafting.zig
Normal file
173
src/gui/windows/inventory_crafting.zig
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const main = @import("root");
|
||||||
|
const items = main.items;
|
||||||
|
const BaseItem = items.BaseItem;
|
||||||
|
const ItemStack = items.ItemStack;
|
||||||
|
const Player = main.game.Player;
|
||||||
|
const Texture = main.graphics.Texture;
|
||||||
|
const Vec2f = main.vec.Vec2f;
|
||||||
|
|
||||||
|
const gui = @import("../gui.zig");
|
||||||
|
const GuiComponent = gui.GuiComponent;
|
||||||
|
const GuiWindow = gui.GuiWindow;
|
||||||
|
const Button = GuiComponent.Button;
|
||||||
|
const HorizontalList = GuiComponent.HorizontalList;
|
||||||
|
const VerticalList = GuiComponent.VerticalList;
|
||||||
|
const Icon = GuiComponent.Icon;
|
||||||
|
const CraftingResultSlot = GuiComponent.CraftingResultSlot;
|
||||||
|
const ImmutableItemSlot = GuiComponent.ImmutableItemSlot;
|
||||||
|
|
||||||
|
pub var window = GuiWindow {
|
||||||
|
.contentSize = Vec2f{64*8, 64*4},
|
||||||
|
.title = "Crafting",
|
||||||
|
.id = "cubyz:inventory_crafting",
|
||||||
|
};
|
||||||
|
|
||||||
|
const padding: f32 = 8;
|
||||||
|
|
||||||
|
var availableItems: std.ArrayList(*BaseItem) = undefined;
|
||||||
|
var itemAmount: std.ArrayList(u32) = undefined;
|
||||||
|
|
||||||
|
var arrowTexture: Texture = undefined;
|
||||||
|
var recipeResult: ItemStack = undefined;
|
||||||
|
|
||||||
|
pub fn init() !void {
|
||||||
|
arrowTexture = try Texture.initFromFile("assets/cubyz/ui/inventory/crafting_arrow.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit() void {
|
||||||
|
arrowTexture.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addItemStackToAvailable(itemStack: ItemStack) Allocator.Error!void {
|
||||||
|
if(itemStack.item) |item| {
|
||||||
|
if(item == .baseItem) {
|
||||||
|
const baseItem = item.baseItem;
|
||||||
|
for(availableItems.items, 0..) |alreadyPresent, i| {
|
||||||
|
if(baseItem == alreadyPresent) {
|
||||||
|
itemAmount.items[i] += itemStack.amount;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try availableItems.append(baseItem);
|
||||||
|
try itemAmount.append(itemStack.amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn onTake(recipeIndex: usize) bool {
|
||||||
|
const recipe = items.recipes()[recipeIndex];
|
||||||
|
for(recipe.sourceItems, recipe.sourceAmounts) |item, _amount| {
|
||||||
|
var amount: u32 = _amount;
|
||||||
|
for(main.game.Player.inventory__SEND_CHANGES_TO_SERVER.items) |*itemStack| {
|
||||||
|
if(itemStack.item) |invItem| {
|
||||||
|
if(invItem == .baseItem and invItem.baseItem == item) {
|
||||||
|
if(amount >= itemStack.amount) {
|
||||||
|
amount -= itemStack.amount;
|
||||||
|
itemStack.clear();
|
||||||
|
} else {
|
||||||
|
itemStack.amount -= @intCast(u16, amount);
|
||||||
|
amount = 0;
|
||||||
|
}
|
||||||
|
if(amount == 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(amount != 0) {
|
||||||
|
std.log.warn("Congratulations, you just managed to cheat {}*{s}, thanks to my lazy coding. Have fun with that :D", .{amount, item.id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findAvailableRecipes(list: *VerticalList) Allocator.Error!bool {
|
||||||
|
const oldAmounts = try main.threadAllocator.dupe(u32, itemAmount.items);
|
||||||
|
defer main.threadAllocator.free(oldAmounts);
|
||||||
|
for(itemAmount.items) |*amount| {
|
||||||
|
amount.* = 0;
|
||||||
|
}
|
||||||
|
// Figure out what items are available in the inventory:
|
||||||
|
for(main.game.Player.inventory__SEND_CHANGES_TO_SERVER.items) |itemStack| {
|
||||||
|
try addItemStackToAvailable(itemStack);
|
||||||
|
}
|
||||||
|
try addItemStackToAvailable(gui.inventory.carriedItemStack);
|
||||||
|
if(std.mem.eql(u32, oldAmounts, itemAmount.items)) return false;
|
||||||
|
// Remove no longer present items:
|
||||||
|
var i: u32 = 0;
|
||||||
|
while(i < availableItems.items.len) : (i += 1) {
|
||||||
|
if(itemAmount.items[i] == 0) {
|
||||||
|
_ = itemAmount.swapRemove(i);
|
||||||
|
_ = availableItems.swapRemove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Find all recipes the player can make:
|
||||||
|
outer: for(items.recipes(), 0..) |*recipe, recipeIndex| {
|
||||||
|
middle: for(recipe.sourceItems, recipe.sourceAmounts) |sourceItem, sourceAmount| {
|
||||||
|
for(availableItems.items, itemAmount.items) |availableItem, availableAmount| {
|
||||||
|
if(availableItem == sourceItem and availableAmount >= sourceAmount) {
|
||||||
|
continue :middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue :outer; // Ingredient not found.
|
||||||
|
}
|
||||||
|
// All ingredients found: Add it to the list.
|
||||||
|
var rowList = try HorizontalList.init();
|
||||||
|
const maxColumns: u32 = 4;
|
||||||
|
const itemsPerColumn = recipe.sourceItems.len/maxColumns;
|
||||||
|
const remainder = recipe.sourceItems.len%maxColumns;
|
||||||
|
i = 0;
|
||||||
|
for(0..maxColumns) |col| {
|
||||||
|
var itemsThisColumn = itemsPerColumn;
|
||||||
|
if(col < remainder) itemsThisColumn += 1;
|
||||||
|
var columnList = try VerticalList.init(.{0, 0}, std.math.inf(f32), 0);
|
||||||
|
for(0..itemsThisColumn) |_| {
|
||||||
|
try columnList.add(try ImmutableItemSlot.init(.{0, 0}, recipe.sourceItems[i], recipe.sourceAmounts[i]));
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
columnList.finish(.center);
|
||||||
|
try rowList.add(columnList);
|
||||||
|
}
|
||||||
|
try rowList.add(try Icon.init(.{8, 0}, .{32, 32}, arrowTexture));
|
||||||
|
const itemSlot = try CraftingResultSlot.init(.{8, 0}, recipe.resultItem, &onTake, recipeIndex);
|
||||||
|
try rowList.add(itemSlot);
|
||||||
|
rowList.finish(.{0, 0}, .center);
|
||||||
|
try list.add(rowList);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh() Allocator.Error!void {
|
||||||
|
var list = try VerticalList.init(.{padding, padding + 16}, 300, 8);
|
||||||
|
if(!try findAvailableRecipes(list)) {
|
||||||
|
list.deinit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(window.rootComponent) |*comp| {
|
||||||
|
comp.deinit();
|
||||||
|
}
|
||||||
|
list.finish(.center);
|
||||||
|
window.rootComponent = list.toComponent();
|
||||||
|
window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @splat(2, padding);
|
||||||
|
gui.updateWindowPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onOpen() Allocator.Error!void {
|
||||||
|
availableItems = std.ArrayList(*BaseItem).init(main.globalAllocator);
|
||||||
|
itemAmount = std.ArrayList(u32).init(main.globalAllocator);
|
||||||
|
try refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onClose() void {
|
||||||
|
if(window.rootComponent) |*comp| {
|
||||||
|
comp.deinit();
|
||||||
|
window.rootComponent = null;
|
||||||
|
}
|
||||||
|
availableItems.deinit();
|
||||||
|
itemAmount.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update() Allocator.Error!void {
|
||||||
|
try refresh();
|
||||||
|
}
|
102
src/items.zig
102
src/items.zig
@ -79,6 +79,17 @@ pub const BaseItem = struct {
|
|||||||
block: ?u16,
|
block: ?u16,
|
||||||
foodValue: f32, // TODO: Effects.
|
foodValue: f32, // TODO: Effects.
|
||||||
|
|
||||||
|
var unobtainable = BaseItem {
|
||||||
|
.image = graphics.Image.defaultImage,
|
||||||
|
.texture = null,
|
||||||
|
.id = "unobtainable",
|
||||||
|
.name = "unobtainable",
|
||||||
|
.stackSize = 0,
|
||||||
|
.material = null,
|
||||||
|
.block = null,
|
||||||
|
.foodValue = 0,
|
||||||
|
};
|
||||||
|
|
||||||
fn init(self: *BaseItem, allocator: Allocator, texturePath: []const u8, replacementTexturePath: []const u8, id: []const u8, json: JsonElement) !void {
|
fn init(self: *BaseItem, allocator: Allocator, texturePath: []const u8, replacementTexturePath: []const u8, id: []const u8, json: JsonElement) !void {
|
||||||
self.id = try allocator.dupe(u8, id);
|
self.id = try allocator.dupe(u8, id);
|
||||||
if(texturePath.len == 0) {
|
if(texturePath.len == 0) {
|
||||||
@ -113,7 +124,7 @@ pub const BaseItem = struct {
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getTexture(self: *BaseItem) !graphics.Texture {
|
pub fn getTexture(self: *BaseItem) !graphics.Texture {
|
||||||
if(self.texture == null) {
|
if(self.texture == null) {
|
||||||
if(self.block) |blockType| {
|
if(self.block) |blockType| {
|
||||||
const c = graphics.c;
|
const c = graphics.c;
|
||||||
@ -1344,18 +1355,31 @@ pub const Inventory = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Recipe = struct {
|
||||||
|
sourceItems: []*BaseItem,
|
||||||
|
sourceAmounts: []u32,
|
||||||
|
resultItem: ItemStack,
|
||||||
|
};
|
||||||
|
|
||||||
var arena: std.heap.ArenaAllocator = undefined;
|
var arena: std.heap.ArenaAllocator = undefined;
|
||||||
var reverseIndices: std.StringHashMap(*BaseItem) = undefined;
|
var reverseIndices: std.StringHashMap(*BaseItem) = undefined;
|
||||||
var itemList: [65536]BaseItem = undefined;
|
var itemList: [65536]BaseItem = undefined;
|
||||||
var itemListSize: u16 = 0;
|
var itemListSize: u16 = 0;
|
||||||
|
|
||||||
|
var recipeList: std.ArrayList(Recipe) = undefined;
|
||||||
|
|
||||||
pub fn iterator() std.StringHashMap(*BaseItem).ValueIterator {
|
pub fn iterator() std.StringHashMap(*BaseItem).ValueIterator {
|
||||||
return reverseIndices.valueIterator();
|
return reverseIndices.valueIterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn recipes() []Recipe {
|
||||||
|
return recipeList.items;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn globalInit() void {
|
pub fn globalInit() void {
|
||||||
arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
reverseIndices = std.StringHashMap(*BaseItem).init(arena.allocator());
|
reverseIndices = std.StringHashMap(*BaseItem).init(arena.allocator());
|
||||||
|
recipeList = std.ArrayList(Recipe).init(arena.allocator());
|
||||||
itemListSize = 0;
|
itemListSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1371,8 +1395,83 @@ pub fn register(_: []const u8, texturePath: []const u8, replacementTexturePath:
|
|||||||
return newItem;
|
return newItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn registerRecipes(file: []const u8) !void {
|
||||||
|
var shortcuts = std.StringHashMap(*BaseItem).init(main.threadAllocator);
|
||||||
|
defer shortcuts.deinit();
|
||||||
|
defer {
|
||||||
|
var keyIterator = shortcuts.keyIterator();
|
||||||
|
while(keyIterator.next()) |key| {
|
||||||
|
main.threadAllocator.free(key.*);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var items = std.ArrayList(*BaseItem).init(main.threadAllocator);
|
||||||
|
defer items.deinit();
|
||||||
|
var itemAmounts = std.ArrayList(u32).init(main.threadAllocator);
|
||||||
|
defer itemAmounts.deinit();
|
||||||
|
var string = std.ArrayList(u8).init(main.threadAllocator);
|
||||||
|
defer string.deinit();
|
||||||
|
var lines = std.mem.split(u8, file, "\n");
|
||||||
|
while(lines.next()) |line| {
|
||||||
|
// shortcuts:
|
||||||
|
if(std.mem.containsAtLeast(u8, line, 1, "=")) {
|
||||||
|
var parts = std.mem.split(u8, line, "=");
|
||||||
|
for(parts.first()) |char| {
|
||||||
|
if(std.ascii.isWhitespace(char)) continue; // TODO: Unicode whitespaces
|
||||||
|
try string.append(char);
|
||||||
|
}
|
||||||
|
const shortcut = try string.toOwnedSlice();
|
||||||
|
const id = std.mem.trim(u8, parts.rest(), &std.ascii.whitespace); // TODO: Unicode whitespaces
|
||||||
|
const item = getByID(id) orelse shortcuts.get(id) orelse &BaseItem.unobtainable;
|
||||||
|
try shortcuts.put(shortcut, item);
|
||||||
|
} else if(std.mem.startsWith(u8, line, "result") and items.items.len != 0) {
|
||||||
|
defer items.clearAndFree();
|
||||||
|
defer itemAmounts.clearAndFree();
|
||||||
|
var id = line["result".len..];
|
||||||
|
var amount: u16 = 1;
|
||||||
|
if(std.mem.containsAtLeast(u8, id, 1, "*")) {
|
||||||
|
var parts = std.mem.split(u8, id, "*");
|
||||||
|
const amountString = std.mem.trim(u8, parts.first(), &std.ascii.whitespace); // TODO: Unicode whitespaces
|
||||||
|
amount = std.fmt.parseInt(u16, amountString, 0) catch 1;
|
||||||
|
id = parts.rest();
|
||||||
|
}
|
||||||
|
id = std.mem.trim(u8, id, &std.ascii.whitespace); // TODO: Unicode whitespaces
|
||||||
|
const item = getByID(id) orelse shortcuts.get(id) orelse continue;
|
||||||
|
const recipe = Recipe {
|
||||||
|
.sourceItems = try arena.allocator().dupe(*BaseItem, items.items),
|
||||||
|
.sourceAmounts = try arena.allocator().dupe(u32, itemAmounts.items),
|
||||||
|
.resultItem = ItemStack{.item = Item{.baseItem = item}, .amount = amount},
|
||||||
|
};
|
||||||
|
try recipeList.append(recipe);
|
||||||
|
} else {
|
||||||
|
var ingredients = std.mem.split(u8, line, ",");
|
||||||
|
outer: while(ingredients.next()) |ingredient| {
|
||||||
|
var id = ingredient;
|
||||||
|
var amount: u16 = 1;
|
||||||
|
if(std.mem.containsAtLeast(u8, id, 1, "*")) {
|
||||||
|
var parts = std.mem.split(u8, id, "*");
|
||||||
|
const amountString = std.mem.trim(u8, parts.first(), &std.ascii.whitespace); // TODO: Unicode whitespaces
|
||||||
|
amount = std.fmt.parseInt(u16, amountString, 0) catch 1;
|
||||||
|
id = parts.rest();
|
||||||
|
}
|
||||||
|
id = std.mem.trim(u8, id, &std.ascii.whitespace); // TODO: Unicode whitespaces
|
||||||
|
const item = getByID(id) orelse shortcuts.get(id) orelse continue;
|
||||||
|
// Resolve duplicates:
|
||||||
|
for(items.items, 0..) |presentItem, i| {
|
||||||
|
if(presentItem == item) {
|
||||||
|
itemAmounts.items[i] += amount;
|
||||||
|
continue :outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try items.append(item);
|
||||||
|
try itemAmounts.append(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset() void {
|
pub fn reset() void {
|
||||||
reverseIndices.clearAndFree();
|
reverseIndices.clearAndFree();
|
||||||
|
recipeList.clearAndFree();
|
||||||
itemListSize = 0;
|
itemListSize = 0;
|
||||||
// TODO: Use arena.reset() instead.
|
// TODO: Use arena.reset() instead.
|
||||||
arena.deinit();
|
arena.deinit();
|
||||||
@ -1381,6 +1480,7 @@ pub fn reset() void {
|
|||||||
|
|
||||||
pub fn deinit() void {
|
pub fn deinit() void {
|
||||||
reverseIndices.clearAndFree();
|
reverseIndices.clearAndFree();
|
||||||
|
recipeList.clearAndFree();
|
||||||
arena.deinit();
|
arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user