#include "server.h" Server::Server(LOG_DEST log) { m_log = log; if (log == LOG_DEST::LOGFILE) { m_logfile = std::ofstream("server.log", std::ofstream::out); if (!m_logfile.is_open()) { m_log = LOG_DEST::CONSOLE; // Fallback console. Log("Ouverture fichier log: repli vers console.", true, false); } } } Server::~Server() { if (m_logfile.is_open()) m_logfile.close(); if (m_sock_udp) closesocket(m_sock_udp); if (m_sock_tcp) closesocket(m_sock_tcp); for (const auto& [key, player] : m_conns) closesocket(player->getSock()); m_conns.clear(); delete m_world; #ifdef _WIN32 WSACleanup(); #endif } int Server::Init() { Log("Initialisation du serveur...", false, false); #ifdef _WIN32 if (WSAStartup(MAKEWORD(2, 2), &m_wsaData) != 0) { /* Initialisation de l'environnement reseau (Windows only) */ Log("Initialisation WinSock.", true, true); return 1; } #endif m_sock_udp = socket(AF_INET, SOCK_DGRAM, 0); if (m_sock_udp == INVALID_SOCKET) { /* Creation du socket UDP */ Log("Creation Socket UDP.", true, true); return 2; } m_sock_tcp = socket(AF_INET, SOCK_STREAM, 0); if (m_sock_tcp == INVALID_SOCKET) { /* Creation du socket TCP */ Log("Creation Socket TCP.", true, true); return 3; } /* Creation structure donnes descripteur du socket serveur */ sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(SRV_PORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(m_sock_udp, (sockaddr*)&addr, sizeof(addr)) != 0) { /* Associer le socket UDP au port */ Log("Association Socket UDP.", true, true); return 4; } if (bind(m_sock_tcp, (sockaddr*)&addr, sizeof(addr)) != 0) { /* Associer le socket TCP au port */ Log("Association Socket TCP.", true, true); return 5; } return 0; } int Server::Ready() { int nbrjoueurs = 0, nbrconn = 0; bool readystart = false; do { Log("Entrez la duree de la partie: ", false, false); std::cin.getline(m_buf.ptr, BUFFER_LENGTH); try { m_game.countdown = std::stoi(m_buf.ptr); } catch(const std::exception& e) { Log(e.what(), true, false); m_game.countdown = 0; } } while (m_game.countdown < 1); do { Log("Entrez le seed de la partie: ", false, false); std::cin.getline(m_buf.ptr, BUFFER_LENGTH); try { m_game.seed = std::stoi(m_buf.ptr); } catch(const std::exception& e) { Log(e.what(), true, false); m_game.seed = 0; } } while (m_game.seed < 1); do { Log("Entrez le nombre de joueurs: ", false, false); std::cin.getline(m_buf.ptr, BUFFER_LENGTH); try { nbrjoueurs = std::stoi(m_buf.ptr); } catch(const std::exception& e) { Log(e.what(), true, false); nbrjoueurs = 0; } if (nbrjoueurs <= 0 || nbrjoueurs > MAX_CONNECTIONS) Log("Nombre de joueurs invalide.", true, false); } while (nbrjoueurs <= 0 || nbrjoueurs > MAX_CONNECTIONS); m_game.gameType = 1; if (listen(m_sock_tcp, MAX_CONNECTIONS) < 0) { Log("Ecoute sur le port TCP.", true, true); return 1; } buildIdList(ID_LIST_SIZE); Log("A l'ecoute sur le port: " + std::to_string(SRV_PORT), false, false); while (!readystart) { sockaddr_in sockad; addrlen_t addrlen = sizeof(sockad); SOCKET sock = accept(m_sock_tcp, (sockaddr*)&sockad, &addrlen); if (sock < 0) Log("Erreur de connexion", true, false); else if (sock > 0) { std::string str = "Nouvelle connexion provenant de: "; str.append(inet_ntop(AF_INET, &sockad.sin_addr, m_buf.ptr, m_buf.len)).append(": ").append(std::to_string(sockad.sin_port)); if (recv(sock, m_buf.ptr, m_buf.len, 0) > 0) { PlayerInfo play; m_buf.len = BUFFER_LENGTH; Packet pck = getPack(&m_buf); if (pck.type != PACKET_TYPE::LOGINF) { Log("Paquet invalide.", true, false); if (pck.type != PACKET_TYPE::ERR) netprot::emptyPack(pck); continue; // Passer au prochain appel si c'est pas un LoginInfo ou un LoginInfo invalide qui rentre. } LoginInfo* log = (LoginInfo*)pck.ptr; log->sid = getUniqueId(); log->tid = 1145389380; // TODO: À changer si on implemente un mode en equipe. Log(str.append(" Nom: ").append(log->name), false, false); str.clear(); Log(str.append(log->name).append(" SID: [").append(std::to_string(log->sid).append("]")), false, false); sendPack(sock, log, &m_buf); play.id = getUniqueId(); strcpy(play.name, log->name); play.tid = log->tid; sendPack(sock, &m_game, &m_buf); Connection* conn = new Connection(sock, sockad, *log, play); m_conns[log->sid] = conn; delete log; if (++nbrconn >= nbrjoueurs) readystart = true; } } } for (auto& [keyin, playin] : m_conns) // Not pretty, but it works. for (auto& [keyout, playout] : m_conns) { if (keyin == keyout) continue; sendPackTo(m_sock_udp, playout->getInfo(), &m_buf, playin->getAddr()); // et envoyer les infos des joueurs distants au nouveau joueur. } return 0; } void Server::Run() { bool endgame = false; Input in; sockaddr_in sockad; addrlen_t socklen = sizeof(sockad); Log("Debut de la partie...", false, false); int players = m_conns.size(); m_world = new World(); m_world->SetSeed(m_game.seed); m_world->GetChunks().Reset(nullptr); m_world->BuildWorld(); for (auto& [key, conn] : m_conns) { // Creation des instances de joueurs et premier sync. int x = (rand() % (CHUNK_SIZE_X * WORLD_SIZE_X - 1)) - (CHUNK_SIZE_X * WORLD_SIZE_X / 2), y = (rand() % (CHUNK_SIZE_Y * WORLD_SIZE_Y - 1)) - (CHUNK_SIZE_Y * WORLD_SIZE_Y / 2); conn->player = new Player(Vector3f(x + .5f, CHUNK_SIZE_Y + 1.8f, y + .5f)); m_players[key] = conn->player; Sync sync; sync.position = conn->player->GetPosition(); sync.hp = conn->player->GetHP(); sync.sid = key; sync.ammo = 0; sync.timestamp = 0; sync.timer = m_game.countdown; sendPack(conn->getSock(), &sync, &m_buf); } int timer = m_game.countdown, sync_acc = 0; std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); Timestamp last = 0; std::vector chatlog; std::vector chunkdiffs; std::vector bullets, outbox_bullets; std::vector netbull; while (!endgame) { using namespace std::chrono; Timestamp tstamp = duration_cast(high_resolution_clock::now() - start).count(); if (last == 0) last = tstamp; sync_acc += tstamp - last; if (sync_acc >= 1000) { sync_acc -= 1000; --timer; } for (auto& [key, conn] : m_conns) { /* In */ int deadplayers = 0; std::vector lsPck; Input in; Chat chat; Sync sync; lsPck = recvPacks(m_sock_udp, &m_buf); for (auto& pck : lsPck) { uint32_t bsize = m_buf.len - (pck - m_buf.ptr); switch (netprot::getType(pck, 1)) { using enum netprot::PACKET_TYPE; case INPUT: if (Deserialize(&in, pck, &bsize)) m_conns[in.sid]->AddInput(in); break; case SYNC: if (Deserialize(&sync, pck, &bsize)) {} break; case CHAT: if (Deserialize(&chat, pck, &bsize)) chatlog.push_back(chat); break; default: break; } } lsPck.clear(); /* Process */ if (conn->m_nsync) { if (conn->player->AmIDead()) { ++deadplayers; conn->m_nsync == false; } else { Timestamp tstamp = conn->Run(m_world); for (auto& chmo : conn->ChunkDiffs) chunkdiffs.emplace_back(chmo); conn->ChunkDiffs.clear(); for (auto& bull : conn->Bullets) { bullets.emplace_back(bull); BulletAdd* nbul = new BulletAdd(); nbul->pos = conn->player->GetPosition(); nbul->dir = conn->player->GetDirection(); nbul->id = key; nbul->tstamp = tstamp; netbull.emplace_back(nbul); } conn->Bullets.clear(); } /* Out */ conn->sendPacks(m_sock_udp, m_conns, timer); } if (deadplayers == players - 1 || timer <= 0) endgame = true; } for (auto& bull : netbull) { for (auto& [key, conn] : m_conns) sendPackTo(m_sock_udp, bull, &m_buf, conn->getAddr()); } for (auto& bull : bullets) { ChunkMod* cmod = nullptr; if (bull->Update(m_world, (1. / 60.), 20, m_players, &cmod)) { if (cmod) chunkdiffs.emplace_back(cmod); outbox_bullets.emplace_back(std::move(bull)); } } for (auto& chat : chatlog) for (auto& [key, conn] : m_conns) sendPackTo(m_sock_udp, &chat, &m_buf, conn->getAddr()); for (auto& chmo : chunkdiffs) { for (auto& [key, conn] : m_conns) sendPackTo(m_sock_udp, chmo, &m_buf, conn->getAddr()); delete chmo; } for (auto& bull : outbox_bullets) delete bull; for (auto& bull : netbull) delete bull; outbox_bullets.clear(); netbull.clear(); chunkdiffs.clear(); chatlog.clear(); } Chat end; end.src_id = 0; char endmess[] = "Game over, motherfuckers."; strcpy(end.mess, endmess); for (auto& [key, conn] : m_conns) sendPackTo(m_sock_udp, &end, &m_buf, conn->getAddr()); // TODO: Gérer les 2-3 secondes post-game avant le billboard pour pas avoir un whiplash à la fin de la game. } inline std::string Server::LogTimestamp() { time_t rawtime; tm timeinfo; char buffer[50]; time(&rawtime); #ifdef _WIN32 localtime_s(&timeinfo, &rawtime); #else localtime_r(&rawtime, &timeinfo); #endif strftime(buffer, sizeof(buffer), "%d-%m-%Y %H:%M:%S", &timeinfo); std::string str(buffer); return "[" + str + "] "; } void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) { switch (m_log) { using enum LOG_DEST; // C++20! case LOGFILE: m_logfile << LogTimestamp() << (is_fatal ? "FATAL " : "") << (is_error ? "ERROR " : "") << str << std::endl; break; case CONSOLE: [[fallthrough]]; // Pour dire que c'est voulu que ça traverse vers le case en dessous (C++17!) default: std::cout << LogTimestamp() << (is_fatal ? "FATAL " : "") << (is_error ? "ERROR " : "") << str << std::endl; break; } if (is_fatal) { if (m_logfile.is_open()) m_logfile.close(); if (m_sock_udp) closesocket(m_sock_udp); if (m_sock_tcp) closesocket(m_sock_tcp); for (const auto& [key, player] : m_conns) { closesocket(player->getSock()); } delete m_world; m_conns.clear(); #ifdef _WIN32 WSACleanup(); #endif exit(-1); } } void Server::buildIdList(size_t size) { std::set lst; srand(time(NULL)); do lst.insert(((uint64_t)rand() << 32 | rand())); while (lst.size() < size); m_ids = std::vector(lst.begin(), lst.end()); } uint64_t Server::getUniqueId() { uint64_t id = m_ids.back(); m_ids.pop_back(); return id; }