#include #include #include #include #include #include #include "game_client.h" #include "game/remote_player.h" #include "network/game_state_message.h" #include "network/message.h" GameClient::GameClient(void) : _our_player_id(0), _input_sequence_number(0) { memset(&_server_addr, 0, sizeof(_server_addr)); } bool GameClient::connect(const char* host, unsigned short port) { if(!_tcp_socket.create() || !_tcp_socket.connect(host,port)) { perror("BettolaClient: Failed to connect TCP socket.\n"); return false; } if(!_udp_socket.create()) { perror("BettolaClient: Failed to create UDP socket.\n"); return false; } /* Setup server address. */ _server_addr.sin_family = AF_INET; _server_addr.sin_port = htons(port); inet_pton(AF_INET, host, &_server_addr.sin_addr); /* Set the socket to non-blocking. */ int tcp_flags = fcntl(_tcp_socket.get_sockfd(), F_GETFL, 0); fcntl(_tcp_socket.get_sockfd(), F_SETFL, tcp_flags | O_NONBLOCK); int udp_flags = fcntl(_udp_socket.get_sockfd(), F_GETFL, 0); fcntl(_udp_socket.get_sockfd(), F_SETFL, udp_flags | O_NONBLOCK); printf("BettolaClient: Connected to server at %s:%hu\n", host, port); return true; } void GameClient::process_network_messages(void) { fd_set read_fds; FD_ZERO(&read_fds); int tcp_fd = _tcp_socket.get_sockfd(); int udp_fd = _udp_socket.get_sockfd(); FD_SET(tcp_fd, &read_fds); FD_SET(udp_fd, &read_fds); int max_fd = std::max(tcp_fd, udp_fd); struct timeval tv = {0,0}; /* Non-blocking. */ if(select(max_fd+1, &read_fds, nullptr, nullptr, &tv) > 0) { if(FD_ISSET(tcp_fd, &read_fds) && _our_player_id == 0) { BettolaLib::Network::MessageHeader header; if(_tcp_socket.recv(&header, sizeof(header)) > 0 && header.type == BettolaLib::Network::MessageType::PlayerId) { _tcp_socket.recv(&_our_player_id, sizeof(_our_player_id)); printf("BetollaClient: Assigned player ID: %u\n", _our_player_id); } } if(FD_ISSET(udp_fd, &read_fds)) { _process_udp_messages(); } } } void GameClient::_process_udp_messages(void) { char buffer[1024]; sockaddr_in from_addr; ssize_t bytes_received; while((bytes_received = _udp_socket.recv_from(buffer,sizeof(buffer),from_addr))>0) { if(bytes_received < sizeof(BettolaLib::Network::MessageHeader)) continue; BettolaLib::Network::MessageHeader header; memcpy(&header, buffer, sizeof(header)); const char* payload = buffer + sizeof(BettolaLib::Network::MessageHeader); size_t payload_size = bytes_received-sizeof(BettolaLib::Network::MessageHeader); if(header.type == BettolaLib::Network::MessageType::GameState) { if(payload_size >= sizeof(BettolaLib::Network::GameStateMessage)) { BettolaLib::Network::GameStateMessage msg; memcpy(&msg, payload, sizeof(msg)); _process_game_state(msg); } } } } void GameClient::_process_game_state(const BettolaLib::Network::GameStateMessage& msg) { /* Track remote players each update. */ std::set players_in_message; for(unsigned int i = 0; i < msg.num_players; ++i) { const auto& ps = msg.players[i]; if(ps.player_id == _our_player_id) { /* This is our player. Reconcile. */ _player.set_position(ps.x, ps.y); /* Remove all inputs from our history that the server has processed. */ while(!_pending_inputs.empty() && _pending_inputs.front().sequence_number <= ps.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) { _player.apply_input(input); } } else { /* Remote player. Find if we already know about them.. */ players_in_message.insert(ps.player_id); auto it = std::find_if(_remote_players.begin(), _remote_players.end(), [&](const RemotePlayer&p) {return p.get_id() == ps.player_id; }); if(it != _remote_players.end()) { /* Found 'em! update their target pos. */ it->set_target_position(ps.x, ps.y); } else { /* They are new, add them to our list. */ _remote_players.emplace_back(ps.player_id, ps.x, ps.y); } } } _remote_players.erase(std::remove_if(_remote_players.begin(), _remote_players.end(), [&](const RemotePlayer& p) { return players_in_message.find(p.get_id()) == players_in_message.end(); }), _remote_players.end()); } void GameClient::send_input(const Player::InputState& input, float dt) { if(_our_player_id > 0) { /* 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::PlayerInput; header.size = sizeof(input_msg); char buffer[sizeof(header) + sizeof(input_msg)]; memcpy(buffer, &header, sizeof(header)); memcpy(buffer+sizeof(header), &input_msg, sizeof(input_msg)); _udp_socket.send_to(buffer, sizeof(buffer), _server_addr); _pending_inputs.push_back(input_msg); /* Store for reconciliation. */ } } void GameClient::update_players(float dt) { _player.update(dt); for(auto& p : _remote_players) { p.update(dt); } }