418 lines
14 KiB
C++

#include <math.h>
#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<Player> 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> 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> 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();
}