diff --git a/build-linux/MorriCraft b/build-linux/MorriCraft index c5796d8..c50c877 100755 Binary files a/build-linux/MorriCraft and b/build-linux/MorriCraft differ diff --git a/build-windows/MorriCraft.exe b/build-windows/MorriCraft.exe index 3d37989..76468f5 100755 Binary files a/build-windows/MorriCraft.exe and b/build-windows/MorriCraft.exe differ diff --git a/src/main.cpp b/src/main.cpp index e5cd9f3..fb6771b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -91,6 +91,9 @@ static float masterMusicVolume = 1.0f; static float masterSoundVolume = 1.0f; static Color myShirtColor = BLUE; static Color myPantsColor = DARKBLUE; +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 }; @@ -780,7 +783,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.1"); + InitWindow(screenWidth, screenHeight, "MorriCraft v2.1.2"); LoadConfig(); SetExitKey(KEY_NULL); // Prevent ESC from closing the window @@ -844,6 +847,10 @@ int main(void) int activeNetField = 0; // 0=none, 1=IP, 2=Port InitNetworking(); + InitAudioDevice(); + hitSound = LoadSound("assets/hit.wav"); + if (hitSound.frameCount == 0) TraceLog(LOG_WARNING, "Hit sound NOT LOADED! Make sure assets/hit.wav exists."); + InventorySlot mouseHeldItem(AIR, 0); // Chat State @@ -1093,6 +1100,11 @@ int main(void) SendAll(sock, (char*)&pData, sizeof(pData)); } } + // Tell the new client its own ID (its socket value) + PacketHeader idHead = { (uint8_t)PACKET_PLAYER_UPDATE, (uint32_t)sizeof(PacketPlayerUpdate) }; + PacketPlayerUpdate idData = { camera3D.position.x, camera3D.position.y - 1.6f, camera3D.position.z, camYaw, (uint32_t)sock }; + SendAll(sock, (char*)&idHead, sizeof(idHead)); + PacketHeader hHead = { (uint8_t)PACKET_PLAYER_UPDATE, (uint32_t)sizeof(PacketPlayerUpdate) }; PacketPlayerUpdate hData = { camera3D.position.x, camera3D.position.y - 1.6f, camera3D.position.z, camYaw, 0 }; SendAll(sock, (char*)&hHead, sizeof(hHead)); @@ -1112,15 +1124,20 @@ int main(void) break; } } - if (!found && !isServer) { - RemotePlayer rp; - rp.id = pu.playerID; - rp.name = "Remote Player"; - rp.position = (Vector3){ pu.x, pu.y, pu.z }; - rp.yaw = pu.yaw; - rp.shirtColor = BLUE; // Default until handshake syncs - rp.pantsColor = DARKBLUE; - remotePlayers.push_back(rp); + if (pu.playerID != 0 && !isServer && pu.playerID != (uint32_t)clientSocket) { + // If it's a new player we haven't seen yet + if (!found) { + RemotePlayer rp; + rp.id = pu.playerID; + rp.name = "Remote Player"; + rp.position = (Vector3){ pu.x, pu.y, pu.z }; + rp.yaw = pu.yaw; + rp.shirtColor = BLUE; + rp.pantsColor = DARKBLUE; + remotePlayers.push_back(rp); + } + } else if (!isServer && pu.playerID == (uint32_t)clientSocket) { + localPlayerID = pu.playerID; } if (isServer) { pu.playerID = (uint32_t)sock; @@ -1154,6 +1171,13 @@ int main(void) globalSeedHash = ss.seed; for (auto& pair : worldChunks) delete pair.second; worldChunks.clear(); + + // Force spawn generation to fix "falling through ground" + int scx = (int)floorf(camera3D.position.x / CHUNK_SIZE); + int scz = (int)floorf(camera3D.position.z / CHUNK_SIZE); + GenerateChunk(scx, scz); + float sy = FindSpawnY((int)camera3D.position.x, (int)camera3D.position.z); + camera3D.position.y = sy + 1.6f; } } else if (head.type == PACKET_CHAT) { PacketChat pc; @@ -1167,6 +1191,30 @@ int main(void) } } } + } else if (head.type == PACKET_PLAYER_HIT) { + PacketPlayerHit ph; + if (RecvAll(sock, (char*)&ph, sizeof(ph)) <= 0) break; + + if (ph.targetID == localPlayerID) { + // I got hit! + playerHealth -= ph.damage; + playerVelocityY = 6.0f; // Small jump + Vector3 pushDir = Vector3Normalize((Vector3){ camera3D.position.x - ph.attackerX, 0, camera3D.position.z - ph.attackerZ }); + camera3D.position.x += pushDir.x * 0.8f; + camera3D.position.z += pushDir.z * 0.8f; + PlaySound(hitSound); + chatLog.push_back({ "You were hit!", 2.0f }); + } + + if (isServer) { + // Broadcast hit to all OTHER clients + for (auto& other : clientSockets) { + if (other != sock) { + SendAll(other, (char*)&head, sizeof(head)); + SendAll(other, (char*)&ph, sizeof(ph)); + } + } + } } else { if (head.size > 0 && head.size < 2048) { std::vector discard(head.size); @@ -1452,6 +1500,34 @@ int main(void) // Block Raycasting (moved outside to update every frame for wireframe) hitBlock = false; if (!inventoryOpen && !isChatting) { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + isSwinging = true; + swingTime = 0.0f; + + // PvP Attack Raycast + Ray ray = GetMouseRay((Vector2){ (float)currentWidth / 2, (float)currentHeight / 2 }, camera3D); + for (auto& rp : remotePlayers) { + BoundingBox pBox = { (Vector3){ rp.position.x - 0.4f, rp.position.y, rp.position.z - 0.4f }, + (Vector3){ rp.position.x + 0.4f, rp.position.y + 1.8f, rp.position.z + 0.4f } }; + RayCollision rCol = GetRayCollisionBox(ray, pBox); + if (rCol.hit && rCol.distance < 4.5f) { + PacketHeader hitH = { (uint8_t)PACKET_PLAYER_HIT, (uint32_t)sizeof(PacketPlayerHit) }; + PacketPlayerHit hitD = { rp.id, 1.0f, camera3D.position.x, camera3D.position.z }; + if (clientSocket != INVALID_SOCKET_VAL) { + SendAll(clientSocket, (char*)&hitH, sizeof(hitH)); + SendAll(clientSocket, (char*)&hitD, sizeof(hitD)); + } else if (serverMode) { + for (auto& s : clientSockets) { + if (s == (Socket)rp.id) { + SendAll(s, (char*)&hitH, sizeof(hitH)); + SendAll(s, (char*)&hitD, sizeof(hitD)); + } + } + } + break; + } + } + } Ray ray = GetMouseRay((Vector2){ (float)currentWidth / 2, (float)currentHeight / 2 }, camera3D); float closestDist = 8.0f; @@ -1868,8 +1944,8 @@ int main(void) DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE); EndMode2D(); - // Show Version Number (v2.1.1) in Red - DrawTextEx(customFont, "v2.1.1", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); + // Show Version Number (v2.1.2) in Red + DrawTextEx(customFont, "v2.1.2", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); // --- PLAYER NAME POPUP (IF MISSING) --- if (playerName == "") { @@ -2602,16 +2678,6 @@ int main(void) camera3D.position.x, camera3D.position.y - 1.6f, camera3D.position.z), (Vector2){ 10, 80 }, 16, 1.0f, DARKGRAY); - // --- In-Game Clock --- - int totalMinutes = (int)(timeOfDay * 24 * 60); - int hours = (totalMinutes / 60) % 24; - int minutes = totalMinutes % 60; - const char* ampm = (hours >= 12) ? "PM" : "AM"; - int displayHours = hours % 12; - if (displayHours == 0) displayHours = 12; - - DrawTextEx(customFont, TextFormat("Time: %i:%02i %s", displayHours, minutes, ampm), (Vector2){ 10, 110 }, 18, 1.0f, (dayFactor > 0.5f) ? BLACK : WHITE); - DrawTextEx(customFont, (dayFactor > 0.5f) ? "Status: Day" : "Status: Night", (Vector2){ 10, 130 }, 16, 1.0f, (dayFactor > 0.5f) ? (Color){180, 150, 0, 255} : (Color){100, 100, 255, 255}); // Draw Chat Log (Bottom Left, above inventory) int chatY = currentHeight - 120; @@ -2640,6 +2706,21 @@ int main(void) int hotbarX = currentWidth / 2 - hotbarW / 2; int hotbarY = currentHeight - slotSize - 12; + // --- Health Hearts --- + int heartX = currentWidth / 2 - 225; + int heartY = hotbarY - 35; + for (int i = 0; i < 8; i++) { + Rectangle heartRect = { (float)heartX + i * 28, (float)heartY, 24, 24 }; + float h = playerHealth - (i * 2); + if (h >= 2.0f) DrawRectangleRec(heartRect, RED); // Full heart + else if (h >= 1.0f) { + DrawRectangle((int)heartRect.x, (int)heartRect.y, 12, 24, RED); // Half heart + DrawRectangleLinesEx(heartRect, 2.0f, DARKGRAY); + } else { + DrawRectangleLinesEx(heartRect, 2.0f, GRAY); // Empty heart + } + } + // Hotbar background panel DrawRectangle(hotbarX - 6, hotbarY - 6, hotbarW + 12, slotSize + 12, (Color){ 30, 30, 30, 180 }); diff --git a/src/network.h b/src/network.h index 44f0ebb..c5bea7e 100644 --- a/src/network.h +++ b/src/network.h @@ -54,7 +54,14 @@ enum PacketType { PACKET_TIME_SYNC = 3, PACKET_SEED_SYNC = 4, PACKET_CHAT = 5, - PACKET_DISCONNECT = 6 + PACKET_DISCONNECT = 6, + PACKET_PLAYER_HIT = 7 +}; + +struct PacketPlayerHit { + uint32_t targetID; + float damage; + float attackerX, attackerZ; }; struct PacketHeader {