diff --git a/build-linux/MorriCraft b/build-linux/MorriCraft index d9aa365..eca48b5 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 cc88437..0a299b1 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 26ca030..97b1a27 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,6 +105,12 @@ struct RemotePlayer { }; static std::vector remotePlayers; +// Global Networking State +static Socket clientSocket = INVALID_SOCKET_VAL; +static Socket serverSocket = INVALID_SOCKET_VAL; +static std::vector clientSockets; +static bool isConnecting = false; + // ---- Inventory System ---- struct InventorySlot { int blockType = AIR; @@ -228,6 +234,23 @@ void RebuildChunkRenderList(Chunk* chunk, int cx, int cz) { chunk->dirty = false; } +void NetSetBlock(int x, int y, int z, int type) { + SetBlock(x, y, z, type); + PacketHeader head = { (uint8_t)PACKET_BLOCK_CHANGE, (uint32_t)sizeof(PacketBlockChange) }; + PacketBlockChange bc = { x, y, z, type }; + + if (clientSocket != INVALID_SOCKET_VAL) { + send(clientSocket, (char*)&head, sizeof(head), 0); + send(clientSocket, (char*)&bc, sizeof(bc), 0); + } + if (serverSocket != INVALID_SOCKET_VAL) { + for (auto& s : clientSockets) { + send(s, (char*)&head, sizeof(head), 0); + send(s, (char*)&bc, sizeof(bc), 0); + } + } +} + void SetBlock(int x, int y, int z, int type) { if (y < 0 || y >= CHUNK_HEIGHT) return; int cx = (int)floorf((float)x / CHUNK_SIZE); @@ -703,7 +726,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.0"); + InitWindow(screenWidth, screenHeight, "MorriCraft v2.0.1"); LoadConfig(); SetExitKey(KEY_NULL); // Prevent ESC from closing the window @@ -761,14 +784,10 @@ int main(void) vfile.close(); } - // Networking State + // Networking State (Target IP/Port remain local for UI) char targetIP[128] = "127.0.0.1"; char targetPort[16] = "12345"; int activeNetField = 0; // 0=none, 1=IP, 2=Port - bool isConnecting = false; - Socket clientSocket = INVALID_SOCKET_VAL; - Socket serverSocket = INVALID_SOCKET_VAL; - std::vector clientSockets; InitNetworking(); InventorySlot mouseHeldItem(AIR, 0); @@ -923,6 +942,7 @@ int main(void) if (select((int)clientSocket + 1, NULL, &writefds, NULL, &tv) > 0) { isConnecting = false; currentState = GAMEPLAY; + DisableCursor(); // Send Handshake PacketHeader head = { (uint8_t)PACKET_HANDSHAKE, (uint32_t)sizeof(PacketHandshake) }; PacketHandshake hand; @@ -932,6 +952,100 @@ int main(void) } } + // Handle incoming data + auto handleIncoming = [&](Socket sock, bool isServer, int clientIdx = -1) { + PacketHeader head; + int bytes = recv(sock, (char*)&head, sizeof(head), 0); + if (bytes > 0) { + if (head.type == PACKET_HANDSHAKE) { + PacketHandshake hand; + recv(sock, (char*)&hand, sizeof(hand), 0); + if (isServer) { + RemotePlayer rp; + rp.sock = sock; + rp.name = hand.name; + rp.position = (Vector3){0,0,0}; + remotePlayers.push_back(rp); + // Send seed to new client + PacketHeader sHead = { (uint8_t)PACKET_SEED_SYNC, (uint32_t)sizeof(PacketSeedSync) }; + PacketSeedSync sData = { globalSeedHash }; + send(sock, (char*)&sHead, sizeof(sHead), 0); + send(sock, (char*)&sData, sizeof(sData), 0); + } + } else if (head.type == PACKET_PLAYER_UPDATE) { + PacketPlayerUpdate pu; + recv(sock, (char*)&pu, sizeof(pu), 0); + for (auto& rp : remotePlayers) { + if (rp.sock == sock) { + rp.position = (Vector3){ pu.x, pu.y, pu.z }; + rp.yaw = pu.yaw; + break; + } + } + if (isServer) { + // Broadcast to others + for (auto& other : clientSockets) { + if (other != sock) { + send(other, (char*)&head, sizeof(head), 0); + send(other, (char*)&pu, sizeof(pu), 0); + } + } + } + } else if (head.type == PACKET_BLOCK_CHANGE) { + PacketBlockChange bc; + recv(sock, (char*)&bc, sizeof(bc), 0); + SetBlock(bc.x, bc.y, bc.z, bc.blockType); + if (isServer) { + for (auto& other : clientSockets) { + if (other != sock) { + send(other, (char*)&head, sizeof(head), 0); + send(other, (char*)&bc, sizeof(bc), 0); + } + } + } + } else if (head.type == PACKET_TIME_SYNC) { + PacketTimeSync ts; + recv(sock, (char*)&ts, sizeof(ts), 0); + if (!isServer) gameTime = ts.gameTime; + } else if (head.type == PACKET_SEED_SYNC) { + PacketSeedSync ss; + recv(sock, (char*)&ss, sizeof(ss), 0); + if (!isServer) { + globalSeedHash = ss.seed; + // Regerate world if seed changed + for (auto& pair : worldChunks) delete pair.second; + worldChunks.clear(); + } + } + } else if (bytes == 0 || (bytes < 0 && SOCKET_ERROR_VAL != -1)) { + // Disconnect handling + if (isServer && clientIdx != -1) { + for (auto it = remotePlayers.begin(); it != remotePlayers.end(); ++it) { + if (it->sock == sock) { + remotePlayers.erase(it); + break; + } + } + closesocket(sock); + clientSockets.erase(clientSockets.begin() + clientIdx); + } + } + }; + + if (clientSocket != INVALID_SOCKET_VAL && !isConnecting) { + handleIncoming(clientSocket, false); + // Send our position + static float netTimer = 0.0f; + netTimer += GetFrameTime(); + if (netTimer > 0.05f) { // 20Hz update + 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 }; + send(clientSocket, (char*)&head, sizeof(head), 0); + send(clientSocket, (char*)&pu, sizeof(pu), 0); + netTimer = 0.0f; + } + } + if (serverSocket != INVALID_SOCKET_VAL) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); @@ -940,6 +1054,21 @@ int main(void) SetNonBlocking(newClient); clientSockets.push_back(newClient); } + for (size_t i = 0; i < clientSockets.size(); i++) { + handleIncoming(clientSockets[i], true, i); + } + // Send time sync periodically + static float timeSyncTimer = 0.0f; + timeSyncTimer += GetFrameTime(); + if (timeSyncTimer > 2.0f) { + PacketHeader head = { (uint8_t)PACKET_TIME_SYNC, (uint32_t)sizeof(PacketTimeSync) }; + PacketTimeSync ts = { gameTime }; + for (auto& s : clientSockets) { + send(s, (char*)&head, sizeof(head), 0); + send(s, (char*)&ts, sizeof(ts), 0); + } + timeSyncTimer = 0.0f; + } } // Handle title music loop fading @@ -1187,7 +1316,7 @@ int main(void) if (breakProgress >= breakSpeed) { AddToInventory(targetBlock); - SetBlock(hitX, hitY, hitZ, AIR); + NetSetBlock(hitX, hitY, hitZ, AIR); breakProgress = 0.0f; isSwinging = true; // Swing when finishing } @@ -1208,7 +1337,7 @@ int main(void) int placeX = hitX + (int)roundf(closestNormal.x); int placeY = hitY + (int)roundf(closestNormal.y); int placeZ = hitZ + (int)roundf(closestNormal.z); - SetBlock(placeX, placeY, placeZ, hotbar[activeHotbarSlot].blockType); + NetSetBlock(placeX, placeY, placeZ, hotbar[activeHotbarSlot].blockType); hotbar[activeHotbarSlot].count--; if (hotbar[activeHotbarSlot].count == 0) hotbar[activeHotbarSlot].blockType = AIR; @@ -1418,8 +1547,8 @@ int main(void) DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE); EndMode2D(); - // Show Version Number (v2.0.0) in Red - DrawTextEx(customFont, "v2.0.0", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); + // Show Version Number (v2.0.1) in Red + DrawTextEx(customFont, "v2.0.1", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); // --- PLAYER NAME POPUP (IF MISSING) --- if (playerName == "") { diff --git a/src/network.h b/src/network.h index bb5cd6a..52c3bbd 100644 --- a/src/network.h +++ b/src/network.h @@ -50,8 +50,10 @@ enum PacketType { PACKET_HANDSHAKE = 0, PACKET_PLAYER_UPDATE = 1, - PACKET_CHUNK_DATA = 2, - PACKET_DISCONNECT = 3 + PACKET_BLOCK_CHANGE = 2, + PACKET_TIME_SYNC = 3, + PACKET_SEED_SYNC = 4, + PACKET_DISCONNECT = 5 }; struct PacketHeader { @@ -66,6 +68,20 @@ struct PacketHandshake { struct PacketPlayerUpdate { float x, y, z; float yaw; + uint32_t playerID; // Assigned by server +}; + +struct PacketBlockChange { + int x, y, z; + int blockType; +}; + +struct PacketTimeSync { + float gameTime; +}; + +struct PacketSeedSync { + int seed; }; // Cross-platform socket initialization