diff --git a/libbettola/include/bettola/network/message.h b/libbettola/include/bettola/network/message.h index f07563e..ee881c2 100644 --- a/libbettola/include/bettola/network/message.h +++ b/libbettola/include/bettola/network/message.h @@ -4,7 +4,7 @@ namespace BettolaLib { namespace Network { enum class MessageType : unsigned char { - PlayerPosition, + PlayerInput, PlayerState, GameState, PlayerId, diff --git a/libbettola/include/bettola/network/player_pos_message.h b/libbettola/include/bettola/network/player_input_message.h similarity index 57% rename from libbettola/include/bettola/network/player_pos_message.h rename to libbettola/include/bettola/network/player_input_message.h index 77cd9ae..b60ae37 100644 --- a/libbettola/include/bettola/network/player_pos_message.h +++ b/libbettola/include/bettola/network/player_input_message.h @@ -5,10 +5,14 @@ namespace BettolaLib { namespace Network { -struct PlayerPosMessage { +struct PlayerInputMessage { unsigned int player_id; - float x; - float y; + unsigned int sequence_number; + bool up; + bool down; + bool left; + bool right; + float dt; }; } /* namespace Network. */ diff --git a/libbettola/include/bettola/network/player_state_message.h b/libbettola/include/bettola/network/player_state_message.h index 02c200c..2acf7b0 100644 --- a/libbettola/include/bettola/network/player_state_message.h +++ b/libbettola/include/bettola/network/player_state_message.h @@ -9,6 +9,7 @@ struct PlayerStateMessage { unsigned int player_id; float x; float y; + unsigned int last_processed_sequence; }; } /* namespace Network. */ diff --git a/src/bettola.cpp b/src/bettola.cpp index d9f44fd..4f9828a 100644 --- a/src/bettola.cpp +++ b/src/bettola.cpp @@ -20,7 +20,7 @@ #include "math/mat4.h" #include "network/message.h" #include "network/game_state_message.h" -#include "network/player_pos_message.h" +#include "network/player_input_message.h" /* Dacav's resolution ;) */ const int SCREEN_WIDTH = 800; @@ -33,9 +33,7 @@ Bettola::Bettola(void) : _vao(0), _vbo(0), _our_player_id(0), - _needs_correction(false), - _correction_target_x(0.0f), - _correction_target_y(0.0f) { + _input_sequence_number(0) { memset(&_server_addr, 0, sizeof(_server_addr)); } @@ -203,41 +201,33 @@ void Bettola::update(double dt) { _player.update(dt); - if(_needs_correction) { - const float interp_speed = 20.0f; /* We'll use a slightly higher speed for self-correction. */ - float current_x = _player.get_x(); - float current_y = _player.get_y(); - _player.set_position(current_x + (_correction_target_x - current_x) * interp_speed * dt, - current_y + (_correction_target_y - current_y) * interp_speed * dt); - - /* If we are very close.. Stop correcting. */ - if(std::abs(current_x - _correction_target_x) < 0.1f && - std::abs(current_y - _correction_target_y) < 0.1f) { - _needs_correction = false; - } - } - for(auto& remote_player : _remote_players) { remote_player.update(dt); } if(_our_player_id > 0) { - /* Send player position to server via UDP.. */ + /* Sent our current input state to the server. */ + BettolaLib::Network::PlayerInputMessage input_msg; + input_msg.player_id = _our_player_id; + input_msg.sequence_number = ++_input_sequence_number; + input_msg.up = _input.up; + input_msg.down = _input.down; + input_msg.left = _input.left; + input_msg.right = _input.right; + input_msg.dt = dt; + BettolaLib::Network::MessageHeader header; - header.type = BettolaLib::Network::MessageType::PlayerPosition; - header.size = sizeof(BettolaLib::Network::PlayerPosMessage); + header.type = BettolaLib::Network::MessageType::PlayerInput; + header.size = sizeof(input_msg); - BettolaLib::Network::PlayerPosMessage msg; - msg.player_id = _our_player_id; - msg.x = _our_player_id; - msg.x = _player.get_x(); - msg.y = _player.get_y(); - - char buffer[sizeof(header) + sizeof(msg)]; + char buffer[sizeof(header) + sizeof(input_msg)]; memcpy(buffer, &header, sizeof(header)); - memcpy(buffer + sizeof(header), &msg, sizeof(msg)); + memcpy(buffer+sizeof(header), &input_msg, sizeof(input_msg)); _udp_socket.send_to(buffer, sizeof(buffer), _server_addr); + + /* Store for reconciliation. */ + _pending_inputs.push_back(input_msg); } static char window_title[256]; @@ -303,22 +293,24 @@ void Bettola::process_game_state(const BettolaLib::Network::GameStateMessage& ms const auto& player_state = msg.players[i]; if(player_state.player_id == _our_player_id) { - /* - * This is our player. Reconcile predicted state with the - * server's authoritative state. - */ - printf(" -> Processing remote player %u at (%.2f, %.2f)\n", player_state.player_id, - player_state.x, player_state.y); - /* This is us! Reconcile our predicted state. */ - float dx = _player.get_x() - player_state.x; - float dy = _player.get_y() - player_state.y; - if((dx*dx+dy*dy) > 0.0001f) { /* If distance is not negligibale. */ - /* Simple correction, snap to server position. */ - //_player.set_position(player_state.x, player_state.y); + /* This is our player. Reconcile. */ + _player.set_position(player_state.x, player_state.y); - _needs_correction = true; - _correction_target_x = player_state.x; - _correction_target_y = player_state.y; + /* Remove all inputs from our history that the server has processed. */ + while(!_pending_inputs.empty() && + _pending_inputs.front().sequence_number <= player_state.last_processed_sequence) { + + _pending_inputs.pop_front(); + } + + /* Now, re-apply any remaining inputs that the server hasn't + * seen yet. This'll bring the client up to date with most recent inputs. + */ + for(const auto& input : _pending_inputs) { + float dir_x = (input.right ? 1.0f : 0.0f) - (input.left ? 1.0f : 0.0f); + float dir_y = (input.down ? 1.0f : 0.0f) - (input.up ? 1.0f : 0.0f); + _player.set_velocity_direction(dir_x, dir_y); + _player.update(input.dt); } } else { /* Remote player. Find if we already know about them.. */ diff --git a/src/bettola.h b/src/bettola.h index 0339382..3143304 100644 --- a/src/bettola.h +++ b/src/bettola.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -9,6 +10,7 @@ #include "game/remote_player.h" #include "math/mat4.h" #include "network/tcpsocket.h" +#include "network/player_input_message.h" #include "network/udpsocket.h" namespace BettolaLib { namespace Network { struct GameStateMessage; } } @@ -61,7 +63,6 @@ private: BettolaLib::Network::UDPSocket _udp_socket; sockaddr_in _server_addr; - bool _needs_correction; - float _correction_target_x; - float _correction_target_y; + unsigned int _input_sequence_number; + std::deque _pending_inputs; }; diff --git a/srv/game/game.cpp b/srv/game/game.cpp index 2e4622f..ce469e7 100644 --- a/srv/game/game.cpp +++ b/srv/game/game.cpp @@ -4,7 +4,7 @@ #include "game.h" #include "network/game_state_message.h" -#include "network/player_pos_message.h" +#include "network/player_input_message.h" #include "network/tcpsocket.h" #include "network/message.h" #include "network/udpsocket.h" @@ -27,12 +27,18 @@ void Game::remove_player(unsigned int player_id) { _players.end()); } -void Game::handle_udp_message(const BettolaLib::Network::MessageHeader& header, - const char* buffer, const sockaddr_in& from_addr) { +void Game::process_udp_message(const char* buffer, size_t size, const sockaddr_in& from_addr) { + if(size < sizeof(BettolaLib::Network::MessageHeader)) return; - if(header.type == BettolaLib::Network::MessageType::PlayerPosition) { - BettolaLib::Network::PlayerPosMessage msg; - memcpy(&msg, buffer, sizeof(msg)); + BettolaLib::Network::MessageHeader header; + memcpy(&header, buffer, sizeof(header)); + + if(header.type == BettolaLib::Network::MessageType::PlayerInput) { + if(size < sizeof(BettolaLib::Network::MessageHeader) + + sizeof(BettolaLib::Network::PlayerInputMessage)) return; + + BettolaLib::Network::PlayerInputMessage msg; + memcpy(&msg, buffer + sizeof(header), sizeof(msg)); Player* player = get_player_by_id(msg.player_id); if(player) { @@ -40,7 +46,11 @@ void Game::handle_udp_message(const BettolaLib::Network::MessageHeader& header, printf("Bettola Server: Associated UDP address for player %u\n", msg.player_id); player->set_udp_addr(from_addr); } - player->set_position(msg.x, msg.y); + player->set_last_processed_sequence(msg.sequence_number); + float dir_x = (msg.right ? 1.0f : 0.0f) - (msg.left ? 1.0f : 0.0f); + float dir_y = (msg.down ? 1.0f : 0.0f) - (msg.up ? 1.0f : 0.0f); + player->set_velocity_direction(dir_x, dir_y); + player->update(msg.dt); } } } @@ -54,6 +64,7 @@ void Game::broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket) { msg.players[i].player_id = _players[i]->get_id(); msg.players[i].x = _players[i]->get_x(); msg.players[i].y = _players[i]->get_y(); + msg.players[i].last_processed_sequence = _players[i]->get_last_processed_sequence(); } BettolaLib::Network::MessageHeader header; diff --git a/srv/game/game.h b/srv/game/game.h index 99aff3d..ba5edd7 100644 --- a/srv/game/game.h +++ b/srv/game/game.h @@ -12,8 +12,7 @@ public: Player* add_player(BettolaLib::Network::TCPSocket* socket); void remove_player(unsigned int player_id); - void handle_udp_message(const BettolaLib::Network::MessageHeader& header, - const char* buffer, const sockaddr_in& from_addr); + void process_udp_message(const char* buffer, size_t size, const sockaddr_in& from_addr); void broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket); Player* get_player_by_socket(const BettolaLib::Network::TCPSocket* socket); diff --git a/srv/game/player.cpp b/srv/game/player.cpp index ddf908f..ad2dbb4 100644 --- a/srv/game/player.cpp +++ b/srv/game/player.cpp @@ -1,4 +1,5 @@ #include +#include #include "player.h" #include "network/tcpsocket.h" @@ -14,7 +15,25 @@ Player::Player(BettolaLib::Network::TCPSocket* socket) : _next_player_id = 1; } _id = _next_player_id++; - _x = 0.0f; _y = 0.0f; + _x = 0.0f; _y = 0.0f; + _vx = 0.0f; _vy = 0.0f; + _speed = 200.0f; /* Must match client! */ + _last_processed_sequence = 0; memset(&_udp_addr, 0, sizeof(_udp_addr)); } +void Player::update(double dt) { + _x += _vx * dt; + _y += _vy * dt; +} + +void Player::set_velocity_direction(float dir_x, float dir_y) { + if(dir_x == 0.0f && dir_y == 0.0f) { + _vx = _vy = 0.0f; + } else { + float mag = sqrt(dir_x * dir_x + dir_y * dir_y); + _vx = (dir_x / mag) * _speed; + _vy = (dir_y / mag) * _speed; + } +} + diff --git a/srv/game/player.h b/srv/game/player.h index c17833c..87114ae 100644 --- a/srv/game/player.h +++ b/srv/game/player.h @@ -8,6 +8,8 @@ class Player { public: Player(BettolaLib::Network::TCPSocket* socket); + void update(double dt); + void set_velocity_direction(float dir_x, float dir_y); void set_position(float x, float y) { _x = x; _y = y; } void set_udp_addr(const sockaddr_in& addr) { _udp_addr = addr; _has_udp_addr = true; } @@ -18,6 +20,8 @@ public: BettolaLib::Network::TCPSocket& get_socket(void) const { return *_socket; } const sockaddr_in& get_udp_addr(void) const { return _udp_addr; } bool has_udp_addr(void) const { return _has_udp_addr; } + void set_last_processed_sequence(unsigned int sequence) { _last_processed_sequence = sequence; } + unsigned int get_last_processed_sequence(void) const { return _last_processed_sequence; } private: static unsigned int _next_player_id; @@ -28,4 +32,7 @@ private: BettolaLib::Network::TCPSocket* _socket; sockaddr_in _udp_addr; bool _has_udp_addr; + unsigned int _last_processed_sequence; + float _vx, _vy; + float _speed; }; diff --git a/srv/main.cpp b/srv/main.cpp index 4daf2e7..85cad3e 100644 --- a/srv/main.cpp +++ b/srv/main.cpp @@ -12,7 +12,7 @@ #include "bettola/network/udpsocket.h" #include "bettola/network/net_common.h" #include "bettola/network/message.h" -#include "bettola/network/player_pos_message.h" +#include "bettola/network/player_input_message.h" #include "game/game.h" int main(void) { @@ -126,13 +126,7 @@ int main(void) { ssize_t bytes_received; while((bytes_received = udp_socket.recv_from(buffer, sizeof(buffer), from_addr)) > 0) { - if(bytes_received >= sizeof(BettolaLib::Network::MessageHeader)) { - BettolaLib::Network::MessageHeader header; - memcpy(&header, buffer, sizeof(header)); - if(bytes_received >= sizeof(BettolaLib::Network::MessageHeader) + header.size) { - game.handle_udp_message(header, buffer+sizeof(BettolaLib::Network::MessageHeader), from_addr); - } - } + game.process_udp_message(buffer, bytes_received, from_addr); } }