MorriCraft v2.2.6: 7AM Spawn, Naming Refinements, and Anti-Stuck Physics
This commit is contained in:
parent
1a1cf3cc5f
commit
091cfab86f
Binary file not shown.
33
README.md
33
README.md
|
|
@ -106,7 +106,38 @@ A pre-built `MorriCraft-Windows.zip` is available in the repository root.
|
||||||
|
|
||||||
## 📜 Version History
|
## 📜 Version History
|
||||||
|
|
||||||
### v2.1.9 - Crafting Overhaul (Latest)
|
### v2.2.6 - Physics & World Management (Current)
|
||||||
|
- **7:00 AM Spawn**: New worlds now start in the morning for immediate daylight.
|
||||||
|
- **Naming System**: Cleaner sequential naming (`World`, `World 1`, `World 2`) instead of nested parentheses.
|
||||||
|
- **Anti-Stuck Physics**: Active collision resolution pushes players out of blocks if they overlap.
|
||||||
|
- **Build Path**: Windows artifacts are now stored in `build-windows/` for easier deployment.
|
||||||
|
|
||||||
|
### v2.2.5 - UX & Inventory Polish
|
||||||
|
- **Shift-Click**: Holding Shift and clicking picks up entire item stacks instantly.
|
||||||
|
- **Scrollable Menus**: Added scroll bars and mouse-wheel support for the "Load World" menu.
|
||||||
|
- **UI Padding**: Increased padding across all panels to ensure text never overlaps borders.
|
||||||
|
|
||||||
|
### v2.2.4 - Visual & Update Stability
|
||||||
|
- **X-Ray Fix**: Neighborhood chunk dirtying ensures internal faces are culled immediately.
|
||||||
|
- **Smart Updates**: Client only prompts for updates if the remote version is strictly higher.
|
||||||
|
- **15x15 Pre-Gen**: Expanded spawn area pre-generation (225 chunks) to eliminate horizon drop-offs.
|
||||||
|
|
||||||
|
### v2.2.3 - Async Generation & Help
|
||||||
|
- **Loading Screen**: Added a progress bar for asynchronous world generation/loading.
|
||||||
|
- **Help Command**: Added `/help` to chat to display all available console commands.
|
||||||
|
- **Spawn Surface**: Guaranteed surface placement after full chunk pre-generation.
|
||||||
|
|
||||||
|
### v2.2.1 - World & Seed Polish
|
||||||
|
- **Cliff Fix**: Resolved issues where new worlds would spawn with missing chunks.
|
||||||
|
- **Render Distance**: Increased default render distance to 4 for a more expansive view.
|
||||||
|
- **Auto-Updater**: Platform-aware updates target Windows vs Linux binaries.
|
||||||
|
|
||||||
|
### v2.2.0 - Biome & Generation Update
|
||||||
|
- **Seed Fix**: Proper avalanche-hash for seeds, making every world truly unique.
|
||||||
|
- **Biomes**: Added Grassland, Desert, and Rocky biomes with sand beaches at sea level.
|
||||||
|
- **Commands**: Added `/seed` command to view the world seed in chat.
|
||||||
|
|
||||||
|
### v2.1.9 - Crafting Overhaul
|
||||||
- **20+ Recipes**: Added Log→Planks and full wooden/stone tool tier crafting.
|
- **20+ Recipes**: Added Log→Planks and full wooden/stone tool tier crafting.
|
||||||
- **Smart Inventory**: Left-click stacks same items, right-click picks up or places one.
|
- **Smart Inventory**: Left-click stacks same items, right-click picks up or places one.
|
||||||
- **Future Item IDs**: Added furnace, chest, ladder, fence, torch, door, and stone slab types.
|
- **Future Item IDs**: Added furnace, chest, ladder, fence, torch, door, and stone slab types.
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1 +1 @@
|
||||||
v2.1.9
|
v2.2.6
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1 +1 @@
|
||||||
v2.1.9
|
v2.2.6
|
||||||
|
|
|
||||||
524
src/main.cpp
524
src/main.cpp
|
|
@ -20,7 +20,7 @@
|
||||||
#define PI 3.1415926535f
|
#define PI 3.1415926535f
|
||||||
#endif
|
#endif
|
||||||
#define CHUNK_HEIGHT 128
|
#define CHUNK_HEIGHT 128
|
||||||
#define RENDER_DISTANCE 2
|
#define RENDER_DISTANCE 4
|
||||||
|
|
||||||
enum BlockType {
|
enum BlockType {
|
||||||
AIR = 0, GRASS = 1, DIRT = 2, COBBLESTONE = 3, LOG = 4, LEAVES = 5, PLANK = 6,
|
AIR = 0, GRASS = 1, DIRT = 2, COBBLESTONE = 3, LOG = 4, LEAVES = 5, PLANK = 6,
|
||||||
|
|
@ -71,8 +71,18 @@ std::string GetBlockName(int type) {
|
||||||
|
|
||||||
// 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) {
|
||||||
unsigned int hash = ix * 3284157443 ^ iy * 1911520717;
|
// Proper hash with avalanche effect - seed fundamentally changes the terrain
|
||||||
hash ^= seed;
|
unsigned int hash = seed;
|
||||||
|
hash ^= (unsigned int)ix * 374761393u;
|
||||||
|
hash = (hash << 17) | (hash >> 15);
|
||||||
|
hash *= 1103515245u;
|
||||||
|
hash ^= (unsigned int)iy * 668265263u;
|
||||||
|
hash = (hash << 13) | (hash >> 19);
|
||||||
|
hash *= 2654435761u;
|
||||||
|
hash ^= hash >> 16;
|
||||||
|
hash *= 2246822519u;
|
||||||
|
hash ^= hash >> 13;
|
||||||
|
|
||||||
float random = hash * (3.14159265f / ~(~0u >> 1));
|
float random = hash * (3.14159265f / ~(~0u >> 1));
|
||||||
float gradientX = cosf(random);
|
float gradientX = cosf(random);
|
||||||
float gradientY = sinf(random);
|
float gradientY = sinf(random);
|
||||||
|
|
@ -137,7 +147,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 };
|
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 };
|
||||||
|
|
||||||
// Forward Declarations
|
// Forward Declarations
|
||||||
bool IsExposedOptimized(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, Chunk* nxP, Chunk* nzM, Chunk* nzP);
|
bool IsExposedOptimized(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, Chunk* nxP, Chunk* nzM, Chunk* nzP);
|
||||||
|
|
@ -183,6 +193,13 @@ static InventorySlot hotbar[9]; // 9 hotbar slots
|
||||||
static InventorySlot inventory[27]; // 3x9 main inventory
|
static InventorySlot inventory[27]; // 3x9 main inventory
|
||||||
static int activeHotbarSlot = 0;
|
static int activeHotbarSlot = 0;
|
||||||
static bool inventoryOpen = false;
|
static bool inventoryOpen = false;
|
||||||
|
static bool isFlying = false;
|
||||||
|
static float worldGenProgress = 0.0f;
|
||||||
|
static int chunksGeneratedCount = 0;
|
||||||
|
static const int totalChunksToPreGen = 225; // 15x15 area around spawn
|
||||||
|
static bool isNewWorldGeneration = false;
|
||||||
|
static float spawnSavedX = 0, spawnSavedY = 0, spawnSavedZ = 0;
|
||||||
|
static int loadWorldScrollOffset = 0;
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
@ -382,10 +399,20 @@ std::string GetRemoteVersion() {
|
||||||
pclose(pipe);
|
pclose(pipe);
|
||||||
// Trim result
|
// Trim result
|
||||||
size_t last = result.find_last_not_of(" \n\r\t");
|
size_t last = result.find_last_not_of(" \n\r\t");
|
||||||
if (last != std::string::npos) result = result.substr(0, last + 1);
|
if (last != std::string::npos) result.erase(last + 1);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
// Simple lexicographical comparison for v2.x.x format
|
||||||
|
return strip(remote) > strip(local);
|
||||||
|
}
|
||||||
|
|
||||||
void SaveConfig() {
|
void SaveConfig() {
|
||||||
std::ofstream file("config.cfg");
|
std::ofstream file("config.cfg");
|
||||||
if (file.is_open()) {
|
if (file.is_open()) {
|
||||||
|
|
@ -491,11 +518,13 @@ float FindSpawnY(int spawnX, int spawnZ) {
|
||||||
GenerateChunk(cx, cz);
|
GenerateChunk(cx, cz);
|
||||||
for (int y = CHUNK_HEIGHT - 1; y >= 1; y--) {
|
for (int y = CHUNK_HEIGHT - 1; y >= 1; y--) {
|
||||||
if (GetBlock(spawnX, y, spawnZ) != AIR) {
|
if (GetBlock(spawnX, y, spawnZ) != AIR) {
|
||||||
// Feet land at top of block (y + 0.5), eye is 1.6 above feet
|
// Feet land at top of block (y + 0.5).
|
||||||
return (float)y + 0.5f + 1.6f;
|
// We use +0.6f + 1.6f to ensure we start slightly ABOVE the block
|
||||||
|
// to avoid getting stuck in collision on frame 1.
|
||||||
|
return (float)y + 0.6f + 1.6f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 40.0f; // Fallback
|
return 64.0f; // Safer fallback (above typical y=32 ground)
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenerateChunk(int cx, int cz) {
|
void GenerateChunk(int cx, int cz) {
|
||||||
|
|
@ -520,24 +549,44 @@ void GenerateChunk(int cx, int cz) {
|
||||||
float worldX = cx * CHUNK_SIZE + x;
|
float worldX = cx * CHUNK_SIZE + x;
|
||||||
float worldZ = cz * CHUNK_SIZE + z;
|
float worldZ = cz * CHUNK_SIZE + z;
|
||||||
|
|
||||||
// Multi-octave fbm noise at a much lower frequency for broad, smooth hills.
|
// Multi-layer terrain generation for interesting landscapes
|
||||||
// Base freq 0.006 means features span ~160 blocks, preventing steep cliffs.
|
// Layer 1: Continent-scale rolling hills (broad, smooth features)
|
||||||
float noiseVal = fbm(worldX * 0.006f, worldZ * 0.006f, globalSeedHash);
|
float continentNoise = fbm(worldX * 0.004f, worldZ * 0.004f, globalSeedHash);
|
||||||
// Terrain sits around Y=32 with only ±6 blocks of variation,
|
// Layer 2: Local detail bumps (smaller, sharper features)
|
||||||
// so the minimum ground level is 26 -- no sudden deep holes.
|
float detailNoise = fbm(worldX * 0.015f, worldZ * 0.015f, globalSeedHash + 9999);
|
||||||
int height = 32 + (int)(noiseVal * 6.0f);
|
// Combine: broad hills (±12) + local detail (±4) = up to ±16 around Y=32
|
||||||
|
int height = 32 + (int)(continentNoise * 12.0f) + (int)(detailNoise * 4.0f);
|
||||||
if (height < 10) height = 10;
|
if (height < 10) height = 10;
|
||||||
if (height >= CHUNK_HEIGHT - 2) height = CHUNK_HEIGHT - 2;
|
if (height >= CHUNK_HEIGHT - 2) height = CHUNK_HEIGHT - 2;
|
||||||
|
|
||||||
|
// Biome noise: determines surface type
|
||||||
|
float biomeNoise = fbm(worldX * 0.008f, worldZ * 0.008f, globalSeedHash + 77777);
|
||||||
|
|
||||||
for (int y = 0; y <= height; y++) {
|
for (int y = 0; y <= height; y++) {
|
||||||
if (y == 0) {
|
if (y == 0) {
|
||||||
newChunk->blocks[x][y][z] = BEDROCK;
|
newChunk->blocks[x][y][z] = BEDROCK;
|
||||||
} else if (y == height) {
|
} else if (y == height) {
|
||||||
newChunk->blocks[x][y][z] = GRASS;
|
// Surface block depends on biome and height
|
||||||
|
if (height <= 30) {
|
||||||
|
// Low areas = beach/sand
|
||||||
|
newChunk->blocks[x][y][z] = SAND;
|
||||||
|
} else if (biomeNoise > 0.3f) {
|
||||||
|
// Desert biome
|
||||||
|
newChunk->blocks[x][y][z] = SAND;
|
||||||
|
} else if (biomeNoise < -0.3f) {
|
||||||
|
// Rocky biome
|
||||||
|
newChunk->blocks[x][y][z] = COBBLESTONE;
|
||||||
|
} else {
|
||||||
|
// Normal grassland
|
||||||
|
newChunk->blocks[x][y][z] = GRASS;
|
||||||
|
}
|
||||||
} else if (y > height - 4) {
|
} else if (y > height - 4) {
|
||||||
// 4 blocks of dirt under the surface — thick enough that ores
|
// Sub-surface: sand in desert/beach, dirt elsewhere
|
||||||
// can never be exposed by normal terrain features.
|
if (height <= 30 || biomeNoise > 0.3f) {
|
||||||
newChunk->blocks[x][y][z] = DIRT;
|
newChunk->blocks[x][y][z] = SAND;
|
||||||
|
} else {
|
||||||
|
newChunk->blocks[x][y][z] = DIRT;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newChunk->blocks[x][y][z] = STONE;
|
newChunk->blocks[x][y][z] = STONE;
|
||||||
|
|
||||||
|
|
@ -576,12 +625,19 @@ void GenerateChunk(int cx, int cz) {
|
||||||
newChunk->generated = true;
|
newChunk->generated = true;
|
||||||
newChunk->modified = true; // Newly generated, so we should save it
|
newChunk->modified = true; // Newly generated, so we should save it
|
||||||
worldChunks[key] = newChunk;
|
worldChunks[key] = newChunk;
|
||||||
|
|
||||||
|
// Mark neighbors as dirty so they rebuild their render lists with new occlusion data
|
||||||
|
ChunkPos neighbors[] = { {cx-1, cz}, {cx+1, cz}, {cx, cz-1}, {cx, cz+1} };
|
||||||
|
for (auto& nPos : neighbors) {
|
||||||
|
auto it = worldChunks.find(nPos);
|
||||||
|
if (it != worldChunks.end()) it->second->dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CheckPlayerCollision(Vector3 pos) {
|
bool CheckPlayerCollision(Vector3 pos) {
|
||||||
BoundingBox playerBox = {
|
BoundingBox playerBox = {
|
||||||
(Vector3){ pos.x - 0.3f, pos.y - 1.5f, pos.z - 0.3f },
|
(Vector3){ pos.x - 0.26f, pos.y - 1.45f, pos.z - 0.26f },
|
||||||
(Vector3){ pos.x + 0.3f, pos.y + 0.1f, pos.z + 0.3f }
|
(Vector3){ pos.x + 0.26f, pos.y + 0.05f, pos.z + 0.26f }
|
||||||
};
|
};
|
||||||
|
|
||||||
int minX = (int)floorf(playerBox.min.x + 0.5f);
|
int minX = (int)floorf(playerBox.min.x + 0.5f);
|
||||||
|
|
@ -843,7 +899,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.1.9");
|
InitWindow(screenWidth, screenHeight, "MorriCraft v2.2.6");
|
||||||
LoadConfig();
|
LoadConfig();
|
||||||
SetExitKey(KEY_NULL); // Prevent ESC from closing the window
|
SetExitKey(KEY_NULL); // Prevent ESC from closing the window
|
||||||
|
|
||||||
|
|
@ -925,7 +981,7 @@ int main(void)
|
||||||
bool isChatting = false;
|
bool isChatting = false;
|
||||||
char chatInput[128] = {0};
|
char chatInput[128] = {0};
|
||||||
uint32_t myNetID = 0; // Assigned by server if client
|
uint32_t myNetID = 0; // Assigned by server if client
|
||||||
float gameTime = 75.0f; // Start at 6:00 AM
|
float gameTime = 87.5f; // Start at 7:00 AM
|
||||||
float breakProgress = 0.0f;
|
float breakProgress = 0.0f;
|
||||||
int lastHitX = -1, lastHitY = -1, lastHitZ = -1;
|
int lastHitX = -1, lastHitY = -1, lastHitZ = -1;
|
||||||
float swingTime = 0.0f;
|
float swingTime = 0.0f;
|
||||||
|
|
@ -1575,20 +1631,44 @@ int main(void)
|
||||||
if (IsKeyPressed(KEY_ENTER)) {
|
if (IsKeyPressed(KEY_ENTER)) {
|
||||||
if (isChatting) {
|
if (isChatting) {
|
||||||
if (strlen(chatInput) > 0) {
|
if (strlen(chatInput) > 0) {
|
||||||
// Send Chat Packet
|
if (chatInput[0] == '/') {
|
||||||
PacketHeader head = { (uint8_t)PACKET_CHAT, (uint32_t)sizeof(PacketChat) };
|
// Command processing - never sent to network
|
||||||
PacketChat pc;
|
std::string cmd(chatInput + 1); // skip the '/'
|
||||||
strncpy(pc.name, playerName.c_str(), 31);
|
// Trim and lowercase
|
||||||
strncpy(pc.message, chatInput, 127);
|
while (!cmd.empty() && cmd.back() == ' ') cmd.pop_back();
|
||||||
if (clientSocket != INVALID_SOCKET_VAL) {
|
|
||||||
SendAll(clientSocket, (char*)&head, sizeof(head));
|
if (cmd == "seed") {
|
||||||
SendAll(clientSocket, (char*)&pc, sizeof(pc));
|
chatLog.push_back({ "[Server] World seed: " + std::to_string(globalSeedHash), 8.0f });
|
||||||
}
|
} else if (cmd == "fly on") {
|
||||||
if (serverSocket != INVALID_SOCKET_VAL) {
|
isFlying = true;
|
||||||
chatLog.push_back({ std::string(playerName) + ": " + chatInput, 5.0f });
|
chatLog.push_back({ "[Server] Flight enabled (Noclip ON)", 5.0f });
|
||||||
for (auto& s : clientSockets) {
|
} else if (cmd == "fly off") {
|
||||||
SendAll(s, (char*)&head, sizeof(head));
|
isFlying = false;
|
||||||
SendAll(s, (char*)&pc, sizeof(pc));
|
chatLog.push_back({ "[Server] Flight disabled", 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 });
|
||||||
|
} else {
|
||||||
|
chatLog.push_back({ "[Server] Unknown command: /" + cmd, 5.0f });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normal chat message - send to network
|
||||||
|
PacketHeader head = { (uint8_t)PACKET_CHAT, (uint32_t)sizeof(PacketChat) };
|
||||||
|
PacketChat pc;
|
||||||
|
strncpy(pc.name, playerName.c_str(), 31);
|
||||||
|
strncpy(pc.message, chatInput, 127);
|
||||||
|
if (clientSocket != INVALID_SOCKET_VAL) {
|
||||||
|
SendAll(clientSocket, (char*)&head, sizeof(head));
|
||||||
|
SendAll(clientSocket, (char*)&pc, sizeof(pc));
|
||||||
|
}
|
||||||
|
if (serverSocket != INVALID_SOCKET_VAL) {
|
||||||
|
chatLog.push_back({ std::string(playerName) + ": " + chatInput, 5.0f });
|
||||||
|
for (auto& s : clientSockets) {
|
||||||
|
SendAll(s, (char*)&head, sizeof(head));
|
||||||
|
SendAll(s, (char*)&pc, sizeof(pc));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chatInput[0] = '\0';
|
chatInput[0] = '\0';
|
||||||
|
|
@ -1642,23 +1722,31 @@ int main(void)
|
||||||
|
|
||||||
if (Vector3Length(moveVec) > 0) {
|
if (Vector3Length(moveVec) > 0) {
|
||||||
moveVec = Vector3Normalize(moveVec);
|
moveVec = Vector3Normalize(moveVec);
|
||||||
float speed = 5.0f * GetFrameTime();
|
float speed = (isFlying ? 15.0f : 5.0f) * GetFrameTime();
|
||||||
|
|
||||||
Vector3 tryX = { oldPos.x + moveVec.x * speed, oldPos.y, oldPos.z };
|
Vector3 tryX = { oldPos.x + moveVec.x * speed, oldPos.y, oldPos.z };
|
||||||
if (!CheckPlayerCollision(tryX)) camera3D.position.x = tryX.x;
|
if (isFlying || !CheckPlayerCollision(tryX)) camera3D.position.x = tryX.x;
|
||||||
|
|
||||||
Vector3 tryZ = { camera3D.position.x, oldPos.y, oldPos.z + moveVec.z * speed };
|
Vector3 tryZ = { camera3D.position.x, oldPos.y, oldPos.z + moveVec.z * speed };
|
||||||
if (!CheckPlayerCollision(tryZ)) camera3D.position.z = tryZ.z;
|
if (isFlying || !CheckPlayerCollision(tryZ)) camera3D.position.z = tryZ.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Vertical Physics (Ground-Lock System) ----
|
// ---- Vertical Physics (Ground-Lock / Flight System) ----
|
||||||
if (isGrounded) {
|
if (isFlying) {
|
||||||
|
playerVelocityY = 0.0f;
|
||||||
|
isGrounded = false;
|
||||||
|
float flySpeed = 10.0f * GetFrameTime();
|
||||||
|
if (!inventoryOpen && !isChatting) {
|
||||||
|
if (IsKeyDown(KEY_SPACE)) camera3D.position.y += flySpeed;
|
||||||
|
if (IsKeyDown(KEY_LEFT_SHIFT)) camera3D.position.y -= flySpeed;
|
||||||
|
}
|
||||||
|
} else if (isGrounded) {
|
||||||
playerVelocityY = 0.0f;
|
playerVelocityY = 0.0f;
|
||||||
|
|
||||||
// Keep player exactly on top of the block with a small epsilon
|
// Keep player exactly on top of the block with a small epsilon
|
||||||
float feetY = camera3D.position.y - 1.6f;
|
float feetY = camera3D.position.y - 1.6f;
|
||||||
float expectedFeetY = floorf(feetY + 0.1f) + 0.5f;
|
float expectedFeetY = floorf(feetY + 0.15f) + 0.5f;
|
||||||
camera3D.position.y = expectedFeetY + 1.6f + 0.01f;
|
camera3D.position.y = expectedFeetY + 1.6f + 0.03f;
|
||||||
|
|
||||||
// Jump
|
// Jump
|
||||||
if (IsKeyPressed(KEY_SPACE) && !inventoryOpen && !isChatting) {
|
if (IsKeyPressed(KEY_SPACE) && !inventoryOpen && !isChatting) {
|
||||||
|
|
@ -1682,7 +1770,7 @@ int main(void)
|
||||||
if (playerVelocityY < 0.0f) {
|
if (playerVelocityY < 0.0f) {
|
||||||
// Landed: Lock to surface
|
// Landed: Lock to surface
|
||||||
float feetY = nextYPos.y - 1.6f;
|
float feetY = nextYPos.y - 1.6f;
|
||||||
camera3D.position.y = floorf(feetY + 0.1f) + 0.5f + 1.6f + 0.01f;
|
camera3D.position.y = floorf(feetY + 0.15f) + 0.5f + 1.6f + 0.03f;
|
||||||
playerVelocityY = 0.0f;
|
playerVelocityY = 0.0f;
|
||||||
isGrounded = true;
|
isGrounded = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1692,8 +1780,14 @@ int main(void)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
camera3D.position.y = nextYPos.y;
|
camera3D.position.y = nextYPos.y;
|
||||||
|
isGrounded = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve any lingering overlap to prevent getting stuck
|
||||||
|
if (!isFlying && CheckPlayerCollision(camera3D.position)) {
|
||||||
|
camera3D.position.y += 0.05f;
|
||||||
|
}
|
||||||
|
|
||||||
// Final Camera state
|
// Final Camera state
|
||||||
camera3D.target.x = camera3D.position.x + sinf(camYaw) * cosf(camPitch);
|
camera3D.target.x = camera3D.position.x + sinf(camYaw) * cosf(camPitch);
|
||||||
|
|
@ -1887,7 +1981,7 @@ int main(void)
|
||||||
// Perform real check after 1 second
|
// Perform real check after 1 second
|
||||||
if (updateTimer > 1.0f && latestVersion == "") {
|
if (updateTimer > 1.0f && latestVersion == "") {
|
||||||
latestVersion = GetRemoteVersion();
|
latestVersion = GetRemoteVersion();
|
||||||
if (latestVersion != "error" && latestVersion != localVersion) {
|
if (IsVersionNewer(latestVersion, localVersion)) {
|
||||||
updateReady = true;
|
updateReady = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1929,20 +2023,36 @@ int main(void)
|
||||||
|
|
||||||
if (!startedDownload) {
|
if (!startedDownload) {
|
||||||
startedDownload = true;
|
startedDownload = true;
|
||||||
// REAL DOWNLOAD ATTEMPT
|
// REAL DOWNLOAD ATTEMPT - Platform-aware URLs
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::string binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/build-windows/MorriCraft.exe";
|
||||||
|
std::string binaryName = "MorriCraft.exe";
|
||||||
|
#else
|
||||||
std::string binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/build-linux/MorriCraft";
|
std::string binaryUrl = "https://git.linology.tech/michael/MorriCraft/raw/branch/master/build-linux/MorriCraft";
|
||||||
|
std::string binaryName = "MorriCraft";
|
||||||
|
#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/version.txt";
|
||||||
|
|
||||||
// We run these in background-ish or just block for a bit since it's a small app
|
// Download new binary and version file
|
||||||
int r1 = system(("curl -L -s -o MorriCraft.new " + binaryUrl).c_str());
|
int r1 = system(("curl -L -s -o " + binaryName + ".new \"" + binaryUrl + "\"").c_str());
|
||||||
int r2 = system(("curl -L -s -o version.txt.new " + versionUrl).c_str());
|
int r2 = system(("curl -L -s -o version.txt.new \"" + versionUrl + "\"").c_str());
|
||||||
|
|
||||||
if (r1 == 0 && r2 == 0) {
|
if (r1 == 0 && r2 == 0) {
|
||||||
system("chmod +x MorriCraft.new");
|
#ifdef _WIN32
|
||||||
system("mv MorriCraft MorriCraft.old 2>/dev/null");
|
// Windows: rename old, move new into place
|
||||||
system("mv MorriCraft.new MorriCraft");
|
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");
|
||||||
|
system("copy version.txt assets\\version.txt >nul 2>&1");
|
||||||
|
#else
|
||||||
|
// Linux: chmod, move
|
||||||
|
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");
|
system("mv version.txt.new version.txt");
|
||||||
system("cp version.txt assets/version.txt 2>/dev/null");
|
system("cp version.txt assets/version.txt 2>/dev/null");
|
||||||
|
#endif
|
||||||
fakeProgress = 1.0f;
|
fakeProgress = 1.0f;
|
||||||
} else {
|
} else {
|
||||||
downloadFailed = true;
|
downloadFailed = true;
|
||||||
|
|
@ -2139,6 +2249,76 @@ int main(void)
|
||||||
activeNetField = 0;
|
activeNetField = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (currentState == WORLD_CREATION_PROGRESS) {
|
||||||
|
// UPDATE: Generate 10 chunks per frame (faster for the larger area)
|
||||||
|
int spiralSize = 15;
|
||||||
|
int spawnCX = (int)floorf(spawnSavedX / CHUNK_SIZE);
|
||||||
|
int spawnCZ = (int)floorf(spawnSavedZ / CHUNK_SIZE);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10 && chunksGeneratedCount < totalChunksToPreGen; i++) {
|
||||||
|
int cx_off = (chunksGeneratedCount % spiralSize) - 7;
|
||||||
|
int cz_off = (chunksGeneratedCount / spiralSize) - 7;
|
||||||
|
GenerateChunk(spawnCX + cx_off, spawnCZ + cz_off);
|
||||||
|
chunksGeneratedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
worldGenProgress = (float)chunksGeneratedCount / (float)totalChunksToPreGen;
|
||||||
|
|
||||||
|
if (chunksGeneratedCount >= totalChunksToPreGen) {
|
||||||
|
// FINALIZE: Place player and save world data
|
||||||
|
if (isNewWorldGeneration) {
|
||||||
|
float spawnY = FindSpawnY(0, 0);
|
||||||
|
camera3D.position = (Vector3){ 0.0f, spawnY, 0.0f };
|
||||||
|
camera3D.target = (Vector3){ 0.0f, spawnY, 1.0f };
|
||||||
|
|
||||||
|
std::ofstream worldFile2("saves/" + currentWorldName + "/world.dat");
|
||||||
|
if (worldFile2.is_open()) {
|
||||||
|
worldFile2 << globalSeedHash << " " << camera3D.position.x << " " << camera3D.position.y << " " << camera3D.position.z;
|
||||||
|
worldFile2.close();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 9; i++) hotbar[i] = InventorySlot(AIR, 0);
|
||||||
|
for (int i = 0; i < 27; i++) inventory[i] = InventorySlot(AIR, 0);
|
||||||
|
} else {
|
||||||
|
// Restoration logic for existing worlds
|
||||||
|
float spawnY = (spawnSavedY > 0) ? spawnSavedY : FindSpawnY((int)spawnSavedX, (int)spawnSavedZ);
|
||||||
|
camera3D.position = (Vector3){ spawnSavedX, spawnY, spawnSavedZ };
|
||||||
|
camera3D.target = (Vector3){ spawnSavedX, spawnY, spawnSavedZ + 1.0f };
|
||||||
|
|
||||||
|
std::ifstream invf("saves/" + currentWorldName + "/inventory.dat", std::ios::binary);
|
||||||
|
if (invf.is_open()) {
|
||||||
|
invf.read((char*)hotbar, sizeof(hotbar));
|
||||||
|
invf.read((char*)inventory, sizeof(inventory));
|
||||||
|
invf.read((char*)&activeHotbarSlot, sizeof(activeHotbarSlot));
|
||||||
|
invf.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
playerVelocityY = 0.0f;
|
||||||
|
camYaw = 0.0f; camPitch = 0.0f;
|
||||||
|
currentState = GAMEPLAY;
|
||||||
|
DisableCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DRAW: Progress UI
|
||||||
|
DrawRectangle(0, 0, currentWidth, currentHeight, BLACK);
|
||||||
|
int barWidth = 400;
|
||||||
|
int barHeight = 40;
|
||||||
|
int barX = currentWidth / 2 - barWidth / 2;
|
||||||
|
int barY = currentHeight / 2 - barHeight / 2;
|
||||||
|
|
||||||
|
DrawTextEx(customFont, isNewWorldGeneration ? "Creating World..." : "Loading World...", (Vector2){ (float)barX, (float)barY - 40 }, 24, 1.0f, WHITE);
|
||||||
|
|
||||||
|
// Background bar
|
||||||
|
DrawRectangle(barX, barY, barWidth, barHeight, DARKGRAY);
|
||||||
|
// Progress bar
|
||||||
|
DrawRectangle(barX, barY, (int)(barWidth * worldGenProgress), barHeight, GREEN);
|
||||||
|
// Border
|
||||||
|
DrawRectangleLinesEx((Rectangle){ (float)barX, (float)barY, (float)barWidth, (float)barHeight }, 2, WHITE);
|
||||||
|
|
||||||
|
char progText[32];
|
||||||
|
snprintf(progText, 32, "%d%%", (int)(worldGenProgress * 100));
|
||||||
|
Vector2 textSize = MeasureTextEx(customFont, progText, 20, 1.0f);
|
||||||
|
DrawTextEx(customFont, progText, (Vector2){ (float)(barX + barWidth / 2 - textSize.x / 2), (float)(barY + barHeight / 2 - textSize.y / 2) }, 20, 1.0f, WHITE);
|
||||||
|
|
||||||
} else if (currentState != GAMEPLAY) {
|
} else if (currentState != GAMEPLAY) {
|
||||||
BeginMode2D(camera);
|
BeginMode2D(camera);
|
||||||
// Draw the texture, scaling it to fit the current window size exactly
|
// Draw the texture, scaling it to fit the current window size exactly
|
||||||
|
|
@ -2149,8 +2329,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.1.9) in Red
|
// Show Version Number (v2.2.6) in Red
|
||||||
DrawTextEx(customFont, "v2.1.9", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
|
DrawTextEx(customFont, "v2.2.6", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
|
||||||
|
|
||||||
// --- PLAYER NAME POPUP (IF MISSING) ---
|
// --- PLAYER NAME POPUP (IF MISSING) ---
|
||||||
if (playerName == "") {
|
if (playerName == "") {
|
||||||
|
|
@ -2249,15 +2429,19 @@ int main(void)
|
||||||
} else if (currentState == LOAD_WORLD_MENU) {
|
} else if (currentState == LOAD_WORLD_MENU) {
|
||||||
targetZoom = 1.0f;
|
targetZoom = 1.0f;
|
||||||
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 180 });
|
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 180 });
|
||||||
|
// Scroll handling
|
||||||
|
float scroll = GetMouseWheelMove();
|
||||||
|
loadWorldScrollOffset -= (int)scroll;
|
||||||
|
if (loadWorldScrollOffset < 0) loadWorldScrollOffset = 0;
|
||||||
|
|
||||||
int panelWidth = 600;
|
int panelWidth = 640; // Wider
|
||||||
int panelHeight = 500;
|
int panelHeight = 520; // Taller
|
||||||
int panelX = (currentWidth / 2) - (panelWidth / 2);
|
int panelX = (currentWidth / 2) - (panelWidth / 2);
|
||||||
int panelY = (currentHeight / 2) - (panelHeight / 2);
|
int panelY = (currentHeight / 2) - (panelHeight / 2);
|
||||||
|
|
||||||
DrawRectangle(panelX, panelY, panelWidth, panelHeight, (Color){ 40, 40, 40, 240 });
|
DrawRectangle(panelX, panelY, panelWidth, panelHeight, (Color){ 40, 40, 40, 240 });
|
||||||
DrawRectangleLinesEx((Rectangle){ (float)panelX, (float)panelY, (float)panelWidth, (float)panelHeight }, 4.0f, (Color){ 100, 100, 100, 255 });
|
DrawRectangleLinesEx((Rectangle){ (float)panelX, (float)panelY, (float)panelWidth, (float)panelHeight }, 4.0f, (Color){ 100, 100, 100, 255 });
|
||||||
DrawTextEx(customFont, "Load World", (Vector2){ (float)panelX + 20, (float)panelY + 20 }, 32, 1.0f, WHITE);
|
DrawTextEx(customFont, "Load World", (Vector2){ (float)panelX + 30, (float)panelY + 30 }, 32, 1.0f, WHITE);
|
||||||
|
|
||||||
// List saves
|
// List saves
|
||||||
std::vector<std::string> savedWorlds;
|
std::vector<std::string> savedWorlds;
|
||||||
|
|
@ -2268,20 +2452,38 @@ int main(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (savedWorlds.size() > 6) {
|
||||||
|
if (loadWorldScrollOffset > (int)savedWorlds.size() - 6) loadWorldScrollOffset = (int)savedWorlds.size() - 6;
|
||||||
|
} else {
|
||||||
|
loadWorldScrollOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (savedWorlds.empty()) {
|
if (savedWorlds.empty()) {
|
||||||
DrawTextEx(customFont, "No saved worlds found.", (Vector2){ (float)panelX + 40, (float)panelY + 100 }, 20, 1.0f, LIGHTGRAY);
|
DrawTextEx(customFont, "No saved worlds found.", (Vector2){ (float)panelX + 40, (float)panelY + 100 }, 20, 1.0f, LIGHTGRAY);
|
||||||
} else {
|
} else {
|
||||||
for (size_t i = 0; i < savedWorlds.size() && i < 5; i++) {
|
// Draw scroll bar
|
||||||
Rectangle worldBtn = { (float)panelX + 40, (float)panelY + 100 + (float)(i * 60), 520, 50 };
|
if (savedWorlds.size() > 6) {
|
||||||
|
float barHeightFull = 360.0f;
|
||||||
|
float scrollRatio = 6.0f / (float)savedWorlds.size();
|
||||||
|
float handleHeight = barHeightFull * scrollRatio;
|
||||||
|
float scrollProgress = (float)loadWorldScrollOffset / (float)(savedWorlds.size() - 6);
|
||||||
|
float handleY = panelY + 100 + (barHeightFull - handleHeight) * scrollProgress;
|
||||||
|
DrawRectangle(panelX + panelWidth - 30, panelY + 100, 10, (int)barHeightFull, DARKGRAY);
|
||||||
|
DrawRectangle(panelX + panelWidth - 30, (int)handleY, 10, (int)handleHeight, LIGHTGRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 6 && (i + loadWorldScrollOffset) < savedWorlds.size(); i++) {
|
||||||
|
size_t idx = i + loadWorldScrollOffset;
|
||||||
|
Rectangle worldBtn = { (float)panelX + 40, (float)panelY + 100 + (float)(i * 65), 550, 55 };
|
||||||
|
|
||||||
Rectangle deleteBtn = { worldBtn.x + worldBtn.width - 50, worldBtn.y + 5, 40, 40 };
|
Rectangle deleteBtn = { worldBtn.x + worldBtn.width - 50, worldBtn.y + 7, 40, 40 };
|
||||||
bool isDeleteHovered = CheckCollisionPointRec(mousePos, deleteBtn);
|
bool isDeleteHovered = CheckCollisionPointRec(mousePos, deleteBtn);
|
||||||
bool isHovered = CheckCollisionPointRec(mousePos, worldBtn) && !isDeleteHovered;
|
bool isHovered = CheckCollisionPointRec(mousePos, worldBtn) && !isDeleteHovered;
|
||||||
|
|
||||||
DrawRectangleRec(worldBtn, isHovered ? (Color){ 80, 80, 80, 255 } : DARKGRAY);
|
DrawRectangleRec(worldBtn, isHovered ? (Color){ 80, 80, 80, 255 } : DARKGRAY);
|
||||||
DrawRectangleLinesEx(worldBtn, 2.0f, isHovered ? WHITE : GRAY);
|
DrawRectangleLinesEx(worldBtn, 2.0f, isHovered ? WHITE : GRAY);
|
||||||
DrawTextEx(customFont, savedWorlds[i].c_str(), (Vector2){ worldBtn.x + 10, worldBtn.y + 15 }, 20, 1.0f, WHITE);
|
DrawTextEx(customFont, savedWorlds[idx].c_str(), (Vector2){ worldBtn.x + 15, worldBtn.y + 17 }, 20, 1.0f, WHITE);
|
||||||
|
|
||||||
// Draw delete button
|
// Draw delete button
|
||||||
DrawRectangleRec(deleteBtn, isDeleteHovered ? RED : MAROON);
|
DrawRectangleRec(deleteBtn, isDeleteHovered ? RED : MAROON);
|
||||||
|
|
@ -2290,79 +2492,51 @@ int main(void)
|
||||||
|
|
||||||
if (!showDeleteConfirm) {
|
if (!showDeleteConfirm) {
|
||||||
if (isDeleteHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
if (isDeleteHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||||
deletingWorldName = savedWorlds[i];
|
deletingWorldName = savedWorlds[idx];
|
||||||
showDeleteConfirm = true;
|
showDeleteConfirm = true;
|
||||||
} else if (isHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
} else if (isHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||||
// Load this world
|
// Load this world
|
||||||
currentWorldName = savedWorlds[i];
|
currentWorldName = savedWorlds[idx];
|
||||||
|
|
||||||
// Load seed and player position from world.dat
|
// Load seed and player position from world.dat
|
||||||
std::ifstream worldFile("saves/" + currentWorldName + "/world.dat");
|
std::ifstream worldFile("saves/" + currentWorldName + "/world.dat");
|
||||||
float savedX = 0.0f, savedY = -1.0f, savedZ = 0.0f;
|
if (worldFile.is_open()) {
|
||||||
if (worldFile.is_open()) {
|
worldFile >> globalSeedHash >> spawnSavedX >> spawnSavedY >> spawnSavedZ;
|
||||||
worldFile >> globalSeedHash >> savedX >> savedY >> savedZ;
|
worldFile.close();
|
||||||
worldFile.close();
|
} else {
|
||||||
} else {
|
globalSeedHash = 12345;
|
||||||
globalSeedHash = 12345;
|
spawnSavedX = 0; spawnSavedY = -1; spawnSavedZ = 0;
|
||||||
}
|
|
||||||
|
|
||||||
// Clear old chunks just in case
|
|
||||||
for (auto& pair : worldChunks) {
|
|
||||||
delete pair.second;
|
|
||||||
}
|
|
||||||
worldChunks.clear();
|
|
||||||
|
|
||||||
if (serverMode) {
|
|
||||||
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (serverSocket != INVALID_SOCKET_VAL) {
|
|
||||||
SetNonBlocking(serverSocket);
|
|
||||||
struct sockaddr_in serv_addr;
|
|
||||||
serv_addr.sin_family = AF_INET;
|
|
||||||
serv_addr.sin_addr.s_addr = INADDR_ANY;
|
|
||||||
serv_addr.sin_port = htons(12345);
|
|
||||||
bind(serverSocket, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
|
|
||||||
listen(serverSocket, 5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear old chunks
|
||||||
|
for (auto& pair : worldChunks) delete pair.second;
|
||||||
|
worldChunks.clear();
|
||||||
|
|
||||||
|
if (serverMode) {
|
||||||
|
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (serverSocket != INVALID_SOCKET_VAL) {
|
||||||
|
SetNonBlocking(serverSocket);
|
||||||
|
struct sockaddr_in serv_addr;
|
||||||
|
serv_addr.sin_family = AF_INET;
|
||||||
|
serv_addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
serv_addr.sin_port = htons(12345);
|
||||||
|
bind(serverSocket, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
|
||||||
|
listen(serverSocket, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start progress screen
|
||||||
|
isNewWorldGeneration = false;
|
||||||
|
chunksGeneratedCount = 0;
|
||||||
|
worldGenProgress = 0.0f;
|
||||||
|
currentState = WORLD_CREATION_PROGRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentState = GAMEPLAY;
|
|
||||||
DisableCursor();
|
|
||||||
inventoryOpen = false;
|
|
||||||
|
|
||||||
// Restore saved position, or find surface if no save
|
|
||||||
float spawnY;
|
|
||||||
if (savedY > 0.0f) {
|
|
||||||
spawnY = savedY;
|
|
||||||
} else {
|
|
||||||
spawnY = FindSpawnY((int)savedX, (int)savedZ);
|
|
||||||
}
|
|
||||||
camera3D.position = (Vector3){ savedX, spawnY, savedZ };
|
|
||||||
camera3D.target = (Vector3){ savedX, spawnY, savedZ + 1.0f };
|
|
||||||
playerVelocityY = 0.0f;
|
|
||||||
camYaw = 0.0f; camPitch = 0.0f;
|
|
||||||
|
|
||||||
// Load inventory from disk
|
|
||||||
std::ifstream invf("saves/" + currentWorldName + "/inventory.dat", std::ios::binary);
|
|
||||||
if (invf.is_open()) {
|
|
||||||
invf.read((char*)hotbar, sizeof(hotbar));
|
|
||||||
invf.read((char*)inventory, sizeof(inventory));
|
|
||||||
invf.read((char*)&activeHotbarSlot, sizeof(activeHotbarSlot));
|
|
||||||
invf.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-generate all chunks around spawn so world appears immediately
|
|
||||||
int spawnCX = (int)floorf(savedX / CHUNK_SIZE);
|
|
||||||
int spawnCZ = (int)floorf(savedZ / CHUNK_SIZE);
|
|
||||||
for (int cx = spawnCX - RENDER_DISTANCE; cx <= spawnCX + RENDER_DISTANCE; cx++)
|
|
||||||
for (int cz = spawnCZ - RENDER_DISTANCE; cz <= spawnCZ + RENDER_DISTANCE; cz++)
|
|
||||||
GenerateChunk(cx, cz);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Back Button
|
// Back Button
|
||||||
int btnWidth = 200;
|
int btnWidth = 220; // Slightly wider
|
||||||
int btnHeight = 50;
|
int btnHeight = 50;
|
||||||
int backBtnX = panelX + (panelWidth / 2) - (btnWidth / 2);
|
int backBtnX = panelX + (panelWidth / 2) - (btnWidth / 2);
|
||||||
int btnY = panelY + panelHeight - 80;
|
int btnY = panelY + panelHeight - 80;
|
||||||
|
|
@ -2531,76 +2705,65 @@ int main(void)
|
||||||
|
|
||||||
} else if (currentState == CREATE_WORLD_MENU) {
|
} else if (currentState == CREATE_WORLD_MENU) {
|
||||||
targetZoom = 1.0f;
|
targetZoom = 1.0f;
|
||||||
|
|
||||||
// Draw dark overlay
|
|
||||||
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 180 });
|
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 180 });
|
||||||
|
|
||||||
// Draw Create World panel
|
int panelWidth = 640;
|
||||||
int panelWidth = 600;
|
int panelHeight = 520;
|
||||||
int panelHeight = 500;
|
|
||||||
int panelX = (currentWidth / 2) - (panelWidth / 2);
|
int panelX = (currentWidth / 2) - (panelWidth / 2);
|
||||||
int panelY = (currentHeight / 2) - (panelHeight / 2);
|
int panelY = (currentHeight / 2) - (panelHeight / 2);
|
||||||
|
|
||||||
DrawRectangle(panelX, panelY, panelWidth, panelHeight, (Color){ 40, 40, 40, 240 });
|
DrawRectangle(panelX, panelY, panelWidth, panelHeight, (Color){ 40, 40, 40, 240 });
|
||||||
DrawRectangleLinesEx((Rectangle){ (float)panelX, (float)panelY, (float)panelWidth, (float)panelHeight }, 4.0f, (Color){ 100, 100, 100, 255 });
|
DrawRectangleLinesEx((Rectangle){ (float)panelX, (float)panelY, (float)panelWidth, (float)panelHeight }, 4.0f, (Color){ 100, 100, 100, 255 });
|
||||||
|
|
||||||
// Title
|
DrawTextEx(customFont, "Create New World", (Vector2){ (float)panelX + 30, (float)panelY + 30 }, 32, 1.0f, WHITE);
|
||||||
DrawTextEx(customFont, "Create New World", (Vector2){ (float)panelX + 20, (float)panelY + 20 }, 32, 1.0f, WHITE);
|
|
||||||
|
|
||||||
// World Name
|
// World Name
|
||||||
DrawTextEx(customFont, "World Name", (Vector2){ (float)panelX + 40, (float)panelY + 100 }, 20, 1.0f, LIGHTGRAY);
|
DrawTextEx(customFont, "World Name", (Vector2){ (float)panelX + 50, (float)panelY + 100 }, 20, 1.0f, LIGHTGRAY);
|
||||||
Rectangle nameBox = { (float)panelX + 40, (float)panelY + 130, 520, 40 };
|
Rectangle nameBox = { (float)panelX + 50, (float)panelY + 130, 540, 45 };
|
||||||
|
|
||||||
if (CheckCollisionPointRec(mousePos, nameBox) && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) activeTextBox = 1;
|
if (CheckCollisionPointRec(mousePos, nameBox) && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) activeTextBox = 1;
|
||||||
|
|
||||||
DrawRectangleRec(nameBox, DARKGRAY);
|
DrawRectangleRec(nameBox, DARKGRAY);
|
||||||
DrawRectangleLinesEx(nameBox, 2.0f, activeTextBox == 1 ? WHITE : GRAY);
|
DrawRectangleLinesEx(nameBox, 2.0f, activeTextBox == 1 ? WHITE : GRAY);
|
||||||
DrawTextEx(customFont, worldName, (Vector2){ nameBox.x + 10, nameBox.y + 10 }, 20, 1.0f, WHITE);
|
DrawTextEx(customFont, worldName, (Vector2){ nameBox.x + 15, nameBox.y + 12 }, 20, 1.0f, WHITE);
|
||||||
|
|
||||||
// Blinking cursor
|
|
||||||
if (activeTextBox == 1 && ((int)(GetTime() * 2) % 2 == 0)) {
|
if (activeTextBox == 1 && ((int)(GetTime() * 2) % 2 == 0)) {
|
||||||
int textW = MeasureTextEx(customFont, worldName, 20, 1.0f).x;
|
int textW = MeasureTextEx(customFont, worldName, 20, 1.0f).x;
|
||||||
DrawRectangle(nameBox.x + 12 + textW, nameBox.y + 10, 12, 20, LIGHTGRAY);
|
DrawRectangle(nameBox.x + 17 + textW, nameBox.y + 12, 12, 20, LIGHTGRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// World Seed
|
// World Seed
|
||||||
DrawTextEx(customFont, "Seed (Optional)", (Vector2){ (float)panelX + 40, (float)panelY + 200 }, 20, 1.0f, LIGHTGRAY);
|
DrawTextEx(customFont, "Seed (Optional)", (Vector2){ (float)panelX + 50, (float)panelY + 200 }, 20, 1.0f, LIGHTGRAY);
|
||||||
Rectangle seedBox = { (float)panelX + 40, (float)panelY + 230, 520, 40 };
|
Rectangle seedBox = { (float)panelX + 50, (float)panelY + 230, 540, 45 };
|
||||||
|
|
||||||
if (CheckCollisionPointRec(mousePos, seedBox) && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) activeTextBox = 2;
|
if (CheckCollisionPointRec(mousePos, seedBox) && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) activeTextBox = 2;
|
||||||
// Click outside to unfocus
|
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON) && !CheckCollisionPointRec(mousePos, nameBox) && !CheckCollisionPointRec(mousePos, seedBox)) activeTextBox = 0;
|
||||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON) && !CheckCollisionPointRec(mousePos, nameBox) && !CheckCollisionPointRec(mousePos, seedBox)) {
|
|
||||||
activeTextBox = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawRectangleRec(seedBox, DARKGRAY);
|
DrawRectangleRec(seedBox, DARKGRAY);
|
||||||
DrawRectangleLinesEx(seedBox, 2.0f, activeTextBox == 2 ? WHITE : GRAY);
|
DrawRectangleLinesEx(seedBox, 2.0f, activeTextBox == 2 ? WHITE : GRAY);
|
||||||
DrawTextEx(customFont, worldSeed, (Vector2){ seedBox.x + 10, seedBox.y + 10 }, 20, 1.0f, WHITE);
|
DrawTextEx(customFont, worldSeed, (Vector2){ seedBox.x + 15, seedBox.y + 12 }, 20, 1.0f, WHITE);
|
||||||
|
|
||||||
if (activeTextBox == 2 && ((int)(GetTime() * 2) % 2 == 0)) {
|
if (activeTextBox == 2 && ((int)(GetTime() * 2) % 2 == 0)) {
|
||||||
int textW = MeasureTextEx(customFont, worldSeed, 20, 1.0f).x;
|
int textW = MeasureTextEx(customFont, worldSeed, 20, 1.0f).x;
|
||||||
DrawRectangle(seedBox.x + 12 + textW, seedBox.y + 10, 12, 20, LIGHTGRAY);
|
DrawRectangle(seedBox.x + 17 + textW, seedBox.y + 12, 12, 20, LIGHTGRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Back Button
|
// Buttons
|
||||||
int btnWidth = 200;
|
int btnWidth = 200;
|
||||||
int btnHeight = 50;
|
int btnHeight = 50;
|
||||||
int backBtnX = panelX + 40;
|
int btnY = panelY + panelHeight - 90;
|
||||||
int btnY = panelY + panelHeight - 80;
|
|
||||||
|
|
||||||
|
// Back Button
|
||||||
|
int backBtnX = panelX + 50;
|
||||||
Rectangle backBtnBounds = { (float)backBtnX, (float)btnY, (float)btnWidth, (float)btnHeight };
|
Rectangle backBtnBounds = { (float)backBtnX, (float)btnY, (float)btnWidth, (float)btnHeight };
|
||||||
bool isBackHovered = CheckCollisionPointRec(mousePos, backBtnBounds);
|
bool isBackHovered = CheckCollisionPointRec(mousePos, backBtnBounds);
|
||||||
DrawRectangleRec(backBtnBounds, isBackHovered ? (Color){ 100, 100, 100, 255 } : (Color){ 60, 60, 60, 255 });
|
DrawRectangleRec(backBtnBounds, isBackHovered ? (Color){ 100, 100, 100, 255 } : (Color){ 60, 60, 60, 255 });
|
||||||
DrawRectangleLinesEx(backBtnBounds, 3.0f, isBackHovered ? WHITE : GRAY);
|
DrawRectangleLinesEx(backBtnBounds, 3.0f, isBackHovered ? WHITE : GRAY);
|
||||||
Vector2 backTextSize = MeasureTextEx(customFont, "Back", 20, 1.0f);
|
Vector2 backTextSize = MeasureTextEx(customFont, "Back", 20, 1.0f);
|
||||||
DrawTextEx(customFont, "Back", (Vector2){ backBtnX + (btnWidth/2) - (backTextSize.x/2), btnY + (btnHeight/2) - (backTextSize.y/2) }, 20, 1.0f, WHITE);
|
DrawTextEx(customFont, "Back", (Vector2){ backBtnX + (btnWidth/2) - (backTextSize.x/2), btnY + (btnHeight/2) - (backTextSize.y/2) }, 20, 1.0f, WHITE);
|
||||||
|
if (isBackHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) currentState = MAIN_MENU;
|
||||||
if (isBackHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
|
||||||
currentState = MAIN_MENU;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Button
|
// Create Button
|
||||||
int createBtnX = panelX + panelWidth - 40 - btnWidth;
|
int createBtnX = panelX + panelWidth - 250;
|
||||||
Rectangle createBtnBounds = { (float)createBtnX, (float)btnY, (float)btnWidth, (float)btnHeight };
|
Rectangle createBtnBounds = { (float)createBtnX, (float)btnY, (float)btnWidth, (float)btnHeight };
|
||||||
bool isCreateHovered = CheckCollisionPointRec(mousePos, createBtnBounds);
|
bool isCreateHovered = CheckCollisionPointRec(mousePos, createBtnBounds);
|
||||||
DrawRectangleRec(createBtnBounds, isCreateHovered ? (Color){ 100, 100, 100, 255 } : (Color){ 60, 60, 60, 255 });
|
DrawRectangleRec(createBtnBounds, isCreateHovered ? (Color){ 100, 100, 100, 255 } : (Color){ 60, 60, 60, 255 });
|
||||||
|
|
@ -2610,15 +2773,18 @@ int main(void)
|
||||||
|
|
||||||
if (isCreateHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
if (isCreateHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||||
currentWorldName = worldName;
|
currentWorldName = worldName;
|
||||||
|
std::string baseName = worldName;
|
||||||
int nameCounter = 1;
|
int nameCounter = 1;
|
||||||
while (std::filesystem::exists("saves/" + currentWorldName)) {
|
while (std::filesystem::exists("saves/" + currentWorldName)) {
|
||||||
currentWorldName = std::string(worldName) + " (" + std::to_string(nameCounter) + ")";
|
currentWorldName = baseName + " " + std::to_string(nameCounter);
|
||||||
nameCounter++;
|
nameCounter++;
|
||||||
}
|
}
|
||||||
// Update the worldName string so the HUD shows it correctly
|
// Update the worldName string so the HUD shows it correctly
|
||||||
snprintf(worldName, sizeof(worldName), "%s", currentWorldName.c_str());
|
snprintf(worldName, sizeof(worldName), "%s", currentWorldName.c_str());
|
||||||
worldNameLen = strlen(worldName);
|
worldNameLen = strlen(worldName);
|
||||||
|
|
||||||
|
gameTime = 87.5f; // Start new world at 7:00 AM
|
||||||
|
|
||||||
if (serverMode) {
|
if (serverMode) {
|
||||||
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (serverSocket != INVALID_SOCKET_VAL) {
|
if (serverSocket != INVALID_SOCKET_VAL) {
|
||||||
|
|
@ -2632,9 +2798,6 @@ int main(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentState = GAMEPLAY;
|
|
||||||
DisableCursor(); // Hide and lock cursor for first-person control
|
|
||||||
|
|
||||||
std::filesystem::create_directories("saves/" + currentWorldName);
|
std::filesystem::create_directories("saves/" + currentWorldName);
|
||||||
|
|
||||||
// Simple hash for seed string
|
// Simple hash for seed string
|
||||||
|
|
@ -2650,27 +2813,12 @@ int main(void)
|
||||||
}
|
}
|
||||||
worldChunks.clear();
|
worldChunks.clear();
|
||||||
|
|
||||||
// Generate spawn chunk and place player on surface
|
// Start progress screen
|
||||||
float spawnY = FindSpawnY(0, 0);
|
isNewWorldGeneration = true;
|
||||||
camera3D.position = (Vector3){ 0.0f, spawnY, 0.0f };
|
chunksGeneratedCount = 0;
|
||||||
camera3D.target = (Vector3){ 0.0f, spawnY, 1.0f };
|
worldGenProgress = 0.0f;
|
||||||
playerVelocityY = 0.0f;
|
spawnSavedX = 0; spawnSavedY = -1; spawnSavedZ = 0; // Fresh world start at origin
|
||||||
camYaw = 0.0f; camPitch = 0.0f; // Reset look so WASD matches view
|
currentState = WORLD_CREATION_PROGRESS;
|
||||||
|
|
||||||
// Re-write world.dat with correct spawnY now that we know it
|
|
||||||
std::ofstream worldFile2("saves/" + currentWorldName + "/world.dat");
|
|
||||||
if (worldFile2.is_open()) {
|
|
||||||
worldFile2 << globalSeedHash << " "
|
|
||||||
<< camera3D.position.x << " "
|
|
||||||
<< camera3D.position.y << " "
|
|
||||||
<< camera3D.position.z;
|
|
||||||
worldFile2.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// New player starts with nothing
|
|
||||||
for (int i = 0; i < 9; i++) { hotbar[i] = InventorySlot(AIR, 0); }
|
|
||||||
for (int i = 0; i < 27; i++) { inventory[i] = InventorySlot(AIR, 0); }
|
|
||||||
activeHotbarSlot = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3017,7 +3165,21 @@ int main(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hov && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
if (hov && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||||
if (isResultSlot) {
|
if (IsKeyDown(KEY_LEFT_SHIFT) && !isResultSlot && slot.blockType != AIR) {
|
||||||
|
// Pick up entire stack into hand immediately
|
||||||
|
if (mouseHeldItem.blockType == AIR) {
|
||||||
|
mouseHeldItem = slot;
|
||||||
|
slot = InventorySlot(AIR, 0);
|
||||||
|
} else if (mouseHeldItem.blockType == slot.blockType) {
|
||||||
|
int space = 64 - mouseHeldItem.count;
|
||||||
|
int toAdd = (slot.count < space) ? slot.count : space;
|
||||||
|
mouseHeldItem.count += toAdd;
|
||||||
|
slot.count -= toAdd;
|
||||||
|
if (slot.count <= 0) slot = InventorySlot(AIR, 0);
|
||||||
|
}
|
||||||
|
UpdateCrafting();
|
||||||
|
UpdateTableCrafting();
|
||||||
|
} else if (isResultSlot) {
|
||||||
if (mouseHeldItem.blockType == AIR && slot.blockType != AIR) {
|
if (mouseHeldItem.blockType == AIR && slot.blockType != AIR) {
|
||||||
mouseHeldItem = slot;
|
mouseHeldItem = slot;
|
||||||
slot = InventorySlot(AIR, 0);
|
slot = InventorySlot(AIR, 0);
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
v2.1.9
|
v2.2.6
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue