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 <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include "rlgl.h"
|
#include "rlgl.h"
|
||||||
|
#include "network.h"
|
||||||
|
|
||||||
#define CHUNK_SIZE 32
|
#define CHUNK_SIZE 32
|
||||||
#ifndef PI
|
#ifndef PI
|
||||||
|
|
@ -85,7 +86,7 @@ static std::string currentWorldName = "";
|
||||||
static std::string playerName = "Player";
|
static std::string playerName = "Player";
|
||||||
static bool serverMode = false;
|
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
|
// Forward Declarations
|
||||||
bool IsExposedOptimized(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, Chunk* nxP, Chunk* nzM, Chunk* nzP);
|
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();
|
void LoadConfig();
|
||||||
std::string GetRemoteVersion();
|
std::string GetRemoteVersion();
|
||||||
|
|
||||||
|
struct RemotePlayer {
|
||||||
|
Socket sock;
|
||||||
|
std::string name;
|
||||||
|
Vector3 position;
|
||||||
|
float yaw;
|
||||||
|
};
|
||||||
|
static std::vector<RemotePlayer> remotePlayers;
|
||||||
|
|
||||||
// ---- Inventory System ----
|
// ---- Inventory System ----
|
||||||
struct InventorySlot {
|
struct InventorySlot {
|
||||||
int blockType = AIR;
|
int blockType = AIR;
|
||||||
|
|
@ -752,7 +761,16 @@ int main(void)
|
||||||
vfile.close();
|
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 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;
|
||||||
|
|
@ -895,6 +913,34 @@ int main(void)
|
||||||
if (IsMusicStreamPlaying(titleMusic)) StopMusicStream(titleMusic);
|
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
|
// Handle title music loop fading
|
||||||
float fadeTime = 2.0f; // 2 seconds fade
|
float fadeTime = 2.0f; // 2 seconds fade
|
||||||
float timePlayed = GetMusicTimePlayed(titleMusic);
|
float timePlayed = GetMusicTimePlayed(titleMusic);
|
||||||
|
|
@ -1280,6 +1326,87 @@ int main(void)
|
||||||
currentState = MAIN_MENU;
|
currentState = MAIN_MENU;
|
||||||
updateReady = false;
|
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) {
|
} else if (currentState != GAMEPLAY) {
|
||||||
BeginMode2D(camera);
|
BeginMode2D(camera);
|
||||||
// Draw the texture, scaling it to fit the current window size exactly
|
// Draw the texture, scaling it to fit the current window size exactly
|
||||||
|
|
@ -1453,6 +1580,19 @@ int main(void)
|
||||||
}
|
}
|
||||||
worldChunks.clear();
|
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;
|
currentState = GAMEPLAY;
|
||||||
DisableCursor();
|
DisableCursor();
|
||||||
inventoryOpen = false;
|
inventoryOpen = false;
|
||||||
|
|
@ -1609,6 +1749,17 @@ int main(void)
|
||||||
DrawRectangleRec(serverCheck, serverMode ? GREEN : DARKGRAY);
|
DrawRectangleRec(serverCheck, serverMode ? GREEN : DARKGRAY);
|
||||||
DrawRectangleLinesEx(serverCheck, 2.0f, isServerHovered ? WHITE : GRAY);
|
DrawRectangleLinesEx(serverCheck, 2.0f, isServerHovered ? WHITE : GRAY);
|
||||||
DrawTextEx(customFont, "Server Mode (Experimental)", (Vector2){ serverCheck.x + 35, serverCheck.y }, 18, 1.0f, WHITE);
|
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)) {
|
if (isServerHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||||
serverMode = !serverMode;
|
serverMode = !serverMode;
|
||||||
SaveConfig();
|
SaveConfig();
|
||||||
|
|
@ -1746,6 +1897,19 @@ int main(void)
|
||||||
snprintf(worldName, sizeof(worldName), "%s", currentWorldName.c_str());
|
snprintf(worldName, sizeof(worldName), "%s", currentWorldName.c_str());
|
||||||
worldNameLen = strlen(worldName);
|
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;
|
currentState = GAMEPLAY;
|
||||||
DisableCursor(); // Hide and lock cursor for first-person control
|
DisableCursor(); // Hide and lock cursor for first-person control
|
||||||
|
|
||||||
|
|
@ -1820,6 +1984,14 @@ int main(void)
|
||||||
DrawSphere(sunPos, 5.0f, YELLOW);
|
DrawSphere(sunPos, 5.0f, YELLOW);
|
||||||
DrawSphere(moonPos, 4.0f, LIGHTGRAY);
|
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 playerCX = (int)floorf(camera3D.position.x / CHUNK_SIZE);
|
||||||
int playerCZ = (int)floorf(camera3D.position.z / 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