MorriCraft v2.2.19: ZIP-based auto-updater and consolidated release structure

This commit is contained in:
Michael Howard 2026-04-25 10:50:58 -05:00
parent 90d6b3c6f1
commit 78bbf35bd3
6 changed files with 530 additions and 95 deletions

View File

@ -106,10 +106,15 @@ A pre-built `MorriCraft-Windows.zip` is available in the repository root.
## 📜 Version History ## 📜 Version History
### v2.2.7 - Performance & Assets (Current) ### v2.2.19 - Torch & persistence Overhaul (Current)
- **Lag Fix**: Reverted render distance to 4 and optimized frustum culling to restore high FPS. - **3D Torches**: Torches are now full 3D pillars with flickering flame effects and real-time proximity lighting (15-block radius).
- **New Icons**: Added high-quality textures for the Wooden Shovel, Pickaxe, Sword, and Hoe. - **World Persistence**: Chest contents and torch positions are now saved and reloaded per world.
- **Physics**: Refined anti-stuck resolution for smoother jumping and landing. - **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 ### v2.2.6 - World Management
- **7:00 AM Spawn**: New worlds now start in the morning for immediate daylight. - **7:00 AM Spawn**: New worlds now start in the morning for immediate daylight.

Binary file not shown.

Binary file not shown.

1
release/version.txt Normal file
View File

@ -0,0 +1 @@
v2.2.19

View File

