MorriCraft v2.2.19: ZIP-based auto-updater and consolidated release structure
This commit is contained in:
parent
90d6b3c6f1
commit
78bbf35bd3
13
README.md
13
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.
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
v2.2.19
|
||||
609
src/main.cpp
609
src/main.cpp
|
|
@ -14,6 +14,7 @@
|
|||
#include <thread>
|
||||
#include <chrono>
|
||||
#include "rlgl.h"
|
||||
#include <map>
|
||||
|
||||
#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<BlockRenderData> renderLists[32];
|
||||
std::vector<BlockRenderData> 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<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.
|
||||
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<InventorySlot>(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<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
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
v2.2.9
|
||||
v2.2.19
|
||||
|
|
|
|||
Loading…
Reference in New Issue