diff --git a/build-linux/MorriCraft b/build-linux/MorriCraft index eca48b5..852898f 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 0a299b1..7e12624 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 97b1a27..743059b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -98,13 +98,20 @@ void LoadConfig(); std::string GetRemoteVersion(); struct RemotePlayer { - Socket sock; + Socket sock; // Server only + uint32_t id; std::string name; Vector3 position; float yaw; }; static std::vector remotePlayers; +struct ChatMessage { + std::string text; + float timer; +}; +static std::vector chatLog; + // Global Networking State static Socket clientSocket = INVALID_SOCKET_VAL; static Socket serverSocket = INVALID_SOCKET_VAL; @@ -726,7 +733,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.0.1"); + InitWindow(screenWidth, screenHeight, "MorriCraft v2.0.2"); LoadConfig(); SetExitKey(KEY_NULL); // Prevent ESC from closing the window @@ -791,6 +798,11 @@ int main(void) InitNetworking(); InventorySlot mouseHeldItem(AIR, 0); + + // Chat State + 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 breakProgress = 0.0f; int lastHitX = -1, lastHitY = -1, lastHitZ = -1; @@ -961,29 +973,73 @@ int main(void) PacketHandshake hand; recv(sock, (char*)&hand, sizeof(hand), 0); if (isServer) { + uint32_t newID = (uint32_t)sock; RemotePlayer rp; rp.sock = sock; + rp.id = newID; rp.name = hand.name; rp.position = (Vector3){0,0,0}; remotePlayers.push_back(rp); - // Send seed to new client + + // Notify others + chatLog.push_back({ std::string(hand.name) + " joined the game", 5.0f }); + PacketHeader nHead = { (uint8_t)PACKET_CHAT, (uint32_t)sizeof(PacketChat) }; + PacketChat nChat; + strncpy(nChat.name, "Server", 31); + strncpy(nChat.message, (std::string(hand.name) + " joined the game").c_str(), 127); + for (auto& s : clientSockets) { + send(s, (char*)&nHead, sizeof(nHead), 0); + send(s, (char*)&nChat, sizeof(nChat), 0); + } + + // Send seed and time to new client PacketHeader sHead = { (uint8_t)PACKET_SEED_SYNC, (uint32_t)sizeof(PacketSeedSync) }; - PacketSeedSync sData = { globalSeedHash }; + PacketSeedSync sData = { (int)globalSeedHash }; send(sock, (char*)&sHead, sizeof(sHead), 0); send(sock, (char*)&sData, sizeof(sData), 0); + + PacketHeader tHead = { (uint8_t)PACKET_TIME_SYNC, (uint32_t)sizeof(PacketTimeSync) }; + PacketTimeSync tData = { gameTime }; + send(sock, (char*)&tHead, sizeof(tHead), 0); + send(sock, (char*)&tData, sizeof(tData), 0); + + // Send existing players to new client + for (auto& rp : remotePlayers) { + if (rp.sock != sock) { + PacketHeader pHead = { (uint8_t)PACKET_PLAYER_UPDATE, (uint32_t)sizeof(PacketPlayerUpdate) }; + PacketPlayerUpdate pData = { rp.position.x, rp.position.y, rp.position.z, rp.yaw, rp.id }; + send(sock, (char*)&pHead, sizeof(pHead), 0); + send(sock, (char*)&pData, sizeof(pData), 0); + } + } + // Send HOST to new client (Host ID is always 0) + 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 }; + send(sock, (char*)&hHead, sizeof(hHead), 0); + send(sock, (char*)&hData, sizeof(hData), 0); } } else if (head.type == PACKET_PLAYER_UPDATE) { PacketPlayerUpdate pu; recv(sock, (char*)&pu, sizeof(pu), 0); + bool found = false; for (auto& rp : remotePlayers) { - if (rp.sock == sock) { + if (rp.id == pu.playerID) { rp.position = (Vector3){ pu.x, pu.y, pu.z }; rp.yaw = pu.yaw; + found = true; 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; + remotePlayers.push_back(rp); + } if (isServer) { - // Broadcast to others + pu.playerID = (uint32_t)sock; // Ensure ID is correct for (auto& other : clientSockets) { if (other != sock) { send(other, (char*)&head, sizeof(head), 0); @@ -1016,16 +1072,41 @@ int main(void) for (auto& pair : worldChunks) delete pair.second; worldChunks.clear(); } + } else if (head.type == PACKET_CHAT) { + PacketChat pc; + recv(sock, (char*)&pc, sizeof(pc), 0); + chatLog.push_back({ std::string(pc.name) + ": " + pc.message, 5.0f }); + if (isServer) { + for (auto& other : clientSockets) { + if (other != sock) { + send(other, (char*)&head, sizeof(head), 0); + send(other, (char*)&pc, sizeof(pc), 0); + } + } + } } } else if (bytes == 0 || (bytes < 0 && SOCKET_ERROR_VAL != -1)) { // Disconnect handling if (isServer && clientIdx != -1) { + std::string leaver = "A player"; for (auto it = remotePlayers.begin(); it != remotePlayers.end(); ++it) { if (it->sock == sock) { + leaver = it->name; remotePlayers.erase(it); break; } } + chatLog.push_back({ leaver + " left the game", 5.0f }); + PacketHeader nHead = { (uint8_t)PACKET_CHAT, (uint32_t)sizeof(PacketChat) }; + PacketChat nChat; + strncpy(nChat.name, "Server", 31); + strncpy(nChat.message, (leaver + " left the game").c_str(), 127); + for (auto& s : clientSockets) { + if (s != sock) { + send(s, (char*)&nHead, sizeof(nHead), 0); + send(s, (char*)&nChat, sizeof(nChat), 0); + } + } closesocket(sock); clientSockets.erase(clientSockets.begin() + clientIdx); } @@ -1069,6 +1150,18 @@ int main(void) } timeSyncTimer = 0.0f; } + // Send Host position periodically + static float hostNetTimer = 0.0f; + hostNetTimer += GetFrameTime(); + if (hostNetTimer > 0.05f) { + PacketHeader head = { (uint8_t)PACKET_PLAYER_UPDATE, (uint32_t)sizeof(PacketPlayerUpdate) }; + PacketPlayerUpdate pu = { camera3D.position.x, camera3D.position.y - 1.6f, camera3D.position.z, camYaw, 0 }; + for (auto& s : clientSockets) { + send(s, (char*)&head, sizeof(head), 0); + send(s, (char*)&pu, sizeof(pu), 0); + } + hostNetTimer = 0.0f; + } } // Handle title music loop fading @@ -1162,8 +1255,53 @@ int main(void) } } - // ---- Manual Camera System (Look + WASD) ---- - if (!inventoryOpen) { + // Handle chat input + if (IsKeyPressed(KEY_ENTER)) { + if (isChatting) { + if (strlen(chatInput) > 0) { + // Send Chat Packet + 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) { + send(clientSocket, (char*)&head, sizeof(head), 0); + send(clientSocket, (char*)&pc, sizeof(pc), 0); + } + if (serverSocket != INVALID_SOCKET_VAL) { + chatLog.push_back({ std::string(playerName) + ": " + chatInput, 5.0f }); + for (auto& s : clientSockets) { + send(s, (char*)&head, sizeof(head), 0); + send(s, (char*)&pc, sizeof(pc), 0); + } + } + chatInput[0] = '\0'; + } + isChatting = false; + DisableCursor(); + } else { + isChatting = true; + EnableCursor(); + } + } + + if (isChatting) { + int c = GetCharPressed(); + while (c > 0) { + if (c >= 32 && c <= 125 && strlen(chatInput) < 120) { + int len = strlen(chatInput); + chatInput[len] = (char)c; + chatInput[len+1] = '\0'; + } + c = GetCharPressed(); + } + if (IsKeyPressed(KEY_BACKSPACE)) { + int len = strlen(chatInput); + if (len > 0) chatInput[len-1] = '\0'; + } + } + + if (!inventoryOpen && !isChatting) { const float MOUSE_SENS = 0.002f; Vector2 md = GetMouseDelta(); camYaw -= md.x * MOUSE_SENS; @@ -1452,9 +1590,21 @@ int main(void) DrawRectangleLines(currentWidth/2 - bw/2, currentHeight/2, bw, bh, GRAY); if (downloadProgress >= 1.0f) { - // Simulate restart + // Update local version file to stop update prompts + std::ofstream vout("version.txt"); + if (vout.is_open()) { + vout << latestVersion; + vout.close(); + } + std::ofstream vout2("assets/version.txt"); + if (vout2.is_open()) { + vout2 << latestVersion; + vout2.close(); + } + currentState = MAIN_MENU; updateReady = false; + downloadProgress = 0.0f; } } else if (currentState == CONNECT_MENU) { DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 220 }); @@ -1547,8 +1697,8 @@ int main(void) DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE); EndMode2D(); - // Show Version Number (v2.0.1) in Red - DrawTextEx(customFont, "v2.0.1", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); + // Show Version Number (v2.0.2) in Red + DrawTextEx(customFont, "v2.0.2", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); // --- PLAYER NAME POPUP (IF MISSING) --- if (playerName == "") { @@ -2291,6 +2441,26 @@ int main(void) 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; + for (auto it = chatLog.begin(); it != chatLog.end();) { + it->timer -= GetFrameTime(); + if (it->timer <= 0) { + it = chatLog.erase(it); + } else { + float alpha = (it->timer > 1.0f) ? 1.0f : it->timer; + DrawTextEx(customFont, it->text.c_str(), (Vector2){ 20, (float)chatY }, 20, 1.0f, Fade(WHITE, alpha)); + chatY -= 25; + ++it; + } + } + + if (isChatting) { + DrawRectangle(10, currentHeight - 100, 400, 40, (Color){ 0, 0, 0, 180 }); + DrawRectangleLines(10, currentHeight - 100, 400, 40, DARKGRAY); + DrawTextEx(customFont, TextFormat("> %s", chatInput), (Vector2){ 20, (float)currentHeight - 90 }, 20, 1.0f, WHITE); + } + // ---- HOTBAR ---- const int slotSize = 50; const int slotGap = 4; diff --git a/src/network.h b/src/network.h index 52c3bbd..6a5e812 100644 --- a/src/network.h +++ b/src/network.h @@ -53,7 +53,8 @@ enum PacketType { PACKET_BLOCK_CHANGE = 2, PACKET_TIME_SYNC = 3, PACKET_SEED_SYNC = 4, - PACKET_DISCONNECT = 5 + PACKET_CHAT = 5, + PACKET_DISCONNECT = 6 }; struct PacketHeader { @@ -84,6 +85,11 @@ struct PacketSeedSync { int seed; }; +struct PacketChat { + char name[32]; + char message[128]; +}; + // Cross-platform socket initialization inline bool InitNetworking() { #ifdef _WIN32