Implement basic multiplayer foundation - v2.0.0
This commit is contained in:
parent
a96e983ef8
commit
acf6d84d7c
176
src/main.cpp
176
src/main.cpp
|
|
@ -11,6 +11,7 @@
|
|||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include "rlgl.h"
|
||||
#include "network.h"
|
||||
|
||||
#define CHUNK_SIZE 32
|
||||
#ifndef PI
|
||||
|
|
@ -85,7 +86,7 @@ static std::string currentWorldName = "";
|
|||
static std::string playerName = "Player";
|
||||
static bool serverMode = false;
|
||||
|
||||
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 };
|
||||
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 };
|
||||
|
||||
// Forward Declarations
|
||||
bool IsExposedOptimized(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, Chunk* nxP, Chunk* nzM, Chunk* nzP);
|
||||
|
|
@ -96,6 +97,14 @@ void SaveConfig();
|
|||
void LoadConfig();
|
||||
std::string GetRemoteVersion();
|
||||
|
||||
struct RemotePlayer {
|
||||
Socket sock;
|
||||
std::string name;
|
||||
Vector3 position;
|
||||
float yaw;
|
||||
};
|
||||
static std::vector<RemotePlayer> remotePlayers;
|
||||
|
||||
// ---- Inventory System ----
|
||||
struct InventorySlot {
|
||||
int blockType = AIR;
|
||||
|
|
@ -752,7 +761,16 @@ int main(void)
|
|||
vfile.close();
|
||||
}
|
||||
|
||||
InventorySlot mouseHeldItem(AIR, 0);
|
||||
// Networking State
|
||||
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();
|
||||
float gameTime = 75.0f; // Start at 6:00 AM
|
||||
float breakProgress = 0.0f;
|
||||
int lastHitX = -1, lastHitY = -1, lastHitZ = -1;
|
||||
|
|
@ -895,6 +913,34 @@ int main(void)
|
|||
if (IsMusicStreamPlaying(titleMusic)) StopMusicStream(titleMusic);
|
||||
}
|
||||
|
||||
// --- NETWORK UPDATE ---
|
||||
if (isConnecting) {
|
||||
fd_set writefds;
|
||||
FD_ZERO(&writefds);
|
||||
FD_SET(clientSocket, &writefds);
|
||||
struct timeval tv = {0, 0};
|
||||
if (select((int)clientSocket + 1, NULL, &writefds, NULL, &tv) > 0) {
|
||||
isConnecting = false;
|
||||
currentState = GAMEPLAY;
|
||||
// Send Handshake
|
||||
PacketHeader head = { (uint8_t)PACKET_HANDSHAKE, (uint32_t)sizeof(PacketHandshake) };
|
||||
PacketHandshake hand;
|
||||
strncpy(hand.name, playerName.c_str(), 31);
|
||||
send(clientSocket, (char*)&head, sizeof(head), 0);
|
||||
send(clientSocket, (char*)&hand, sizeof(hand), 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (serverSocket != INVALID_SOCKET_VAL) {
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
Socket newClient = accept(serverSocket, (struct sockaddr*)&client_addr, &client_len);
|
||||
if (newClient != INVALID_SOCKET_VAL) {
|
||||
SetNonBlocking(newClient);
|
||||
clientSockets.push_back(newClient);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle title music loop fading
|
||||
float fadeTime = 2.0f; // 2 seconds fade
|
||||
float timePlayed = GetMusicTimePlayed(titleMusic);
|
||||
|
|
@ -1280,6 +1326,87 @@ int main(void)
|
|||
currentState = MAIN_MENU;
|
||||
updateReady = false;
|
||||
}
|
||||
} else if (currentState == CONNECT_MENU) {
|
||||
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 220 });
|
||||
int cw = 500, ch = 350;
|
||||
Rectangle cBox = { (float)currentWidth/2 - cw/2, (float)currentHeight/2 - ch/2, (float)cw, (float)ch };
|
||||
DrawRectangleRec(cBox, (Color){ 30, 30, 30, 255 });
|
||||
DrawRectangleLinesEx(cBox, 4.0f, DARKGRAY);
|
||||
|
||||
DrawTextEx(customFont, "DIRECT CONNECT", (Vector2){ cBox.x + 110, cBox.y + 30 }, 28, 1.0f, WHITE);
|
||||
|
||||
// IP Address Input
|
||||
DrawTextEx(customFont, "Server IP Address:", (Vector2){ cBox.x + 50, cBox.y + 90 }, 20, 1.0f, LIGHTGRAY);
|
||||
Rectangle ipRect = { cBox.x + 50, cBox.y + 120, 400, 40 };
|
||||
bool isIPHovered = CheckCollisionPointRec(mousePos, ipRect);
|
||||
DrawRectangleRec(ipRect, (activeNetField == 1) ? BLACK : (isIPHovered ? (Color){ 50, 50, 50, 255 } : (Color){ 40, 40, 40, 255 }));
|
||||
DrawRectangleLinesEx(ipRect, 2, (activeNetField == 1) ? GREEN : GRAY);
|
||||
DrawTextEx(customFont, targetIP, (Vector2){ ipRect.x + 10, ipRect.y + 10 }, 20, 1.0f, WHITE);
|
||||
if (isIPHovered && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) activeNetField = 1;
|
||||
|
||||
// Port Input
|
||||
DrawTextEx(customFont, "Server Port:", (Vector2){ cBox.x + 50, cBox.y + 180 }, 20, 1.0f, LIGHTGRAY);
|
||||
Rectangle portRect = { cBox.x + 50, cBox.y + 210, 150, 40 };
|
||||
bool isPortHovered = CheckCollisionPointRec(mousePos, portRect);
|
||||
DrawRectangleRec(portRect, (activeNetField == 2) ? BLACK : (isPortHovered ? (Color){ 50, 50, 50, 255 } : (Color){ 40, 40, 40, 255 }));
|
||||
DrawRectangleLinesEx(portRect, 2, (activeNetField == 2) ? GREEN : GRAY);
|
||||
DrawTextEx(customFont, targetPort, (Vector2){ portRect.x + 10, portRect.y + 10 }, 20, 1.0f, WHITE);
|
||||
if (isPortHovered && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) activeNetField = 2;
|
||||
|
||||
// Input handling for IP/Port
|
||||
int key = GetCharPressed();
|
||||
while (key > 0) {
|
||||
if (activeNetField == 1 && strlen(targetIP) < 127) {
|
||||
int len = strlen(targetIP);
|
||||
targetIP[len] = (char)key;
|
||||
targetIP[len+1] = '\0';
|
||||
} else if (activeNetField == 2 && strlen(targetPort) < 15) {
|
||||
int len = strlen(targetPort);
|
||||
targetPort[len] = (char)key;
|
||||
targetPort[len+1] = '\0';
|
||||
}
|
||||
key = GetCharPressed();
|
||||
}
|
||||
if (IsKeyPressed(KEY_BACKSPACE)) {
|
||||
if (activeNetField == 1) {
|
||||
int len = strlen(targetIP);
|
||||
if (len > 0) targetIP[len-1] = '\0';
|
||||
} else if (activeNetField == 2) {
|
||||
int len = strlen(targetPort);
|
||||
if (len > 0) targetPort[len-1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
// Join Button Logic
|
||||
Rectangle joinBtn = { cBox.x + 50, cBox.y + 280, 180, 45 };
|
||||
bool isJoinHovered = CheckCollisionPointRec(mousePos, joinBtn);
|
||||
DrawRectangleRec(joinBtn, isJoinHovered ? GREEN : (Color){ 0, 120, 0, 255 });
|
||||
DrawTextEx(customFont, "JOIN SERVER", (Vector2){ joinBtn.x + 25, joinBtn.y + 12 }, 20, 1.0f, WHITE);
|
||||
|
||||
if (isJoinHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||
clientSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (clientSocket != INVALID_SOCKET_VAL) {
|
||||
SetNonBlocking(clientSocket);
|
||||
struct sockaddr_in serv_addr;
|
||||
serv_addr.sin_family = AF_INET;
|
||||
serv_addr.sin_port = htons(atoi(targetPort));
|
||||
inet_pton(AF_INET, targetIP, &serv_addr.sin_addr);
|
||||
|
||||
connect(clientSocket, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
|
||||
isConnecting = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel Button
|
||||
Rectangle cancelBtn = { cBox.x + 270, cBox.y + 280, 180, 45 };
|
||||
bool isCancelHovered = CheckCollisionPointRec(mousePos, cancelBtn);
|
||||
DrawRectangleRec(cancelBtn, isCancelHovered ? RED : (Color){ 120, 0, 0, 255 });
|
||||
DrawTextEx(customFont, "CANCEL", (Vector2){ cancelBtn.x + 55, cancelBtn.y + 12 }, 20, 1.0f, WHITE);
|
||||
if (isCancelHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||
currentState = MAIN_MENU;
|
||||
activeNetField = 0;
|
||||
}
|
||||
|
||||
} else if (currentState != GAMEPLAY) {
|
||||
BeginMode2D(camera);
|
||||
// Draw the texture, scaling it to fit the current window size exactly
|
||||
|
|
@ -1453,6 +1580,19 @@ int main(void)
|
|||
}
|
||||
worldChunks.clear();
|
||||
|
||||
if (serverMode) {
|
||||
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (serverSocket != INVALID_SOCKET_VAL) {
|
||||
SetNonBlocking(serverSocket);
|
||||
struct sockaddr_in serv_addr;
|
||||
serv_addr.sin_family = AF_INET;
|
||||
serv_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
serv_addr.sin_port = htons(12345);
|
||||
bind(serverSocket, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
|
||||
listen(serverSocket, 5);
|
||||
}
|
||||
}
|
||||
|
||||
currentState = GAMEPLAY;
|
||||
DisableCursor();
|
||||
inventoryOpen = false;
|
||||
|
|
@ -1609,6 +1749,17 @@ int main(void)
|
|||
DrawRectangleRec(serverCheck, serverMode ? GREEN : DARKGRAY);
|
||||
DrawRectangleLinesEx(serverCheck, 2.0f, isServerHovered ? WHITE : GRAY);
|
||||
DrawTextEx(customFont, "Server Mode (Experimental)", (Vector2){ serverCheck.x + 35, serverCheck.y }, 18, 1.0f, WHITE);
|
||||
|
||||
// Connect Button
|
||||
Rectangle connBtn = { (float)currentWidth/2 - 100, (float)currentHeight/2 + 70, 200, 40 };
|
||||
bool isConnHovered = CheckCollisionPointRec(mousePos, connBtn);
|
||||
DrawRectangleRec(connBtn, isConnHovered ? DARKGRAY : BLACK);
|
||||
DrawRectangleLinesEx(connBtn, 2, GRAY);
|
||||
DrawTextEx(customFont, "CONNECT", (Vector2){ connBtn.x + 50, connBtn.y + 10 }, 20, 1.0f, WHITE);
|
||||
if (isConnHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||
currentState = CONNECT_MENU;
|
||||
}
|
||||
|
||||
if (isServerHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||
serverMode = !serverMode;
|
||||
SaveConfig();
|
||||
|
|
@ -1746,6 +1897,19 @@ int main(void)
|
|||
snprintf(worldName, sizeof(worldName), "%s", currentWorldName.c_str());
|
||||
worldNameLen = strlen(worldName);
|
||||
|
||||
if (serverMode) {
|
||||
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (serverSocket != INVALID_SOCKET_VAL) {
|
||||
SetNonBlocking(serverSocket);
|
||||
struct sockaddr_in serv_addr;
|
||||
serv_addr.sin_family = AF_INET;
|
||||
serv_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
serv_addr.sin_port = htons(12345);
|
||||
bind(serverSocket, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
|
||||
listen(serverSocket, 5);
|
||||
}
|
||||
}
|
||||
|
||||
currentState = GAMEPLAY;
|
||||
DisableCursor(); // Hide and lock cursor for first-person control
|
||||
|
||||
|
|
@ -1820,6 +1984,14 @@ int main(void)
|
|||
DrawSphere(sunPos, 5.0f, YELLOW);
|
||||
DrawSphere(moonPos, 4.0f, LIGHTGRAY);
|
||||
|
||||
// --- DRAW REMOTE PLAYERS ---
|
||||
for (const auto& rp : remotePlayers) {
|
||||
DrawCapsule(rp.position, (Vector3){ rp.position.x, rp.position.y + 1.8f, rp.position.z }, 0.4f, 8, 8, BLUE);
|
||||
// Draw Nameplate
|
||||
Vector2 namePos = GetWorldToScreen((Vector3){ rp.position.x, rp.position.y + 2.2f, rp.position.z }, camera3D);
|
||||
DrawTextEx(customFont, rp.name.c_str(), (Vector2){ namePos.x - MeasureTextEx(customFont, rp.name.c_str(), 20, 1.0f).x/2, namePos.y }, 20, 1.0f, WHITE);
|
||||
}
|
||||
|
||||
int playerCX = (int)floorf(camera3D.position.x / CHUNK_SIZE);
|
||||
int playerCZ = (int)floorf(camera3D.position.z / CHUNK_SIZE);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
#ifndef NETWORK_H
|
||||
#define NETWORK_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
typedef SOCKET Socket;
|
||||
#define INVALID_SOCKET_VAL INVALID_SOCKET
|
||||
#define SOCKET_ERROR_VAL SOCKET_ERROR
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
typedef int Socket;
|
||||
#define INVALID_SOCKET_VAL -1
|
||||
#define SOCKET_ERROR_VAL -1
|
||||
#define closesocket close
|
||||
#endif
|
||||
|
||||
enum PacketType {
|
||||
PACKET_HANDSHAKE = 0,
|
||||
PACKET_PLAYER_UPDATE = 1,
|
||||
PACKET_CHUNK_DATA = 2,
|
||||
PACKET_DISCONNECT = 3
|
||||
};
|
||||
|
||||
struct PacketHeader {
|
||||
uint8_t type;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
struct PacketHandshake {
|
||||
char name[32];
|
||||
};
|
||||
|
||||
struct PacketPlayerUpdate {
|
||||
float x, y, z;
|
||||
float yaw;
|
||||
};
|
||||
|
||||
// Cross-platform socket initialization
|
||||
inline bool InitNetworking() {
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ShutdownNetworking() {
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool SetNonBlocking(Socket s) {
|
||||
#ifdef _WIN32
|
||||
unsigned long mode = 1;
|
||||
return ioctlsocket(s, FIONBIO, &mode) == 0;
|
||||
#else
|
||||
int flags = fcntl(s, F_GETFL, 0);
|
||||
if (flags == -1) return false;
|
||||
return fcntl(s, F_SETFL, flags | O_NONBLOCK) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue