#include #include "Client.h" #include "Types.h" #include "Timer.h" /* TODO: this should be moved to common somewhere */ #define MAX_SHOT_DISTANCE 250.0 #define SHOT_EXPAND_SPEED 75.0 Client::Client() { m_net_client = new Network(); m_net_client->Create(59243, "127.0.0.1"); // Just connect to local host for now - testing m_client_has_focus = true; m_players.clear(); m_current_player = 0; m_left_button_pressed = false; m_drawing_shot = false; m_shot_fired = false; } Client::~Client() { // Send disconnect message bool connection_closed = false; double close_timer; sf::Packet client_packet; sf::Uint8 packet_type = PLAYER_DISCONNECT; Timer client_timer; client_timer.Init(); client_packet.clear(); client_packet << packet_type; client_packet << m_current_player; m_net_client->sendData(client_packet, true); // No time out needed here, since the // message will timeout after a couple of attempts // then exit anyway. close_timer = Timer::GetTimeDouble(); while(!connection_closed) { // Time must be updated before any messages are sent // Especially guaranteed messages, since the time needs to be // non zero. client_timer.Update(); m_net_client->Receive(); while(m_net_client->getData(client_packet)) { sf::Uint8 packet_type; client_packet >> packet_type; switch(packet_type) { case PLAYER_DISCONNECT: { sf::Uint8 player_index; // This completely removes the player from the game // Deletes member from the player list client_packet >> player_index; if(player_index == m_current_player) { connection_closed = true; } break; } } } m_net_client->Transmit(); // temporary for now. otherwise this thread consumed way too processing sf::sleep(sf::seconds(0.005)); // 5 milli-seconds // If the server does not respond within one second just close // and the server can deal with the problems. if((Timer::GetTimeDouble() - close_timer) > 1.0) { connection_closed = true; } } m_net_client->Destroy(); m_players.clear(); } void Client::run(bool fullscreen, int width, int height, std::string pname) { Timer client_timer; client_timer.Init(); m_current_player_name = pname; if (!create_window(fullscreen, width, height)) return; m_clock.restart(); sf::Mouse::setPosition(sf::Vector2i(m_width / 2, m_height / 2), *m_window); double last_time = 0.0; while (m_window->isOpen()) { double current_time = m_clock.getElapsedTime().asSeconds(); double elapsed_time = current_time - last_time; sf::Event event; while (m_window->pollEvent(event)) { switch (event.type) { case sf::Event::Closed: m_window->close(); break; case sf::Event::KeyPressed: switch (event.key.code) { case sf::Keyboard::Escape: m_window->close(); break; case sf::Keyboard::F1: grab_mouse(!m_mouse_grabbed); break; default: break; } break; case sf::Event::MouseButtonPressed: if((event.mouseButton.button == sf::Mouse::Left) && (m_shot_fired == false) && // Don't allow shots ontop of each other // The server needs to allow player to shoot, so that // multiple shots cannot be fired at the same time (m_players[m_current_player]->m_shot_allowed) && (m_client_has_focus)) { m_left_button_pressed = true; } break; case sf::Event::MouseButtonReleased: if((event.mouseButton.button == sf::Mouse::Left) && // Prevents a shot from being fired upon release // while another shot is currently being fired. (m_players[m_current_player]->m_shot_allowed) && (m_left_button_pressed) && (m_client_has_focus)) { m_drawing_shot = false; m_left_button_pressed = false; m_shot_fired = true; } break; case sf::Event::Resized: resize_window(event.size.width, event.size.height); break; case sf::Event::LostFocus: m_client_has_focus = false; break; case sf::Event::GainedFocus: m_client_has_focus = true; break; default: break; } } // Time must be updated before any messages are sent // Especially guaranteed messages, since the time needs to be // non zero. client_timer.Update(); update(elapsed_time); redraw(); last_time = current_time; // temporary for now. otherwise this thread consumed way too processing sf::sleep(sf::seconds(0.005)); // 5 milli-seconds } } void Client::recenter_cursor() { sf::Mouse::setPosition(sf::Vector2i(m_width / 2, m_height / 2), *m_window); } void Client::grab_mouse(bool grab) { m_mouse_grabbed = grab; m_window->setMouseCursorVisible(!grab); if (grab) recenter_cursor(); } void Client::update(double elapsed_time) { static bool registered_player = false; sf::Packet client_packet; m_net_client->Receive(); client_packet.clear(); // Handle all received data (only really want the latest) while(m_net_client->getData(client_packet)) { sf::Uint8 packet_type; client_packet >> packet_type; switch(packet_type) { case PLAYER_CONNECT: { sf::Uint16 players_port = sf::Socket::AnyPort; sf::Uint8 pindex; std::string name = ""; client_packet >> pindex; client_packet >> name; client_packet >> players_port; // Should be a much better way of doing this. // Perhaps generate a random number if((name == m_current_player_name) && (players_port == m_net_client->getLocalPort())) { m_current_player = pindex; } // Create a new player if one does not exist. if(m_players.end() == m_players.find(pindex)) { refptr p = new Player(); p->name = name; client_packet >> p->direction; client_packet >> p->x; client_packet >> p->y; m_players[pindex] = p; } break; } case PLAYER_UPDATE: { sf::Uint8 player_index; // Update player position as calculated from the server. client_packet >> player_index; if(m_players.end() != m_players.find(player_index)) { client_packet >> m_players[player_index]->direction; client_packet >> m_players[player_index]->x; client_packet >> m_players[player_index]->y; client_packet >> m_players[player_index]->hover; } break; } case PLAYER_DISCONNECT: { sf::Uint8 player_index; // This completely removes the player from the game // Deletes member from the player list client_packet >> player_index; m_players.erase(player_index); break; } case PLAYER_DEATH: { // This will set a death flag in the player struct. break; } case TILE_DAMAGED: { float x; float y; sf::Uint8 pindex; client_packet >> x; client_packet >> y; client_packet >> pindex; // Damage the tile if it exists if((!m_map.get_tile_at(x, y).isNull())) { m_map.get_tile_at(x, y)->shot(); } // Allow player to shoot again if(m_players.end() != m_players.find(pindex)) { m_players[pindex]->m_shot_allowed = true; if(pindex == m_current_player) { m_shot_fired = false; } } break; } default : { // Eat the packet break; } } } // For now, we are going to do a very crude shove data into // packet from keyboard and mouse events. // TODO: Clean this up and make it more robust if(m_players.size() > 0) { refptr player = m_players[m_current_player]; sf::Uint8 w_pressed = KEY_NOT_PRESSED; sf::Uint8 a_pressed = KEY_NOT_PRESSED; sf::Uint8 s_pressed = KEY_NOT_PRESSED; sf::Uint8 d_pressed = KEY_NOT_PRESSED; sf::Int32 rel_mouse_movement = 0; // This is a fix so that the mouse will not move outside the window and // cause the user to click on another program. // Note: Does not work well with fast movement. if(m_client_has_focus) { if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) { a_pressed = KEY_PRESSED; } if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) { d_pressed = KEY_PRESSED; } if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) { w_pressed = KEY_PRESSED; } if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) { s_pressed = KEY_PRESSED; } if (m_mouse_grabbed) { rel_mouse_movement = sf::Mouse::getPosition(*m_window).x - m_width / 2; recenter_cursor(); } if (m_left_button_pressed) { if (m_drawing_shot) { m_drawing_shot_distance += SHOT_EXPAND_SPEED * elapsed_time; if (m_drawing_shot_distance > MAX_SHOT_DISTANCE) m_drawing_shot_distance = MAX_SHOT_DISTANCE; } else { m_drawing_shot = true; m_drawing_shot_distance = 0.0f; } } } m_player_dir_x = cos(player->direction); m_player_dir_y = sin(player->direction); // Send an update to the server if something has changed if((player->w_pressed != w_pressed) || (player->a_pressed != a_pressed) || (player->s_pressed != s_pressed) || (player->d_pressed != d_pressed) || (player->rel_mouse_movement != rel_mouse_movement)) { sf::Uint8 packet_type = PLAYER_UPDATE; client_packet.clear(); client_packet << packet_type; client_packet << m_current_player; client_packet << w_pressed; client_packet << a_pressed; client_packet << s_pressed; client_packet << d_pressed; client_packet << rel_mouse_movement; m_net_client->sendData(client_packet); player->w_pressed = w_pressed; player->a_pressed = a_pressed; player->s_pressed = s_pressed; player->d_pressed = d_pressed; player->rel_mouse_movement = rel_mouse_movement; } if(m_shot_fired) { float shot_distance = m_drawing_shot_distance + SHOT_RING_WIDTH / 2.0; sf::Uint8 packet_type = PLAYER_SHOT; client_packet.clear(); client_packet << packet_type; client_packet << m_current_player; client_packet << shot_distance; m_net_client->sendData(client_packet, true); m_drawing_shot_distance = 0; player->m_shot_allowed = false; refptr shot = new Shot( sf::Vector2f(player->x, player->y), player->direction, shot_distance); m_shots.push_back(shot); } } else if(!registered_player) { // Needs to be 32 bit so that the packet << overload will work. sf::Uint16 players_port = m_net_client->getLocalPort(); sf::Uint8 packet_type = PLAYER_CONNECT; client_packet.clear(); client_packet << packet_type; client_packet << m_current_player; client_packet << m_current_player_name; // Send the players port. This will server as a unique // identifier and prevent users with the same name from controlling // each other. client_packet << players_port; m_net_client->sendData(client_packet, true); registered_player = true; } else { // Do nothing. } m_net_client->Transmit(); }