@ -14,6 +14,7 @@
#include <thread> #include <thread>
#include <chrono> #include <chrono>
#include "rlgl.h" #include "rlgl.h"
#include <map>
#define CHUNK_SIZE 32 #define CHUNK_SIZE 32
#ifndef PI #ifndef PI
@ -28,7 +29,9 @@ enum BlockType {
STICK = 14, WOOD_AXE = 15, STICK = 14, WOOD_AXE = 15,
WOOD_PICKAXE = 16, WOOD_SWORD = 17, WOOD_SHOVEL = 18, WOOD_HOE = 19, 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, 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) { std::string GetBlockName(int type) {
@ -65,10 +68,28 @@ std::string GetBlockName(int type) {
case TORCH: return "Torch"; case TORCH: return "Torch";
case DOOR: return "Wooden Door"; case DOOR: return "Wooden Door";
case STONE_SLAB: return "Stone Slab"; 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 // Simple 2D Perlin Noise implementation
float dotGridGradient(int ix, int iy, float x, float y, unsigned int seed) { float dotGridGradient(int ix, int iy, float x, float y, unsigned int seed) {
// Proper hash with avalanche effect - seed fundamentally changes the terrain // Proper hash with avalanche effect - seed fundamentally changes the terrain
@ -133,7 +154,7 @@ struct Chunk {
bool generated = false; bool generated = false;
bool modified = false; bool modified = false;
bool dirty = true; bool dirty = true;
std::vector<BlockRenderData> renderLists[32]; std::vector<BlockRenderData> renderLists[64];
Chunk() : generated(false), modified(false), maxY(0), dirty(true) {} Chunk() : generated(false), modified(false), maxY(0), dirty(true) {}
}; };
@ -152,7 +173,7 @@ static float playerHealth = 16.0f;
static uint32_t localPlayerID = 0; static uint32_t localPlayerID = 0;
static Sound hitSound; 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 // Forward Declarations
unsigned char GetExposedFaces(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, Chunk* nxP, Chunk* nzM, Chunk* nzP); 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 { struct InventorySlot {
int blockType = AIR; int blockType = AIR;
int count = 0; int count = 0;
InventorySlot() : blockType(AIR), count(0) {} int durability = 0;
InventorySlot(int bt, int c) : blockType(bt), count(c) {} 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 static InventorySlot hotbar[9]; // 9 hotbar slots
@ -206,6 +232,28 @@ static bool isNewWorldGeneration = false;
static float spawnSavedX = 0, spawnSavedY = 0, spawnSavedZ = 0; static float spawnSavedX = 0, spawnSavedY = 0, spawnSavedZ = 0;
static int loadWorldScrollOffset = 0; static int loadWorldScrollOffset = 0;
static std::map<uint64_t, std::vector<InventorySlot>> chestInventories;
static Vector3 activeChestPos = {0};
static int cheatScrollOffset = 0;
static std::vector<Vector3> 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. // Adds one block to inventory: first fills existing stacks, then empty slots.
void AddToInventory(int blockType) { void AddToInventory(int blockType) {
// Search hotbar first // 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) { 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 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; 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); unsigned char faces = GetExposedFaces(lx, ly, lz, chunk, nxM, nxP, nzM, nzP);
if (faces != 0) { 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) { 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); SetBlock(x, y, z, type);
PacketHeader head = { (uint8_t)PACKET_BLOCK_CHANGE, (uint32_t)sizeof(PacketBlockChange) }; PacketHeader head = { (uint8_t)PACKET_BLOCK_CHANGE, (uint32_t)sizeof(PacketBlockChange) };
PacketBlockChange bc = { x, y, z, type }; 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 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]; char buffer[128];
std::string result = ""; std::string result = "";
std::string cmd = "curl -s -m 5 " + url; // 5 second timeout 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) { bool IsVersionNewer(std::string remote, std::string local) {
if (remote.empty() || remote == "error" || remote == local) return false; if (remote.empty() || remote == "error" || remote == local) return false;
auto strip = [](std::string s) { int r1=0, r2=0, r3=0, l1=0, l2=0, l3=0;
if (!s.empty() && s[0] == 'v') return s.substr(1);
return s; // 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() { void SaveConfig() {
@ -682,7 +770,7 @@ bool CheckPlayerCollision(Vector3 pos) {
for (int x = minX; x <= maxX; x++) { for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) { for (int y = minY; y <= maxY; y++) {
for (int z = minZ; z <= maxZ; z++) { 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 = { BoundingBox blockBox = {
(Vector3){ x - 0.5f, y - 0.5f, z - 0.5f }, (Vector3){ x - 0.5f, y - 0.5f, z - 0.5f },
(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) { 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; float hw = w/2.0f; float hh = h/2.0f; float hl = l/2.0f;
if (mask & 1) { // Front (+Z) if (mask & 1) { // Front (+Z)
rlNormal3f(0, 0, 1);
rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - hw, y - hh, z + hl); 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, 1.0f); rlVertex3f(x + hw, y - hh, z + hl);
rlTexCoord2f(1.0f, 0.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, 0.0f); rlVertex3f(x - hw, y + hh, z + hl);
} }
if (mask & 2) { // Back (-Z) if (mask & 2) { // Back (-Z)
rlNormal3f(0, 0, -1);
rlTexCoord2f(1.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(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, 0.0f); rlVertex3f(x + hw, y + hh, z - hl);
rlTexCoord2f(0.0f, 1.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) if (mask & 4) { // Top (+Y)
rlNormal3f(0, 1, 0);
rlTexCoord2f(0.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(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, 0.0f); rlVertex3f(x + hw, y + hh, z + hl);
rlTexCoord2f(1.0f, 1.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) if (mask & 8) { // Bottom (-Y)
rlNormal3f(0, -1, 0);
rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - hw, y - hh, z - hl); 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, 1.0f); rlVertex3f(x + hw, y - hh, z - hl);
rlTexCoord2f(0.0f, 0.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, 0.0f); rlVertex3f(x - hw, y - hh, z + hl);
} }
if (mask & 16) { // Right (+X) if (mask & 16) { // Right (+X)
rlNormal3f(1, 0, 0);
rlTexCoord2f(1.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(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, 0.0f); rlVertex3f(x + hw, y + hh, z + hl);
rlTexCoord2f(0.0f, 1.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) if (mask & 32) { // Left (-X)
rlNormal3f(-1, 0, 0);
rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - hw, y - hh, z - hl); 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, 1.0f); rlVertex3f(x - hw, y - hh, z + hl);
rlTexCoord2f(1.0f, 0.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. // By default, windows have minimize, maximize, and close buttons on the top bar.
SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT); SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT);
InitWindow(screenWidth, screenHeight, "MorriCraft v2.2.7"); InitWindow(screenWidth, screenHeight, "MorriCraft v2.2.19");
LoadConfig(); LoadConfig();
SetExitKey(KEY_NULL); // Prevent ESC from closing the window SetExitKey(KEY_NULL); // Prevent ESC from closing the window
@ -912,6 +1006,9 @@ int main(void)
Sound digWood = LoadSound("assets/wood1.ogg"); Sound digWood = LoadSound("assets/wood1.ogg");
Sound digStone = LoadSound("assets/stone1.ogg"); Sound digStone = LoadSound("assets/stone1.ogg");
Sound digSand = LoadSound("assets/sand1.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(digGrass, 0.6f);
SetSoundVolume(digWood, 0.6f); SetSoundVolume(digWood, 0.6f);
@ -931,7 +1028,7 @@ int main(void)
float updateTimer = 0.0f; float updateTimer = 0.0f;
float downloadProgress = 0.0f; float downloadProgress = 0.0f;
std::string latestVersion = ""; std::string latestVersion = "";
std::string localVersion = "v2.2.9"; // Default fallback std::string localVersion = "v2.2.19"; // Default fallback
// Read local version // Read local version
std::ifstream vfile("assets/version.txt"); std::ifstream vfile("assets/version.txt");
@ -987,6 +1084,8 @@ int main(void)
// 3D Block Textures Setup // 3D Block Textures Setup
Texture2D blockTextures[64] = {0}; Texture2D blockTextures[64] = {0};
blockTextures[CHEST] = LoadTexture("assets/chest.png");
blockTextures[TORCH] = LoadTexture("assets/torch.png");
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");
@ -1220,7 +1319,7 @@ int main(void)
// Update // Update
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Update ONLY active music streams and ensure others are fully stopped // 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) { if (!inGame) {
UpdateMusicStream(titleMusic); UpdateMusicStream(titleMusic);
@ -1536,11 +1635,11 @@ int main(void)
// --- GLOBAL TIME & AUDIO MANAGEMENT --- // --- 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(); if (currentState == GAMEPLAY) gameTime += GetFrameTime();
float timeOfDay = fmodf(gameTime, cycleLength) / cycleLength; float timeOfDay = fmodf(gameTime, cycleLength) / cycleLength;
float sunAngle = timeOfDay * 2.0f * 3.14159f - 3.14159f/2.0f; 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; float quickMix = (dayFactor - 0.5f) * 5.0f + 0.5f;
if (quickMix > 1.0f) quickMix = 1.0f; if (quickMix > 1.0f) quickMix = 1.0f;
if (quickMix < 0.0f) quickMix = 0.0f; if (quickMix < 0.0f) quickMix = 0.0f;
@ -1568,8 +1667,9 @@ int main(void)
// --- GLOBAL INPUTS (Outside gameplay state) --- // --- GLOBAL INPUTS (Outside gameplay state) ---
if (IsKeyPressed(KEY_E) && !isChatting && (currentState == GAMEPLAY || currentState == CRAFTING_GUI)) { if (IsKeyPressed(KEY_E) && !isChatting && (currentState == GAMEPLAY || currentState == CRAFTING_GUI || currentState == CHEST_GUI || currentState == CHEAT_GUI)) {
if (currentState == CRAFTING_GUI) { if (currentState == CRAFTING_GUI || currentState == CHEST_GUI || currentState == CHEAT_GUI) {
if (currentState == CHEST_GUI) PlaySound(chestCloseSound);
currentState = GAMEPLAY; currentState = GAMEPLAY;
DisableCursor(); DisableCursor();
} else { } else {
@ -1632,15 +1732,20 @@ int main(void)
isFlying = false; isFlying = false;
chatLog.push_back({ "[Server] Flight disabled", 5.0f }); chatLog.push_back({ "[Server] Flight disabled", 5.0f });
} else if (cmd == "test") { } else if (cmd == "test") {
GiveItems(CRAFTING_TABLE, 1); AddToInventory(CRAFTING_TABLE);
GiveItems(LOG, 64); for(int i=0; i<64; i++) AddToInventory(LOG);
chatLog.push_back({ "[Server] Added test items to inventory", 5.0f }); 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") { } else if (cmd == "help") {
chatLog.push_back({ "[Server] Commands List:", 8.0f }); chatLog.push_back({ "[Server] Commands List:", 8.0f });
chatLog.push_back({ " /help - Shows this 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({ " /seed - Shows the world seed", 8.0f });
chatLog.push_back({ " /fly [on|off] - Toggle flight mode", 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({ " /test - Give basic crafting materials", 8.0f });
chatLog.push_back({ " /cheat - Open creative menu", 8.0f });
} else { } else {
chatLog.push_back({ "[Server] Unknown command: /" + cmd, 5.0f }); chatLog.push_back({ "[Server] Unknown command: /" + cmd, 5.0f });
} }
@ -1664,8 +1769,7 @@ int main(void)
} }
chatInput[0] = '\0'; chatInput[0] = '\0';
} }
isChatting = false; if (currentState == GAMEPLAY) DisableCursor();
DisableCursor();
} }
} }
@ -1883,6 +1987,17 @@ int main(void)
if (breakProgress >= breakSpeed) { if (breakProgress >= breakSpeed) {
AddToInventory(targetBlock); AddToInventory(targetBlock);
NetSetBlock(hitX, hitY, hitZ, AIR); 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; breakProgress = 0.0f;
isSwinging = true; // Swing when finishing isSwinging = true; // Swing when finishing
} }
@ -1899,14 +2014,36 @@ int main(void)
if (targetBlock == CRAFTING_TABLE) { if (targetBlock == CRAFTING_TABLE) {
currentState = CRAFTING_GUI; currentState = CRAFTING_GUI;
EnableCursor(); 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<InventorySlot>(27, InventorySlot(AIR, 0));
}
currentState = CHEST_GUI;
PlaySound(chestOpenSound);
EnableCursor();
} else if (hotbar[activeHotbarSlot].count > 0) { } else if (hotbar[activeHotbarSlot].count > 0) {
int placeX = hitX + (int)roundf(closestNormal.x); int placeX = hitX + (int)roundf(closestNormal.x);
int placeY = hitY + (int)roundf(closestNormal.y); int placeY = hitY + (int)roundf(closestNormal.y);
int placeZ = hitZ + (int)roundf(closestNormal.z); int placeZ = hitZ + (int)roundf(closestNormal.z);
NetSetBlock(placeX, placeY, placeZ, hotbar[activeHotbarSlot].blockType);
hotbar[activeHotbarSlot].count--; // Check if space is occupied by player
if (hotbar[activeHotbarSlot].count == 0) BoundingBox playerBB = {
hotbar[activeHotbarSlot].blockType = AIR; (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 isSwinging = true; // Swing when placing
} }
} }
@ -2009,13 +2146,13 @@ int main(void)
std::thread([]() { std::thread([]() {
std::string binaryUrl, binaryName; std::string binaryUrl, binaryName;
#ifdef _WIN32 #ifdef _WIN32
binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/build-windows/MorriCraft.exe"; binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/release/MorriCraft-Windows.zip";
binaryName = "MorriCraft.exe"; binaryName = "MorriCraft-Windows.zip";
#else #else
binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/build-linux/MorriCraft"; binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/release/MorriCraft-Linux.zip";
binaryName = "MorriCraft"; binaryName = "MorriCraft-Linux.zip";
#endif #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 // Download binary
int r1 = system(("curl -L -s -o " + binaryName + ".new \"" + binaryUrl + "\"").c_str()); int r1 = system(("curl -L -s -o " + binaryName + ".new \"" + binaryUrl + "\"").c_str());
@ -2027,17 +2164,20 @@ int main(void)
if (r1 == 0 && r2 == 0) { if (r1 == 0 && r2 == 0) {
#ifdef _WIN32 #ifdef _WIN32
system(("rename " + binaryName + " " + binaryName + ".old").c_str()); // Windows Update: Move exe, unzip assets
system(("rename " + binaryName + ".new " + binaryName).c_str()); system("move MorriCraft.exe MorriCraft.old >nul 2>&1");
system("rename version.txt version.txt.old"); system("powershell -Command \"Expand-Archive -Path MorriCraft-Windows.zip -DestinationPath . -Force\"");
system("rename version.txt.new version.txt"); system("move version.txt.new version.txt >nul 2>&1");
system("copy version.txt assets\\version.txt >nul 2>&1"); system("copy version.txt assets\\version.txt >nul 2>&1");
system("del MorriCraft-Windows.zip");
#else #else
system(("chmod +x " + binaryName + ".new").c_str()); // Linux Update: Move binary, unzip assets
system(("mv " + binaryName + " " + binaryName + ".old 2>/dev/null").c_str()); system("mv MorriCraft MorriCraft.old 2>/dev/null");
system(("mv " + binaryName + ".new " + binaryName).c_str()); system("unzip -o MorriCraft-Linux.zip");
system("mv version.txt.new version.txt"); 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("cp version.txt assets/version.txt 2>/dev/null");
system("rm MorriCraft-Linux.zip");
#endif #endif
currentProgress = 1.0f; currentProgress = 1.0f;
downloadFinished = true; downloadFinished = true;
@ -2057,7 +2197,7 @@ int main(void)
DrawRectangleRec(backBtn, DARKGRAY); DrawRectangleRec(backBtn, DARKGRAY);
DrawTextEx(customFont, "BACK", (Vector2){ backBtn.x + 70, backBtn.y + 10 }, 20, 1.0f, WHITE); DrawTextEx(customFont, "BACK", (Vector2){ backBtn.x + 70, backBtn.y + 10 }, 20, 1.0f, WHITE);
} else { } 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; int bw = 500, bh = 35;
Rectangle barBg = { (float)currentWidth/2 - bw/2, (float)currentHeight/2 - bh/2, (float)bw, (float)bh }; Rectangle barBg = { (float)currentWidth/2 - bw/2, (float)currentHeight/2 - bh/2, (float)bw, (float)bh };
DrawRectangleRec(barBg, BLACK); DrawRectangleRec(barBg, BLACK);
@ -2070,7 +2210,6 @@ int main(void)
int pw = 500, ph = 220; int pw = 500, ph = 220;
Rectangle pBox = { (float)currentWidth/2 - pw/2, (float)currentHeight/2 - ph/2, (float)pw, (float)ph }; Rectangle pBox = { (float)currentWidth/2 - pw/2, (float)currentHeight/2 - ph/2, (float)pw, (float)ph };
DrawRectangleRec(pBox, (Color){ 30, 30, 30, 255 }); 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, "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); 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); 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) { } else if (currentState == SKIN_EDITOR) {
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 20, 20, 20, 255 }); DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 20, 20, 20, 255 });
int panelWidth = 700; int panelWidth = 700;
@ -2315,8 +2469,8 @@ int main(void)
DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE); DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE);
EndMode2D(); EndMode2D();
// Show Version Number (v2.2.9) in Red // Show Version Number (v2.2.19) in Red
DrawTextEx(customFont, "v2.2.9", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); DrawTextEx(customFont, "v2.2.19", (Vector2){ (float)currentWidth - 120, (float)currentHeight - 30 }, 22, 1.0f, RED);
// --- PLAYER NAME POPUP (IF MISSING) --- // --- PLAYER NAME POPUP (IF MISSING) ---
if (playerName == "") { if (playerName == "") {
@ -2489,11 +2643,38 @@ int main(void)
if (worldFile.is_open()) { if (worldFile.is_open()) {
worldFile >> globalSeedHash >> spawnSavedX >> spawnSavedY >> spawnSavedZ; worldFile >> globalSeedHash >> spawnSavedX >> spawnSavedY >> spawnSavedZ;
worldFile.close(); worldFile.close();
} else {
globalSeedHash = 12345; globalSeedHash = 12345;
spawnSavedX = 0; spawnSavedY = -1; spawnSavedZ = 0; 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<count; i++) {
uint64_t key; cf.read((char*)&key, 8);
std::vector<InventorySlot> 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 // Clear old chunks
for (auto& pair : worldChunks) delete pair.second; for (auto& pair : worldChunks) delete pair.second;
worldChunks.clear(); worldChunks.clear();
@ -2544,20 +2725,19 @@ int main(void)
if (showDeleteConfirm) { if (showDeleteConfirm) {
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 200 }); DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 200 });
int confWidth = 550; int confWidth = 650;
int confHeight = 240; int confHeight = 300;
int confX = (currentWidth / 2) - (confWidth / 2); int confX = (currentWidth / 2) - (confWidth / 2);
int confY = (currentHeight / 2) - (confHeight / 2); int confY = (currentHeight / 2) - (confHeight / 2);
DrawRectangle(confX, confY, confWidth, confHeight, (Color){ 50, 20, 20, 255 }); 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); 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 + "'?"; std::string warnText = "Are you sure you want to delete '" + deletingWorldName + "'?";
Vector2 nameSize = MeasureTextEx(customFont, warnText.c_str(), 22, 1.0f); 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 // Yes Button
Rectangle yesBtn = { (float)confX + 80, (float)confY + 150, 140, 40 }; Rectangle yesBtn = { (float)confX + 80, (float)confY + 150, 140, 40 };
@ -2809,7 +2989,7 @@ int main(void)
} }
// Draw Gameplay overlay if we entered gameplay // 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 = ""; hoveredItemName = "";
// ---- Atmospheric Calculations (Previously global) ---- // ---- Atmospheric Calculations (Previously global) ----
// lightLevel and blockTint are derived from the global dayFactor // 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 < 64; renderType++) {
for (int renderType = 1; renderType <= 13; renderType++) {
if (renderType == GRASS) { if (renderType == GRASS) {
for (Chunk* chunk : visibleChunks) { for (Chunk* chunk : visibleChunks) {
for (auto& data : chunk->renderLists[GRASS]) { 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) { } else if (renderType == LOG) {
for (Chunk* chunk : visibleChunks) { for (Chunk* chunk : visibleChunks) {
for (auto& data : chunk->renderLists[LOG]) { 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) { } else if (renderType == CRAFTING_TABLE) {
for (Chunk* chunk : visibleChunks) { for (Chunk* chunk : visibleChunks) {
for (auto& data : chunk->renderLists[CRAFTING_TABLE]) { 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 { } else {
rlSetTexture(blockTextures[renderType].id); rlSetTexture(blockTextures[renderType].id);
rlBegin(RL_QUADS); 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 (Chunk* chunk : visibleChunks) {
for (auto& data : chunk->renderLists[renderType]) { 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); 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; float size = 1.02f;
DrawCubeWires((Vector3){(float)hitX, (float)hitY, (float)hitZ}, size, size, size, BLACK); DrawCubeWires((Vector3){(float)hitX, (float)hitY, (float)hitZ}, size, size, size, BLACK);
// Draw breaking progress indicator
// Draw breaking progress indicator // Draw breaking progress indicator
if (breakProgress > 0.0f) { if (breakProgress > 0.0f) {
float pSize = (breakProgress / 1.5f) * 1.05f; // Scale visual based on progress 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(rightVM, 0.28f));
handPos = Vector3Add(handPos, Vector3Scale(upVM, -0.25f - swingVal * 0.15f)); handPos = Vector3Add(handPos, Vector3Scale(upVM, -0.25f - swingVal * 0.15f));
// Draw Arm (rotated cube) // Viewmodel logic
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)
int heldBT = hotbar[activeHotbarSlot].blockType; int heldBT = hotbar[activeHotbarSlot].blockType;
if (heldBT != AIR) {
// Calculate position at the end of the arm if (heldBT == AIR) {
// The arm is rotated by camPitch - 15.0f - swingVal * 20.0f // Draw Arm (rotated cube)
float armPitch = (camPitch * 180.0f/PI - 15.0f - swingVal * 20.0f) * PI/180.0f; rlPushMatrix();
Vector3 armForward = { rlTranslatef(handPos.x, handPos.y, handPos.z);
forwardVM.x * cosf(-armPitch) - forwardVM.y * sinf(-armPitch), rlRotatef(camYaw * 180.0f/PI, 0, 1, 0);
forwardVM.x * sinf(-armPitch) + forwardVM.y * cosf(-armPitch), rlRotatef(camPitch * 180.0f/PI - 15.0f - swingVal * 20.0f, 1, 0, 0);
forwardVM.z DrawCube((Vector3){0,0,0}, 0.1f, 0.12f, 0.4f, (Color){220, 180, 150, 255});
}; // This is a simplification, but let's use the handPos + offset rlPopMatrix();
} else {
// Draw Held Item
Vector3 itemPos = Vector3Add(handPos, Vector3Scale(forwardVM, 0.25f)); Vector3 itemPos = Vector3Add(handPos, Vector3Scale(forwardVM, 0.25f));
itemPos = Vector3Add(itemPos, Vector3Scale(upVM, 0.05f)); itemPos = Vector3Add(itemPos, Vector3Scale(upVM, 0.05f));
itemPos = Vector3Add(itemPos, Vector3Scale(rightVM, -0.05f)); itemPos = Vector3Add(itemPos, Vector3Scale(rightVM, -0.05f));
@ -3003,17 +3223,19 @@ int main(void)
rlTranslatef(itemPos.x, itemPos.y, itemPos.z); rlTranslatef(itemPos.x, itemPos.y, itemPos.z);
rlRotatef(camYaw * 180.0f/PI + 45.0f, 0, 1, 0); rlRotatef(camYaw * 180.0f/PI + 45.0f, 0, 1, 0);
rlRotatef(20.0f, 1, 0, 1); 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(); rlPopMatrix();
rlSetTexture(0); rlSetTexture(0);
} else { } else {
// Item (Stick/Axe) as Billboard // Item (Stick/Axe) as Billboard
Rectangle src = { 0, 0, (float)itemTex.width, (float)itemTex.height }; Rectangle src = { 0, 0, (float)itemTex.width, (float)itemTex.height };
Vector2 itemSize = { 0.45f, 0.45f }; Vector2 itemSize = { 0.35f, 0.35f };
// Pivot at bottom-left corner (Bug #3 fix)
// In DrawBillboardPro, origin (0,0) is center. Bottom-left is (-w/2, -h/2).
Vector2 pivot = { -itemSize.x/2.0f, -itemSize.y/2.0f }; 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), DrawTextEx(customFont, TextFormat("%i", hotbar[s].count),
(Vector2){ (float)(sx + (slotSize/2) - (countSize.x/2)), (float)(hotbarY + slotSize - 14) }, (Vector2){ (float)(sx + (slotSize/2) - (countSize.x/2)), (float)(hotbarY + slotSize - 14) },
12, 1.0f, WHITE); 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 // Slot number label
@ -3140,6 +3371,15 @@ int main(void)
DrawRectangleRec(slotRect, hov ? (Color){90,90,90,255} : (Color){60,60,60,255}); DrawRectangleRec(slotRect, hov ? (Color){90,90,90,255} : (Color){60,60,60,255});
DrawRectangleLinesEx(slotRect, 2.0f, hov ? WHITE : (Color){100,100,100,200}); 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) { if (hov && slot.blockType != AIR) {
hoveredItemName = GetBlockName(slot.blockType); 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 --- // --- Draw Held Item Last ---
if (mouseHeldItem.blockType != AIR && mouseHeldItem.count > 0) { if (mouseHeldItem.blockType != AIR && mouseHeldItem.count > 0) {
int bt = mouseHeldItem.blockType; int bt = mouseHeldItem.blockType;
@ -3463,6 +3867,31 @@ int main(void)
invf.write((char*)&activeHotbarSlot, sizeof(activeHotbarSlot)); invf.write((char*)&activeHotbarSlot, sizeof(activeHotbarSlot));
invf.close(); 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 // Save chunks and clear memory
for (auto& pair : worldChunks) { for (auto& pair : worldChunks) {

View File

@ -1 +1 @@
v2.2.9 v2.2.19