diff --git a/build-linux/MorriCraft b/build-linux/MorriCraft index 6ab3175..8ff31bc 100755 Binary files a/build-linux/MorriCraft and b/build-linux/MorriCraft differ diff --git a/build-linux/assets/version.txt b/build-linux/assets/version.txt index 14fc0d5..9600f9b 100644 --- a/build-linux/assets/version.txt +++ b/build-linux/assets/version.txt @@ -1 +1 @@ -v2.1.8 +v2.1.9 diff --git a/build-windows/MorriCraft.exe b/build-windows/MorriCraft.exe index 95f14f0..14253a0 100755 Binary files a/build-windows/MorriCraft.exe and b/build-windows/MorriCraft.exe differ diff --git a/build-windows/assets/version.txt b/build-windows/assets/version.txt index 14fc0d5..9600f9b 100644 --- a/build-windows/assets/version.txt +++ b/build-windows/assets/version.txt @@ -1 +1 @@ -v2.1.8 +v2.1.9 diff --git a/src/main.cpp b/src/main.cpp index 6ccfed9..bd83823 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,7 +25,10 @@ enum BlockType { 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, - 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) { @@ -46,6 +49,22 @@ std::string GetBlockName(int type) { case SAND: return "Sand"; case STICK: return "Stick"; 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"; } } @@ -99,7 +118,7 @@ struct Chunk { bool generated = false; bool modified = false; bool dirty = true; - std::vector renderLists[16]; + std::vector renderLists[32]; 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) { - 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 auto itNM = worldChunks.find({cx-1, cz}); @@ -940,7 +959,7 @@ int main(void) camera3D.projection = CAMERA_PERSPECTIVE; // 3D Block Textures Setup - Texture2D blockTextures[32] = {0}; + Texture2D blockTextures[64] = {0}; blockTextures[DIRT] = LoadTexture("assets/dirt.png"); blockTextures[GRASS] = LoadTexture("assets/grass.png"); blockTextures[COBBLESTONE] = LoadTexture("assets/cobblestone.png"); @@ -986,48 +1005,179 @@ int main(void) InventorySlot tableResult(AIR, 0); 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 if (craftingSlots[0].blockType == PLANK && craftingSlots[1].blockType == PLANK && craftingSlots[2].blockType == PLANK && craftingSlots[3].blockType == PLANK) { craftingResult = InventorySlot(CRAFTING_TABLE, 1); - } + return; + } + // Recipe: 2 Planks (vertical) -> 4 Sticks - else if ((craftingSlots[0].blockType == PLANK && craftingSlots[2].blockType == PLANK) || - (craftingSlots[1].blockType == PLANK && craftingSlots[3].blockType == PLANK)) { + if ((craftingSlots[0].blockType == PLANK && craftingSlots[2].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); + return; } - else { - craftingResult = InventorySlot(AIR, 0); - } + + craftingResult = InventorySlot(AIR, 0); }; auto UpdateTableCrafting = [&]() { - // Wooden Axe Recipe - // [P P .] - // [P S .] - // [. S .] - if (tableSlots[0].blockType == PLANK && tableSlots[1].blockType == PLANK && - tableSlots[3].blockType == PLANK && tableSlots[4].blockType == STICK && - tableSlots[7].blockType == STICK) { - tableResult = InventorySlot(WOOD_AXE, 1); + // Helper: get block type at grid position, -1 if out of bounds + auto T = [&](int i) -> int { return (i >= 0 && i < 9) ? tableSlots[i].blockType : AIR; }; + + // Count filled slots and find bounding box + int filledSlots = 0; + for (int i = 0; i < 9; i++) { + if (T(i) != AIR) filledSlots++; } - // Sticks (vertical anywhere) - else if ((tableSlots[0].blockType == PLANK && tableSlots[3].blockType == PLANK) || - (tableSlots[1].blockType == PLANK && tableSlots[4].blockType == PLANK) || - (tableSlots[2].blockType == PLANK && tableSlots[5].blockType == PLANK) || - (tableSlots[3].blockType == PLANK && tableSlots[6].blockType == PLANK) || - (tableSlots[4].blockType == PLANK && tableSlots[7].blockType == PLANK) || - (tableSlots[5].blockType == PLANK && tableSlots[8].blockType == PLANK)) { - tableResult = InventorySlot(STICK, 4); + + // === SINGLE SLOT RECIPES === + if (filledSlots == 1) { + for (int i = 0; i < 9; i++) { + if (T(i) == LOG) { tableResult = InventorySlot(PLANK, 4); return; } + } } - // 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); + + // === 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; } } - else { - tableResult = InventorySlot(AIR, 0); + + // 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); }; // Block Selection State (for wireframe) bool hitBlock = false; @@ -2886,9 +3036,20 @@ int main(void) } } } else { - InventorySlot tmp = slot; - slot = mouseHeldItem; - mouseHeldItem = tmp; + // 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; + slot = mouseHeldItem; + mouseHeldItem = tmp; + } UpdateCrafting(); UpdateTableCrafting(); } @@ -2897,10 +3058,16 @@ int main(void) if (hov && IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { if (!isResultSlot) { if (mouseHeldItem.blockType == AIR) { - // Pick up half? Not requested, but common. - // For now just following user request: "holding a stack... right click into a box, place one" + // Empty hand: pick up one item from the slot + 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 { - // Holding something, place one + // Holding something: place one into the slot if (slot.blockType == AIR) { slot = InventorySlot(mouseHeldItem.blockType, 1); mouseHeldItem.count--; diff --git a/version.txt b/version.txt index 14fc0d5..9600f9b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.1.8 +v2.1.9