Implement world state synchronization - v2.0.1

This commit is contained in:
Michael Howard 2026-04-23 17:18:16 -05:00
parent dea13eaf2c
commit 99e63720fb
4 changed files with 157 additions and 12 deletions

Binary file not shown.

Binary file not shown.

View File

@ -105,6 +105,12 @@ struct RemotePlayer {
};
static std::vector<RemotePlayer> remotePlayers;
// Global Networking State
static Socket clientSocket = INVALID_SOCKET_VAL;
static Socket serverSocket = INVALID_SOCKET_VAL;
static std::vector<Socket> 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<Socket> 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 == "") {

View File

@ -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