diff --git a/README.md b/README.md index 3228c05..568272e 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,15 @@ A pre-built `MorriCraft-Windows.zip` is available in the repository root. ## 📜 Version History -### v2.2.7 - Performance & Assets (Current) -- **Lag Fix**: Reverted render distance to 4 and optimized frustum culling to restore high FPS. -- **New Icons**: Added high-quality textures for the Wooden Shovel, Pickaxe, Sword, and Hoe. -- **Physics**: Refined anti-stuck resolution for smoother jumping and landing. +### v2.2.19 - Torch & persistence Overhaul (Current) +- **3D Torches**: Torches are now full 3D pillars with flickering flame effects and real-time proximity lighting (15-block radius). +- **World Persistence**: Chest contents and torch positions are now saved and reloaded per world. +- **Slower Days**: The day/night cycle speed has been cut in half for more building time. +- **Asset Updates**: The auto-updater now downloads full asset ZIP packages, ensuring sounds and textures stay up to date. +- **Cheat Menu**: Added `/cheat` command to open a scrollable item browser for testing. +- **GUI Polish**: Right-click stack splitting now works in the Chest GUI, and mouse visibility issues in menus are resolved. + +### v2.2.18 - Persistence & UI Fixes ### v2.2.6 - World Management - **7:00 AM Spawn**: New worlds now start in the morning for immediate daylight. diff --git a/release/MorriCraft-Linux.zip b/release/MorriCraft-Linux.zip new file mode 100644 index 0000000..213398b Binary files /dev/null and b/release/MorriCraft-Linux.zip differ diff --git a/release/MorriCraft-Windows.zip b/release/MorriCraft-Windows.zip new file mode 100644 index 0000000..9d99590 Binary files /dev/null and b/release/MorriCraft-Windows.zip differ diff --git a/release/version.txt b/release/version.txt new file mode 100644 index 0000000..388aeab --- /dev/null +++ b/release/version.txt @@ -0,0 +1 @@ +v2.2.19 diff --git a/src/main.cpp b/src/main.cpp index fab02e3..6749dc5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,7 @@ #include #include #include "rlgl.h" +#include #define CHUNK_SIZE 32 #ifndef PI @@ -28,7 +29,9 @@ enum BlockType { 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 + FURNACE = 25, CHEST = 26, LADDER = 27, FENCE = 28, TORCH = 29, DOOR = 30, STONE_SLAB = 31, + IRON_AXE = 32, IRON_PICKAXE = 33, IRON_SWORD = 34, IRON_SHOVEL = 35, IRON_HOE = 36, + DIAMOND_AXE = 37, DIAMOND_PICKAXE = 38, DIAMOND_SWORD = 39, DIAMOND_SHOVEL = 40, DIAMOND_HOE = 41 }; std::string GetBlockName(int type) { @@ -65,10 +68,28 @@ std::string GetBlockName(int type) { case TORCH: return "Torch"; case DOOR: return "Wooden Door"; case STONE_SLAB: return "Stone Slab"; - default: return "Unknown Item"; + case IRON_AXE: return "Iron Axe"; + case IRON_PICKAXE: return "Iron Pickaxe"; + case IRON_SWORD: return "Iron Sword"; + case IRON_SHOVEL: return "Iron Shovel"; + case IRON_HOE: return "Iron Hoe"; + case DIAMOND_AXE: return "Diamond Axe"; + case DIAMOND_PICKAXE: return "Diamond Pickaxe"; + case DIAMOND_SWORD: return "Diamond Sword"; + case DIAMOND_SHOVEL: return "Diamond Shovel"; + case DIAMOND_HOE: return "Diamond Hoe"; + default: return "Unknown"; } } +int GetMaxDurability(int type) { + if (type >= WOOD_AXE && type <= WOOD_HOE) return 60; + if (type >= STONE_AXE && type <= STONE_HOE) return 132; + if (type >= IRON_AXE && type <= IRON_HOE) return 251; + if (type >= DIAMOND_AXE && type <= DIAMOND_HOE) return 1561; + return 0; +} + // Simple 2D Perlin Noise implementation float dotGridGradient(int ix, int iy, float x, float y, unsigned int seed) { // Proper hash with avalanche effect - seed fundamentally changes the terrain @@ -133,7 +154,7 @@ struct Chunk { bool generated = false; bool modified = false; bool dirty = true; - std::vector renderLists[32]; + std::vector renderLists[64]; Chunk() : generated(false), modified(false), maxY(0), dirty(true) {} }; @@ -152,7 +173,7 @@ static float playerHealth = 16.0f; static uint32_t localPlayerID = 0; static Sound hitSound; -enum MenuState { MAIN_MENU, OPTIONS_MENU, CREATE_WORLD_MENU, LOAD_WORLD_MENU, GAMEPLAY, PAUSE_MENU, CRAFTING_GUI, CHECKING_UPDATES, UPDATE_NOTES, UPDATE_FOUND, DOWNLOADING_UPDATE, CONNECT_MENU, SKIN_EDITOR, WORLD_CREATION_PROGRESS }; +enum MenuState { MAIN_MENU, OPTIONS_MENU, CREATE_WORLD_MENU, LOAD_WORLD_MENU, GAMEPLAY, PAUSE_MENU, CRAFTING_GUI, CHECKING_UPDATES, UPDATE_NOTES, UPDATE_FOUND, DOWNLOADING_UPDATE, CONNECT_MENU, SKIN_EDITOR, WORLD_CREATION_PROGRESS, CHEST_GUI, CHEAT_GUI }; // Forward Declarations unsigned char GetExposedFaces(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, Chunk* nxP, Chunk* nzM, Chunk* nzP); @@ -190,8 +211,13 @@ static bool isConnecting = false; struct InventorySlot { int blockType = AIR; int count = 0; - InventorySlot() : blockType(AIR), count(0) {} - InventorySlot(int bt, int c) : blockType(bt), count(c) {} + int durability = 0; + int maxDurability = 0; + InventorySlot() : blockType(AIR), count(0), durability(0), maxDurability(0) {} + InventorySlot(int bt, int c) : blockType(bt), count(c) { + maxDurability = GetMaxDurability(bt); + durability = maxDurability; + } }; static InventorySlot hotbar[9]; // 9 hotbar slots @@ -206,6 +232,28 @@ static bool isNewWorldGeneration = false; static float spawnSavedX = 0, spawnSavedY = 0, spawnSavedZ = 0; static int loadWorldScrollOffset = 0; +static std::map> chestInventories; +static Vector3 activeChestPos = {0}; +static int cheatScrollOffset = 0; +static std::vector torchPositions; +static float dayFactor = 1.0f; + +float GetBlockLight(Vector3 pos) { + float bL = 0.0f; + for (const auto& tp : torchPositions) { + float d = Vector3Distance(pos, tp); + if (d < 15.0f) { + float intensity = 1.0f - (d / 15.0f); + if (intensity > bL) bL = intensity; + } + } + return fmaxf(dayFactor, bL); +} + +uint64_t GetPosKey(int x, int y, int z) { + return ((uint64_t)(x + 1000000) << 40) | ((uint64_t)(y + 10000) << 20) | (uint64_t)(z + 1000000); +} + // Adds one block to inventory: first fills existing stacks, then empty slots. void AddToInventory(int blockType) { // Search hotbar first @@ -313,7 +361,7 @@ unsigned char GetExposedFaces(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, } void RebuildChunkRenderList(Chunk* chunk, int cx, int cz) { - for (int i = 0; i < 32; i++) chunk->renderLists[i].clear(); + for (int i = 0; i < 64; i++) chunk->renderLists[i].clear(); auto itNM = worldChunks.find({cx-1, cz}); Chunk* nxM = (itNM != worldChunks.end()) ? itNM->second : nullptr; auto itNP = worldChunks.find({cx+1, cz}); Chunk* nxP = (itNP != worldChunks.end()) ? itNP->second : nullptr; @@ -331,7 +379,21 @@ void RebuildChunkRenderList(Chunk* chunk, int cx, int cz) { unsigned char faces = GetExposedFaces(lx, ly, lz, chunk, nxM, nxP, nzM, nzP); if (faces != 0) { - chunk->renderLists[bt].push_back({ (float)(worldX+lx), (float)ly, (float)(worldZ+lz), faces }); + // Simple Torch Lighting (v2.2.19) + float blockLight = 0.0f; + Vector3 bPos = {(float)(worldX+lx), (float)ly, (float)(worldZ+lz)}; + for (const auto& tp : torchPositions) { + float dist = Vector3Distance(bPos, tp); + if (dist < 15.0f) { + float l = 1.0f - (dist / 15.0f); + if (l > blockLight) blockLight = l; + } + } + + BlockRenderData brd = { (float)(worldX+lx), (float)ly, (float)(worldZ+lz), faces }; + // We'll store the light level in a temporary way or just bake it into the tint later. + // For now, let's add a light field to BlockRenderData if we can. + chunk->renderLists[bt].push_back(brd); } } } @@ -373,6 +435,17 @@ int SendAll(Socket s, const char* buf, int len) { } void NetSetBlock(int x, int y, int z, int type) { + // Torch tracking + int oldBT = GetBlock(x, y, z); + if (oldBT == TORCH) { + for (auto it = torchPositions.begin(); it != torchPositions.end(); ++it) { + if (it->x == x && it->y == y && it->z == z) { + torchPositions.erase(it); break; + } + } + } + if (type == TORCH) torchPositions.push_back({(float)x, (float)y, (float)z}); + SetBlock(x, y, z, type); PacketHeader head = { (uint8_t)PACKET_BLOCK_CHANGE, (uint32_t)sizeof(PacketBlockChange) }; PacketBlockChange bc = { x, y, z, type }; @@ -415,7 +488,7 @@ void SetBlock(int x, int y, int z, int type) { } std::string GetRemoteVersion() { - std::string url = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/version.txt"; + std::string url = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/release/version.txt"; char buffer[128]; std::string result = ""; std::string cmd = "curl -s -m 5 " + url; // 5 second timeout @@ -433,12 +506,27 @@ std::string GetRemoteVersion() { bool IsVersionNewer(std::string remote, std::string local) { if (remote.empty() || remote == "error" || remote == local) return false; - auto strip = [](std::string s) { - if (!s.empty() && s[0] == 'v') return s.substr(1); - return s; + int r1=0, r2=0, r3=0, l1=0, l2=0, l3=0; + + // Try to parse vX.Y.Z or X.Y.Z + auto parse = [](const char* s, int &v1, int &v2, int &v3) { + if (sscanf(s, "v%d.%d.%d", &v1, &v2, &v3) == 3) return true; + if (sscanf(s, "%d.%d.%d", &v1, &v2, &v3) == 3) return true; + // Fallback for vX.Y or X.Y + v3 = 0; + if (sscanf(s, "v%d.%d", &v1, &v2) == 2) return true; + if (sscanf(s, "%d.%d", &v1, &v2) == 2) return true; + return false; }; - // Simple lexicographical comparison for v2.x.x format - return strip(remote) > strip(local); + + if (!parse(remote.c_str(), r1, r2, r3)) return false; + if (!parse(local.c_str(), l1, l2, l3)) return false; + + if (r1 > l1) return true; + if (r1 < l1) return false; + if (r2 > l2) return true; + if (r2 < l2) return false; + return r3 > l3; } void SaveConfig() { @@ -682,7 +770,7 @@ bool CheckPlayerCollision(Vector3 pos) { for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { for (int z = minZ; z <= maxZ; z++) { - if (GetBlock(x, y, z) != AIR) { + if (GetBlock(x, y, z) != AIR && GetBlock(x, y, z) != TORCH) { BoundingBox blockBox = { (Vector3){ x - 0.5f, y - 0.5f, z - 0.5f }, (Vector3){ x + 0.5f, y + 0.5f, z + 0.5f } @@ -743,36 +831,42 @@ void DrawTexturedCube(Vector3 position, float width, float height, float length, void DrawCubeVertices(float x, float y, float z, float w, float h, float l, unsigned char mask) { float hw = w/2.0f; float hh = h/2.0f; float hl = l/2.0f; if (mask & 1) { // Front (+Z) + rlNormal3f(0, 0, 1); rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - hw, y - hh, z + hl); rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + hw, y - hh, z + hl); rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + hw, y + hh, z + hl); rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - hw, y + hh, z + hl); } if (mask & 2) { // Back (-Z) + rlNormal3f(0, 0, -1); rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - hw, y - hh, z - hl); rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - hw, y + hh, z - hl); rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + hw, y + hh, z - hl); rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + hw, y - hh, z - hl); } if (mask & 4) { // Top (+Y) + rlNormal3f(0, 1, 0); rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - hw, y + hh, z - hl); rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - hw, y + hh, z + hl); rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + hw, y + hh, z + hl); rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + hw, y + hh, z - hl); } if (mask & 8) { // Bottom (-Y) + rlNormal3f(0, -1, 0); rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - hw, y - hh, z - hl); rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + hw, y - hh, z - hl); rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + hw, y - hh, z + hl); rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - hw, y - hh, z + hl); } if (mask & 16) { // Right (+X) + rlNormal3f(1, 0, 0); rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + hw, y - hh, z - hl); rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + hw, y + hh, z - hl); rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + hw, y + hh, z + hl); rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + hw, y - hh, z + hl); } if (mask & 32) { // Left (-X) + rlNormal3f(-1, 0, 0); rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - hw, y - hh, z - hl); rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - hw, y - hh, z + hl); rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - hw, y + hh, z + hl); @@ -870,7 +964,7 @@ int main(void) // By default, windows have minimize, maximize, and close buttons on the top bar. SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT); - InitWindow(screenWidth, screenHeight, "MorriCraft v2.2.7"); + InitWindow(screenWidth, screenHeight, "MorriCraft v2.2.19"); LoadConfig(); SetExitKey(KEY_NULL); // Prevent ESC from closing the window @@ -912,6 +1006,9 @@ int main(void) Sound digWood = LoadSound("assets/wood1.ogg"); Sound digStone = LoadSound("assets/stone1.ogg"); Sound digSand = LoadSound("assets/sand1.ogg"); + Sound toolBreakSound = LoadSound("assets/break.ogg"); + Sound chestOpenSound = LoadSound("assets/chestopen.ogg"); + Sound chestCloseSound = LoadSound("assets/chestclosed.ogg"); SetSoundVolume(digGrass, 0.6f); SetSoundVolume(digWood, 0.6f); @@ -931,7 +1028,7 @@ int main(void) float updateTimer = 0.0f; float downloadProgress = 0.0f; std::string latestVersion = ""; - std::string localVersion = "v2.2.9"; // Default fallback + std::string localVersion = "v2.2.19"; // Default fallback // Read local version std::ifstream vfile("assets/version.txt"); @@ -987,6 +1084,8 @@ int main(void) // 3D Block Textures Setup Texture2D blockTextures[64] = {0}; + blockTextures[CHEST] = LoadTexture("assets/chest.png"); + blockTextures[TORCH] = LoadTexture("assets/torch.png"); blockTextures[DIRT] = LoadTexture("assets/dirt.png"); blockTextures[GRASS] = LoadTexture("assets/grass.png"); blockTextures[COBBLESTONE] = LoadTexture("assets/cobblestone.png"); @@ -1220,7 +1319,7 @@ int main(void) // Update //---------------------------------------------------------------------------------- // Update ONLY active music streams and ensure others are fully stopped - bool inGame = (currentState == GAMEPLAY || currentState == PAUSE_MENU || currentState == CRAFTING_GUI || (currentState == OPTIONS_MENU && optionsReturnState != MAIN_MENU)); + bool inGame = (currentState == GAMEPLAY || currentState == PAUSE_MENU || currentState == CRAFTING_GUI || currentState == CHEAT_GUI || currentState == CHEST_GUI || (currentState == OPTIONS_MENU && optionsReturnState != MAIN_MENU)); if (!inGame) { UpdateMusicStream(titleMusic); @@ -1536,11 +1635,11 @@ int main(void) // --- GLOBAL TIME & AUDIO MANAGEMENT --- - float cycleLength = 300.0f; + float cycleLength = 600.0f; // Slower day cycle (v2.2.19: cut speed in half) if (currentState == GAMEPLAY) gameTime += GetFrameTime(); float timeOfDay = fmodf(gameTime, cycleLength) / cycleLength; float sunAngle = timeOfDay * 2.0f * 3.14159f - 3.14159f/2.0f; - float dayFactor = (sinf(sunAngle) + 1.0f) / 2.0f; + dayFactor = (sinf(sunAngle) + 1.0f) / 2.0f; float quickMix = (dayFactor - 0.5f) * 5.0f + 0.5f; if (quickMix > 1.0f) quickMix = 1.0f; if (quickMix < 0.0f) quickMix = 0.0f; @@ -1568,8 +1667,9 @@ int main(void) // --- GLOBAL INPUTS (Outside gameplay state) --- - if (IsKeyPressed(KEY_E) && !isChatting && (currentState == GAMEPLAY || currentState == CRAFTING_GUI)) { - if (currentState == CRAFTING_GUI) { + if (IsKeyPressed(KEY_E) && !isChatting && (currentState == GAMEPLAY || currentState == CRAFTING_GUI || currentState == CHEST_GUI || currentState == CHEAT_GUI)) { + if (currentState == CRAFTING_GUI || currentState == CHEST_GUI || currentState == CHEAT_GUI) { + if (currentState == CHEST_GUI) PlaySound(chestCloseSound); currentState = GAMEPLAY; DisableCursor(); } else { @@ -1632,15 +1732,20 @@ int main(void) isFlying = false; chatLog.push_back({ "[Server] Flight disabled", 5.0f }); } else if (cmd == "test") { - GiveItems(CRAFTING_TABLE, 1); - GiveItems(LOG, 64); - chatLog.push_back({ "[Server] Added test items to inventory", 5.0f }); + AddToInventory(CRAFTING_TABLE); + for(int i=0; i<64; i++) AddToInventory(LOG); + chatLog.push_back({"[System] Granted testing items.", 5.0f}); + } else if (cmd == "cheat") { + currentState = CHEAT_GUI; + EnableCursor(); + chatLog.push_back({"[System] Opened Cheat Inventory.", 5.0f}); } else if (cmd == "help") { chatLog.push_back({ "[Server] Commands List:", 8.0f }); chatLog.push_back({ " /help - Shows this list", 8.0f }); chatLog.push_back({ " /seed - Shows the world seed", 8.0f }); chatLog.push_back({ " /fly [on|off] - Toggle flight mode", 8.0f }); chatLog.push_back({ " /test - Give basic crafting materials", 8.0f }); + chatLog.push_back({ " /cheat - Open creative menu", 8.0f }); } else { chatLog.push_back({ "[Server] Unknown command: /" + cmd, 5.0f }); } @@ -1664,8 +1769,7 @@ int main(void) } chatInput[0] = '\0'; } - isChatting = false; - DisableCursor(); + if (currentState == GAMEPLAY) DisableCursor(); } } @@ -1883,6 +1987,17 @@ int main(void) if (breakProgress >= breakSpeed) { AddToInventory(targetBlock); NetSetBlock(hitX, hitY, hitZ, AIR); + + // Tool Durability Logic (v2.2.19) + InventorySlot* slot = &hotbar[activeHotbarSlot]; + if (slot->maxDurability > 0) { + slot->durability--; + if (slot->durability <= 0) { + PlaySound(toolBreakSound); + *slot = InventorySlot(AIR, 0); + } + } + breakProgress = 0.0f; isSwinging = true; // Swing when finishing } @@ -1899,14 +2014,36 @@ int main(void) if (targetBlock == CRAFTING_TABLE) { currentState = CRAFTING_GUI; EnableCursor(); + } else if (targetBlock == CHEST) { + activeChestPos = (Vector3){ (float)hitX, (float)hitY, (float)hitZ }; + uint64_t key = GetPosKey(hitX, hitY, hitZ); + if (chestInventories.find(key) == chestInventories.end()) { + chestInventories[key] = std::vector(27, InventorySlot(AIR, 0)); + } + currentState = CHEST_GUI; + PlaySound(chestOpenSound); + EnableCursor(); } else if (hotbar[activeHotbarSlot].count > 0) { int placeX = hitX + (int)roundf(closestNormal.x); int placeY = hitY + (int)roundf(closestNormal.y); int placeZ = hitZ + (int)roundf(closestNormal.z); - NetSetBlock(placeX, placeY, placeZ, hotbar[activeHotbarSlot].blockType); - hotbar[activeHotbarSlot].count--; - if (hotbar[activeHotbarSlot].count == 0) - hotbar[activeHotbarSlot].blockType = AIR; + + // Check if space is occupied by player + BoundingBox playerBB = { + (Vector3){camera3D.position.x - 0.3f, camera3D.position.y - 1.5f, camera3D.position.z - 0.3f}, + (Vector3){camera3D.position.x + 0.3f, camera3D.position.y + 0.3f, camera3D.position.z + 0.3f} + }; + BoundingBox blockBB = { + (Vector3){(float)placeX - 0.5f, (float)placeY - 0.5f, (float)placeZ - 0.5f}, + (Vector3){(float)placeX + 0.5f, (float)placeY + 0.5f, (float)placeZ + 0.5f} + }; + + // Torches and some other blocks don't collide, but for now we just check occupancy + if (!CheckCollisionBoxes(playerBB, blockBB) || hotbar[activeHotbarSlot].blockType == TORCH) { + NetSetBlock(placeX, placeY, placeZ, hotbar[activeHotbarSlot].blockType); + hotbar[activeHotbarSlot].count--; + if (hotbar[activeHotbarSlot].count <= 0) hotbar[activeHotbarSlot].blockType = AIR; + } isSwinging = true; // Swing when placing } } @@ -2009,13 +2146,13 @@ int main(void) std::thread([]() { std::string binaryUrl, binaryName; #ifdef _WIN32 - binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/build-windows/MorriCraft.exe"; - binaryName = "MorriCraft.exe"; + binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/release/MorriCraft-Windows.zip"; + binaryName = "MorriCraft-Windows.zip"; #else - binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/build-linux/MorriCraft"; - binaryName = "MorriCraft"; + binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/release/MorriCraft-Linux.zip"; + binaryName = "MorriCraft-Linux.zip"; #endif - std::string versionUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/version.txt"; + std::string versionUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/release/version.txt"; // Download binary int r1 = system(("curl -L -s -o " + binaryName + ".new \"" + binaryUrl + "\"").c_str()); @@ -2027,17 +2164,20 @@ int main(void) if (r1 == 0 && r2 == 0) { #ifdef _WIN32 - system(("rename " + binaryName + " " + binaryName + ".old").c_str()); - system(("rename " + binaryName + ".new " + binaryName).c_str()); - system("rename version.txt version.txt.old"); - system("rename version.txt.new version.txt"); + // Windows Update: Move exe, unzip assets + system("move MorriCraft.exe MorriCraft.old >nul 2>&1"); + system("powershell -Command \"Expand-Archive -Path MorriCraft-Windows.zip -DestinationPath . -Force\""); + system("move version.txt.new version.txt >nul 2>&1"); system("copy version.txt assets\\version.txt >nul 2>&1"); + system("del MorriCraft-Windows.zip"); #else - system(("chmod +x " + binaryName + ".new").c_str()); - system(("mv " + binaryName + " " + binaryName + ".old 2>/dev/null").c_str()); - system(("mv " + binaryName + ".new " + binaryName).c_str()); - system("mv version.txt.new version.txt"); + // Linux Update: Move binary, unzip assets + system("mv MorriCraft MorriCraft.old 2>/dev/null"); + system("unzip -o MorriCraft-Linux.zip"); + system("chmod +x MorriCraft"); + system("mv version.txt.new version.txt 2>/dev/null"); system("cp version.txt assets/version.txt 2>/dev/null"); + system("rm MorriCraft-Linux.zip"); #endif currentProgress = 1.0f; downloadFinished = true; @@ -2057,7 +2197,7 @@ int main(void) DrawRectangleRec(backBtn, DARKGRAY); DrawTextEx(customFont, "BACK", (Vector2){ backBtn.x + 70, backBtn.y + 10 }, 20, 1.0f, WHITE); } else { - DrawTextEx(customFont, "DOWNLOADING & REPLACING SYSTEM FILES...", (Vector2){ (float)currentWidth/2 - 250, (float)currentHeight/2 - 60 }, 24, 1.0f, YELLOW); + DrawTextEx(customFont, "Updating...", (Vector2){ (float)currentWidth/2 - 50, (float)currentHeight/2 - 60 }, 24, 1.0f, YELLOW); int bw = 500, bh = 35; Rectangle barBg = { (float)currentWidth/2 - bw/2, (float)currentHeight/2 - bh/2, (float)bw, (float)bh }; DrawRectangleRec(barBg, BLACK); @@ -2070,7 +2210,6 @@ int main(void) int pw = 500, ph = 220; Rectangle pBox = { (float)currentWidth/2 - pw/2, (float)currentHeight/2 - ph/2, (float)pw, (float)ph }; DrawRectangleRec(pBox, (Color){ 30, 30, 30, 255 }); - DrawRectangleLinesEx(pBox, 4.0f, GREEN); DrawTextEx(customFont, "UPDATE SUCCESSFUL!", (Vector2){ pBox.x + 80, pBox.y + 40 }, 28, 1.0f, GREEN); DrawTextEx(customFont, "PLEASE RESTART THE CLIENT TO APPLY.", (Vector2){ pBox.x + 60, pBox.y + 90 }, 20, 1.0f, WHITE); @@ -2081,6 +2220,21 @@ int main(void) DrawTextEx(customFont, "EXIT GAME", (Vector2){ exitBtn.x + 40, exitBtn.y + 12 }, 20, 1.0f, WHITE); } } + } else if (currentState == CHEAT_GUI) { + DrawRectangle(0, 0, currentWidth, currentHeight, (Color){0, 0, 0, 200}); + DrawTextEx(customFont, "CHEAT MENU (Press E to Close)", (Vector2){20, 20}, 24, 1.0f, WHITE); + // Grid of all blocks + for(int i = 0; i < 64; i++) { + if (blockTextures[i].id == 0) continue; + int px = 50 + (i % 10) * 60; + int py = 80 + (i / 10) * 60; + Rectangle r = {(float)px, (float)py, 50, 50}; + DrawRectangleRec(r, GRAY); + DrawTexturePro(blockTextures[i], (Rectangle){0,0,(float)blockTextures[i].width, (float)blockTextures[i].height}, (Rectangle){(float)px+5,(float)py+5,40,40}, (Vector2){0,0}, 0.0f, WHITE); + if (CheckCollisionPointRec(mousePos, r) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + AddToInventory(i); + } + } } else if (currentState == SKIN_EDITOR) { DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 20, 20, 20, 255 }); int panelWidth = 700; @@ -2315,8 +2469,8 @@ int main(void) DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE); EndMode2D(); - // Show Version Number (v2.2.9) in Red - DrawTextEx(customFont, "v2.2.9", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); + // Show Version Number (v2.2.19) in Red + DrawTextEx(customFont, "v2.2.19", (Vector2){ (float)currentWidth - 120, (float)currentHeight - 30 }, 22, 1.0f, RED); // --- PLAYER NAME POPUP (IF MISSING) --- if (playerName == "") { @@ -2489,11 +2643,38 @@ int main(void) if (worldFile.is_open()) { worldFile >> globalSeedHash >> spawnSavedX >> spawnSavedY >> spawnSavedZ; worldFile.close(); - } else { globalSeedHash = 12345; spawnSavedX = 0; spawnSavedY = -1; spawnSavedZ = 0; } + // Load Chests (v2.2.19) + chestInventories.clear(); + std::ifstream cf("saves/" + currentWorldName + "/chests.dat", std::ios::binary); + if (cf.is_open()) { + uint32_t count; cf.read((char*)&count, 4); + for (uint32_t i=0; i inv(27, InventorySlot(AIR, 0)); + for (int j=0; j<27; j++) { + cf.read((char*)&inv[j].blockType, 4); + cf.read((char*)&inv[j].count, 4); + cf.read((char*)&inv[j].durability, 4); + } + chestInventories[key] = inv; + } + cf.close(); + } + + // Load Torches (v2.2.19) + torchPositions.clear(); + std::ifstream tf("saves/" + currentWorldName + "/torches.dat", std::ios::binary); + if (tf.is_open()) { + uint32_t count; tf.read((char*)&count, 4); + torchPositions.resize(count); + tf.read((char*)torchPositions.data(), count * sizeof(Vector3)); + tf.close(); + } + // Clear old chunks for (auto& pair : worldChunks) delete pair.second; worldChunks.clear(); @@ -2544,20 +2725,19 @@ int main(void) if (showDeleteConfirm) { DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 200 }); - int confWidth = 550; - int confHeight = 240; + int confWidth = 650; + int confHeight = 300; int confX = (currentWidth / 2) - (confWidth / 2); int confY = (currentHeight / 2) - (confHeight / 2); DrawRectangle(confX, confY, confWidth, confHeight, (Color){ 50, 20, 20, 255 }); - DrawRectangleLinesEx((Rectangle){ (float)confX, (float)confY, (float)confWidth, (float)confHeight }, 4.0f, RED); Vector2 warnSize = MeasureTextEx(customFont, "Delete World?", 28, 1.0f); - DrawTextEx(customFont, "Delete World?", (Vector2){ confX + (confWidth/2) - (warnSize.x/2), confY + 30 }, 28, 1.0f, WHITE); + DrawTextEx(customFont, "Delete World?", (Vector2){ (float)(confX + (confWidth/2) - (warnSize.x/2)), (float)(confY + 30) }, 28, 1.0f, WHITE); std::string warnText = "Are you sure you want to delete '" + deletingWorldName + "'?"; Vector2 nameSize = MeasureTextEx(customFont, warnText.c_str(), 22, 1.0f); - DrawTextEx(customFont, warnText.c_str(), (Vector2){ confX + (confWidth/2) - (nameSize.x/2), confY + 85 }, 22, 1.0f, LIGHTGRAY); + DrawTextEx(customFont, warnText.c_str(), (Vector2){ (float)(confX + (confWidth/2) - (nameSize.x/2)), (float)(confY + 85) }, 22, 1.0f, LIGHTGRAY); // Yes Button Rectangle yesBtn = { (float)confX + 80, (float)confY + 150, 140, 40 }; @@ -2809,7 +2989,7 @@ int main(void) } // Draw Gameplay overlay if we entered gameplay - if (currentState == GAMEPLAY || currentState == PAUSE_MENU || currentState == CRAFTING_GUI) { + if (currentState == GAMEPLAY || currentState == PAUSE_MENU || currentState == CRAFTING_GUI || currentState == CHEST_GUI || currentState == CHEAT_GUI) { hoveredItemName = ""; // ---- Atmospheric Calculations (Previously global) ---- // lightLevel and blockTint are derived from the global dayFactor @@ -2888,36 +3068,84 @@ int main(void) } } - // 2. Batch render per type - for (int renderType = 1; renderType <= 13; renderType++) { + for (int renderType = 1; renderType < 64; renderType++) { if (renderType == GRASS) { for (Chunk* chunk : visibleChunks) { for (auto& data : chunk->renderLists[GRASS]) { - DrawGrassBlock((Vector3){data.x, data.y, data.z}, blockTextures[GRASS].id, grassTopTexture.id, blockTextures[DIRT].id, blockTint, data.faces); + float light = GetBlockLight((Vector3){data.x, data.y, data.z}); + Color tint = {(unsigned char)(255*light), (unsigned char)(255*light), (unsigned char)(255*light), 255}; + DrawGrassBlock((Vector3){data.x, data.y, data.z}, blockTextures[GRASS].id, grassTopTexture.id, blockTextures[DIRT].id, tint, data.faces); } } } else if (renderType == LOG) { for (Chunk* chunk : visibleChunks) { for (auto& data : chunk->renderLists[LOG]) { - DrawLog((Vector3){data.x, data.y, data.z}, logSideTexture.id, logTopTexture.id, blockTint, data.faces); + float light = GetBlockLight((Vector3){data.x, data.y, data.z}); + Color tint = {(unsigned char)(255*light), (unsigned char)(255*light), (unsigned char)(255*light), 255}; + DrawLog((Vector3){data.x, data.y, data.z}, logSideTexture.id, logTopTexture.id, tint, data.faces); } } } else if (renderType == CRAFTING_TABLE) { for (Chunk* chunk : visibleChunks) { for (auto& data : chunk->renderLists[CRAFTING_TABLE]) { - DrawCraftingTable((Vector3){data.x, data.y, data.z}, craftingSideTexture.id, craftingTopTexture.id, blockTextures[DIRT].id, blockTint, data.faces); + float light = GetBlockLight((Vector3){data.x, data.y, data.z}); + Color tint = {(unsigned char)(255*light), (unsigned char)(255*light), (unsigned char)(255*light), 255}; + DrawCraftingTable((Vector3){data.x, data.y, data.z}, craftingSideTexture.id, craftingTopTexture.id, blockTextures[DIRT].id, tint, data.faces); + } + } + } else if (renderType == TORCH) { + for (Chunk* chunk : visibleChunks) { + for (auto& data : chunk->renderLists[TORCH]) { + // 3D Flickering Torch (v2.2.19) + float flicker = sinf(GetTime() * 12.0f) * 0.03f; + float h = 0.6f + flicker; + float w = 0.12f; + Texture2D tex = blockTextures[TORCH]; + rlSetTexture(tex.id); + rlPushMatrix(); + rlTranslatef(data.x, data.y - 0.3f, data.z); + rlBegin(RL_QUADS); + rlColor4ub(255, 255, 255, 255); + // Front + rlTexCoord2f(0,0); rlVertex3f(-w/2, h, w/2); + rlTexCoord2f(0,1); rlVertex3f(-w/2, 0, w/2); + rlTexCoord2f(1,1); rlVertex3f(w/2, 0, w/2); + rlTexCoord2f(1,0); rlVertex3f(w/2, h, w/2); + // Back + rlTexCoord2f(0,0); rlVertex3f(w/2, h, -w/2); + rlTexCoord2f(0,1); rlVertex3f(w/2, 0, -w/2); + rlTexCoord2f(1,1); rlVertex3f(-w/2, 0, -w/2); + rlTexCoord2f(1,0); rlVertex3f(-w/2, h, -w/2); + // Right + rlTexCoord2f(0,0); rlVertex3f(w/2, h, w/2); + rlTexCoord2f(0,1); rlVertex3f(w/2, 0, w/2); + rlTexCoord2f(1,1); rlVertex3f(w/2, 0, -w/2); + rlTexCoord2f(1,0); rlVertex3f(w/2, h, -w/2); + // Left + rlTexCoord2f(0,0); rlVertex3f(-w/2, h, -w/2); + rlTexCoord2f(0,1); rlVertex3f(-w/2, 0, -w/2); + rlTexCoord2f(1,1); rlVertex3f(-w/2, 0, w/2); + rlTexCoord2f(1,0); rlVertex3f(-w/2, h, w/2); + // Top + rlTexCoord2f(0,0); rlVertex3f(-w/2, h, -w/2); + rlTexCoord2f(0,1); rlVertex3f(-w/2, h, w/2); + rlTexCoord2f(1,1); rlVertex3f(w/2, h, w/2); + rlTexCoord2f(1,0); rlVertex3f(w/2, h, -w/2); + rlEnd(); + rlPopMatrix(); } } } else { rlSetTexture(blockTextures[renderType].id); rlBegin(RL_QUADS); - rlColor4ub(blockTint.r, blockTint.g, blockTint.b, 255); - if (renderType == LEAVES) { - rlColor4ub((unsigned char)(blockTint.r * 0.6f), (unsigned char)(blockTint.g * 0.9f), (unsigned char)(blockTint.b * 0.6f), 255); - } - for (Chunk* chunk : visibleChunks) { for (auto& data : chunk->renderLists[renderType]) { + float light = GetBlockLight((Vector3){data.x, data.y, data.z}); + Color c = {(unsigned char)(255*light), (unsigned char)(255*light), (unsigned char)(255*light), 255}; + if (renderType == LEAVES) { + c.r *= 0.6f; c.g *= 0.9f; c.b *= 0.6f; + } + rlColor4ub(c.r, c.g, c.b, 255); DrawCubeVertices(data.x, data.y, data.z, 1.0f, 1.0f, 1.0f, data.faces); } } @@ -2929,7 +3157,6 @@ int main(void) float size = 1.02f; DrawCubeWires((Vector3){(float)hitX, (float)hitY, (float)hitZ}, size, size, size, BLACK); - // Draw breaking progress indicator // Draw breaking progress indicator if (breakProgress > 0.0f) { float pSize = (breakProgress / 1.5f) * 1.05f; // Scale visual based on progress @@ -2970,26 +3197,19 @@ int main(void) handPos = Vector3Add(handPos, Vector3Scale(rightVM, 0.28f)); handPos = Vector3Add(handPos, Vector3Scale(upVM, -0.25f - swingVal * 0.15f)); - // Draw Arm (rotated cube) - rlPushMatrix(); - rlTranslatef(handPos.x, handPos.y, handPos.z); - rlRotatef(camYaw * 180.0f/PI, 0, 1, 0); - rlRotatef(camPitch * 180.0f/PI - 15.0f - swingVal * 20.0f, 1, 0, 0); // Point forward + swing - DrawCube((Vector3){0,0,0}, 0.1f, 0.12f, 0.4f, (Color){220, 180, 150, 255}); - rlPopMatrix(); - - // Draw Held Item - Fixed pivot and attachment (Bug #3) + // Viewmodel logic int heldBT = hotbar[activeHotbarSlot].blockType; - if (heldBT != AIR) { - // Calculate position at the end of the arm - // The arm is rotated by camPitch - 15.0f - swingVal * 20.0f - float armPitch = (camPitch * 180.0f/PI - 15.0f - swingVal * 20.0f) * PI/180.0f; - Vector3 armForward = { - forwardVM.x * cosf(-armPitch) - forwardVM.y * sinf(-armPitch), - forwardVM.x * sinf(-armPitch) + forwardVM.y * cosf(-armPitch), - forwardVM.z - }; // This is a simplification, but let's use the handPos + offset - + + if (heldBT == AIR) { + // Draw Arm (rotated cube) + rlPushMatrix(); + rlTranslatef(handPos.x, handPos.y, handPos.z); + rlRotatef(camYaw * 180.0f/PI, 0, 1, 0); + rlRotatef(camPitch * 180.0f/PI - 15.0f - swingVal * 20.0f, 1, 0, 0); + DrawCube((Vector3){0,0,0}, 0.1f, 0.12f, 0.4f, (Color){220, 180, 150, 255}); + rlPopMatrix(); + } else { + // Draw Held Item Vector3 itemPos = Vector3Add(handPos, Vector3Scale(forwardVM, 0.25f)); itemPos = Vector3Add(itemPos, Vector3Scale(upVM, 0.05f)); itemPos = Vector3Add(itemPos, Vector3Scale(rightVM, -0.05f)); @@ -3003,17 +3223,19 @@ int main(void) rlTranslatef(itemPos.x, itemPos.y, itemPos.z); rlRotatef(camYaw * 180.0f/PI + 45.0f, 0, 1, 0); rlRotatef(20.0f, 1, 0, 1); - DrawCubeVertices(0,0,0, 0.12f, 0.12f, 0.12f, 63); + rlBegin(RL_QUADS); + rlColor4ub(255, 255, 255, 255); + DrawCubeVertices(0,0,0, 0.15f, 0.15f, 0.15f, 63); + rlEnd(); rlPopMatrix(); rlSetTexture(0); } else { // Item (Stick/Axe) as Billboard Rectangle src = { 0, 0, (float)itemTex.width, (float)itemTex.height }; - Vector2 itemSize = { 0.45f, 0.45f }; - // Pivot at bottom-left corner (Bug #3 fix) - // In DrawBillboardPro, origin (0,0) is center. Bottom-left is (-w/2, -h/2). + Vector2 itemSize = { 0.35f, 0.35f }; Vector2 pivot = { -itemSize.x/2.0f, -itemSize.y/2.0f }; - DrawBillboardPro(camera3D, itemTex, src, itemPos, camera3D.up, itemSize, pivot, -45.0f + swingVal * 40.0f, WHITE); + // Back to 135 degrees + DrawBillboardPro(camera3D, itemTex, src, itemPos, camera3D.up, itemSize, pivot, 135.0f + swingVal * 40.0f, WHITE); } } } @@ -3117,6 +3339,15 @@ int main(void) DrawTextEx(customFont, TextFormat("%i", hotbar[s].count), (Vector2){ (float)(sx + (slotSize/2) - (countSize.x/2)), (float)(hotbarY + slotSize - 14) }, 12, 1.0f, WHITE); + + // Draw Hotbar Durability Bar (v2.2.19) + if (hotbar[s].maxDurability > 0 && hotbar[s].durability < hotbar[s].maxDurability) { + float durP = (float)hotbar[s].durability / hotbar[s].maxDurability; + int barW = slotSize - 10; + int barH = 3; + DrawRectangle(sx + 5, hotbarY + slotSize - 8, barW, barH, BLACK); + DrawRectangle(sx + 5, hotbarY + slotSize - 8, (int)(barW * durP), barH, (durP < 0.25f ? RED : (durP < 0.5f ? ORANGE : GREEN))); + } } // Slot number label @@ -3140,6 +3371,15 @@ int main(void) DrawRectangleRec(slotRect, hov ? (Color){90,90,90,255} : (Color){60,60,60,255}); DrawRectangleLinesEx(slotRect, 2.0f, hov ? WHITE : (Color){100,100,100,200}); + // Draw Durability Bar (v2.2.19) + if (slot.maxDurability > 0 && slot.durability < slot.maxDurability) { + float durPercent = (float)slot.durability / slot.maxDurability; + int barW = slotRect.width - 10; + int barH = 3; + DrawRectangle(slotRect.x + 5, slotRect.y + slotRect.height - 8, barW, barH, BLACK); + DrawRectangle(slotRect.x + 5, slotRect.y + slotRect.height - 8, (int)(barW * durPercent), barH, (durPercent < 0.25f) ? RED : (durPercent < 0.5f ? ORANGE : GREEN)); + } + if (hov && slot.blockType != AIR) { hoveredItemName = GetBlockName(slot.blockType); } @@ -3363,6 +3603,170 @@ int main(void) } } + if (currentState == CHEST_GUI) { + // --- CHEST GUI (3x9 Chest + 3x9 Player Inventory) --- + int invPanelW = 600, invPanelH = 500; + int guiX = currentWidth/2 - invPanelW/2; + int guiY = currentHeight/2 - invPanelH/2; + + DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 150 }); + DrawRectangleRec((Rectangle){(float)guiX, (float)guiY, (float)invPanelW, (float)invPanelH}, (Color){ 40, 40, 40, 255 }); + DrawRectangleLinesEx((Rectangle){(float)guiX, (float)guiY, (float)invPanelW, (float)invPanelH}, 3.0f, LIGHTGRAY); + + DrawTextEx(customFont, "Chest", (Vector2){ (float)guiX + 20, (float)guiY + 15 }, 24, 1.0f, WHITE); + DrawTextEx(customFont, "Inventory", (Vector2){ (float)guiX + 20, (float)guiY + 240 }, 24, 1.0f, WHITE); + + uint64_t key = GetPosKey((int)activeChestPos.x, (int)activeChestPos.y, (int)activeChestPos.z); + auto& chestInv = chestInventories[key]; + + // Chest Slots (3x9) + for (int i = 0; i < 27; i++) { + int row = i / 9; int col = i % 9; + Rectangle slotRect = { (float)guiX + 20 + col * 64, (float)guiY + 50 + row * 64, 58, 58 }; + bool hov = CheckCollisionPointRec(mousePos, slotRect); + DrawRectangleRec(slotRect, hov ? (Color){90,90,90,255} : (Color){60,60,60,255}); + DrawRectangleLinesEx(slotRect, 2.0f, hov ? WHITE : (Color){100,100,100,200}); + + if (chestInv[i].blockType != AIR) { + Texture2D tex = blockTextures[chestInv[i].blockType]; + DrawTexturePro(tex, (Rectangle){0,0,(float)tex.width,(float)tex.height}, (Rectangle){slotRect.x+5, slotRect.y+5, 48, 48}, (Vector2){0,0}, 0, WHITE); + DrawTextEx(customFont, TextFormat("%d", chestInv[i].count), (Vector2){slotRect.x+40, slotRect.y+40}, 16, 1.0f, WHITE); + } + + if (hov && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + // Left click: stack if same type, swap if different + if (mouseHeldItem.blockType != AIR && chestInv[i].blockType == mouseHeldItem.blockType) { + int space = 64 - chestInv[i].count; + int toAdd = (mouseHeldItem.count < space) ? mouseHeldItem.count : space; + chestInv[i].count += toAdd; + mouseHeldItem.count -= toAdd; + if (mouseHeldItem.count <= 0) mouseHeldItem = InventorySlot(AIR, 0); + } else { + InventorySlot temp = chestInv[i]; chestInv[i] = mouseHeldItem; mouseHeldItem = temp; + } + } + if (hov && IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { + // Right click: pick up/place one (v2.2.19) + if (mouseHeldItem.blockType == AIR) { + if (chestInv[i].blockType != AIR && chestInv[i].count > 0) { + mouseHeldItem = InventorySlot(chestInv[i].blockType, 1); + chestInv[i].count--; + if (chestInv[i].count <= 0) chestInv[i] = InventorySlot(AIR, 0); + } + } else { + if (chestInv[i].blockType == AIR) { + chestInv[i] = InventorySlot(mouseHeldItem.blockType, 1); + mouseHeldItem.count--; + } else if (chestInv[i].blockType == mouseHeldItem.blockType && chestInv[i].count < 64) { + chestInv[i].count++; + mouseHeldItem.count--; + } + if (mouseHeldItem.count <= 0) mouseHeldItem = InventorySlot(AIR, 0); + } + } + } + + // Player Inventory Slots (3x9) + for (int i = 0; i < 27; i++) { + int row = i / 9; int col = i % 9; + Rectangle slotRect = { (float)guiX + 20 + col * 64, (float)guiY + 280 + row * 64, 58, 58 }; + bool hov = CheckCollisionPointRec(mousePos, slotRect); + DrawRectangleRec(slotRect, hov ? (Color){90,90,90,255} : (Color){60,60,60,255}); + DrawRectangleLinesEx(slotRect, 2.0f, hov ? WHITE : (Color){100,100,100,200}); + + if (inventory[i].blockType != AIR) { + Texture2D tex = blockTextures[inventory[i].blockType]; + DrawTexturePro(tex, (Rectangle){0,0,(float)tex.width,(float)tex.height}, (Rectangle){slotRect.x+5, slotRect.y+5, 48, 48}, (Vector2){0,0}, 0, WHITE); + DrawTextEx(customFont, TextFormat("%d", inventory[i].count), (Vector2){slotRect.x+40, slotRect.y+40}, 16, 1.0f, WHITE); + } + + if (hov && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + if (mouseHeldItem.blockType != AIR && inventory[i].blockType == mouseHeldItem.blockType) { + int space = 64 - inventory[i].count; + int toAdd = (mouseHeldItem.count < space) ? mouseHeldItem.count : space; + inventory[i].count += toAdd; + mouseHeldItem.count -= toAdd; + if (mouseHeldItem.count <= 0) mouseHeldItem = InventorySlot(AIR, 0); + } else { + InventorySlot temp = inventory[i]; inventory[i] = mouseHeldItem; mouseHeldItem = temp; + } + } + if (hov && IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { + // Right click logic for player inv in chest GUI (v2.2.19) + if (mouseHeldItem.blockType == AIR) { + if (inventory[i].blockType != AIR && inventory[i].count > 0) { + mouseHeldItem = InventorySlot(inventory[i].blockType, 1); + inventory[i].count--; + if (inventory[i].count <= 0) inventory[i] = InventorySlot(AIR, 0); + } + } else { + if (inventory[i].blockType == AIR) { + inventory[i] = InventorySlot(mouseHeldItem.blockType, 1); + mouseHeldItem.count--; + } else if (inventory[i].blockType == mouseHeldItem.blockType && inventory[i].count < 64) { + inventory[i].count++; + mouseHeldItem.count--; + } + if (mouseHeldItem.count <= 0) mouseHeldItem = InventorySlot(AIR, 0); + } + } + } + + if (IsKeyPressed(KEY_ESCAPE)) { + currentState = GAMEPLAY; DisableCursor(); PlaySound(chestCloseSound); + } + } + + if (currentState == CHEAT_GUI) { + // --- CHEAT / CREATIVE INVENTORY GUI --- + int panelW = 700, panelH = 500; + int guiX = currentWidth/2 - panelW/2; + int guiY = currentHeight/2 - panelH/2; + + DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 150 }); + DrawRectangleRec((Rectangle){(float)guiX, (float)guiY, (float)panelW, (float)panelH}, (Color){ 30, 30, 30, 255 }); + DrawRectangleLinesEx((Rectangle){(float)guiX, (float)guiY, (float)panelW, (float)panelH}, 3.0f, LIGHTGRAY); + + DrawTextEx(customFont, "Cheat Inventory (Scroll to see more)", (Vector2){ (float)guiX + 20, (float)guiY + 15 }, 24, 1.0f, GOLD); + + // Handle Scroll + cheatScrollOffset -= GetMouseWheelMove() * 40; + if (cheatScrollOffset < 0) cheatScrollOffset = 0; + + BeginScissorMode(guiX + 10, guiY + 60, panelW - 20, panelH - 100); + + int cols = 9; + int slotS = 64; + for (int i = 1; i <= 41; i++) { // All blocks and tools + if (i == 14 || i == 15 || i == 16 || (i >= 17 && i <= 24)) continue; // Skip untextured/meta items if any + + int row = (i-1) / cols; + int col = (i-1) % cols; + Rectangle r = { (float)guiX + 30 + col * (slotS + 5), (float)guiY + 70 + row * (slotS + 5) - cheatScrollOffset, (float)slotS, (float)slotS }; + + bool hov = CheckCollisionPointRec(mousePos, r); + DrawRectangleRec(r, hov ? GRAY : DARKGRAY); + DrawRectangleLinesEx(r, 1.5f, hov ? WHITE : (Color){80,80,80,255}); + + Texture2D tex = (i == GRASS) ? grassTopTexture : blockTextures[i]; + if (tex.id > 0) { + DrawTexturePro(tex, (Rectangle){0,0,(float)tex.width,(float)tex.height}, (Rectangle){r.x+8, r.y+8, 48, 48}, (Vector2){0,0}, 0, WHITE); + } + + if (hov && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + // Give 64 of selected item to hotbar + hotbar[activeHotbarSlot] = InventorySlot(i, 64); + } + } + EndScissorMode(); + + DrawTextEx(customFont, "Click item to set to active hotbar slot", (Vector2){ (float)guiX + 20, (float)guiY + panelH - 30 }, 18, 1.0f, LIGHTGRAY); + + if (IsKeyPressed(KEY_ESCAPE) || IsKeyPressed(KEY_E)) { + currentState = GAMEPLAY; DisableCursor(); + } + } + // --- Draw Held Item Last --- if (mouseHeldItem.blockType != AIR && mouseHeldItem.count > 0) { int bt = mouseHeldItem.blockType; @@ -3463,6 +3867,31 @@ int main(void) invf.write((char*)&activeHotbarSlot, sizeof(activeHotbarSlot)); invf.close(); } + + // Save Chests (v2.2.19) + std::ofstream cf("saves/" + currentWorldName + "/chests.dat", std::ios::binary); + if (cf.is_open()) { + uint32_t count = (uint32_t)chestInventories.size(); + cf.write((char*)&count, 4); + for (auto const& [key, inv] : chestInventories) { + cf.write((char*)&key, 8); + for (int j=0; j<27; j++) { + cf.write((char*)&inv[j].blockType, 4); + cf.write((char*)&inv[j].count, 4); + cf.write((char*)&inv[j].durability, 4); + } + } + cf.close(); + } + + // Save Torches (v2.2.19) + std::ofstream tf("saves/" + currentWorldName + "/torches.dat", std::ios::binary); + if (tf.is_open()) { + uint32_t count = (uint32_t)torchPositions.size(); + tf.write((char*)&count, 4); + if (count > 0) tf.write((char*)torchPositions.data(), count * sizeof(Vector3)); + tf.close(); + } // Save chunks and clear memory for (auto& pair : worldChunks) { diff --git a/version.txt b/version.txt index 2a64493..388aeab 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.2.9 +v2.2.19