From b028fb2b609f9601d70afc4fb47f4976b36be208 Mon Sep 17 00:00:00 2001 From: lemonchu Date: Sun, 31 Dec 2023 19:38:04 +0800 Subject: [PATCH 1/8] add input control --- src/GameBall/logic/units/regular_ball.cpp | 74 +++++++++++------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/GameBall/logic/units/regular_ball.cpp b/src/GameBall/logic/units/regular_ball.cpp index af1cc95..3f3861f 100644 --- a/src/GameBall/logic/units/regular_ball.cpp +++ b/src/GameBall/logic/units/regular_ball.cpp @@ -45,43 +45,43 @@ void RegularBall::UpdateTick() { auto physics_world = world_->PhysicsWorld(); auto &sphere = physics_world->GetSphere(sphere_id_); - // auto owner = world_->GetPlayer(player_id_); - // if (owner) { - // if (UnitId() == owner->PrimaryUnitId()) { - // auto input = owner->TakePlayerInput(); - // - // glm::vec3 forward = glm::normalize(glm::vec3{input.orientation}); - // glm::vec3 right = - // glm::normalize(glm::cross(forward, glm::vec3{0.0f, 1.0f, 0.0f})); - // - // glm::vec3 moving_direction{}; - // - // float angular_acceleration = glm::radians(2880.0f); - // - // if (input.move_forward) { - // moving_direction -= right; - // } - // if (input.move_backward) { - // moving_direction += right; - // } - // if (input.move_left) { - // moving_direction -= forward; - // } - // if (input.move_right) { - // moving_direction += forward; - // } - // - // if (glm::length(moving_direction) > 0.0f) { - // moving_direction = glm::normalize(moving_direction); - // sphere.angular_velocity += - // moving_direction * angular_acceleration * delta_time; - // } - // - // if (input.brake) { - // sphere.angular_velocity = glm::vec3{0.0f}; - // } - // } - // } + auto owner = world_->GetPlayer(player_id_); + if (owner) { + if (UnitId() == owner->PrimaryUnitId()) { + auto input = owner->TakePlayerInput(); + + glm::vec3 forward = glm::normalize(glm::vec3{input.orientation}); + glm::vec3 right = + glm::normalize(glm::cross(forward, glm::vec3{0.0f, 1.0f, 0.0f})); + + glm::vec3 moving_direction{}; + + float angular_acceleration = glm::radians(2880.0f); + + if (input.move_forward) { + moving_direction -= right; + } + if (input.move_backward) { + moving_direction += right; + } + if (input.move_left) { + moving_direction -= forward; + } + if (input.move_right) { + moving_direction += forward; + } + + if (glm::length(moving_direction) > 0.0f) { + moving_direction = glm::normalize(moving_direction); + sphere.angular_velocity += + moving_direction * angular_acceleration * delta_time; + } + + if (input.brake) { + sphere.angular_velocity = glm::vec3{0.0f}; + } + } + } sphere.velocity *= std::pow(0.5f, delta_time); sphere.angular_velocity *= std::pow(0.2f, delta_time); From f80129fe967cdfd7089032bb9ef8d24259d7e347 Mon Sep 17 00:00:00 2001 From: lemonchu Date: Thu, 11 Jan 2024 10:23:26 +0800 Subject: [PATCH 2/8] make the ball jump --- src/GameBall/logic/player_input.cpp | 10 ++++ src/GameBall/logic/player_input.h | 2 + src/GameBall/logic/units/regular_ball.cpp | 73 +++++++++++++++-------- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/GameBall/logic/player_input.cpp b/src/GameBall/logic/player_input.cpp index c002a91..9208d35 100644 --- a/src/GameBall/logic/player_input.cpp +++ b/src/GameBall/logic/player_input.cpp @@ -19,6 +19,16 @@ PlayerInput PlayerInputController::GetInput() { auto yaw = pitch_yaw.y; input_.orientation = {glm::sin(glm::radians(yaw)), 0.0f, -glm::cos(glm::radians(yaw))}; + + if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS && input_.jump_released) { + input_.jump = true; + input_.jump_released = false; + } else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) != GLFW_PRESS) { + input_.jump_released = true; + } else { + input_.jump = false; + } + auto result = input_; input_ = {}; return result; diff --git a/src/GameBall/logic/player_input.h b/src/GameBall/logic/player_input.h index 98b60c0..e481988 100644 --- a/src/GameBall/logic/player_input.h +++ b/src/GameBall/logic/player_input.h @@ -12,6 +12,8 @@ struct PlayerInput { bool move_left{false}; bool move_right{false}; bool brake{false}; + bool jump{false}; + bool jump_released{true}; glm::vec3 orientation{0.0f, 0.0f, 1.0f}; }; diff --git a/src/GameBall/logic/units/regular_ball.cpp b/src/GameBall/logic/units/regular_ball.cpp index 3f3861f..0a31b17 100644 --- a/src/GameBall/logic/units/regular_ball.cpp +++ b/src/GameBall/logic/units/regular_ball.cpp @@ -45,32 +45,50 @@ void RegularBall::UpdateTick() { auto physics_world = world_->PhysicsWorld(); auto &sphere = physics_world->GetSphere(sphere_id_); - auto owner = world_->GetPlayer(player_id_); - if (owner) { - if (UnitId() == owner->PrimaryUnitId()) { - auto input = owner->TakePlayerInput(); - - glm::vec3 forward = glm::normalize(glm::vec3{input.orientation}); - glm::vec3 right = - glm::normalize(glm::cross(forward, glm::vec3{0.0f, 1.0f, 0.0f})); - - glm::vec3 moving_direction{}; - - float angular_acceleration = glm::radians(2880.0f); + // Check if the ball has fallen into the void + if (position_.y < -10.0f) { + position_ = + glm::vec3{0.0f, 1.0f, 0.0f}; + sphere.position = position_; + velocity_ = glm::vec3{0.0f}; + sphere.velocity = velocity_; + augular_momentum_ = glm::vec3{0.0f}; + sphere.angular_velocity = sphere.inertia_inv * augular_momentum_; + } + + auto owner = world_->GetPlayer(player_id_); + if (owner) { + if (UnitId() == owner->PrimaryUnitId()) { + auto input = owner->TakePlayerInput(); + + glm::vec3 forward = glm::normalize(glm::vec3{input.orientation}); + glm::vec3 right = + glm::normalize(glm::cross(forward, glm::vec3{0.0f, 1.0f, 0.0f})); + + glm::vec3 moving_direction{}; + + float angular_acceleration = glm::radians(2880.0f); + + if (input.move_forward) { + moving_direction -= right; + } + if (input.move_backward) { + moving_direction += right; + } + if (input.move_left) { + moving_direction -= forward; + } + if (input.move_right) { + moving_direction += forward; + } - if (input.move_forward) { - moving_direction -= right; - } - if (input.move_backward) { - moving_direction += right; - } - if (input.move_left) { - moving_direction -= forward; - } - if (input.move_right) { - moving_direction += forward; - } + if (input.jump) { + if(sphere.position.y < 1.01f && sphere.position.y > 0.99f) + sphere.velocity.y += 4.0f; // Adjust the force as needed + } + // Only update angular velocity when the ball is grounded + if (sphere.position.y < 1.01f && sphere.position.y > 0.99f) { if (glm::length(moving_direction) > 0.0f) { moving_direction = glm::normalize(moving_direction); sphere.angular_velocity += @@ -82,14 +100,19 @@ void RegularBall::UpdateTick() { } } } + } sphere.velocity *= std::pow(0.5f, delta_time); - sphere.angular_velocity *= std::pow(0.2f, delta_time); + // Only update angular velocity when the ball is grounded + if (sphere.position.y < 1.01f && sphere.position.y > 0.99f) { + sphere.angular_velocity *= std::pow(0.2f, delta_time); + } position_ = sphere.position; velocity_ = sphere.velocity; orientation_ = sphere.orientation; augular_momentum_ = sphere.inertia * sphere.angular_velocity; + //std::cerr << "position: " << position_.x << ", " << position_.y << ", "<< position_.z << std::endl; } void RegularBall::SetMass(float mass) { From 92bc17a0799e8e4e4725b94e2dd78ad15ce20f9e Mon Sep 17 00:00:00 2001 From: lemonchu Date: Tue, 16 Jan 2024 01:12:49 +0800 Subject: [PATCH 3/8] add room mode --- src/GameBall/core/game_ball.cpp | 272 +++++++++++++++++++++- src/GameBall/core/game_ball.h | 2 + src/GameBall/core/p2pnode.cpp | 260 +++++++++++++++++++++ src/GameBall/core/p2pnode.h | 75 ++++++ src/GameBall/logic/player.cpp | 15 +- src/GameBall/logic/player.h | 10 + src/GameBall/logic/units/regular_ball.cpp | 6 +- src/GameBall/logic/world.h | 3 + src/GameBall/main.cpp | 9 + src/GameX/application/application.h | 5 + 10 files changed, 642 insertions(+), 15 deletions(-) create mode 100644 src/GameBall/core/p2pnode.cpp create mode 100644 src/GameBall/core/p2pnode.h diff --git a/src/GameBall/core/game_ball.cpp b/src/GameBall/core/game_ball.cpp index a47b79e..3effbb0 100644 --- a/src/GameBall/core/game_ball.cpp +++ b/src/GameBall/core/game_ball.cpp @@ -1,13 +1,156 @@ #include "GameBall/core/game_ball.h" +#include #include +#include #include "GameBall/core/actors/actors.h" +#include "GameBall/core/p2pnode.h" #include "GameBall/logic/obstacles/obstacles.h" #include "GameBall/logic/units/units.h" namespace GameBall { +struct InputPacket { + uint64_t player_id = -1; + Logic::PlayerInput input; +}; + +std::thread listen_thread; +std::queue input_queue; +std::atomic is_running(true); + +std::string PlayerInputToString(const Logic::PlayerInput &input) { + std::string str; + str += input.move_forward ? '1' : '0'; + str += input.move_backward ? '1' : '0'; + str += input.move_left ? '1' : '0'; + str += input.move_right ? '1' : '0'; + str += input.brake ? '1' : '0'; + str += input.jump ? '1' : '0'; + str += input.jump_released ? '1' : '0'; + + std::ostringstream oss; + oss << "(" << input.orientation.x << "," << input.orientation.y << "," + << input.orientation.z << ")"; + str += oss.str(); + + return str; +} + +Logic::PlayerInput StringToPlayerInput(const std::string &str) { + Logic::PlayerInput input; + input.move_forward = (str[0] == '1'); + input.move_backward = (str[1] == '1'); + input.move_left = (str[2] == '1'); + input.move_right = (str[3] == '1'); + input.brake = (str[4] == '1'); + input.jump = (str[5] == '1'); + input.jump_released = (str[6] == '1'); + + std::istringstream iss(str.substr(7)); + char left_bracket, comma1, comma2, right_bracket; + iss >> left_bracket >> input.orientation.x >> comma1 >> input.orientation.y >> + comma2 >> input.orientation.z >> right_bracket; + + return input; +} + +// Credit to Yuchen Yang +// Ball State Compression Module +// Convert a 18*float array to a 96-base64 string + +#define BLOCK_SIZE 12 +char _str_id[255], _str_tp[64]; +void base64Init() { + for (int i = 'A'; i <= 'Z'; ++i) + _str_tp[_str_id[i] = i - 'A'] = i; + for (int i = 'a'; i <= 'z'; ++i) + _str_tp[_str_id[i] = i - 'a' + 26] = i; + for (int i = '0'; i <= '9'; ++i) + _str_tp[_str_id[i] = i - '0' + 52] = i; + _str_tp[_str_id['+'] = 62] = '+'; + _str_tp[_str_id['/'] = 63] = '/'; +} +std::string encode_state(float *data) { + uint8_t *p = (uint8_t *)data; + std::string q; + for (int i = 0; i < BLOCK_SIZE; i++) { + uint64_t now = 0; + for (int j = 0; j < 6; j++) + now = now << 8 | p[i * 6 + j]; + for (int j = 0; j < 8; j++) + q += _str_tp[now >> (j * 6) & 63]; + } + return q; +} +void decode_state(std::string s, float *ret) { + uint8_t *p = (uint8_t *)ret; + for (int i = 0; i < BLOCK_SIZE; i++) { + uint64_t now = 0; + for (int j = 7; j >= 0; j--) + now = now << 6 | _str_id[s[i * 8 + j]]; + for (int j = 0; j < 6; j++) + p[i * 6 + 5 - j] = now >> (j * 8) & 255; + } +} + +void GameBall::receivePackets() { + std::cout << "Server started." << std::endl; + auto world_ = logic_manager_->World(); + + while (is_running) { + auto [msg, c_ip, c_port] = world_->game_node_.receive(); + + if (msg == "join") { + { + std::lock_guard lock(logic_manager_->logic_mutex_); + auto new_player = world_->CreatePlayer(); + auto new_unit = world_->CreateUnit( + new_player->PlayerId(), glm::vec3{0.0f, 1.0f, 0.0f}, 1.0f, 1.0f); + + new_player->SetNetInfo(c_ip, c_port); + new_player->SetPrimaryUnit(new_unit->UnitId()); + + std::cout << "New client joined: " << c_ip << ":" << c_port + << std::endl; + + world_->game_node_.send(std::to_string(new_player->PlayerId()), c_ip, + c_port); + } + continue; + } + + size_t pos = msg.find(':'); + if (!(pos != std::string::npos && pos > 0 && std::all_of(msg.begin(), msg.begin() + pos, ::isdigit))) { + continue; + } + uint64_t id = std::stoull(msg.substr(0, pos)); + std::string command = msg.substr(pos + 1); + + if (command == "quit") { + { + std::lock_guard lock(logic_manager_->logic_mutex_); + + world_->UnregisterUnit(world_->GetPlayer(id)->PrimaryUnitId()); + world_->RemoveUnit(world_->GetPlayer(id)->PrimaryUnitId()); + + world_->UnregisterPlayer(id); + world_->RemovePlayer(id); + + world_->game_node_.send("Bye", c_ip, c_port); + } + continue; + } + + InputPacket inputPacket; + inputPacket.player_id = id; + inputPacket.input = StringToPlayerInput(command); + input_queue.push(inputPacket); + } + std::cout << "Server stopped." << std::endl; +} + GameBall::GameBall(const GameSettings &settings) : GameX::Base::Application(settings) { auto extent = FrameExtent(); @@ -27,6 +170,30 @@ GameBall::~GameBall() { void GameBall::OnInit() { auto world = logic_manager_->World(); + if (settings_.mode == "server") { + // Node on, User input off, Host on + world->game_node_.is_server = true; + world->game_node_.initialize(settings_.port); + + } else if (settings_.mode == "client") { + // Node on, User input on, Host off + + world->game_node_.is_server = false; + world->game_node_.initialize(settings_.port); + + } else if (settings_.mode == "room") { + // Node on, User input on, Host on + + world->game_node_.is_server = true; + world->game_node_.initialize(settings_.port); + + } else if (settings_.mode == "local") { + // Node off, User input on, Host off + + world->game_node_.is_server = false; + // Do nothing here, just like before. + } + scene_->SetEnvmapImage(asset_manager_->ImageFile("textures/envmap.hdr")); ambient_light_ = scene_->CreateLight(); @@ -35,20 +202,46 @@ void GameBall::OnInit() { directional_light_ = scene_->CreateLight(); directional_light_->SetLight(glm::vec3{1.0f}, glm::vec3{3.0f, 2.0f, 1.0f}); - auto primary_player = world->CreatePlayer(); - auto enemy_player = world->CreatePlayer(); - auto primary_unit = world->CreateUnit( - primary_player->PlayerId(), glm::vec3{0.0f, 1.0f, 0.0f}, 1.0f, 1.0f); - auto enemy_unit = world->CreateUnit( - enemy_player->PlayerId(), glm::vec3{-5.0f, 1.0f, 0.0f}, 1.0f, 1.0f); + base64Init(); + + if (settings_.mode == "room") { + // Server only performs calculations, no rendering and playing. + + auto primary_player = world->CreatePlayer(); + //auto enemy_player = world->CreatePlayer(); + + auto primary_unit = world->CreateUnit( + primary_player->PlayerId(), glm::vec3{0.0f, 1.0f, 0.0f}, 1.0f, 1.0f); + //auto enemy_unit = world->CreateUnit( + // enemy_player->PlayerId(), glm::vec3{-5.0f, 1.0f, 0.0f}, 1.0f, 1.0f); + + primary_player_id_ = primary_player->PlayerId(); + + primary_player->SetPrimaryUnit(primary_unit->UnitId()); + + std::thread t1(&GameBall::receivePackets, this); + listen_thread = std::move(t1); + listen_thread.detach(); + } + + if (settings_.mode == "local"){ + auto primary_player = world->CreatePlayer(); + auto enemy_player = world->CreatePlayer(); + + auto primary_unit = world->CreateUnit( + primary_player->PlayerId(), glm::vec3{0.0f, 1.0f, 0.0f}, 1.0f, 1.0f); + auto enemy_unit = world->CreateUnit( + enemy_player->PlayerId(), glm::vec3{-5.0f, 1.0f, 0.0f}, 1.0f, 1.0f); + + primary_player_id_ = primary_player->PlayerId(); + + primary_player->SetPrimaryUnit(primary_unit->UnitId()); + } + auto primary_obstacle = world->CreateObstacle( glm::vec3{0.0f, -10.0f, 0.0f}, std::numeric_limits::infinity(), false, 20.0f); - primary_player_id_ = primary_player->PlayerId(); - - primary_player->SetPrimaryUnit(primary_unit->UnitId()); - VkExtent2D extent = FrameExtent(); float aspect = static_cast(extent.width) / extent.height; camera_ = scene_->CreateCamera(glm::vec3{0.0f, 10.0f, 10.0f}, @@ -76,6 +269,11 @@ void GameBall::OnCleanup() { actors_.erase(actor->SyncedLogicWorldVersion()); delete actor; } + + is_running = false; + if (listen_thread.joinable()) { + listen_thread.join(); + } } void GameBall::OnUpdate() { @@ -101,6 +299,60 @@ void GameBall::OnUpdate() { } primary_player->SetInput(player_input); } + + if (!input_queue.empty()) { + auto inputPacket = input_queue.front(); + input_queue.pop(); + auto player = logic_manager_->world_->GetPlayer(inputPacket.player_id); + if (player) { + player->SetInput(inputPacket.input); + } + } + } + + // Generate State data; + float ball_data[48]; + auto client_list = logic_manager_->world_->player_map_; + auto ball_num = client_list.size(); + std::string msg_broadcast; + + for (auto &pair : client_list) { + auto player = pair.second; + if (player && player->PlayerId() != primary_player_id_) { + auto unit = logic_manager_->world_->GetUnit(player->PrimaryUnitId()); + if (unit) { + auto sphere_id = player->PrimaryUnitId(); + auto &sphere = + logic_manager_->world_->PhysicsWorld()->GetSphere(sphere_id); + auto regular_ball = dynamic_cast(logic_manager_->world_->GetUnit(sphere_id)); + ball_data[0] = sphere.position.x; + ball_data[1] = sphere.position.y; + ball_data[2] = sphere.position.z; + ball_data[3] = sphere.velocity.x; + ball_data[4] = sphere.velocity.y; + ball_data[5] = sphere.velocity.z; + ball_data[6] = regular_ball->AngularMomentum().x; + ball_data[7] = regular_ball->AngularMomentum().x; + ball_data[8] = regular_ball->AngularMomentum().x; + ball_data[9] = sphere.orientation[0][0]; + ball_data[10] = sphere.orientation[0][1]; + ball_data[11] = sphere.orientation[0][2]; + ball_data[12] = sphere.orientation[1][0]; + ball_data[13] = sphere.orientation[1][1]; + ball_data[14] = sphere.orientation[1][2]; + ball_data[15] = sphere.orientation[2][0]; + ball_data[16] = sphere.orientation[2][1]; + ball_data[17] = sphere.orientation[2][2]; + } + msg_broadcast += std::to_string(player->PrimaryUnitId()) + ":" + encode_state(ball_data) + ","; + } + } + + for (auto &pair : client_list) { + auto player = pair.second; + if (player) { + logic_manager_->world_->game_node_.send(msg_broadcast, player->GetIp(), player->GetPort()); + } } std::queue actors_to_remove; diff --git a/src/GameBall/core/game_ball.h b/src/GameBall/core/game_ball.h index 3da4bd6..6e94212 100644 --- a/src/GameBall/core/game_ball.h +++ b/src/GameBall/core/game_ball.h @@ -51,6 +51,8 @@ class GameBall : public GameX::Base::Application { return camera_controller_.get(); } + void receivePackets(); + private: friend class Logic::Manager; friend class Logic::World; diff --git a/src/GameBall/core/p2pnode.cpp b/src/GameBall/core/p2pnode.cpp new file mode 100644 index 0000000..a515a80 --- /dev/null +++ b/src/GameBall/core/p2pnode.cpp @@ -0,0 +1,260 @@ +// +// Created by LemonPig on 2023/12/16. +// + +#include "p2pnode.h" +#ifdef _WIN32 +P2PNode::P2PNode() : is_initialized(false), sockfd(INVALID_SOCKET) { + // Initialize Winsock + WSADATA wsaData; + int iResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if (iResult != 0) { + printf("WSAStartup failed: %d\n", iResult); + exit(1); + } +} + +P2PNode::~P2PNode() { + closesocket(sockfd); + WSACleanup(); +} + +void P2PNode::initialize(int port) { + if (is_initialized) { + std::cerr << "Node is already initialized." << std::endl; + return; + } + + sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sockfd == INVALID_SOCKET) { + std::cerr << "Error creating socket: " << WSAGetLastError() << std::endl; + WSACleanup(); + exit(1); + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) { + std::cerr << "Bind failed with error: " << WSAGetLastError() << std::endl; + closesocket(sockfd); + WSACleanup(); + exit(1); + } + + is_initialized = true; +} + +void P2PNode::send(const std::string& message, const std::string& ip, int port) const { + if (!is_initialized) { + printf("Node is not initialized.\n"); + return; + } + + sockaddr_in dest_addr; + ZeroMemory(&dest_addr, sizeof(dest_addr)); + dest_addr.sin_family = AF_INET; + InetPtonA(AF_INET, ip.c_str(), &(dest_addr.sin_addr)); + dest_addr.sin_port = htons(port); + + sendto(sockfd, message.c_str(), message.length(), 0, + (struct sockaddr *)&dest_addr, sizeof(dest_addr)); +} + +std::tuple P2PNode::receive() { + if (!is_initialized) { + printf("Node is not initialized.\n"); + return std::make_tuple("", "", 0); + } + + char buffer[1024]; + sockaddr_in sender_addr; + int sender_addr_size = sizeof(sender_addr); + + int len = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, + (struct sockaddr *)&sender_addr, &sender_addr_size); + if (len == SOCKET_ERROR) { + printf("recvfrom failed: %d\n", WSAGetLastError()); + closesocket(sockfd); + WSACleanup(); + exit(1); + } + buffer[len] = '\0'; + + char sender_ip[INET_ADDRSTRLEN]; + InetNtopA(AF_INET, &(sender_addr.sin_addr), sender_ip, INET_ADDRSTRLEN); + int sender_port = ntohs(sender_addr.sin_port); + + return std::make_tuple(std::string(buffer), std::string(sender_ip), sender_port); +} + +void P2PNode::closeConnection() { + if (sockfd != INVALID_SOCKET) { + closesocket(sockfd); + sockfd = INVALID_SOCKET; + } + is_initialized = false; +} + +bool P2PNode::isInited() const { + return is_initialized; +} + +#elif __APPLE__ + +P2PNode::P2PNode() : is_initialized(false), is_server(false), sockfd(-1) {} + +P2PNode::~P2PNode(){ + closeConnection(); +} + +void P2PNode::initialize(int port) { + if (is_initialized) { + std::cerr << "Node is already initialized." << std::endl; + return; + } + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + std::cerr << "Error creating socket" << std::endl; + exit(1); + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + std::cerr << "Error binding socket" << std::endl; + exit(1); + } + + is_initialized = true; +} + +void P2PNode::send(const std::string& message, const std::string& ip, uint16_t port) const { + if (!is_initialized) { + std::cerr << "Node is not initialized." << std::endl; + return; + } + + struct sockaddr_in dest_addr; + memset(&dest_addr, 0, sizeof(dest_addr)); + dest_addr.sin_family = AF_INET; + dest_addr.sin_addr.s_addr = inet_addr(ip.c_str()); + dest_addr.sin_port = htons(port); + + sendto(sockfd, message.c_str(), message.length(), 0, + (struct sockaddr *)&dest_addr, sizeof(dest_addr)); +} + +std::tuple P2PNode::receive() { + if (!is_initialized) { + std::cerr << "Node is not initialized." << std::endl; + return std::make_tuple("", "", 0); + } + + char buffer[1024]; + struct sockaddr_in sender_addr; + socklen_t sender_addr_size = sizeof(sender_addr); + + int len = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, + (struct sockaddr *)&sender_addr, &sender_addr_size); + buffer[len] = '\0'; + + std::string sender_ip = inet_ntoa(sender_addr.sin_addr); + uint16_t sender_port = ntohs(sender_addr.sin_port); + + return std::make_tuple(std::string(buffer), sender_ip, sender_port); +} + +void P2PNode::closeConnection() { + if (sockfd != -1) { + close(sockfd); + sockfd = -1; + is_initialized = false; + } +} + +bool P2PNode::isInited() const { + return is_initialized; +} +#elif __linux__ +// Linux implementation +#else +// Other OS implementation +#endif + +// vector getLocalIPs() +// Returns a vector of strings containing the local IPv4 addresses + +#ifdef _WIN32 +// Windows implementation +std::vector getLocalIPs() { + std::vector ips; + ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO); + PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); + PIP_ADAPTER_INFO pAdapter = NULL; + + if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { + free(pAdapterInfo); + pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen); + } + + if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == NO_ERROR) { + pAdapter = pAdapterInfo; + while (pAdapter) { + ips.push_back(pAdapter->IpAddressList.IpAddress.String); + pAdapter = pAdapter->Next; + } + } + + if (pAdapterInfo) { + free(pAdapterInfo); + } + + return ips; +} +#elif __APPLE__ + +#define NI_MAXHOST 1025 +// macOS implementation +std::vector getLocalIPs() { + struct ifaddrs *ifaddr, *ifa; + char host[NI_MAXHOST]; + std::vector ips; + + if (getifaddrs(&ifaddr) == -1) { + perror("getifaddrs"); + exit(EXIT_FAILURE); + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + + if (ifa->ifa_addr->sa_family == AF_INET) { // Check for IPv4 + if (strcmp(ifa->ifa_name, "lo") != 0) { // Exclude loopback interface + int s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), + host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + if (s != 0) { + std::cerr << "getnameinfo() failed: " << gai_strerror(s) << std::endl; + continue; + } + + ips.push_back(std::string(host)); + } + } + } + + freeifaddrs(ifaddr); + return ips; +} +#elif __linux__ +// Linux implementation +#else +// Other OS implementation +#endif \ No newline at end of file diff --git a/src/GameBall/core/p2pnode.h b/src/GameBall/core/p2pnode.h new file mode 100644 index 0000000..04b5d51 --- /dev/null +++ b/src/GameBall/core/p2pnode.h @@ -0,0 +1,75 @@ +// +// Created by LemonPig on 2023/12/16. +// + +#ifndef LEMON_CHAT_P2PNODE_H +#define LEMON_CHAT_P2PNODE_H + +#define MAX_PLAYER 5 + +// Include Necessary Headers +// For Win32 and macOS +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "iphlpapi.lib") +#pragma comment(lib, "ws2_32.lib") + +#elif __APPLE__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + +// P2PNode Class + +class P2PNode { + private: + +#ifdef _WIN32 + SOCKET sockfd; +#elif __APPLE__ + int sockfd; +#endif + bool is_initialized; + struct sockaddr_in addr; + + public: + + P2PNode(); + + void initialize(int port); + + bool isInited() const; + + ~P2PNode(); + + void send(const std::string& message, const std::string& ip, uint16_t port) const; + + std::tuple receive(); + + void closeConnection(); + + bool is_server; + +}; + +std::vector getLocalIPs(); + +#endif //LEMON_CHAT_P2PNODE_H \ No newline at end of file diff --git a/src/GameBall/logic/player.cpp b/src/GameBall/logic/player.cpp index 6668885..de1bdd5 100644 --- a/src/GameBall/logic/player.cpp +++ b/src/GameBall/logic/player.cpp @@ -4,7 +4,7 @@ namespace GameBall::Logic { -Player::Player(World *world) : world_(world) { +Player::Player(World *world) : world_(world), port_(DEFAULT_PORT){ player_id_ = world_->RegisterPlayer(this); } @@ -37,4 +37,17 @@ PlayerInput Player::TakePlayerInput() { input_ = {}; return input; } + +const std::string& Player::GetIp() const{ + return ip_; +} + +const uint16_t& Player::GetPort() const{ + return port_; +} + +void Player::SetNetInfo(const std::string &ip, const uint16_t &port){ + ip_ = ip; + port_ = port; +} } // namespace GameBall::Logic diff --git a/src/GameBall/logic/player.h b/src/GameBall/logic/player.h index ef33e2e..cabd983 100644 --- a/src/GameBall/logic/player.h +++ b/src/GameBall/logic/player.h @@ -20,6 +20,12 @@ class Player { PlayerInput TakePlayerInput(); + const std::string &GetIp() const; + + const uint16_t &GetPort() const; + + void SetNetInfo(const std::string &ip, const uint16_t &port); + private: World *world_; uint64_t player_id_{}; @@ -27,5 +33,9 @@ class Player { uint64_t primary_unit_id_{}; PlayerInput input_{}; + + std::string ip_; + + uint16_t port_; }; } // namespace GameBall::Logic diff --git a/src/GameBall/logic/units/regular_ball.cpp b/src/GameBall/logic/units/regular_ball.cpp index 0a31b17..f794d75 100644 --- a/src/GameBall/logic/units/regular_ball.cpp +++ b/src/GameBall/logic/units/regular_ball.cpp @@ -45,10 +45,9 @@ void RegularBall::UpdateTick() { auto physics_world = world_->PhysicsWorld(); auto &sphere = physics_world->GetSphere(sphere_id_); - // Check if the ball has fallen into the void if (position_.y < -10.0f) { position_ = - glm::vec3{0.0f, 1.0f, 0.0f}; + glm::vec3{0.0f, 10.0f, 0.0f}; sphere.position = position_; velocity_ = glm::vec3{0.0f}; sphere.velocity = velocity_; @@ -84,7 +83,7 @@ void RegularBall::UpdateTick() { if (input.jump) { if(sphere.position.y < 1.01f && sphere.position.y > 0.99f) - sphere.velocity.y += 4.0f; // Adjust the force as needed + sphere.velocity.y += 6.0f; // Adjust the force as needed } // Only update angular velocity when the ball is grounded @@ -112,7 +111,6 @@ void RegularBall::UpdateTick() { velocity_ = sphere.velocity; orientation_ = sphere.orientation; augular_momentum_ = sphere.inertia * sphere.angular_velocity; - //std::cerr << "position: " << position_.x << ", " << position_.y << ", "<< position_.z << std::endl; } void RegularBall::SetMass(float mass) { diff --git a/src/GameBall/logic/world.h b/src/GameBall/logic/world.h index 59dadf6..f709107 100644 --- a/src/GameBall/logic/world.h +++ b/src/GameBall/logic/world.h @@ -8,6 +8,7 @@ #include "GameBall/logic/obstacle.h" #include "GameBall/logic/player.h" #include "GameBall/logic/unit.h" +#include "GameBall/core/p2pnode.h" namespace GameBall::Logic { class Manager; @@ -91,6 +92,8 @@ class World { void UpdateTick(); + P2PNode game_node_; + private: friend ::GameBall::GameBall; friend ::GameBall::Logic::Manager; diff --git a/src/GameBall/main.cpp b/src/GameBall/main.cpp index 4d4f217..d33cb3d 100644 --- a/src/GameBall/main.cpp +++ b/src/GameBall/main.cpp @@ -1,6 +1,9 @@ #include "GameBall/core/game_ball.h" #include "absl/flags/flag.h" #include "absl/flags/parse.h" +#include "GameBall/core/p2pnode.h" + +#define DEFAULT_PORT 1115 // Use abseil flags to parse command line arguments. @@ -9,6 +12,9 @@ ABSL_FLAG(bool, fullscreen, false, "Run in fullscreen mode."); // Width and Height ABSL_FLAG(int, width, -1, "Width of the window."); ABSL_FLAG(int, height, -1, "Height of the window."); +ABSL_FLAG(std::string, mode, "", "Game mode"); +ABSL_FLAG(std::string, address, "", "Server address"); +ABSL_FLAG(int, port, DEFAULT_PORT, "Server Port"); int main(int argc, char *argv[]) { absl::ParseCommandLine(argc, argv); @@ -17,6 +23,9 @@ int main(int argc, char *argv[]) { settings.fullscreen = absl::GetFlag(FLAGS_fullscreen); settings.width = absl::GetFlag(FLAGS_width); settings.height = absl::GetFlag(FLAGS_height); + settings.mode = absl::GetFlag(FLAGS_mode); + settings.address = absl::GetFlag(FLAGS_address); + settings.port = absl::GetFlag(FLAGS_port); GameBall::GameBall game(settings); game.Run(); return 0; diff --git a/src/GameX/application/application.h b/src/GameX/application/application.h index f9b7d4f..bbe44ca 100644 --- a/src/GameX/application/application.h +++ b/src/GameX/application/application.h @@ -4,12 +4,17 @@ #include "GameX/renderer/renderer.h" #include "GameX/utils/utils.h" +#define DEFAULT_PORT 1115 + namespace GameX::Base { struct ApplicationSettings { bool fullscreen{false}; int width{-1}; int height{-1}; + std::string mode; + std::string address; + int port{DEFAULT_PORT}; }; class Application { From 3f8089e6a6ed3ef2a2cbace45f3a6920014228d7 Mon Sep 17 00:00:00 2001 From: lemonchu Date: Tue, 16 Jan 2024 01:14:20 +0800 Subject: [PATCH 4/8] local mode fix --- src/GameBall/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GameBall/main.cpp b/src/GameBall/main.cpp index d33cb3d..f6dbdd9 100644 --- a/src/GameBall/main.cpp +++ b/src/GameBall/main.cpp @@ -12,7 +12,7 @@ ABSL_FLAG(bool, fullscreen, false, "Run in fullscreen mode."); // Width and Height ABSL_FLAG(int, width, -1, "Width of the window."); ABSL_FLAG(int, height, -1, "Height of the window."); -ABSL_FLAG(std::string, mode, "", "Game mode"); +ABSL_FLAG(std::string, mode, "local", "Game mode"); ABSL_FLAG(std::string, address, "", "Server address"); ABSL_FLAG(int, port, DEFAULT_PORT, "Server Port"); From e68fe781883b9cee43bcc7caf251bd550b3bf76f Mon Sep 17 00:00:00 2001 From: lemonchu Date: Tue, 16 Jan 2024 01:21:16 +0800 Subject: [PATCH 5/8] linux udp support --- src/GameBall/core/p2pnode.cpp | 7 ++----- src/GameBall/core/p2pnode.h | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/GameBall/core/p2pnode.cpp b/src/GameBall/core/p2pnode.cpp index a515a80..fcec99b 100644 --- a/src/GameBall/core/p2pnode.cpp +++ b/src/GameBall/core/p2pnode.cpp @@ -102,7 +102,7 @@ bool P2PNode::isInited() const { return is_initialized; } -#elif __APPLE__ +#elif __APPLE__ || __linux__ P2PNode::P2PNode() : is_initialized(false), is_server(false), sockfd(-1) {} @@ -182,8 +182,6 @@ void P2PNode::closeConnection() { bool P2PNode::isInited() const { return is_initialized; } -#elif __linux__ -// Linux implementation #else // Other OS implementation #endif @@ -218,7 +216,7 @@ std::vector getLocalIPs() { return ips; } -#elif __APPLE__ +#elif __APPLE__ || __linux__ #define NI_MAXHOST 1025 // macOS implementation @@ -253,7 +251,6 @@ std::vector getLocalIPs() { freeifaddrs(ifaddr); return ips; } -#elif __linux__ // Linux implementation #else // Other OS implementation diff --git a/src/GameBall/core/p2pnode.h b/src/GameBall/core/p2pnode.h index 04b5d51..e064959 100644 --- a/src/GameBall/core/p2pnode.h +++ b/src/GameBall/core/p2pnode.h @@ -22,7 +22,7 @@ #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "ws2_32.lib") -#elif __APPLE__ +#elif __APPLE__ || __linux__ #include #include #include @@ -44,7 +44,7 @@ class P2PNode { #ifdef _WIN32 SOCKET sockfd; -#elif __APPLE__ +#elif __APPLE__ || __linux__ int sockfd; #endif bool is_initialized; From 1bea05b38cecb2661048c4be9820aebdd36be1fa Mon Sep 17 00:00:00 2001 From: lemonchu Date: Tue, 16 Jan 2024 09:01:28 +0800 Subject: [PATCH 6/8] minor fix --- src/GameBall/core/p2pnode.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/GameBall/core/p2pnode.h b/src/GameBall/core/p2pnode.h index e064959..64bdee0 100644 --- a/src/GameBall/core/p2pnode.h +++ b/src/GameBall/core/p2pnode.h @@ -18,6 +18,7 @@ #include #include #include +#include #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "ws2_32.lib") @@ -34,6 +35,7 @@ #include #include #include +#include #endif From e4e4f00d3d27940051648c78302b2cd499b548fc Mon Sep 17 00:00:00 2001 From: lemonchu Date: Tue, 16 Jan 2024 17:52:07 +0800 Subject: [PATCH 7/8] feat: udp multiplayer room --- .../core/actors/common_ball_actor.cpp | 4 + src/GameBall/core/game_ball.cpp | 532 +++++++++++------- src/GameBall/core/packet_tool.h | 85 +++ src/GameBall/logic/player_input.cpp | 22 + src/GameBall/logic/player_input.h | 4 + src/GameBall/logic/units/regular_ball.cpp | 98 ++-- src/GameBall/logic/units/regular_ball.h | 1 + src/GameBall/logic/world.cpp | 16 +- src/GameBall/main.cpp | 8 +- src/GameX/physics/world.cpp | 4 + src/GameX/physics/world.h | 2 + test/regular_ball_test.cpp | 2 + 12 files changed, 525 insertions(+), 253 deletions(-) create mode 100644 src/GameBall/core/packet_tool.h diff --git a/src/GameBall/core/actors/common_ball_actor.cpp b/src/GameBall/core/actors/common_ball_actor.cpp index 82d6c37..203c04b 100644 --- a/src/GameBall/core/actors/common_ball_actor.cpp +++ b/src/GameBall/core/actors/common_ball_actor.cpp @@ -12,6 +12,10 @@ CommonBallActor::CommonBallActor(GameBall *app) : Actor(app) { } CommonBallActor::~CommonBallActor() { + if (entity_) { + app_->Scene()->DestroyEntity(entity_.get()); + entity_ = nullptr; + } } void CommonBallActor::Update(float delta_time) { diff --git a/src/GameBall/core/game_ball.cpp b/src/GameBall/core/game_ball.cpp index 3effbb0..ab10b7a 100644 --- a/src/GameBall/core/game_ball.cpp +++ b/src/GameBall/core/game_ball.cpp @@ -1,4 +1,5 @@ #include "GameBall/core/game_ball.h" +#include "packet_tool.h" #include #include @@ -18,137 +19,83 @@ struct InputPacket { std::thread listen_thread; std::queue input_queue; +std::queue video_stream; std::atomic is_running(true); - -std::string PlayerInputToString(const Logic::PlayerInput &input) { - std::string str; - str += input.move_forward ? '1' : '0'; - str += input.move_backward ? '1' : '0'; - str += input.move_left ? '1' : '0'; - str += input.move_right ? '1' : '0'; - str += input.brake ? '1' : '0'; - str += input.jump ? '1' : '0'; - str += input.jump_released ? '1' : '0'; - - std::ostringstream oss; - oss << "(" << input.orientation.x << "," << input.orientation.y << "," - << input.orientation.z << ")"; - str += oss.str(); - - return str; -} - -Logic::PlayerInput StringToPlayerInput(const std::string &str) { - Logic::PlayerInput input; - input.move_forward = (str[0] == '1'); - input.move_backward = (str[1] == '1'); - input.move_left = (str[2] == '1'); - input.move_right = (str[3] == '1'); - input.brake = (str[4] == '1'); - input.jump = (str[5] == '1'); - input.jump_released = (str[6] == '1'); - - std::istringstream iss(str.substr(7)); - char left_bracket, comma1, comma2, right_bracket; - iss >> left_bracket >> input.orientation.x >> comma1 >> input.orientation.y >> - comma2 >> input.orientation.z >> right_bracket; - - return input; -} - -// Credit to Yuchen Yang -// Ball State Compression Module -// Convert a 18*float array to a 96-base64 string - -#define BLOCK_SIZE 12 -char _str_id[255], _str_tp[64]; -void base64Init() { - for (int i = 'A'; i <= 'Z'; ++i) - _str_tp[_str_id[i] = i - 'A'] = i; - for (int i = 'a'; i <= 'z'; ++i) - _str_tp[_str_id[i] = i - 'a' + 26] = i; - for (int i = '0'; i <= '9'; ++i) - _str_tp[_str_id[i] = i - '0' + 52] = i; - _str_tp[_str_id['+'] = 62] = '+'; - _str_tp[_str_id['/'] = 63] = '/'; -} -std::string encode_state(float *data) { - uint8_t *p = (uint8_t *)data; - std::string q; - for (int i = 0; i < BLOCK_SIZE; i++) { - uint64_t now = 0; - for (int j = 0; j < 6; j++) - now = now << 8 | p[i * 6 + j]; - for (int j = 0; j < 8; j++) - q += _str_tp[now >> (j * 6) & 63]; - } - return q; -} -void decode_state(std::string s, float *ret) { - uint8_t *p = (uint8_t *)ret; - for (int i = 0; i < BLOCK_SIZE; i++) { - uint64_t now = 0; - for (int j = 7; j >= 0; j--) - now = now << 6 | _str_id[s[i * 8 + j]]; - for (int j = 0; j < 6; j++) - p[i * 6 + 5 - j] = now >> (j * 8) & 255; - } -} +std::string global_mode; +uint64_t data_id = 0; void GameBall::receivePackets() { - std::cout << "Server started." << std::endl; auto world_ = logic_manager_->World(); - while (is_running) { - auto [msg, c_ip, c_port] = world_->game_node_.receive(); + // Room mode logics + if (settings_.mode == "room") { + std::cout << "Server started." << std::endl; - if (msg == "join") { - { - std::lock_guard lock(logic_manager_->logic_mutex_); - auto new_player = world_->CreatePlayer(); - auto new_unit = world_->CreateUnit( - new_player->PlayerId(), glm::vec3{0.0f, 1.0f, 0.0f}, 1.0f, 1.0f); + while (is_running) { + auto [msg, c_ip, c_port] = world_->game_node_.receive(); - new_player->SetNetInfo(c_ip, c_port); - new_player->SetPrimaryUnit(new_unit->UnitId()); + if (msg == "join") { + { + std::lock_guard lock(logic_manager_->logic_mutex_); + auto new_player = world_->CreatePlayer(); + auto new_unit = world_->CreateUnit( + new_player->PlayerId(), glm::vec3{0.0f, 1.0f, 0.0f}, 1.0f, 1.0f); - std::cout << "New client joined: " << c_ip << ":" << c_port - << std::endl; + new_player->SetNetInfo(c_ip, c_port); + new_player->SetPrimaryUnit(new_unit->UnitId()); - world_->game_node_.send(std::to_string(new_player->PlayerId()), c_ip, - c_port); + world_->game_node_.send(std::to_string(new_player->PlayerId()), c_ip, + c_port); + } + continue; } - continue; - } - - size_t pos = msg.find(':'); - if (!(pos != std::string::npos && pos > 0 && std::all_of(msg.begin(), msg.begin() + pos, ::isdigit))) { - continue; - } - uint64_t id = std::stoull(msg.substr(0, pos)); - std::string command = msg.substr(pos + 1); - if (command == "quit") { - { - std::lock_guard lock(logic_manager_->logic_mutex_); - - world_->UnregisterUnit(world_->GetPlayer(id)->PrimaryUnitId()); - world_->RemoveUnit(world_->GetPlayer(id)->PrimaryUnitId()); - - world_->UnregisterPlayer(id); - world_->RemovePlayer(id); - - world_->game_node_.send("Bye", c_ip, c_port); + // Split the msg, and check if the command is quit. + size_t pos = msg.find(':'); + if (!(pos != std::string::npos && pos > 0 && + std::all_of(msg.begin(), msg.begin() + pos, ::isdigit))) { + continue; + } + uint64_t id = std::stoull(msg.substr(0, pos)); + std::string command = msg.substr(pos + 1); + + // (A Open Bug) TODO: Fix the bug of client quit + // GameX still not have a proper scheme to handle client quit + // and it will crash if the client quit. + // The crash will occur in GameBall::OnUpdate(), when it tries to + // Clear the unregistered actors_ map. + if (command == "quit") { + { + std::lock_guard lock(logic_manager_->logic_mutex_); + + world_->UnregisterObject(world_->GetPlayer(id)->PrimaryUnitId()); + world_->UnregisterPlayer(id); + + world_->game_node_.send("Bye", c_ip, c_port); + } + continue; } - continue; + + // If the command is not quit, then it is a sync of user input, just push it to the input queue. + InputPacket inputPacket; + inputPacket.player_id = id; + inputPacket.input = StringToPlayerInput(command); + input_queue.push(inputPacket); } + std::cout << "Server stopped." << std::endl; + return; + } - InputPacket inputPacket; - inputPacket.player_id = id; - inputPacket.input = StringToPlayerInput(command); - input_queue.push(inputPacket); + // Client Logics + if (settings_.mode == "client") { + std::cout << "Client started." << std::endl; + while (is_running) { + auto [msg, c_ip, c_port] = world_->game_node_.receive(); + // Receive Video Stream Calculated by Server + video_stream.push(msg); + } + std::cout << "Client stopped." << std::endl; } - std::cout << "Server stopped." << std::endl; } GameBall::GameBall(const GameSettings &settings) @@ -169,31 +116,28 @@ GameBall::~GameBall() { void GameBall::OnInit() { auto world = logic_manager_->World(); + global_mode = settings_.mode; - if (settings_.mode == "server") { - // Node on, User input off, Host on - world->game_node_.is_server = true; - world->game_node_.initialize(settings_.port); - - } else if (settings_.mode == "client") { - // Node on, User input on, Host off - + if (settings_.mode == "client") { world->game_node_.is_server = false; - world->game_node_.initialize(settings_.port); + world->game_node_.initialize(settings_.port+1); } else if (settings_.mode == "room") { - // Node on, User input on, Host on - world->game_node_.is_server = true; world->game_node_.initialize(settings_.port); } else if (settings_.mode == "local") { - // Node off, User input on, Host off - world->game_node_.is_server = false; // Do nothing here, just like before. } + if (settings_.mode == "room") { + auto ip_list = getLocalIPs(); + for (auto &ip : ip_list) { + std::cout << "Server IP: " << ip << std::endl; + } + } + scene_->SetEnvmapImage(asset_manager_->ImageFile("textures/envmap.hdr")); ambient_light_ = scene_->CreateLight(); @@ -205,15 +149,10 @@ void GameBall::OnInit() { base64Init(); if (settings_.mode == "room") { - // Server only performs calculations, no rendering and playing. - auto primary_player = world->CreatePlayer(); - //auto enemy_player = world->CreatePlayer(); auto primary_unit = world->CreateUnit( primary_player->PlayerId(), glm::vec3{0.0f, 1.0f, 0.0f}, 1.0f, 1.0f); - //auto enemy_unit = world->CreateUnit( - // enemy_player->PlayerId(), glm::vec3{-5.0f, 1.0f, 0.0f}, 1.0f, 1.0f); primary_player_id_ = primary_player->PlayerId(); @@ -224,7 +163,7 @@ void GameBall::OnInit() { listen_thread.detach(); } - if (settings_.mode == "local"){ + if (settings_.mode == "local") { auto primary_player = world->CreatePlayer(); auto enemy_player = world->CreatePlayer(); @@ -238,6 +177,26 @@ void GameBall::OnInit() { primary_player->SetPrimaryUnit(primary_unit->UnitId()); } + if (settings_.mode == "client") { + auto primary_player = world->CreatePlayer(); + + auto primary_unit = world->CreateUnit( + primary_player->PlayerId(), glm::vec3{0.0f, 1.0f, 0.0f}, 1.0f, 1.0f); + + primary_player_id_ = primary_player->PlayerId(); + + primary_player->SetPrimaryUnit(primary_unit->UnitId()); + printf("Set ID!\n"); + world->game_node_.send("join", settings_.address, settings_.port); + auto [msg, c_ip, c_port] = world->game_node_.receive(); + + data_id = std::stoull(msg); + printf("Login Finished!\n"); + std::thread t1(&GameBall::receivePackets, this); + listen_thread = std::move(t1); + listen_thread.detach(); + } + auto primary_obstacle = world->CreateObstacle( glm::vec3{0.0f, -10.0f, 0.0f}, std::numeric_limits::infinity(), false, 20.0f); @@ -274,8 +233,15 @@ void GameBall::OnCleanup() { if (listen_thread.joinable()) { listen_thread.join(); } + + if (settings_.mode == "client"){ + auto world = logic_manager_->World();; + world->game_node_.send(std::to_string(data_id) + ":quit", settings_.address, settings_.port); + } } +Logic::PlayerInput last_input; + void GameBall::OnUpdate() { static auto last_time = std::chrono::steady_clock::now(); auto current_time = std::chrono::steady_clock::now(); @@ -286,6 +252,18 @@ void GameBall::OnUpdate() { auto player_input = player_input_controller_->GetInput(); + // Client only collect user inputs, and send them to the server + if (settings_.mode == "client") { + if (player_input != last_input) { + last_input = player_input; + auto send_input = std::to_string(data_id) + ":" + PlayerInputToString(player_input); + logic_manager_->world_->game_node_.send(send_input, settings_.address, settings_.port); + } + } + + auto &client_list = logic_manager_->world_->player_map_; + auto ball_num = client_list.size(); + { std::lock_guard lock(logic_manager_->logic_mutex_); logic_manager_->world_->SyncWorldState(this); @@ -300,85 +278,235 @@ void GameBall::OnUpdate() { primary_player->SetInput(player_input); } - if (!input_queue.empty()) { - auto inputPacket = input_queue.front(); - input_queue.pop(); - auto player = logic_manager_->world_->GetPlayer(inputPacket.player_id); - if (player) { - player->SetInput(inputPacket.input); + if (settings_.mode == "room") { + if (!input_queue.empty()) { + auto inputPacket = input_queue.front(); + input_queue.pop(); + auto player = logic_manager_->world_->GetPlayer(inputPacket.player_id); + if (player) { + player->SetInput(inputPacket.input); + } + } + } else if (settings_.mode == "client") { + if (!video_stream.empty()) { + auto frame = video_stream.front(); + video_stream.pop(); + // Avoid too long queue + if (video_stream.size() > 128) { + std::queue emptyQueue; + video_stream.swap(emptyQueue); + } else if (video_stream.size() > 64) { + video_stream.pop(); + video_stream.pop(); + } + + std::stringstream ss(frame); + std::string tmp_str; + std::map frameMap; + + while (std::getline(ss, tmp_str, ',')) { + std::stringstream fs(tmp_str); + std::string idStr; + std::string data; + if (std::getline(fs, idStr, ':') && std::getline(fs, data)) { + uint64_t id = std::stoull(idStr); + frameMap[id] = data; + } + } + + // Sync the picked frame to the game + // Sync the primary_user first + + float ball_data[48]; + + if (ball_num > frameMap.size()) { + // Delete Balls + int64_t del_num = ball_num - frameMap.size(); + for (auto &pair : client_list) { + if (del_num == 0) + break; + if (pair.second->PlayerId() == primary_player_id_) + continue; + auto player = pair.second; + auto _sphere_id = player->PrimaryUnitId(); + auto &_sphere = + logic_manager_->world_->PhysicsWorld()->GetSphere(_sphere_id); + auto _regular_ball = dynamic_cast( + logic_manager_->world_->GetUnit(_sphere_id)); + logic_manager_->world_->UnregisterUnit(_sphere_id); + logic_manager_->world_->RemoveUnit(_sphere_id); + del_num--; + } + } + if (ball_num < frameMap.size()) { + // Add Balls + int64_t add_num = frameMap.size() - ball_num; + for (auto &pair : frameMap) { + if (add_num == 0) + break; + if (pair.first == primary_player_id_) + continue; + auto player = logic_manager_->world_->CreatePlayer(); + auto unit = + logic_manager_->world_->CreateUnit( + player->PlayerId(), glm::vec3{0.0f, 1.0f, 0.0f}, 1.0f, + 1.0f); + player->SetPrimaryUnit(unit->UnitId()); + player->SetNetInfo(settings_.address, settings_.port); + add_num--; + } + } + + if (ball_num == frameMap.size()) { + auto pri_player = + logic_manager_->world_->GetPlayer(primary_player_id_); + auto sphere_id = pri_player->PrimaryUnitId(); + auto &sphere = + logic_manager_->world_->PhysicsWorld()->GetSphere(sphere_id); + auto regular_ball = dynamic_cast( + logic_manager_->world_->GetUnit(sphere_id)); + + decode_state(frameMap[data_id], ball_data); + sphere.position.x = ball_data[0]; + sphere.position.y = ball_data[1]; + sphere.position.z = ball_data[2]; + sphere.velocity.x = ball_data[3]; + sphere.velocity.y = ball_data[4]; + sphere.velocity.z = ball_data[5]; + if(regular_ball == nullptr) std::cout<<"NULL"<setAngularMomentum((glm::f32)ball_data[6], + (glm::f32)ball_data[7], + (glm::f32)ball_data[8]); + sphere.orientation[0][0] = (glm::f32)ball_data[9]; + sphere.orientation[0][1] = (glm::f32)ball_data[10]; + sphere.orientation[0][2] = (glm::f32)ball_data[11]; + sphere.orientation[1][0] = (glm::f32)ball_data[12]; + sphere.orientation[1][1] = (glm::f32)ball_data[13]; + sphere.orientation[1][2] = (glm::f32)ball_data[14]; + sphere.orientation[2][0] = (glm::f32)ball_data[15]; + sphere.orientation[2][1] = (glm::f32)ball_data[16]; + sphere.orientation[2][2] = (glm::f32)ball_data[17]; + + frameMap.erase(data_id); + + // Sync the rest: frameMap and client_list + // Do not care about ID, just update them in any order. Pick ith frame + // to update ith client Iterate both maps + auto iter1 = frameMap.begin(); + auto iter2 = client_list.begin(); + while (iter1 != frameMap.end() && iter2 != client_list.end()) { + if (iter2->second->PlayerId() == primary_player_id_) + iter2++; + if (iter2 == client_list.end()) + break; + + // Update the client + auto player = iter2->second; + auto _sphere_id = player->PrimaryUnitId(); + auto &_sphere = + logic_manager_->world_->PhysicsWorld()->GetSphere(_sphere_id); + auto _regular_ball = dynamic_cast( + logic_manager_->world_->GetUnit(_sphere_id)); + + decode_state(iter1->second, ball_data); + _sphere.position.x = ball_data[0]; + _sphere.position.y = ball_data[1]; + _sphere.position.z = ball_data[2]; + _sphere.velocity.x = ball_data[3]; + _sphere.velocity.y = ball_data[4]; + _sphere.velocity.z = ball_data[5]; + _regular_ball->setAngularMomentum((glm::f32)ball_data[6], + (glm::f32)ball_data[7], + (glm::f32)ball_data[8]); + _sphere.orientation[0][0] = (glm::f32)ball_data[9]; + _sphere.orientation[0][1] = (glm::f32)ball_data[10]; + _sphere.orientation[0][2] = (glm::f32)ball_data[11]; + _sphere.orientation[1][0] = (glm::f32)ball_data[12]; + _sphere.orientation[1][1] = (glm::f32)ball_data[13]; + _sphere.orientation[1][2] = (glm::f32)ball_data[14]; + _sphere.orientation[2][0] = (glm::f32)ball_data[15]; + _sphere.orientation[2][1] = (glm::f32)ball_data[16]; + _sphere.orientation[2][2] = (glm::f32)ball_data[17]; + + iter1++; + iter2++; + } + } } } - } - // Generate State data; - float ball_data[48]; - auto client_list = logic_manager_->world_->player_map_; - auto ball_num = client_list.size(); - std::string msg_broadcast; - - for (auto &pair : client_list) { - auto player = pair.second; - if (player && player->PlayerId() != primary_player_id_) { - auto unit = logic_manager_->world_->GetUnit(player->PrimaryUnitId()); - if (unit) { - auto sphere_id = player->PrimaryUnitId(); - auto &sphere = - logic_manager_->world_->PhysicsWorld()->GetSphere(sphere_id); - auto regular_ball = dynamic_cast(logic_manager_->world_->GetUnit(sphere_id)); - ball_data[0] = sphere.position.x; - ball_data[1] = sphere.position.y; - ball_data[2] = sphere.position.z; - ball_data[3] = sphere.velocity.x; - ball_data[4] = sphere.velocity.y; - ball_data[5] = sphere.velocity.z; - ball_data[6] = regular_ball->AngularMomentum().x; - ball_data[7] = regular_ball->AngularMomentum().x; - ball_data[8] = regular_ball->AngularMomentum().x; - ball_data[9] = sphere.orientation[0][0]; - ball_data[10] = sphere.orientation[0][1]; - ball_data[11] = sphere.orientation[0][2]; - ball_data[12] = sphere.orientation[1][0]; - ball_data[13] = sphere.orientation[1][1]; - ball_data[14] = sphere.orientation[1][2]; - ball_data[15] = sphere.orientation[2][0]; - ball_data[16] = sphere.orientation[2][1]; - ball_data[17] = sphere.orientation[2][2]; + if (settings_.mode == "room") { + float ball_data[48]; + std::string msg_broadcast; + + for (auto &pair : client_list) { + auto player = pair.second; + if (player) { + auto unit = logic_manager_->world_->GetUnit(player->PrimaryUnitId()); + if (unit) { + auto sphere_id = player->PrimaryUnitId(); + auto &sphere = + logic_manager_->world_->PhysicsWorld()->GetSphere(sphere_id); + auto regular_ball = dynamic_cast( + logic_manager_->world_->GetUnit(sphere_id)); + ball_data[0] = sphere.position.x; + ball_data[1] = sphere.position.y; + ball_data[2] = sphere.position.z; + ball_data[3] = sphere.velocity.x; + ball_data[4] = sphere.velocity.y; + ball_data[5] = sphere.velocity.z; + ball_data[6] = regular_ball->AngularMomentum().x; + ball_data[7] = regular_ball->AngularMomentum().x; + ball_data[8] = regular_ball->AngularMomentum().x; + ball_data[9] = sphere.orientation[0][0]; + ball_data[10] = sphere.orientation[0][1]; + ball_data[11] = sphere.orientation[0][2]; + ball_data[12] = sphere.orientation[1][0]; + ball_data[13] = sphere.orientation[1][1]; + ball_data[14] = sphere.orientation[1][2]; + ball_data[15] = sphere.orientation[2][0]; + ball_data[16] = sphere.orientation[2][1]; + ball_data[17] = sphere.orientation[2][2]; + } + msg_broadcast += std::to_string(player->PrimaryUnitId()) + ":" + + encode_state(ball_data) + ","; + } } - msg_broadcast += std::to_string(player->PrimaryUnitId()) + ":" + encode_state(ball_data) + ","; - } - } - for (auto &pair : client_list) { - auto player = pair.second; - if (player) { - logic_manager_->world_->game_node_.send(msg_broadcast, player->GetIp(), player->GetPort()); + for (auto &pair : client_list) { + auto player = pair.second; + if (player && player->PlayerId() != + primary_player_id_) { // Do not send it to the host + logic_manager_->world_->game_node_.send( + msg_broadcast, player->GetIp(), player->GetPort()); + } + } } - } - std::queue actors_to_remove; - for (auto &actor : actors_) { - if (actor.second->SyncedLogicWorldVersion() == - synced_logic_world_version_) { - actor.second->Update(delta_time); - } else { - actors_to_remove.push(actor.second); + std::queue actors_to_remove; + for (auto &actor : actors_) { + if (actor.second->SyncedLogicWorldVersion() == + synced_logic_world_version_) { + actor.second->Update(delta_time); + } else { + actors_to_remove.push(actor.second); + } } - } - while (!actors_to_remove.empty()) { - auto actor = actors_to_remove.front(); - actors_to_remove.pop(); - actors_.erase(actor->SyncedLogicWorldVersion()); - delete actor; - } + while (!actors_to_remove.empty()) { + auto actor = actors_to_remove.front(); + actors_to_remove.pop(); + actors_.erase(actor->SyncedLogicWorldVersion()); + delete actor; + } - auto actor = GetActor(primary_player_primary_unit_object_id_); - if (actor) { - camera_controller_->SetCenter(actor->Position()); + auto actor = GetActor(primary_player_primary_unit_object_id_); + if (actor) { + camera_controller_->SetCenter(actor->Position()); + } + camera_controller_->Update(delta_time); } - camera_controller_->Update(delta_time); } - void GameBall::OnRender() { auto cmd_buffer = VkCore()->CommandBuffer(); Renderer()->RenderPipeline()->Render(cmd_buffer->Handle(), *scene_, *camera_, diff --git a/src/GameBall/core/packet_tool.h b/src/GameBall/core/packet_tool.h new file mode 100644 index 0000000..6625029 --- /dev/null +++ b/src/GameBall/core/packet_tool.h @@ -0,0 +1,85 @@ +#include +#include +#ifndef GAMEX_PACKET_TOOL_H +#define GAMEX_PACKET_TOOL_H + +namespace GameBall { + +std::string PlayerInputToString(const Logic::PlayerInput &input) { + std::string str; + str += input.move_forward ? '1' : '0'; + str += input.move_backward ? '1' : '0'; + str += input.move_left ? '1' : '0'; + str += input.move_right ? '1' : '0'; + str += input.brake ? '1' : '0'; + str += input.jump ? '1' : '0'; + str += input.jump_released ? '1' : '0'; + + std::ostringstream oss; + oss << "(" << input.orientation.x << "," << input.orientation.y << "," + << input.orientation.z << ")"; + str += oss.str(); + + return str; +} + +Logic::PlayerInput StringToPlayerInput(const std::string &str) { + Logic::PlayerInput input; + input.move_forward = (str[0] == '1'); + input.move_backward = (str[1] == '1'); + input.move_left = (str[2] == '1'); + input.move_right = (str[3] == '1'); + input.brake = (str[4] == '1'); + input.jump = (str[5] == '1'); + input.jump_released = (str[6] == '1'); + + std::istringstream iss(str.substr(7)); + char left_bracket, comma1, comma2, right_bracket; + iss >> left_bracket >> input.orientation.x >> comma1 >> input.orientation.y >> + comma2 >> input.orientation.z >> right_bracket; + + return input; +} + +// Credit to Yuchen Yang +// Ball State Compression Module +// Convert a 18*float array to a 96-base64 string + +#define BLOCK_SIZE 12 +char _str_id[255], _str_tp[64]; +void base64Init() { + for (int i = 'A'; i <= 'Z'; ++i) + _str_tp[_str_id[i] = i - 'A'] = i; + for (int i = 'a'; i <= 'z'; ++i) + _str_tp[_str_id[i] = i - 'a' + 26] = i; + for (int i = '0'; i <= '9'; ++i) + _str_tp[_str_id[i] = i - '0' + 52] = i; + _str_tp[_str_id['+'] = 62] = '+'; + _str_tp[_str_id['/'] = 63] = '/'; +} +std::string encode_state(float *data) { + uint8_t *p = (uint8_t *)data; + std::string q; + for (int i = 0; i < BLOCK_SIZE; i++) { + uint64_t now = 0; + for (int j = 0; j < 6; j++) + now = now << 8 | p[i * 6 + j]; + for (int j = 0; j < 8; j++) + q += _str_tp[now >> (j * 6) & 63]; + } + return q; +} +void decode_state(std::string s, float *ret) { + uint8_t *p = (uint8_t *)ret; + for (int i = 0; i < BLOCK_SIZE; i++) { + uint64_t now = 0; + for (int j = 7; j >= 0; j--) + now = now << 6 | _str_id[s[i * 8 + j]]; + for (int j = 0; j < 6; j++) + p[i * 6 + 5 - j] = now >> (j * 8) & 255; + } +} + +} // namespace GameBall + +#endif // GAMEX_PACKET_TOOL_H diff --git a/src/GameBall/logic/player_input.cpp b/src/GameBall/logic/player_input.cpp index 9208d35..954d5b2 100644 --- a/src/GameBall/logic/player_input.cpp +++ b/src/GameBall/logic/player_input.cpp @@ -34,4 +34,26 @@ PlayerInput PlayerInputController::GetInput() { return result; } +bool operator==(const Logic::PlayerInput& lhs, const Logic::PlayerInput& rhs) { + // Compare each member of Logic::PlayerInput + return lhs.move_forward == rhs.move_forward + && lhs.move_backward == rhs.move_backward + && lhs.move_left == rhs.move_left + && lhs.move_right == rhs.move_right + && lhs.brake == rhs.brake + && lhs.jump == rhs.jump + && lhs.jump_released == rhs.jump_released; +} + +bool operator!=(const Logic::PlayerInput& lhs, const Logic::PlayerInput& rhs) { + // Compare each member of Logic::PlayerInput + return lhs.move_forward != rhs.move_forward + || lhs.move_backward != rhs.move_backward + || lhs.move_left != rhs.move_left + || lhs.move_right != rhs.move_right + || lhs.brake != rhs.brake + || lhs.jump != rhs.jump + || lhs.jump_released != rhs.jump_released; +} + } // namespace GameBall::Logic diff --git a/src/GameBall/logic/player_input.h b/src/GameBall/logic/player_input.h index e481988..3628621 100644 --- a/src/GameBall/logic/player_input.h +++ b/src/GameBall/logic/player_input.h @@ -17,6 +17,10 @@ struct PlayerInput { glm::vec3 orientation{0.0f, 0.0f, 1.0f}; }; +bool operator==(const Logic::PlayerInput& lhs, const Logic::PlayerInput& rhs); + +bool operator!=(const Logic::PlayerInput& lhs, const Logic::PlayerInput& rhs); + class PlayerInputController { public: PlayerInputController(GameBall *app); diff --git a/src/GameBall/logic/units/regular_ball.cpp b/src/GameBall/logic/units/regular_ball.cpp index f794d75..70e1cf2 100644 --- a/src/GameBall/logic/units/regular_ball.cpp +++ b/src/GameBall/logic/units/regular_ball.cpp @@ -3,6 +3,8 @@ #include "GameBall/core/game_ball.h" #include "GameBall/logic/world.h" +extern bool is_server; + namespace GameBall::Logic::Units { RegularBall::RegularBall(World *world, uint64_t player_id, @@ -27,7 +29,6 @@ RegularBall::RegularBall(World *world, } RegularBall::~RegularBall() { - ; } SYNC_ACTOR_FUNC(RegularBall) { @@ -44,63 +45,65 @@ void RegularBall::UpdateTick() { float delta_time = world_->TickDeltaT(); auto physics_world = world_->PhysicsWorld(); auto &sphere = physics_world->GetSphere(sphere_id_); + if(is_server){ + if (position_.y < -10.0f) { + position_ = + glm::vec3{0.0f, 10.0f, 0.0f}; + sphere.position = position_; + velocity_ = glm::vec3{0.0f}; + sphere.velocity = velocity_; + augular_momentum_ = glm::vec3{0.0f}; + sphere.angular_velocity = sphere.inertia_inv * augular_momentum_; + } - if (position_.y < -10.0f) { - position_ = - glm::vec3{0.0f, 10.0f, 0.0f}; - sphere.position = position_; - velocity_ = glm::vec3{0.0f}; - sphere.velocity = velocity_; - augular_momentum_ = glm::vec3{0.0f}; - sphere.angular_velocity = sphere.inertia_inv * augular_momentum_; - } - - auto owner = world_->GetPlayer(player_id_); - if (owner) { - if (UnitId() == owner->PrimaryUnitId()) { - auto input = owner->TakePlayerInput(); - - glm::vec3 forward = glm::normalize(glm::vec3{input.orientation}); - glm::vec3 right = - glm::normalize(glm::cross(forward, glm::vec3{0.0f, 1.0f, 0.0f})); + auto owner = world_->GetPlayer(player_id_); + if (owner) { + if (UnitId() == owner->PrimaryUnitId()) { + auto input = owner->PlayerId() == 1 ? owner->TakePlayerInput() : owner->GetPlayerInput(); - glm::vec3 moving_direction{}; + glm::vec3 forward = glm::normalize(glm::vec3{input.orientation}); + glm::vec3 right = + glm::normalize(glm::cross(forward, glm::vec3{0.0f, 1.0f, 0.0f})); - float angular_acceleration = glm::radians(2880.0f); + glm::vec3 moving_direction{}; - if (input.move_forward) { - moving_direction -= right; - } - if (input.move_backward) { - moving_direction += right; - } - if (input.move_left) { - moving_direction -= forward; - } - if (input.move_right) { - moving_direction += forward; - } + float angular_acceleration = glm::radians(2880.0f); - if (input.jump) { - if(sphere.position.y < 1.01f && sphere.position.y > 0.99f) - sphere.velocity.y += 6.0f; // Adjust the force as needed - } + if (input.move_forward) { + moving_direction -= right; + } + if (input.move_backward) { + moving_direction += right; + } + if (input.move_left) { + moving_direction -= forward; + } + if (input.move_right) { + moving_direction += forward; + } - // Only update angular velocity when the ball is grounded - if (sphere.position.y < 1.01f && sphere.position.y > 0.99f) { - if (glm::length(moving_direction) > 0.0f) { - moving_direction = glm::normalize(moving_direction); - sphere.angular_velocity += - moving_direction * angular_acceleration * delta_time; + if (input.jump) { + if(sphere.position.y < 1.01f && sphere.position.y > 0.99f) + sphere.velocity.y += 6.0f; // Adjust the force as needed } - if (input.brake) { - sphere.angular_velocity = glm::vec3{0.0f}; + // Only update angular velocity when the ball is grounded + if (sphere.position.y < 1.01f && sphere.position.y > 0.99f) { + if (glm::length(moving_direction) > 0.0f) { + moving_direction = glm::normalize(moving_direction); + sphere.angular_velocity += + moving_direction * angular_acceleration * delta_time; + } + + if (input.brake) { + sphere.angular_velocity = glm::vec3{0.0f}; + } } } } } + sphere.velocity *= std::pow(0.5f, delta_time); // Only update angular velocity when the ball is grounded if (sphere.position.y < 1.01f && sphere.position.y > 0.99f) { @@ -164,5 +167,10 @@ glm::mat3 RegularBall::Orientation() const { glm::vec3 RegularBall::AngularMomentum() const { return augular_momentum_; } +void RegularBall::setAngularMomentum(const glm::f32 a, const glm::f32 b, const glm::f32 c) { + augular_momentum_[0] = a; + augular_momentum_[1] = b; + augular_momentum_[2] = c; +} } // namespace GameBall::Logic::Units diff --git a/src/GameBall/logic/units/regular_ball.h b/src/GameBall/logic/units/regular_ball.h index 4aebf2b..70fa719 100644 --- a/src/GameBall/logic/units/regular_ball.h +++ b/src/GameBall/logic/units/regular_ball.h @@ -30,6 +30,7 @@ class RegularBall : public Unit { glm::vec3 Velocity() const; glm::mat3 Orientation() const; glm::vec3 AngularMomentum() const; + void setAngularMomentum(glm::f32 a, glm::f32 b, glm::f32 c); private: float radius_{1.0f}; diff --git a/src/GameBall/logic/world.cpp b/src/GameBall/logic/world.cpp index b1b9f84..a48021e 100644 --- a/src/GameBall/logic/world.cpp +++ b/src/GameBall/logic/world.cpp @@ -2,6 +2,8 @@ #include "GameBall/core/game_ball.h" +extern bool is_server; + namespace GameBall::Logic { World::World() { @@ -76,16 +78,20 @@ void World::UnregisterPlayer(uint64_t player_id) { void World::UpdateTick() { // LAND_INFO("Update Tick... {}", world_version_); - - physics_world_->ApplyGravity(TickDeltaT()); - - physics_world_->SolveCollisions(); + if (is_server) { + physics_world_->ApplyGravity(TickDeltaT()); + physics_world_->SolveCollisions(); + } for (auto &pair : object_map_) { pair.second->UpdateTick(); } - physics_world_->SolveCollisions(); + + if (is_server) { + physics_world_->SolveCollisions(); + } + physics_world_->Update(TickDeltaT()); world_version_++; diff --git a/src/GameBall/main.cpp b/src/GameBall/main.cpp index f6dbdd9..59b2bd6 100644 --- a/src/GameBall/main.cpp +++ b/src/GameBall/main.cpp @@ -12,10 +12,12 @@ ABSL_FLAG(bool, fullscreen, false, "Run in fullscreen mode."); // Width and Height ABSL_FLAG(int, width, -1, "Width of the window."); ABSL_FLAG(int, height, -1, "Height of the window."); -ABSL_FLAG(std::string, mode, "local", "Game mode"); +ABSL_FLAG(std::string, mode, "", "Game mode"); ABSL_FLAG(std::string, address, "", "Server address"); ABSL_FLAG(int, port, DEFAULT_PORT, "Server Port"); +bool is_server = false; + int main(int argc, char *argv[]) { absl::ParseCommandLine(argc, argv); @@ -26,6 +28,10 @@ int main(int argc, char *argv[]) { settings.mode = absl::GetFlag(FLAGS_mode); settings.address = absl::GetFlag(FLAGS_address); settings.port = absl::GetFlag(FLAGS_port); + + if(settings.mode == "room"){ + is_server = true; + } GameBall::GameBall game(settings); game.Run(); return 0; diff --git a/src/GameX/physics/world.cpp b/src/GameX/physics/world.cpp index 6a68a67..c67b694 100644 --- a/src/GameX/physics/world.cpp +++ b/src/GameX/physics/world.cpp @@ -27,6 +27,10 @@ uint64_t World::CreateSphere(float radius, float mass) { return id; } +void World::DeleteSphere(uint64_t id) { + spheres_.erase(id); +} + uint64_t World::CreateCube(float side_length, float mass) { uint64_t id = next_cube_id_++; cubes_.emplace(id, Cube(mass, side_length)); diff --git a/src/GameX/physics/world.h b/src/GameX/physics/world.h index fe902b1..c69703b 100644 --- a/src/GameX/physics/world.h +++ b/src/GameX/physics/world.h @@ -13,6 +13,8 @@ class World { uint64_t CreateSphere(float radius = 1.0f, float mass = 1.0f); + void DeleteSphere(uint64_t id); + uint64_t CreateCube(float side_length = 1.0f, float mass = 1.0f); Sphere &GetSphere(uint64_t id); diff --git a/test/regular_ball_test.cpp b/test/regular_ball_test.cpp index a444926..9aaaa3e 100644 --- a/test/regular_ball_test.cpp +++ b/test/regular_ball_test.cpp @@ -6,6 +6,8 @@ #include "GameBall/logic/obstacles/obstacles.h" #include "GameBall/logic/units/units.h" +bool is_server = true; + void TestPlayerInput(GameBall::Logic::PlayerInput input, glm::vec3 expected_position) { GameBall::Logic::World world; From 014acc84b292d550184187281a45ce43e23ead44 Mon Sep 17 00:00:00 2001 From: lemon <44695478+lemonchu@users.noreply.github.com> Date: Tue, 16 Jan 2024 22:17:20 +0800 Subject: [PATCH 8/8] Update p2pnode.cpp --- src/GameBall/core/p2pnode.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GameBall/core/p2pnode.cpp b/src/GameBall/core/p2pnode.cpp index fcec99b..0d1f942 100644 --- a/src/GameBall/core/p2pnode.cpp +++ b/src/GameBall/core/p2pnode.cpp @@ -47,7 +47,7 @@ void P2PNode::initialize(int port) { is_initialized = true; } -void P2PNode::send(const std::string& message, const std::string& ip, int port) const { +void P2PNode::send(const std::string& message, const std::string& ip, uint16_t port) const { if (!is_initialized) { printf("Node is not initialized.\n"); return; @@ -63,7 +63,7 @@ void P2PNode::send(const std::string& message, const std::string& ip, int port) (struct sockaddr *)&dest_addr, sizeof(dest_addr)); } -std::tuple P2PNode::receive() { +std::tuple P2PNode::receive() { if (!is_initialized) { printf("Node is not initialized.\n"); return std::make_tuple("", "", 0); @@ -254,4 +254,4 @@ std::vector getLocalIPs() { // Linux implementation #else // Other OS implementation -#endif \ No newline at end of file +#endif