Crafting Overhaul: 20+ recipes, Log->Planks, proper stack/split mechanics - v2.1.9

This commit is contained in:
Michael Howard 2026-04-24 16:06:24 -05:00
parent b200e9e07f
commit 910e2d6a6e
6 changed files with 208 additions and 41 deletions

Binary file not shown.

View File

@ -1 +1 @@
v2.1.8 v2.1.9

Binary file not shown.

View File

@ -1 +1 @@
v2.1.8 v2.1.9

View File

@ -25,7 +25,10 @@
enum BlockType { enum BlockType {
AIR = 0, GRASS = 1, DIRT = 2, COBBLESTONE = 3, LOG = 4, LEAVES = 5, PLANK = 6, AIR = 0, GRASS = 1, DIRT = 2, COBBLESTONE = 3, LOG = 4, LEAVES = 5, PLANK = 6,
STONE = 7, BEDROCK = 8, DIAMOND_ORE = 9, IRON_ORE = 10, GRAVEL = 11, CRAFTING_TABLE = 12, SAND = 13, STONE = 7, BEDROCK = 8, DIAMOND_ORE = 9, IRON_ORE = 10, GRAVEL = 11, CRAFTING_TABLE = 12, SAND = 13,
STICK = 14, WOOD_AXE = 15 STICK = 14, WOOD_AXE = 15,
WOOD_PICKAXE = 16, WOOD_SWORD = 17, WOOD_SHOVEL = 18, WOOD_HOE = 19,
STONE_AXE = 20, STONE_PICKAXE = 21, STONE_SWORD = 22, STONE_SHOVEL = 23, STONE_HOE = 24,
FURNACE = 25, CHEST = 26, LADDER = 27, FENCE = 28, TORCH = 29, DOOR = 30, STONE_SLAB = 31
}; };
std::string GetBlockName(int type) { std::string GetBlockName(int type) {
@ -46,6 +49,22 @@ std::string GetBlockName(int type) {
case SAND: return "Sand"; case SAND: return "Sand";
case STICK: return "Stick"; case STICK: return "Stick";
case WOOD_AXE: return "Wooden Axe"; case WOOD_AXE: return "Wooden Axe";
case WOOD_PICKAXE: return "Wooden Pickaxe";
case WOOD_SWORD: return "Wooden Sword";
case WOOD_SHOVEL: return "Wooden Shovel";
case WOOD_HOE: return "Wooden Hoe";
case STONE_AXE: return "Stone Axe";
case STONE_PICKAXE: return "Stone Pickaxe";
case STONE_SWORD: return "Stone Sword";
case STONE_SHOVEL: return "Stone Shovel";
case STONE_HOE: return "Stone Hoe";
case FURNACE: return "Furnace";
case CHEST: return "Chest";
case LADDER: return "Ladder";
case FENCE: return "Fence";
case TORCH: return "Torch";
case DOOR: return "Wooden Door";
case STONE_SLAB: return "Stone Slab";
default: return "Unknown Item"; default: return "Unknown Item";
} }
} }
@ -99,7 +118,7 @@ struct Chunk {
bool generated = false; bool generated = false;
bool modified = false; bool modified = false;
bool dirty = true; bool dirty = true;
std::vector<Vector3> renderLists[16]; std::vector<Vector3> renderLists[32];
Chunk() : generated(false), modified(false), maxY(0), dirty(true) {} Chunk() : generated(false), modified(false), maxY(0), dirty(true) {}
}; };
@ -245,7 +264,7 @@ int GetBlock(int x, int y, int z) {
} }
void RebuildChunkRenderList(Chunk* chunk, int cx, int cz) { void RebuildChunkRenderList(Chunk* chunk, int cx, int cz) {
for (int i = 0; i < 16; i++) chunk->renderLists[i].clear(); for (int i = 0; i < 32; i++) chunk->renderLists[i].clear();
// Neighbors for exposure check // Neighbors for exposure check
auto itNM = worldChunks.find({cx-1, cz}); auto itNM = worldChunks.find({cx-1, cz});
@ -940,7 +959,7 @@ int main(void)
camera3D.projection = CAMERA_PERSPECTIVE; camera3D.projection = CAMERA_PERSPECTIVE;
// 3D Block Textures Setup // 3D Block Textures Setup
Texture2D blockTextures[32] = {0}; Texture2D blockTextures[64] = {0};
blockTextures[DIRT] = LoadTexture("assets/dirt.png"); blockTextures[DIRT] = LoadTexture("assets/dirt.png");
blockTextures[GRASS] = LoadTexture("assets/grass.png"); blockTextures[GRASS] = LoadTexture("assets/grass.png");
blockTextures[COBBLESTONE] = LoadTexture("assets/cobblestone.png"); blockTextures[COBBLESTONE] = LoadTexture("assets/cobblestone.png");
@ -986,48 +1005,179 @@ int main(void)
InventorySlot tableResult(AIR, 0); InventorySlot tableResult(AIR, 0);
auto UpdateCrafting = [&]() { auto UpdateCrafting = [&]() {
// Count non-empty slots
int filledSlots = 0;
for (int i = 0; i < 4; i++) {
if (craftingSlots[i].blockType != AIR) filledSlots++;
}
// Recipe: 1 Log -> 4 Planks (single slot, any position)
if (filledSlots == 1) {
for (int i = 0; i < 4; i++) {
if (craftingSlots[i].blockType == LOG) {
craftingResult = InventorySlot(PLANK, 4);
return;
}
}
}
// Recipe: 4 Planks -> 1 Crafting Table // Recipe: 4 Planks -> 1 Crafting Table
if (craftingSlots[0].blockType == PLANK && craftingSlots[1].blockType == PLANK && if (craftingSlots[0].blockType == PLANK && craftingSlots[1].blockType == PLANK &&
craftingSlots[2].blockType == PLANK && craftingSlots[3].blockType == PLANK) { craftingSlots[2].blockType == PLANK && craftingSlots[3].blockType == PLANK) {
craftingResult = InventorySlot(CRAFTING_TABLE, 1); craftingResult = InventorySlot(CRAFTING_TABLE, 1);
return;
} }
// Recipe: 2 Planks (vertical) -> 4 Sticks // Recipe: 2 Planks (vertical) -> 4 Sticks
else if ((craftingSlots[0].blockType == PLANK && craftingSlots[2].blockType == PLANK) || if ((craftingSlots[0].blockType == PLANK && craftingSlots[2].blockType == PLANK &&
(craftingSlots[1].blockType == PLANK && craftingSlots[3].blockType == PLANK)) { craftingSlots[1].blockType == AIR && craftingSlots[3].blockType == AIR) ||
(craftingSlots[1].blockType == PLANK && craftingSlots[3].blockType == PLANK &&
craftingSlots[0].blockType == AIR && craftingSlots[2].blockType == AIR)) {
craftingResult = InventorySlot(STICK, 4); craftingResult = InventorySlot(STICK, 4);
return;
} }
else {
craftingResult = InventorySlot(AIR, 0); craftingResult = InventorySlot(AIR, 0);
}
}; };
auto UpdateTableCrafting = [&]() { auto UpdateTableCrafting = [&]() {
// Wooden Axe Recipe // Helper: get block type at grid position, -1 if out of bounds
// [P P .] auto T = [&](int i) -> int { return (i >= 0 && i < 9) ? tableSlots[i].blockType : AIR; };
// [P S .]
// [. S .] // Count filled slots and find bounding box
if (tableSlots[0].blockType == PLANK && tableSlots[1].blockType == PLANK && int filledSlots = 0;
tableSlots[3].blockType == PLANK && tableSlots[4].blockType == STICK && for (int i = 0; i < 9; i++) {
tableSlots[7].blockType == STICK) { if (T(i) != AIR) filledSlots++;
tableResult = InventorySlot(WOOD_AXE, 1);
} }
// Sticks (vertical anywhere)
else if ((tableSlots[0].blockType == PLANK && tableSlots[3].blockType == PLANK) || // === SINGLE SLOT RECIPES ===
(tableSlots[1].blockType == PLANK && tableSlots[4].blockType == PLANK) || if (filledSlots == 1) {
(tableSlots[2].blockType == PLANK && tableSlots[5].blockType == PLANK) || for (int i = 0; i < 9; i++) {
(tableSlots[3].blockType == PLANK && tableSlots[6].blockType == PLANK) || if (T(i) == LOG) { tableResult = InventorySlot(PLANK, 4); return; }
(tableSlots[4].blockType == PLANK && tableSlots[7].blockType == PLANK) ||
(tableSlots[5].blockType == PLANK && tableSlots[8].blockType == PLANK)) {
tableResult = InventorySlot(STICK, 4);
} }
// 4 Planks -> 1 Table (3x3 grid)
else if (tableSlots[0].blockType == PLANK && tableSlots[1].blockType == PLANK &&
tableSlots[3].blockType == PLANK && tableSlots[4].blockType == PLANK) {
tableResult = InventorySlot(CRAFTING_TABLE, 1);
} }
else {
// === Helper: Check a 3x3 pattern against grid ===
// Pattern: array of 9 ints, -1 means "must be empty", type means "must match"
auto checkPattern = [&](int p[9]) -> bool {
for (int i = 0; i < 9; i++) {
if (p[i] == -1) { if (T(i) != AIR) return false; }
else { if (T(i) != p[i]) return false; }
}
return true;
};
// === TOOLS (check all valid column offsets) ===
// Wooden Pickaxe: PPP / .S. / .S.
for (int c = 0; c <= 0; c++) {
int p[9] = {PLANK, PLANK, PLANK, -1, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(WOOD_PICKAXE, 1); return; }
}
// Wooden Axe: PP. / PS. / .S. (and mirrored PP. -> .PP)
{ int p[9] = {PLANK, PLANK, -1, PLANK, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(WOOD_AXE, 1); return; } }
{ int p[9] = {-1, PLANK, PLANK, -1, STICK, PLANK, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(WOOD_AXE, 1); return; } }
// Wooden Sword: .P. / .P. / .S.
{ int p[9] = {-1, PLANK, -1, -1, PLANK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(WOOD_SWORD, 1); return; } }
// Wooden Shovel: .P. / .S. / .S.
{ int p[9] = {-1, PLANK, -1, -1, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(WOOD_SHOVEL, 1); return; } }
// Wooden Hoe: PP. / .S. / .S. (and mirrored)
{ int p[9] = {PLANK, PLANK, -1, -1, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(WOOD_HOE, 1); return; } }
{ int p[9] = {-1, PLANK, PLANK, -1, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(WOOD_HOE, 1); return; } }
// Stone Pickaxe: CCC / .S. / .S.
{ int p[9] = {COBBLESTONE, COBBLESTONE, COBBLESTONE, -1, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(STONE_PICKAXE, 1); return; } }
// Stone Axe: CC. / CS. / .S. (and mirrored)
{ int p[9] = {COBBLESTONE, COBBLESTONE, -1, COBBLESTONE, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(STONE_AXE, 1); return; } }
{ int p[9] = {-1, COBBLESTONE, COBBLESTONE, -1, STICK, COBBLESTONE, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(STONE_AXE, 1); return; } }
// Stone Sword: .C. / .C. / .S.
{ int p[9] = {-1, COBBLESTONE, -1, -1, COBBLESTONE, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(STONE_SWORD, 1); return; } }
// Stone Shovel: .C. / .S. / .S.
{ int p[9] = {-1, COBBLESTONE, -1, -1, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(STONE_SHOVEL, 1); return; } }
// Stone Hoe: CC. / .S. / .S. (and mirrored)
{ int p[9] = {COBBLESTONE, COBBLESTONE, -1, -1, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(STONE_HOE, 1); return; } }
{ int p[9] = {-1, COBBLESTONE, COBBLESTONE, -1, STICK, -1, -1, STICK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(STONE_HOE, 1); return; } }
// === BLOCKS ===
// Furnace: CCC / C.C / CCC
{ int p[9] = {COBBLESTONE, COBBLESTONE, COBBLESTONE, COBBLESTONE, -1, COBBLESTONE, COBBLESTONE, COBBLESTONE, COBBLESTONE};
if (checkPattern(p)) { tableResult = InventorySlot(FURNACE, 1); return; } }
// Chest: PPP / P.P / PPP
{ int p[9] = {PLANK, PLANK, PLANK, PLANK, -1, PLANK, PLANK, PLANK, PLANK};
if (checkPattern(p)) { tableResult = InventorySlot(CHEST, 1); return; } }
// Door: PP. / PP. / PP.
{ int p[9] = {PLANK, PLANK, -1, PLANK, PLANK, -1, PLANK, PLANK, -1};
if (checkPattern(p)) { tableResult = InventorySlot(DOOR, 3); return; } }
{ int p[9] = {-1, PLANK, PLANK, -1, PLANK, PLANK, -1, PLANK, PLANK};
if (checkPattern(p)) { tableResult = InventorySlot(DOOR, 3); return; } }
// Fence: PSP / PSP / ... (bottom row empty)
{ int p[9] = {PLANK, STICK, PLANK, PLANK, STICK, PLANK, -1, -1, -1};
if (checkPattern(p)) { tableResult = InventorySlot(FENCE, 3); return; } }
{ int p[9] = {-1, -1, -1, PLANK, STICK, PLANK, PLANK, STICK, PLANK};
if (checkPattern(p)) { tableResult = InventorySlot(FENCE, 3); return; } }
// Ladder: S.S / SSS / S.S
{ int p[9] = {STICK, -1, STICK, STICK, STICK, STICK, STICK, -1, STICK};
if (checkPattern(p)) { tableResult = InventorySlot(LADDER, 3); return; } }
// Stone Slab: ... / ... / CCC (bottom row)
{ int p[9] = {-1, -1, -1, -1, -1, -1, COBBLESTONE, COBBLESTONE, COBBLESTONE};
if (checkPattern(p)) { tableResult = InventorySlot(STONE_SLAB, 6); return; } }
// === SIMPLE 2x2 RECIPES (in any 2x2 sub-grid of the 3x3) ===
// Crafting Table: 2x2 planks (check all four 2x2 positions)
for (int r = 0; r <= 1; r++) {
for (int c = 0; c <= 1; c++) {
int i0 = r*3+c, i1 = r*3+c+1, i2 = (r+1)*3+c, i3 = (r+1)*3+c+1;
if (T(i0) == PLANK && T(i1) == PLANK && T(i2) == PLANK && T(i3) == PLANK) {
// Make sure other slots are empty
bool othersEmpty = true;
for (int i = 0; i < 9; i++) {
if (i != i0 && i != i1 && i != i2 && i != i3 && T(i) != AIR) othersEmpty = false;
}
if (othersEmpty) { tableResult = InventorySlot(CRAFTING_TABLE, 1); return; }
}
}
}
// Sticks: 2 planks vertical (in any column, any two adjacent rows)
for (int c = 0; c < 3; c++) {
for (int r = 0; r <= 1; r++) {
int top = r*3+c, bot = (r+1)*3+c;
if (T(top) == PLANK && T(bot) == PLANK) {
bool othersEmpty = true;
for (int i = 0; i < 9; i++) {
if (i != top && i != bot && T(i) != AIR) othersEmpty = false;
}
if (othersEmpty) { tableResult = InventorySlot(STICK, 4); return; }
}
}
}
tableResult = InventorySlot(AIR, 0); tableResult = InventorySlot(AIR, 0);
}
}; };
// Block Selection State (for wireframe) // Block Selection State (for wireframe)
bool hitBlock = false; bool hitBlock = false;
@ -2886,9 +3036,20 @@ int main(void)
} }
} }
} else { } else {
// Left click: stack if same type, swap if different
if (mouseHeldItem.blockType != AIR && slot.blockType == mouseHeldItem.blockType) {
// Same type: merge stacks
int space = 64 - slot.count;
int toAdd = (mouseHeldItem.count < space) ? mouseHeldItem.count : space;
slot.count += toAdd;
mouseHeldItem.count -= toAdd;
if (mouseHeldItem.count <= 0) mouseHeldItem = InventorySlot(AIR, 0);
} else {
// Different type or one is empty: swap
InventorySlot tmp = slot; InventorySlot tmp = slot;
slot = mouseHeldItem; slot = mouseHeldItem;
mouseHeldItem = tmp; mouseHeldItem = tmp;
}
UpdateCrafting(); UpdateCrafting();
UpdateTableCrafting(); UpdateTableCrafting();
} }
@ -2897,10 +3058,16 @@ int main(void)
if (hov && IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { if (hov && IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) {
if (!isResultSlot) { if (!isResultSlot) {
if (mouseHeldItem.blockType == AIR) { if (mouseHeldItem.blockType == AIR) {
// Pick up half? Not requested, but common. // Empty hand: pick up one item from the slot
// For now just following user request: "holding a stack... right click into a box, place one" if (slot.blockType != AIR && slot.count > 0) {
mouseHeldItem = InventorySlot(slot.blockType, 1);
slot.count--;
if (slot.count <= 0) slot = InventorySlot(AIR, 0);
UpdateCrafting();
UpdateTableCrafting();
}
} else { } else {
// Holding something, place one // Holding something: place one into the slot
if (slot.blockType == AIR) { if (slot.blockType == AIR) {
slot = InventorySlot(mouseHeldItem.blockType, 1); slot = InventorySlot(mouseHeldItem.blockType, 1);
mouseHeldItem.count--; mouseHeldItem.count--;

View File

@ -1 +1 @@
v2.1.8 v2.1.9