From 183c5f186a58435740cf0248b5ac32062d92f62d Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Tue, 3 Oct 2023 12:43:54 -0400 Subject: [PATCH 01/65] =?UTF-8?q?Architecture=20g=C3=A9n=C3=A9rale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-common/player.cpp | 6 ++++ SQCSim-common/player.h | 8 ++++- SQCSim2021/SQCSim2021.vcxproj | 5 ++-- SQCSim2021/SQCSim2021.vcxproj.filters | 7 +++-- SQCSim2021/connector.h | 2 ++ SQCSim2021/engine.cpp | 4 +-- SQCSim2021/engine.h | 4 +-- SQCSim2021/remoteplayer.h | 25 ++++++++++++++++ .../{worldrenderer.cpp => renderer.cpp} | 16 ++++++---- SQCSim2021/renderer.h | 30 +++++++++++++++++++ SQCSim2021/worldrenderer.h | 23 -------------- 11 files changed, 93 insertions(+), 37 deletions(-) create mode 100644 SQCSim2021/remoteplayer.h rename SQCSim2021/{worldrenderer.cpp => renderer.cpp} (92%) create mode 100644 SQCSim2021/renderer.h delete mode 100644 SQCSim2021/worldrenderer.h diff --git a/SQCSim-common/player.cpp b/SQCSim-common/player.cpp index 9fe810c..10ec32c 100644 --- a/SQCSim-common/player.cpp +++ b/SQCSim-common/player.cpp @@ -8,6 +8,8 @@ Player::Player(const Vector3f& position, float rotX, float rotY) : m_position(po m_username = "Zelda Bee-Bop56"; } +Player::~Player() {} + void Player::TurnLeftRight(float value) { m_rotY += value; if (m_rotY > 360) m_rotY = 0; @@ -206,3 +208,7 @@ void Player::Teleport(int& x, int& z) { m_position.x -= x * CHUNK_SIZE_X; m_position.z -= z * CHUNK_SIZE_Z; } + +Vector3f Player::InterpolatePosition(const Vector3f& vec1, const Vector3f& vec2, const Timestamp& tim1, const Timestamp& tim2, const Timestamp& now) { + return Vector3f(); +} diff --git a/SQCSim-common/player.h b/SQCSim-common/player.h index e439fa2..d592613 100644 --- a/SQCSim-common/player.h +++ b/SQCSim-common/player.h @@ -12,6 +12,8 @@ public: enum Sound { NOSOUND, STEP, FALL }; Player(const Vector3f& position, float rotX = 0, float rotY = 0); + ~Player(); + void TurnLeftRight(float value); void TurnTopBottom(float value); Vector3f GetInput(bool front, bool back, bool left, bool right, bool jump, bool dash, float elapsedTime); @@ -26,12 +28,13 @@ public: float GetHP() const; void Teleport(int& x, int& z); -private: +protected: Vector3f m_position; Vector3f m_velocity; Vector3f m_direction; std::string m_username; + uint64_t id = 0; float m_rotX = 0; float m_rotY = 0; @@ -40,6 +43,9 @@ private: float m_hp; bool m_airborne; + + Vector3f InterpolatePosition(const Vector3f& vec1, const Vector3f& vec2, const Timestamp& tim1, const Timestamp& tim2, const Timestamp& now); + }; #endif //_PLAYER_H__ diff --git a/SQCSim2021/SQCSim2021.vcxproj b/SQCSim2021/SQCSim2021.vcxproj index d120d7b..eadb743 100644 --- a/SQCSim2021/SQCSim2021.vcxproj +++ b/SQCSim2021/SQCSim2021.vcxproj @@ -25,13 +25,14 @@ + - + @@ -46,7 +47,7 @@ - + diff --git a/SQCSim2021/SQCSim2021.vcxproj.filters b/SQCSim2021/SQCSim2021.vcxproj.filters index 3bf0928..b5d2499 100644 --- a/SQCSim2021/SQCSim2021.vcxproj.filters +++ b/SQCSim2021/SQCSim2021.vcxproj.filters @@ -47,7 +47,10 @@ Fichiers d%27en-tête - + + Fichiers d%27en-tête + + Fichiers d%27en-tête @@ -88,7 +91,7 @@ Fichiers sources - + Fichiers sources diff --git a/SQCSim2021/connector.h b/SQCSim2021/connector.h index 297be6f..3ded42b 100644 --- a/SQCSim2021/connector.h +++ b/SQCSim2021/connector.h @@ -17,6 +17,8 @@ public: //void SendInput(); //int Sync(); + + // void updateRemotePlayers(std::map rplayers); private: #ifdef _WIN32 WSADATA m_wsaData; diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index f44eb96..2e2d99f 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -531,9 +531,9 @@ void Engine::Render(float elapsedTime) { m_bullets[x] = nullptr; } - m_wrenderer.RenderWorld(&m_world, m_renderCount, m_player.GetPosition(), m_player.GetDirection(), all, m_shader01, m_textureAtlas); + m_renderer.RenderWorld(&m_world, m_renderCount, m_player.GetPosition(), m_player.GetDirection(), all, m_shader01, m_textureAtlas); m_world.Update(m_bullets, m_player.GetPosition(), m_blockinfo); - m_wrenderer.UpdateWorld(&m_world, m_player.GetPosition(), m_blockinfo); + m_renderer.UpdateMesh(&m_world, m_player.GetPosition(), m_blockinfo); if (m_isSkybox) m_skybox.Render(skybox); diff --git a/SQCSim2021/engine.h b/SQCSim2021/engine.h index fa3a088..5d85415 100644 --- a/SQCSim2021/engine.h +++ b/SQCSim2021/engine.h @@ -18,7 +18,7 @@ #include "audio.h" #include "textureatlas.h" #include "connector.h" -#include "worldrenderer.h" +#include "renderer.h" class Engine : public OpenglContext { public: @@ -60,7 +60,7 @@ private: TextureAtlas m_textureAtlas = TextureAtlas(BTYPE_LAST); World m_world = World(); - WorldRenderer m_wrenderer = WorldRenderer(); + Renderer m_renderer = Renderer(); Texture m_textureSkybox; Texture m_textureFont; diff --git a/SQCSim2021/remoteplayer.h b/SQCSim2021/remoteplayer.h new file mode 100644 index 0000000..bc84d76 --- /dev/null +++ b/SQCSim2021/remoteplayer.h @@ -0,0 +1,25 @@ +#ifndef REMOTEPLAYER_H__ +#define REMOTEPLAYER_H__ +#include "../SQCSim-common/player.h" +#include "../SQCSim-common/netprotocol.h" +#include "define.h" +#include "textureatlas.h" +#include "shader.h" + +class RemotePlayer : public Player { +public: + enum Anim { STILL, RUNNING, JUMPING, SHOOTING, JUMPSHOOT, DEAD }; + + RemotePlayer(); + + void Init(); + void Feed(const netprot::Output out); + +private: + netprot::Output current, next; + float m_aminacc; + Anim m_animstate; + uint64_t m_team_id; + +}; +#endif \ No newline at end of file diff --git a/SQCSim2021/worldrenderer.cpp b/SQCSim2021/renderer.cpp similarity index 92% rename from SQCSim2021/worldrenderer.cpp rename to SQCSim2021/renderer.cpp index dcf6d45..e506c94 100644 --- a/SQCSim2021/worldrenderer.cpp +++ b/SQCSim2021/renderer.cpp @@ -1,13 +1,13 @@ -#include "worldrenderer.h" +#include "renderer.h" -WorldRenderer::WorldRenderer() { +Renderer::Renderer() { m_meshes.Reset(nullptr); } -WorldRenderer::~WorldRenderer() { +Renderer::~Renderer() { } -void WorldRenderer::RenderWorld(World* origin, int& rendercount, const Vector3f& player_pos, const Vector3f& player_dir, Transformation& world, Shader& shader, TextureAtlas& atlas) { +void Renderer::RenderWorld(World* origin, int& rendercount, const Vector3f& player_pos, const Vector3f& player_dir, Transformation& world, Shader& shader, TextureAtlas& atlas) const { rendercount = 0; Vector3f angle; Vector3f cursor; @@ -107,7 +107,7 @@ void WorldRenderer::RenderWorld(World* origin, int& rendercount, const Vector3f& glStencilFunc(GL_GREATER, 1, 0xFF); }; -void WorldRenderer::UpdateWorld(World* origin, const Vector3f& player, BlockInfo* blockinfo[BTYPE_LAST]) { +void Renderer::UpdateMesh(World* origin, const Vector3f& player, BlockInfo* blockinfo[BTYPE_LAST]) { int cx = player.x; int cy = player.z; static int frameUpdate = 2; @@ -211,3 +211,9 @@ void WorldRenderer::UpdateWorld(World* origin, const Vector3f& player, BlockInfo } } } + +void Renderer::RenderPlayer(Player* player, Transformation tran) const { +} + +void Renderer::RenderPlayer(RemotePlayer* rplayer, const Vector3f& player_pos, const Vector3f& player_dir) const { +} diff --git a/SQCSim2021/renderer.h b/SQCSim2021/renderer.h new file mode 100644 index 0000000..8afdc3b --- /dev/null +++ b/SQCSim2021/renderer.h @@ -0,0 +1,30 @@ +#ifndef RENDERER_H__ +#define RENDERER_H__ +#include +#include +#include "../SQCSim-common/world.h" +#include "../SQCSim-common/transformation.h" +#include "define.h" +#include "mesh.h" +#include "textureatlas.h" +#include "shader.h" +#include "remoteplayer.h" + +class Renderer { +private: + Array2d m_meshes = Array2d(WORLD_SIZE_X, WORLD_SIZE_Y); + + TextureAtlas* m_playertext = nullptr; + Shader* m_playershader = nullptr; + +public: + Renderer(); + ~Renderer(); + + void UpdateMesh(World* origin, const Vector3f& player, BlockInfo* blockinfo[BTYPE_LAST]); + + void RenderWorld(World* origin, int& rendercount, const Vector3f& player_pos, const Vector3f& player_dir, Transformation& world, Shader& shader, TextureAtlas& atlas) const; + void RenderPlayer(Player* player, Transformation tran) const; + void RenderPlayer(RemotePlayer* rplayer, const Vector3f& player_pos, const Vector3f& player_dir) const; +}; +#endif \ No newline at end of file diff --git a/SQCSim2021/worldrenderer.h b/SQCSim2021/worldrenderer.h deleted file mode 100644 index 46e6624..0000000 --- a/SQCSim2021/worldrenderer.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef WORLDRENDERER_H__ -#define WORLDRENDERER_H__ -#include -#include -#include "../SQCSim-common/world.h" -#include "../SQCSim-common/transformation.h" -#include "define.h" -#include "mesh.h" -#include "textureatlas.h" -#include "shader.h" - -class WorldRenderer { -private: - Array2d m_meshes = Array2d(WORLD_SIZE_X, WORLD_SIZE_Y); - -public: - WorldRenderer(); - ~WorldRenderer(); - - void RenderWorld(World* origin, int& rendercount, const Vector3f& player_pos, const Vector3f& player_dir, Transformation& world, Shader& shader, TextureAtlas& atlas); - void UpdateWorld(World* origin, const Vector3f& player, BlockInfo* blockinfo[BTYPE_LAST]); -}; -#endif \ No newline at end of file From d7e55c8446847867c2735be03969d4f53cea037a Mon Sep 17 00:00:00 2001 From: Claudel-D-Roy <112507354+Claudel-D-Roy@users.noreply.github.com> Date: Sun, 15 Oct 2023 16:23:43 -0400 Subject: [PATCH 02/65] =?UTF-8?q?D=C3=A9but=20de=20la=20classe=20remotepla?= =?UTF-8?q?yer.cpp,=20=C3=A0=20v=C3=A9rifier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + SQCSim-common/netprotocol.h | 6 ++- SQCSim2021/SQCSim2021.vcxproj | 1 + SQCSim2021/SQCSim2021.vcxproj.filters | 3 ++ SQCSim2021/remoteplayer.cpp | 65 +++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 SQCSim2021/remoteplayer.cpp diff --git a/.gitignore b/.gitignore index f0c1c5a..1e812db 100644 --- a/.gitignore +++ b/.gitignore @@ -373,3 +373,5 @@ FodyWeavers.xsd /x64/Release/SQCSim2023.exe /x64/Debug/SQCSim2023.exe /x64/Debug/SQCSim2021.pdb +SQCSim2021/SQCSim2021.vcxproj.filters +SQCSim2021/SQCSim2021.vcxproj diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index d7c7a93..f7692b4 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -33,7 +33,11 @@ namespace netprot { bool jumping, shooting, hit, - powerup; + powerup, + dead, + still, + jumpshot, + running; }; struct Input { // cli -> srv UDP ~frame diff --git a/SQCSim2021/SQCSim2021.vcxproj b/SQCSim2021/SQCSim2021.vcxproj index eadb743..04701c8 100644 --- a/SQCSim2021/SQCSim2021.vcxproj +++ b/SQCSim2021/SQCSim2021.vcxproj @@ -41,6 +41,7 @@ + diff --git a/SQCSim2021/SQCSim2021.vcxproj.filters b/SQCSim2021/SQCSim2021.vcxproj.filters index b5d2499..5c8a864 100644 --- a/SQCSim2021/SQCSim2021.vcxproj.filters +++ b/SQCSim2021/SQCSim2021.vcxproj.filters @@ -94,5 +94,8 @@ Fichiers sources + + Fichiers sources + \ No newline at end of file diff --git a/SQCSim2021/remoteplayer.cpp b/SQCSim2021/remoteplayer.cpp new file mode 100644 index 0000000..6dfb3a4 --- /dev/null +++ b/SQCSim2021/remoteplayer.cpp @@ -0,0 +1,65 @@ +#include "remoteplayer.h" +#include +#include + + +RemotePlayer::RemotePlayer() : Player(Vector3f(0, 0, 0), 0, 0), m_aminacc(0.0f), m_animstate(Anim::STILL), m_team_id(0), current(), next() { + +} + +void RemotePlayer::Init() { + +} + +void RemotePlayer::Feed(const netprot::Output out) { + + next.position = out.position; + next.direction = out.direction; + next.states = out.states; + next.id = out.id; + + //a revoir pour le jump et le shoot en meme temps lorsque les test seront possible + + if (current.position != next.position) + { + Vector3f positionDelta = next.position - current.position; + m_position = next.position + positionDelta; + m_direction = next.direction; + m_team_id = next.id; + + } + + if(next.direction != current.direction) + { + m_direction = next.direction; + current.direction = next.direction; + } + + if (next.states.shooting) { + m_animstate = Anim::SHOOTING; + } + else if (next.states.jumping) { + m_animstate = Anim::JUMPING; + } + else if (next.states.dead) { + m_animstate = Anim::DEAD; + } + else if(next.states.jumpshot){ + m_animstate = Anim::JUMPSHOOT; + } + else if (next.states.still) { + m_animstate = Anim::STILL; + } + else if (next.states.running) { + m_animstate = Anim::RUNNING; + } + + + current.direction = next.direction; + current.position = next.position; + current.states = next.states; + current.id = next.id; + + + +} \ No newline at end of file From 2cb7d4c776ba446fb6db9b9fefda4385cb60e000 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Wed, 18 Oct 2023 09:33:56 -0400 Subject: [PATCH 03/65] =?UTF-8?q?D=C3=A9but?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-srv/define.h | 1 + SQCSim-srv/server.cpp | 38 ++++++++++++++++++++++++++++++---- SQCSim-srv/server.h | 1 + SQCSim2021/engine.cpp | 47 ++++++++++++++++++++++++++----------------- 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/SQCSim-srv/define.h b/SQCSim-srv/define.h index 626a26a..bba3377 100644 --- a/SQCSim-srv/define.h +++ b/SQCSim-srv/define.h @@ -10,6 +10,7 @@ #define MAX_CONNECTIONS 16 #define ID_LIST_SIZE 127 #define BUFFER_LENGTH 150 +#define SRV_MANUAL_SETUP true typedef unsigned char LogDest; enum LOG_DEST { CONSOLE, LOGFILE, LOG_LAST }; diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index cbda844..b115adf 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -65,6 +65,31 @@ int Server::Init() { } int Server::Ready() { + int nbrjoueurs = 0, + nbrconn = 0; +#ifdef SRV_MANUAL_SETUP + do { + Log("Entrez la durée de la partie: ", false, false); + std::cin >> m_game.countdown; + std::cout << std::endl; + } while (m_game.countdown < 1); + do { + Log("Entrez le seed de la partie: ", false, false); + std::cin >> m_game.seed; + std::cout << std::endl; + } while (m_game.seed < 1); + do { + Log("Entrez le nombre de joueurs: ", false, false); + std::cin >> nbrjoueurs; + std::cout << std::endl; + } while (nbrjoueurs > 0 && nbrjoueurs >= MAX_CONNECTIONS); +#else // setup preconfiguré + m_game.countdown = 360; + m_game.seed = 9370707; + nbrjoueurs = 1; +#endif + m_game.gameType = 1; + if (listen(m_sock_tcp, MAX_CONNECTIONS) < 0) { Log("Écoute sur le port TCP.", true, true); return 1; @@ -79,13 +104,17 @@ int Server::Ready() { buildIdList(ID_LIST_SIZE); - m_game.countdown = 360; - m_game.gameType = 1; - m_game.seed = 9370707; + while (!readystart) { sockaddr_in sockad; + +#ifdef _WIN32 + int addrlen = sizeof(sockad); +#else // maudit que c'est con, ça. unsigned int addrlen = sizeof(sockad); +#endif + SOCKET sock = accept(m_sock_tcp, (sockaddr*)&sockad, &addrlen); if (sock < 0) @@ -127,7 +156,8 @@ int Server::Ready() { Connection* conn = new Connection(sock, sockad, log, play); m_players[log.sid] = conn; - readystart = true; + if (++nbrconn >= nbrjoueurs) + readystart = true; } } } diff --git a/SQCSim-srv/server.h b/SQCSim-srv/server.h index 6c77038..ddaf97b 100644 --- a/SQCSim-srv/server.h +++ b/SQCSim-srv/server.h @@ -34,6 +34,7 @@ private: netprot::GameInfo m_game; World* m_world = nullptr; + const bool m_manual_setup = SRV_MANUAL_SETUP; std::string LogTimestamp(); void Log(std::string str, bool is_error, bool is_fatal); diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index a5939e6..b5fc361 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -229,6 +229,10 @@ void Engine::Init() { abort(); } + uint64_t seed = SEED; + std::string playname = ""; + char ch; + glDisable(GL_FRAMEBUFFER_SRGB); glEnable(GL_DEPTH_TEST); glEnable(GL_STENCIL_TEST); @@ -258,31 +262,37 @@ void Engine::Init() { // Array pour les balles. for (int x = 0; x < MAX_BULLETS; ++x) { m_bullets[x] = nullptr; - m_whoosh[x] = nullptr; // = m_audio.m_engine.m_audio.m_engine->addSoundSourceFromFile(AUDIO_PATH "noise.ogg", irrklang::ESM_AUTO_DETECT, false); + m_whoosh[x] = nullptr; } - uint64_t seed = SEED; - std::string playname = "La Chienne � Jacques"; - if (NETWORK_TEST) { // Test connexion r�seau. +#ifdef NETWORK_TEST + std::cout << "Jouer en ligne? [o/N] "; + std::cin >> ch; + std::cout << std::endl; + + if (ch == 'o' || ch == 'O') { + std::cout << "Veuillez entrer un nom de joueur: "; + std::getline(std::cin, playname); + std::cout << std::endl; + if (!m_conn.Init()) { if (!m_conn.Connect(SRV_ADDR, playname)) { - // setup jeu en r�seau. - std::cout << "ID re�u du serveur: " << std::to_string(m_conn.getId()) << "!" << std::endl; - std::cout << "Seed re�u du serveur: " << std::to_string(m_conn.getSeed()) << "!" << std::endl; + // setup jeu en reseau. + std::cout << "ID recu du serveur: " << std::to_string(m_conn.getId()) << "!" << std::endl; + std::cout << "Seed recu du serveur: " << std::to_string(m_conn.getSeed()) << "!" << std::endl; seed = m_conn.getSeed(); } else std::cout << "Erreur de connexion." << std::endl; } - else std::cout << "Erreur de cr�ation de socket." << std::endl; + else std::cout << "Erreur de creation de socket." << std::endl; } +#endif m_world.SetSeed(seed); // Init Chunks m_world.GetChunks().Reset(nullptr); - - // Gestion de souris. CenterMouse(); HideCursor(); @@ -461,7 +471,7 @@ void Engine::DisplayHud(int timer) { glVertex2f(fPosX, fPosY); // Haut-Gauche glEnd(); - //TODO: Associer avec m�chanique de vie du joueur + //TODO: Associer avec mechanique de vie du joueur // Barre HP glColor3f(0.0f * facteurOmbrage, 1.0f * facteurOmbrage, 0.0f * facteurOmbrage); @@ -472,7 +482,7 @@ void Engine::DisplayHud(int timer) { glVertex2f(fPosX, fPosY); // Haut-Gauche glEnd(); - // Barre �quip + // Barre equip glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor3f(1.0f * facteurOmbrage, 1.0f * facteurOmbrage, 1.0f * facteurOmbrage); @@ -633,6 +643,7 @@ float Engine::GetScale() const { } int Engine::GetFps(float elapsedTime) const { return 1 / elapsedTime; } + int Engine::GetCountdown(float elapsedTime) { if (m_resetcountdown) { @@ -684,16 +695,16 @@ void Engine::Render(float elapsedTime) { if (leftright) vstep = Vector3f(m_player.GetPosition().x + m_player.GetDirection().z, m_player.GetPosition().y - 1.7f, m_player.GetPosition().z + m_player.GetDirection().x); else vstep = Vector3f(m_player.GetPosition().x - m_player.GetDirection().z, m_player.GetPosition().y - 1.7f, m_player.GetPosition().z - m_player.GetDirection().x); - m_audio.Create3DAudioObj(step, AUDIO_PATH "step.wav", vstep, m_player.GetVelocity(), .8f); + m_audio.Create3DAudioObj(step, AUDIO_PATH "step.wav", vstep, m_player.GetVelocity(), .8f, false); leftright = !leftright; break; case Player::Sound::FALL: - m_audio.Create3DAudioObj(step, AUDIO_PATH "hit.wav", m_player.GetPosition(), m_player.GetVelocity(), 1.f); + m_audio.Create3DAudioObj(step, AUDIO_PATH "hit.wav", m_player.GetPosition(), m_player.GetVelocity(), 1.f, false); break; default: break; } - m_audio.Update3DAudio(m_player.GetPOV(), m_player.GetDirection(), m_player.GetVelocity()); // Ajustement du positionnement 3D avec les coordonn�es du joueur et - // son vecteur de v�locit� (pour l'effet Doppler) + m_audio.Update3DAudio(m_player.GetPOV(), m_player.GetDirection(), m_player.GetVelocity()); // Ajustement du positionnement 3D avec les coordonnees du joueur et + // son vecteur de velocite (pour l'effet Doppler) pollTime = 0; } @@ -755,7 +766,7 @@ void Engine::Render(float elapsedTime) { static bool fell = false; if (m_player.GetPosition().y < 1.7f && !fell) { - m_audio.Create3DAudioObj(m_scream, AUDIO_PATH "scream.wav", m_player.GetPOV(), m_player.GetVelocity(), 1.f); + m_audio.Create3DAudioObj(m_scream, AUDIO_PATH "scream.wav", m_player.GetPOV(), m_player.GetVelocity(), 1.f, false); fell = true; } else if (m_player.GetPosition().y < -20.f) { @@ -813,7 +824,7 @@ void Engine::KeyPressEvent(unsigned char key) { break; case 10: // K - Debugging DisplayNotification() m_keyK = true; - m_messageNotification = "notifications systeme peuvent �tre affich�"; + m_messageNotification = "notifications systeme peuvent etre affichees"; break; case 11: // L - Debugging DisplayNotification() m_keyL = true; From 0e7b8973e75ce9150d8905c1de509f81bb3751fe Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Wed, 18 Oct 2023 09:59:34 -0400 Subject: [PATCH 04/65] setup louche en console --- SQCSim2021/connector.cpp | 7 +++++-- SQCSim2021/define.h | 1 - SQCSim2021/engine.cpp | 34 +++++++++++++++++----------------- SQCSim2021/engine.h | 10 +++++----- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index 31e1b81..13c4cdc 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -69,7 +69,7 @@ int Connector::Connect(char* srv_addr, std::string name) { buf = new char[150] {0}; buflen = 150; int rpack = 0; - + int errors = 0; while (rpack < 2) { recv(m_sock_tcp, buf, buflen, 0); @@ -89,9 +89,12 @@ int Connector::Connect(char* srv_addr, std::string name) { ++rpack; break; default: - std::cout << "Packet invalide." << std::endl; + ++errors; + //std::cout << "Packet invalide." << std::endl; break; } + if (errors > 100) + return 1; } return 0; } diff --git a/SQCSim2021/define.h b/SQCSim2021/define.h index 67c49c3..7f4c4af 100644 --- a/SQCSim2021/define.h +++ b/SQCSim2021/define.h @@ -20,7 +20,6 @@ #include #endif -#define NETWORK_TEST false #define SRV_ADDR "127.0.0.1" #define COUNTDOWN 300 diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index b5fc361..b36496f 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -265,28 +265,28 @@ void Engine::Init() { m_whoosh[x] = nullptr; } -#ifdef NETWORK_TEST - std::cout << "Jouer en ligne? [o/N] "; - std::cin >> ch; - std::cout << std::endl; - - if (ch == 'o' || ch == 'O') { - std::cout << "Veuillez entrer un nom de joueur: "; - std::getline(std::cin, playname); + if (true) { + std::cout << "Jouer en ligne? [o/N] "; + std::cin >> ch; std::cout << std::endl; - if (!m_conn.Init()) { - if (!m_conn.Connect(SRV_ADDR, playname)) { - // setup jeu en reseau. - std::cout << "ID recu du serveur: " << std::to_string(m_conn.getId()) << "!" << std::endl; - std::cout << "Seed recu du serveur: " << std::to_string(m_conn.getSeed()) << "!" << std::endl; - seed = m_conn.getSeed(); + if (ch == 'o' || ch == 'O') { + std::cout << "Veuillez entrer un nom de joueur: "; + std::cin >> playname; + std::cout << std::endl; + + if (!m_conn.Init()) { + if (!m_conn.Connect(SRV_ADDR, playname)) { + // setup jeu en reseau. + std::cout << "ID recu du serveur: " << std::to_string(m_conn.getId()) << "!" << std::endl; + std::cout << "Seed recu du serveur: " << std::to_string(m_conn.getSeed()) << "!" << std::endl; + seed = m_conn.getSeed(); + } + else std::cout << "Erreur de connexion." << std::endl; } - else std::cout << "Erreur de connexion." << std::endl; + else std::cout << "Erreur de creation de socket." << std::endl; } - else std::cout << "Erreur de creation de socket." << std::endl; } -#endif m_world.SetSeed(seed); diff --git a/SQCSim2021/engine.h b/SQCSim2021/engine.h index 57b4e2e..a8338c1 100644 --- a/SQCSim2021/engine.h +++ b/SQCSim2021/engine.h @@ -82,11 +82,11 @@ private: //Menu enum class GameState { MAIN_MENU, OPTIONS, QUIT, NEWG, PLAY }; GameState m_gamestate = GameState::MAIN_MENU; - Texture MenuTitleTexture; - Texture MenuBGTexture; - Texture MenuStartTexture; - Texture MenuQuitTexture; - Texture MenuOptionsTexture; + Texture MenuTitleTexture, + MenuBGTexture, + MenuStartTexture, + MenuQuitTexture, + MenuOptionsTexture; float m_scale; float m_time = 0; From 67a49a4cadbff5e46ff055f44b2284a763f40afd Mon Sep 17 00:00:00 2001 From: Claudel-D-Roy <112507354+Claudel-D-Roy@users.noreply.github.com> Date: Mon, 23 Oct 2023 15:35:31 -0400 Subject: [PATCH 05/65] push de remote player --- SQCSim2021/remoteplayer.cpp | 54 ++++++++++++++++++++----------------- SQCSim2021/remoteplayer.h | 4 +-- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/SQCSim2021/remoteplayer.cpp b/SQCSim2021/remoteplayer.cpp index 6dfb3a4..1bf39c9 100644 --- a/SQCSim2021/remoteplayer.cpp +++ b/SQCSim2021/remoteplayer.cpp @@ -3,7 +3,7 @@ #include -RemotePlayer::RemotePlayer() : Player(Vector3f(0, 0, 0), 0, 0), m_aminacc(0.0f), m_animstate(Anim::STILL), m_team_id(0), current(), next() { +RemotePlayer::RemotePlayer() : Player(Vector3f(0, 0, 0), 0, 0), m_aminacc(0.0f), m_animstate(Anim::STILL), m_team_id(0), current(), previous() { } @@ -13,52 +13,58 @@ void RemotePlayer::Init() { void RemotePlayer::Feed(const netprot::Output out) { - next.position = out.position; - next.direction = out.direction; - next.states = out.states; - next.id = out.id; + current.position = out.position; + current.direction = out.direction; + current.states = out.states; + current.id = out.id; //a revoir pour le jump et le shoot en meme temps lorsque les test seront possible - if (current.position != next.position) + if (current.position != previous.position) { - Vector3f positionDelta = next.position - current.position; - m_position = next.position + positionDelta; - m_direction = next.direction; - m_team_id = next.id; + Vector3f positionDelta = current.position - previous.position; + m_position = current.position + positionDelta; + m_direction = current.direction; + m_team_id = current.id; } - if(next.direction != current.direction) + if(current.direction != previous.direction) { - m_direction = next.direction; - current.direction = next.direction; + m_direction = current.direction; + current.direction = current.direction; } - if (next.states.shooting) { + if (current.states.shooting) { + true; m_animstate = Anim::SHOOTING; } - else if (next.states.jumping) { + else if (current.states.jumping) { + true; m_animstate = Anim::JUMPING; } - else if (next.states.dead) { + else if (current.states.dead) { + true; m_animstate = Anim::DEAD; } - else if(next.states.jumpshot){ - m_animstate = Anim::JUMPSHOOT; + else if(current.states.powerup){ + true; + m_animstate = Anim::POWERUP; } - else if (next.states.still) { + else if (current.states.still) { + true; m_animstate = Anim::STILL; } - else if (next.states.running) { + else if (current.states.running) { + true; m_animstate = Anim::RUNNING; } - current.direction = next.direction; - current.position = next.position; - current.states = next.states; - current.id = next.id; + previous.direction = current.direction; + previous.position = current.position; + previous.states = current.states; + previous.id = current.id; diff --git a/SQCSim2021/remoteplayer.h b/SQCSim2021/remoteplayer.h index bc84d76..70b585b 100644 --- a/SQCSim2021/remoteplayer.h +++ b/SQCSim2021/remoteplayer.h @@ -8,7 +8,7 @@ class RemotePlayer : public Player { public: - enum Anim { STILL, RUNNING, JUMPING, SHOOTING, JUMPSHOOT, DEAD }; + enum Anim { STILL = 1, RUNNING = 2, JUMPING = 4, SHOOTING = 8, POWERUP = 16, DEAD = 32 }; RemotePlayer(); @@ -16,7 +16,7 @@ public: void Feed(const netprot::Output out); private: - netprot::Output current, next; + netprot::Output current, previous; float m_aminacc; Anim m_animstate; uint64_t m_team_id; From 590220bc924c7892a7c94ee1e41ea31d643511f9 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 23 Oct 2023 15:41:07 -0400 Subject: [PATCH 06/65] ajout d'adresse de serveur --- SQCSim2021/connector.cpp | 2 +- SQCSim2021/connector.h | 2 +- SQCSim2021/engine.cpp | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index 13c4cdc..0583b30 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -42,7 +42,7 @@ int Connector::Init() { return 0; } -int Connector::Connect(char* srv_addr, std::string name) { +int Connector::Connect(const char* srv_addr, std::string name) { sockaddr_in add; m_srvsockaddr.sin_family = AF_INET; m_srvsockaddr.sin_port = htons(SRV_PORT); diff --git a/SQCSim2021/connector.h b/SQCSim2021/connector.h index 297be6f..81e2262 100644 --- a/SQCSim2021/connector.h +++ b/SQCSim2021/connector.h @@ -11,7 +11,7 @@ public: ~Connector(); int Init(); - int Connect(char* srv_addr, std::string name); + int Connect(const char* srv_addr, std::string name); uint64_t getId() const; unsigned int getSeed() const; diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index b36496f..12e6e2e 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -230,7 +230,7 @@ void Engine::Init() { } uint64_t seed = SEED; - std::string playname = ""; + std::string playname = "", srvname = ""; char ch; glDisable(GL_FRAMEBUFFER_SRGB); @@ -275,8 +275,12 @@ void Engine::Init() { std::cin >> playname; std::cout << std::endl; + std::cout << "Veuillez entrer une adresse de serveur: "; + std::cin >> srvname; + std::cout << std::endl; + if (!m_conn.Init()) { - if (!m_conn.Connect(SRV_ADDR, playname)) { + if (!m_conn.Connect(srvname.c_str(), playname)) { // setup jeu en reseau. std::cout << "ID recu du serveur: " << std::to_string(m_conn.getId()) << "!" << std::endl; std::cout << "Seed recu du serveur: " << std::to_string(m_conn.getSeed()) << "!" << std::endl; From b943a268ae6f835b3425350f3c0d117d0e623b47 Mon Sep 17 00:00:00 2001 From: Jonathan Trottier Date: Mon, 23 Oct 2023 15:43:55 -0400 Subject: [PATCH 07/65] barre de vie --- SQCSim-common/player.cpp | 18 +++++++++++++++++- SQCSim-common/player.h | 3 +++ SQCSim2021/engine.cpp | 12 +++++++++++- SQCSim2021/engine.h | 3 +++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/SQCSim-common/player.cpp b/SQCSim-common/player.cpp index 9fe810c..c963849 100644 --- a/SQCSim-common/player.cpp +++ b/SQCSim-common/player.cpp @@ -4,7 +4,7 @@ Player::Player(const Vector3f& position, float rotX, float rotY) : m_position(position), m_rotX(rotX), m_rotY(rotY) { m_velocity = Vector3f(0, 0, 0); m_airborne = true; - m_hp = 0.75f; //TODO: Remettre à 1.0f + m_hp = 1.0f; //TODO: Remettre à 1.0f m_username = "Zelda Bee-Bop56"; } @@ -206,3 +206,19 @@ void Player::Teleport(int& x, int& z) { m_position.x -= x * CHUNK_SIZE_X; m_position.z -= z * CHUNK_SIZE_Z; } + +void Player::InflictDamage(Player playerHit, float hitPoints) +{ + + playerHit.m_hp -= hitPoints; + + + if (playerHit.GetHP() <= 0) + { // Quand l'autre joueur est mort. + + + + } + + +} diff --git a/SQCSim-common/player.h b/SQCSim-common/player.h index e439fa2..79e0008 100644 --- a/SQCSim-common/player.h +++ b/SQCSim-common/player.h @@ -26,6 +26,9 @@ public: float GetHP() const; void Teleport(int& x, int& z); + + void InflictDamage(Player playerHit, float hitPoints); + private: Vector3f m_position; Vector3f m_velocity; diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index f44eb96..f427a29 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -131,6 +131,12 @@ void Engine::LoadResource() { void Engine::UnloadResource() {} +void Engine::InstantDamage() +{ + m_player.InflictDamage(m_player, 0.10f); + m_damage = false; +} + void Engine::SystemNotification(std::string systemLog) { std::string message = ""; @@ -536,7 +542,8 @@ void Engine::Render(float elapsedTime) { m_wrenderer.UpdateWorld(&m_world, m_player.GetPosition(), m_blockinfo); if (m_isSkybox) m_skybox.Render(skybox); - + if (m_damage) + InstantDamage(); ProcessNotificationQueue(); DrawHud(elapsedTime, bloc); @@ -604,6 +611,9 @@ void Engine::KeyPressEvent(unsigned char key) { break; case 8: // I - Ignorer break; + case 9: // J - InstantDamage + m_damage = true; + break; case 17: // R - Ignorer break; case 19: // T - Ignorer diff --git a/SQCSim2021/engine.h b/SQCSim2021/engine.h index fa3a088..e6584eb 100644 --- a/SQCSim2021/engine.h +++ b/SQCSim2021/engine.h @@ -43,6 +43,7 @@ private: bool LoadTexture(Texture& texture, const std::string& filename, bool useMipmaps = true, bool stopOnError = true); + void InstantDamage(); void SystemNotification(std::string systemLog); void KillNotification(Player killer, Player killed); void DisplayNotification(std::string message); @@ -83,6 +84,8 @@ private: int m_renderCount = 0; int m_countdown = COUNTDOWN; + bool m_damage = false; + bool m_wireframe = false; bool m_isSkybox = true; bool m_block = false; From 49240635b68fc6df15ff20af9d5d10d699150af9 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 23 Oct 2023 16:20:52 -0400 Subject: [PATCH 08/65] sendpack qui fonctionne avec le client --- SQCSim-common/netprotocol.h | 12 +++++++++++- SQCSim-srv/connection.h | 2 +- SQCSim-srv/server.h | 14 +++++++------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index d7c7a93..f21e7aa 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -116,5 +116,15 @@ namespace netprot { bool Deserialize(ErrorLog* errlog, char* buf, const uint32_t buflen); // srv PacketType getType(char* buf, uint32_t buflen); // srv/cli -} + + template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); + + template + void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen) { + netprot::Serialize(pack, buf, buflen); + send(sock, *buf, *buflen, 0); + *buflen = BUFFER_LENGTH; + } +}; + #endif diff --git a/SQCSim-srv/connection.h b/SQCSim-srv/connection.h index 6dee988..7649732 100644 --- a/SQCSim-srv/connection.h +++ b/SQCSim-srv/connection.h @@ -33,7 +33,7 @@ private: std::map m_output_manifest; std::map m_chatlog; - SOCKET m_sock; + SOCKET m_sock_tcp, m_sock_udp; sockaddr_in m_addr; netprot::LoginInfo m_loginfo; netprot::PlayerInfo m_playinfo; diff --git a/SQCSim-srv/server.h b/SQCSim-srv/server.h index ddaf97b..2e1c1ba 100644 --- a/SQCSim-srv/server.h +++ b/SQCSim-srv/server.h @@ -41,15 +41,15 @@ private: void buildIdList(size_t size); uint64_t getUniqueId(); - template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); + //template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); }; -template -void Server::sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen) { - netprot::Serialize(pack, buf, buflen); - send(sock, *buf, *buflen, 0); - *buflen = BUFFER_LENGTH; -} +//template +//void Server::sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen) { +// netprot::Serialize(pack, buf, buflen); +// send(sock, *buf, *buflen, 0); +// *buflen = BUFFER_LENGTH; +//} #endif From 498fb7baf787d395221e72ad909d4a08407e7e8a Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 23 Oct 2023 16:21:15 -0400 Subject: [PATCH 09/65] lol --- SQCSim-srv/connection.h | 2 +- SQCSim-srv/server.cpp | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/SQCSim-srv/connection.h b/SQCSim-srv/connection.h index 7649732..6dee988 100644 --- a/SQCSim-srv/connection.h +++ b/SQCSim-srv/connection.h @@ -33,7 +33,7 @@ private: std::map m_output_manifest; std::map m_chatlog; - SOCKET m_sock_tcp, m_sock_udp; + SOCKET m_sock; sockaddr_in m_addr; netprot::LoginInfo m_loginfo; netprot::PlayerInfo m_playinfo; diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index b115adf..51df967 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -144,7 +144,7 @@ int Server::Ready() { //netprot::Serialize(&log, &buf, &buflen); //send(sock, buf, buflen, 0); //buflen = 150; - sendPack(sock, &log, &buf, &buflen); + netprot::sendPack(sock, &log, &buf, &buflen); play.id = getUniqueId(); memcpy(play.name, log.name, std::strlen(log.name) + 1); @@ -152,7 +152,7 @@ int Server::Ready() { //netprot::Serialize(&m_game, &buf, &buflen); //send(sock, buf, buflen, 0); - sendPack(sock, &m_game, &buf, &buflen); + netprot::sendPack(sock, &m_game, &buf, &buflen); Connection* conn = new Connection(sock, sockad, log, play); m_players[log.sid] = conn; @@ -166,8 +166,15 @@ int Server::Ready() { } void Server::Run() { - + char* buf = new char[BUFFER_LENGTH]; Log("Partie en cours...", false, false); + + while (true) { + if (recv(m_sock_udp, buf, BUFFER_LENGTH, 0) > 0) { + + + } + } } inline std::string Server::LogTimestamp() { From 9f615db929e6f549bfa865ade67f54d943ace4b5 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 23 Oct 2023 16:23:27 -0400 Subject: [PATCH 10/65] erratum --- SQCSim-common/define.h | 2 ++ SQCSim-srv/server.cpp | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/SQCSim-common/define.h b/SQCSim-common/define.h index f65bc2b..cfb4fae 100644 --- a/SQCSim-common/define.h +++ b/SQCSim-common/define.h @@ -7,6 +7,8 @@ #define SRV_PORT 1025 #define CLI_PORT 1026 +#define BUFFER_LENGTH 150 + #define CHUNK_SIZE_X 4 #define CHUNK_SIZE_Y 64 #define CHUNK_SIZE_Z 4 diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 51df967..8f4f9a6 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -141,17 +141,12 @@ int Server::Ready() { str.append(log.name).append(" SID: [").append(std::to_string(log.sid).append("]")); Log(str, false, false); - //netprot::Serialize(&log, &buf, &buflen); - //send(sock, buf, buflen, 0); - //buflen = 150; netprot::sendPack(sock, &log, &buf, &buflen); play.id = getUniqueId(); memcpy(play.name, log.name, std::strlen(log.name) + 1); play.tid = log.tid; - //netprot::Serialize(&m_game, &buf, &buflen); - //send(sock, buf, buflen, 0); netprot::sendPack(sock, &m_game, &buf, &buflen); Connection* conn = new Connection(sock, sockad, log, play); From eea6a30aa536503ab3b635b496d424715f653f51 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 23 Oct 2023 16:23:57 -0400 Subject: [PATCH 11/65] aaaaaaa --- SQCSim-srv/define.h | 1 - 1 file changed, 1 deletion(-) diff --git a/SQCSim-srv/define.h b/SQCSim-srv/define.h index bba3377..b1f4621 100644 --- a/SQCSim-srv/define.h +++ b/SQCSim-srv/define.h @@ -9,7 +9,6 @@ #define MAX_CONNECTIONS 16 #define ID_LIST_SIZE 127 -#define BUFFER_LENGTH 150 #define SRV_MANUAL_SETUP true typedef unsigned char LogDest; From ecfe5b893b81cd19ce91c8dcb538c2cdde436030 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 23 Oct 2023 16:44:34 -0400 Subject: [PATCH 12/65] connection? --- SQCSim2021/connector.h | 6 ++++-- SQCSim2021/engine.cpp | 19 +++++++++++++++++++ SQCSim2021/engine.h | 2 ++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/SQCSim2021/connector.h b/SQCSim2021/connector.h index 81e2262..3d66e08 100644 --- a/SQCSim2021/connector.h +++ b/SQCSim2021/connector.h @@ -17,6 +17,9 @@ public: //void SendInput(); //int Sync(); + + SOCKET m_sock_udp = 0, + m_sock_tcp = 0; private: #ifdef _WIN32 WSADATA m_wsaData; @@ -29,8 +32,7 @@ private: netprot::GameInfo m_gameinfo; sockaddr_in m_srvsockaddr; - SOCKET m_sock_udp = 0, - m_sock_tcp = 0; + }; #endif diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index 12e6e2e..382dfc7 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -285,6 +285,7 @@ void Engine::Init() { std::cout << "ID recu du serveur: " << std::to_string(m_conn.getId()) << "!" << std::endl; std::cout << "Seed recu du serveur: " << std::to_string(m_conn.getSeed()) << "!" << std::endl; seed = m_conn.getSeed(); + m_networkgame = true; } else std::cout << "Erreur de connexion." << std::endl; } @@ -778,6 +779,24 @@ void Engine::Render(float elapsedTime) { fell = false; } + if (m_networkgame) { + static char* buf = new char[BUFFER_LENGTH]; + uint32_t buflen = BUFFER_LENGTH; + netprot::Input input; + + input.direction = m_player.GetDirection(); + input.sid = m_conn.getId(); + input.timestamp = 12345; + input.keys.forward = m_keyW; + input.keys.backward = m_keyS; + input.keys.left = m_keyA; + input.keys.right = m_keyD; + input.keys.jump = m_keySpace; + input.keys.block = m_mouseR; + input.keys.shoot = m_mouseL; + + netprot::sendPack(m_conn.m_sock_udp, &input, &buf, &buflen); + } } else if (m_gamestate == GameState::MAIN_MENU || m_gamestate == GameState::OPTIONS) { diff --git a/SQCSim2021/engine.h b/SQCSim2021/engine.h index a8338c1..f28c86b 100644 --- a/SQCSim2021/engine.h +++ b/SQCSim2021/engine.h @@ -120,6 +120,8 @@ private: float m_mousemx = 0; float m_mousemy = 0; + bool m_networkgame = false; + std::string m_messageNotification = ""; }; From fc4aa20005abe05b7055d9c59a827053283c88a4 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 23 Oct 2023 16:54:09 -0400 Subject: [PATCH 13/65] Update server.cpp --- SQCSim-srv/server.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 8f4f9a6..aad9971 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -162,13 +162,17 @@ int Server::Ready() { void Server::Run() { char* buf = new char[BUFFER_LENGTH]; + uint32_t buflen = BUFFER_LENGTH; + netprot::Input in; Log("Partie en cours...", false, false); while (true) { if (recv(m_sock_udp, buf, BUFFER_LENGTH, 0) > 0) { - - + Deserialize(&in, buf, buflen); + std::cout << "Id: " << in.sid << "\r\n" + << "Direction: { " << in.direction.x << ", " << in.direction.y << ", " << in.direction.z << " }" << "\r\n"; } + std::cout << "!" << std::endl; } } From 2c3abff96fddf913b545651451dcb36c90560f74 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 23 Oct 2023 17:49:40 -0400 Subject: [PATCH 14/65] =?UTF-8?q?et=20c'est=20un=20d=C3=A9but!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-common/netprotocol.h | 1 + SQCSim-srv/server.cpp | 5 ++++- SQCSim2021/connector.h | 2 +- SQCSim2021/engine.cpp | 6 +++++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index f21e7aa..7c41418 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -125,6 +125,7 @@ namespace netprot { send(sock, *buf, *buflen, 0); *buflen = BUFFER_LENGTH; } + }; #endif diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index aad9971..8324d23 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -164,10 +164,13 @@ void Server::Run() { char* buf = new char[BUFFER_LENGTH]; uint32_t buflen = BUFFER_LENGTH; netprot::Input in; + sockaddr_in sockad; + int socklen = sizeof(sockad); + Log("Partie en cours...", false, false); while (true) { - if (recv(m_sock_udp, buf, BUFFER_LENGTH, 0) > 0) { + if (recvfrom(m_sock_udp, buf, BUFFER_LENGTH, 0, (sockaddr*)&sockad, &socklen) > 0) { Deserialize(&in, buf, buflen); std::cout << "Id: " << in.sid << "\r\n" << "Direction: { " << in.direction.x << ", " << in.direction.y << ", " << in.direction.z << " }" << "\r\n"; diff --git a/SQCSim2021/connector.h b/SQCSim2021/connector.h index 3d66e08..395a0b4 100644 --- a/SQCSim2021/connector.h +++ b/SQCSim2021/connector.h @@ -20,6 +20,7 @@ public: SOCKET m_sock_udp = 0, m_sock_tcp = 0; + sockaddr_in m_srvsockaddr; private: #ifdef _WIN32 WSADATA m_wsaData; @@ -31,7 +32,6 @@ private: netprot::LoginInfo m_loginfo; netprot::GameInfo m_gameinfo; - sockaddr_in m_srvsockaddr; }; diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index 382dfc7..d544415 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -783,6 +783,7 @@ void Engine::Render(float elapsedTime) { static char* buf = new char[BUFFER_LENGTH]; uint32_t buflen = BUFFER_LENGTH; netprot::Input input; + sockaddr_in addr = m_conn.m_srvsockaddr; input.direction = m_player.GetDirection(); input.sid = m_conn.getId(); @@ -795,7 +796,10 @@ void Engine::Render(float elapsedTime) { input.keys.block = m_mouseR; input.keys.shoot = m_mouseL; - netprot::sendPack(m_conn.m_sock_udp, &input, &buf, &buflen); + netprot::Serialize(&input, &buf, &buflen); + sendto(m_conn.m_sock_udp, buf, buflen, 0, (sockaddr*)&addr, sizeof(addr)); + + //netprot::sendPackTo(m_conn.m_sock_udp, &input, &buf, &buflen, &m_conn.m_srvsockaddr); } } else if (m_gamestate == GameState::MAIN_MENU || m_gamestate == GameState::OPTIONS) From ffeb95d953a6e67eadfac948db2f9e464217f9eb Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Tue, 24 Oct 2023 07:32:16 -0400 Subject: [PATCH 15/65] direction fonctionnelle! --- SQCSim-common/define.h | 2 ++ SQCSim-common/netprotocol.cpp | 56 ++++++++++++++++++++++++++++++++++- SQCSim-srv/server.cpp | 10 ++----- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/SQCSim-common/define.h b/SQCSim-common/define.h index cfb4fae..8060703 100644 --- a/SQCSim-common/define.h +++ b/SQCSim-common/define.h @@ -44,6 +44,7 @@ typedef uint64_t Timestamp; #include #include +#define addrlen_t int #define popen _popen #define pclose _pclose @@ -57,6 +58,7 @@ typedef uint64_t Timestamp; #include #include +#define addrlen_t unsigned int #define SOCKET int #define INVALID_SOCKET -1 #define closesocket close diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index d4edca2..b71662c 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -281,7 +281,61 @@ void netprot::Serialize(ErrorLog* errlog, char* buf[], uint32_t* buflen) { bool netprot::Deserialize(Input* in, char* buf, const uint32_t buflen) { - return false; + if (buflen <= sizeof(Input)) + return false; + + uint8_t diff[sizeof(uint64_t)] = { 0,0,0,0,0,0,0,0 }; + memcpy(diff, &buf[1], sizeof(uint64_t)); + in->timestamp = + (uint64_t)diff[0] << 56 | + (uint64_t)diff[1] << 48 | + (uint64_t)diff[2] << 40 | + (uint64_t)diff[3] << 32 | + (uint64_t)diff[4] << 24 | + (uint64_t)diff[5] << 16 | + (uint64_t)diff[6] << 8 | + (uint64_t)diff[7]; + + memcpy(diff, &buf[1 + sizeof(uint64_t)], sizeof(uint64_t)); + in->sid = + (uint64_t)diff[0] << 56 | + (uint64_t)diff[1] << 48 | + (uint64_t)diff[2] << 40 | + (uint64_t)diff[3] << 32 | + (uint64_t)diff[4] << 24 | + (uint64_t)diff[5] << 16 | + (uint64_t)diff[6] << 8 | + (uint64_t)diff[7]; + + uint8_t keys = 0; + memcpy(&keys, &buf[2 + sizeof(uint64_t)], sizeof(uint8_t)); + in->keys.forward = keys && 0b10000000; + in->keys.backward = keys && 0b01000000; + in->keys.left = keys && 0b00100000; + in->keys.right = keys && 0b00010000; + in->keys.jump = keys && 0b00001000; + in->keys.shoot = keys && 0b00000100; + in->keys.block = keys && 0b00000010; + + uint8_t subvec[3 * sizeof(uint32_t)] = { 0,0,0,0,0,0,0,0,0,0,0,0 }; + memcpy(subvec, &buf[3 + sizeof(uint64_t)], sizeof(uint8_t) * 12); + uint32_t vec[3] = { + (uint32_t)subvec[0] << 24 | + (uint32_t)subvec[1] << 16 | + (uint32_t)subvec[2] << 8 | + (uint32_t)subvec[3], + (uint32_t)subvec[4] << 24 | + (uint32_t)subvec[5] << 16 | + (uint32_t)subvec[6] << 8 | + (uint32_t)subvec[7], + (uint32_t)subvec[8] << 24 | + (uint32_t)subvec[9] << 16 | + (uint32_t)subvec[10] << 8 | + (uint32_t)subvec[11]}; + + memcpy(&in->direction, vec, sizeof(uint32_t) * 3); + + return true; } bool netprot::Deserialize(Output* out, char* buf, const uint32_t buflen) { diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 8324d23..9c647d8 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -104,16 +104,10 @@ int Server::Ready() { buildIdList(ID_LIST_SIZE); - - while (!readystart) { sockaddr_in sockad; -#ifdef _WIN32 - int addrlen = sizeof(sockad); -#else // maudit que c'est con, ça. - unsigned int addrlen = sizeof(sockad); -#endif + addrlen_t addrlen = sizeof(sockad); SOCKET sock = accept(m_sock_tcp, (sockaddr*)&sockad, &addrlen); @@ -223,7 +217,7 @@ void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) void Server::buildIdList(size_t size) { std::set lst; - do lst.insert(((uint64_t)rand() << 25) % 8675309); // EIGHT SIX SEVENFIVE THREE AUGHT NIIIIIIiIIiiIiINE! + do lst.insert(((uint64_t)rand() << 32 | rand())); // EIGHT SIX SEVENFIVE THREE AUGHT NIIIIIIiIIiiIiINE! while (lst.size() < size); m_ids = std::vector(lst.begin(), lst.end()); From cf6e58cd95c6e34ba19968f8b852a370ad1c01a5 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Tue, 24 Oct 2023 09:17:21 -0400 Subject: [PATCH 16/65] =?UTF-8?q?erratas=20dans=20la=20s=C3=A9rialisation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-common/netprotocol.cpp | 12 ++++++------ SQCSim-common/netprotocol.h | 9 +++++++++ SQCSim2021/engine.cpp | 8 ++++---- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index b71662c..62ac48d 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -37,7 +37,7 @@ void netprot::Serialize(Input* in, char* buf[], uint32_t* buflen) { keys.shoot & 0b00000100 | keys.block & 0b00000010 ; - memcpy(*buf + sizeof(uint64_t) + 2, &keys8, sizeof(uint8_t)); + memcpy(*buf + sizeof(uint64_t) * 2 + 1, &keys8, sizeof(uint8_t)); uint32_t vec[3]; memcpy(vec, &in->direction, sizeof(Vector3f)); // Pour dénaturer les floats. @@ -56,13 +56,13 @@ void netprot::Serialize(Input* in, char* buf[], uint32_t* buflen) { (vec[2] >> 8) & 0xFF, vec[2] & 0xFF}; - memcpy(*buf + sizeof(uint64_t) + 3, vec8, sizeof(uint32_t) * 3); + memcpy(*buf + sizeof(uint64_t) * 2 + 2, vec8, sizeof(uint32_t) * 3); - *buflen = sizeof(uint64_t) + 3 + sizeof(uint32_t) * 3; + *buflen = sizeof(uint64_t) * 2 + 2 + sizeof(uint32_t) * 3; } void netprot::Serialize(Output* out, char* buf[], uint32_t* buflen) { - + } void netprot::Serialize(Sync* sync, char* buf[], uint32_t* buflen) { @@ -308,7 +308,7 @@ bool netprot::Deserialize(Input* in, char* buf, const uint32_t buflen) { (uint64_t)diff[7]; uint8_t keys = 0; - memcpy(&keys, &buf[2 + sizeof(uint64_t)], sizeof(uint8_t)); + memcpy(&keys, &buf[1 + sizeof(uint64_t) * 2], sizeof(uint8_t)); in->keys.forward = keys && 0b10000000; in->keys.backward = keys && 0b01000000; in->keys.left = keys && 0b00100000; @@ -318,7 +318,7 @@ bool netprot::Deserialize(Input* in, char* buf, const uint32_t buflen) { in->keys.block = keys && 0b00000010; uint8_t subvec[3 * sizeof(uint32_t)] = { 0,0,0,0,0,0,0,0,0,0,0,0 }; - memcpy(subvec, &buf[3 + sizeof(uint64_t)], sizeof(uint8_t) * 12); + memcpy(subvec, &buf[2 + sizeof(uint64_t) * 2], sizeof(uint8_t) * 12); uint32_t vec[3] = { (uint32_t)subvec[0] << 24 | (uint32_t)subvec[1] << 16 | diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index 7c41418..72bc26e 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -118,6 +118,7 @@ namespace netprot { PacketType getType(char* buf, uint32_t buflen); // srv/cli template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); + template void sendPackTo(SOCKET sock, T* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad); template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen) { @@ -126,6 +127,14 @@ namespace netprot { *buflen = BUFFER_LENGTH; } + template + void sendPackTo(SOCKET sock, T* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad) { + sockaddr_in addr = *sockad; + netprot::Serialize(pack, buf, buflen); + sendto(sock, *buf, *buflen, 0, (sockaddr*)&addr, sizeof(addr)); + *buflen = BUFFER_LENGTH; + } + }; #endif diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index d544415..7f4747d 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -783,7 +783,7 @@ void Engine::Render(float elapsedTime) { static char* buf = new char[BUFFER_LENGTH]; uint32_t buflen = BUFFER_LENGTH; netprot::Input input; - sockaddr_in addr = m_conn.m_srvsockaddr; + //sockaddr_in addr = m_conn.m_srvsockaddr; input.direction = m_player.GetDirection(); input.sid = m_conn.getId(); @@ -796,10 +796,10 @@ void Engine::Render(float elapsedTime) { input.keys.block = m_mouseR; input.keys.shoot = m_mouseL; - netprot::Serialize(&input, &buf, &buflen); - sendto(m_conn.m_sock_udp, buf, buflen, 0, (sockaddr*)&addr, sizeof(addr)); + //netprot::Serialize(&input, &buf, &buflen); + //sendto(m_conn.m_sock_udp, buf, buflen, 0, (sockaddr*)&addr, sizeof(addr)); - //netprot::sendPackTo(m_conn.m_sock_udp, &input, &buf, &buflen, &m_conn.m_srvsockaddr); + netprot::sendPackTo(m_conn.m_sock_udp, &input, &buf, &buflen, &m_conn.m_srvsockaddr); } } else if (m_gamestate == GameState::MAIN_MENU || m_gamestate == GameState::OPTIONS) From 4964bc539450b2b50884f64375892becbb660335 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Tue, 24 Oct 2023 10:43:30 -0400 Subject: [PATCH 17/65] ajouts dans netprot --- SQCSim-common/netprotocol.cpp | 60 +++++++++++++++++++++++++++++++++-- SQCSim-common/netprotocol.h | 7 ++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index 62ac48d..534c264 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -62,11 +62,15 @@ void netprot::Serialize(Input* in, char* buf[], uint32_t* buflen) { } void netprot::Serialize(Output* out, char* buf[], uint32_t* buflen) { - + *buf[0] = netprot::PACKET_TYPE::OUTPUT; + + } void netprot::Serialize(Sync* sync, char* buf[], uint32_t* buflen) { - + *buf[0] = netprot::PACKET_TYPE::SYNC; + + } void netprot::Serialize(TeamInfo* tinfo, char* buf[], uint32_t* buflen) { @@ -558,3 +562,55 @@ netprot::PacketType netprot::getType(char* buf, const uint32_t buflen) { return netprot::PACKET_TYPE::ERR; return buf[0]; } + +netprot::Packet netprot::getPack(char* buf, uint32_t buflen) { + Packet pck = { nullptr, PACKET_TYPE::ERR }; + Input* in = nullptr; + Output* out = nullptr; + Sync* sync = nullptr; + Chat* chat = nullptr; + GameInfo* ginfo = nullptr; + ErrorLog* errlog = nullptr; + + switch (getType(buf, buflen)) { + case PACKET_TYPE::INPUT: + in = new Input(); + netprot::Deserialize(in, buf, buflen); + pck.type = PACKET_TYPE::INPUT; + pck.ptr = (void*)in; + break; + case PACKET_TYPE::OUTPUT: + out = new Output(); + netprot::Deserialize(out, buf, buflen); + pck.type = PACKET_TYPE::OUTPUT; + pck.ptr = (void*)out; + break; + case PACKET_TYPE::SYNC: + sync = new Sync(); + netprot::Deserialize(sync, buf, buflen); + pck.type = PACKET_TYPE::SYNC; + pck.ptr = (void*)sync; + break; + case PACKET_TYPE::CHAT: + chat = new Chat(); + netprot::Deserialize(chat, buf, buflen); + pck.type = PACKET_TYPE::CHAT; + pck.ptr = (void*)chat; + break; + case PACKET_TYPE::GAMEINFO: + ginfo = new GameInfo(); + netprot::Deserialize(ginfo, buf, buflen); + pck.type = PACKET_TYPE::GAMEINFO; + pck.ptr = (void*)ginfo; + break; + case PACKET_TYPE::ERRLOG: + errlog = new ErrorLog(); + netprot::Deserialize(errlog, buf, buflen); + pck.type = PACKET_TYPE::ERRLOG; + pck.ptr = (void*)errlog; + break; + default: + break; + } + return pck; +} diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index 72bc26e..c383dfa 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -19,6 +19,11 @@ namespace netprot { LAST_PACK }; + struct Packet { + void* ptr; + PACKET_TYPE type; + }; + struct Keys { bool forward, backward, @@ -117,6 +122,8 @@ namespace netprot { PacketType getType(char* buf, uint32_t buflen); // srv/cli + Packet getPack(char* buf, uint32_t buflen); + template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); template void sendPackTo(SOCKET sock, T* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad); From dd2396e5e4e52cb90d665bce0aad1c1a563b08cf Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Wed, 25 Oct 2023 10:00:54 -0400 Subject: [PATCH 18/65] ajout LoginInfo dans le parseur de paquet. --- SQCSim-common/netprotocol.cpp | 79 ++++++++++---- SQCSim-common/netprotocol.h | 5 +- SQCSim-srv/server.cpp | 188 ++++++++-------------------------- 3 files changed, 108 insertions(+), 164 deletions(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index 534c264..093b902 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -571,46 +571,89 @@ netprot::Packet netprot::getPack(char* buf, uint32_t buflen) { Chat* chat = nullptr; GameInfo* ginfo = nullptr; ErrorLog* errlog = nullptr; + LoginInfo* loginf = nullptr; switch (getType(buf, buflen)) { case PACKET_TYPE::INPUT: in = new Input(); - netprot::Deserialize(in, buf, buflen); - pck.type = PACKET_TYPE::INPUT; - pck.ptr = (void*)in; + if (netprot::Deserialize(in, buf, buflen)) { + pck.type = PACKET_TYPE::INPUT; + pck.ptr = (void*)in; + } break; case PACKET_TYPE::OUTPUT: out = new Output(); - netprot::Deserialize(out, buf, buflen); - pck.type = PACKET_TYPE::OUTPUT; - pck.ptr = (void*)out; + if (netprot::Deserialize(out, buf, buflen)) { + pck.type = PACKET_TYPE::OUTPUT; + pck.ptr = (void*)out; + } break; case PACKET_TYPE::SYNC: sync = new Sync(); - netprot::Deserialize(sync, buf, buflen); - pck.type = PACKET_TYPE::SYNC; - pck.ptr = (void*)sync; + if (netprot::Deserialize(sync, buf, buflen)) { + pck.type = PACKET_TYPE::SYNC; + pck.ptr = (void*)sync; + } break; case PACKET_TYPE::CHAT: chat = new Chat(); - netprot::Deserialize(chat, buf, buflen); - pck.type = PACKET_TYPE::CHAT; - pck.ptr = (void*)chat; + if (netprot::Deserialize(chat, buf, buflen)) { + pck.type = PACKET_TYPE::CHAT; + pck.ptr = (void*)chat; + } break; case PACKET_TYPE::GAMEINFO: ginfo = new GameInfo(); - netprot::Deserialize(ginfo, buf, buflen); - pck.type = PACKET_TYPE::GAMEINFO; - pck.ptr = (void*)ginfo; + if (netprot::Deserialize(ginfo, buf, buflen)) { + pck.type = PACKET_TYPE::GAMEINFO; + pck.ptr = (void*)ginfo; + } break; case PACKET_TYPE::ERRLOG: errlog = new ErrorLog(); - netprot::Deserialize(errlog, buf, buflen); - pck.type = PACKET_TYPE::ERRLOG; - pck.ptr = (void*)errlog; + if (netprot::Deserialize(errlog, buf, buflen)) { + pck.type = PACKET_TYPE::ERRLOG; + pck.ptr = (void*)errlog; + } + break; + case PACKET_TYPE::LOGINF: + loginf = new LoginInfo(); + if (netprot::Deserialize(loginf, buf, buflen)) { + pck.type = PACKET_TYPE::LOGINF; + pck.ptr = (void*)loginf; + } break; default: break; } return pck; } + +bool netprot::emptyPack(netprot::Packet pck) { + switch (pck.type) { + case PACKET_TYPE::INPUT: + delete (Input*)pck.ptr; + break; + case PACKET_TYPE::OUTPUT: + delete (Output*)pck.ptr; + break; + case PACKET_TYPE::SYNC: + delete (Sync*)pck.ptr; + break; + case PACKET_TYPE::CHAT: + delete (Chat*)pck.ptr; + break; + case PACKET_TYPE::GAMEINFO: + delete (GameInfo*)pck.ptr; + break; + case PACKET_TYPE::ERRLOG: + delete (ErrorLog*)pck.ptr; + break; + case PACKET_TYPE::LOGINF: + delete (LoginInfo*)pck.ptr; + break; + default: + return false; + } + return true; +} diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index c383dfa..97c880b 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -20,7 +20,7 @@ namespace netprot { }; struct Packet { - void* ptr; + void* ptr = nullptr; PACKET_TYPE type; }; @@ -120,9 +120,10 @@ namespace netprot { bool Deserialize(Chat* chat, char* buf, const uint32_t buflen); // srv/cli bool Deserialize(ErrorLog* errlog, char* buf, const uint32_t buflen); // srv - PacketType getType(char* buf, uint32_t buflen); // srv/cli + PacketType getType(char* buf, uint32_t buflen); Packet getPack(char* buf, uint32_t buflen); + bool emptyPack(Packet pck); template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); template void sendPackTo(SOCKET sock, T* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad); diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 9c647d8..6e34580 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -67,7 +67,6 @@ int Server::Init() { int Server::Ready() { int nbrjoueurs = 0, nbrconn = 0; -#ifdef SRV_MANUAL_SETUP do { Log("Entrez la durée de la partie: ", false, false); std::cin >> m_game.countdown; @@ -83,11 +82,7 @@ int Server::Ready() { std::cin >> nbrjoueurs; std::cout << std::endl; } while (nbrjoueurs > 0 && nbrjoueurs >= MAX_CONNECTIONS); -#else // setup preconfiguré - m_game.countdown = 360; - m_game.seed = 9370707; - nbrjoueurs = 1; -#endif + m_game.gameType = 1; if (listen(m_sock_tcp, MAX_CONNECTIONS) < 0) { @@ -106,48 +101,58 @@ int Server::Ready() { 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 connection provenant de: "; - char* strbuf = new char[BUFFER_LENGTH]; + static char* strbuf = new char[BUFFER_LENGTH]; uint32_t strbuflen = BUFFER_LENGTH; + std::string str = "Nouvelle connection provenant de: "; str.append(inet_ntop(AF_INET, &sockad.sin_addr, strbuf, strbuflen)).append(": ").append(std::to_string(sockad.sin_port)); if (recv(sock, buf, buflen, 0) > 0) { - netprot::LoginInfo log; + netprot::LoginInfo* log; netprot::PlayerInfo play; - if (netprot::Deserialize(&log, buf, buflen)) { - log.sid = getUniqueId(); - log.tid = 0; - - str.append(" Nom: ").append(log.name); - Log(str, false, false); - str = ""; - - str.append(log.name).append(" SID: [").append(std::to_string(log.sid).append("]")); - Log(str, false, false); - - netprot::sendPack(sock, &log, &buf, &buflen); - - play.id = getUniqueId(); - memcpy(play.name, log.name, std::strlen(log.name) + 1); - play.tid = log.tid; - - netprot::sendPack(sock, &m_game, &buf, &buflen); - Connection* conn = new Connection(sock, sockad, log, play); - - m_players[log.sid] = conn; - if (++nbrconn >= nbrjoueurs) - readystart = true; + netprot::Packet pck = netprot::getPack(buf, buflen); + if (pck.type != netprot::PACKET_TYPE::LOGINF) { + if (pck.type != netprot::PACKET_TYPE::ERR) + netprot::emptyPack(pck); + continue; } + log = (netprot::LoginInfo*)pck.ptr; + + log->sid = getUniqueId(); + log->tid = 0; + + str.append(" Nom: ").append(log->name); + Log(str, false, false); + str = ""; + + str.append(log->name).append(" SID: [").append(std::to_string(log->sid).append("]")); + Log(str, false, false); + + netprot::sendPack(sock, log, &buf, &buflen); + + play.id = getUniqueId(); + memcpy(play.name, log->name, std::strlen(log->name) + 1); + play.tid = log->tid; + + netprot::sendPack(sock, &m_game, &buf, &buflen); + Connection* conn = new Connection(sock, sockad, *log, play); + + // TODO: Envoyer les infos de joueur distant aux joueurs déjà connectés + // et envoyer les infos des joueurs distants au nouveau joueur. + + m_players[log->sid] = conn; + delete log; // le pck va se supprimer tout seul, mais le pointer du log qui vient de lui, non. + + if (++nbrconn >= nbrjoueurs) + readystart = true; + } } } @@ -163,6 +168,11 @@ void Server::Run() { Log("Partie en cours...", false, false); + for (auto& conn : m_players) // Gérer le point de spawn des joueurs. + conn.second->player = new Player(Vector3f(64., 128., 64.)); + + // TODO: Faire un premier sync pour que le joueur parte à la bonne place. + while (true) { if (recvfrom(m_sock_udp, buf, BUFFER_LENGTH, 0, (sockaddr*)&sockad, &socklen) > 0) { Deserialize(&in, buf, buflen); @@ -248,113 +258,3 @@ uint64_t Server::getUniqueId() { Log(str, false, false); */ - -///* Recevoir paquet */ -//while (true) { -// char buffer[2048]; -// sockaddr_in client; -// -//#ifdef _WIN32 // Mais pourquoi? -// int clen = sizeof(client); -//#else -// unsigned int clen = sizeof(client); -//#endif -// -// int count = recvfrom(socket_udp, buffer, sizeof(buffer) - 1, 0, (sockaddr*)&client, &clen); -// -// if (count < 0) { -// Log("Erreur de reception de paquet.", socket_udp); -// return 4; -// } -// -// buffer[count] = '\0'; -// -// /* Gérer le paquet reçu */ -// std::string commande(buffer); -// -// if (commande.find("echo ") == 0) { /* ECHO */ -// std::string::size_type pos = commande.find(' '); -// std::string parametres = commande.substr(pos + 1); -// -// sendto(socket_udp, parametres.c_str(), parametres.length(), 0, (const sockaddr*)&client, sizeof(client)); -// } -// else if (commande.find("date ") == 0) { /* DATE */ -// time_t rawtime; -// struct tm* timeinfo = new tm(); -// char tbuffer[80]; -// -// time(&rawtime); -// -//#ifdef _WIN32 -// localtime_s(timeinfo, &rawtime); -//#else -// localtime_r(&rawtime, timeinfo); -//#endif -// -// strftime(tbuffer, 80, "%a %b %e %T %G", timeinfo); -// -// sendto(socket_udp, tbuffer, sizeof(tbuffer), 0, (const sockaddr*)&client, sizeof(client)); -// delete timeinfo; -// } -// else if (commande.find("ping ") == 0) { /* PING */ -// sendto(socket_udp, "pong", sizeof("pong"), 0, (const sockaddr*)&client, sizeof(client)); -// } -// else if (commande.find("usager ") == 0) { /* USAGER */ -// std::string user; -// -//#ifdef _WIN32 -// wchar_t userbuf[30]; -// DWORD usersize = 30; -// -// GetUserNameW(userbuf, &usersize); -// -// std::wstring wuser = userbuf; -// user = std::string(wuser.begin(), wuser.end()); -//#else -// char ptr[30]; -// getlogin_r(ptr, sizeof(ptr) - 1); -// user = std::string(ptr); -//#endif -// -// sendto(socket_udp, user.c_str(), user.length(), 0, (const sockaddr*)&client, sizeof(client)); -// } -// else if (commande.find("exec ") == 0) { /* EXEC */ -// std::string::size_type pos = commande.find(' '); -// std::string parametres = commande.substr(pos + 1); -// -// FILE* pipe = nullptr; -// char buffer[301]; // 300 caractères + '\0' -// std::string reponse; -// -// pipe = popen(parametres.c_str(), "r"); -// -// if (!pipe) -// reponse = "Erreur de commande!"; -// else while (!feof(pipe)) { -// if (fgets(buffer, sizeof(buffer) - 1, pipe)) -// reponse += buffer; -// } -// -// if (pipe) -// pclose(pipe); -// -// if (reponse.length() > 300) -// reponse = reponse.substr(0, 300); -// else if (reponse.length() < 1) -// reponse = "OK!"; -// -// sendto(socket_udp, reponse.c_str(), reponse.length(), 0, (const sockaddr*)&client, sizeof(client)); -// } -// else if (commande.find("bye ") == 0) { /* BYE */ -// Log("", 0); // Message d'erreur pas de message d'erreur! -// return 0; -// } -// else sendto(socket_udp, "huh?", sizeof("huh?"), 0, (const sockaddr*)&client, sizeof(client)); /* DEFAULT */ -//} -// -///* Ce bout de code ne devrait theoriquement jamais etre atteint, mais au cas. */ -//Log("", 0); -//return 0; -//return false; -//} -// From 9b4defba2a22146595cc163ee0761a12d4f69db3 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Wed, 25 Oct 2023 12:16:14 -0400 Subject: [PATCH 19/65] =?UTF-8?q?=C3=A0=20tester?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-srv/connection.cpp | 23 +++++++++---------- SQCSim-srv/connection.h | 24 ++++++++++++-------- SQCSim-srv/server.cpp | 48 +++++++++++++++++++++------------------ SQCSim-srv/server.h | 2 ++ SQCSim2021/connector.cpp | 3 +++ SQCSim2021/connector.h | 2 -- 6 files changed, 56 insertions(+), 46 deletions(-) diff --git a/SQCSim-srv/connection.cpp b/SQCSim-srv/connection.cpp index 356396f..90c7a95 100644 --- a/SQCSim-srv/connection.cpp +++ b/SQCSim-srv/connection.cpp @@ -4,8 +4,8 @@ Connection::Connection(SOCKET sock, sockaddr_in sockaddr, - netprot::LoginInfo log, - netprot::PlayerInfo play): + LoginInfo log, + PlayerInfo play): m_sock(sock), m_addr(sockaddr), m_loginfo(log), @@ -13,9 +13,7 @@ Connection::Connection(SOCKET sock, } -Connection::~Connection() { - closesocket(m_sock); -} +Connection::~Connection() { closesocket(m_sock); } uint64_t Connection::GetHash(bool self) const { return self? m_loginfo.sid: m_playinfo.id; } @@ -23,19 +21,17 @@ uint64_t Connection::GetTeamHash() const { return m_loginfo.tid; } std::string Connection::GetName() const { return m_loginfo.name; } -void Connection::AddInput(netprot::Input in) { - m_input_manifest.insert({ in.timestamp, in }); -} +void Connection::AddInput(Input in) { m_input_manifest.insert({ in.timestamp, in }); } -netprot::Output* Connection::getOutput(Timestamp time) { +Output* Connection::getOutput(Timestamp time) { auto out = m_output_manifest.find(time); if (out != m_output_manifest.end()) return &out->second; return nullptr; } -netprot::Sync Connection::getSync(Timestamp time) { - netprot::Sync sync; +Sync Connection::getSync(Timestamp time) { + Sync sync; auto out = m_output_manifest.find(time); if (out != m_output_manifest.end()) { sync.timestamp = out->second.timestamp; @@ -45,10 +41,13 @@ netprot::Sync Connection::getSync(Timestamp time) { return sync; } +SOCKET Connection::getSock() const { return m_sock; } + +PlayerInfo* Connection::getInfo() const { return (PlayerInfo*)&m_playinfo; } + void Connection::CleanInputManifest(Timestamp time) { auto wat = m_input_manifest.find(time); while (wat != m_input_manifest.begin()) m_input_manifest.erase(wat--); - } diff --git a/SQCSim-srv/connection.h b/SQCSim-srv/connection.h index 6dee988..6fb7a9e 100644 --- a/SQCSim-srv/connection.h +++ b/SQCSim-srv/connection.h @@ -8,13 +8,15 @@ #include "../SQCSim-common/netprotocol.h" #include "define.h" +using namespace netprot; + class Connection { public: Connection( SOCKET sock, sockaddr_in sockaddr, - netprot::LoginInfo log, - netprot::PlayerInfo play); + LoginInfo log, + PlayerInfo play); ~Connection(); Player* player = nullptr; @@ -23,20 +25,22 @@ public: uint64_t GetTeamHash() const; std::string GetName() const; - void AddInput(netprot::Input in); - netprot::Output* getOutput(Timestamp time); - netprot::Sync getSync(Timestamp time); + void AddInput(Input in); + Output* getOutput(Timestamp time); + Sync getSync(Timestamp time); + SOCKET getSock() const; + PlayerInfo* getInfo() const; void CleanInputManifest(Timestamp time); private: - std::map m_input_manifest; - std::map m_output_manifest; - std::map m_chatlog; + std::map m_input_manifest; + std::map m_output_manifest; + std::map m_chatlog; SOCKET m_sock; sockaddr_in m_addr; - netprot::LoginInfo m_loginfo; - netprot::PlayerInfo m_playinfo; + LoginInfo m_loginfo; + PlayerInfo m_playinfo; }; #endif \ No newline at end of file diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 6e34580..a9e3538 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -114,45 +114,49 @@ int Server::Ready() { str.append(inet_ntop(AF_INET, &sockad.sin_addr, strbuf, strbuflen)).append(": ").append(std::to_string(sockad.sin_port)); if (recv(sock, buf, buflen, 0) > 0) { - netprot::LoginInfo* log; - netprot::PlayerInfo play; + LoginInfo* log; + PlayerInfo play; - netprot::Packet pck = netprot::getPack(buf, buflen); - if (pck.type != netprot::PACKET_TYPE::LOGINF) { - if (pck.type != netprot::PACKET_TYPE::ERR) - netprot::emptyPack(pck); - continue; + Packet pck = getPack(buf, buflen); + if (pck.type != PACKET_TYPE::LOGINF) { + Log("Paquet invalide.", true, false); + if (pck.type != PACKET_TYPE::ERR) + emptyPack(pck); + continue; // Passer au prochain appel si c'est pas un LoginInfo ou un LoginInfo invalide qui rentre. } - log = (netprot::LoginInfo*)pck.ptr; + log = (LoginInfo*)pck.ptr; log->sid = getUniqueId(); log->tid = 0; - str.append(" Nom: ").append(log->name); - Log(str, false, false); - str = ""; + Log(str.append(" Nom: ").append(log->name), false, false); + str.clear(); - str.append(log->name).append(" SID: [").append(std::to_string(log->sid).append("]")); - Log(str, false, false); - - netprot::sendPack(sock, log, &buf, &buflen); + Log(str.append(log->name).append(" SID: [").append(std::to_string(log->sid).append("]")), false, false); + sendPack(sock, log, &buf, &buflen); + play.id = getUniqueId(); - memcpy(play.name, log->name, std::strlen(log->name) + 1); + strcpy_s(play.name, log->name); + play.tid = log->tid; - netprot::sendPack(sock, &m_game, &buf, &buflen); + sendPack(sock, &m_game, &buf, &buflen); Connection* conn = new Connection(sock, sockad, *log, play); - // TODO: Envoyer les infos de joueur distant aux joueurs déjà connectés - // et envoyer les infos des joueurs distants au nouveau joueur. + for (auto& player : m_players) { + sendPack(player.second->getSock(), &play, &buf, &buflen); // Envoyer les infos de joueur distant aux joueurs déjà connectés + buflen = BUFFER_LENGTH; + sendPack(sock, player.second->getInfo(), &buf, &buflen); // et envoyer les infos des joueurs distants au nouveau joueur. + buflen = BUFFER_LENGTH; + } m_players[log->sid] = conn; + delete log; // le pck va se supprimer tout seul, mais le pointer du log qui vient de lui, non. if (++nbrconn >= nbrjoueurs) readystart = true; - } } } @@ -166,12 +170,12 @@ void Server::Run() { sockaddr_in sockad; int socklen = sizeof(sockad); - Log("Partie en cours...", false, false); + Log("Debut de la partie...", false, false); for (auto& conn : m_players) // Gérer le point de spawn des joueurs. conn.second->player = new Player(Vector3f(64., 128., 64.)); - // TODO: Faire un premier sync pour que le joueur parte à la bonne place. + // TODO: Faire un premier sync pour que les joueurs partent à la bonne place. while (true) { if (recvfrom(m_sock_udp, buf, BUFFER_LENGTH, 0, (sockaddr*)&sockad, &socklen) > 0) { diff --git a/SQCSim-srv/server.h b/SQCSim-srv/server.h index 2e1c1ba..a66f885 100644 --- a/SQCSim-srv/server.h +++ b/SQCSim-srv/server.h @@ -10,6 +10,8 @@ #include "define.h" #include "connection.h" +using namespace netprot; + class Server { public: Server(LogDest log = LOG_DEST::CONSOLE); diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index 0583b30..7709a76 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -88,6 +88,9 @@ int Connector::Connect(const char* srv_addr, std::string name) { } ++rpack; break; + case netprot::PACKET_TYPE::PLAYINF: + // TODO: Populer un remotePlayer avec ça. + break; default: ++errors; //std::cout << "Packet invalide." << std::endl; diff --git a/SQCSim2021/connector.h b/SQCSim2021/connector.h index 395a0b4..5a9773e 100644 --- a/SQCSim2021/connector.h +++ b/SQCSim2021/connector.h @@ -32,7 +32,5 @@ private: netprot::LoginInfo m_loginfo; netprot::GameInfo m_gameinfo; - - }; #endif From f5a1906877746bb7ba07291791f396557ed3220d Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Thu, 26 Oct 2023 10:39:08 -0400 Subject: [PATCH 20/65] Cleanup de srv --- SQCSim-srv/cmake/CMakeLists.txt | 2 +- SQCSim-srv/define.h | 4 +++ SQCSim-srv/server.cpp | 43 ++++++++++++++++++--------------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/SQCSim-srv/cmake/CMakeLists.txt b/SQCSim-srv/cmake/CMakeLists.txt index 4a1f624..ef8f3f2 100644 --- a/SQCSim-srv/cmake/CMakeLists.txt +++ b/SQCSim-srv/cmake/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.18.4) project(SQCSim-Server VERSION 0.1) add_compile_options("-Wno-narrowing") -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_STANDARD_REQUIRED True) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../out") diff --git a/SQCSim-srv/define.h b/SQCSim-srv/define.h index b1f4621..eaf044e 100644 --- a/SQCSim-srv/define.h +++ b/SQCSim-srv/define.h @@ -11,6 +11,10 @@ #define ID_LIST_SIZE 127 #define SRV_MANUAL_SETUP true +#ifdef _WIN32 +#define strcpy strcpy_s +#endif + typedef unsigned char LogDest; enum LOG_DEST { CONSOLE, LOGFILE, LOG_LAST }; diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index a9e3538..0ed0387 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -67,6 +67,12 @@ int Server::Init() { int Server::Ready() { int nbrjoueurs = 0, nbrconn = 0; + char *buf = new char[BUFFER_LENGTH], + *strbuf = new char[BUFFER_LENGTH]; + uint32_t buflen = BUFFER_LENGTH, + strbuflen = BUFFER_LENGTH; + bool readystart = false; + do { Log("Entrez la durée de la partie: ", false, false); std::cin >> m_game.countdown; @@ -90,15 +96,11 @@ int Server::Ready() { return 1; } - char* buf = new char[150]; - uint32_t buflen = 150; - bool readystart = false; srand(time(NULL)); - - Log("À l'écoute sur le port: " + std::to_string(SRV_PORT), false, false); - buildIdList(ID_LIST_SIZE); - + + Log("À l'écoute sur le port: " + std::to_string(SRV_PORT), false, false); + while (!readystart) { sockaddr_in sockad; addrlen_t addrlen = sizeof(sockad); @@ -107,9 +109,6 @@ int Server::Ready() { if (sock < 0) Log("Erreur de connexion", true, false); else if (sock > 0) { - static char* strbuf = new char[BUFFER_LENGTH]; - uint32_t strbuflen = BUFFER_LENGTH; - std::string str = "Nouvelle connection provenant de: "; str.append(inet_ntop(AF_INET, &sockad.sin_addr, strbuf, strbuflen)).append(": ").append(std::to_string(sockad.sin_port)); @@ -137,17 +136,17 @@ int Server::Ready() { sendPack(sock, log, &buf, &buflen); play.id = getUniqueId(); - strcpy_s(play.name, log->name); + strcpy(play.name, log->name); play.tid = log->tid; sendPack(sock, &m_game, &buf, &buflen); Connection* conn = new Connection(sock, sockad, *log, play); - for (auto& player : m_players) { - sendPack(player.second->getSock(), &play, &buf, &buflen); // Envoyer les infos de joueur distant aux joueurs déjà connectés + for (auto& [key, player] : m_players) { + sendPack(player->getSock(), &play, &buf, &buflen); // Envoyer les infos de joueur distant aux joueurs déjà connectés buflen = BUFFER_LENGTH; - sendPack(sock, player.second->getInfo(), &buf, &buflen); // et envoyer les infos des joueurs distants au nouveau joueur. + sendPack(sock, player->getInfo(), &buf, &buflen); // et envoyer les infos des joueurs distants au nouveau joueur. buflen = BUFFER_LENGTH; } @@ -160,6 +159,8 @@ int Server::Ready() { } } } + delete buf; + delete strbuf; return 0; } @@ -168,12 +169,12 @@ void Server::Run() { uint32_t buflen = BUFFER_LENGTH; netprot::Input in; sockaddr_in sockad; - int socklen = sizeof(sockad); + addrlen_t socklen = sizeof(sockad); Log("Debut de la partie...", false, false); - for (auto& conn : m_players) // Gérer le point de spawn des joueurs. - conn.second->player = new Player(Vector3f(64., 128., 64.)); + for (auto& [key, conn]: m_players) // Gérer le point de spawn des joueurs. + conn->player = new Player(Vector3f(64., 128., 64.)); // TODO: Faire un premier sync pour que les joueurs partent à la bonne place. @@ -208,10 +209,11 @@ inline std::string Server::LogTimestamp() { void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) { switch (m_log) { - case LOG_DEST::LOGFILE: + using enum LOG_DEST; // C++20! + case LOGFILE: m_logfile << LogTimestamp() << (is_fatal ? "FATAL " : "") << (is_error ? "ERROR " : "") << str << std::endl; break; - case LOG_DEST::CONSOLE: + case CONSOLE: default: std::cout << LogTimestamp() << (is_fatal ? "FATAL " : "") << (is_error ? "ERROR " : "") << str << std::endl; break; @@ -222,6 +224,9 @@ void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) closesocket(m_sock_udp); if (m_sock_tcp) closesocket(m_sock_tcp); + for (auto& [key, player] : m_players) + closesocket(player->getSock()); + m_players.clear(); #ifdef _WIN32 WSACleanup(); #endif From 8870550512bd7bbc2b0ba2c6fef86cd9cdcc5ae0 Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Thu, 26 Oct 2023 10:42:24 -0400 Subject: [PATCH 21/65] erratum --- SQCSim-srv/server.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 0ed0387..f0b79fb 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -159,8 +159,8 @@ int Server::Ready() { } } } - delete buf; - delete strbuf; + delete[] buf; + delete[] strbuf; return 0; } @@ -224,7 +224,7 @@ void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) closesocket(m_sock_udp); if (m_sock_tcp) closesocket(m_sock_tcp); - for (auto& [key, player] : m_players) + for (const auto& [key, player] : m_players) closesocket(player->getSock()); m_players.clear(); #ifdef _WIN32 From 8e958991920fd6847b61f8f552e65262dc4a3050 Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Thu, 26 Oct 2023 10:47:17 -0400 Subject: [PATCH 22/65] erratum II --- SQCSim-srv/server.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index f0b79fb..34715e5 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -17,7 +17,10 @@ Server::~Server() { if (m_sock_udp) closesocket(m_sock_udp); if (m_sock_tcp) - closesocket(m_sock_tcp); + closesocket(m_sock_tcp); + for (const auto& [key, player] : m_players) + closesocket(player->getSock()); + m_players.clear(); #ifdef _WIN32 WSACleanup(); #endif @@ -230,6 +233,7 @@ void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) #ifdef _WIN32 WSACleanup(); #endif + exit(-1); } } From 397cf7f00db6880090c051acad5bb35f66ed33ce Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Thu, 26 Oct 2023 10:55:36 -0400 Subject: [PATCH 23/65] erratum III --- SQCSim-srv/server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 34715e5..90c97fc 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -99,7 +99,6 @@ int Server::Ready() { return 1; } - srand(time(NULL)); buildIdList(ID_LIST_SIZE); Log("À l'écoute sur le port: " + std::to_string(SRV_PORT), false, false); @@ -240,6 +239,7 @@ void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) void Server::buildIdList(size_t size) { std::set lst; + srand(time(NULL)); do lst.insert(((uint64_t)rand() << 32 | rand())); // EIGHT SIX SEVENFIVE THREE AUGHT NIIIIIIiIIiiIiINE! while (lst.size() < size); From 2f2b49054fa80bef1e6db364acf17df777265675 Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Thu, 26 Oct 2023 11:17:13 -0400 Subject: [PATCH 24/65] erratum IV --- SQCSim-srv/server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 90c97fc..7320d3b 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -192,8 +192,8 @@ void Server::Run() { inline std::string Server::LogTimestamp() { time_t rawtime; - struct tm timeinfo; - char buffer[80]; + tm timeinfo; + char buffer[50]; time(&rawtime); From 05f9658f5578a53e812790b7da181eafbae08d23 Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Thu, 26 Oct 2023 17:18:30 -0400 Subject: [PATCH 25/65] erratum V? --- SQCSim-srv/server.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 7320d3b..63b4a3d 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -222,6 +222,8 @@ void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) } if (is_fatal) { + if (m_logfile.is_open()) + m_logfile.close(); if (m_sock_udp) closesocket(m_sock_udp); if (m_sock_tcp) From 60cc69a407f6e56e4366176b0e12c39354b6c715 Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Thu, 26 Oct 2023 17:57:42 -0400 Subject: [PATCH 26/65] Erratum VI: The Final Chapter --- SQCSim-srv/connection.cpp | 2 ++ SQCSim-srv/connection.h | 3 ++- SQCSim-srv/server.cpp | 30 +++++++++++++++++--------- SQCSim2021/media/audio/whitenoise.ogg | Bin 0 -> 225581 bytes 4 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 SQCSim2021/media/audio/whitenoise.ogg diff --git a/SQCSim-srv/connection.cpp b/SQCSim-srv/connection.cpp index 90c7a95..079b0fb 100644 --- a/SQCSim-srv/connection.cpp +++ b/SQCSim-srv/connection.cpp @@ -45,6 +45,8 @@ SOCKET Connection::getSock() const { return m_sock; } PlayerInfo* Connection::getInfo() const { return (PlayerInfo*)&m_playinfo; } +sockaddr_in* Connection::getAddr() const { return (sockaddr_in*)&m_addr; } + void Connection::CleanInputManifest(Timestamp time) { auto wat = m_input_manifest.find(time); diff --git a/SQCSim-srv/connection.h b/SQCSim-srv/connection.h index 6fb7a9e..21bff9b 100644 --- a/SQCSim-srv/connection.h +++ b/SQCSim-srv/connection.h @@ -30,6 +30,7 @@ public: Sync getSync(Timestamp time); SOCKET getSock() const; PlayerInfo* getInfo() const; + sockaddr_in* getAddr() const; void CleanInputManifest(Timestamp time); private: @@ -43,4 +44,4 @@ private: PlayerInfo m_playinfo; }; -#endif \ No newline at end of file +#endif diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 63b4a3d..600752a 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -77,7 +77,7 @@ int Server::Ready() { bool readystart = false; do { - Log("Entrez la durée de la partie: ", false, false); + Log("Entrez la dur�e de la partie: ", false, false); std::cin >> m_game.countdown; std::cout << std::endl; } while (m_game.countdown < 1); @@ -95,13 +95,13 @@ int Server::Ready() { m_game.gameType = 1; if (listen(m_sock_tcp, MAX_CONNECTIONS) < 0) { - Log("Écoute sur le port TCP.", true, true); + Log("Ecoute sur le port TCP.", true, true); return 1; } buildIdList(ID_LIST_SIZE); - Log("À l'écoute sur le port: " + std::to_string(SRV_PORT), false, false); + Log("A l'ecoute sur le port: " + std::to_string(SRV_PORT), false, false); while (!readystart) { sockaddr_in sockad; @@ -111,7 +111,7 @@ int Server::Ready() { if (sock < 0) Log("Erreur de connexion", true, false); else if (sock > 0) { - std::string str = "Nouvelle connection provenant de: "; + std::string str = "Nouvelle connexion provenant de: "; str.append(inet_ntop(AF_INET, &sockad.sin_addr, strbuf, strbuflen)).append(": ").append(std::to_string(sockad.sin_port)); if (recv(sock, buf, buflen, 0) > 0) { @@ -146,7 +146,7 @@ int Server::Ready() { Connection* conn = new Connection(sock, sockad, *log, play); for (auto& [key, player] : m_players) { - sendPack(player->getSock(), &play, &buf, &buflen); // Envoyer les infos de joueur distant aux joueurs déjà connectés + sendPack(player->getSock(), &play, &buf, &buflen); // Envoyer les infos de joueur distant aux joueurs d�j� connect�s buflen = BUFFER_LENGTH; sendPack(sock, player->getInfo(), &buf, &buflen); // et envoyer les infos des joueurs distants au nouveau joueur. buflen = BUFFER_LENGTH; @@ -169,16 +169,24 @@ int Server::Ready() { void Server::Run() { char* buf = new char[BUFFER_LENGTH]; uint32_t buflen = BUFFER_LENGTH; - netprot::Input in; + Input in; sockaddr_in sockad; addrlen_t socklen = sizeof(sockad); Log("Debut de la partie...", false, false); - for (auto& [key, conn]: m_players) // Gérer le point de spawn des joueurs. + for (auto& [key, conn]: m_players) { // Creation des instances de joueurs et premier sync. conn->player = new Player(Vector3f(64., 128., 64.)); - - // TODO: Faire un premier sync pour que les joueurs partent à la bonne place. + Player *player = conn->player; + Sync sync; + sync.position = player->GetPosition(); + sync.hp = player->GetHP(); + sync.sid = key; + sync.ammo = 0; + sync.timestamp = 0; + sync.timer = m_game.countdown; + sendPackTo(conn->getSock(), &sync, &buf, &buflen, conn->getAddr()); + } while (true) { if (recvfrom(m_sock_udp, buf, BUFFER_LENGTH, 0, (sockaddr*)&sockad, &socklen) > 0) { @@ -228,8 +236,10 @@ void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) closesocket(m_sock_udp); if (m_sock_tcp) closesocket(m_sock_tcp); - for (const auto& [key, player] : m_players) + for (const auto& [key, player] : m_players) { + delete player->player; closesocket(player->getSock()); + } m_players.clear(); #ifdef _WIN32 WSACleanup(); diff --git a/SQCSim2021/media/audio/whitenoise.ogg b/SQCSim2021/media/audio/whitenoise.ogg new file mode 100644 index 0000000000000000000000000000000000000000..7443756db30beb3c2eb4e70a7a3d45c714618695 GIT binary patch literal 225581 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�|+a?Dqs(zy5Y+>=Q7zkZw$ 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 Date: Fri, 27 Oct 2023 09:28:34 -0400 Subject: [PATCH 27/65] Gros refactoring sale. --- SQCSim-common/netprotocol.cpp | 278 +++++++++++++++++----------------- SQCSim-common/netprotocol.h | 41 ++++- SQCSim-srv/server.cpp | 19 +-- 3 files changed, 182 insertions(+), 156 deletions(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index 093b902..1053191 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -1,29 +1,29 @@ #include "netprotocol.h" void netprot::Serialize(Input* in, char* buf[], uint32_t* buflen) { - *buf[0] = netprot::PACKET_TYPE::INPUT; + *buf[0] = (char)netprot::PACKET_TYPE::INPUT; uint64_t time = in->timestamp; - uint8_t time8[sizeof(uint64_t)] = {(time >> 56) & 0xFF, - (time >> 48) & 0xFF, - (time >> 40) & 0xFF, - (time >> 32) & 0xFF, - (time >> 24) & 0xFF, - (time >> 16) & 0xFF, - (time >> 8 ) & 0xFF, - time & 0xFF}; + uint8_t time8[sizeof(uint64_t)] = {(uint8_t)((time >> 56) & 0xFF), + (uint8_t)((time >> 48) & 0xFF), + (uint8_t)((time >> 40) & 0xFF), + (uint8_t)((time >> 32) & 0xFF), + (uint8_t)((time >> 24) & 0xFF), + (uint8_t)((time >> 16) & 0xFF), + (uint8_t)((time >> 8 ) & 0xFF), + (uint8_t)(time & 0xFF)}; memcpy(*buf + 1, time8, sizeof(uint64_t)); uint64_t sid = in->sid; - uint8_t sid8[sizeof(uint64_t)] = {(sid >> 56) & 0xFF, - (sid >> 48) & 0xFF, - (sid >> 40) & 0xFF, - (sid >> 32) & 0xFF, - (sid >> 24) & 0xFF, - (sid >> 16) & 0xFF, - (sid >> 8 ) & 0xFF, - sid & 0xFF}; + uint8_t sid8[sizeof(uint64_t)] = {(uint8_t)((sid >> 56) & 0xFF), + (uint8_t)((sid >> 48) & 0xFF), + (uint8_t)((sid >> 40) & 0xFF), + (uint8_t)((sid >> 32) & 0xFF), + (uint8_t)((sid >> 24) & 0xFF), + (uint8_t)((sid >> 16) & 0xFF), + (uint8_t)((sid >> 8 ) & 0xFF), + (uint8_t)( sid & 0xFF)}; memcpy(*buf + sizeof(uint64_t) + 1, sid8, sizeof(uint64_t)); @@ -40,21 +40,21 @@ void netprot::Serialize(Input* in, char* buf[], uint32_t* buflen) { memcpy(*buf + sizeof(uint64_t) * 2 + 1, &keys8, sizeof(uint8_t)); uint32_t vec[3]; - memcpy(vec, &in->direction, sizeof(Vector3f)); // Pour dénaturer les floats. + memcpy(vec, &in->direction, sizeof(Vector3f)); // Pour d�naturer les floats. uint8_t vec8[3 * sizeof(uint32_t)] = { - (vec[0] >> 24) & 0xFF, - (vec[0] >> 16) & 0xFF, - (vec[0] >> 8) & 0xFF, - vec[0] & 0xFF, - (vec[1] >> 24) & 0xFF, - (vec[1] >> 16) & 0xFF, - (vec[1] >> 8) & 0xFF, - vec[1] & 0xFF, - (vec[2] >> 24) & 0xFF, - (vec[2] >> 16) & 0xFF, - (vec[2] >> 8) & 0xFF, - vec[2] & 0xFF}; + (uint8_t)((vec[0] >> 24) & 0xFF), + (uint8_t)((vec[0] >> 16) & 0xFF), + (uint8_t)((vec[0] >> 8) & 0xFF), + (uint8_t)(vec[0] & 0xFF), + (uint8_t)((vec[1] >> 24) & 0xFF), + (uint8_t)((vec[1] >> 16) & 0xFF), + (uint8_t)((vec[1] >> 8) & 0xFF), + (uint8_t)(vec[1] & 0xFF), + (uint8_t)((vec[2] >> 24) & 0xFF), + (uint8_t)((vec[2] >> 16) & 0xFF), + (uint8_t)((vec[2] >> 8) & 0xFF), + (uint8_t)(vec[2] & 0xFF)}; memcpy(*buf + sizeof(uint64_t) * 2 + 2, vec8, sizeof(uint32_t) * 3); @@ -62,33 +62,33 @@ void netprot::Serialize(Input* in, char* buf[], uint32_t* buflen) { } void netprot::Serialize(Output* out, char* buf[], uint32_t* buflen) { - *buf[0] = netprot::PACKET_TYPE::OUTPUT; + *buf[0] = (char)netprot::PACKET_TYPE::OUTPUT; } void netprot::Serialize(Sync* sync, char* buf[], uint32_t* buflen) { - *buf[0] = netprot::PACKET_TYPE::SYNC; + *buf[0] = (char)netprot::PACKET_TYPE::SYNC; } void netprot::Serialize(TeamInfo* tinfo, char* buf[], uint32_t* buflen) { - *buf[0] = netprot::PACKET_TYPE::TEAMINF; + *buf[0] = (char)netprot::PACKET_TYPE::TEAMINF; size_t namesize = std::strlen(tinfo->name) + 1; memcpy(*buf + 1, &tinfo->name, namesize); uint64_t tid = tinfo->id; uint8_t tid8[sizeof(uint64_t)] = { - (tid >> 56) & 0xFF, - (tid >> 48) & 0xFF, - (tid >> 40) & 0xFF, - (tid >> 32) & 0xFF, - (tid >> 24) & 0xFF, - (tid >> 16) & 0xFF, - (tid >> 8) & 0xFF, - tid & 0xFF + (uint8_t)((tid >> 56) & 0xFF), + (uint8_t)((tid >> 48) & 0xFF), + (uint8_t)((tid >> 40) & 0xFF), + (uint8_t)((tid >> 32) & 0xFF), + (uint8_t)((tid >> 24) & 0xFF), + (uint8_t)((tid >> 16) & 0xFF), + (uint8_t)((tid >> 8) & 0xFF), + (uint8_t)(tid & 0xFF) }; memcpy(*buf + namesize + 2, tid8, sizeof(uint64_t)); @@ -97,35 +97,35 @@ void netprot::Serialize(TeamInfo* tinfo, char* buf[], uint32_t* buflen) { } void netprot::Serialize(LoginInfo* linfo, char* buf[], uint32_t* buflen) { - *buf[0] = netprot::PACKET_TYPE::LOGINF; + *buf[0] = (char)netprot::PACKET_TYPE::LOGINF; size_t namesize = std::strlen(linfo->name) + 1; memcpy(*buf + 1, &linfo->name, namesize); uint64_t sid = linfo->sid; uint8_t sid8[sizeof(uint64_t)] = { - (sid >> 56) & 0xFF, - (sid >> 48) & 0xFF, - (sid >> 40) & 0xFF, - (sid >> 32) & 0xFF, - (sid >> 24) & 0xFF, - (sid >> 16) & 0xFF, - (sid >> 8) & 0xFF, - sid & 0xFF + (uint8_t)((sid >> 56) & 0xFF), + (uint8_t)((sid >> 48) & 0xFF), + (uint8_t)((sid >> 40) & 0xFF), + (uint8_t)((sid >> 32) & 0xFF), + (uint8_t)((sid >> 24) & 0xFF), + (uint8_t)((sid >> 16) & 0xFF), + (uint8_t)((sid >> 8) & 0xFF), + (uint8_t)(sid & 0xFF) }; memcpy(*buf + namesize + 2, sid8, sizeof(uint64_t)); uint64_t tid = linfo->tid; uint8_t tid8[sizeof(uint64_t)] = { - (tid >> 56) & 0xFF, - (tid >> 48) & 0xFF, - (tid >> 40) & 0xFF, - (tid >> 32) & 0xFF, - (tid >> 24) & 0xFF, - (tid >> 16) & 0xFF, - (tid >> 8) & 0xFF, - tid & 0xFF + (uint8_t)((tid >> 56) & 0xFF), + (uint8_t)((tid >> 48) & 0xFF), + (uint8_t)((tid >> 40) & 0xFF), + (uint8_t)((tid >> 32) & 0xFF), + (uint8_t)((tid >> 24) & 0xFF), + (uint8_t)((tid >> 16) & 0xFF), + (uint8_t)((tid >> 8) & 0xFF), + (uint8_t)(tid & 0xFF) }; memcpy(*buf + namesize + 2 + sizeof(uint64_t), tid8, sizeof(uint64_t)); @@ -134,35 +134,35 @@ void netprot::Serialize(LoginInfo* linfo, char* buf[], uint32_t* buflen) { } void netprot::Serialize(PlayerInfo* pinfo, char* buf[], uint32_t* buflen) { - *buf[0] = netprot::PACKET_TYPE::PLAYINF; + *buf[0] = (char)netprot::PACKET_TYPE::PLAYINF; size_t namesize = std::strlen(pinfo->name) + 1; memcpy(*buf + 1, &pinfo->name, namesize); uint64_t id = pinfo->id; uint8_t id8[sizeof(uint64_t)] = { - (id >> 56) & 0xFF, - (id >> 48) & 0xFF, - (id >> 40) & 0xFF, - (id >> 32) & 0xFF, - (id >> 24) & 0xFF, - (id >> 16) & 0xFF, - (id >> 8) & 0xFF, - id & 0xFF + (uint8_t)((id >> 56) & 0xFF), + (uint8_t)((id >> 48) & 0xFF), + (uint8_t)((id >> 40) & 0xFF), + (uint8_t)((id >> 32) & 0xFF), + (uint8_t)((id >> 24) & 0xFF), + (uint8_t)((id >> 16) & 0xFF), + (uint8_t)((id >> 8) & 0xFF), + (uint8_t)(id & 0xFF) }; memcpy(*buf + namesize + 2, id8, sizeof(uint64_t)); uint64_t tid = pinfo->tid; uint8_t tid8[sizeof(uint64_t)] = { - (tid >> 56) & 0xFF, - (tid >> 48) & 0xFF, - (tid >> 40) & 0xFF, - (tid >> 32) & 0xFF, - (tid >> 24) & 0xFF, - (tid >> 16) & 0xFF, - (tid >> 8) & 0xFF, - tid & 0xFF + (uint8_t)((tid >> 56) & 0xFF), + (uint8_t)((tid >> 48) & 0xFF), + (uint8_t)((tid >> 40) & 0xFF), + (uint8_t)((tid >> 32) & 0xFF), + (uint8_t)((tid >> 24) & 0xFF), + (uint8_t)((tid >> 16) & 0xFF), + (uint8_t)((tid >> 8) & 0xFF), + (uint8_t)(tid & 0xFF) }; memcpy(*buf + namesize + 2 + sizeof(uint64_t), tid8, sizeof(uint64_t)); @@ -171,46 +171,46 @@ void netprot::Serialize(PlayerInfo* pinfo, char* buf[], uint32_t* buflen) { } void netprot::Serialize(GameInfo* ginfo, char* buf[], uint32_t* buflen) { - *buf[0] = netprot::PACKET_TYPE::GAMEINFO; + *buf[0] = (char)netprot::PACKET_TYPE::GAMEINFO; uint64_t game = ginfo->seed; uint8_t seed8[sizeof(uint64_t)] = { - (game >> 56) & 0xFF, - (game >> 48) & 0xFF, - (game >> 40) & 0xFF, - (game >> 32) & 0xFF, - (game >> 24) & 0xFF, - (game >> 16) & 0xFF, - (game >> 8) & 0xFF, - game & 0xFF + (uint8_t)((game >> 56) & 0xFF), + (uint8_t)((game >> 48) & 0xFF), + (uint8_t)((game >> 40) & 0xFF), + (uint8_t)((game >> 32) & 0xFF), + (uint8_t)((game >> 24) & 0xFF), + (uint8_t)((game >> 16) & 0xFF), + (uint8_t)((game >> 8) & 0xFF), + (uint8_t)(game & 0xFF) }; memcpy(*buf + 1, seed8, sizeof(uint64_t)); game = ginfo->countdown; uint8_t count8[sizeof(uint64_t)] = { - (game >> 56) & 0xFF, - (game >> 48) & 0xFF, - (game >> 40) & 0xFF, - (game >> 32) & 0xFF, - (game >> 24) & 0xFF, - (game >> 16) & 0xFF, - (game >> 8) & 0xFF, - game & 0xFF + (uint8_t)((game >> 56) & 0xFF), + (uint8_t)((game >> 48) & 0xFF), + (uint8_t)((game >> 40) & 0xFF), + (uint8_t)((game >> 32) & 0xFF), + (uint8_t)((game >> 24) & 0xFF), + (uint8_t)((game >> 16) & 0xFF), + (uint8_t)((game >> 8) & 0xFF), + (uint8_t)(game & 0xFF) }; memcpy(*buf + sizeof(uint64_t) + 1, count8, sizeof(uint64_t)); game = ginfo->countdown; uint8_t gtype8[sizeof(uint64_t)] = { - (game >> 56) & 0xFF, - (game >> 48) & 0xFF, - (game >> 40) & 0xFF, - (game >> 32) & 0xFF, - (game >> 24) & 0xFF, - (game >> 16) & 0xFF, - (game >> 8) & 0xFF, - game & 0xFF + (uint8_t)((game >> 56) & 0xFF), + (uint8_t)((game >> 48) & 0xFF), + (uint8_t)((game >> 40) & 0xFF), + (uint8_t)((game >> 32) & 0xFF), + (uint8_t)((game >> 24) & 0xFF), + (uint8_t)((game >> 16) & 0xFF), + (uint8_t)((game >> 8) & 0xFF), + (uint8_t)(game & 0xFF) }; memcpy(*buf + sizeof(uint64_t) + 1, gtype8, sizeof(uint64_t)); @@ -219,46 +219,46 @@ void netprot::Serialize(GameInfo* ginfo, char* buf[], uint32_t* buflen) { } void netprot::Serialize(Chat* chat, char* buf[], uint32_t* buflen) { - *buf[0] = netprot::PACKET_TYPE::CHAT; + *buf[0] = (char)netprot::PACKET_TYPE::CHAT; uint64_t src = chat->src_id; uint8_t src8[sizeof(uint64_t)] = { - (src >> 56) & 0xFF, - (src >> 48) & 0xFF, - (src >> 40) & 0xFF, - (src >> 32) & 0xFF, - (src >> 24) & 0xFF, - (src >> 16) & 0xFF, - (src >> 8) & 0xFF, - src & 0xFF + (uint8_t)((src >> 56) & 0xFF), + (uint8_t)((src >> 48) & 0xFF), + (uint8_t)((src >> 40) & 0xFF), + (uint8_t)((src >> 32) & 0xFF), + (uint8_t)((src >> 24) & 0xFF), + (uint8_t)((src >> 16) & 0xFF), + (uint8_t)((src >> 8) & 0xFF), + (uint8_t)(src & 0xFF) }; memcpy(*buf + 1, src8, sizeof(uint64_t)); uint64_t dst = chat->dest_id; uint8_t dst8[sizeof(uint64_t)] = { - (dst >> 56) & 0xFF, - (dst >> 48) & 0xFF, - (dst >> 40) & 0xFF, - (dst >> 32) & 0xFF, - (dst >> 24) & 0xFF, - (dst >> 16) & 0xFF, - (dst >> 8) & 0xFF, - dst & 0xFF + (uint8_t)((dst >> 56) & 0xFF), + (uint8_t)((dst >> 48) & 0xFF), + (uint8_t)((dst >> 40) & 0xFF), + (uint8_t)((dst >> 32) & 0xFF), + (uint8_t)((dst >> 24) & 0xFF), + (uint8_t)((dst >> 16) & 0xFF), + (uint8_t)((dst >> 8) & 0xFF), + (uint8_t)(dst & 0xFF) }; memcpy(*buf + 1 + sizeof(uint64_t), dst8, sizeof(uint64_t)); uint64_t dstteam = chat->dest_id; uint8_t dstt8[sizeof(uint64_t)] = { - (dstteam >> 56) & 0xFF, - (dstteam >> 48) & 0xFF, - (dstteam >> 40) & 0xFF, - (dstteam >> 32) & 0xFF, - (dstteam >> 24) & 0xFF, - (dstteam >> 16) & 0xFF, - (dstteam >> 8) & 0xFF, - dstteam & 0xFF + (uint8_t)((dstteam >> 56) & 0xFF), + (uint8_t)((dstteam >> 48) & 0xFF), + (uint8_t)((dstteam >> 40) & 0xFF), + (uint8_t)((dstteam >> 32) & 0xFF), + (uint8_t)((dstteam >> 24) & 0xFF), + (uint8_t)((dstteam >> 16) & 0xFF), + (uint8_t)((dstteam >> 8) & 0xFF), + (uint8_t)(dstteam & 0xFF) }; memcpy(*buf + 1 + sizeof(uint64_t) * 2, dstt8, sizeof(uint64_t)); @@ -271,7 +271,7 @@ void netprot::Serialize(Chat* chat, char* buf[], uint32_t* buflen) { } void netprot::Serialize(ErrorLog* errlog, char* buf[], uint32_t* buflen) { - *buf[0] = netprot::PACKET_TYPE::ERRLOG; + *buf[0] = (char)netprot::PACKET_TYPE::ERRLOG; size_t messize = std::strlen(errlog->mess) + 1; @@ -313,13 +313,13 @@ bool netprot::Deserialize(Input* in, char* buf, const uint32_t buflen) { uint8_t keys = 0; memcpy(&keys, &buf[1 + sizeof(uint64_t) * 2], sizeof(uint8_t)); - in->keys.forward = keys && 0b10000000; - in->keys.backward = keys && 0b01000000; - in->keys.left = keys && 0b00100000; - in->keys.right = keys && 0b00010000; - in->keys.jump = keys && 0b00001000; - in->keys.shoot = keys && 0b00000100; - in->keys.block = keys && 0b00000010; + in->keys.forward = keys & 0b10000000; + in->keys.backward = keys & 0b01000000; + in->keys.left = keys & 0b00100000; + in->keys.right = keys & 0b00010000; + in->keys.jump = keys & 0b00001000; + in->keys.shoot = keys & 0b00000100; + in->keys.block = keys & 0b00000010; uint8_t subvec[3 * sizeof(uint32_t)] = { 0,0,0,0,0,0,0,0,0,0,0,0 }; memcpy(subvec, &buf[2 + sizeof(uint64_t) * 2], sizeof(uint8_t) * 12); @@ -555,12 +555,12 @@ bool netprot::Deserialize(ErrorLog* errlog, char* buf, const uint32_t buflen) { return true; } -netprot::PacketType netprot::getType(char* buf, const uint32_t buflen) { +netprot::PACKET_TYPE netprot::getType(char* buf, const uint32_t buflen) { if (buflen < 1 || - buf[0] >= netprot::PACKET_TYPE::LAST_PACK || - buf[0] <= netprot::PACKET_TYPE::ERR) + buf[0] >= (char)netprot::PACKET_TYPE::LAST_PACK || + buf[0] <= (char)netprot::PACKET_TYPE::ERR) return netprot::PACKET_TYPE::ERR; - return buf[0]; + return (netprot::PACKET_TYPE)buf[0]; } netprot::Packet netprot::getPack(char* buf, uint32_t buflen) { @@ -629,6 +629,10 @@ netprot::Packet netprot::getPack(char* buf, uint32_t buflen) { return pck; } +netprot::Packet netprot::getPack(netprot::Buffer* buf) { + return netprot::getPack(buf->ptr, buf->len); +} + bool netprot::emptyPack(netprot::Packet pck) { switch (pck.type) { case PACKET_TYPE::INPUT: diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index 97c880b..23e9c4a 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -1,26 +1,33 @@ #ifndef NETPROTOCOL_H__ #define NETPROTOCOL_H__ -#include "define.h" #include +#include "define.h" #include "vector3.h" -/* Protocole Particulier de Partie à Plusieurs Personnes (PPPPP) */ +/* Protocole Particulier de Partie a Plusieurs Personnes (PPPPP) */ // Packet: packet[0] = PacketType, packet[1..n-1] = {packet} namespace netprot { - typedef uint8_t PacketType; - enum PACKET_TYPE { + enum class PACKET_TYPE: uint8_t { ERR, INPUT, OUTPUT, SYNC, TEAMINF, SELFINF, PLAYINF, LOGINF, CHUNKMOD, PLAYERMOD, PICKUPMOD, GAMEINFO, ENDINFO , CHAT, ERRLOG, LAST_PACK }; + + struct Buffer { // Pour pouvoir rendre l'utilisation des buffers plus clean. + char* ptr = new char[BUFFER_LENGTH]; + int32_t len = BUFFER_LENGTH; - struct Packet { - void* ptr = nullptr; + ~Buffer() { delete[] ptr; } + void rstLen() { len = BUFFER_LENGTH; } + }; + + struct Packet { // Pour pouvoir recevoir les paquets du recv() sans avoir à les aiguiller dans la même thread. + void* ptr = nullptr; // Notez que le pointeur doit être supprimé séparément lorsqu'il n'est plus utile. PACKET_TYPE type; }; @@ -120,14 +127,19 @@ namespace netprot { bool Deserialize(Chat* chat, char* buf, const uint32_t buflen); // srv/cli bool Deserialize(ErrorLog* errlog, char* buf, const uint32_t buflen); // srv - PacketType getType(char* buf, uint32_t buflen); + PACKET_TYPE getType(char* buf, uint32_t buflen); Packet getPack(char* buf, uint32_t buflen); + Packet getPack(Buffer* buf); + bool emptyPack(Packet pck); template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); template void sendPackTo(SOCKET sock, T* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad); + template void sendPack(SOCKET sock, T* pack, Buffer* buf); + template void sendPackTo(SOCKET sock, T* pack, Buffer* buf, sockaddr_in* sockad); + template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen) { netprot::Serialize(pack, buf, buflen); @@ -143,6 +155,19 @@ namespace netprot { *buflen = BUFFER_LENGTH; } -}; + template + void sendPack(SOCKET sock, T* pack, Buffer* buf) { + netprot::Serialize(pack, buf->ptr, buf->len); + send(sock, buf->ptr, buf->len, 0); + buf->rstLen(); + } + template + void sendPackTo(SOCKET sock, T* pack, Buffer* buf, sockaddr_in* sockad) { + sockaddr_in addr = *sockad; + netprot::Serialize(pack, buf->ptr, buf->len); + sendto(sock, buf->ptr, buf->len, 0, (sockaddr*)&addr, sizeof(addr)); + buf->rstLen(); + } +}; #endif diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 600752a..90cce82 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -75,21 +75,20 @@ int Server::Ready() { uint32_t buflen = BUFFER_LENGTH, strbuflen = BUFFER_LENGTH; bool readystart = false; - do { - Log("Entrez la dur�e de la partie: ", false, false); - std::cin >> m_game.countdown; - std::cout << std::endl; + Log("Entrez la duree de la partie: ", false, false); + std::cin.getline(strbuf, BUFFER_LENGTH); + m_game.countdown = std::stoi(strbuf); } while (m_game.countdown < 1); do { Log("Entrez le seed de la partie: ", false, false); - std::cin >> m_game.seed; - std::cout << std::endl; + std::cin.getline(strbuf, BUFFER_LENGTH); + m_game.seed = std::stoi(strbuf); } while (m_game.seed < 1); do { Log("Entrez le nombre de joueurs: ", false, false); - std::cin >> nbrjoueurs; - std::cout << std::endl; + std::cin.getline(strbuf, BUFFER_LENGTH); + nbrjoueurs = std::stoi(strbuf); } while (nbrjoueurs > 0 && nbrjoueurs >= MAX_CONNECTIONS); m_game.gameType = 1; @@ -128,7 +127,7 @@ int Server::Ready() { log = (LoginInfo*)pck.ptr; log->sid = getUniqueId(); - log->tid = 0; + log->tid = 0; // TODO: À changer si on implemente un mode en equipe. Log(str.append(" Nom: ").append(log->name), false, false); str.clear(); @@ -154,8 +153,6 @@ int Server::Ready() { m_players[log->sid] = conn; - delete log; // le pck va se supprimer tout seul, mais le pointer du log qui vient de lui, non. - if (++nbrconn >= nbrjoueurs) readystart = true; } From 9ef44a3993de7b65c550ccc491cb3f9ae49d22bb Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Fri, 27 Oct 2023 09:51:40 -0400 Subject: [PATCH 28/65] Ev'ryday I'm bufferin'. --- SQCSim-common/netprotocol.h | 6 +-- SQCSim-srv/define.h | 3 -- SQCSim-srv/server.cpp | 100 +++++++++++++++++++++++++----------- SQCSim-srv/server.h | 23 ++++----- 4 files changed, 82 insertions(+), 50 deletions(-) diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index 23e9c4a..8cbcba5 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -20,7 +20,7 @@ namespace netprot { struct Buffer { // Pour pouvoir rendre l'utilisation des buffers plus clean. char* ptr = new char[BUFFER_LENGTH]; - int32_t len = BUFFER_LENGTH; + uint32_t len = BUFFER_LENGTH; ~Buffer() { delete[] ptr; } void rstLen() { len = BUFFER_LENGTH; } @@ -157,7 +157,7 @@ namespace netprot { template void sendPack(SOCKET sock, T* pack, Buffer* buf) { - netprot::Serialize(pack, buf->ptr, buf->len); + netprot::Serialize(pack, &buf->ptr, &buf->len); send(sock, buf->ptr, buf->len, 0); buf->rstLen(); } @@ -165,7 +165,7 @@ namespace netprot { template void sendPackTo(SOCKET sock, T* pack, Buffer* buf, sockaddr_in* sockad) { sockaddr_in addr = *sockad; - netprot::Serialize(pack, buf->ptr, buf->len); + netprot::Serialize(pack, &buf->ptr, &buf->len); sendto(sock, buf->ptr, buf->len, 0, (sockaddr*)&addr, sizeof(addr)); buf->rstLen(); } diff --git a/SQCSim-srv/define.h b/SQCSim-srv/define.h index eaf044e..3951791 100644 --- a/SQCSim-srv/define.h +++ b/SQCSim-srv/define.h @@ -15,7 +15,4 @@ #define strcpy strcpy_s #endif -typedef unsigned char LogDest; -enum LOG_DEST { CONSOLE, LOGFILE, LOG_LAST }; - #endif diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 90cce82..c47591a 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -1,6 +1,6 @@ #include "server.h" -Server::Server(LogDest log) { +Server::Server(LOG_DEST log) { m_log = log; if (log == LOG_DEST::LOGFILE) { m_logfile = std::ofstream("server.log", std::ofstream::out); @@ -70,25 +70,21 @@ int Server::Init() { int Server::Ready() { int nbrjoueurs = 0, nbrconn = 0; - char *buf = new char[BUFFER_LENGTH], - *strbuf = new char[BUFFER_LENGTH]; - uint32_t buflen = BUFFER_LENGTH, - strbuflen = BUFFER_LENGTH; bool readystart = false; do { Log("Entrez la duree de la partie: ", false, false); - std::cin.getline(strbuf, BUFFER_LENGTH); - m_game.countdown = std::stoi(strbuf); + std::cin.getline(m_buf.ptr, BUFFER_LENGTH); + m_game.countdown = std::stoi(m_buf.ptr); } while (m_game.countdown < 1); do { Log("Entrez le seed de la partie: ", false, false); - std::cin.getline(strbuf, BUFFER_LENGTH); - m_game.seed = std::stoi(strbuf); + std::cin.getline(m_buf.ptr, BUFFER_LENGTH); + m_game.seed = std::stoi(m_buf.ptr); } while (m_game.seed < 1); do { Log("Entrez le nombre de joueurs: ", false, false); - std::cin.getline(strbuf, BUFFER_LENGTH); - nbrjoueurs = std::stoi(strbuf); + std::cin.getline(m_buf.ptr, BUFFER_LENGTH); + nbrjoueurs = std::stoi(m_buf.ptr); } while (nbrjoueurs > 0 && nbrjoueurs >= MAX_CONNECTIONS); m_game.gameType = 1; @@ -111,13 +107,13 @@ int Server::Ready() { 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, strbuf, strbuflen)).append(": ").append(std::to_string(sockad.sin_port)); + 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, buf, buflen, 0) > 0) { + if (recv(sock, m_buf.ptr, m_buf.len, 0) > 0) { LoginInfo* log; PlayerInfo play; - Packet pck = getPack(buf, buflen); + Packet pck = getPack(&m_buf); if (pck.type != PACKET_TYPE::LOGINF) { Log("Paquet invalide.", true, false); if (pck.type != PACKET_TYPE::ERR) @@ -134,21 +130,19 @@ int Server::Ready() { Log(str.append(log->name).append(" SID: [").append(std::to_string(log->sid).append("]")), false, false); - sendPack(sock, log, &buf, &buflen); + sendPack(sock, log, &m_buf.ptr, &m_buf.len); play.id = getUniqueId(); strcpy(play.name, log->name); play.tid = log->tid; - sendPack(sock, &m_game, &buf, &buflen); + sendPack(sock, &m_game, &m_buf.ptr, &m_buf.len); Connection* conn = new Connection(sock, sockad, *log, play); for (auto& [key, player] : m_players) { - sendPack(player->getSock(), &play, &buf, &buflen); // Envoyer les infos de joueur distant aux joueurs d�j� connect�s - buflen = BUFFER_LENGTH; - sendPack(sock, player->getInfo(), &buf, &buflen); // et envoyer les infos des joueurs distants au nouveau joueur. - buflen = BUFFER_LENGTH; + sendPack(player->getSock(), &play, &m_buf); // Envoyer les infos de joueur distant aux joueurs d�j� connect�s + sendPack(sock, player->getInfo(), &m_buf); // et envoyer les infos des joueurs distants au nouveau joueur. } m_players[log->sid] = conn; @@ -158,14 +152,10 @@ int Server::Ready() { } } } - delete[] buf; - delete[] strbuf; return 0; } void Server::Run() { - char* buf = new char[BUFFER_LENGTH]; - uint32_t buflen = BUFFER_LENGTH; Input in; sockaddr_in sockad; addrlen_t socklen = sizeof(sockad); @@ -182,16 +172,64 @@ void Server::Run() { sync.ammo = 0; sync.timestamp = 0; sync.timer = m_game.countdown; - sendPackTo(conn->getSock(), &sync, &buf, &buflen, conn->getAddr()); + sendPackTo(conn->getSock(), &sync, &m_buf, conn->getAddr()); } while (true) { - if (recvfrom(m_sock_udp, buf, BUFFER_LENGTH, 0, (sockaddr*)&sockad, &socklen) > 0) { - Deserialize(&in, buf, buflen); - std::cout << "Id: " << in.sid << "\r\n" - << "Direction: { " << in.direction.x << ", " << in.direction.y << ", " << in.direction.z << " }" << "\r\n"; - } - std::cout << "!" << std::endl; + if (recvfrom(m_sock_udp, m_buf.ptr, m_buf.len, 0, (sockaddr*)&sockad, &socklen) > 0) { + Packet pck = getPack(&m_buf); + switch (pck.type) { + case netprot::PACKET_TYPE::ERR: + std::cout << "ERROR!" << std::endl; + break; + case netprot::PACKET_TYPE::INPUT: + std::cout << "INPUT!" << std::endl; + break; + case netprot::PACKET_TYPE::OUTPUT: + std::cout << "OUTPUT!" << std::endl; + break; + case netprot::PACKET_TYPE::SYNC: + std::cout << "SYNC!" << std::endl; + break; + case netprot::PACKET_TYPE::TEAMINF: + std::cout << "TEAMINF!" << std::endl; + break; + case netprot::PACKET_TYPE::SELFINF: + std::cout << "SELFINF!" << std::endl; + break; + case netprot::PACKET_TYPE::PLAYINF: + std::cout << "PLAYINF!" << std::endl; + break; + case netprot::PACKET_TYPE::LOGINF: + std::cout << "LOGINF!" << std::endl; + break; + case netprot::PACKET_TYPE::CHUNKMOD: + std::cout << "CHUNKMOD!" << std::endl; + break; + case netprot::PACKET_TYPE::PLAYERMOD: + std::cout << "PLAYERMOD!" << std::endl; + break; + case netprot::PACKET_TYPE::PICKUPMOD: + std::cout << "PICKUPMOD!" << std::endl; + break; + case netprot::PACKET_TYPE::GAMEINFO: + std::cout << "GAMEINFO!" << std::endl; + break; + case netprot::PACKET_TYPE::ENDINFO: + std::cout << "ENDINFO!" << std::endl; + break; + case netprot::PACKET_TYPE::CHAT: + std::cout << "CHAT!" << std::endl; + break; + case netprot::PACKET_TYPE::ERRLOG: + std::cout << "ERRLOG!" << std::endl; + break; + case netprot::PACKET_TYPE::LAST_PACK: + std::cout << "wtf?!" << std::endl; + break; + } + emptyPack(pck); + } } } diff --git a/SQCSim-srv/server.h b/SQCSim-srv/server.h index a66f885..33b529f 100644 --- a/SQCSim-srv/server.h +++ b/SQCSim-srv/server.h @@ -14,7 +14,9 @@ using namespace netprot; class Server { public: - Server(LogDest log = LOG_DEST::CONSOLE); + enum LOG_DEST: unsigned char { CONSOLE, LOGFILE, LOG_LAST }; + + Server(LOG_DEST log = LOG_DEST::CONSOLE); ~Server(); int Init(); @@ -22,18 +24,21 @@ public: void Run(); private: + #ifdef _WIN32 WSADATA m_wsaData; #endif SOCKET m_sock_udp = 0, m_sock_tcp = 0; - LogDest m_log; + LOG_DEST m_log; std::ofstream m_logfile; + Buffer m_buf; + std::map m_players; - std::map m_chatlog; + std::map m_chatlog; std::vector m_ids; - netprot::GameInfo m_game; + GameInfo m_game; World* m_world = nullptr; const bool m_manual_setup = SRV_MANUAL_SETUP; @@ -42,16 +47,8 @@ private: void Log(std::string str, bool is_error, bool is_fatal); void buildIdList(size_t size); - uint64_t getUniqueId(); - //template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); + uint64_t getUniqueId(); }; -//template -//void Server::sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen) { -// netprot::Serialize(pack, buf, buflen); -// send(sock, *buf, *buflen, 0); -// *buflen = BUFFER_LENGTH; -//} - #endif From 0dded0bc9b59db03b98f1bf66723343840f8be5a Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Fri, 27 Oct 2023 10:40:53 -0400 Subject: [PATCH 29/65] =?UTF-8?q?try=20c=C3=A2tch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-srv/server.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index c47591a..9d3178b 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -74,18 +74,35 @@ int Server::Ready() { do { Log("Entrez la duree de la partie: ", false, false); std::cin.getline(m_buf.ptr, BUFFER_LENGTH); - m_game.countdown = std::stoi(m_buf.ptr); + 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); - m_game.seed = std::stoi(m_buf.ptr); + 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); - nbrjoueurs = std::stoi(m_buf.ptr); - } while (nbrjoueurs > 0 && nbrjoueurs >= MAX_CONNECTIONS); + 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; From 6a8a5051679930e3c0fa4b511058b7d50b81f08a Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Fri, 27 Oct 2023 12:02:29 -0400 Subject: [PATCH 30/65] Yeehaw. --- SQCSim-common/netprotocol.cpp | 111 +++++++++++++++++++++++++++++++--- SQCSim-common/netprotocol.h | 2 + SQCSim-srv/server.cpp | 69 ++++++++------------- 3 files changed, 130 insertions(+), 52 deletions(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index 1053191..837180a 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -637,27 +637,122 @@ bool netprot::emptyPack(netprot::Packet pck) { switch (pck.type) { case PACKET_TYPE::INPUT: delete (Input*)pck.ptr; - break; + return true; case PACKET_TYPE::OUTPUT: delete (Output*)pck.ptr; - break; + return true; case PACKET_TYPE::SYNC: delete (Sync*)pck.ptr; - break; + return true; case PACKET_TYPE::CHAT: delete (Chat*)pck.ptr; - break; + return true; case PACKET_TYPE::GAMEINFO: delete (GameInfo*)pck.ptr; - break; + return true; case PACKET_TYPE::ERRLOG: delete (ErrorLog*)pck.ptr; - break; + return true; case PACKET_TYPE::LOGINF: delete (LoginInfo*)pck.ptr; - break; + return true; default: return false; } - return true; } + +template <> +void netprot::sendPack(SOCKET sock, Packet* pack, char** buf, uint32_t* buflen) { + switch (pack->type) { + case PACKET_TYPE::INPUT: + sendPack(sock, (Input*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::OUTPUT: + sendPack(sock, (Output*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::SYNC: + sendPack(sock, (Sync*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::TEAMINF: + sendPack(sock, (TeamInfo*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::PLAYINF: + sendPack(sock, (PlayerInfo*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::LOGINF: + sendPack(sock, (LoginInfo*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::CHUNKMOD: + //sendPack(sock, (ChunkMod*)pack->ptr, buf, buflen); + case PACKET_TYPE::PLAYERMOD: + //sendPack(sock, (PlayerMod*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::PICKUPMOD: + //sendPack(sock, (PickupMod*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::GAMEINFO: + sendPack(sock, (GameInfo*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::ENDINFO: + //sendPack(sock, (EndInfo*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::CHAT: + sendPack(sock, (Chat*)pack->ptr, buf, buflen); + case PACKET_TYPE::ERRLOG: + sendPack(sock, (ErrorLog*)pack->ptr, buf, buflen); + return; + case PACKET_TYPE::LAST_PACK: [[fallthrough]]; + case PACKET_TYPE::ERR: [[fallthrough]]; + default: + return; + } +} + +template <> +void netprot::sendPackTo(SOCKET sock, Packet* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad) { + switch (pack->type) { + case PACKET_TYPE::INPUT: + sendPackTo(sock, (Input*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::OUTPUT: + sendPackTo(sock, (Output*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::SYNC: + sendPackTo(sock, (Sync*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::TEAMINF: + sendPackTo(sock, (TeamInfo*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::PLAYINF: + sendPackTo(sock, (PlayerInfo*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::LOGINF: + sendPackTo(sock, (LoginInfo*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::CHUNKMOD: + //sendPackTo(sock, (ChunkMod*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::PLAYERMOD: + //sendPackTo(sock, (PlayerMod*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::PICKUPMOD: + //sendPackTo(sock, (PickupMod*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::GAMEINFO: + sendPackTo(sock, (GameInfo*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::ENDINFO: + //sendPackTo(sock, (EndInfo*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::CHAT: + sendPackTo(sock, (Chat*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::ERRLOG: + sendPackTo(sock, (ErrorLog*)pack->ptr, buf, buflen, sockad); + return; + case PACKET_TYPE::LAST_PACK: [[fallthrough]]; + case PACKET_TYPE::ERR: [[fallthrough]]; + default: + return; + } +} \ No newline at end of file diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index 8cbcba5..33f233e 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -134,6 +134,8 @@ namespace netprot { bool emptyPack(Packet pck); + Packet makePack(void* ptr, PACKET_TYPE type); // Pour pouvoir faire une liste de stock à supprimer sans avoir à en faire une pour chaque type. + template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); template void sendPackTo(SOCKET sock, T* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad); diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 9d3178b..a0c2a5e 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -196,52 +196,53 @@ void Server::Run() { if (recvfrom(m_sock_udp, m_buf.ptr, m_buf.len, 0, (sockaddr*)&sockad, &socklen) > 0) { Packet pck = getPack(&m_buf); switch (pck.type) { - case netprot::PACKET_TYPE::ERR: + using enum netprot::PACKET_TYPE; + case ERR: std::cout << "ERROR!" << std::endl; break; - case netprot::PACKET_TYPE::INPUT: + case INPUT: std::cout << "INPUT!" << std::endl; break; - case netprot::PACKET_TYPE::OUTPUT: + case OUTPUT: std::cout << "OUTPUT!" << std::endl; break; - case netprot::PACKET_TYPE::SYNC: + case SYNC: std::cout << "SYNC!" << std::endl; break; - case netprot::PACKET_TYPE::TEAMINF: + case TEAMINF: std::cout << "TEAMINF!" << std::endl; break; - case netprot::PACKET_TYPE::SELFINF: + case SELFINF: std::cout << "SELFINF!" << std::endl; break; - case netprot::PACKET_TYPE::PLAYINF: + case PLAYINF: std::cout << "PLAYINF!" << std::endl; break; - case netprot::PACKET_TYPE::LOGINF: + case LOGINF: std::cout << "LOGINF!" << std::endl; break; - case netprot::PACKET_TYPE::CHUNKMOD: + case CHUNKMOD: std::cout << "CHUNKMOD!" << std::endl; break; - case netprot::PACKET_TYPE::PLAYERMOD: + case PLAYERMOD: std::cout << "PLAYERMOD!" << std::endl; break; - case netprot::PACKET_TYPE::PICKUPMOD: + case PICKUPMOD: std::cout << "PICKUPMOD!" << std::endl; break; - case netprot::PACKET_TYPE::GAMEINFO: + case GAMEINFO: std::cout << "GAMEINFO!" << std::endl; break; - case netprot::PACKET_TYPE::ENDINFO: + case ENDINFO: std::cout << "ENDINFO!" << std::endl; break; - case netprot::PACKET_TYPE::CHAT: + case CHAT: std::cout << "CHAT!" << std::endl; break; - case netprot::PACKET_TYPE::ERRLOG: + case ERRLOG: std::cout << "ERRLOG!" << std::endl; break; - case netprot::PACKET_TYPE::LAST_PACK: + case LAST_PACK: std::cout << "wtf?!" << std::endl; break; } @@ -271,14 +272,14 @@ inline std::string Server::LogTimestamp() { 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: - default: - std::cout << LogTimestamp() << (is_fatal ? "FATAL " : "") << (is_error ? "ERROR " : "") << str << std::endl; - break; + 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) { @@ -315,23 +316,3 @@ uint64_t Server::getUniqueId() { m_ids.pop_back(); return id; } - -// Test serialize/deserialize: -/* - netprot::LoginInfo log, log2; - std::cout << "Nom? "; - std::cin.getline(log.name, 32); // NO! STD::CIN >> VARIABLE;! EVEEEEEERRRR!!! - log.sid = 12345; - char* buf = new char[150]; - uint32_t buflen = 150; - - netprot::Serialize(&log, &buf, &buflen); - - bool is_work = netprot::Deserialize(&log2, buf, buflen); - - std::string str; - str.append(is_work ? "Y " : "N ").append(log2.name).append(": ").append(std::to_string(log2.sid)); - - Log(str, false, false); -*/ - From da594fa3a25678cde6b0b5f971f3ab4f18f3dbc6 Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Fri, 27 Oct 2023 12:23:45 -0400 Subject: [PATCH 31/65] correction client --- SQCSim2021/cmake/CMakeLists.txt | 2 +- SQCSim2021/engine.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SQCSim2021/cmake/CMakeLists.txt b/SQCSim2021/cmake/CMakeLists.txt index fbe850f..f1aad75 100644 --- a/SQCSim2021/cmake/CMakeLists.txt +++ b/SQCSim2021/cmake/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.18.4) project(SQCSim-Client VERSION 0.8) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_STANDARD_REQUIRED True) set(CMAKE_BUILD_DIRECTORY "./build") diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index 7f4747d..b0d8905 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -756,7 +756,10 @@ void Engine::Render(float elapsedTime) { else if (!m_whoosh[x]) { m_whoosh[x] = m_audio.Create3DAudioObj(m_whoosh[x], AUDIO_PATH "noise.wav", m_bullets[x]->getPos(), m_bullets[x]->getVel(), true, (m_bullets[x]->getPos() - m_player.GetPosition()).Length()); } - else m_audio.Render3DAudioObj(m_whoosh[x], m_bullets[x]->getPos(), m_bullets[x]->getVel(), 5 - (m_bullets[x]->getPos() - m_player.GetPosition()).Length()); + else { + Vector3f pos = m_bullets[x]->getPos(), vel = m_bullets[x]->getVel(); + m_audio.Render3DAudioObj(m_whoosh[x], pos, vel, 5 - (m_bullets[x]->getPos() - m_player.GetPosition()).Length()); + } } } From f7b7a7f14d0eb5aab3b32c8f7f792a50ff58f247 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 13:27:00 -0400 Subject: [PATCH 32/65] Changements C++20 et retour audio --- SQCSim-common/SQCSim-common.vcxproj | 5 ++++- SQCSim-common/bullet.cpp | 1 - SQCSim-common/define.h | 2 ++ SQCSim-srv/SQCSim-srv.vcxproj | 5 ++++- SQCSim2021/SQCSim2021.vcxproj | 8 ++++---- SQCSim2021/engine.cpp | 15 ++++++++++++--- 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/SQCSim-common/SQCSim-common.vcxproj b/SQCSim-common/SQCSim-common.vcxproj index 991cd1d..4cebfaf 100644 --- a/SQCSim-common/SQCSim-common.vcxproj +++ b/SQCSim-common/SQCSim-common.vcxproj @@ -76,6 +76,7 @@ true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp20 Console @@ -90,6 +91,7 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp20 Console @@ -104,6 +106,7 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp20 Console @@ -118,7 +121,7 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - stdcpp17 + stdcpp20 Console diff --git a/SQCSim-common/bullet.cpp b/SQCSim-common/bullet.cpp index 873879d..d5e7f91 100644 --- a/SQCSim-common/bullet.cpp +++ b/SQCSim-common/bullet.cpp @@ -20,7 +20,6 @@ bool Bullet::Update(World* world, float elapsedtime, int perframe) { } else if ((m_currentpos - m_startpos).Length() > VIEW_DISTANCE) return true; } - return false; } diff --git a/SQCSim-common/define.h b/SQCSim-common/define.h index 8060703..7fd0139 100644 --- a/SQCSim-common/define.h +++ b/SQCSim-common/define.h @@ -31,6 +31,8 @@ #define TEXTURE_SIZE 512 #define MAX_BULLETS 512 +#define BULLET_TIME .1 + typedef uint8_t BlockType; enum BLOCK_TYPE { BTYPE_AIR, BTYPE_DIRT, BTYPE_GRASS, BTYPE_METAL, BTYPE_ICE, BTYPE_LAST }; typedef uint64_t Timestamp; diff --git a/SQCSim-srv/SQCSim-srv.vcxproj b/SQCSim-srv/SQCSim-srv.vcxproj index 525fc03..d080276 100644 --- a/SQCSim-srv/SQCSim-srv.vcxproj +++ b/SQCSim-srv/SQCSim-srv.vcxproj @@ -76,6 +76,7 @@ true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp20 Console @@ -90,7 +91,7 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - Default + stdcpp20 Console @@ -105,6 +106,7 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp20 Console @@ -119,6 +121,7 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp20 Console diff --git a/SQCSim2021/SQCSim2021.vcxproj b/SQCSim2021/SQCSim2021.vcxproj index d120d7b..9fd58a9 100644 --- a/SQCSim2021/SQCSim2021.vcxproj +++ b/SQCSim2021/SQCSim2021.vcxproj @@ -130,7 +130,7 @@ Level3 Disabled WIN32;_DEBUG;_CONSOLE;NOMINMAX;%(PreprocessorDefinitions) - stdcpp17 + stdcpp20 Console @@ -145,7 +145,7 @@ Level3 Disabled WIN32;_DEBUG;_CONSOLE;NOMINMAX;%(PreprocessorDefinitions) - stdcpp17 + stdcpp20 Console @@ -162,7 +162,7 @@ true true WIN32;NDEBUG;_CONSOLE;NOMINMAX;%(PreprocessorDefinitions) - stdcpp17 + stdcpp20 AnySuitable Speed Fast @@ -184,7 +184,7 @@ true true WIN32;NDEBUG;_CONSOLE;NOMINMAX;%(PreprocessorDefinitions) - stdcpp17 + stdcpp20 AnySuitable Speed Fast diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index b0d8905..40de73a 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -733,10 +733,18 @@ void Engine::Render(float elapsedTime) { m_bullets[x] = new Bullet(m_player.GetPOV() + m_player.GetDirection(), m_player.GetDirection()); break; } - else if (x == MAX_BULLETS - 1) { // S'il y a pas d'espace dans l'array, prendre la place de la premi�re balle de l'array. + else if (x == MAX_BULLETS - 1) { // S'il y a pas d'espace dans l'array, prendre la place de la première balle de l'array. m_bullets[0]->~Bullet(); m_bullets[0] = new Bullet(m_player.GetPOV() + m_player.GetDirection(), m_player.GetDirection()); } + bulletTime = BULLET_TIME; + m_audio.Create3DAudioObj(m_powpow, AUDIO_PATH "pow.wav", m_player.GetPOV(), m_player.GetDirection() * 10, false, .5f); + if (m_flash) { // Coupe le rendering et affiche un frame blanc, pour simuler un flash. + glClearColor(.8f, .8f, .8f, 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + glClearColor(0.f, 0.f, 0.f, 1.f); + return; + } } } else if (m_mouseR) @@ -744,7 +752,7 @@ void Engine::Render(float elapsedTime) { for (int x = 0; x < MAX_BULLETS; ++x) { // Array de bullets en jeu. if (m_bullets[x]) { - for (int b = 0; b < BULLET_UPDATES_PER_FRAME; ++b) + for (int b = 0; b < BULLET_UPDATES_PER_FRAME; ++b) { if (m_bullets[x]->Update(&m_world, elapsedTime, BULLET_UPDATES_PER_FRAME)) { m_bullets[x]->~Bullet(); if (m_whoosh[x]) @@ -758,8 +766,9 @@ void Engine::Render(float elapsedTime) { } else { Vector3f pos = m_bullets[x]->getPos(), vel = m_bullets[x]->getVel(); - m_audio.Render3DAudioObj(m_whoosh[x], pos, vel, 5 - (m_bullets[x]->getPos() - m_player.GetPosition()).Length()); + m_audio.Render3DAudioObj(m_whoosh[x], pos, vel, 5 - (m_bullets[x]->getPos() - m_player.GetPosition()).Length()); } + } } } From 1c7523558b461052ecc98fa4d8374a6709d399f8 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 13:39:18 -0400 Subject: [PATCH 33/65] =?UTF-8?q?Chang=C3=A9=20de=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-common/define.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQCSim-common/define.h b/SQCSim-common/define.h index 7fd0139..bbeaaff 100644 --- a/SQCSim-common/define.h +++ b/SQCSim-common/define.h @@ -4,8 +4,8 @@ #include #include -#define SRV_PORT 1025 -#define CLI_PORT 1026 +#define SRV_PORT 10000 +#define CLI_PORT 10001 #define BUFFER_LENGTH 150 From ae201846b14bbb027ca63f482020ccba22d22d78 Mon Sep 17 00:00:00 2001 From: Claudel-D-Roy <112507354+Claudel-D-Roy@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:54:37 -0400 Subject: [PATCH 34/65] push de correction --- SQCSim2021/remoteplayer.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/SQCSim2021/remoteplayer.cpp b/SQCSim2021/remoteplayer.cpp index 1bf39c9..6ff160c 100644 --- a/SQCSim2021/remoteplayer.cpp +++ b/SQCSim2021/remoteplayer.cpp @@ -25,7 +25,8 @@ void RemotePlayer::Feed(const netprot::Output out) { Vector3f positionDelta = current.position - previous.position; m_position = current.position + positionDelta; m_direction = current.direction; - m_team_id = current.id; + //Rajouter un get ID public dans la classe player + //m_team_id = current.id; } @@ -36,27 +37,27 @@ void RemotePlayer::Feed(const netprot::Output out) { } if (current.states.shooting) { - true; + //true; m_animstate = Anim::SHOOTING; } else if (current.states.jumping) { - true; + //true; m_animstate = Anim::JUMPING; } else if (current.states.dead) { - true; + //true; m_animstate = Anim::DEAD; } else if(current.states.powerup){ - true; + //true; m_animstate = Anim::POWERUP; } else if (current.states.still) { - true; + //true; m_animstate = Anim::STILL; } else if (current.states.running) { - true; + //true; m_animstate = Anim::RUNNING; } From 57d60e02a76eb1332816abbb837a58d0cf32f854 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 13:55:15 -0400 Subject: [PATCH 35/65] getId() --- SQCSim-common/player.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQCSim-common/player.cpp b/SQCSim-common/player.cpp index 10ec32c..57c2259 100644 --- a/SQCSim-common/player.cpp +++ b/SQCSim-common/player.cpp @@ -209,6 +209,8 @@ void Player::Teleport(int& x, int& z) { m_position.z -= z * CHUNK_SIZE_Z; } +uint64_t Player::getId() const { return id; } + Vector3f Player::InterpolatePosition(const Vector3f& vec1, const Vector3f& vec2, const Timestamp& tim1, const Timestamp& tim2, const Timestamp& now) { return Vector3f(); } From 889884490b84b7c652b33a09fc19257e45d179e5 Mon Sep 17 00:00:00 2001 From: Claudel-D-Roy <112507354+Claudel-D-Roy@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:56:48 -0400 Subject: [PATCH 36/65] push avant merge --- SQCSim2021/remoteplayer.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SQCSim2021/remoteplayer.cpp b/SQCSim2021/remoteplayer.cpp index 6ff160c..1d2de20 100644 --- a/SQCSim2021/remoteplayer.cpp +++ b/SQCSim2021/remoteplayer.cpp @@ -24,9 +24,7 @@ void RemotePlayer::Feed(const netprot::Output out) { { Vector3f positionDelta = current.position - previous.position; m_position = current.position + positionDelta; - m_direction = current.direction; - //Rajouter un get ID public dans la classe player - //m_team_id = current.id; + m_direction = current.direction; } From 8970bc33a140f8bcf8f37c26cd8a739e4a699741 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 14:08:06 -0400 Subject: [PATCH 37/65] =?UTF-8?q?R=C3=A9p=C3=A2r=C3=A2ge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-common/player.h | 2 ++ SQCSim2021/engine.cpp | 4 ++-- SQCSim2021/engine.h | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/SQCSim-common/player.h b/SQCSim-common/player.h index d592613..757528b 100644 --- a/SQCSim-common/player.h +++ b/SQCSim-common/player.h @@ -28,6 +28,8 @@ public: float GetHP() const; void Teleport(int& x, int& z); + uint64_t getId() const; + protected: Vector3f m_position; Vector3f m_velocity; diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index 40de73a..5fdc85e 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -772,9 +772,9 @@ void Engine::Render(float elapsedTime) { } } - m_wrenderer.RenderWorld(&m_world, m_renderCount, m_player.GetPosition(), m_player.GetDirection(), all, m_shader01, m_textureAtlas); + m_renderer.RenderWorld(&m_world, m_renderCount, m_player.GetPosition(), m_player.GetDirection(), all, m_shader01, m_textureAtlas); m_world.Update(m_bullets, m_player.GetPosition(), m_blockinfo); - m_wrenderer.UpdateWorld(&m_world, m_player.GetPosition(), m_blockinfo); + m_renderer.UpdateMesh(&m_world, m_player.GetPosition(), m_blockinfo); if (m_isSkybox) m_skybox.Render(skybox); diff --git a/SQCSim2021/engine.h b/SQCSim2021/engine.h index 6e41b9f..09a2314 100644 --- a/SQCSim2021/engine.h +++ b/SQCSim2021/engine.h @@ -19,6 +19,7 @@ #include "textureatlas.h" #include "connector.h" #include "renderer.h" +#include "remoteplayer.h" class Engine : public OpenglContext { public: @@ -79,8 +80,10 @@ private: Bullet* m_bullets[MAX_BULLETS]; + std::map m_players; + //Menu - enum class GameState { MAIN_MENU, OPTIONS, QUIT, NEWG, PLAY }; + enum class GameState: uint8_t { MAIN_MENU, OPTIONS, QUIT, NEWG, PLAY }; GameState m_gamestate = GameState::MAIN_MENU; Texture MenuTitleTexture, MenuBGTexture, From 61629b3c8e7edad0e9a60682b1b901058fb80ba8 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 14:37:53 -0400 Subject: [PATCH 38/65] RemotePlayer --- SQCSim-srv/server.cpp | 2 +- SQCSim2021/connector.cpp | 22 +++++++++++++++++----- SQCSim2021/connector.h | 5 ++++- SQCSim2021/engine.cpp | 7 +++++-- SQCSim2021/remoteplayer.cpp | 2 +- SQCSim2021/remoteplayer.h | 3 ++- 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index a0c2a5e..342a139 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -180,7 +180,7 @@ void Server::Run() { Log("Debut de la partie...", false, false); for (auto& [key, conn]: m_players) { // Creation des instances de joueurs et premier sync. - conn->player = new Player(Vector3f(64., 128., 64.)); + conn->player = new Player(Vector3f(32.5f, CHUNK_SIZE_Y + 1.8f, 32.5f)); Player *player = conn->player; Sync sync; sync.position = player->GetPosition(); diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index 7709a76..e5cecd6 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -68,9 +68,10 @@ int Connector::Connect(const char* srv_addr, std::string name) { delete[] buf; buf = new char[150] {0}; buflen = 150; - int rpack = 0; + bool ready = false; int errors = 0; - while (rpack < 2) { + netprot::PlayerInfo pinfo{}; + while (!ready) { recv(m_sock_tcp, buf, buflen, 0); switch (netprot::getType(buf, buflen)) { @@ -79,17 +80,26 @@ int Connector::Connect(const char* srv_addr, std::string name) { std::cout << "Packet LoginInfo invalide." << std::endl; return 2; } - ++rpack; break; case netprot::PACKET_TYPE::GAMEINFO: if (!netprot::Deserialize(&m_gameinfo, buf, buflen)) { std::cout << "Packet GameInfo invalide." << std::endl; return 3; } - ++rpack; break; case netprot::PACKET_TYPE::PLAYINF: - // TODO: Populer un remotePlayer avec ça. + if (!netprot::Deserialize(&pinfo, buf, buflen)) { + std::cout << "Packet PlayerInfo invalide." << std::endl; + return 3; + } + m_players[pinfo.id] = pinfo; + break; + case netprot::PACKET_TYPE::SYNC: + if (!netprot::Deserialize(&m_origin, buf, buflen)) { + std::cout << "Packet Sync invalide." << std::endl; + return 3; + } + ready = true; break; default: ++errors; @@ -105,3 +115,5 @@ int Connector::Connect(const char* srv_addr, std::string name) { uint64_t Connector::getId() const { return m_loginfo.sid; } unsigned int Connector::getSeed() const { return m_gameinfo.seed; } + +netprot::Sync Connector::getOrigin() const { return m_origin; } diff --git a/SQCSim2021/connector.h b/SQCSim2021/connector.h index 5dd6db4..4fd883e 100644 --- a/SQCSim2021/connector.h +++ b/SQCSim2021/connector.h @@ -14,6 +14,8 @@ public: int Connect(const char* srv_addr, std::string name); uint64_t getId() const; unsigned int getSeed() const; + netprot::Sync getOrigin() const; + //void SendInput(); //int Sync(); @@ -24,16 +26,17 @@ public: // void updateRemotePlayers(std::map rplayers); + std::map m_players; private: #ifdef _WIN32 WSADATA m_wsaData; #endif std::map m_inputmanifest; - std::map m_players; std::map m_teams; netprot::LoginInfo m_loginfo; netprot::GameInfo m_gameinfo; + netprot::Sync m_origin; }; #endif diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index 5fdc85e..ae6b4b4 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -265,7 +265,6 @@ void Engine::Init() { m_whoosh[x] = nullptr; } - if (true) { std::cout << "Jouer en ligne? [o/N] "; std::cin >> ch; std::cout << std::endl; @@ -284,6 +283,11 @@ void Engine::Init() { // setup jeu en reseau. std::cout << "ID recu du serveur: " << std::to_string(m_conn.getId()) << "!" << std::endl; std::cout << "Seed recu du serveur: " << std::to_string(m_conn.getSeed()) << "!" << std::endl; + m_player = Player(m_conn.getOrigin().position); + + for (auto& [key, player] : m_conn.m_players) + m_players[key] = new RemotePlayer(player); + seed = m_conn.getSeed(); m_networkgame = true; } @@ -291,7 +295,6 @@ void Engine::Init() { } else std::cout << "Erreur de creation de socket." << std::endl; } - } m_world.SetSeed(seed); diff --git a/SQCSim2021/remoteplayer.cpp b/SQCSim2021/remoteplayer.cpp index 1d2de20..7769ddc 100644 --- a/SQCSim2021/remoteplayer.cpp +++ b/SQCSim2021/remoteplayer.cpp @@ -3,7 +3,7 @@ #include -RemotePlayer::RemotePlayer() : Player(Vector3f(0, 0, 0), 0, 0), m_aminacc(0.0f), m_animstate(Anim::STILL), m_team_id(0), current(), previous() { +RemotePlayer::RemotePlayer(netprot::PlayerInfo pinfo) : Player(Vector3f(0, 0, 0), 0, 0), m_pinfo(pinfo), m_aminacc(0.0f), m_animstate(Anim::STILL), m_team_id(0), current(), previous() { } diff --git a/SQCSim2021/remoteplayer.h b/SQCSim2021/remoteplayer.h index 70b585b..e8fb8a5 100644 --- a/SQCSim2021/remoteplayer.h +++ b/SQCSim2021/remoteplayer.h @@ -10,13 +10,14 @@ class RemotePlayer : public Player { public: enum Anim { STILL = 1, RUNNING = 2, JUMPING = 4, SHOOTING = 8, POWERUP = 16, DEAD = 32 }; - RemotePlayer(); + RemotePlayer(netprot::PlayerInfo pinfo); void Init(); void Feed(const netprot::Output out); private: netprot::Output current, previous; + netprot::PlayerInfo m_pinfo; float m_aminacc; Anim m_animstate; uint64_t m_team_id; From 49c9576757bda7e0c8acd9c3028dfefed90f05bf Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 15:07:38 -0400 Subject: [PATCH 39/65] Ser/deser Sync --- SQCSim-common/netprotocol.cpp | 142 +++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index 837180a..f324bd0 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -64,13 +64,95 @@ void netprot::Serialize(Input* in, char* buf[], uint32_t* buflen) { void netprot::Serialize(Output* out, char* buf[], uint32_t* buflen) { *buf[0] = (char)netprot::PACKET_TYPE::OUTPUT; + uint64_t time = out->timestamp; + uint8_t time8[sizeof(uint64_t)] = { (uint8_t)((time >> 56) & 0xFF), + (uint8_t)((time >> 48) & 0xFF), + (uint8_t)((time >> 40) & 0xFF), + (uint8_t)((time >> 32) & 0xFF), + (uint8_t)((time >> 24) & 0xFF), + (uint8_t)((time >> 16) & 0xFF), + (uint8_t)((time >> 8) & 0xFF), + (uint8_t)(time & 0xFF) }; + memcpy(*buf + 1, time8, sizeof(uint64_t)); + + uint64_t sid = out->id; + uint8_t sid8[sizeof(uint64_t)] = { (uint8_t)((sid >> 56) & 0xFF), + (uint8_t)((sid >> 48) & 0xFF), + (uint8_t)((sid >> 40) & 0xFF), + (uint8_t)((sid >> 32) & 0xFF), + (uint8_t)((sid >> 24) & 0xFF), + (uint8_t)((sid >> 16) & 0xFF), + (uint8_t)((sid >> 8) & 0xFF), + (uint8_t)(sid & 0xFF) }; + + memcpy(*buf + sizeof(uint64_t) + 1, sid8, sizeof(uint64_t)); + + // TODO: Finir ca. } void netprot::Serialize(Sync* sync, char* buf[], uint32_t* buflen) { *buf[0] = (char)netprot::PACKET_TYPE::SYNC; + uint64_t time = sync->timestamp; + uint8_t stamp8[sizeof(uint64_t)] = { (uint8_t)((time >> 56) & 0xFF), + (uint8_t)((time >> 48) & 0xFF), + (uint8_t)((time >> 40) & 0xFF), + (uint8_t)((time >> 32) & 0xFF), + (uint8_t)((time >> 24) & 0xFF), + (uint8_t)((time >> 16) & 0xFF), + (uint8_t)((time >> 8) & 0xFF), + (uint8_t)(time & 0xFF) }; + memcpy(*buf + 1, stamp8, sizeof(uint64_t)); + + uint64_t sid = sync->sid; + uint8_t sid8[sizeof(uint64_t)] = { (uint8_t)((sid >> 56) & 0xFF), + (uint8_t)((sid >> 48) & 0xFF), + (uint8_t)((sid >> 40) & 0xFF), + (uint8_t)((sid >> 32) & 0xFF), + (uint8_t)((sid >> 24) & 0xFF), + (uint8_t)((sid >> 16) & 0xFF), + (uint8_t)((sid >> 8) & 0xFF), + (uint8_t)(sid & 0xFF) }; + memcpy(*buf + sizeof(uint64_t) + 1, sid8, sizeof(uint64_t)); + + uint32_t timer = sync->timer; + uint8_t time8[sizeof(uint32_t)] = {(uint8_t)((timer >> 24) & 0xFF), + (uint8_t)((timer >> 16) & 0xFF), + (uint8_t)((timer >> 8) & 0xFF), + (uint8_t)(timer & 0xFF) }; + + memcpy(*buf + sizeof(uint64_t) * 2 + 1, time8, sizeof(uint32_t)); + + uint16_t ammo = sync->ammo; + uint8_t ammo8[sizeof(uint16_t)] = { (uint8_t)((ammo >> 8) & 0xFF), + (uint8_t)(ammo & 0xFF) }; + + memcpy(*buf + sizeof(uint64_t) * 2 + sizeof(uint32_t) + 1, ammo8, sizeof(uint16_t)); + + memcpy(*buf + sizeof(uint64_t) * 2 + sizeof(uint32_t) + sizeof(uint16_t) + 1, &sync->hp, sizeof(uint8_t)); + + uint32_t vec[3]; + memcpy(vec, &sync->position, sizeof(Vector3f)); // Pour d�naturer les floats. + + uint8_t vec8[3 * sizeof(uint32_t)] = { + (uint8_t)((vec[0] >> 24) & 0xFF), + (uint8_t)((vec[0] >> 16) & 0xFF), + (uint8_t)((vec[0] >> 8) & 0xFF), + (uint8_t)(vec[0] & 0xFF), + (uint8_t)((vec[1] >> 24) & 0xFF), + (uint8_t)((vec[1] >> 16) & 0xFF), + (uint8_t)((vec[1] >> 8) & 0xFF), + (uint8_t)(vec[1] & 0xFF), + (uint8_t)((vec[2] >> 24) & 0xFF), + (uint8_t)((vec[2] >> 16) & 0xFF), + (uint8_t)((vec[2] >> 8) & 0xFF), + (uint8_t)(vec[2] & 0xFF) }; + + memcpy(*buf + sizeof(uint64_t) * 2 + sizeof(uint32_t) + sizeof(uint16_t) + 2, vec8, sizeof(uint32_t) * 3); + + *buflen = sizeof(uint64_t) * 2 + sizeof(uint32_t) * 4 + sizeof(uint16_t) + 2; } void netprot::Serialize(TeamInfo* tinfo, char* buf[], uint32_t* buflen) { @@ -347,7 +429,65 @@ bool netprot::Deserialize(Output* out, char* buf, const uint32_t buflen) { } bool netprot::Deserialize(Sync* sync, char* buf, const uint32_t buflen) { - return false; + if (buflen <= sizeof(Sync)) + return false; + + uint8_t diff[sizeof(uint64_t)] = { 0,0,0,0,0,0,0,0 }; + memcpy(diff, &buf[1], sizeof(uint64_t)); + sync->timestamp = + (uint64_t)diff[0] << 56 | + (uint64_t)diff[1] << 48 | + (uint64_t)diff[2] << 40 | + (uint64_t)diff[3] << 32 | + (uint64_t)diff[4] << 24 | + (uint64_t)diff[5] << 16 | + (uint64_t)diff[6] << 8 | + (uint64_t)diff[7]; + + memcpy(diff, &buf[1 + sizeof(uint64_t)], sizeof(uint64_t)); + sync->sid = + (uint64_t)diff[0] << 56 | + (uint64_t)diff[1] << 48 | + (uint64_t)diff[2] << 40 | + (uint64_t)diff[3] << 32 | + (uint64_t)diff[4] << 24 | + (uint64_t)diff[5] << 16 | + (uint64_t)diff[6] << 8 | + (uint64_t)diff[7]; + + memcpy(diff, &buf[1 + sizeof(uint64_t) * 2], sizeof(uint32_t)); + sync->timer = + (uint32_t)diff[0] << 24 | + (uint32_t)diff[1] << 16 | + (uint32_t)diff[2] << 8 | + (uint32_t)diff[3]; + + memcpy(diff, &buf[1 + sizeof(uint64_t) * 2 + sizeof(uint32_t)], sizeof(uint16_t)); + sync->ammo = + (uint16_t)diff[0] << 8 | + (uint16_t)diff[1]; + + memcpy(&sync->hp, &buf[1 + sizeof(uint64_t) * 2 + sizeof(uint32_t) + sizeof(uint16_t)], sizeof(uint8_t)); + + uint8_t subvec[3 * sizeof(uint32_t)] = { 0,0,0,0,0,0,0,0,0,0,0,0 }; + memcpy(subvec, &buf[2 + sizeof(uint64_t) * 2 + sizeof(uint32_t) + sizeof(uint16_t)], sizeof(uint8_t) * 12); + uint32_t vec[3] = { + (uint32_t)subvec[0] << 24 | + (uint32_t)subvec[1] << 16 | + (uint32_t)subvec[2] << 8 | + (uint32_t)subvec[3], + (uint32_t)subvec[4] << 24 | + (uint32_t)subvec[5] << 16 | + (uint32_t)subvec[6] << 8 | + (uint32_t)subvec[7], + (uint32_t)subvec[8] << 24 | + (uint32_t)subvec[9] << 16 | + (uint32_t)subvec[10] << 8 | + (uint32_t)subvec[11] }; + + memcpy(&sync->position, vec, sizeof(uint32_t) * 3); + + return true; } bool netprot::Deserialize(TeamInfo* tinfo, char* buf, const uint32_t buflen) { From f0bf7d94cef80929cd3ae453cb8ff659cc4d2f24 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 15:19:44 -0400 Subject: [PATCH 40/65] pas trop loin --- SQCSim-srv/server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 342a139..1e5f0bc 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -180,7 +180,7 @@ void Server::Run() { Log("Debut de la partie...", false, false); for (auto& [key, conn]: m_players) { // Creation des instances de joueurs et premier sync. - conn->player = new Player(Vector3f(32.5f, CHUNK_SIZE_Y + 1.8f, 32.5f)); + conn->player = new Player(Vector3f(8.5f, CHUNK_SIZE_Y + 1.8f, 8.5f)); Player *player = conn->player; Sync sync; sync.position = player->GetPosition(); From e2e66f3b6c22c478437f845fd86bf7ae4ebe2dcb Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 15:44:33 -0400 Subject: [PATCH 41/65] =?UTF-8?q?=C3=A0=20tester?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-srv/server.cpp | 2 +- SQCSim2021/connector.cpp | 80 ++++++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 1e5f0bc..045052f 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -189,7 +189,7 @@ void Server::Run() { sync.ammo = 0; sync.timestamp = 0; sync.timer = m_game.countdown; - sendPackTo(conn->getSock(), &sync, &m_buf, conn->getAddr()); + sendPack(conn->getSock(), &sync, &m_buf); } while (true) { diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index e5cecd6..c7451e7 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -56,8 +56,8 @@ int Connector::Connect(const char* srv_addr, std::string name) { std::cout << "Échec de la connexion." << std::endl; } - char* buf = new char[150]; - uint32_t buflen = 150; + char* buf = new char[1500]; + uint32_t buflen = 1500; netprot::LoginInfo log; memcpy(&log.name, name.c_str(), name.size() + 1); @@ -72,42 +72,52 @@ int Connector::Connect(const char* srv_addr, std::string name) { int errors = 0; netprot::PlayerInfo pinfo{}; while (!ready) { - recv(m_sock_tcp, buf, buflen, 0); + uint32_t bytes = recv(m_sock_tcp, buf, buflen, 0); + uint32_t curr = 0; + char* currbuf = buf; - switch (netprot::getType(buf, buflen)) { - case netprot::PACKET_TYPE::LOGINF: - if (!netprot::Deserialize(&m_loginfo, buf, buflen)) { - std::cout << "Packet LoginInfo invalide." << std::endl; - return 2; + while (bytes > 0) { + switch (netprot::getType(currbuf, buflen)) { + case netprot::PACKET_TYPE::LOGINF: + if (!netprot::Deserialize(&m_loginfo, currbuf, buflen)) { + std::cout << "Packet LoginInfo invalide." << std::endl; + return 2; + } + curr = sizeof(netprot::LoginInfo); + break; + case netprot::PACKET_TYPE::GAMEINFO: + if (!netprot::Deserialize(&m_gameinfo, currbuf, buflen)) { + std::cout << "Packet GameInfo invalide." << std::endl; + return 3; + } + curr = sizeof(netprot::GameInfo); + break; + case netprot::PACKET_TYPE::PLAYINF: + if (!netprot::Deserialize(&pinfo, currbuf, buflen)) { + std::cout << "Packet PlayerInfo invalide." << std::endl; + return 3; + } + m_players[pinfo.id] = pinfo; + curr = sizeof(netprot::PlayerInfo); + break; + case netprot::PACKET_TYPE::SYNC: + if (!netprot::Deserialize(&m_origin, currbuf, buflen)) { + std::cout << "Packet Sync invalide." << std::endl; + return 3; + } + curr = sizeof(netprot::Sync); + ready = true; + break; + default: + ++errors; + //std::cout << "Packet invalide." << std::endl; + break; } - break; - case netprot::PACKET_TYPE::GAMEINFO: - if (!netprot::Deserialize(&m_gameinfo, buf, buflen)) { - std::cout << "Packet GameInfo invalide." << std::endl; - return 3; - } - break; - case netprot::PACKET_TYPE::PLAYINF: - if (!netprot::Deserialize(&pinfo, buf, buflen)) { - std::cout << "Packet PlayerInfo invalide." << std::endl; - return 3; - } - m_players[pinfo.id] = pinfo; - break; - case netprot::PACKET_TYPE::SYNC: - if (!netprot::Deserialize(&m_origin, buf, buflen)) { - std::cout << "Packet Sync invalide." << std::endl; - return 3; - } - ready = true; - break; - default: - ++errors; - //std::cout << "Packet invalide." << std::endl; - break; + currbuf = &currbuf[curr]; + bytes -= curr; + if (errors > 100) + return 1; } - if (errors > 100) - return 1; } return 0; } From 76a7d0218da4d7a07570cab38fb32b327edb0740 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 15:47:34 -0400 Subject: [PATCH 42/65] Who knows?! --- SQCSim2021/connector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index c7451e7..b76127e 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -113,7 +113,7 @@ int Connector::Connect(const char* srv_addr, std::string name) { //std::cout << "Packet invalide." << std::endl; break; } - currbuf = &currbuf[curr]; + currbuf = &currbuf[curr + 1]; bytes -= curr; if (errors > 100) return 1; From c23d1b212087245dd986e660aa88926dccb50c16 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Fri, 27 Oct 2023 16:20:05 -0400 Subject: [PATCH 43/65] Surprise. --- SQCSim2021/connector.cpp | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index b76127e..f385e77 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -70,42 +70,54 @@ int Connector::Connect(const char* srv_addr, std::string name) { buflen = 150; bool ready = false; int errors = 0; + int bytes = 0; + char* currbuf = buf; netprot::PlayerInfo pinfo{}; while (!ready) { - uint32_t bytes = recv(m_sock_tcp, buf, buflen, 0); - uint32_t curr = 0; - char* currbuf = buf; + int len = recv(m_sock_tcp, &buf[bytes], buflen, 0); + + if (len <= 0) + return 8; + + bytes += len; + buflen -= bytes; - while (bytes > 0) { switch (netprot::getType(currbuf, buflen)) { case netprot::PACKET_TYPE::LOGINF: + if (bytes < sizeof(netprot::LoginInfo)) + break; if (!netprot::Deserialize(&m_loginfo, currbuf, buflen)) { std::cout << "Packet LoginInfo invalide." << std::endl; return 2; } - curr = sizeof(netprot::LoginInfo); + currbuf = buf + sizeof(netprot::LoginInfo) + 2; break; case netprot::PACKET_TYPE::GAMEINFO: + if (bytes < sizeof(netprot::GameInfo)) + break; if (!netprot::Deserialize(&m_gameinfo, currbuf, buflen)) { std::cout << "Packet GameInfo invalide." << std::endl; return 3; } - curr = sizeof(netprot::GameInfo); + currbuf = buf + sizeof(netprot::GameInfo) + 2; break; case netprot::PACKET_TYPE::PLAYINF: + if (bytes < sizeof(netprot::PlayerInfo)) + break; if (!netprot::Deserialize(&pinfo, currbuf, buflen)) { std::cout << "Packet PlayerInfo invalide." << std::endl; return 3; } m_players[pinfo.id] = pinfo; - curr = sizeof(netprot::PlayerInfo); + currbuf = buf + sizeof(netprot::PlayerInfo) + 2; break; case netprot::PACKET_TYPE::SYNC: + if (bytes < sizeof(netprot::Sync)) + break; if (!netprot::Deserialize(&m_origin, currbuf, buflen)) { std::cout << "Packet Sync invalide." << std::endl; return 3; } - curr = sizeof(netprot::Sync); ready = true; break; default: @@ -113,11 +125,8 @@ int Connector::Connect(const char* srv_addr, std::string name) { //std::cout << "Packet invalide." << std::endl; break; } - currbuf = &currbuf[curr + 1]; - bytes -= curr; if (errors > 100) return 1; - } } return 0; } From 42d612ad336730fc7c13b634d615aa76cc4daa86 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sat, 28 Oct 2023 11:18:01 -0400 Subject: [PATCH 44/65] I read the news today, oh boy. --- SQCSim-common/netprotocol.cpp | 35 ++++++++++++++++++++++++++++++++ SQCSim-common/netprotocol.h | 14 ++++++++++++- SQCSim2021/connector.cpp | 38 +++++++++++++++++++++++++---------- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index f324bd0..4da9da7 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -801,6 +801,40 @@ bool netprot::emptyPack(netprot::Packet pck) { } } +std::vector netprot::recvPacks(SOCKET sock, Buffer* buf) { + std::vector lsPck; + int len = 0, end = 0; + bool pck_received = false; + char* cursor = nullptr; + + while (true) { + while (!pck_received) { + int bytes = recv(sock, &buf->ptr[len], buf->len - len, NULL); + if (bytes < 0) + return lsPck; + len += bytes; + // TODO: Voir si on trouve un footer (5 '\0' d'affilee). + // Si oui, mettre l'index du debut du footer dans + // la variable "end" et mettre pck_received a true. + } + + cursor = &buf->ptr[end]; + + lsPck.push_back(getPack(buf)); + + while (*cursor == '\0') { + cursor++; + end++; + } + + if (cursor) { // mettre le début du prochain paquet au début du buffer. + memcpy(buf->ptr, cursor, buf->len - end); // et voir s'il y a en un autre + len = 0; + pck_received = true; + } + } +} + template <> void netprot::sendPack(SOCKET sock, Packet* pack, char** buf, uint32_t* buflen) { switch (pack->type) { @@ -838,6 +872,7 @@ void netprot::sendPack(SOCKET sock, Packet* pack, char** buf, u return; case PACKET_TYPE::CHAT: sendPack(sock, (Chat*)pack->ptr, buf, buflen); + return; case PACKET_TYPE::ERRLOG: sendPack(sock, (ErrorLog*)pack->ptr, buf, buflen); return; diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index 693f03f..f0395be 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -28,7 +28,7 @@ namespace netprot { struct Packet { // Pour pouvoir recevoir les paquets du recv() sans avoir à les aiguiller dans la même thread. void* ptr = nullptr; // Notez que le pointeur doit être supprimé séparément lorsqu'il n'est plus utile. - PACKET_TYPE type; + PACKET_TYPE type = PACKET_TYPE::ERR; }; struct Keys { @@ -145,10 +145,14 @@ namespace netprot { template void sendPack(SOCKET sock, T* pack, Buffer* buf); template void sendPackTo(SOCKET sock, T* pack, Buffer* buf, sockaddr_in* sockad); + + std::vector recvPacks(SOCKET sock, Buffer* buf); template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen) { netprot::Serialize(pack, buf, buflen); + memset(buf->ptr[buf->len], , '\0', sizeof(uint64_t) + sizeof(uint8_t)); + buf->len += sizeof(uint64_t) + sizeof(uint8_t) send(sock, *buf, *buflen, 0); *buflen = BUFFER_LENGTH; } @@ -157,6 +161,8 @@ namespace netprot { void sendPackTo(SOCKET sock, T* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad) { sockaddr_in addr = *sockad; netprot::Serialize(pack, buf, buflen); + memset(buf->ptr[buf->len], , '\0', sizeof(uint64_t) + sizeof(uint8_t)); + buf->len += sizeof(uint64_t) + sizeof(uint8_t) sendto(sock, *buf, *buflen, 0, (sockaddr*)&addr, sizeof(addr)); *buflen = BUFFER_LENGTH; } @@ -164,6 +170,8 @@ namespace netprot { template void sendPack(SOCKET sock, T* pack, Buffer* buf) { netprot::Serialize(pack, &buf->ptr, &buf->len); + memset(buf->ptr[buf->len], , '\0', sizeof(uint64_t) + sizeof(uint8_t)); + buf->len += sizeof(uint64_t) + sizeof(uint8_t) send(sock, buf->ptr, buf->len, 0); buf->rstLen(); } @@ -172,8 +180,12 @@ namespace netprot { void sendPackTo(SOCKET sock, T* pack, Buffer* buf, sockaddr_in* sockad) { sockaddr_in addr = *sockad; netprot::Serialize(pack, &buf->ptr, &buf->len); + memset(buf->ptr[buf->len], ,'\0', sizeof(uint64_t) + sizeof(uint8_t)); + buf->len += sizeof(uint64_t) + sizeof(uint8_t) sendto(sock, buf->ptr, buf->len, 0, (sockaddr*)&addr, sizeof(addr)); buf->rstLen(); } + + }; #endif diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index f385e77..d0dbdf5 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -54,6 +54,12 @@ int Connector::Connect(const char* srv_addr, std::string name) { if (connect(m_sock_tcp, (sockaddr*)&m_srvsockaddr, sizeof(m_srvsockaddr)) < 0) { std::cout << "Échec de la connexion." << std::endl; + return 2; + } + + if (ioctlsocket(m_sock_tcp, FIONBIO, NULL) < 0) { + std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; + return 3; } char* buf = new char[1500]; @@ -70,17 +76,24 @@ int Connector::Connect(const char* srv_addr, std::string name) { buflen = 150; bool ready = false; int errors = 0; - int bytes = 0; - char* currbuf = buf; + int bytes = 0, ptrbuf = 0; + char* currbuf = new char[150]; netprot::PlayerInfo pinfo{}; while (!ready) { - int len = recv(m_sock_tcp, &buf[bytes], buflen, 0); + int len = 0; + bool is_fullpack = false; - if (len <= 0) - return 8; + do { + len = recv(m_sock_tcp, &buf[bytes], buflen, 0); + bytes += len; + buflen -= bytes; + + for (int i = 0; i < buflen; ++i) { + // TODOTODOTODOSAINT-TROPEZ + } + + } while (!is_fullpack); - bytes += len; - buflen -= bytes; switch (netprot::getType(currbuf, buflen)) { case netprot::PACKET_TYPE::LOGINF: @@ -91,32 +104,35 @@ int Connector::Connect(const char* srv_addr, std::string name) { return 2; } currbuf = buf + sizeof(netprot::LoginInfo) + 2; + //ptrbuf += sizeof(netprot::LoginInfo) + 2; break; case netprot::PACKET_TYPE::GAMEINFO: if (bytes < sizeof(netprot::GameInfo)) break; if (!netprot::Deserialize(&m_gameinfo, currbuf, buflen)) { std::cout << "Packet GameInfo invalide." << std::endl; - return 3; + return 4; } currbuf = buf + sizeof(netprot::GameInfo) + 2; + //ptrbuf += sizeof(netprot::GameInfo) + 2; break; case netprot::PACKET_TYPE::PLAYINF: if (bytes < sizeof(netprot::PlayerInfo)) break; if (!netprot::Deserialize(&pinfo, currbuf, buflen)) { std::cout << "Packet PlayerInfo invalide." << std::endl; - return 3; + return 4; } m_players[pinfo.id] = pinfo; currbuf = buf + sizeof(netprot::PlayerInfo) + 2; + //ptrbuf += sizeof(netprot::PlayerInfo) + 2; break; case netprot::PACKET_TYPE::SYNC: if (bytes < sizeof(netprot::Sync)) break; if (!netprot::Deserialize(&m_origin, currbuf, buflen)) { std::cout << "Packet Sync invalide." << std::endl; - return 3; + return 4; } ready = true; break; @@ -126,7 +142,7 @@ int Connector::Connect(const char* srv_addr, std::string name) { break; } if (errors > 100) - return 1; + return 5; } return 0; } From 3f8d860b938c49a23184e89c6fdd570dd595f04a Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sat, 28 Oct 2023 12:03:22 -0400 Subject: [PATCH 45/65] About a lucky man who made the grade --- SQCSim-common/netprotocol.h | 34 +++++++++---- SQCSim2021/connector.cpp | 97 ++++++++++++------------------------- 2 files changed, 56 insertions(+), 75 deletions(-) diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index f0395be..3aa8ad9 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -74,29 +74,39 @@ namespace netprot { uint16_t ammo = 0; uint8_t hp = 0; Vector3f position; + Sync() {} + Sync(Sync* sync) : timestamp(sync->timestamp), sid(sync->sid), timer(sync->timer), ammo(sync->ammo), hp(sync->hp), position(sync->position) {} }; struct TeamInfo { // cli <-> srv TCP once char name[32]; uint64_t id = 0; + TeamInfo() {} + TeamInfo(TeamInfo* tem) : id(tem->id) { strcpy_s(tem->name, name); } }; struct LoginInfo { // cli <-> srv TCP once char name[32]; uint64_t sid = 0, tid = 0; + LoginInfo() {} + LoginInfo(LoginInfo* ply): sid(ply->sid), tid(ply->tid) { strcpy_s(ply->name, name); } }; struct PlayerInfo { // cli <-> srv TCP once char name[32]; uint64_t id = 0, tid = 0; + PlayerInfo() {} + PlayerInfo(PlayerInfo* log) : id(log->id), tid(log->tid) { strcpy_s(log->name, name); } }; struct GameInfo { // cli <-> srv TCP event (before game start)/ once uint64_t seed; uint32_t countdown; - uint8_t gameType; // TOOD: enum. + uint8_t gameType; // TODD: enum. + GameInfo() {} + GameInfo(GameInfo* gam) : seed(gam->seed), countdown(gam->countdown), gameType(gam->gameType) {} }; struct Chat { // cli <-> srv TCP event @@ -104,11 +114,15 @@ namespace netprot { dest_id = 0, dest_team_id = 0; char mess[140]; // Good 'nough for twitr, good 'nough for me. + Chat() {} + Chat(Chat* cha) : src_id(cha->src_id), dest_id(cha->dest_id), dest_team_id(cha->dest_team_id) { strcpy_s(cha->mess, mess); } }; struct ErrorLog { // srv -> cli TCP event char mess[140]; bool is_fatal; + ErrorLog() {}; + ErrorLog(ErrorLog* err) : is_fatal(err->is_fatal) { strcpy_s(err->mess, mess); } }; void Serialize(Input* in, char* buf[], uint32_t* buflen); // cli @@ -138,7 +152,7 @@ namespace netprot { bool emptyPack(Packet pck); - Packet makePack(void* ptr, PACKET_TYPE type); // Pour pouvoir faire une liste de stock à supprimer sans avoir à en faire une pour chaque type. + Packet makePack(void* ptr, PACKET_TYPE type); // Pour pouvoir faire une liste de stock a supprimer sans avoir a en faire une pour chaque type. template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen); template void sendPackTo(SOCKET sock, T* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad); @@ -151,8 +165,8 @@ namespace netprot { template void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen) { netprot::Serialize(pack, buf, buflen); - memset(buf->ptr[buf->len], , '\0', sizeof(uint64_t) + sizeof(uint8_t)); - buf->len += sizeof(uint64_t) + sizeof(uint8_t) + memset(&buf[*buflen], '\0', sizeof(uint64_t) + sizeof(uint8_t)); + buflen += sizeof(uint64_t) + sizeof(uint8_t); send(sock, *buf, *buflen, 0); *buflen = BUFFER_LENGTH; } @@ -161,8 +175,8 @@ namespace netprot { void sendPackTo(SOCKET sock, T* pack, char** buf, uint32_t* buflen, sockaddr_in* sockad) { sockaddr_in addr = *sockad; netprot::Serialize(pack, buf, buflen); - memset(buf->ptr[buf->len], , '\0', sizeof(uint64_t) + sizeof(uint8_t)); - buf->len += sizeof(uint64_t) + sizeof(uint8_t) + memset(&buf[*buflen], '\0', sizeof(uint64_t) + sizeof(uint8_t)); + buflen += sizeof(uint64_t) + sizeof(uint8_t); sendto(sock, *buf, *buflen, 0, (sockaddr*)&addr, sizeof(addr)); *buflen = BUFFER_LENGTH; } @@ -170,8 +184,8 @@ namespace netprot { template void sendPack(SOCKET sock, T* pack, Buffer* buf) { netprot::Serialize(pack, &buf->ptr, &buf->len); - memset(buf->ptr[buf->len], , '\0', sizeof(uint64_t) + sizeof(uint8_t)); - buf->len += sizeof(uint64_t) + sizeof(uint8_t) + memset(&buf->ptr[buf->len], '\0', sizeof(uint64_t) + sizeof(uint8_t)); + buf->len += sizeof(uint64_t) + sizeof(uint8_t); send(sock, buf->ptr, buf->len, 0); buf->rstLen(); } @@ -180,8 +194,8 @@ namespace netprot { void sendPackTo(SOCKET sock, T* pack, Buffer* buf, sockaddr_in* sockad) { sockaddr_in addr = *sockad; netprot::Serialize(pack, &buf->ptr, &buf->len); - memset(buf->ptr[buf->len], ,'\0', sizeof(uint64_t) + sizeof(uint8_t)); - buf->len += sizeof(uint64_t) + sizeof(uint8_t) + memset(&buf->ptr[buf->len], '\0', sizeof(uint64_t) + sizeof(uint8_t)); + buf->len += sizeof(uint64_t) + sizeof(uint8_t); sendto(sock, buf->ptr, buf->len, 0, (sockaddr*)&addr, sizeof(addr)); buf->rstLen(); } diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index d0dbdf5..f850b56 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -62,87 +62,54 @@ int Connector::Connect(const char* srv_addr, std::string name) { return 3; } - char* buf = new char[1500]; - uint32_t buflen = 1500; + netprot::Buffer bf; netprot::LoginInfo log; - memcpy(&log.name, name.c_str(), name.size() + 1); + strcpy_s(log.name, name.c_str()); - netprot::Serialize(&log, &buf, &buflen); + netprot::sendPack(m_sock_tcp, &log, &bf); - int se = send(m_sock_tcp, buf, buflen, 0); - - delete[] buf; - buf = new char[150] {0}; - buflen = 150; bool ready = false; int errors = 0; - int bytes = 0, ptrbuf = 0; - char* currbuf = new char[150]; - netprot::PlayerInfo pinfo{}; + std::vector lsPck; while (!ready) { - int len = 0; - bool is_fullpack = false; + lsPck = netprot::recvPacks(m_sock_tcp, &bf); - do { - len = recv(m_sock_tcp, &buf[bytes], buflen, 0); - bytes += len; - buflen -= bytes; + if (lsPck.empty()) + errors++; - for (int i = 0; i < buflen; ++i) { - // TODOTODOTODOSAINT-TROPEZ - } - - } while (!is_fullpack); - - - switch (netprot::getType(currbuf, buflen)) { - case netprot::PACKET_TYPE::LOGINF: - if (bytes < sizeof(netprot::LoginInfo)) - break; - if (!netprot::Deserialize(&m_loginfo, currbuf, buflen)) { - std::cout << "Packet LoginInfo invalide." << std::endl; - return 2; - } - currbuf = buf + sizeof(netprot::LoginInfo) + 2; - //ptrbuf += sizeof(netprot::LoginInfo) + 2; + for (auto& pck : lsPck) { + bool bypass_delete = false; + netprot::PlayerInfo* pl = nullptr; + switch (pck.type) { + using enum netprot::PACKET_TYPE; + case LOGINF: + m_loginfo = netprot::LoginInfo((netprot::LoginInfo*)pck.ptr); break; - case netprot::PACKET_TYPE::GAMEINFO: - if (bytes < sizeof(netprot::GameInfo)) - break; - if (!netprot::Deserialize(&m_gameinfo, currbuf, buflen)) { - std::cout << "Packet GameInfo invalide." << std::endl; - return 4; - } - currbuf = buf + sizeof(netprot::GameInfo) + 2; - //ptrbuf += sizeof(netprot::GameInfo) + 2; + case GAMEINFO: + m_gameinfo = netprot::GameInfo((netprot::GameInfo*)pck.ptr); break; - case netprot::PACKET_TYPE::PLAYINF: - if (bytes < sizeof(netprot::PlayerInfo)) - break; - if (!netprot::Deserialize(&pinfo, currbuf, buflen)) { - std::cout << "Packet PlayerInfo invalide." << std::endl; - return 4; - } - m_players[pinfo.id] = pinfo; - currbuf = buf + sizeof(netprot::PlayerInfo) + 2; - //ptrbuf += sizeof(netprot::PlayerInfo) + 2; + case PLAYINF: + pl = (netprot::PlayerInfo*)pck.ptr; + m_players[pl->id] = pl; + bypass_delete = true; break; - case netprot::PACKET_TYPE::SYNC: - if (bytes < sizeof(netprot::Sync)) - break; - if (!netprot::Deserialize(&m_origin, currbuf, buflen)) { - std::cout << "Packet Sync invalide." << std::endl; - return 4; - } + case TEAMINF: + // TODO: Faire dequoi avec TeamInfo si on fini par avoir des teams. + break; + case SYNC: + m_origin = netprot::Sync((netprot::Sync*)pck.ptr); ready = true; break; default: - ++errors; - //std::cout << "Packet invalide." << std::endl; + errors++; break; } - if (errors > 100) - return 5; + if (!bypass_delete) + netprot::emptyPack(pck); + } + lsPck.clear(); + if (errors > 100) + return 4; } return 0; } From a69e7161056a9f7394e701bef790db662c8348d0 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sat, 28 Oct 2023 12:06:01 -0400 Subject: [PATCH 46/65] And though the news was rather sad --- SQCSim2021/connector.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index f850b56..58d4f29 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -74,9 +74,6 @@ int Connector::Connect(const char* srv_addr, std::string name) { while (!ready) { lsPck = netprot::recvPacks(m_sock_tcp, &bf); - if (lsPck.empty()) - errors++; - for (auto& pck : lsPck) { bool bypass_delete = false; netprot::PlayerInfo* pl = nullptr; From 10fcfed05aa02602fde2175e759960e5e9c43a63 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sat, 28 Oct 2023 12:13:45 -0400 Subject: [PATCH 47/65] but I just had to laugh --- SQCSim-common/define.h | 1 + SQCSim-common/netprotocol.h | 10 +++++----- SQCSim-srv/define.h | 4 ---- SQCSim2021/connector.cpp | 4 ++-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/SQCSim-common/define.h b/SQCSim-common/define.h index bbeaaff..0d71942 100644 --- a/SQCSim-common/define.h +++ b/SQCSim-common/define.h @@ -46,6 +46,7 @@ typedef uint64_t Timestamp; #include #include +#define strcpy strcpy_s #define addrlen_t int #define popen _popen #define pclose _pclose diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index 3aa8ad9..787e0f1 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -82,7 +82,7 @@ namespace netprot { char name[32]; uint64_t id = 0; TeamInfo() {} - TeamInfo(TeamInfo* tem) : id(tem->id) { strcpy_s(tem->name, name); } + TeamInfo(TeamInfo* tem) : id(tem->id) { strcpy(tem->name, name); } }; struct LoginInfo { // cli <-> srv TCP once @@ -90,7 +90,7 @@ namespace netprot { uint64_t sid = 0, tid = 0; LoginInfo() {} - LoginInfo(LoginInfo* ply): sid(ply->sid), tid(ply->tid) { strcpy_s(ply->name, name); } + LoginInfo(LoginInfo* ply): sid(ply->sid), tid(ply->tid) { strcpy(ply->name, name); } }; struct PlayerInfo { // cli <-> srv TCP once @@ -98,7 +98,7 @@ namespace netprot { uint64_t id = 0, tid = 0; PlayerInfo() {} - PlayerInfo(PlayerInfo* log) : id(log->id), tid(log->tid) { strcpy_s(log->name, name); } + PlayerInfo(PlayerInfo* log) : id(log->id), tid(log->tid) { strcpy(log->name, name); } }; struct GameInfo { // cli <-> srv TCP event (before game start)/ once @@ -115,14 +115,14 @@ namespace netprot { dest_team_id = 0; char mess[140]; // Good 'nough for twitr, good 'nough for me. Chat() {} - Chat(Chat* cha) : src_id(cha->src_id), dest_id(cha->dest_id), dest_team_id(cha->dest_team_id) { strcpy_s(cha->mess, mess); } + Chat(Chat* cha) : src_id(cha->src_id), dest_id(cha->dest_id), dest_team_id(cha->dest_team_id) { strcpy(cha->mess, mess); } }; struct ErrorLog { // srv -> cli TCP event char mess[140]; bool is_fatal; ErrorLog() {}; - ErrorLog(ErrorLog* err) : is_fatal(err->is_fatal) { strcpy_s(err->mess, mess); } + ErrorLog(ErrorLog* err) : is_fatal(err->is_fatal) { strcpy(err->mess, mess); } }; void Serialize(Input* in, char* buf[], uint32_t* buflen); // cli diff --git a/SQCSim-srv/define.h b/SQCSim-srv/define.h index 3951791..e30bd49 100644 --- a/SQCSim-srv/define.h +++ b/SQCSim-srv/define.h @@ -11,8 +11,4 @@ #define ID_LIST_SIZE 127 #define SRV_MANUAL_SETUP true -#ifdef _WIN32 -#define strcpy strcpy_s -#endif - #endif diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index 58d4f29..dd83166 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -57,14 +57,14 @@ int Connector::Connect(const char* srv_addr, std::string name) { return 2; } - if (ioctlsocket(m_sock_tcp, FIONBIO, NULL) < 0) { + if (ioctlsocket(m_sock_tcp, FIONBIO, nullptr) < 0) { std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; return 3; } netprot::Buffer bf; netprot::LoginInfo log; - strcpy_s(log.name, name.c_str()); + strcpy(log.name, name.c_str()); netprot::sendPack(m_sock_tcp, &log, &bf); From d0c39e49b0d0f20310e11d62bd839cf9e5664c54 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sat, 28 Oct 2023 12:59:19 -0400 Subject: [PATCH 48/65] Update netprotocol.cpp --- SQCSim-common/netprotocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index 4da9da7..ca2f6be 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -810,7 +810,7 @@ std::vector netprot::recvPacks(SOCKET sock, Buffer* buf) { while (true) { while (!pck_received) { int bytes = recv(sock, &buf->ptr[len], buf->len - len, NULL); - if (bytes < 0) + if (bytes < 0) // si recv() retourne -1; ça veut dire qu'il y a plus rien a lire. return lsPck; len += bytes; // TODO: Voir si on trouve un footer (5 '\0' d'affilee). From aab330123438590b3665eb69be924a3618b00e18 Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Sat, 28 Oct 2023 14:11:01 -0400 Subject: [PATCH 49/65] Client Linux qui re-marche --- SQCSim-common/define.h | 4 ++++ SQCSim2021/cmake/CMakeLists.txt | 3 ++- SQCSim2021/connector.cpp | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/SQCSim-common/define.h b/SQCSim-common/define.h index 0d71942..9cb966e 100644 --- a/SQCSim-common/define.h +++ b/SQCSim-common/define.h @@ -46,6 +46,8 @@ typedef uint64_t Timestamp; #include #include +#define ioctl ioctlsocket +#define O_NONBLOCK FIONBIO #define strcpy strcpy_s #define addrlen_t int #define popen _popen @@ -57,10 +59,12 @@ typedef uint64_t Timestamp; #include #include #include +#include #include #include #include + #define addrlen_t unsigned int #define SOCKET int #define INVALID_SOCKET -1 diff --git a/SQCSim2021/cmake/CMakeLists.txt b/SQCSim2021/cmake/CMakeLists.txt index f1aad75..4da3eb4 100644 --- a/SQCSim2021/cmake/CMakeLists.txt +++ b/SQCSim2021/cmake/CMakeLists.txt @@ -50,7 +50,8 @@ add_executable(SQCSim-client "../textureatlas.cpp" "../tool.cpp" "../vertexbuffer.cpp" - "../worldrenderer.cpp" + "../renderer.cpp" + "../remoteplayer.cpp" "../main.cpp" ) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index dd83166..d4119a7 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -57,7 +57,7 @@ int Connector::Connect(const char* srv_addr, std::string name) { return 2; } - if (ioctlsocket(m_sock_tcp, FIONBIO, nullptr) < 0) { + if (ioctl(m_sock_tcp, SOCK_NONBLOCK, nullptr) < 0) { std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; return 3; } From 03be36ecdab90b8c172126134e266afd3be8f74e Mon Sep 17 00:00:00 2001 From: Marc-Eric Martel Date: Sat, 28 Oct 2023 14:16:39 -0400 Subject: [PATCH 50/65] J'ai pu de messages de warning quand je build le client --- SQCSim-common/netprotocol.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index ca2f6be..b6f0214 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -806,10 +806,10 @@ std::vector netprot::recvPacks(SOCKET sock, Buffer* buf) { int len = 0, end = 0; bool pck_received = false; char* cursor = nullptr; - + while (true) { while (!pck_received) { - int bytes = recv(sock, &buf->ptr[len], buf->len - len, NULL); + int bytes = recv(sock, &buf->ptr[len], buf->len - len, 0); if (bytes < 0) // si recv() retourne -1; ça veut dire qu'il y a plus rien a lire. return lsPck; len += bytes; @@ -930,4 +930,4 @@ void netprot::sendPackTo(SOCKET sock, Packet* pack, char** buf, default: return; } -} \ No newline at end of file +} From c33e6f865c266f63be73e20e04a11104e413544d Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sat, 28 Oct 2023 14:41:23 -0400 Subject: [PATCH 51/65] I believe in a thing called SOCK_NONBLOCK --- SQCSim-common/define.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQCSim-common/define.h b/SQCSim-common/define.h index 9cb966e..b716121 100644 --- a/SQCSim-common/define.h +++ b/SQCSim-common/define.h @@ -47,7 +47,7 @@ typedef uint64_t Timestamp; #include #define ioctl ioctlsocket -#define O_NONBLOCK FIONBIO +#define SOCK_NONBLOCK FIONBIO #define strcpy strcpy_s #define addrlen_t int #define popen _popen From b54f8c31e5c6e891adbdc96ade05f2e311e1907e Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sun, 29 Oct 2023 14:54:36 -0400 Subject: [PATCH 52/65] windoes --- SQCSim-common/netprotocol.cpp | 7 +++ SQCSim-common/netprotocol.h | 4 +- SQCSim-common/world.cpp | 6 +++ SQCSim-common/world.h | 3 +- SQCSim-srv/connection.h | 2 +- SQCSim-srv/main.cpp | 3 +- SQCSim-srv/server.cpp | 94 ++++++++++++----------------------- SQCSim-srv/server.h | 4 +- SQCSim2021/connector.cpp | 4 +- 9 files changed, 56 insertions(+), 71 deletions(-) diff --git a/SQCSim-common/netprotocol.cpp b/SQCSim-common/netprotocol.cpp index b6f0214..d31d425 100644 --- a/SQCSim-common/netprotocol.cpp +++ b/SQCSim-common/netprotocol.cpp @@ -801,6 +801,13 @@ bool netprot::emptyPack(netprot::Packet pck) { } } +netprot::Packet netprot::makePack(void* ptr, PACKET_TYPE type) { + Packet pck; + pck.ptr = ptr; + pck.type = type; + return pck; +} + std::vector netprot::recvPacks(SOCKET sock, Buffer* buf) { std::vector lsPck; int len = 0, end = 0; diff --git a/SQCSim-common/netprotocol.h b/SQCSim-common/netprotocol.h index 787e0f1..987e5fe 100644 --- a/SQCSim-common/netprotocol.h +++ b/SQCSim-common/netprotocol.h @@ -166,7 +166,7 @@ namespace netprot { void sendPack(SOCKET sock, T* pack, char** buf, uint32_t* buflen) { netprot::Serialize(pack, buf, buflen); memset(&buf[*buflen], '\0', sizeof(uint64_t) + sizeof(uint8_t)); - buflen += sizeof(uint64_t) + sizeof(uint8_t); + buflen += (sizeof(uint64_t) + sizeof(uint8_t)); send(sock, *buf, *buflen, 0); *buflen = BUFFER_LENGTH; } @@ -176,7 +176,7 @@ namespace netprot { sockaddr_in addr = *sockad; netprot::Serialize(pack, buf, buflen); memset(&buf[*buflen], '\0', sizeof(uint64_t) + sizeof(uint8_t)); - buflen += sizeof(uint64_t) + sizeof(uint8_t); + buflen += (sizeof(uint64_t) + sizeof(uint8_t)); sendto(sock, *buf, *buflen, 0, (sockaddr*)&addr, sizeof(addr)); *buflen = BUFFER_LENGTH; } diff --git a/SQCSim-common/world.cpp b/SQCSim-common/world.cpp index 1be12e7..7215b6b 100644 --- a/SQCSim-common/world.cpp +++ b/SQCSim-common/world.cpp @@ -6,6 +6,12 @@ World::~World() {} Array2d& World::GetChunks() { return m_chunks; } +void World::BuildWorld() { + for (int x = 0; x < WORLD_SIZE_X; ++x) + for (int y = 0; y < WORLD_SIZE_Y; ++y) + m_chunks.Set(x - m_center[0], y - m_center[1], new Chunk(x + m_center[0], y + m_center[1], m_seed)); +} + void World::SetSeed(uint64_t seed) { m_seed = seed; } diff --git a/SQCSim-common/world.h b/SQCSim-common/world.h index 0cc628d..8a166af 100644 --- a/SQCSim-common/world.h +++ b/SQCSim-common/world.h @@ -21,6 +21,7 @@ public: ~World(); Array2d& GetChunks(); + void BuildWorld(); void SetSeed(uint64_t seed); @@ -45,7 +46,7 @@ private: unsigned int m_center[2] = { UINT16_MAX / 2 - WORLD_SIZE_X, UINT16_MAX / 2 - WORLD_SIZE_Y }; - void UpdateChunk(int& updates, unsigned int chx, unsigned int chy, BlockInfo* blockinfo[BTYPE_LAST]); + //void UpdateChunk(int& updates, unsigned int chx, unsigned int chy, BlockInfo* blockinfo[BTYPE_LAST]); void UpdateWorld(const Vector3f& player, BlockInfo* blockinfo[BTYPE_LAST]); void TransposeWorld(Vector3f& player, Bullet* bullets[MAX_BULLETS]); diff --git a/SQCSim-srv/connection.h b/SQCSim-srv/connection.h index 21bff9b..b7a69ee 100644 --- a/SQCSim-srv/connection.h +++ b/SQCSim-srv/connection.h @@ -19,7 +19,7 @@ public: PlayerInfo play); ~Connection(); - Player* player = nullptr; + std::unique_ptr player = nullptr; uint64_t GetHash(bool self = true) const; uint64_t GetTeamHash() const; diff --git a/SQCSim-srv/main.cpp b/SQCSim-srv/main.cpp index a1e104d..98d39f9 100644 --- a/SQCSim-srv/main.cpp +++ b/SQCSim-srv/main.cpp @@ -1,9 +1,8 @@ #include "server.h" int main() { - Server* server = new Server(); + std::unique_ptr server = std::make_unique(); if (server->Init() == 0) if (server->Ready() == 0) server->Run(); - delete server; } \ No newline at end of file diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index 045052f..e1b1ff9 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -127,17 +127,16 @@ int Server::Ready() { 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) { - LoginInfo* log; PlayerInfo play; Packet pck = getPack(&m_buf); if (pck.type != PACKET_TYPE::LOGINF) { Log("Paquet invalide.", true, false); if (pck.type != PACKET_TYPE::ERR) - emptyPack(pck); + netprot::emptyPack(pck); continue; // Passer au prochain appel si c'est pas un LoginInfo ou un LoginInfo invalide qui rentre. } - log = (LoginInfo*)pck.ptr; + LoginInfo* log = (LoginInfo*)pck.ptr; log->sid = getUniqueId(); log->tid = 0; // TODO: À changer si on implemente un mode en equipe. @@ -155,14 +154,16 @@ int Server::Ready() { play.tid = log->tid; sendPack(sock, &m_game, &m_buf.ptr, &m_buf.len); - Connection* conn = new Connection(sock, sockad, *log, play); + std::unique_ptr conn = std::make_unique(sock, sockad, *log, play); for (auto& [key, player] : m_players) { sendPack(player->getSock(), &play, &m_buf); // Envoyer les infos de joueur distant aux joueurs d�j� connect�s sendPack(sock, player->getInfo(), &m_buf); // et envoyer les infos des joueurs distants au nouveau joueur. } - m_players[log->sid] = conn; + m_players[log->sid] = std::move(conn); + + delete log; if (++nbrconn >= nbrjoueurs) readystart = true; @@ -179,12 +180,15 @@ void Server::Run() { Log("Debut de la partie...", false, false); - for (auto& [key, conn]: m_players) { // Creation des instances de joueurs et premier sync. - conn->player = new Player(Vector3f(8.5f, CHUNK_SIZE_Y + 1.8f, 8.5f)); - Player *player = conn->player; + m_world = std::make_unique(); + m_world->SetSeed(m_game.seed); + m_world->BuildWorld(); + + for (auto& [key, conn] : m_players) { // Creation des instances de joueurs et premier sync. + conn->player = std::make_unique(Vector3f(8.5f, CHUNK_SIZE_Y + 1.8f, 8.5f)); Sync sync; - sync.position = player->GetPosition(); - sync.hp = player->GetHP(); + sync.position = conn->player->GetPosition(); + sync.hp = conn->player->GetHP(); sync.sid = key; sync.ammo = 0; sync.timestamp = 0; @@ -197,56 +201,25 @@ void Server::Run() { Packet pck = getPack(&m_buf); switch (pck.type) { using enum netprot::PACKET_TYPE; - case ERR: - std::cout << "ERROR!" << std::endl; - break; - case INPUT: - std::cout << "INPUT!" << std::endl; - break; - case OUTPUT: - std::cout << "OUTPUT!" << std::endl; - break; - case SYNC: - std::cout << "SYNC!" << std::endl; - break; - case TEAMINF: - std::cout << "TEAMINF!" << std::endl; - break; - case SELFINF: - std::cout << "SELFINF!" << std::endl; - break; - case PLAYINF: - std::cout << "PLAYINF!" << std::endl; - break; - case LOGINF: - std::cout << "LOGINF!" << std::endl; - break; - case CHUNKMOD: - std::cout << "CHUNKMOD!" << std::endl; - break; - case PLAYERMOD: - std::cout << "PLAYERMOD!" << std::endl; - break; - case PICKUPMOD: - std::cout << "PICKUPMOD!" << std::endl; - break; - case GAMEINFO: - std::cout << "GAMEINFO!" << std::endl; - break; - case ENDINFO: - std::cout << "ENDINFO!" << std::endl; - break; - case CHAT: - std::cout << "CHAT!" << std::endl; - break; - case ERRLOG: - std::cout << "ERRLOG!" << std::endl; - break; - case LAST_PACK: - std::cout << "wtf?!" << std::endl; - break; + case ERR: std::puts("ERROR!"); break; + case INPUT: std::puts("INPUT!"); break; + case OUTPUT: std::puts("OUTPUT!"); break; + case SYNC: std::puts("SYNC!"); break; + case TEAMINF: std::puts("TEAMINF!"); break; + case SELFINF: std::puts("SELFINF!"); break; + case PLAYINF: std::puts("PLAYINF!"); break; + case LOGINF: std::puts("LOGINF!"); break; + case CHUNKMOD: std::puts("CHUNKMOD!"); break; + case PLAYERMOD: std::puts("PLAYERMOD!"); break; + case PICKUPMOD: std::puts("PICKUPMOD!"); break; + case GAMEINFO: std::puts("GAMEINFO!"); break; + case ENDINFO: std::puts("ENDINFO!"); break; + case CHAT: std::puts("CHAT!"); break; + case ERRLOG: std::puts("ERRLOG!"); break; + case LAST_PACK: [[falltrough]]; + default: std::puts("wtf?!"); break; } - emptyPack(pck); + netprot::emptyPack(pck); } } } @@ -290,7 +263,6 @@ void Server::Log(std::string str, bool is_error = false, bool is_fatal = false) if (m_sock_tcp) closesocket(m_sock_tcp); for (const auto& [key, player] : m_players) { - delete player->player; closesocket(player->getSock()); } m_players.clear(); @@ -305,7 +277,7 @@ void Server::buildIdList(size_t size) { std::set lst; srand(time(NULL)); - do lst.insert(((uint64_t)rand() << 32 | rand())); // EIGHT SIX SEVENFIVE THREE AUGHT NIIIIIIiIIiiIiINE! + do lst.insert(((uint64_t)rand() << 32 | rand())); while (lst.size() < size); m_ids = std::vector(lst.begin(), lst.end()); diff --git a/SQCSim-srv/server.h b/SQCSim-srv/server.h index 33b529f..10b7709 100644 --- a/SQCSim-srv/server.h +++ b/SQCSim-srv/server.h @@ -35,12 +35,12 @@ private: Buffer m_buf; - std::map m_players; + std::map> m_players; std::map m_chatlog; std::vector m_ids; GameInfo m_game; - World* m_world = nullptr; + std::unique_ptr m_world = nullptr; const bool m_manual_setup = SRV_MANUAL_SETUP; std::string LogTimestamp(); diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index d4119a7..57912cb 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -56,12 +56,12 @@ int Connector::Connect(const char* srv_addr, std::string name) { std::cout << "Échec de la connexion." << std::endl; return 2; } - + /* if (ioctl(m_sock_tcp, SOCK_NONBLOCK, nullptr) < 0) { std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; return 3; } - + */ netprot::Buffer bf; netprot::LoginInfo log; strcpy(log.name, name.c_str()); From 4f64be7e0a9d961a7af64208646ea9e8399bc942 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sun, 29 Oct 2023 14:59:29 -0400 Subject: [PATCH 53/65] Feeeeeed the woooooooooorld --- SQCSim-srv/server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index e1b1ff9..f2e1356 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -182,7 +182,7 @@ void Server::Run() { m_world = std::make_unique(); m_world->SetSeed(m_game.seed); - m_world->BuildWorld(); + //m_world->BuildWorld(); for (auto& [key, conn] : m_players) { // Creation des instances de joueurs et premier sync. conn->player = std::make_unique(Vector3f(8.5f, CHUNK_SIZE_Y + 1.8f, 8.5f)); From b50a2f44e233a2114df81e777a2ef2929f902b71 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sun, 29 Oct 2023 15:07:01 -0400 Subject: [PATCH 54/65] We are the wooooorld, we are the childreeen --- SQCSim-common/world.cpp | 2 +- SQCSim-srv/server.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SQCSim-common/world.cpp b/SQCSim-common/world.cpp index 7215b6b..c18c32c 100644 --- a/SQCSim-common/world.cpp +++ b/SQCSim-common/world.cpp @@ -9,7 +9,7 @@ Array2d& World::GetChunks() { return m_chunks; } void World::BuildWorld() { for (int x = 0; x < WORLD_SIZE_X; ++x) for (int y = 0; y < WORLD_SIZE_Y; ++y) - m_chunks.Set(x - m_center[0], y - m_center[1], new Chunk(x + m_center[0], y + m_center[1], m_seed)); + m_chunks.Set(x, y, new Chunk(x + m_center[0], y + m_center[1], m_seed)); } void World::SetSeed(uint64_t seed) { diff --git a/SQCSim-srv/server.cpp b/SQCSim-srv/server.cpp index f2e1356..983ed59 100644 --- a/SQCSim-srv/server.cpp +++ b/SQCSim-srv/server.cpp @@ -182,7 +182,8 @@ void Server::Run() { m_world = std::make_unique(); m_world->SetSeed(m_game.seed); - //m_world->BuildWorld(); + m_world->GetChunks().Reset(nullptr); + m_world->BuildWorld(); for (auto& [key, conn] : m_players) { // Creation des instances de joueurs et premier sync. conn->player = std::make_unique(Vector3f(8.5f, CHUNK_SIZE_Y + 1.8f, 8.5f)); From 15b4dfefc0dab4eba8dc75d76ba75a772fb4fb1e Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sun, 29 Oct 2023 15:20:19 -0400 Subject: [PATCH 55/65] That'll be good 'nough for now. --- SQCSim2021/connector.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index 57912cb..83759cd 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -62,6 +62,13 @@ int Connector::Connect(const char* srv_addr, std::string name) { return 3; } */ + + u_long flag = 1; + if (ioctlsocket(m_sock_tcp, FIONBIO, &flag) < 0) { + std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; + return 3; + } + netprot::Buffer bf; netprot::LoginInfo log; strcpy(log.name, name.c_str()); From 53ed81193637d5c6c0a97eaa84de7407d67b19c0 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sun, 29 Oct 2023 15:24:03 -0400 Subject: [PATCH 56/65] =?UTF-8?q?Va=20falloir=20checker=20si=20Linux=20va?= =?UTF-8?q?=20=C3=AAtre=20content=20ou=20pas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim2021/connector.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index 83759cd..bb4ba8e 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -56,15 +56,9 @@ int Connector::Connect(const char* srv_addr, std::string name) { std::cout << "Échec de la connexion." << std::endl; return 2; } - /* - if (ioctl(m_sock_tcp, SOCK_NONBLOCK, nullptr) < 0) { - std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; - return 3; - } - */ - + u_long flag = 1; - if (ioctlsocket(m_sock_tcp, FIONBIO, &flag) < 0) { + if (ioctl(m_sock_tcp, SOCK_NONBLOCK, &flag) < 0) { std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; return 3; } From e2dbf52b17d447df459832ad9e65c026c5db020f Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sun, 29 Oct 2023 16:43:09 -0400 Subject: [PATCH 57/65] a tester sur Linux (rien change sur windows) --- SQCSim2021/connector.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index bb4ba8e..c9e6a27 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -56,8 +56,12 @@ int Connector::Connect(const char* srv_addr, std::string name) { std::cout << "Échec de la connexion." << std::endl; return 2; } - + +#ifdef _WIN32 u_long flag = 1; +#else + uint64_t** flag = nullptr; +#endif if (ioctl(m_sock_tcp, SOCK_NONBLOCK, &flag) < 0) { std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; return 3; From 6ce7e62e05a4eff1ea059b4725d6604a4260eabe Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Sun, 29 Oct 2023 16:45:57 -0400 Subject: [PATCH 58/65] =?UTF-8?q?pour=20=C3=AAtre=20s=C3=BBr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim2021/connector.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index c9e6a27..d3f8f10 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -59,10 +59,10 @@ int Connector::Connect(const char* srv_addr, std::string name) { #ifdef _WIN32 u_long flag = 1; -#else - uint64_t** flag = nullptr; -#endif if (ioctl(m_sock_tcp, SOCK_NONBLOCK, &flag) < 0) { +#else + if (ioctl(m_sock_tcp, SOCK_NONBLOCK, NULL) < 0) { +#endif std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; return 3; } From a95dd02896c70a6137c7b49af1ec1f2c7d7697e5 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 30 Oct 2023 11:07:26 -0400 Subject: [PATCH 59/65] Cleanup ioctl --- SQCSim2021/connector.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index d3f8f10..831a952 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -57,12 +57,8 @@ int Connector::Connect(const char* srv_addr, std::string name) { return 2; } -#ifdef _WIN32 u_long flag = 1; if (ioctl(m_sock_tcp, SOCK_NONBLOCK, &flag) < 0) { -#else - if (ioctl(m_sock_tcp, SOCK_NONBLOCK, NULL) < 0) { -#endif std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; return 3; } From 3da8be6d5256c4ce0ac209abf2ad9435f046ea8e Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 30 Oct 2023 11:11:13 -0400 Subject: [PATCH 60/65] =?UTF-8?q?Gestion=20pour=20que=20=C3=A7a=20marche?= =?UTF-8?q?=20partout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim-common/define.h | 5 +++-- SQCSim2021/connector.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/SQCSim-common/define.h b/SQCSim-common/define.h index b716121..05d90da 100644 --- a/SQCSim-common/define.h +++ b/SQCSim-common/define.h @@ -46,10 +46,11 @@ typedef uint64_t Timestamp; #include #include +#define flag_t u_long +#define addrlen_t int #define ioctl ioctlsocket #define SOCK_NONBLOCK FIONBIO #define strcpy strcpy_s -#define addrlen_t int #define popen _popen #define pclose _pclose @@ -64,7 +65,7 @@ typedef uint64_t Timestamp; #include #include - +#define flag_t unsigned int #define addrlen_t unsigned int #define SOCKET int #define INVALID_SOCKET -1 diff --git a/SQCSim2021/connector.cpp b/SQCSim2021/connector.cpp index 831a952..3611a37 100644 --- a/SQCSim2021/connector.cpp +++ b/SQCSim2021/connector.cpp @@ -57,7 +57,7 @@ int Connector::Connect(const char* srv_addr, std::string name) { return 2; } - u_long flag = 1; + flag_t flag = 1; if (ioctl(m_sock_tcp, SOCK_NONBLOCK, &flag) < 0) { std::cout << "Impossible de mettre le socket en mode non-bloquant." << std::endl; return 3; From 3249d68e60a3e76c82f153b36e4e702b1633a1dd Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 30 Oct 2023 13:24:46 -0400 Subject: [PATCH 61/65] =?UTF-8?q?Correction=20pour=20l'entr=C3=A9e=20de=20?= =?UTF-8?q?donn=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQCSim2021/engine.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index 22fdd9e..a847f77 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -280,7 +280,6 @@ void Engine::Init() { } uint64_t seed = SEED; - std::string playname = "", srvname = ""; char ch; glDisable(GL_FRAMEBUFFER_SRGB); @@ -320,13 +319,27 @@ void Engine::Init() { std::cout << std::endl; if (ch == 'o' || ch == 'O') { - std::cout << "Veuillez entrer un nom de joueur: "; - std::cin >> playname; - std::cout << std::endl; + char* input = new char[32]; + std::string playname, srvname; - std::cout << "Veuillez entrer une adresse de serveur: "; - std::cin >> srvname; - std::cout << std::endl; + while (playname.size() < 1) { + std::cout << "Veuillez entrer un nom de joueur: "; + std::cin.ignore(); + std::cin.getline(input, 32); + std::cout << std::endl; + playname = input; + if (playname.size() < 1 || playname.size() > 32) + std::puts("Nom invalide."); + } + while (srvname.size() < 1) { + std::cout << "Veuillez entrer une adresse de serveur: "; + std::cin.getline(input, 32); + std::cout << std::endl; + srvname = input; + if (srvname.size() < 1 || srvname.size() > 32) + std::puts("Adresse serveur invalide."); + } + delete[] input; if (!m_conn.Init()) { if (!m_conn.Connect(srvname.c_str(), playname)) { From 6e75bec25cd6ca8a1e73f7cd509827933c212823 Mon Sep 17 00:00:00 2001 From: Jonathan Trottier Date: Mon, 30 Oct 2023 14:36:44 -0400 Subject: [PATCH 62/65] degats dans bullet --- SQCSim-common/bullet.cpp | 22 ++++++++++++++++++++-- SQCSim-common/bullet.h | 7 ++++++- SQCSim-common/player.cpp | 13 +++++++++---- SQCSim-common/player.h | 4 ++-- SQCSim2021/engine.cpp | 7 +++++-- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/SQCSim-common/bullet.cpp b/SQCSim-common/bullet.cpp index 873879d..b4ccd09 100644 --- a/SQCSim-common/bullet.cpp +++ b/SQCSim-common/bullet.cpp @@ -7,11 +7,29 @@ Bullet::Bullet(Vector3f pos, Vector3f dir, uint64_t tid): m_startpos(pos), m_cur Bullet::~Bullet() {} -bool Bullet::Update(World* world, float elapsedtime, int perframe) { +bool Bullet::Update(World* world, float elapsedtime, int perframe, std::map mapPlayer) { int max = 100 / perframe; + float damage = 0.57f; for (int x = 0; x < max; ++x) { m_currentpos += m_velocity * elapsedtime; + + std::map::iterator it = mapPlayer.begin(); + + while (it != mapPlayer.end()) + { + + Player* player = it->second; + Vector3f playerPos = player->GetPosition(); + if (playerPos == m_currentpos) + { + player->InflictDamage(damage); + return true; + } + it++; + } + + if (!world->ChunkAt(m_currentpos)) return true; else if (world->BlockAt(m_currentpos) != BTYPE_AIR) { @@ -20,7 +38,7 @@ bool Bullet::Update(World* world, float elapsedtime, int perframe) { } else if ((m_currentpos - m_startpos).Length() > VIEW_DISTANCE) return true; } - + return false; } diff --git a/SQCSim-common/bullet.h b/SQCSim-common/bullet.h index 1b5e227..a035290 100644 --- a/SQCSim-common/bullet.h +++ b/SQCSim-common/bullet.h @@ -3,8 +3,11 @@ #include "define.h" #include "vector3.h" +#include +#include "player.h" class World; +class Player; class Bullet { public: @@ -12,7 +15,7 @@ public: Bullet(Vector3f pos, Vector3f dir, uint64_t tid); ~Bullet(); - bool Update(World* world, float elapsedtime, int perframe); + bool Update(World* world, float elapsedtime, int perframe, std::map m_mapPlayer); void Transpose(int& x, int& z); Vector3f getPos() const; Vector3f getVel() const; @@ -23,6 +26,8 @@ private: m_currentpos, m_velocity; uint64_t m_tid = 0; + + }; #endif // BULLET_H__ diff --git a/SQCSim-common/player.cpp b/SQCSim-common/player.cpp index c963849..15e935d 100644 --- a/SQCSim-common/player.cpp +++ b/SQCSim-common/player.cpp @@ -206,15 +206,20 @@ void Player::Teleport(int& x, int& z) { m_position.x -= x * CHUNK_SIZE_X; m_position.z -= z * CHUNK_SIZE_Z; } +bool Player::AmIDead() +{ + return m_hp <= 0; +} -void Player::InflictDamage(Player playerHit, float hitPoints) + +void Player::InflictDamage(float hitPoints) { - playerHit.m_hp -= hitPoints; + m_hp -= hitPoints; - if (playerHit.GetHP() <= 0) - { // Quand l'autre joueur est mort. + if (AmIDead()) + { // Quand le joueur est mort. diff --git a/SQCSim-common/player.h b/SQCSim-common/player.h index 79e0008..59d7776 100644 --- a/SQCSim-common/player.h +++ b/SQCSim-common/player.h @@ -26,8 +26,8 @@ public: float GetHP() const; void Teleport(int& x, int& z); - - void InflictDamage(Player playerHit, float hitPoints); + bool AmIDead(); + void InflictDamage(float hitPoints); private: Vector3f m_position; diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index e668998..2d741e7 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -391,7 +391,7 @@ void Engine::UnloadResource() {} void Engine::InstantDamage() { - m_player.InflictDamage(m_player, 0.10f); + m_player.InflictDamage(0.10f); m_damage = false; } @@ -991,7 +991,10 @@ void Engine::Render(float elapsedTime) { DrawHud(elapsedTime, bloc); DisplayPovGun(); ProcessNotificationQueue(); - + if (m_damage) + { + InstantDamage(); + } static bool fell = false; if (m_player.GetPosition().y < 1.7f && !fell) { m_audio.Create3DAudioObj(m_scream, AUDIO_PATH "scream.wav", m_player.GetPOV(), m_player.GetVelocity(), false,1.f); From 602bb844281b362ae00e58aae8e98ffdbc57cfe1 Mon Sep 17 00:00:00 2001 From: Jonathan Trottier Date: Mon, 30 Oct 2023 14:56:04 -0400 Subject: [PATCH 63/65] fonctionnel --- SQCSim-common/bullet.h | 1 + SQCSim-common/player.cpp | 4 ++-- SQCSim2021/engine.cpp | 2 +- SQCSim2021/engine.h | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/SQCSim-common/bullet.h b/SQCSim-common/bullet.h index a035290..cc04737 100644 --- a/SQCSim-common/bullet.h +++ b/SQCSim-common/bullet.h @@ -6,6 +6,7 @@ #include #include "player.h" + class World; class Player; diff --git a/SQCSim-common/player.cpp b/SQCSim-common/player.cpp index 5c92653..790507f 100644 --- a/SQCSim-common/player.cpp +++ b/SQCSim-common/player.cpp @@ -219,14 +219,14 @@ void Player::InflictDamage(float hitPoints) m_hp -= hitPoints; - + if (AmIDead()) { // Quand le joueur est mort. } - +} uint64_t Player::getId() const { return id; } diff --git a/SQCSim2021/engine.cpp b/SQCSim2021/engine.cpp index f2ce768..5c2f9f3 100644 --- a/SQCSim2021/engine.cpp +++ b/SQCSim2021/engine.cpp @@ -1009,7 +1009,7 @@ void Engine::Render(float elapsedTime) { for (int x = 0; x < MAX_BULLETS; ++x) { // Array de bullets en jeu. if (m_bullets[x]) { for (int b = 0; b < BULLET_UPDATES_PER_FRAME; ++b) { - if (m_bullets[x]->Update(&m_world, elapsedTime, BULLET_UPDATES_PER_FRAME)) { + if (m_bullets[x]->Update(&m_world, elapsedTime, BULLET_UPDATES_PER_FRAME, m_players)) { m_bullets[x]->~Bullet(); if (m_whoosh[x]) m_whoosh[x]->drop(); diff --git a/SQCSim2021/engine.h b/SQCSim2021/engine.h index 888d696..928a102 100644 --- a/SQCSim2021/engine.h +++ b/SQCSim2021/engine.h @@ -87,7 +87,7 @@ private: Bullet* m_bullets[MAX_BULLETS]; - std::map m_players; + std::map m_players; //Menu enum class GameState: uint8_t { MAIN_MENU, OPTIONS, QUIT, NEWG, PLAY }; From 9b0c4dc594836eb567d0f355d07b801e46c90771 Mon Sep 17 00:00:00 2001 From: Jonathan Trottier Date: Mon, 30 Oct 2023 14:58:17 -0400 Subject: [PATCH 64/65] modif des degats --- SQCSim-common/bullet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQCSim-common/bullet.cpp b/SQCSim-common/bullet.cpp index b4ccd09..d59d635 100644 --- a/SQCSim-common/bullet.cpp +++ b/SQCSim-common/bullet.cpp @@ -9,7 +9,7 @@ Bullet::~Bullet() {} bool Bullet::Update(World* world, float elapsedtime, int perframe, std::map mapPlayer) { int max = 100 / perframe; - float damage = 0.57f; + float damage = 0.057f; for (int x = 0; x < max; ++x) { m_currentpos += m_velocity * elapsedtime; From 2a4a5e13cc9430eb9485bbfcd86af67385a63485 Mon Sep 17 00:00:00 2001 From: MarcEricMartel Date: Mon, 30 Oct 2023 15:42:02 -0400 Subject: [PATCH 65/65] Correction pour pr20 --- SQCSim-common/bullet.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/SQCSim-common/bullet.cpp b/SQCSim-common/bullet.cpp index d59d635..09dde11 100644 --- a/SQCSim-common/bullet.cpp +++ b/SQCSim-common/bullet.cpp @@ -13,22 +13,12 @@ bool Bullet::Update(World* world, float elapsedtime, int perframe, std::map::iterator it = mapPlayer.begin(); - - while (it != mapPlayer.end()) - { - - Player* player = it->second; - Vector3f playerPos = player->GetPosition(); - if (playerPos == m_currentpos) - { + for (auto& [key, player] : mapPlayer) { + if ((m_currentpos - player->GetPosition()).Length() < .4f) { player->InflictDamage(damage); return true; } - it++; } - if (!world->ChunkAt(m_currentpos)) return true;