1949 字
10 分钟
florr.oi联机服务端
//florr.oi 联机服务端 - v2.0#include <iostream>#include <string>#include <thread>#include <vector>#include <map>#include <mutex>#include <winsock2.h>#include <ws2tcpip.h>#include <windows.h>#include <chrono>
using namespace std;
// --- Dynamic Load ws2_32.dll ---typedef int(WSAAPI *P_WS)(WORD, LPWSADATA);typedef int(WSAAPI *P_WC)(void);typedef SOCKET(WSAAPI *P_SK)(int, int, int);typedef int(WSAAPI *P_CS)(SOCKET);typedef int(WSAAPI *P_BD)(SOCKET, const struct sockaddr*, int);typedef int(WSAAPI *P_LS)(SOCKET, int);typedef SOCKET(WSAAPI *P_AC)(SOCKET, struct sockaddr*, int*);typedef int(WSAAPI *P_RC)(SOCKET, char*, int, int);typedef int(WSAAPI *P_SD)(SOCKET, const char*, int, int);typedef int(WSAAPI *P_ST)(SOCKET, const char*, int, int, const struct sockaddr*, int);typedef u_short(WSAAPI *P_HS)(u_short);typedef int(WSAAPI *P_SO)(SOCKET, int, int, const char*, int);typedef char*(WSAAPI *P_IN)(struct in_addr);typedef int(WSAAPI *P_GHN)(char*, int);typedef hostent*(WSAAPI *P_GHB)(const char*);
struct API_HUB { HMODULE hS; P_WS ws; P_WC wc; P_SK sk; P_CS cs; P_BD bd; P_LS ls; P_AC ac; P_RC rcv; P_SD sd; P_ST st; P_HS hs; P_SO so; P_IN in; P_GHN ghn; P_GHB ghb;
void init() { hS = LoadLibraryA("ws2_32.dll"); if (hS) { ws = (P_WS)GetProcAddress(hS, "WSAStartup"); wc = (P_WC)GetProcAddress(hS, "WSACleanup"); sk = (P_SK)GetProcAddress(hS, "socket"); cs = (P_CS)GetProcAddress(hS, "closesocket"); bd = (P_BD)GetProcAddress(hS, "bind"); ls = (P_LS)GetProcAddress(hS, "listen"); ac = (P_AC)GetProcAddress(hS, "accept"); rcv = (P_RC)GetProcAddress(hS, "recv"); sd = (P_SD)GetProcAddress(hS, "send"); st = (P_ST)GetProcAddress(hS, "sendto"); hs = (P_HS)GetProcAddress(hS, "htons"); so = (P_SO)GetProcAddress(hS, "setsockopt"); in = (P_IN)GetProcAddress(hS, "inet_ntoa"); ghn = (P_GHN)GetProcAddress(hS, "gethostname"); ghb = (P_GHB)GetProcAddress(hS, "gethostbyname"); } }} API;
// --- Data Structures ---struct Petal { string type; // common, unusual, rare, epic, legendary, mythic string kind; // melee, explosive, missile, speed, guardian double x = 0, y = 0; bool isActive = true;};
struct Player { string id, name; double x = 0, y = 0, hp = 100, maxHp = 100; int lvl = 1; bool isAlive = true, isBleeding = false; long long lastHeartbeat = 0; vector<Petal> petals;};
struct Room { string code, hostId, hostName; int cheatRule = 0; bool devAllowed = true; bool isPaused = false; map<string, Player> players;};
map<string, Room> rooms;mutex roomMutex;map<string, long long> lastDamageTime; // key = attackerId|targetId, cooldown tracker
// --- Utility: Simple JSON Field Extraction ---string getJsonString(const string& json, const string& key) { size_t pos = json.find("\"" + key + "\":"); if (pos == string::npos) return ""; pos += key.length() + 3; size_t start = json.find("\"", pos); if (start == string::npos) return ""; size_t end = json.find("\"", start + 1); return json.substr(start + 1, end - start - 1);}
double getJsonDouble(const string& json, const string& key) { size_t pos = json.find("\"" + key + "\":"); if (pos == string::npos) return 0.0; pos += key.length() + 3; size_t end = json.find_first_of(",}", pos); if (end == string::npos) return 0.0; string valStr = json.substr(pos, end - pos); try { return stod(valStr); } catch (...) { return 0.0; }}
bool getJsonBool(const string& json, const string& key) { size_t pos = json.find("\"" + key + "\":"); if (pos == string::npos) return false; pos += key.length() + 3; return (json.substr(pos, 4) == "true");}
vector<Petal> getJsonPetals(const string& json) { vector<Petal> petals; // Find the start of the petals array size_t petalsPos = json.find("\"petals\":"); if (petalsPos == string::npos) return petals;
// Find the [ position size_t arrStart = json.find('[', petalsPos); if (arrStart == string::npos) return petals;
// Find the matching ], handling nesting int depth = 1; size_t i = arrStart + 1; while (i < json.length() && depth > 0) { if (json[i] == '[') depth++; else if (json[i] == ']') depth--; i++; }
if (depth > 0) return petals;
size_t arrEnd = i - 1; string petalsStr = json.substr(arrStart + 1, arrEnd - arrStart - 1); if (petalsStr.empty()) return petals;
// Parse each petal object size_t pos = 0; while (pos < petalsStr.length()) { // Skip whitespace while (pos < petalsStr.length() && (petalsStr[pos] == ' ' || petalsStr[pos] == '\t' || petalsStr[pos] == '\n' || petalsStr[pos] == '\r' || petalsStr[pos] == ',')) { pos++; }
if (pos >= petalsStr.length()) break; if (petalsStr[pos] != '{') break;
// Find the matching } int objDepth = 1; size_t objEnd = pos + 1; while (objEnd < petalsStr.length() && objDepth > 0) { if (petalsStr[objEnd] == '{') objDepth++; else if (petalsStr[objEnd] == '}') objDepth--; objEnd++; }
if (objDepth > 0) break;
string petalObj = petalsStr.substr(pos, objEnd - pos); Petal p; p.type = getJsonString(petalObj, "type"); p.kind = getJsonString(petalObj, "kind"); p.x = getJsonDouble(petalObj, "x"); p.y = getJsonDouble(petalObj, "y"); p.isActive = getJsonBool(petalObj, "isActive"); petals.push_back(p);
pos = objEnd; }
return petals;}
long long getTimestamp() { // Get current timestamp in milliseconds return std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now().time_since_epoch() ).count();}
// --- Compact double formatting (Bug3 fix: to_string produces redundant precision e.g. 100.000000) ---string fmtDouble(double v) { char buf[32]; snprintf(buf, sizeof(buf), "%.2f", v); return buf;}
// --- HTTP Response Builder ---void sendHttpResponse(SOCKET clientSocket, const string& jsonBody) { string response = "HTTP/1.1 200 OK\r\n" "Content-Type: application/json; charset=utf-8\r\n" "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Headers: Content-Type\r\n" "Connection: close\r\n" "Content-Length: " + to_string(jsonBody.length()) + "\r\n\r\n" + jsonBody; API.sd(clientSocket, response.c_str(), (int)response.length(), 0);}
// --- Client HTTP Request Handler ---void handleClient(SOCKET clientSocket) { char buffer[4096] = {0}; API.rcv(clientSocket, buffer, (int)sizeof(buffer) - 1, 0); string request(buffer);
if (request.find("OPTIONS") == 0) { sendHttpResponse(clientSocket, "{}"); API.cs(clientSocket); return; }
size_t bodyPos = request.find("\r\n\r\n"); string body = (bodyPos != string::npos) ? request.substr(bodyPos + 4) : "";
if (request.find("POST /api/room/create") != string::npos) { string clientId = getJsonString(body, "client_id"); string name = getJsonString(body, "name");
srand((unsigned int)GetTickCount() ^ (unsigned int)GetCurrentThreadId());
lock_guard<mutex> lock(roomMutex);
string code; do { code = to_string(100000 + rand() % 900000); } while (rooms.count(code) > 0); Room r; r.code = code; r.hostId = clientId; r.hostName = name; Player p; p.id = clientId; p.name = name; p.lastHeartbeat = getTimestamp(); r.players[clientId] = p; rooms[code] = r;
sendHttpResponse(clientSocket, "{\"success\":true, \"code\":\"" + code + "\"}"); } else if (request.find("POST /api/room/join") != string::npos) { string clientId = getJsonString(body, "client_id"); string code = getJsonString(body, "code"); string name = getJsonString(body, "name");
lock_guard<mutex> lock(roomMutex); if (rooms.count(code) > 0 && rooms[code].players.size() < 32) { Player p; p.id = clientId; p.name = name; p.lastHeartbeat = getTimestamp(); rooms[code].players[clientId] = p; sendHttpResponse(clientSocket, "{\"success\":true}"); } else { sendHttpResponse(clientSocket, "{\"success\":false, \"message\":\"Room not found or full\"}"); } } else if (request.find("GET /api/room/list") != string::npos) { lock_guard<mutex> lock(roomMutex); string res = "{\"rooms\":["; bool first = true; for (auto& pair : rooms) { if (!first) res += ","; res += "{\"code\":\"" + pair.first + "\",\"name\":\"" + pair.second.hostName + "\",\"count\":" + to_string(pair.second.players.size()) + "}"; first = false; } res += "]}"; sendHttpResponse(clientSocket, res); } else if (request.find("POST /api/state") != string::npos) { string clientId = getJsonString(body, "client_id"); string code = getJsonString(body, "room_code");
lock_guard<mutex> lock(roomMutex); if (rooms.count(code)) { auto& p = rooms[code].players[clientId]; p.x = getJsonDouble(body, "x"); p.y = getJsonDouble(body, "y"); double reportedHp = getJsonDouble(body, "hp"); if (reportedHp <= p.hp || p.hp > p.maxHp) { p.hp = reportedHp; } p.isBleeding = getJsonBool(body, "isBleeding"); p.petals = getJsonPetals(body); p.lastHeartbeat = getTimestamp();
string res = "{\"success\":true, \"isPaused\":" + string(rooms[code].isPaused ? "true" : "false") + ", \"players\":{"; bool first = true; { res += "\"" + clientId + "\":{"; res += "\"x\":" + fmtDouble(p.x) + ",\"y\":" + fmtDouble(p.y); res += ",\"hp\":" + fmtDouble(p.hp) + ",\"isBleeding\":" + (p.isBleeding ? "true" : "false"); res += ",\"isAlive\":true,\"name\":\"" + p.name + "\""; res += ",\"petals\":["; bool firstPetal2 = true; for (auto& petal : p.petals) { if (!firstPetal2) res += ","; res += "{\"type\":\"" + petal.type + "\",\"kind\":\"" + petal.kind + "\""; res += ",\"isActive\":" + string(petal.isActive ? "true" : "false") + "}"; firstPetal2 = false; } res += "]}"; first = false; } for (auto& other : rooms[code].players) { if (other.first == clientId) continue; if (getTimestamp() - other.second.lastHeartbeat > 5000) continue;
if (!first) res += ","; res += "\"" + other.first + "\":{"; res += "\"x\":" + fmtDouble(other.second.x) + ",\"y\":" + fmtDouble(other.second.y); res += ",\"hp\":" + fmtDouble(other.second.hp) + ",\"isBleeding\":" + (other.second.isBleeding ? "true" : "false"); res += ",\"isAlive\":true,\"name\":\"" + other.second.name + "\""; res += ",\"petals\":["; bool firstPetal2 = true; for (auto& petal : other.second.petals) { if (!firstPetal2) res += ","; res += "{\"type\":\"" + petal.type + "\",\"kind\":\"" + petal.kind + "\""; res += ",\"isActive\":" + string(petal.isActive ? "true" : "false") + "}"; firstPetal2 = false; } res += "]"; res += "}"; first = false; } res += "}}"; sendHttpResponse(clientSocket, res); } else { sendHttpResponse(clientSocket, "{\"success\":false}"); } } else if (request.find("POST /api/damage") != string::npos) { string clientId = getJsonString(body, "client_id"); string roomCode = getJsonString(body, "room_code"); string targetId = getJsonString(body, "target_id"); double damage = getJsonDouble(body, "damage"); bool isBleedAttack = getJsonBool(body, "is_bleed_attack"); int duration = (int)getJsonDouble(body, "duration");
if (clientId.empty() || roomCode.empty() || targetId.empty()) { sendHttpResponse(clientSocket, "{\"success\":false, \"message\":\"Bad request: missing required fields (client_id/room_code/target_id)\"}"); cout << "[ERROR] /api/damage: missing required fields" << endl; API.cs(clientSocket); return; } if (damage < 0 || damage > 1000) { sendHttpResponse(clientSocket, "{\"success\":false, \"message\":\"Invalid damage value, range must be 0-1000\"}"); cout << "[ERROR] /api/damage: invalid damage value (" << damage << ")" << endl; API.cs(clientSocket); return; }
lock_guard<mutex> lock(roomMutex); if (rooms.count(roomCode) == 0) { sendHttpResponse(clientSocket, "{\"success\":false, \"message\":\"Target room not found\"}"); cout << "[ERROR] /api/damage: room " << roomCode << " not found" << endl; API.cs(clientSocket); return; }
Room& room = rooms[roomCode]; if (room.players.count(targetId) == 0) { sendHttpResponse(clientSocket, "{\"success\":false, \"message\":\"Target player not found or offline\"}"); cout << "[ERROR] /api/damage: target player " << targetId << " not found in room " << roomCode << endl; API.cs(clientSocket); return; }
if (room.players.count(clientId) == 0) { sendHttpResponse(clientSocket, "{\"success\":false, \"message\":\"Attacker not in target room\"}"); cout << "[ERROR] /api/damage: attacker " << clientId << " not in room " << roomCode << endl; API.cs(clientSocket); return; }
Player& target = room.players[targetId]; { string cooldownKey = clientId + "|" + targetId; long long nowMs = getTimestamp(); auto it = lastDamageTime.find(cooldownKey); if (it != lastDamageTime.end() && (nowMs - it->second) < 500) { sendHttpResponse(clientSocket, "{\"success\":false, \"message\":\"Damage cooldown: 500ms limit per attacker-target pair\"}"); cout << "[DAMAGE] cooldown: " << clientId << " -> " << targetId << " skipped (" << (nowMs - it->second) << "ms since last hit)" << endl; API.cs(clientSocket); return; } lastDamageTime[cooldownKey] = nowMs; } target.hp -= damage; if (target.hp < 0) target.hp = 0;
if (isBleedAttack) { target.isBleeding = true; if (duration <= 0) duration = 5; //cout << "[DAMAGE] room:" << roomCode // << " | attacker:" << clientId // << " -> target:" << targetId // << " | damage:" << damage // << " | bleed:yes (duration " << duration << "s)" // << " | target_HP:" << target.hp << endl; } else { //cout << "[DAMAGE] room:" << roomCode // << " | attacker:" << clientId // << " -> target:" << targetId // << " | damage:" << damage // << " | bleed:no" // << " | target_HP:" << target.hp << endl; }
sendHttpResponse(clientSocket, "{\"success\":true, \"message\":\"Damage applied\", \"target_hp\":" + fmtDouble(target.hp) + ", \"target_bleeding\":" + (target.isBleeding ? "true" : "false") + "}"); } API.cs(clientSocket);}
// --- TCP Listener Thread ---void tcpServer() { SOCKET listenSocket = API.sk(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in serverAddr = {0}; serverAddr.sin_family = AF_INET; serverAddr.sin_port = API.hs(28082); serverAddr.sin_addr.s_addr = INADDR_ANY;
API.bd(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)); API.ls(listenSocket, SOMAXCONN);
cout << "[INFO] IP : "; char hostName[256]; API.ghn(hostName, sizeof(hostName)); struct hostent* host = API.ghb(hostName); for (int i = 0; host->h_addr_list[i] != NULL; i++) { cout << API.in(*(struct in_addr*)host->h_addr_list[i]) << " "; } cout << endl;
while (true) { SOCKET clientSocket = API.ac(listenSocket, NULL, NULL); if (clientSocket != INVALID_SOCKET) { thread(handleClient, clientSocket).detach(); } }}
// --- UDP Beacon Discovery Thread ---void udpBeacon() { SOCKET udpSocket = API.sk(AF_INET, SOCK_DGRAM, IPPROTO_UDP); BOOL broadcast = TRUE; API.so(udpSocket, SOL_SOCKET, SO_BROADCAST, (char*)&broadcast, sizeof(broadcast));
sockaddr_in bcastAddr = {0}; bcastAddr.sin_family = AF_INET; bcastAddr.sin_port = API.hs(8889); bcastAddr.sin_addr.s_addr = INADDR_BROADCAST;
while (true) { Sleep(1500); lock_guard<mutex> lock(roomMutex); for (auto& pair : rooms) { string msg = "FLOORIO_LAN_V1|TYPE:ROOM_BEACON|CODE:" + pair.first + "|PLAYERS:" + to_string(pair.second.players.size()); API.st(udpSocket, msg.c_str(), (int)msg.length(), 0, (SOCKADDR*)&bcastAddr, sizeof(bcastAddr)); } }}
int main() { // 1. Initialize dynamic function pointers API.init();
// 2. Use low-level API normally WSADATA wsaData; API.ws(MAKEWORD(2, 2), &wsaData); srand((unsigned int)GetTickCount());
thread tcpThread(tcpServer); thread udpThread(udpBeacon);
tcpThread.join(); udpThread.join();
API.wc(); return 0;}