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
|
||||
|
||||
### 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.
|
||||
- **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.
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
v2.1.9
|
||||
v2.2.6
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
v2.1.9
|
||||
v2.2.6
|
||||
|
|
|
|||
444
src/main.cpp
444
src/main.cpp
|
|
@ -20,7 +20,7 @@
|
|||
#define PI 3.1415926535f
|
||||
#endif
|
||||
#define CHUNK_HEIGHT 128
|
||||
#define RENDER_DISTANCE 2
|
||||
#define RENDER_DISTANCE 4
|
||||
|
||||
enum BlockType {
|
||||
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
|
||||
float dotGridGradient(int ix, int iy, float x, float y, unsigned int seed) {
|
||||
unsigned int hash = ix * 3284157443 ^ iy * 1911520717;
|
||||
hash ^= seed;
|
||||
// Proper hash with avalanche effect - seed fundamentally changes the terrain
|
||||
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 gradientX = cosf(random);
|
||||
float gradientY = sinf(random);
|
||||
|
|
@ -137,7 +147,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 };
|
||||
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
|
||||
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 int activeHotbarSlot = 0;
|
||||
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.
|
||||
void AddToInventory(int blockType) {
|
||||
|
|
@ -382,10 +399,20 @@ std::string GetRemoteVersion() {
|
|||
pclose(pipe);
|
||||
// Trim result
|
||||
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;
|
||||
}
|
||||
|
||||
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() {
|
||||
std::ofstream file("config.cfg");
|
||||
if (file.is_open()) {
|
||||
|
|
@ -491,11 +518,13 @@ float FindSpawnY(int spawnX, int spawnZ) {
|
|||
GenerateChunk(cx, cz);
|
||||
for (int y = CHUNK_HEIGHT - 1; y >= 1; y--) {
|
||||
if (GetBlock(spawnX, y, spawnZ) != AIR) {
|
||||
// Feet land at top of block (y + 0.5), eye is 1.6 above feet
|
||||
return (float)y + 0.5f + 1.6f;
|
||||
// Feet land at top of block (y + 0.5).
|
||||
// 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) {
|
||||
|
|
@ -520,24 +549,44 @@ void GenerateChunk(int cx, int cz) {
|
|||
float worldX = cx * CHUNK_SIZE + x;
|
||||
float worldZ = cz * CHUNK_SIZE + z;
|
||||
|
||||
// Multi-octave fbm noise at a much lower frequency for broad, smooth hills.
|
||||
// Base freq 0.006 means features span ~160 blocks, preventing steep cliffs.
|
||||
float noiseVal = fbm(worldX * 0.006f, worldZ * 0.006f, globalSeedHash);
|
||||
// Terrain sits around Y=32 with only ±6 blocks of variation,
|
||||
// so the minimum ground level is 26 -- no sudden deep holes.
|
||||
int height = 32 + (int)(noiseVal * 6.0f);
|
||||
// Multi-layer terrain generation for interesting landscapes
|
||||
// Layer 1: Continent-scale rolling hills (broad, smooth features)
|
||||
float continentNoise = fbm(worldX * 0.004f, worldZ * 0.004f, globalSeedHash);
|
||||
// Layer 2: Local detail bumps (smaller, sharper features)
|
||||
float detailNoise = fbm(worldX * 0.015f, worldZ * 0.015f, globalSeedHash + 9999);
|
||||
// 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 >= 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++) {
|
||||
if (y == 0) {
|
||||
newChunk->blocks[x][y][z] = BEDROCK;
|
||||
} else if (y == height) {
|
||||
// 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) {
|
||||
// 4 blocks of dirt under the surface — thick enough that ores
|
||||
// can never be exposed by normal terrain features.
|
||||
// Sub-surface: sand in desert/beach, dirt elsewhere
|
||||
if (height <= 30 || biomeNoise > 0.3f) {
|
||||
newChunk->blocks[x][y][z] = SAND;
|
||||
} else {
|
||||
newChunk->blocks[x][y][z] = DIRT;
|
||||
}
|
||||
} else {
|
||||
newChunk->blocks[x][y][z] = STONE;
|
||||
|
||||
|
|
@ -576,12 +625,19 @@ void GenerateChunk(int cx, int cz) {
|
|||
newChunk->generated = true;
|
||||
newChunk->modified = true; // Newly generated, so we should save it
|
||||
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) {
|
||||
BoundingBox playerBox = {
|
||||
(Vector3){ pos.x - 0.3f, pos.y - 1.5f, pos.z - 0.3f },
|
||||
(Vector3){ pos.x + 0.3f, pos.y + 0.1f, pos.z + 0.3f }
|
||||
(Vector3){ pos.x - 0.26f, pos.y - 1.45f, pos.z - 0.26f },
|
||||
(Vector3){ pos.x + 0.26f, pos.y + 0.05f, pos.z + 0.26f }
|
||||
};
|
||||
|
||||
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.
|
||||
SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT);
|
||||
|
||||
InitWindow(screenWidth, screenHeight, "MorriCraft v2.1.9");
|
||||
InitWindow(screenWidth, screenHeight, "MorriCraft v2.2.6");
|
||||
LoadConfig();
|
||||
SetExitKey(KEY_NULL); // Prevent ESC from closing the window
|
||||
|
||||
|
|
@ -925,7 +981,7 @@ int main(void)
|
|||
bool isChatting = false;
|
||||
char chatInput[128] = {0};
|
||||
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;
|
||||
int lastHitX = -1, lastHitY = -1, lastHitZ = -1;
|
||||
float swingTime = 0.0f;
|
||||
|
|
@ -1575,7 +1631,30 @@ int main(void)
|
|||
if (IsKeyPressed(KEY_ENTER)) {
|
||||
if (isChatting) {
|
||||
if (strlen(chatInput) > 0) {
|
||||
// Send Chat Packet
|
||||
if (chatInput[0] == '/') {
|
||||
// Command processing - never sent to network
|
||||
std::string cmd(chatInput + 1); // skip the '/'
|
||||
// Trim and lowercase
|
||||
while (!cmd.empty() && cmd.back() == ' ') cmd.pop_back();
|
||||
|
||||
if (cmd == "seed") {
|
||||
chatLog.push_back({ "[Server] World seed: " + std::to_string(globalSeedHash), 8.0f });
|
||||
} else if (cmd == "fly on") {
|
||||
isFlying = true;
|
||||
chatLog.push_back({ "[Server] Flight enabled (Noclip ON)", 5.0f });
|
||||
} else if (cmd == "fly off") {
|
||||
isFlying = false;
|
||||
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);
|
||||
|
|
@ -1591,6 +1670,7 @@ int main(void)
|
|||
SendAll(s, (char*)&pc, sizeof(pc));
|
||||
}
|
||||
}
|
||||
}
|
||||
chatInput[0] = '\0';
|
||||
}
|
||||
isChatting = false;
|
||||
|
|
@ -1642,23 +1722,31 @@ int main(void)
|
|||
|
||||
if (Vector3Length(moveVec) > 0) {
|
||||
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 };
|
||||
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 };
|
||||
if (!CheckPlayerCollision(tryZ)) camera3D.position.z = tryZ.z;
|
||||
if (isFlying || !CheckPlayerCollision(tryZ)) camera3D.position.z = tryZ.z;
|
||||
}
|
||||
|
||||
// ---- Vertical Physics (Ground-Lock System) ----
|
||||
if (isGrounded) {
|
||||
// ---- Vertical Physics (Ground-Lock / Flight System) ----
|
||||
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;
|
||||
|
||||
// Keep player exactly on top of the block with a small epsilon
|
||||
float feetY = camera3D.position.y - 1.6f;
|
||||
float expectedFeetY = floorf(feetY + 0.1f) + 0.5f;
|
||||
camera3D.position.y = expectedFeetY + 1.6f + 0.01f;
|
||||
float expectedFeetY = floorf(feetY + 0.15f) + 0.5f;
|
||||
camera3D.position.y = expectedFeetY + 1.6f + 0.03f;
|
||||
|
||||
// Jump
|
||||
if (IsKeyPressed(KEY_SPACE) && !inventoryOpen && !isChatting) {
|
||||
|
|
@ -1682,7 +1770,7 @@ int main(void)
|
|||
if (playerVelocityY < 0.0f) {
|
||||
// Landed: Lock to surface
|
||||
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;
|
||||
isGrounded = true;
|
||||
} else {
|
||||
|
|
@ -1692,9 +1780,15 @@ int main(void)
|
|||
}
|
||||
} else {
|
||||
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
|
||||
camera3D.target.x = camera3D.position.x + sinf(camYaw) * cosf(camPitch);
|
||||
camera3D.target.y = camera3D.position.y + sinf(camPitch);
|
||||
|
|
@ -1887,7 +1981,7 @@ int main(void)
|
|||
// Perform real check after 1 second
|
||||
if (updateTimer > 1.0f && latestVersion == "") {
|
||||
latestVersion = GetRemoteVersion();
|
||||
if (latestVersion != "error" && latestVersion != localVersion) {
|
||||
if (IsVersionNewer(latestVersion, localVersion)) {
|
||||
updateReady = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1929,20 +2023,36 @@ int main(void)
|
|||
|
||||
if (!startedDownload) {
|
||||
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 binaryName = "MorriCraft";
|
||||
#endif
|
||||
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
|
||||
int r1 = system(("curl -L -s -o MorriCraft.new " + binaryUrl).c_str());
|
||||
int r2 = system(("curl -L -s -o version.txt.new " + versionUrl).c_str());
|
||||
// Download new binary and version file
|
||||
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());
|
||||
|
||||
if (r1 == 0 && r2 == 0) {
|
||||
system("chmod +x MorriCraft.new");
|
||||
system("mv MorriCraft MorriCraft.old 2>/dev/null");
|
||||
system("mv MorriCraft.new MorriCraft");
|
||||
#ifdef _WIN32
|
||||
// Windows: rename old, move new into place
|
||||
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("cp version.txt assets/version.txt 2>/dev/null");
|
||||
#endif
|
||||
fakeProgress = 1.0f;
|
||||
} else {
|
||||
downloadFailed = true;
|
||||
|
|
@ -2139,6 +2249,76 @@ int main(void)
|
|||
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) {
|
||||
BeginMode2D(camera);
|
||||
// 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);
|
||||
EndMode2D();
|
||||
|
||||
// Show Version Number (v2.1.9) in Red
|
||||
DrawTextEx(customFont, "v2.1.9", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
|
||||
// Show Version Number (v2.2.6) in Red
|
||||
DrawTextEx(customFont, "v2.2.6", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
|
||||
|
||||
// --- PLAYER NAME POPUP (IF MISSING) ---
|
||||
if (playerName == "") {
|
||||
|
|
@ -2249,15 +2429,19 @@ int main(void)
|
|||
} else if (currentState == LOAD_WORLD_MENU) {
|
||||
targetZoom = 1.0f;
|
||||
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 panelHeight = 500;
|
||||
int panelWidth = 640; // Wider
|
||||
int panelHeight = 520; // Taller
|
||||
int panelX = (currentWidth / 2) - (panelWidth / 2);
|
||||
int panelY = (currentHeight / 2) - (panelHeight / 2);
|
||||
|
||||
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 });
|
||||
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
|
||||
std::vector<std::string> savedWorlds;
|
||||
|
|
@ -2269,19 +2453,37 @@ int main(void)
|
|||
}
|
||||
}
|
||||
|
||||
if (savedWorlds.size() > 6) {
|
||||
if (loadWorldScrollOffset > (int)savedWorlds.size() - 6) loadWorldScrollOffset = (int)savedWorlds.size() - 6;
|
||||
} else {
|
||||
loadWorldScrollOffset = 0;
|
||||
}
|
||||
|
||||
if (savedWorlds.empty()) {
|
||||
DrawTextEx(customFont, "No saved worlds found.", (Vector2){ (float)panelX + 40, (float)panelY + 100 }, 20, 1.0f, LIGHTGRAY);
|
||||
} else {
|
||||
for (size_t i = 0; i < savedWorlds.size() && i < 5; i++) {
|
||||
Rectangle worldBtn = { (float)panelX + 40, (float)panelY + 100 + (float)(i * 60), 520, 50 };
|
||||
// Draw scroll bar
|
||||
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);
|
||||
}
|
||||
|
||||
Rectangle deleteBtn = { worldBtn.x + worldBtn.width - 50, worldBtn.y + 5, 40, 40 };
|
||||
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 + 7, 40, 40 };
|
||||
bool isDeleteHovered = CheckCollisionPointRec(mousePos, deleteBtn);
|
||||
bool isHovered = CheckCollisionPointRec(mousePos, worldBtn) && !isDeleteHovered;
|
||||
|
||||
DrawRectangleRec(worldBtn, isHovered ? (Color){ 80, 80, 80, 255 } : DARKGRAY);
|
||||
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
|
||||
DrawRectangleRec(deleteBtn, isDeleteHovered ? RED : MAROON);
|
||||
|
|
@ -2290,26 +2492,24 @@ int main(void)
|
|||
|
||||
if (!showDeleteConfirm) {
|
||||
if (isDeleteHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||
deletingWorldName = savedWorlds[i];
|
||||
deletingWorldName = savedWorlds[idx];
|
||||
showDeleteConfirm = true;
|
||||
} else if (isHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||
// Load this world
|
||||
currentWorldName = savedWorlds[i];
|
||||
currentWorldName = savedWorlds[idx];
|
||||
|
||||
// Load seed and player position from world.dat
|
||||
std::ifstream worldFile("saves/" + currentWorldName + "/world.dat");
|
||||
float savedX = 0.0f, savedY = -1.0f, savedZ = 0.0f;
|
||||
if (worldFile.is_open()) {
|
||||
worldFile >> globalSeedHash >> savedX >> savedY >> savedZ;
|
||||
worldFile >> globalSeedHash >> spawnSavedX >> spawnSavedY >> spawnSavedZ;
|
||||
worldFile.close();
|
||||
} else {
|
||||
globalSeedHash = 12345;
|
||||
spawnSavedX = 0; spawnSavedY = -1; spawnSavedZ = 0;
|
||||
}
|
||||
|
||||
// Clear old chunks just in case
|
||||
for (auto& pair : worldChunks) {
|
||||
delete pair.second;
|
||||
}
|
||||
// Clear old chunks
|
||||
for (auto& pair : worldChunks) delete pair.second;
|
||||
worldChunks.clear();
|
||||
|
||||
if (serverMode) {
|
||||
|
|
@ -2325,44 +2525,18 @@ int main(void)
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// Start progress screen
|
||||
isNewWorldGeneration = false;
|
||||
chunksGeneratedCount = 0;
|
||||
worldGenProgress = 0.0f;
|
||||
currentState = WORLD_CREATION_PROGRESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Back Button
|
||||
int btnWidth = 200;
|
||||
int btnWidth = 220; // Slightly wider
|
||||
int btnHeight = 50;
|
||||
int backBtnX = panelX + (panelWidth / 2) - (btnWidth / 2);
|
||||
int btnY = panelY + panelHeight - 80;
|
||||
|
|
@ -2531,76 +2705,65 @@ int main(void)
|
|||
|
||||
} else if (currentState == CREATE_WORLD_MENU) {
|
||||
targetZoom = 1.0f;
|
||||
|
||||
// Draw dark overlay
|
||||
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 180 });
|
||||
|
||||
// Draw Create World panel
|
||||
int panelWidth = 600;
|
||||
int panelHeight = 500;
|
||||
int panelWidth = 640;
|
||||
int panelHeight = 520;
|
||||
int panelX = (currentWidth / 2) - (panelWidth / 2);
|
||||
int panelY = (currentHeight / 2) - (panelHeight / 2);
|
||||
|
||||
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 });
|
||||
|
||||
// Title
|
||||
DrawTextEx(customFont, "Create New World", (Vector2){ (float)panelX + 20, (float)panelY + 20 }, 32, 1.0f, WHITE);
|
||||
DrawTextEx(customFont, "Create New World", (Vector2){ (float)panelX + 30, (float)panelY + 30 }, 32, 1.0f, WHITE);
|
||||
|
||||
// World Name
|
||||
DrawTextEx(customFont, "World Name", (Vector2){ (float)panelX + 40, (float)panelY + 100 }, 20, 1.0f, LIGHTGRAY);
|
||||
Rectangle nameBox = { (float)panelX + 40, (float)panelY + 130, 520, 40 };
|
||||
DrawTextEx(customFont, "World Name", (Vector2){ (float)panelX + 50, (float)panelY + 100 }, 20, 1.0f, LIGHTGRAY);
|
||||
Rectangle nameBox = { (float)panelX + 50, (float)panelY + 130, 540, 45 };
|
||||
|
||||
if (CheckCollisionPointRec(mousePos, nameBox) && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) activeTextBox = 1;
|
||||
|
||||
DrawRectangleRec(nameBox, DARKGRAY);
|
||||
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)) {
|
||||
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
|
||||
DrawTextEx(customFont, "Seed (Optional)", (Vector2){ (float)panelX + 40, (float)panelY + 200 }, 20, 1.0f, LIGHTGRAY);
|
||||
Rectangle seedBox = { (float)panelX + 40, (float)panelY + 230, 520, 40 };
|
||||
DrawTextEx(customFont, "Seed (Optional)", (Vector2){ (float)panelX + 50, (float)panelY + 200 }, 20, 1.0f, LIGHTGRAY);
|
||||
Rectangle seedBox = { (float)panelX + 50, (float)panelY + 230, 540, 45 };
|
||||
|
||||
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);
|
||||
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)) {
|
||||
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 btnHeight = 50;
|
||||
int backBtnX = panelX + 40;
|
||||
int btnY = panelY + panelHeight - 80;
|
||||
int btnY = panelY + panelHeight - 90;
|
||||
|
||||
// Back Button
|
||||
int backBtnX = panelX + 50;
|
||||
Rectangle backBtnBounds = { (float)backBtnX, (float)btnY, (float)btnWidth, (float)btnHeight };
|
||||
bool isBackHovered = CheckCollisionPointRec(mousePos, backBtnBounds);
|
||||
DrawRectangleRec(backBtnBounds, isBackHovered ? (Color){ 100, 100, 100, 255 } : (Color){ 60, 60, 60, 255 });
|
||||
DrawRectangleLinesEx(backBtnBounds, 3.0f, isBackHovered ? WHITE : GRAY);
|
||||
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);
|
||||
|
||||
if (isBackHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||
currentState = MAIN_MENU;
|
||||
}
|
||||
if (isBackHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) currentState = MAIN_MENU;
|
||||
|
||||
// Create Button
|
||||
int createBtnX = panelX + panelWidth - 40 - btnWidth;
|
||||
int createBtnX = panelX + panelWidth - 250;
|
||||
Rectangle createBtnBounds = { (float)createBtnX, (float)btnY, (float)btnWidth, (float)btnHeight };
|
||||
bool isCreateHovered = CheckCollisionPointRec(mousePos, createBtnBounds);
|
||||
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)) {
|
||||
currentWorldName = worldName;
|
||||
std::string baseName = worldName;
|
||||
int nameCounter = 1;
|
||||
while (std::filesystem::exists("saves/" + currentWorldName)) {
|
||||
currentWorldName = std::string(worldName) + " (" + std::to_string(nameCounter) + ")";
|
||||
currentWorldName = baseName + " " + std::to_string(nameCounter);
|
||||
nameCounter++;
|
||||
}
|
||||
// Update the worldName string so the HUD shows it correctly
|
||||
snprintf(worldName, sizeof(worldName), "%s", currentWorldName.c_str());
|
||||
worldNameLen = strlen(worldName);
|
||||
|
||||
gameTime = 87.5f; // Start new world at 7:00 AM
|
||||
|
||||
if (serverMode) {
|
||||
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
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);
|
||||
|
||||
// Simple hash for seed string
|
||||
|
|
@ -2650,27 +2813,12 @@ int main(void)
|
|||
}
|
||||
worldChunks.clear();
|
||||
|
||||
// Generate spawn chunk and place player on surface
|
||||
float spawnY = FindSpawnY(0, 0);
|
||||
camera3D.position = (Vector3){ 0.0f, spawnY, 0.0f };
|
||||
camera3D.target = (Vector3){ 0.0f, spawnY, 1.0f };
|
||||
playerVelocityY = 0.0f;
|
||||
camYaw = 0.0f; camPitch = 0.0f; // Reset look so WASD matches view
|
||||
|
||||
// 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;
|
||||
// Start progress screen
|
||||
isNewWorldGeneration = true;
|
||||
chunksGeneratedCount = 0;
|
||||
worldGenProgress = 0.0f;
|
||||
spawnSavedX = 0; spawnSavedY = -1; spawnSavedZ = 0; // Fresh world start at origin
|
||||
currentState = WORLD_CREATION_PROGRESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3017,7 +3165,21 @@ int main(void)
|
|||
}
|
||||
|
||||
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) {
|
||||
mouseHeldItem = slot;
|
||||
slot = InventorySlot(AIR, 0);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
v2.1.9
|
||||
v2.2.6
|
||||
|
|
|
|||
Loading…
Reference in New Issue