Finalize Multiplayer v2.0.2 with Chat and Sync fixes

This commit is contained in:
Michael Howard 2026-04-23 17:28:26 -05:00
parent 99e63720fb
commit dbb6662538
4 changed files with 188 additions and 12 deletions

Binary file not shown.

Binary file not shown.

View File

@ -98,13 +98,20 @@ void LoadConfig();
std::string GetRemoteVersion(); std::string GetRemoteVersion();
struct RemotePlayer { struct RemotePlayer {
Socket sock; Socket sock; // Server only
uint32_t id;
std::string name; std::string name;
Vector3 position; Vector3 position;
float yaw; float yaw;
}; };
static std::vector<RemotePlayer> remotePlayers; static std::vector<RemotePlayer> remotePlayers;
struct ChatMessage {
std::string text;
float timer;
};
static std::vector<ChatMessage> chatLog;
// Global Networking State // Global Networking State
static Socket clientSocket = INVALID_SOCKET_VAL; static Socket clientSocket = INVALID_SOCKET_VAL;
static Socket serverSocket = 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. // By default, windows have minimize, maximize, and close buttons on the top bar.
SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT); SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT);
InitWindow(screenWidth, screenHeight, "MorriCraft v2.0.1"); InitWindow(screenWidth, screenHeight, "MorriCraft v2.0.2");
LoadConfig(); LoadConfig();
SetExitKey(KEY_NULL); // Prevent ESC from closing the window SetExitKey(KEY_NULL); // Prevent ESC from closing the window
@ -791,6 +798,11 @@ int main(void)
InitNetworking(); InitNetworking();
InventorySlot mouseHeldItem(AIR, 0); 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 gameTime = 75.0f; // Start at 6:00 AM
float breakProgress = 0.0f; float breakProgress = 0.0f;
int lastHitX = -1, lastHitY = -1, lastHitZ = -1; int lastHitX = -1, lastHitY = -1, lastHitZ = -1;
@ -961,29 +973,73 @@ int main(void)
PacketHandshake hand; PacketHandshake hand;
recv(sock, (char*)&hand, sizeof(hand), 0); recv(sock, (char*)&hand, sizeof(hand), 0);
if (isServer) { if (isServer) {
uint32_t newID = (uint32_t)sock;
RemotePlayer rp; RemotePlayer rp;
rp.sock = sock; rp.sock = sock;
rp.id = newID;
rp.name = hand.name; rp.name = hand.name;
rp.position = (Vector3){0,0,0}; rp.position = (Vector3){0,0,0};
remotePlayers.push_back(rp); 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) }; 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*)&sHead, sizeof(sHead), 0);
send(sock, (char*)&sData, sizeof(sData), 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) { } else if (head.type == PACKET_PLAYER_UPDATE) {
PacketPlayerUpdate pu; PacketPlayerUpdate pu;
recv(sock, (char*)&pu, sizeof(pu), 0); recv(sock, (char*)&pu, sizeof(pu), 0);
bool found = false;
for (auto& rp : remotePlayers) { for (auto& rp : remotePlayers) {
if (rp.sock == sock) { if (rp.id == pu.playerID) {
rp.position = (Vector3){ pu.x, pu.y, pu.z }; rp.position = (Vector3){ pu.x, pu.y, pu.z };
rp.yaw = pu.yaw; rp.yaw = pu.yaw;
found = true;
break; 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) { if (isServer) {
// Broadcast to others pu.playerID = (uint32_t)sock; // Ensure ID is correct
for (auto& other : clientSockets) { for (auto& other : clientSockets) {
if (other != sock) { if (other != sock) {
send(other, (char*)&head, sizeof(head), 0); send(other, (char*)&head, sizeof(head), 0);
@ -1016,16 +1072,41 @@ int main(void)
for (auto& pair : worldChunks) delete pair.second; for (auto& pair : worldChunks) delete pair.second;
worldChunks.clear(); 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)) { } else if (bytes == 0 || (bytes < 0 && SOCKET_ERROR_VAL != -1)) {
// Disconnect handling // Disconnect handling
if (isServer && clientIdx != -1) { if (isServer && clientIdx != -1) {
std::string leaver = "A player";
for (auto it = remotePlayers.begin(); it != remotePlayers.end(); ++it) { for (auto it = remotePlayers.begin(); it != remotePlayers.end(); ++it) {
if (it->sock == sock) { if (it->sock == sock) {
leaver = it->name;
remotePlayers.erase(it); remotePlayers.erase(it);
break; 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); closesocket(sock);
clientSockets.erase(clientSockets.begin() + clientIdx); clientSockets.erase(clientSockets.begin() + clientIdx);
} }
@ -1069,6 +1150,18 @@ int main(void)
} }
timeSyncTimer = 0.0f; 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 // Handle title music loop fading
@ -1162,8 +1255,53 @@ int main(void)
} }
} }
// ---- Manual Camera System (Look + WASD) ---- // Handle chat input
if (!inventoryOpen) { 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; const float MOUSE_SENS = 0.002f;
Vector2 md = GetMouseDelta(); Vector2 md = GetMouseDelta();
camYaw -= md.x * MOUSE_SENS; camYaw -= md.x * MOUSE_SENS;
@ -1452,9 +1590,21 @@ int main(void)
DrawRectangleLines(currentWidth/2 - bw/2, currentHeight/2, bw, bh, GRAY); DrawRectangleLines(currentWidth/2 - bw/2, currentHeight/2, bw, bh, GRAY);
if (downloadProgress >= 1.0f) { 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; currentState = MAIN_MENU;
updateReady = false; updateReady = false;
downloadProgress = 0.0f;
} }
} else if (currentState == CONNECT_MENU) { } else if (currentState == CONNECT_MENU) {
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 220 }); 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); DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE);
EndMode2D(); EndMode2D();
// Show Version Number (v2.0.1) in Red // Show Version Number (v2.0.2) in Red
DrawTextEx(customFont, "v2.0.1", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); DrawTextEx(customFont, "v2.0.2", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
// --- PLAYER NAME POPUP (IF MISSING) --- // --- PLAYER NAME POPUP (IF MISSING) ---
if (playerName == "") { 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, 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}); 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 ---- // ---- HOTBAR ----
const int slotSize = 50; const int slotSize = 50;
const int slotGap = 4; const int slotGap = 4;

View File

@ -53,7 +53,8 @@ enum PacketType {
PACKET_BLOCK_CHANGE = 2, PACKET_BLOCK_CHANGE = 2,
PACKET_TIME_SYNC = 3, PACKET_TIME_SYNC = 3,
PACKET_SEED_SYNC = 4, PACKET_SEED_SYNC = 4,
PACKET_DISCONNECT = 5 PACKET_CHAT = 5,
PACKET_DISCONNECT = 6
}; };
struct PacketHeader { struct PacketHeader {
@ -84,6 +85,11 @@ struct PacketSeedSync {
int seed; int seed;
}; };
struct PacketChat {
char name[32];
char message[128];
};
// Cross-platform socket initialization // Cross-platform socket initialization
inline bool InitNetworking() { inline bool InitNetworking() {
#ifdef _WIN32 #ifdef _WIN32