diff --git a/src/GameBall/CMakeLists.txt b/src/GameBall/CMakeLists.txt index b3dd846..eba800d 100644 --- a/src/GameBall/CMakeLists.txt +++ b/src/GameBall/CMakeLists.txt @@ -1,6 +1,8 @@ file(GLOB_RECURSE SOURCES *.cpp *.h) -add_executable(GameBall ${SOURCES}) +add_executable(GameBall ${SOURCES} + core/p2pnode.cpp + core/p2pnode.h) target_link_libraries(GameBall PRIVATE GameX absl::flags absl::flags_parse) # Delete main.cpp from sources diff --git a/src/GameBall/core/camera_third_person.cpp b/src/GameBall/core/camera_third_person.cpp index 3175bca..0f520df 100644 --- a/src/GameBall/core/camera_third_person.cpp +++ b/src/GameBall/core/camera_third_person.cpp @@ -99,4 +99,12 @@ void CameraControllerThirdPerson::CursorMove(float x, float y) { dst_pitch_ += y * 0.1f; dst_pitch_ = glm::clamp(dst_pitch_, -89.0f, 89.0f); } + +void CameraControllerThirdPerson::CursorScroll(float x, float y) { + float target_diatance_ = dst_distance_ + y; + target_diatance_ = glm::clamp(target_diatance_, 5.0f, 35.0f); + + dst_distance_ += (target_diatance_ - dst_distance_) * 0.3f; +} + } // namespace GameBall diff --git a/src/GameBall/core/camera_third_person.h b/src/GameBall/core/camera_third_person.h index 463242f..d7f146d 100644 --- a/src/GameBall/core/camera_third_person.h +++ b/src/GameBall/core/camera_third_person.h @@ -33,6 +33,8 @@ class CameraControllerThirdPerson { void CursorMove(float x, float y); + void CursorScroll(float x,float y); + private: GameX::Graphics::PCamera camera_; float dst_distance_{10.0f}; diff --git a/src/GameBall/core/game_ball.cpp b/src/GameBall/core/game_ball.cpp index a47b79e..4f0d2b3 100644 --- a/src/GameBall/core/game_ball.cpp +++ b/src/GameBall/core/game_ball.cpp @@ -1,13 +1,212 @@ #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 listener; +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.speed_up ? '1' : '0'; + str += input.brake ? '1' : '0'; + str += input.left_arrow ? '1' : '0'; + str += input.right_arrow ? '1' : '0'; + str += input.grow ? '1' : '0'; + str += input.shrink ? '1' : '0'; + str += input.restart ? '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.speed_up = (str[4] == '1'); + input.brake = (str[5] == '1'); + input.left_arrow = (str[6] == '1'); + input.right_arrow = (str[7] == '1'); + input.grow = (str[8] == '1'); + input.shrink = (str[9] == '1'); + input.restart = (str[10] == '1'); + + std::istringstream iss(str.substr(11)); + 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; +} + +static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + } + + return ret; +} + +std::vector base64_decode(std::string const& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::vector ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = std::find(base64_chars, base64_chars + 64, char_array_4[i]) - base64_chars; + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret.push_back(char_array_3[i]); + i = 0; + } + } + + if (i) { + for (j = 0; j < i; j++) + char_array_4[j] = std::find(base64_chars, base64_chars + 64, char_array_4[j]) - base64_chars; + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + + for (j = 0; (j < i - 1); j++) + ret.push_back(char_array_3[j]); + } + + return ret; +} + +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 +226,32 @@ 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. + } + + float ground_size = 40.0f; + scene_->SetEnvmapImage(asset_manager_->ImageFile("textures/envmap.hdr")); ambient_light_ = scene_->CreateLight(); @@ -35,19 +260,43 @@ 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); auto primary_obstacle = world->CreateObstacle( - glm::vec3{0.0f, -10.0f, 0.0f}, std::numeric_limits::infinity(), - false, 20.0f); + glm::vec3{0.0f, -ground_size, 0.0f}, + std::numeric_limits::infinity(), false, 2 * ground_size); - primary_player_id_ = primary_player->PlayerId(); + 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); + listener = std::move(t1); + listener.detach(); + } - primary_player->SetPrimaryUnit(primary_unit->UnitId()); + 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()); + } VkExtent2D extent = FrameExtent(); float aspect = static_cast(extent.width) / extent.height; @@ -76,6 +325,11 @@ void GameBall::OnCleanup() { actors_.erase(actor->SyncedLogicWorldVersion()); delete actor; } + + is_running.store(false); + if (listener.joinable()) { + listener.join(); + } } void GameBall::OnUpdate() { @@ -101,6 +355,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()) + ":" + base64_encode(reinterpret_cast(ball_data), sizeof(float)*48) + ","; + } + } + + 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; @@ -134,6 +442,7 @@ void GameBall::OnRender() { OutputImage(cmd_buffer->Handle(), film_->output_image.get()); } + void GameBall::CursorPosCallback(double xpos, double ypos) { static double last_xpos = xpos; static double last_ypos = ypos; @@ -150,4 +459,11 @@ void GameBall::CursorPosCallback(double xpos, double ypos) { ignore_next_mouse_move_ = false; } +void GameBall::ScrollCallback(double xoffset, double yoffset) { + if (!ignore_next_mouse_move_) { + camera_controller_->CursorScroll(xoffset, yoffset); + } + ignore_next_mouse_move_ = false; +} + } // namespace GameBall diff --git a/src/GameBall/core/game_ball.h b/src/GameBall/core/game_ball.h index 3da4bd6..3b89ccf 100644 --- a/src/GameBall/core/game_ball.h +++ b/src/GameBall/core/game_ball.h @@ -46,11 +46,14 @@ class GameBall : public GameX::Base::Application { } void CursorPosCallback(double xpos, double ypos) override; + void ScrollCallback(double xoffset, double yoffset) override; CameraControllerThirdPerson *CameraController() { return camera_controller_.get(); } + void receivePackets(); + private: friend class Logic::Manager; friend class Logic::World; @@ -70,6 +73,7 @@ class GameBall : public GameX::Base::Application { std::unique_ptr camera_controller_; uint64_t primary_player_id_{0}; + uint64_t enemy_player_id_{0}; uint64_t primary_player_primary_unit_object_id_{0}; bool ignore_next_mouse_move_{true}; diff --git a/src/GameBall/core/p2pnode.cpp b/src/GameBall/core/p2pnode.cpp new file mode 100644 index 0000000..d965a61 --- /dev/null +++ b/src/GameBall/core/p2pnode.cpp @@ -0,0 +1,246 @@ +#include "p2pnode.h" +#ifdef _WIN32 +p2pnode::p2pnode() : is_initialized(false), sockfd(INVALID_SOCKET) { + // Init Winsock + WSADATA wsadata; + int iRes = WSAStartup(MAKEWORD(2, 2), &wsadata); + if (iRes) { + std::cerr << "WSAStartUp failed: " << iRes << std::endl; + exit(1); + } +} + +p2pnode::~p2pnode() { + closesocket(sockfd); + WSACleanup(); +} + +void p2pnode::initialize(uint16_t 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 &mes, + const std::string &ip, + uint16_t port) const { + if (!is_initialized) { + std::cerr << "Node is not initialized." << std::endl; + 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, mes.c_str(), mes.length(), 0, (struct sockaddr *)&dest_addr, + sizeof(dest_addr)); +} + +std::tuple p2pnode::receive() const { + if (!is_initialized) { + std::cerr << "Node is not initialized." << std::endl; + 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) { + std::cerr << "Receive failed: " << WSAGetLastError() << std::endl; + 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::isInit() const { + return is_initialized; +} + +#elif __APPLE__ || __linux__ +p2pnode::p2pnode() : is_initialized(false), is_server(false), sockfd(-1) {} + +p2pnode::~p2pnode(){ + closeConnection(); +} + +void p2pnode::initialize(uint16_t 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& mes, 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, mes.c_str(), mes.length(), 0, + (struct sockaddr *)&dest_addr, sizeof(dest_addr)); +} + +std::tuple p2pnode::receive() const{ + 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::isInit() const { + return is_initialized; +} +#endif + +#ifdef _WIN32 +std::vector localIPs() { + std::vector ips; + ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO); + auto pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); + PIP_ADAPTER_INFO pAdapter = nullptr; + + 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.emplace_back(pAdapter->IpAddressList.IpAddress.String); + pAdapter = pAdapter->Next; + } + } + + if (pAdapterInfo) { + free(pAdapterInfo); + } + + return ips; +} +#elif __APPLE__ || __linux__ + +#define NI_MAXHOST 1025 +// macOS implementation +std::vector localIPs() { + 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.emplace_back(host); + } + } + } + + freeifaddrs(ifaddr); + return ips; +} + +#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..eead98f --- /dev/null +++ b/src/GameBall/core/p2pnode.h @@ -0,0 +1,55 @@ +#ifndef GAMEX_P2PNODE_H +#define GAMEX_P2PNODE_H + +const int MAX_PLAYER = 5; + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include + +#include +#pragma comment(lib, "iphlpapi.lib") +#pragma comment(lib, "ws2_32.lib") + +#elif __APPLE__ || __linux__ +#include +#include +#include +#include +#include +#include +#include + +#endif + +class p2pnode { + private: +#ifdef _WIN32 + SOCKET sockfd; +#elif __APPLE__ || __linux__ + int sockfd; +#endif + bool is_initialized; + struct sockaddr_in addr; + + public: + p2pnode(); + ~p2pnode(); + void initialize(uint16_t port); + [[nodiscard]] bool isInit() const; + void send(const std::string &mes, const std::string &ip, uint16_t port) const; + [[nodiscard]] std::tuple receive() const; + void closeConnection(); + bool is_server; +}; + +std::vector localIPs(); + +#endif // GAMEX_P2PNODE_H diff --git a/src/GameBall/logic/player.cpp b/src/GameBall/logic/player.cpp index 6668885..cc6e1c9 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..68bf229 100644 --- a/src/GameBall/logic/player.h +++ b/src/GameBall/logic/player.h @@ -20,6 +20,10 @@ 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 +31,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/player_input.cpp b/src/GameBall/logic/player_input.cpp index c002a91..64d2a21 100644 --- a/src/GameBall/logic/player_input.cpp +++ b/src/GameBall/logic/player_input.cpp @@ -12,7 +12,13 @@ PlayerInput PlayerInputController::GetInput() { input_.move_backward = (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS); input_.move_left = (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS); input_.move_right = (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS); + input_.speed_up = (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS); input_.brake = (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS); + input_.left_arrow = (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS); + input_.right_arrow = (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS); + input_.grow = (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS); + input_.shrink = (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS); + input_.restart = (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS); auto camera_controller = app_->CameraController(); auto pitch_yaw = camera_controller->GetPitchYaw(); auto pitch = pitch_yaw.x; diff --git a/src/GameBall/logic/player_input.h b/src/GameBall/logic/player_input.h index 98b60c0..4b0a658 100644 --- a/src/GameBall/logic/player_input.h +++ b/src/GameBall/logic/player_input.h @@ -11,7 +11,13 @@ struct PlayerInput { bool move_backward{false}; bool move_left{false}; bool move_right{false}; + bool speed_up{false}; bool brake{false}; + bool left_arrow{false}; + bool right_arrow{false}; + bool restart{false}; + bool grow{false}; + bool shrink{false}; 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 af1cc95..c0e3280 100644 --- a/src/GameBall/logic/units/regular_ball.cpp +++ b/src/GameBall/logic/units/regular_ball.cpp @@ -21,8 +21,8 @@ RegularBall::RegularBall(World *world, sphere.orientation = orientation_; sphere.velocity = velocity_; sphere.angular_velocity = glm::vec3{0.0f}; - sphere.elasticity = 1.0f; - sphere.friction = 10.0f; + sphere.elasticity = sphere.elasticities[sphere.type]; + sphere.friction = sphere.frictions[sphere.type]; sphere.gravity = glm::vec3{0.0f, -9.8f, 0.0f}; } @@ -33,63 +33,148 @@ RegularBall::~RegularBall() { SYNC_ACTOR_FUNC(RegularBall) { auto physics_world = world_->PhysicsWorld(); auto &sphere = physics_world->GetSphere(sphere_id_); - actor->SetMass(1.0f); + actor->SetMass(sphere.masses[sphere.type]); actor->SetGravity(glm::vec3{0.0f, -9.8f, 0.0f}); actor->SetTransform(glm::mat3{radius_}); actor->SetMotion(position_, velocity_, orientation_, augular_momentum_); actor->SetMomentOfInertia(sphere.inertia[0][0]); } +long double last_change_time = -1, cur_time = 0; +glm::vec3 player_pos = {0.0f, 0.0f, 0.0f}; void RegularBall::UpdateTick() { float delta_time = world_->TickDeltaT(); + cur_time += delta_time; 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}; - // } - // } - // } - - sphere.velocity *= std::pow(0.5f, delta_time); - 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; + bool restart = false; + float delta_v = 0.5f, delta_av = 0.2f; + auto owner = world_->GetPlayer(player_id_); + + if (owner) { + if (UnitId() == owner->PrimaryUnitId()) { + // Controls player ball + 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.left_arrow && (cur_time - last_change_time >= 1.0) && + sphere.radius == 1.0f) { + sphere.type = (sphere.type - 1) < 0 ? 2 : (sphere.type - 1); + last_change_time = cur_time; + LAND_INFO("Sphere changed into type {}.", sphere.type); + } + if (input.right_arrow && (cur_time - last_change_time >= 1.0) && + sphere.radius == 1.0f) { + sphere.type = (sphere.type + 1) % 3; + last_change_time = cur_time; + LAND_INFO("Sphere changed into type {}.", sphere.type); + } + if (input.shrink && (cur_time - last_change_time >= 1.0) && + sphere.radius > 0.25f) { + sphere.position = + sphere.position + glm::vec3{0.0f, -0.5f * sphere.radius, 0.0f}; + sphere.radius *= 0.5f; + sphere.mass *= 0.125f; + last_change_time = cur_time; + LAND_INFO("Sphere shrank."); + } + if (input.grow && (cur_time - last_change_time >= 1.0) && + sphere.radius < 4.0f) { + sphere.position = + sphere.position + glm::vec3{0.0f, sphere.radius, 0.0f}; + sphere.radius *= 2.0f; + sphere.mass *= 8.0f; + last_change_time = cur_time; + LAND_INFO("Sphere enlarged."); + } + if (input.brake) { + sphere.angular_velocity *= 0.7f; + input.speed_up = false; + } else if (input.speed_up) { + sphere.velocity *= std::pow(3 * delta_v, delta_time); + sphere.angular_velocity *= std::pow(3 * delta_av, delta_time); + } else { + sphere.velocity *= std::pow(delta_v, delta_time); + sphere.angular_velocity *= std::pow(delta_av, delta_time); + } + restart = input.restart || sphere.position.y <= -7.0f; + if (restart) { + LAND_INFO("You LOSE~"); + sphere.position = glm::vec3{0.0f, sphere.radius + 0.1f, 0.0f}; + } + + if (glm::length(moving_direction) > 0.0f) { + moving_direction = glm::normalize(moving_direction); + sphere.angular_velocity += + moving_direction * angular_acceleration * delta_time; + } + + sphere.elasticity = sphere.elasticities[sphere.type]; + sphere.friction = sphere.frictions[sphere.type]; + sphere.mass = sphere.masses[sphere.type]; + player_pos = sphere.position; + } else { + // Controls enemy ball + if(cur_time>=5.0){ + glm::vec3 forward = glm::normalize(player_pos - sphere.position); + glm::vec3 back = + glm::normalize(glm::vec3{0.0f, 0.0f, 0.0f} - sphere.position); + glm::vec3 right = + glm::normalize(glm::cross(forward, glm::vec3{0.0f, 1.0f, 0.0f})); + back = glm::normalize(glm::cross(back, glm::vec3{0.0f, 1.0f, 0.0f})); + glm::vec3 moving_direction{}; + if (abs(sphere.position.x) <= 38.0f && abs(sphere.position.z) <= 38.0f) + moving_direction -= right; + else + moving_direction -= back; + float angular_acceleration = glm::radians(2880.0f); + + if (glm::length(moving_direction) > 0.0f) { + moving_direction = glm::normalize(moving_direction); + sphere.angular_velocity += + moving_direction * angular_acceleration * delta_time; + } + } + + sphere.velocity *= std::pow(delta_v, delta_time); + sphere.angular_velocity *= std::pow(delta_av, delta_time); + if (sphere.position.y <= -7.0f) { + LAND_INFO("You WON!!"); + sphere.position = glm::vec3{-5.0f, sphere.radius + 0.1f, 0.0f}; + restart = true; + } + } + } + + if (!restart) { + position_ = sphere.position; + velocity_ = sphere.velocity; + orientation_ = sphere.orientation; + augular_momentum_ = sphere.inertia * sphere.angular_velocity; + } else { + velocity_ = sphere.velocity = glm::vec3{0.0f}; + augular_momentum_ = sphere.angular_velocity = glm::vec3{0.0f}; + orientation_ = sphere.orientation = glm::mat3{1.0f}; + } } void RegularBall::SetMass(float mass) { diff --git a/src/GameBall/logic/world.h b/src/GameBall/logic/world.h index 59dadf6..3ebdd65 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..0135699 100644 --- a/src/GameBall/main.cpp +++ b/src/GameBall/main.cpp @@ -1,14 +1,16 @@ #include "GameBall/core/game_ball.h" +#include "GameBall/core/p2pnode.h" #include "absl/flags/flag.h" #include "absl/flags/parse.h" // Use abseil flags to parse command line arguments. - 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, addr, "","Server address"); +ABSL_FLAG(int, port, DEFAULT_PORT, "Server Port"); int main(int argc, char *argv[]) { absl::ParseCommandLine(argc, argv); @@ -17,6 +19,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_addr); + 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..3b24358 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" +const uint16_t 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 { diff --git a/src/GameX/physics/sphere.h b/src/GameX/physics/sphere.h index d93532b..6c11561 100644 --- a/src/GameX/physics/sphere.h +++ b/src/GameX/physics/sphere.h @@ -10,5 +10,9 @@ struct Sphere : public RigidBody { void SetRadiusMass(float radius = 1.0f, float mass = 1.0f); float radius{1.0f}; + int type = 0; + float elasticities[3] = {1.0f, 2.0f, 3.0f}; + float masses[3] = {1.0f, 0.7f, 0.5f}; + float frictions[3] = {10.0f, 13.0f, 15.0f}; }; } // namespace GameX::Physics