#include #include #include /* FINE LSP!! I'll play your games!!!! */ #include /* ~HJAPPY?!?!?! */ #include #include #include #include #include #include #include #include #include #include #include "bettola.h" #include "bettola/network/net_common.h" #include "game/remote_player.h" #include "math/mat4.h" #include "network/message.h" #include "network/game_state_message.h" #include "network/player_input_message.h" /* Dacav's resolution ;) */ const int SCREEN_WIDTH = 800; const int SCREEN_HEIGHT = 600; Bettola::Bettola(void) : _is_running(false), _window(nullptr), _gl_context(nullptr), _vao(0), _vbo(0), _our_player_id(0), _input_sequence_number(0) { memset(&_server_addr, 0, sizeof(_server_addr)); } Bettola::~Bettola(void) { if(_gl_context) { SDL_GL_DestroyContext(_gl_context); } /* Explicitly close client socket. */ _tcp_socket.close(); _udp_socket.close(); if(_window) { SDL_DestroyWindow(_window); } SDL_Quit(); if(_gl_context) { /* Should only be deleted if it was successfully created * and if _gl_context is valid. */ glDeleteVertexArrays(1, &_vao); glDeleteBuffers(1, &_vbo); } } int Bettola::run(void) { if(!init_sdl()) return -1; if(!create_window()) return -1; if(!create_gl_context()) return -1; if(!init_client_connection()) return -1; if(!init_glew()) return -1; if(!_shader.load_from_files("assets/shaders/simple.vert", "assets/shaders/simple.frag")) { return -1; } _projection = BettolaMath::Mat4::orthographic(0.0f, 800.0f, 600.0f, 0.0f, -1.0f, 1.0f); _shader.use(); unsigned int proj_loc = glGetUniformLocation(_shader.get_id(), "projection"); glUniformMatrix4fv(proj_loc, 1, GL_FALSE, _projection.get_ptr()); setup_quad(); glViewport(0,0,SCREEN_WIDTH, SCREEN_HEIGHT); _is_running = true; Uint64 perf_freq = SDL_GetPerformanceFrequency(); Uint64 last_count = SDL_GetPerformanceCounter(); double delta_time = 0; while(_is_running) { Uint64 current_counter = SDL_GetPerformanceCounter(); delta_time = (double)(current_counter-last_count) / (double)perf_freq; last_count = current_counter; process_events(); process_network(); update(delta_time); render(); } return 0; } void Bettola::process_events(void) { SDL_Event event; while(SDL_PollEvent(&event)) { if(event.type == SDL_EVENT_QUIT) { _is_running = false; } else if(event.type == SDL_EVENT_KEY_DOWN) { switch(event.key.key) { case SDLK_W: _input.up = true; break; case SDLK_S: _input.down = true; break; case SDLK_A: _input.left = true; break; case SDLK_D: _input.right = true; break; } } else if(event.type == SDL_EVENT_KEY_UP) { switch(event.key.key) { case SDLK_W: _input.up = false; break; case SDLK_S: _input.down = false; break; case SDLK_A: _input.left = false; break; case SDLK_D: _input.right = false; break; } } } } void Bettola::process_network(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; tv.tv_sec = 0; tv.tv_usec = 0; /* Non-blocking. */ int activity = select(max_fd+1, &read_fds, nullptr, nullptr, &tv); if(activity > 0) { if(FD_ISSET(tcp_fd, &read_fds)) { /* We only expect player ID assignment for now. */ if(_our_player_id == 0) { BettolaLib::Network::MessageHeader id_header; ssize_t id_bytes_received = _tcp_socket.recv(&id_header, sizeof(id_header)); if(id_bytes_received > 0 && id_header.type == BettolaLib::Network::MessageType::PlayerId) { _tcp_socket.recv(&_our_player_id, sizeof(_our_player_id)); printf("Bettola: Assigned player ID: %u\n", _our_player_id); } } } if(FD_ISSET(udp_fd, &read_fds)) { process_udp_messages(); } } } void Bettola::process_udp_messages(void) { char buffer[1024]; sockaddr_in from_addr; ssize_t id_bytes_received; while((id_bytes_received = _udp_socket.recv_from(buffer,sizeof(buffer),from_addr))>0) { if(id_bytes_received < sizeof(BettolaLib::Network::MessageHeader)) { continue; } BettolaLib::Network::MessageHeader header; memcpy(&header, buffer, sizeof(header)); memcpy(&header, buffer, sizeof(header)); const char* payload = buffer + sizeof(BettolaLib::Network::MessageHeader); size_t payload_size = id_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 Bettola::update(double dt) { float dir_x = 0.0f; float dir_y = 0.0f; if(_input.up) dir_y -= 1.0f; if(_input.down) dir_y += 1.0f; if(_input.left) dir_x -= 1.0f; if(_input.right) dir_x += 1.0f; _player.set_velocity_direction(dir_x, dir_y); _player.update(dt); for(auto& remote_player : _remote_players) { remote_player.update(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); /* Store for reconciliation. */ _pending_inputs.push_back(input_msg); } static char window_title[256]; static double time_since_title_update = 0.0; time_since_title_update += dt; if(time_since_title_update > 0.25) { snprintf(window_title, 256, "Bettola - FPS %.2f", 1.0/dt); SDL_SetWindowTitle(_window, window_title); time_since_title_update = 0.0; } } void Bettola::render(void) { glClearColor(0.1f, 0.1f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); _shader.use(); BettolaMath::Mat4 trans_matrix = BettolaMath::Mat4::translation(_player.get_x(), _player.get_y(), 0.0f); BettolaMath::Mat4 scale_matrix = BettolaMath::Mat4::scale(_player.get_width(), _player.get_height(), 1.0f); BettolaMath::Mat4 model= trans_matrix.multiply(scale_matrix); unsigned int model_loc = glGetUniformLocation(_shader.get_id(), "model"); glUniformMatrix4fv(model_loc, 1, GL_FALSE, model.get_ptr()); glBindVertexArray(_vao); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); /* Render remote players. */ for(const auto& remote_player : _remote_players) { BettolaMath::Mat4 remote_trans_matrix = BettolaMath::Mat4::translation(remote_player.get_x(), remote_player.get_y(), 0.0f); BettolaMath::Mat4 remote_scale_matrix = /* Assuming remote players have same size as local player. */ BettolaMath::Mat4::scale(50.0f, 50.0f, 1.0f); BettolaMath::Mat4 remote_model = remote_trans_matrix.multiply(remote_scale_matrix); glUniformMatrix4fv(model_loc, 1, GL_FALSE, remote_model.get_ptr()); glBindVertexArray(_vao); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); } SDL_GL_SwapWindow(_window); } void Bettola::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& player_state = msg.players[i]; if(player_state.player_id == _our_player_id) { /* This is our player. Reconcile. */ _player.set_position(player_state.x, 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.. */ players_in_message.insert(player_state.player_id); auto it = std::find_if(_remote_players.begin(), _remote_players.end(), [&](const RemotePlayer& p) { return p.get_id() == player_state.player_id; }); if(it != _remote_players.end()) { /* Found 'em! update their target pos. */ it->set_target_position(player_state.x, player_state.y); } else { /* They are new, add them to our list. */ _remote_players.emplace_back(player_state.player_id, player_state.x, player_state.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()); } bool Bettola::init_sdl(void) { if(!SDL_Init(SDL_INIT_VIDEO)) { fprintf(stderr, "Failed to iniit SDL! SDL ERROR: %s\n", SDL_GetError()); return false; } return true; } bool Bettola::init_glew(void) { glewExperimental = GL_TRUE; GLenum glew_error = glewInit(); if(glew_error != GLEW_OK) { fprintf(stderr, "Failed to init GLEW! %s\n", glewGetErrorString(glew_error)); return false; } return true; } bool Bettola::create_window(void) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); /* Don't you dare f.ckin fail to open!!!!! */ _window = SDL_CreateWindow("Bettola Makes No Sense!", SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL); if(!_window) { fprintf(stderr, "Failed to create window! SDL_ERROR: %s\n", SDL_GetError()); return false; } return true; } bool Bettola::create_gl_context(void) { _gl_context = SDL_GL_CreateContext(_window); if(!_gl_context) { fprintf(stderr, "Failed to create OpenGL context! SDL_ERROR: %s\n", SDL_GetError()); return false; } return true; } void Bettola::setup_quad(void) { float vertices[] = { /* Should be a quad??!?? */ -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.5f, 0.5f, 0.0f, 0.5f, 0.5f, 0.0f, -0.5f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, }; glGenVertexArrays(1, &_vao); glGenBuffers(1, &_vbo); glBindVertexArray(_vao); glBindBuffer(GL_ARRAY_BUFFER, _vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } bool Bettola::init_client_connection(void) { SDL_Delay(1000); /* Wait a second before trying to connect. */ if(!_tcp_socket.create()) { printf("Bettola client: Failed to create socket.\n"); return false; } if(!_tcp_socket.connect("127.0.0.1", BettolaLib::Network::DEFAULT_PORT)) { perror("Bettola Client: Failed to connect to server.\n"); return false; } if(!_udp_socket.create()) { printf("Bettola Client: Failed to create UDP socket.\n"); return false; } /* Setup server address for UDP. */ _server_addr.sin_family = AF_INET; _server_addr.sin_port = htons(BettolaLib::Network::DEFAULT_PORT); inet_pton(AF_INET, "127.0.0.1", &_server_addr.sin_addr); /* Set the socket to non-blocking. */ int flags = fcntl(_tcp_socket.get_sockfd(), F_GETFL, 0); if(flags == -1) { perror("fcntl F_GETFL failed."); return false; } fcntl(_tcp_socket.get_sockfd(), F_SETFL, flags | O_NONBLOCK); fcntl(_udp_socket.get_sockfd(), F_SETFL, flags | O_NONBLOCK); printf("Bettola Client: Connected to server at 127.0.01.:%hu\n", BettolaLib::Network::DEFAULT_PORT); return true; }