1 /*
2 * GameController.cpp
3 *
4 * This file is part of Leges Motus, a networked, 2D shooter set in zero gravity.
5 *
6 * Copyright 2009-2010 Andrew Ayer, Nathan Partlan, Jeffrey Pfau
7 *
8 * Leges Motus is free and open source software. You may redistribute it and/or
9 * modify it under the terms of version 2, or (at your option) version 3, of the
10 * GNU General Public License (GPL), as published by the Free Software Foundation.
11 *
12 * Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
14 * PARTICULAR PURPOSE. See the full text of the GNU General Public License for
15 * further detail.
16 *
17 * For a full copy of the GNU General Public License, please see the COPYING file
18 * in the root of the source code tree. You may also retrieve a copy from
19 * <http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the
20 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 * 02111-1307 USA
22 *
23 */
24
25 #include "GameController.hpp"
26 #include "GameWindow.hpp"
27 #include "ClientNetwork.hpp"
28 #include "GraphicalPlayer.hpp"
29 #include "Sprite.hpp"
30 #include "TiledGraphic.hpp"
31 #include "TableBackground.hpp"
32 #include "ClientConfiguration.hpp"
33 #include "ServerBrowser.hpp"
34 #include "TransitionManager.hpp"
35 #include "BaseMapObject.hpp"
36 #include "TextMenuItem.hpp"
37 #include "Weapon.hpp"
38 #include "GraphicMenuItem.hpp"
39 #include "common/PacketReader.hpp"
40 #include "common/PacketWriter.hpp"
41 #include "common/network.hpp"
42 #include "common/math.hpp"
43 #include "common/team.hpp"
44 #include "common/StringTokenizer.hpp"
45 #include "common/IPAddress.hpp"
46 #include "common/timer.hpp"
47 #include "common/misc.hpp"
48 #include "common/Shape.hpp"
49 #include "common/Circle.hpp"
50 #include "common/Version.hpp"
51
52 #include "SDL_image.h"
53
54 #include <vector>
55 #include <algorithm>
56 #include <cstdio>
57 #include <cstdlib>
58 #include <ctime>
59 #include <cstring>
60 #include <iostream>
61 #include <limits>
62 #include <sstream>
63
64 using namespace LM;
65 using namespace std;
66
67 const int GameController::MESSAGE_DISPLAY_TIME = 10000;
68 const unsigned int GameController::MAX_MESSAGES_TO_DISPLAY = 20;
69 const int GameController::SHOT_DISPLAY_TIME = 180;
70 const uint64_t GameController::MUZZLE_FLASH_LENGTH = 80;
71 const int GameController::GATE_WARNING_FLASH_LENGTH = 3000;
72 const double GameController::RANDOM_ROTATION_SCALE = 1.0;
73 const Color GameController::BLUE_COLOR(0.6, 0.6, 1.0);
74 const Color GameController::RED_COLOR(1.0, 0.6, 0.6);
75 const Color GameController::BRIGHT_GREEN(0.0, 0.6, 0.0, 0.8);
76 const Color GameController::BRIGHT_ORANGE(1.0, 0.5, 0.0, 0.8);
77 const Color GameController::BLUE_SHADOW(0.1, 0.1, 0.3);
78 const Color GameController::RED_SHADOW(0.3, 0.1, 0.1);
79 const Color GameController::TEXT_COLOR(1.0, 1.0, 1.0);
80 const Color GameController::TEXT_SHADOW(0.1, 0.1, 0.1);
81 const Color GameController::GREYED_COLOR(0.5, 0.5, 0.5);
82 const Color GameController::GREYED_SHADOW(0.2, 0.2, 0.2);
83 const Color GameController::TEXT_BG_COLOR(0.0, 0.0, 0.0, 0.7);
84 const Color GameController::BUTTON_HOVER_COLOR(0.6, 0.7, 0.9);
85 const Color GameController::BUTTON_HOVER_SHADOW(0.1, 0.2, 0.4);
86 const int GameController::GATE_STATUS_RECT_WIDTH = 80;
87 const int GameController::FROZEN_STATUS_RECT_WIDTH = 60;
88 const int GameController::ENERGY_BAR_WIDTH = 100;
89 const int GameController::COOLDOWN_BAR_WIDTH = 150;
90 const int GameController::STATUS_BAR_HEIGHT = 40;
91 const int GameController::DOUBLE_CLICK_TIME = 300;
92 const int GameController::NETWORK_TIMEOUT_LIMIT = 10000;
93 const int GameController::TEXT_LAYER = -4;
94 const unsigned int GameController::PING_FREQUENCY = 2000;
95 const unsigned int GameController::CHAT_TRANSITION_TIME = 200;
96 const unsigned int GameController::CHAT_LIMIT = 120;
97
sort_resolution(pair<int,int> pairone,pair<int,int> pairtwo)98 static bool sort_resolution(pair<int, int> pairone, pair<int, int> pairtwo) {
99 if (pairone.first == pairtwo.first) {
100 return pairone.second < pairtwo.second;
101 }
102 return pairone.first < pairtwo.first;
103 }
104
GameController(PathManager & path_manager,ClientConfiguration * config)105 GameController::GameController(PathManager& path_manager, ClientConfiguration* config) : m_path_manager(path_manager), m_network(*this) {
106 preinit(config);
107 init(GameWindow::get_optimal_instance(config->get_bool_value("vsync")?(GameWindow::VSYNC):0, config->get_int_value("multisample")));
108 }
109
GameController(PathManager & path_manager,ClientConfiguration * config,int width,int height,bool fullscreen,int depth)110 GameController::GameController(PathManager& path_manager, ClientConfiguration* config, int width, int height, bool fullscreen, int depth) : m_path_manager(path_manager), m_network(*this) {
111 preinit(config);
112 int flags = 0;
113 if (fullscreen) {
114 flags |= GameWindow::FULLSCREEN;
115 }
116 if (config->get_bool_value("vsync")) {
117 flags |= GameWindow::VSYNC;
118 }
119 init(GameWindow::get_instance(width, height, depth, flags, config->get_int_value("multisample")));
120 }
121
122 /*
123 * Delete all of the sprites and subsystems.
124 */
~GameController()125 GameController::~GameController() {
126 clear_players();
127
128 // TEMPORARY SPRITE CODE
129 delete blue_sprite;
130 delete blue_back_arm;
131 delete red_sprite;
132 delete red_back_arm;
133 delete m_crosshairs;
134 delete m_chat_input;
135
136 for (unsigned int i = 0; i < m_shots.size(); i++) {
137 m_window->unregister_graphic(m_shots[i].first, GameWindow::LAYER_GAME);
138 delete m_shots[i].first;
139 m_shots.erase(m_shots.begin() + i);
140 }
141
142 for (unsigned int i = 0; i < m_messages.size(); i++) {
143 m_text_manager->remove_string(m_messages[i].message);
144 m_transition_manager.remove_transition(m_messages[i].transition);
145 m_messages.erase(m_messages.begin() + i);
146 }
147
148 m_text_manager->remove_all_strings();
149
150 delete m_overlay_background;
151 delete m_menu_back;
152
153 delete m_server_browser;
154 delete m_chat_log;
155
156 delete m_blue_gate_status_rect;
157 delete m_blue_gate_status_rect_back;
158 delete m_red_gate_status_rect;
159 delete m_red_gate_status_rect_back;
160 delete m_frozen_status_rect;
161 delete m_frozen_status_rect_back;
162 delete m_energy_bar;
163 delete m_energy_bar_back;
164
165 delete m_chat_window_back;
166
167 delete m_weapon_selector;
168
169 // TEMPORARY MAP CODE BY ANDREW
170 delete m_map;
171
172 clear_weapons();
173
174 delete m_text_manager;
175 m_sound_controller->destroy_instance();
176 delete m_font;
177 delete m_bold_font;
178 delete m_menu_font;
179 delete m_medium_font;
180
181 delete m_logo;
182
183 m_radar->unregister_with_window(m_window);
184 delete m_radar;
185
186 m_graphics_cache.clear(); // Make sure the graphics cache is clear before m_window is destroyed, or bad stuff may happen (TODO: make GameWindow managed in a more sensible way)
187
188 // The GameWindow instance should always be destroyed last, since other stuff may depend on it.
189 m_window->deinit_video();
190 m_window->destroy_instance();
191 }
192
193 /*
194 * Initialize the base of the game controller before the Window gets created
195 */
preinit(ClientConfiguration * config)196 void GameController::preinit(ClientConfiguration* config) {
197 #ifndef __WIN32
198 GameWindow::set_icon(IMG_Load(m_path_manager.data_path("blue_head512.png", "sprites")));
199 #else
200 GameWindow::set_icon(IMG_Load(m_path_manager.data_path("blue_head32.png", "sprites")));
201 #endif
202 m_configuration = config;
203 }
204
205 /*
206 * Initialize all of the subsystems, sprites, etc.
207 */
init(GameWindow * window)208 void GameController::init(GameWindow* window) {
209 srand ( time(NULL) );
210
211 get_ticks();
212
213 initialize_key_bindings();
214
215 // Initial game state will be showing the main menu.
216 m_game_state = SHOW_MENUS;
217 m_restart = false;
218
219 m_screen_width = window->get_width();
220 m_screen_height = window->get_height();
221
222 window->set_layer_visible(true, GameWindow::LAYER_SUPER);
223
224 m_join_sent_time = 0;
225 m_last_ping_sent = 0;
226
227 m_client_version = LM_VERSION;
228 m_protocol_number = PROTOCOL_VERSION;
229
230 m_pixel_depth = window->get_depth();
231 m_fullscreen = window->is_fullscreen();
232 m_quit_game = false;
233 m_window = window;
234
235 m_last_damage_time = 0;
236 m_last_recharge_time = 0;
237 m_time_to_unfreeze = 0;
238 m_total_time_frozen = 0;
239 m_last_clicked = 0;
240 m_muzzle_flash_start = 0;
241
242 m_cooldown_updated = false;
243
244 m_curr_weapon_image = NULL;
245 m_weapon_selector = NULL;
246 m_current_weapon = NULL;
247
248 m_menu_back = new TableBackground(1, m_screen_width);
249 m_menu_back->set_row_height(0, m_screen_height);
250 m_menu_back->set_cell_color(0, Color(0, 0, 0, 0.6));
251 m_menu_back->set_border_width(0);
252 m_menu_back->set_priority(10);
253 m_menu_back->set_center_x(0);
254 m_window->register_graphic(m_menu_back, GameWindow::LAYER_MENU);
255
256 m_font = new Font(m_path_manager.data_path("JuraMedium.ttf", "fonts"), 14);
257 m_bold_font = new Font(m_path_manager.data_path("JuraDemiBold.ttf", "fonts"), 14);
258 m_bold_font->set_font_style(true, false);
259 m_text_manager = new TextManager(m_font, m_window);
260
261 m_menu_font = new Font(m_path_manager.data_path("JuraDemiBold.ttf", "fonts"), 24);
262 m_large_menu_font = new Font(m_path_manager.data_path("JuraDemiBold.ttf", "fonts"), 30);
263 m_medium_font = new Font(m_path_manager.data_path("JuraMedium.ttf", "fonts"), 20);
264
265 m_sound_controller = SoundController::get_instance(*this, m_path_manager);
266 m_holding_gate = false;
267 m_gate_lower_sounds[0] = -1;
268 m_gate_lower_sounds[1] = -1;
269 m_sound_controller->set_sound_on(m_configuration->get_bool_value("sound"));
270
271 m_map = new GraphicalMap(m_path_manager, m_window);
272 m_map_width = 0;
273 m_map_height = 0;
274
275 m_round_end_time = 0;
276
277 // Initialize all of the components of the player sprites.
278 blue_sprite = new Sprite(m_path_manager.data_path("blue_armless.png","sprites"));
279 blue_back_arm = new Sprite(m_path_manager.data_path("blue_backarm.png","sprites"));
280 blue_back_arm->set_center_x(27);
281 blue_back_arm->set_center_y(29);
282 blue_back_arm->set_x(-5);
283 blue_back_arm->set_y(-20);
284 blue_back_arm->set_priority(1);
285
286 blue_player.add_graphic(blue_sprite, "torso");
287 blue_player.add_graphic(blue_back_arm, "backarm");
288 make_front_arm_graphic(blue_player, "blue_frontarm.png", NULL, NULL);
289
290 red_sprite = new Sprite(m_path_manager.data_path("red_armless.png","sprites"));
291 red_back_arm = new Sprite(m_path_manager.data_path("red_backarm.png","sprites"));
292 red_back_arm->set_center_x(27);
293 red_back_arm->set_center_y(29);
294 red_back_arm->set_x(-5);
295 red_back_arm->set_y(-20);
296 red_back_arm->set_priority(1);
297
298 red_player.add_graphic(red_sprite, "torso");
299 red_player.add_graphic(red_back_arm, "backarm");
300 make_front_arm_graphic(red_player, "red_frontarm.png", NULL, NULL);
301
302 m_crosshairs = new Sprite(m_path_manager.data_path("crosshairs.png", "sprites"));
303 m_crosshairs->set_priority(-10);
304 m_window->register_graphic(m_crosshairs, GameWindow::LAYER_SUPER);
305
306 m_logo = new Sprite(m_path_manager.data_path("legesmotuslogo.png", "sprites"));
307 m_logo->set_x(m_screen_width/2);
308 m_logo->set_y(100);
309 m_logo->set_priority(0);
310 m_window->register_graphic(m_logo, GameWindow::LAYER_MENU);
311
312 // Set the text manager to draw a shadow behind everything.
313 ConstantCurve curve(0, 1);
314 m_text_manager->set_active_font(m_large_menu_font);
315 m_text_manager->set_shadow_alpha(1.0);
316 m_text_manager->set_shadow_offset(1.0, 1.0);
317 if (m_configuration->get_bool_value("text_shadow")) {
318 m_text_manager->set_shadow_convolve(&curve, 5, 1.0);
319 }
320 m_text_manager->set_shadow_color(TEXT_SHADOW);
321 m_text_manager->set_shadow(true);
322
323 // Initialize and display the message that we are contacting the metaserver.
324 /*Text* metaservermessage = m_text_manager->place_string("Attempting to contact metaserver, please wait...", m_screen_width/2, m_screen_height/2, TextManager::CENTER, GameWindow::LAYER_SUPER, TEXT_LAYER);
325 metaservermessage->set_invisible(false);
326 m_window->redraw();*/
327
328 // Initialize all of the menu items.
329 m_version_nag1 = NULL;
330 m_version_nag2 = NULL;
331
332 // Main menu
333 m_main_menu.add_item(TextMenuItem::with_manager(m_text_manager, "Connect to Server", "connect", 50, 200));
334 m_item_resume = TextMenuItem::with_manager(m_text_manager, "Resume Game", "resume", 50, 240, MenuItem::DISABLED);
335 m_main_menu.add_item(m_item_resume);
336 m_item_disconnect = TextMenuItem::with_manager(m_text_manager, "Disconnect", "disconnect", 50, 280, MenuItem::DISABLED);
337 m_main_menu.add_item(m_item_disconnect);
338 m_main_menu.add_item(TextMenuItem::with_manager(m_text_manager, "Options", "submenu:options", 50, 320));
339 m_main_menu.add_item(TextMenuItem::with_manager(m_text_manager, "Quit", "quit", 50, 360));
340 m_main_menu.add_item(TextMenuItem::with_manager(m_text_manager, "Thanks for playing! Please visit", "", 50, 430, MenuItem::STATIC));
341 TextMenuItem* thanks2 = TextMenuItem::with_manager(m_text_manager, "http://legesmotus.cs.brown.edu", "", 50, 470, MenuItem::STATIC);
342 thanks2->set_plain_fg_color(Color(0.4, 1.0, 0.4));
343 m_main_menu.add_item(thanks2);
344 m_main_menu.add_item(TextMenuItem::with_manager(m_text_manager, "to leave feedback for us!", "", 50, 510, MenuItem::STATIC));
345
346 m_text_manager->set_active_font(m_font);
347 m_main_menu.add_item(TextMenuItem::with_manager(m_text_manager, string("v. ").append(m_client_version), "", m_screen_width - 90, m_screen_height - 40, MenuItem::STATIC));
348 m_text_manager->set_active_font(m_menu_font);
349
350 m_window->register_graphic(m_main_menu.get_graphic_group(), GameWindow::LAYER_MENU);
351
352 // Options menu
353 ListMenuItem* current_lmi;
354 m_options_menu.add_item(TextMenuItem::with_manager(m_text_manager, "Cancel", "cancel", 50, m_screen_height - 50));
355 m_options_menu.add_item(TextMenuItem::with_manager(m_text_manager, "Enter Name:", "name", 50, 200));
356 current_lmi = new ListMenuItem("sound", TextMenuItem::with_manager(m_text_manager, "Sound:", "sound", 50, 240));
357 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "On", "on", 210, 240));
358 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "Off", "off", 210, 240));
359 current_lmi->set_default_index(m_sound_controller->is_sound_on() ? 0 : 1);
360 current_lmi->set_current_index(m_sound_controller->is_sound_on() ? 0 : 1);
361 m_options_form.add_item("sound", current_lmi);
362 m_options_menu.add_item(current_lmi);
363 current_lmi = new ListMenuItem("fullscreen", TextMenuItem::with_manager(m_text_manager, "Fullscreen:", "fullscreen", 50, 280));
364 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "On", "on", 210, 280));
365 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "Off", "off", 210, 280));
366 current_lmi->set_default_index(m_configuration->get_bool_value("fullscreen") ? 0 : 1);
367 current_lmi->set_current_index(m_configuration->get_bool_value("fullscreen") ? 0 : 1);
368 m_options_form.add_item("fullscreen", current_lmi);
369 m_options_menu.add_item(current_lmi);
370 current_lmi = new ListMenuItem("text_background", TextMenuItem::with_manager(m_text_manager, "Text Background:", "text_background", 460, 200));
371 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "On", "on", 700, 200));
372 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "Off", "off", 700, 200));
373 current_lmi->set_default_index(m_configuration->get_bool_value("text_background") ? 0 : 1);
374 current_lmi->set_current_index(m_configuration->get_bool_value("text_background") ? 0 : 1);
375 m_options_form.add_item("text_background", current_lmi);
376 m_options_menu.add_item(current_lmi);
377 current_lmi = new ListMenuItem("text_shadow", TextMenuItem::with_manager(m_text_manager, "Text Shadow:", "text_shadow", 460, 240));
378 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "On", "on", 700, 240));
379 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "Off", "off", 700, 240));
380 current_lmi->set_default_index(m_configuration->get_bool_value("text_shadow") ? 0 : 1);
381 current_lmi->set_current_index(m_configuration->get_bool_value("text_shadow") ? 0 : 1);
382 m_options_form.add_item("text_shadow", current_lmi);
383 m_options_menu.add_item(current_lmi);
384 current_lmi = new ListMenuItem("text_sliding", TextMenuItem::with_manager(m_text_manager, "Text Sliding:", "text_sliding", 460, 280));
385 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "On", "on", 700, 280));
386 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "Off", "off", 700, 280));
387 current_lmi->set_default_index(m_configuration->get_bool_value("text_sliding") ? 0 : 1);
388 current_lmi->set_current_index(m_configuration->get_bool_value("text_sliding") ? 0 : 1);
389 m_options_form.add_item("text_sliding", current_lmi);
390 m_options_menu.add_item(current_lmi);
391 current_lmi = new ListMenuItem("text_sliding", TextMenuItem::with_manager(m_text_manager, "Vertical Sync:", "vsync", 50, 360));
392 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "On", "on", 240, 360));
393 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, "Off", "off", 240, 360));
394 current_lmi->set_default_index(m_configuration->get_bool_value("vsync") ? 0 : 1);
395 current_lmi->set_current_index(m_configuration->get_bool_value("vsync") ? 0 : 1);
396 m_options_form.add_item("vsync", current_lmi);
397 m_options_menu.add_item(current_lmi);
398 current_lmi = new ListMenuItem("multisample", TextMenuItem::with_manager(m_text_manager, "Multisample:", "multisample", 460, 320));
399 for (int i = 0; i <= GameWindow::MAX_MSAA; ++i) {
400 stringstream s;
401 s << i;
402 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, s.str(), s.str(), 700, 320));
403 }
404 current_lmi->set_default_index(min<int>(m_configuration->get_int_value("multisample"), GameWindow::MAX_MSAA));
405 current_lmi->set_current_index(min<int>(m_configuration->get_int_value("multisample"), GameWindow::MAX_MSAA));
406 m_options_form.add_item("multisample", current_lmi);
407 m_options_menu.add_item(current_lmi);
408 m_options_menu.add_item(TextMenuItem::with_manager(m_text_manager, "Apply", "apply", m_screen_width - 200, m_screen_height - 50));
409 current_lmi = new ListMenuItem("resolution", TextMenuItem::with_manager(m_text_manager, "Resolution:", "resolution", 50, 320));
410 m_options_form.add_item("resolution", current_lmi);
411 m_options_menu.add_item(current_lmi);
412
413 // Initialize the name input box
414 m_name_bar_back = new TableBackground(1, 0);
415 m_name_bar_back->set_cell_color(0, Color(0, 0, 0, 0));
416 m_name_bar_back->set_border_color(Color(0, 0, 0, 0));
417 m_name_input = new TextInput(m_text_manager, 210, 200);
418 m_name_input->set_window(m_window);
419 m_name_input->set_background(m_name_bar_back);
420 m_name_input->set_background_scale(true);
421 m_name_input->set_background_padding(2);
422 m_name_input->set_invisible(true);
423 m_name_input->set_priority(0);
424 m_name_input->set_crop_width(230);
425 m_name_bar_back->set_priority(1);
426 m_options_form.add_item("name", m_name_input);
427
428 // TODO: move this to preinit--it doesn't require a GameWindow, and should be done before one is made
429 int depth;
430 m_window->supported_resolutions(NULL, NULL, &depth, &m_num_resolutions);
431 int supported_widths[m_num_resolutions];
432 int supported_heights[m_num_resolutions];
433 bool found_res = false;
434 m_window->supported_resolutions(supported_widths, supported_heights, &depth, &m_num_resolutions);
435 for (size_t i = 0; i < m_num_resolutions; i++) {
436 if (m_screen_width == supported_widths[i] && m_screen_height == supported_heights[i]) {
437 found_res = true;
438 }
439 if (supported_widths[i] < 640 || supported_heights[i] < 480) {
440 continue;
441 }
442 m_resolutions.push_back(make_pair(supported_widths[i], supported_heights[i]));
443 }
444 m_num_resolutions = m_resolutions.size();
445 if (found_res == false) {
446 m_resolutions.push_back(make_pair(m_screen_width, m_screen_height));
447 m_num_resolutions++;
448 }
449 sort(m_resolutions.begin(), m_resolutions.end(), sort_resolution);
450
451 for (size_t i = 0; i < m_num_resolutions; i++) {
452 int width = m_resolutions[i].first;
453 int height = m_resolutions[i].second;
454 stringstream resolution;
455 resolution << width << "x" << height;
456 current_lmi->add_option(TextMenuItem::with_manager(m_text_manager, resolution.str(), resolution.str(), 210, 320));
457 if (m_screen_width == width && m_screen_height == height) {
458 current_lmi->set_current_index(i);
459 current_lmi->set_default_index(i);
460 }
461 }
462 m_window->register_graphic(m_options_menu.get_graphic_group(), GameWindow::LAYER_MENU);
463 toggle_options_menu(false);
464
465 // Initialize the weapon selector menu.
466 init_weapon_selector();
467
468 // Server browser
469 m_server_browser = new ServerBrowser(*this, m_window, m_text_manager, m_screen_width, m_screen_height, m_font, m_medium_font, m_menu_font);
470 m_server_browser->set_visible(false);
471
472 // Chat log
473 m_chat_log = new ChatLog(*this, m_window, m_text_manager, m_screen_width, m_screen_height, m_font, m_medium_font, m_menu_font);
474 m_chat_log->set_visible(false);
475
476 // Initialize the overlay.
477 m_overlay_background = new TableBackground(3, m_screen_width - 300);
478 m_overlay_background->set_row_height(0, 80);
479 m_overlay_background->set_row_height(1, 43);
480 m_overlay_background->set_row_height(2, m_screen_height - 323);
481 m_overlay_background->set_priority(-2);
482 m_overlay_background->set_border_color(Color(1,1,1,0.8));
483 m_overlay_background->set_border_width(2);
484 m_overlay_background->set_cell_color(0, Color(0.1,0.1,0.1,0.8));
485 m_overlay_background->set_cell_color(1, Color(0.2,0.1,0.1,0.8));
486 m_overlay_background->set_cell_color(2, Color(0.1,0.1,0.15,0.8));
487 m_overlay_background->set_y(100);
488 m_overlay_background->set_x(m_screen_width/2);
489 m_overlay_background->set_border_collapse(true);
490 m_overlay_background->set_corner_radius(20);
491 m_window->register_graphic(m_overlay_background, GameWindow::LAYER_HUD);
492
493 m_overlay_scrollbar = new ScrollBar();
494 m_overlay_scrollbar->set_priority(-3);
495 m_overlay_scrollbar->set_length(m_overlay_background->get_row_height(2) - 20);
496 m_overlay_scrollbar->set_x(m_overlay_background->get_x() + m_overlay_background->get_image_width()/2 - 20);
497 m_overlay_scrollbar->set_y(m_overlay_background->get_y() + m_overlay_background->get_row_height(0) + m_overlay_background->get_row_height(1) + 5 + m_overlay_scrollbar->get_length()/2);
498 m_overlay_scrollbar->set_section_color(ScrollBar::BUTTONS, Color(0.7,0.2,0.1));
499 m_overlay_scrollbar->set_section_color(ScrollBar::TRACK, Color(0.2,0.1,0.1));
500 m_overlay_scrollbar->set_section_color(ScrollBar::TRACKER, Color(0.2,0.2,0.4));
501 m_overlay_scrollbar->set_scroll_speed(3);
502
503 m_overlay_scrollarea = new ScrollArea(m_overlay_background->get_image_width(), m_overlay_background->get_row_height(2) - 30, m_overlay_background->get_image_width(), 10, NULL, m_overlay_scrollbar);
504 m_overlay_scrollarea->set_priority(TEXT_LAYER);
505 m_overlay_scrollarea->get_group()->set_priority(TEXT_LAYER);
506 m_overlay_scrollarea->set_x(m_overlay_background->get_x() + 5);
507 m_overlay_scrollarea->set_y(m_overlay_background->get_y() + m_overlay_background->get_row_height(0) + m_overlay_background->get_row_height(1) + 15);
508 m_overlay_scrollarea->set_center_x(m_overlay_scrollarea->get_width()/2);
509 m_overlay_scrollarea->set_center_y(0);
510
511 m_window->register_graphic(m_overlay_scrollbar, GameWindow::LAYER_HUD);
512 m_window->register_graphic(m_overlay_scrollarea, GameWindow::LAYER_HUD);
513
514 m_overlay_items["red label"] = m_text_manager->place_string("Red Team:", m_overlay_background->get_x() - m_overlay_background->get_image_width()/2 + 10, 115, TextManager::LEFT, GameWindow::LAYER_HUD, TEXT_LAYER);
515 m_overlay_items["blue label"] = m_text_manager->place_string("Blue Team:", m_overlay_background->get_x(), 115, TextManager::LEFT, GameWindow::LAYER_HUD, TEXT_LAYER);
516
517 m_text_manager->set_active_font(m_medium_font);
518 m_overlay_items["name label"] = m_text_manager->place_string("Name", m_overlay_background->get_x() - m_overlay_background->get_image_width()/2 + 10, 190, TextManager::LEFT, GameWindow::LAYER_HUD, TEXT_LAYER);
519 m_overlay_items["score label"] = m_text_manager->place_string("Score", m_overlay_background->get_x(), 190, TextManager::LEFT, GameWindow::LAYER_HUD, TEXT_LAYER);
520
521 change_team_scores(0, 0);
522 update_individual_scores();
523
524 // Initialize the gate warning.
525 m_text_manager->set_active_font(m_large_menu_font);
526 m_text_manager->set_active_color(1.0, 0.4, 0.4);
527 m_gate_warning = m_text_manager->place_string("Your gate is going down!", m_screen_width/2, m_screen_height - 200, TextManager::CENTER, GameWindow::LAYER_HUD);
528 m_gate_warning->set_invisible(true);
529 m_gate_warning_time = 0;
530
531 // Initialize the gate status bars.
532 m_blue_gate_status_rect = new TableBackground(1, GATE_STATUS_RECT_WIDTH);
533 m_blue_gate_status_rect->set_row_height(0, STATUS_BAR_HEIGHT);
534 m_blue_gate_status_rect->set_priority(-1);
535 m_blue_gate_status_rect->set_cell_color(0, Color(0.0, 0.0, 1.0, 0.5));
536 m_blue_gate_status_rect->set_x(m_screen_width - 2 * m_blue_gate_status_rect->get_image_width() - 20);
537 m_blue_gate_status_rect->set_y(m_screen_height - m_blue_gate_status_rect->get_image_height() - 20);
538 m_window->register_graphic(m_blue_gate_status_rect, GameWindow::LAYER_HUD);
539 m_blue_gate_status_rect_back = new TableBackground(1, GATE_STATUS_RECT_WIDTH);
540 m_blue_gate_status_rect_back->set_row_height(0, STATUS_BAR_HEIGHT);
541 m_blue_gate_status_rect_back->set_priority(0);
542 m_blue_gate_status_rect_back->set_cell_color(0, Color(0.1, 0.1, 0.1, 0.5));
543 m_blue_gate_status_rect_back->set_x(m_screen_width - 2 * m_blue_gate_status_rect->get_image_width() - 20);
544 m_blue_gate_status_rect_back->set_y(m_screen_height - m_blue_gate_status_rect->get_image_height() - 20);
545 m_window->register_graphic(m_blue_gate_status_rect_back, GameWindow::LAYER_HUD);
546
547 m_red_gate_status_rect = new TableBackground(1, GATE_STATUS_RECT_WIDTH);
548 m_red_gate_status_rect->set_row_height(0, STATUS_BAR_HEIGHT);
549 m_red_gate_status_rect->set_priority(-1);
550 m_red_gate_status_rect->set_cell_color(0, Color(1.0, 0.0, 0.0, 0.5));
551 m_red_gate_status_rect->set_x(m_screen_width - m_red_gate_status_rect->get_image_width() - 10);
552 m_red_gate_status_rect->set_y(m_screen_height - m_red_gate_status_rect->get_image_height() - 20);
553 m_window->register_graphic(m_red_gate_status_rect, GameWindow::LAYER_HUD);
554 m_red_gate_status_rect_back = new TableBackground(1, GATE_STATUS_RECT_WIDTH);
555 m_red_gate_status_rect_back->set_row_height(0, STATUS_BAR_HEIGHT);
556 m_red_gate_status_rect_back->set_priority(0);
557 m_red_gate_status_rect_back->set_cell_color(0, Color(0.1, 0.1, 0.1, 0.5));
558 m_red_gate_status_rect_back->set_x(m_screen_width - m_red_gate_status_rect->get_image_width() - 10);
559 m_red_gate_status_rect_back->set_y(m_screen_height - m_red_gate_status_rect->get_image_height() - 20);
560 m_window->register_graphic(m_red_gate_status_rect_back, GameWindow::LAYER_HUD);
561
562 // Initialize the gate status bar labels.
563 m_text_manager->set_active_color(TEXT_COLOR);
564 m_text_manager->set_active_font(m_font);
565 m_blue_gate_status_text = m_text_manager->place_string("Blue Gate", m_blue_gate_status_rect->get_x() + 1, m_blue_gate_status_rect->get_y() + m_blue_gate_status_rect->get_image_height()/4, TextManager::CENTER, GameWindow::LAYER_HUD, TEXT_LAYER);
566 m_red_gate_status_text = m_text_manager->place_string("Red Gate", m_red_gate_status_rect->get_x() + 2, m_red_gate_status_rect->get_y() + m_red_gate_status_rect->get_image_height()/4, TextManager::CENTER, GameWindow::LAYER_HUD, TEXT_LAYER);
567
568 // Initialize the frozen status bar.
569 m_frozen_status_rect = new TableBackground(1, FROZEN_STATUS_RECT_WIDTH);
570 m_frozen_status_rect->set_row_height(0, 20);
571 m_frozen_status_rect->set_priority(0);
572 m_frozen_status_rect->set_cell_color(0, Color(0.0, 0.5, 1.0, 0.5));
573 m_frozen_status_rect->set_x(m_screen_width/2);
574 m_frozen_status_rect->set_y(m_screen_height/2 + 50);
575 m_window->register_graphic(m_frozen_status_rect, GameWindow::LAYER_HUD);
576 m_frozen_status_rect_back = new TableBackground(1, FROZEN_STATUS_RECT_WIDTH);
577 m_frozen_status_rect_back->set_row_height(0, 20);
578 m_frozen_status_rect_back->set_priority(1);
579 m_frozen_status_rect_back->set_cell_color(0, Color(0.1, 0.1, 0.1, 0.5));
580 m_frozen_status_rect_back->set_x(m_frozen_status_rect->get_x());
581 m_frozen_status_rect_back->set_y(m_frozen_status_rect->get_y());
582 m_window->register_graphic(m_frozen_status_rect_back, GameWindow::LAYER_HUD);
583
584 // Initialize the frozen status bar label.
585 m_text_manager->set_active_color(1.0, 1.0, 1.0);
586 m_text_manager->set_active_font(m_font);
587 m_frozen_status_text = m_text_manager->place_string("Frozen", m_frozen_status_rect->get_x() + 1, m_frozen_status_rect->get_y() + 2, TextManager::CENTER, GameWindow::LAYER_HUD);
588
589 // Initialize the energy bar.
590 m_energy_text = NULL;
591 m_energy_bar = new TableBackground(1, ENERGY_BAR_WIDTH);
592 m_energy_bar->set_row_height(0, STATUS_BAR_HEIGHT);
593 m_energy_bar->set_priority(-1);
594 m_energy_bar->set_cell_color(0, BRIGHT_GREEN);
595 m_energy_bar->set_x(ENERGY_BAR_WIDTH);
596 m_energy_bar->set_y(m_screen_height - m_energy_bar->get_image_height() - 20);
597 m_window->register_graphic(m_energy_bar, GameWindow::LAYER_HUD);
598 m_energy_bar_back = new TableBackground(1, ENERGY_BAR_WIDTH);
599 m_energy_bar_back->set_row_height(0, STATUS_BAR_HEIGHT);
600 m_energy_bar_back->set_priority(0);
601 m_energy_bar_back->set_cell_color(0, Color(0.1, 0.1, 0.1, 0.5));
602 m_energy_bar_back->set_x(m_energy_bar->get_x());
603 m_energy_bar_back->set_y(m_energy_bar->get_y());
604 m_window->register_graphic(m_energy_bar_back, GameWindow::LAYER_HUD);
605
606 update_energy_bar(0);
607
608 // Initialize the cooldown bar.
609 m_cooldown_bar = new TableBackground(1, COOLDOWN_BAR_WIDTH);
610 m_cooldown_bar->set_row_height(0, STATUS_BAR_HEIGHT/2);
611 m_cooldown_bar->set_priority(-1);
612 m_cooldown_bar->set_cell_color(0, BRIGHT_ORANGE);
613 m_cooldown_bar->set_x(m_energy_bar->get_x() + ENERGY_BAR_WIDTH/2 + m_cooldown_bar->get_image_width()/2 + 20);
614 m_cooldown_bar->set_y(m_energy_bar->get_y() + m_energy_bar->get_image_height() - m_cooldown_bar->get_image_height());
615 m_window->register_graphic(m_cooldown_bar, GameWindow::LAYER_HUD);
616 m_cooldown_bar_back = new TableBackground(1, COOLDOWN_BAR_WIDTH);
617 m_cooldown_bar_back->set_row_height(0, STATUS_BAR_HEIGHT/2);
618 m_cooldown_bar_back->set_priority(0);
619 m_cooldown_bar_back->set_cell_color(0, Color(0.1, 0.1, 0.1, 0.5));
620 m_cooldown_bar_back->set_x(m_cooldown_bar->get_x());
621 m_cooldown_bar_back->set_y(m_cooldown_bar->get_y());
622 m_window->register_graphic(m_cooldown_bar_back, GameWindow::LAYER_HUD);
623
624 update_cooldown_bar(0);
625
626 // Initialize the input bar
627 TableBackground* input_bar_back = new TableBackground(1, 0);
628 input_bar_back->set_cell_color(0, TEXT_BG_COLOR);
629 m_chat_input = new TextInput(m_text_manager, 20, m_screen_height - 100, CHAT_LIMIT);
630 m_chat_input->set_window(m_window);
631 m_chat_input->set_background(input_bar_back);
632 m_chat_input->set_background_scale(true);
633 m_chat_input->set_background_padding(2);
634 m_chat_input->set_invisible(true);
635 m_chat_input->set_priority(0);
636 input_bar_back->set_priority(1);
637 m_team_chatting = false;
638
639 // Initialize the chat window background
640 m_chat_window_back = new TableBackground(1, 0);
641 m_chat_window_back->set_row_height(0, 20);
642 m_chat_window_back->set_priority(0);
643 m_chat_window_back->set_cell_color(0, TEXT_BG_COLOR);
644 m_chat_window_back->set_x(17);
645 m_chat_window_back->set_y(17);
646 m_chat_window_back->set_invisible(true);
647 m_window->register_graphic(m_chat_window_back, GameWindow::LAYER_SUPER);
648
649 // TODO: add dynamically, not at allocation
650 if (m_configuration->get_bool_value("text_sliding")) {
651 m_chat_window_transition_x = new Transition(m_chat_window_back, &Graphic::set_width, new LinearCurve(0,0));
652 m_chat_window_transition_x->set_curve_ownership(true);
653 m_transition_manager.add_transition(m_chat_window_transition_x);
654
655 m_chat_window_transition_y = new Transition(m_chat_window_back, &Graphic::set_height, new LinearCurve(0,0));
656 m_chat_window_transition_y->set_curve_ownership(true);
657 m_transition_manager.add_transition(m_chat_window_transition_y);
658 } else {
659 m_chat_window_transition_x = NULL;
660 m_chat_window_transition_y = NULL;
661 }
662
663 // Set up the radar.
664 m_radar = new Radar(m_path_manager, m_params.radar_scale, m_params.radar_mode);
665 m_radar->set_x(m_screen_width - 120);
666 m_radar->set_y(120);
667 m_radar->register_with_window(m_window);
668
669 m_current_scan_id = 0;
670 m_offline_mode = false;
671 // TODO: better error messages if meta server address can't be resolved
672 if (const char* metaserver_address = getenv("LM_METASERVER")) {
673 // Address specified by $LM_METASERVER environment avariable
674 if (!resolve_hostname(m_metaserver_address, metaserver_address)) {
675 display_message("Unable to contact specified metaserver. Internet-wide server browsing will be disabled.");
676 m_offline_mode = true;
677 cerr << "Unable to resolve metaserver hostname, `" << metaserver_address << "' as specified in the $LM_METASERVER environment variable. Internet-wide server browsing will not be enabled." << endl;
678 }
679 } else if (!resolve_hostname(m_metaserver_address, METASERVER_HOSTNAME, METASERVER_PORTNO)) {
680 display_message("Unable to contact metaserver. Internet-wide server browsing will be disabled.");
681 m_offline_mode = true;
682 cerr << "Unable to resolve metaserver hostname. Internet-wide server browsing will not be enabled." << endl;
683 }
684
685 // We're done contacting the metaserver, remove the message.
686 //m_text_manager->remove_string(metaservermessage);
687
688 m_focus = NULL;
689
690 #ifndef LM_NO_UPGRADE_NAG
691 if (!m_offline_mode) {
692 check_for_upgrade();
693 }
694 #endif
695 }
696
697 /*
698 * The main game loop.
699 */
run(int lockfps)700 void GameController::run(int lockfps) {
701 cout << "SDL window is: " << m_window->get_width() << " pixels wide and "
702 << m_window->get_height() << " pixels tall." << endl;
703
704 unsigned long startframe = get_ticks();
705 unsigned long lastmoveframe = startframe;
706
707 /* 1 second / FPS = milliseconds per frame */
708 double delay = 1000.0 / lockfps;
709
710 display_message("Welcome to Leges Motus!");
711
712 while(m_quit_game == false) {
713 m_cooldown_updated = false;
714
715 process_input();
716
717 m_network.resend_acks();
718 m_network.receive_packets();
719
720 if (m_quit_game == true) {
721 break;
722 }
723
724 if (m_join_sent_time != 0 && m_join_sent_time + 5000 < get_ticks()) {
725 display_message("Error: Could not connect to server.", RED_COLOR, RED_SHADOW);
726 disconnect();
727 m_join_sent_time = 0;
728 }
729
730 // Check if my player is set to unfreeze.
731 if (!m_players.empty() && m_time_to_unfreeze < get_ticks() && m_time_to_unfreeze != 0) {
732 unfreeze();
733 }
734
735 if (!m_players.empty() && m_players[m_player_id].is_damaged() && !m_players[m_player_id].is_dead() && !m_players[m_player_id].is_frozen()) {
736 // Figure out how much energy to recharge, based on the time elapsed since the last recharge
737 uint64_t now = get_ticks();
738
739 if (m_params.recharge_continuously || !m_last_damage_time || now - m_last_damage_time >= m_params.recharge_delay) {
740 uint64_t time_elapsed = 0;
741 if (!m_params.recharge_continuously && m_last_damage_time) {
742 time_elapsed = (now - m_last_damage_time) - m_params.recharge_delay;
743 } else if (m_last_recharge_time) {
744 time_elapsed = now - m_last_recharge_time;
745 }
746
747 m_last_damage_time = 0;
748 m_last_recharge_time = now - time_elapsed % m_params.recharge_rate;
749
750 int recharge = m_params.recharge_amount * (time_elapsed / m_params.recharge_rate);
751 if (recharge) {
752 m_players[m_player_id].change_energy(recharge);
753 update_energy_bar();
754 }
755 }
756 }
757
758 // Update movement twice as often as graphics.
759 unsigned long currframe = get_ticks();
760 if ((currframe - lastmoveframe) >= (delay/2)) {
761 move_objects((get_ticks() - lastmoveframe) / delay); // scale all position changes to keep game speed constant.
762
763 if (m_time_to_unfreeze != 0) {
764 m_frozen_status_rect->set_image_width(((m_time_to_unfreeze - get_ticks())/(double)m_total_time_frozen) * FROZEN_STATUS_RECT_WIDTH);
765 }
766
767 if (m_round_end_time) {
768 // If round will end in less than 2 minutes, display a message.
769 if (get_ticks() >= m_round_end_time - 120000 && lastmoveframe < m_round_end_time - 120000) {
770 display_message("Round will end in TWO minutes.", RED_COLOR, RED_SHADOW, true);
771 }
772
773 // If round will end in less than 30 seconds, display a message.
774 if (get_ticks() >= m_round_end_time - 30000 && lastmoveframe < m_round_end_time - 30000) {
775 display_message("Round will end in THIRTY seconds.", RED_COLOR, RED_SHADOW, true);
776 }
777
778 static const string numeral_to_english[10] = { "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "TEN" };
779
780 for (int a = 10000; a >= 1000; a-=1000) {
781 // If round will end in less than <a> seconds, display a message.
782 if (get_ticks() >= m_round_end_time - a && lastmoveframe < m_round_end_time - a) {
783 display_message("Round will end in " + numeral_to_english[a/1000-1] + " seconds.", RED_COLOR, RED_SHADOW, true);
784 }
785 }
786 }
787
788 lastmoveframe = get_ticks();
789 }
790
791
792 // Update graphics if frame rate is correct.
793 if((currframe - startframe) >= delay) {
794 if (m_network.is_connected() && m_join_sent_time == 0) {
795 if ((currframe - m_last_ping_sent) >= PING_FREQUENCY) {
796 ping_server(m_network.get_server_address());
797 m_last_ping_sent = currframe;
798 }
799
800 if (m_network.get_last_packet_time() + NETWORK_TIMEOUT_LIMIT < currframe) {
801 display_message("Connection to the server has timed out.", RED_COLOR, RED_SHADOW);
802 disconnect();
803 continue;
804 }
805 }
806
807 if (!m_server_browser->is_invisible()) {
808 m_server_browser->autoscroll(currframe - startframe);
809 }
810 if (!m_overlay_scrollbar->is_invisible()) {
811 m_overlay_scrollbar->autoscroll(currframe - startframe);
812 }
813 if (!m_chat_log->is_invisible()) {
814 m_chat_log->autoscroll(currframe - startframe);
815 }
816
817 bool erasedone = false;
818 // Erase messages that are too old.
819 double height;
820 if (m_configuration->get_bool_value("text_sliding")) {
821 height = m_chat_window_transition_y->get_curve()->get_end();
822 } else {
823 height = m_chat_window_back->get_row_height(0);
824 }
825 for (vector<Message>::iterator iter = m_messages.begin();
826 iter != m_messages.end();) {
827 if (iter->timeout < currframe || m_messages.size() > MAX_MESSAGES_TO_DISPLAY) {
828 height -= iter->message->get_image_height();
829 m_text_manager->remove_string(iter->message);
830 if (iter->transition != NULL) {
831 m_transition_manager.remove_transition(iter->transition);
832 delete iter->transition;
833 }
834 iter = m_messages.erase(iter);
835 erasedone = true;
836 } else {
837 iter++;
838 }
839 }
840
841
842 if (erasedone) {
843 m_chat_window_back->set_image_width(0);
844 // Reposition messages that remain after removing.
845 double max_w = 0;
846 for (unsigned int i = 0; i < m_messages.size(); i++) {
847 int y = 20 + (m_font->ascent() + m_font->descent() + 5) * i;
848 if (m_configuration->get_bool_value("text_sliding")) {
849 m_messages[i].transition->change_curve(currframe, new LinearCurve(0, y), CHAT_TRANSITION_TIME);
850 } else {
851 m_messages[i].message->set_y(y);
852 }
853 max_w = max<double>(m_messages[i].message->get_image_width() + 6, max_w);
854 }
855 if (m_configuration->get_bool_value("text_sliding")) {
856 m_chat_window_transition_x->change_curve(currframe, new LinearCurve(0, max_w), CHAT_TRANSITION_TIME);
857 m_chat_window_transition_y->change_curve(currframe, new LinearCurve(0, height), CHAT_TRANSITION_TIME);
858 } else {
859 m_chat_window_back->set_width(max_w);
860 m_chat_window_back->set_height(height);
861 }
862 if (m_messages.size() == 0) {
863 m_chat_window_back->set_invisible(true);
864 }
865 }
866
867 // Change shot displays based on time.
868 for (unsigned int i = 0; i < m_shots.size(); i++) {
869 double shot_time = (double)(SHOT_DISPLAY_TIME-(m_shots[i].second - currframe))/SHOT_DISPLAY_TIME;
870 double shot_curve = -4.5*shot_time*(shot_time-1.0)/(shot_time+0.5); //fancy curve
871 m_shots[i].first->set_scale_x(shot_curve);
872 m_shots[i].first->set_scale_y(shot_curve);
873 m_shots[i].first->set_rotation(shot_time*90.0);
874 if (m_shots[i].second < currframe) {
875 m_window->unregister_graphic(m_shots[i].first, GameWindow::LAYER_GAME);
876 delete m_shots[i].first;
877 m_shots.erase(m_shots.begin() + i);
878 }
879 }
880
881 // Change the display of the gate warning message based on time.
882 if (m_gate_warning_time != 0 && m_gate_warning_time < currframe - GATE_WARNING_FLASH_LENGTH) {
883 m_gate_warning_time = 0;
884 m_gate_warning->set_invisible(true);
885 } else if (m_gate_warning_time != 0 && m_gate_warning_time < currframe - (5*GATE_WARNING_FLASH_LENGTH)/6) {
886 m_gate_warning->set_invisible(false);
887 } else if (m_gate_warning_time != 0 && m_gate_warning_time < currframe - (4*GATE_WARNING_FLASH_LENGTH)/6) {
888 m_gate_warning->set_invisible(true);
889 } else if (m_gate_warning_time != 0 && m_gate_warning_time < currframe - (3*GATE_WARNING_FLASH_LENGTH)/6) {
890 m_gate_warning->set_invisible(false);
891 } else if (m_gate_warning_time != 0 && m_gate_warning_time < currframe - (2*GATE_WARNING_FLASH_LENGTH)/6) {
892 m_gate_warning->set_invisible(true);
893 } else if (m_gate_warning_time != 0 && m_gate_warning_time < currframe - (1*GATE_WARNING_FLASH_LENGTH)/6) {
894 m_gate_warning->set_invisible(false);
895 }
896
897 // Update the cooldown bar.
898 if (!m_cooldown_updated) {
899 update_cooldown_bar();
900 }
901
902 // Uncomment if framerate is needed.
903 // the framerate:
904 m_framerate = (1000/(currframe - startframe));
905 m_transition_manager.update(currframe);
906
907 if (!m_players.empty()) {
908 //cerr << m_players[m_player_id].get_sprite()->get_rotation() << ", ";
909 m_players[m_player_id].set_rotation_degrees(m_players[m_player_id].get_sprite()->get_rotation());
910
911 // Change gun sprite if muzzle flash is done.
912 Graphic* frontarm = m_players[m_player_id].get_sprite()->get_graphic("frontarm");
913 if (m_muzzle_flash_start != 0 && get_ticks() - m_muzzle_flash_start >= MUZZLE_FLASH_LENGTH && frontarm->get_graphic("normal")->is_invisible()) {
914 frontarm->get_graphic("normal")->set_invisible(false);
915 send_animation_packet("frontarm/normal", "invisible", false);
916 frontarm->get_graphic("firing")->set_invisible(true);
917 send_animation_packet("frontarm/firing", "invisible", true);
918 m_muzzle_flash_start = 0;
919 }
920
921 m_crosshairs->set_x(m_mouse_x);
922 m_crosshairs->set_y(m_mouse_y);
923
924 // Turn arm of player to face crosshairs.
925 if (!m_players[m_player_id].is_frozen() && !m_players[m_player_id].is_dead()) {
926 double angle = get_normalized_angle(to_degrees(get_crosshairs_angle()) - m_players[m_player_id].get_rotation_degrees());
927
928 if (angle < 90 || angle > 270) {
929 m_players[m_player_id].get_sprite()->set_scale_x(-1);
930 send_animation_packet("all", "scale_x", -1);
931 angle *= -1;
932 angle += 55;
933 } else {
934 m_players[m_player_id].get_sprite()->set_scale_x(1);
935 send_animation_packet("all", "scale_x", 1);
936 angle -= 120;
937 }
938 m_players[m_player_id].get_sprite()->get_graphic("frontarm")->set_rotation(angle);
939 send_animation_packet("frontarm", "rotation", int(round(angle))); // XXX: going double->int here
940 }
941 send_my_player_update();
942 m_radar->update(currframe);
943
944 // Set the new offset of the window so that the player is centered.
945 m_offset_x = m_players[m_player_id].get_x() - (m_screen_width/2.0);
946 m_offset_y = m_players[m_player_id].get_y() - (m_screen_height/2.0);
947 m_window->set_offset_x(m_offset_x);
948 m_window->set_offset_y(m_offset_y);
949 }
950
951 update_visible_elements();
952
953 m_window->redraw();
954 startframe = get_ticks();
955 }
956 }
957
958 if (!m_restart) {
959 disconnect();
960 }
961 }
962
update_visible_elements()963 void GameController::update_visible_elements() {
964 // Show and hide elements depending on game state (started, menus, etc.)
965 if (m_game_state == SHOW_MENUS) {
966 set_hud_visible(false);
967
968 m_logo->set_invisible(false);
969 m_window->set_layer_visible(true, GameWindow::LAYER_MENU);
970
971 toggle_main_menu(true);
972 toggle_options_menu(false);
973 m_server_browser->set_visible(false);
974 m_weapon_selector->get_graphic_group()->set_invisible(true);
975 } else if (m_game_state == SHOW_OPTIONS_MENU) {
976 set_hud_visible(false);
977
978 m_logo->set_invisible(false);
979 m_window->set_layer_visible(true, GameWindow::LAYER_MENU);
980
981 toggle_main_menu(false);
982 toggle_options_menu(true);
983 m_server_browser->set_visible(false);
984 m_weapon_selector->get_graphic_group()->set_invisible(true);
985 } else if (m_game_state == SHOW_SERVER_BROWSER) {
986 set_hud_visible(false);
987
988 m_logo->set_invisible(false);
989 m_window->set_layer_visible(true, GameWindow::LAYER_MENU);
990
991 toggle_main_menu(false);
992 toggle_options_menu(false);
993 m_server_browser->set_visible(true);
994 m_weapon_selector->get_graphic_group()->set_invisible(true);
995 } else {
996
997 set_hud_visible(true);
998 m_window->set_layer_visible(false, GameWindow::LAYER_MENU);
999 }
1000 }
1001
init_weapon_selector()1002 void GameController::init_weapon_selector() {
1003 // Initialize the weapon selector menu.
1004 if (m_weapon_selector != NULL) {
1005 m_window->unregister_graphic(m_weapon_selector->get_graphic_group(), GameWindow::LAYER_HUD);
1006 delete m_weapon_selector;
1007 }
1008
1009 m_weapon_selector_back = new RadialBackground(1);
1010 m_weapon_selector_back->set_border_color(Color(0.0,0.0,0.0,0.0));
1011 m_weapon_selector_back->set_inner_radius(80.0);
1012 m_weapon_selector_back->set_outer_radius(70.0);
1013 m_weapon_selector_back->set_border_radius(3.0);
1014 m_weapon_selector_back->set_border_angle(3);
1015 m_weapon_selector_back->set_x(m_screen_width/2);
1016 m_weapon_selector_back->set_y(m_screen_height/2);
1017 m_weapon_selector_back->set_rotation(180.0/m_weapons.size());
1018
1019 m_weapon_selector = new RadialMenu(m_weapon_selector_back, Color(0.2,0.2,0.6,0.8), Color(0.1,0.1,0.3,1));
1020
1021 GraphicMenuItem *cur_item;
1022 for (map<std::string, Weapon*>::iterator it(m_weapons.begin()); it != m_weapons.end(); ++it) {
1023 Graphic* this_graphic = m_graphics_cache.new_graphic<Sprite>(m_path_manager.data_path(it->second->hud_graphic(), "sprites"));
1024 cur_item = new GraphicMenuItem(this_graphic, it->first);
1025 cur_item->set_scale(0.75);
1026 m_weapon_selector->add_item(cur_item);
1027 }
1028
1029 m_weapon_selector->get_graphic_group()->set_invisible(true);
1030 m_window->register_graphic(m_weapon_selector->get_graphic_group(), GameWindow::LAYER_HUD);
1031 }
1032
1033 /*
1034 * Set the HUD visible or invisible.
1035 */
set_hud_visible(bool visible)1036 void GameController::set_hud_visible(bool visible) {
1037 m_radar->set_invisible(!visible);
1038
1039 m_blue_gate_status_rect->set_invisible(!visible);
1040 m_blue_gate_status_text->set_invisible(!visible);
1041 m_blue_gate_status_rect_back->set_invisible(!visible);
1042 m_red_gate_status_rect->set_invisible(!visible);
1043 m_red_gate_status_text->set_invisible(!visible);
1044 m_red_gate_status_rect_back->set_invisible(!visible);
1045
1046 GraphicalPlayer* player = get_player_by_id(m_player_id);
1047
1048 if (player == NULL) {
1049 m_frozen_status_rect->set_invisible(true);
1050 m_frozen_status_text->set_invisible(true);
1051 m_frozen_status_rect_back->set_invisible(true);
1052 m_energy_bar->set_invisible(true);
1053 m_energy_bar_back->set_invisible(true);
1054 m_energy_text->set_invisible(true);
1055 m_cooldown_bar->set_invisible(true);
1056 m_cooldown_bar_back->set_invisible(true);
1057 if (m_curr_weapon_image != NULL) {
1058 m_curr_weapon_image->set_invisible(true);
1059 }
1060 return;
1061 }
1062
1063 m_energy_bar->set_invisible(!visible);
1064 m_energy_bar_back->set_invisible(!visible);
1065 m_energy_text->set_invisible(!visible);
1066
1067 m_cooldown_bar->set_invisible(!visible);
1068 m_cooldown_bar_back->set_invisible(!visible);
1069
1070 if (m_curr_weapon_image != NULL) {
1071 // Uncomment the following to show the current weapon image only when done switching
1072 m_curr_weapon_image->set_invisible(!visible);
1073 // if (m_last_weapon_switch == 0 || (get_ticks() - m_last_weapon_switch) > m_params.weapon_switch_delay) {
1074 // m_curr_weapon_image->set_invisible(!visible);
1075 // } else {
1076 // m_curr_weapon_image->set_invisible(true);
1077 // }
1078 }
1079
1080 if (player->is_frozen() && !player->is_invisible() && m_total_time_frozen > 100) {
1081 m_frozen_status_rect->set_invisible(!visible);
1082 m_frozen_status_text->set_invisible(!visible);
1083 m_frozen_status_rect_back->set_invisible(!visible);
1084 } else {
1085 m_frozen_status_rect->set_invisible(true);
1086 m_frozen_status_text->set_invisible(true);
1087 m_frozen_status_rect_back->set_invisible(true);
1088 }
1089 }
1090
1091 /*
1092 * Update the energy bar.
1093 */
update_energy_bar(int new_energy)1094 void GameController::update_energy_bar(int new_energy) {
1095 if (GraphicalPlayer* player = get_player_by_id(m_player_id)) {
1096 if (new_energy != -1) {
1097 player->set_energy(new_energy);
1098 } else {
1099 new_energy = player->get_energy();
1100 }
1101 } else if (new_energy == -1) {
1102 new_energy = 0;
1103 }
1104
1105 m_energy_bar->set_image_width((ENERGY_BAR_WIDTH-2) * ((double)new_energy/(GraphicalPlayer::MAX_ENERGY)) + 2);
1106
1107 if (m_energy_text != NULL) {
1108 m_text_manager->remove_string(m_energy_text);
1109 }
1110
1111 // Re-initialize the label.
1112 m_text_manager->set_active_color(1.0, 1.0, 1.0);
1113 m_text_manager->set_active_font(m_font);
1114
1115 ostringstream energystring;
1116 energystring << "E: " << new_energy;
1117 m_energy_text = m_text_manager->place_string(energystring.str(), m_energy_bar->get_x(), m_energy_bar->get_y() + STATUS_BAR_HEIGHT/2, TextManager::CENTER, GameWindow::LAYER_HUD, TEXT_LAYER);
1118 m_energy_text->set_y(m_energy_bar->get_y() + STATUS_BAR_HEIGHT/2 - m_energy_text->get_image_height()/2);
1119 }
1120
1121 /*
1122 * Update the cooldown bar.
1123 */
update_cooldown_bar(double new_cooldown)1124 void GameController::update_cooldown_bar(double new_cooldown) {
1125 if (m_current_weapon != NULL) {
1126 if (new_cooldown == -1) {
1127 if (m_current_weapon->has_limited_ammo()) {
1128 // Show ammo consumption in cooldown bar
1129 new_cooldown = 1.0 - m_current_weapon->get_current_ammo()/((double)m_current_weapon->get_total_ammo());
1130 } else {
1131 new_cooldown = m_current_weapon->get_remaining_cooldown()/((double)m_current_weapon->get_total_cooldown());
1132 }
1133 if ((get_ticks() - m_last_weapon_switch) < m_params.weapon_switch_delay) {
1134 if (m_last_weapon_switch != 0 && new_cooldown < (m_params.weapon_switch_delay - (get_ticks() - m_last_weapon_switch))/(double)m_params.weapon_switch_delay) {
1135 new_cooldown = (m_params.weapon_switch_delay - (get_ticks() - m_last_weapon_switch))/(double)m_params.weapon_switch_delay;
1136 }
1137 }
1138 }
1139 } else if (new_cooldown == -1) {
1140 new_cooldown = 0;
1141 }
1142
1143 m_cooldown_updated = true;
1144
1145 m_cooldown_bar->set_image_width((COOLDOWN_BAR_WIDTH-2) * (1.0-new_cooldown) + 2);
1146 }
1147
1148 /*
1149 * Set the dimensions of the screen.
1150 */
set_screen_dimensions(int width,int height)1151 void GameController::set_screen_dimensions(int width, int height) {
1152 m_screen_width = width;
1153 m_screen_height = height;
1154 }
1155
1156 /*
1157 * Process the current SDL input state.
1158 */
process_input()1159 void GameController::process_input() {
1160 SDL_Event event;
1161 while(SDL_PollEvent(&event) != 0) {
1162 switch(event.type) {
1163 case SDL_KEYDOWN:
1164 if (event.key.keysym.sym == m_key_bindings.quit || event.key.keysym.sym == m_alt_key_bindings.quit) {
1165 cerr << "Quit key pressed - quitting." << endl;
1166 m_quit_game = true;
1167 }
1168
1169 // If we're typing into the input bar...
1170 if (m_focus != NULL) {
1171 if (event.key.keysym.sym == m_key_bindings.show_overlay || event.key.keysym.sym == m_alt_key_bindings.show_overlay) {
1172 parse_key_input();
1173 break;
1174 }
1175
1176 m_text_manager->set_active_color(TEXT_COLOR);
1177 // If we're going back to the menu, remove the input bar.
1178 if (event.key.keysym.sym == m_key_bindings.show_menu || event.key.keysym.sym == m_alt_key_bindings.show_menu) {
1179 if (!m_chat_log->is_invisible()) {
1180 m_chat_log->set_visible(false);
1181 }
1182 SDL_EnableUNICODE(0);
1183 SDL_EnableKeyRepeat(0, 0); // Disable key repeat
1184 m_chat_input->set_invisible(true);
1185 m_chat_input->reset();
1186 m_focus = NULL;
1187 //m_input_text = "> ";
1188 } else if (event.key.keysym.sym == m_key_bindings.send_chat || event.key.keysym.sym == m_alt_key_bindings.send_chat) {
1189 if (m_focus == m_chat_input) {
1190 if (m_input_text.find("[TEAM]> ") == 0) {
1191 string msg = m_input_text.substr(8);
1192 m_input_text = ((string)"> /tchat ").append(msg);
1193 }
1194
1195 string message = m_chat_input->get_value();
1196
1197 // Check message for commands.
1198 if (message == "/quit") {
1199 m_quit_game = true;
1200 } else if (message.find("/name ") == 0) {
1201 string new_name(message.substr(6));
1202 if (!m_network.is_connected()) {
1203 ostringstream msg;
1204 msg << "You are now known as " << new_name;
1205 display_message(msg.str());
1206 }
1207 set_player_name(new_name);
1208 } else if (message.find("/team ") == 0) {
1209 string new_team_string(message.substr(6));
1210 char new_team = parse_team_string(new_team_string.c_str());
1211 if (is_valid_team(new_team)) {
1212 send_team_change_packet(new_team);
1213 } else {
1214 display_message("Please enter '/team blue' or '/team red'");
1215 }
1216 } else if (message == "/copying" || message == "/warranty" || message == "/legal" || message == "/copyright") {
1217 display_legalese();
1218 } else if (message.find("/tchat ") == 0) {
1219 send_team_message(message.substr(7));
1220 } else if (m_team_chatting) {
1221 send_team_message(message);
1222 } else {
1223 send_message(message);
1224 }
1225 m_chat_input->set_invisible(true);
1226 m_chat_input->reset();
1227 } else if (m_focus == m_name_input) {
1228 m_name_bar_back->set_border_color(Color(0, 0, 0, 0));
1229 }
1230
1231 // Remove the input bar.
1232 SDL_EnableUNICODE(0);
1233 SDL_EnableKeyRepeat(0, 0); // Disable key repeat
1234 m_focus = NULL;
1235 } else {
1236 if (m_focus == m_chat_input) {
1237 m_text_manager->set_active_font(m_font);
1238 } else if (m_focus == m_name_input) {
1239 m_text_manager->set_active_font(m_menu_font);
1240 }
1241 m_focus->keyboard_event(event.key);
1242 }
1243 } else {
1244 //Check which key using: event.key.keysym.sym == SDLK_<SOMETHING>
1245 if (m_game_state == GAME_IN_PROGRESS && (event.key.keysym.sym == m_key_bindings.jump || event.key.keysym.sym == m_alt_key_bindings.jump)) {
1246 attempt_jump();
1247 } else if (event.key.keysym.sym == m_key_bindings.open_chat || event.key.keysym.sym == m_key_bindings.open_console || event.key.keysym.sym == m_alt_key_bindings.open_chat || event.key.keysym.sym == m_alt_key_bindings.open_console) {
1248 SDL_EnableUNICODE(1);
1249 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
1250 m_text_manager->set_active_color(TEXT_COLOR);
1251 m_team_chatting = false;
1252 m_chat_input->reset();
1253 m_text_manager->set_active_font(m_bold_font);
1254 m_text_manager->set_active_color(TEXT_COLOR);
1255 m_chat_input->set_prefix("> ");
1256 m_chat_input->set_invisible(false);
1257 m_focus = m_chat_input;
1258 if (event.key.keysym.sym == m_key_bindings.open_console || event.key.keysym.sym == m_alt_key_bindings.open_console) {
1259 m_chat_log->set_visible(true);
1260 }
1261 } else if (event.key.keysym.sym == m_key_bindings.open_team_chat || event.key.keysym.sym == m_alt_key_bindings.open_team_chat) {
1262 SDL_EnableUNICODE(1);
1263 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
1264 m_text_manager->set_active_color(TEXT_COLOR);
1265 m_text_manager->set_active_font(m_font);
1266 m_team_chatting = true;
1267 m_chat_input->reset();
1268 m_text_manager->set_active_font(m_bold_font);
1269 m_text_manager->set_active_color(TEXT_COLOR);
1270 m_chat_input->set_prefix("[TEAM]> ");
1271 m_chat_input->set_invisible(false);
1272 m_focus = m_chat_input;
1273 } else if (event.key.keysym.sym == m_key_bindings.show_menu || event.key.keysym.sym == m_alt_key_bindings.show_menu) {
1274 if (!m_chat_log->is_invisible()) {
1275 m_chat_log->set_visible(false);
1276 } else if (m_game_state == SHOW_MENUS && !m_players.empty()) {
1277 m_game_state = GAME_IN_PROGRESS;
1278 } else {
1279 reset_options();
1280 m_game_state = SHOW_MENUS;
1281 }
1282 }
1283 }
1284 break;
1285
1286 case SDL_KEYUP:
1287 break;
1288
1289 case SDL_MOUSEMOTION:
1290 // Use: event.motion.xrel, event.motion.yrel (changes in position), event.motion.x, event.motion.y
1291 m_mouse_x = event.motion.x;
1292 m_mouse_y = event.motion.y;
1293 m_crosshairs->set_x(m_mouse_x);
1294 m_crosshairs->set_y(m_mouse_y);
1295 if (!m_server_browser->is_invisible()) {
1296 m_server_browser->scrollbar_motion_event(event.motion);
1297 }
1298 if (!m_overlay_scrollbar->is_invisible()) {
1299 m_overlay_scrollbar->mouse_motion_event(event.motion);
1300 }
1301 if (!m_chat_log->is_invisible()) {
1302 m_chat_log->scrollbar_motion_event(event.motion);
1303 }
1304 if (m_weapon_selector != NULL && m_weapons.size() > 0 /*&& !m_weapon_selector->get_graphic_group()->is_invisible()*/) {
1305 m_weapon_selector->mouse_motion_event(event.motion);
1306 }
1307
1308 if (m_game_state == SHOW_MENUS) {
1309 m_main_menu.mouse_motion_event(event.motion);
1310 // TODO: Move this. Why is it HERE?
1311 if (!m_network.is_connected() || !m_join_sent_time == 0) {
1312 m_item_resume->set_state(MenuItem::DISABLED);
1313 m_item_disconnect->set_state(MenuItem::DISABLED);
1314 }
1315 } else if (m_game_state == SHOW_OPTIONS_MENU) {
1316 m_options_menu.mouse_motion_event(event.motion);
1317 }
1318 break;
1319
1320 case SDL_MOUSEBUTTONDOWN:
1321 // Firing code, use event.button.button, event.button.x, event.button.y
1322 process_mouse_click(event);
1323 if (!m_server_browser->is_invisible()) {
1324 m_server_browser->scrollbar_button_event(event.button);
1325 }
1326 if (!m_overlay_scrollbar->is_invisible()) {
1327 m_overlay_scrollbar->mouse_button_event(event.button);
1328 }
1329 if (!m_chat_log->is_invisible()) {
1330 m_chat_log->scrollbar_button_event(event.button);
1331 }
1332 break;
1333
1334 case SDL_MOUSEBUTTONUP:
1335 process_mouse_click(event);
1336 if (!m_server_browser->is_invisible()) {
1337 m_server_browser->scrollbar_button_event(event.button);
1338 }
1339 if (!m_overlay_scrollbar->is_invisible()) {
1340 m_overlay_scrollbar->mouse_button_event(event.button);
1341 }
1342 if (!m_chat_log->is_invisible()) {
1343 m_chat_log->scrollbar_button_event(event.button);
1344 }
1345 break;
1346
1347 case SDL_QUIT:
1348 m_quit_game = true;
1349 break;
1350
1351 default:
1352 break;
1353 }
1354 }
1355
1356 // Check if left mouse button is held down, for weapon firing code.
1357 int mouse_x = 0;
1358 int mouse_y = 0;
1359 Uint8 mouse_state = SDL_GetMouseState(&mouse_x, &mouse_y);
1360 if (mouse_state&SDL_BUTTON(1)) {
1361 if (m_game_state == GAME_IN_PROGRESS) {
1362 if (m_players.empty() || m_players[m_player_id].is_frozen() || m_players[m_player_id].is_dead() || !m_current_weapon) {
1363 // Do nothing. We don't have a current player or weapon.
1364 } else if (!m_overlay_background->is_invisible()) {
1365 // Do nothing. The overlay is up.
1366 } else if (!m_chat_log->is_invisible()) {
1367 // Do nothing. The chat log is up.
1368 } else if (m_current_weapon->is_continuous()) {
1369 // Fire the gun if it's ready.
1370 double x_dist = (mouse_x + m_offset_x) - m_players[m_player_id].get_x();
1371 double y_dist = (mouse_y + m_offset_y) - m_players[m_player_id].get_y();
1372 double direction = atan2(y_dist, x_dist);
1373 if (m_last_weapon_switch == 0 || (get_ticks() - m_last_weapon_switch) > m_params.weapon_switch_delay) {
1374 m_current_weapon->fire(m_players[m_player_id], *this, m_players[m_player_id].get_position(), direction);
1375 }
1376 }
1377 }
1378 }
1379
1380 // Check if the right mouse button is held down, for weapon switching.
1381 if (mouse_state&SDL_BUTTON(3)) {
1382 if (GraphicalPlayer* player = get_player_by_id(m_player_id)) {
1383 if (m_weapons.size() > 0 && m_game_state == GAME_IN_PROGRESS) {
1384 m_weapon_selector->get_graphic_group()->set_invisible(false);
1385 }
1386 }
1387 } else {
1388 if (m_game_state == GAME_IN_PROGRESS) {
1389 if (!m_weapon_selector->get_graphic_group()->is_invisible()) {
1390 MenuItem* over = m_weapon_selector->item_at_position(mouse_x, mouse_y);
1391 if (over != NULL) {
1392 change_weapon(over->get_name().c_str());
1393 }
1394 }
1395 m_weapon_selector->get_graphic_group()->set_invisible(true);
1396 }
1397 }
1398
1399 parse_key_input();
1400 }
1401 /*
1402 * Set which keys are used for which functions.
1403 */
initialize_key_bindings()1404 void GameController::initialize_key_bindings() {
1405 // -1 = unused
1406 m_key_bindings.quit = -1;
1407 m_key_bindings.jump = SDLK_SPACE;
1408 m_key_bindings.show_overlay = SDLK_TAB;
1409 m_key_bindings.show_menu = SDLK_ESCAPE;
1410 m_key_bindings.open_chat = SDLK_t;
1411 m_key_bindings.open_team_chat = SDLK_y;
1412 m_key_bindings.open_console = SDLK_BACKQUOTE;
1413 m_key_bindings.send_chat = SDLK_RETURN;
1414 m_key_bindings.weapon_1 = SDLK_1;
1415 m_key_bindings.weapon_2 = SDLK_2;
1416 m_key_bindings.weapon_3 = SDLK_3;
1417 m_key_bindings.weapon_4 = SDLK_4;
1418 m_key_bindings.weapon_5 = SDLK_5;
1419 m_key_bindings.weapon_6 = SDLK_6;
1420 m_key_bindings.weapon_7 = SDLK_7;
1421 m_key_bindings.weapon_8 = SDLK_8;
1422
1423 m_alt_key_bindings.quit = -1;
1424 m_alt_key_bindings.jump = -1;
1425 m_alt_key_bindings.show_overlay = -1;
1426 m_alt_key_bindings.show_menu = -1;
1427 m_alt_key_bindings.open_chat = -1;
1428 m_alt_key_bindings.open_team_chat = -1;
1429 m_alt_key_bindings.open_console = -1;
1430 m_alt_key_bindings.send_chat = SDLK_KP_ENTER;
1431 m_alt_key_bindings.weapon_1 = SDLK_KP1;
1432 m_alt_key_bindings.weapon_2 = SDLK_KP2;
1433 m_alt_key_bindings.weapon_3 = SDLK_KP3;
1434 m_alt_key_bindings.weapon_4 = SDLK_KP4;
1435 m_alt_key_bindings.weapon_5 = SDLK_KP5;
1436 m_alt_key_bindings.weapon_6 = SDLK_KP6;
1437 m_alt_key_bindings.weapon_7 = SDLK_KP7;
1438 m_alt_key_bindings.weapon_8 = SDLK_KP8;
1439 }
1440
1441 /*
1442 * Used to process any keys that are to be held down rather than pressed once.
1443 */
parse_key_input()1444 void GameController::parse_key_input() {
1445 // For keys that can be held down:
1446 m_keys = SDL_GetKeyState(NULL);
1447
1448 if (m_game_state == GAME_IN_PROGRESS && m_focus == NULL) {
1449 if ((m_key_bindings.jump != -1 && m_keys[m_key_bindings.jump]) || (m_alt_key_bindings.jump != -1 && m_keys[m_alt_key_bindings.jump])) {
1450 attempt_jump();
1451 }
1452 if ((m_key_bindings.weapon_1 != -1 && m_keys[m_key_bindings.weapon_1]) || (m_alt_key_bindings.weapon_1 != -1 && m_keys[m_alt_key_bindings.weapon_1])) {
1453 change_weapon(0U);
1454 }
1455 if ((m_key_bindings.weapon_2 != -1 && m_keys[m_key_bindings.weapon_2]) || (m_alt_key_bindings.weapon_2 != -1 && m_keys[m_alt_key_bindings.weapon_2])) {
1456 change_weapon(1U);
1457 }
1458 if ((m_key_bindings.weapon_3 != -1 && m_keys[m_key_bindings.weapon_3]) || (m_alt_key_bindings.weapon_3 != -1 && m_keys[m_alt_key_bindings.weapon_3])) {
1459 change_weapon(2U);
1460 }
1461 if ((m_key_bindings.weapon_4 != -1 && m_keys[m_key_bindings.weapon_4]) || (m_alt_key_bindings.weapon_4 != -1 && m_keys[m_alt_key_bindings.weapon_4])) {
1462 change_weapon(3U);
1463 }
1464 if ((m_key_bindings.weapon_5 != -1 && m_keys[m_key_bindings.weapon_5]) || (m_alt_key_bindings.weapon_5 != -1 && m_keys[m_alt_key_bindings.weapon_5])) {
1465 change_weapon(4U);
1466 }
1467 if ((m_key_bindings.weapon_6 != -1 && m_keys[m_key_bindings.weapon_6]) || (m_alt_key_bindings.weapon_6 != -1 && m_keys[m_alt_key_bindings.weapon_6])) {
1468 change_weapon(5U);
1469 }
1470 if ((m_key_bindings.weapon_7 != -1 && m_keys[m_key_bindings.weapon_7]) || (m_alt_key_bindings.weapon_7 != -1 && m_keys[m_alt_key_bindings.weapon_7])) {
1471 change_weapon(6U);
1472 }
1473 if ((m_key_bindings.weapon_8 != -1 && m_keys[m_key_bindings.weapon_8]) || (m_alt_key_bindings.weapon_8 != -1 && m_keys[m_alt_key_bindings.weapon_8])) {
1474 change_weapon(7U);
1475 }
1476 }
1477
1478 if ((m_key_bindings.show_overlay != -1 && m_keys[m_key_bindings.show_overlay]) || (m_alt_key_bindings.show_overlay != -1 && m_keys[m_alt_key_bindings.show_overlay])) {
1479 if (m_game_state == GAME_IN_PROGRESS) {
1480 if (m_overlay_background->is_invisible() == true) {
1481 toggle_score_overlay(true);
1482 }
1483 } else if (m_game_state != GAME_OVER) {
1484 if (m_overlay_background->is_invisible() == false) {
1485 toggle_score_overlay(false);
1486 }
1487 }
1488 } else {
1489 if (m_game_state != GAME_OVER && m_overlay_background->is_invisible() == false) {
1490 toggle_score_overlay(false);
1491 }
1492 }
1493 }
1494
1495 /*
1496 * Process a mouse click, depending on the game state.
1497 */
process_mouse_click(SDL_Event event)1498 void GameController::process_mouse_click(SDL_Event event) {
1499 switch (m_game_state) {
1500 case SHOW_MENUS: {
1501 if (event.button.button != 1) {
1502 return;
1503 }
1504 MenuItem* item = m_main_menu.mouse_button_event(event.button);
1505 if (item && event.type == SDL_MOUSEBUTTONUP) {
1506 item->set_state(MenuItem::NORMAL);
1507 m_sound_controller->play_sound("click");
1508 if (item->get_name() == "quit") {
1509 m_quit_game = true;
1510 } else if (item->get_name() == "resume") {
1511 if (!m_players.empty()) {
1512 m_game_state = GAME_IN_PROGRESS;
1513 } else {
1514 display_message("Not connected to server.");
1515 }
1516 } else if (item->get_name() == "submenu:options") {
1517 m_game_state = SHOW_OPTIONS_MENU;
1518 } else if (item->get_name() == "disconnect") {
1519 if (!m_players.empty()) {
1520 disconnect();
1521 } else {
1522 display_message("Not connected to server.");
1523 }
1524 } else if (item->get_name() == "connect") {
1525 m_game_state = SHOW_SERVER_BROWSER;
1526 scan_all();
1527 }
1528 }
1529 }
1530 break;
1531 case SHOW_OPTIONS_MENU: {
1532 if (event.button.button != 1) {
1533 return;
1534 }
1535 MenuItem* item = m_options_menu.mouse_button_event(event.button);
1536 if (item && event.type == SDL_MOUSEBUTTONUP) {
1537 m_sound_controller->play_sound("click");
1538 if(item->get_name() == "cancel") {
1539 if (m_focus == m_name_input) {
1540 // TODO generalize
1541 m_name_bar_back->set_border_color(Color(0, 0, 0, 0));
1542 m_focus = NULL;
1543 }
1544 reset_options();
1545 m_game_state = SHOW_MENUS;
1546 } else if(item->get_name() == "name") {
1547 // Open the input bar and allow the name to be entered.
1548 SDL_EnableUNICODE(1);
1549 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
1550 if (m_focus != NULL && m_focus != m_name_input) {
1551 m_focus->reset();
1552 m_focus->set_invisible(true);
1553 }
1554 m_name_bar_back->set_border_color(TEXT_COLOR);
1555 m_focus = m_name_input;
1556 } else {
1557 m_name_bar_back->set_border_color(Color(0, 0, 0, 0));
1558 m_focus = NULL;
1559 if(item->get_name() == "apply") {
1560 string resolution = m_options_form.get_item("resolution")->get_value();
1561 size_t xpos = resolution.find('x');
1562 int width;
1563 int height;
1564 int multisample;
1565 istringstream wstring(resolution.substr(0, xpos));
1566 istringstream hstring(resolution.substr(xpos + 1));
1567 istringstream mstring(m_options_form.get_item("multisample")->get_value());
1568 wstring >> width;
1569 hstring >> height;
1570 mstring >> multisample;
1571 m_sound_controller->set_sound_on(m_options_form.get_item("sound")->get_value() == "on");
1572 bool fullscreen = m_options_form.get_item("fullscreen")->get_value() == "on";
1573 bool text_shadow = m_options_form.get_item("text_shadow")->get_value() == "on";
1574 bool text_sliding = m_options_form.get_item("text_sliding")->get_value() == "on";
1575 bool text_background = m_options_form.get_item("text_background")->get_value() == "on";
1576 bool vsync = m_options_form.get_item("vsync")->get_value() == "on";
1577 if (width != m_configuration->get_int_value("screen_width") ||
1578 height != m_configuration->get_int_value("screen_height") ||
1579 fullscreen != m_configuration->get_bool_value("fullscreen") ||
1580 vsync != m_configuration->get_bool_value("vsync") ||
1581 multisample != m_configuration->get_int_value("multisample") ||
1582 text_shadow != m_configuration->get_bool_value("text_shadow") ||
1583 text_sliding != m_configuration->get_bool_value("text_sliding") ||
1584 text_background != m_configuration->get_bool_value("text_background")) {
1585 m_configuration->set_int_value("screen_width", width);
1586 m_configuration->set_int_value("screen_height", height);
1587 m_configuration->set_bool_value("fullscreen", fullscreen);
1588 m_configuration->set_bool_value("vsync", vsync);
1589 m_configuration->set_int_value("multisample", multisample);
1590 m_configuration->set_bool_value("text_shadow", text_shadow);
1591 m_configuration->set_bool_value("text_sliding", text_sliding);
1592 m_configuration->set_bool_value("text_background", text_background);
1593 m_restart = true;
1594 m_quit_game = true;
1595 } else {
1596 m_game_state = SHOW_MENUS;
1597 }
1598
1599 string new_name(m_name_input->get_value());
1600 if (new_name != m_name) {
1601 if (!m_network.is_connected()) {
1602 ostringstream msg;
1603 msg << "You are now known as " << new_name;
1604 display_message(msg.str());
1605 }
1606 set_player_name(new_name);
1607 }
1608 }
1609 }
1610 }
1611 }
1612 break;
1613 case SHOW_SERVER_BROWSER: {
1614 if (event.button.button != 1 || event.type != SDL_MOUSEBUTTONUP) {
1615 return;
1616 }
1617
1618 string button = m_server_browser->check_button_press(event.button.x, event.button.y);
1619
1620 if (button != "") {
1621 m_sound_controller->play_sound("click");
1622 }
1623
1624 if (button == "Back") {
1625 // Back.
1626 m_game_state = SHOW_MENUS;
1627 m_server_browser->deselect();
1628 } else if (button == "Refresh") {
1629 // Refresh.
1630 m_server_browser->clear();
1631 m_server_browser->deselect();
1632 scan_all();
1633 } else if (button == "Connect") {
1634 // Connect.
1635 int selected_item = m_server_browser->get_selected_item();
1636 if (selected_item < 0 || selected_item > m_server_browser->get_count()) {
1637 return;
1638 }
1639 connect_to_server(selected_item);
1640 m_server_browser->set_visible(false);
1641 m_game_state = SHOW_MENUS;
1642 }
1643
1644 if (m_server_browser->get_count() == 0) {
1645 return;
1646 }
1647
1648 int selected_item = m_server_browser->check_item_select(event.button.x, event.button.y);
1649
1650 if (selected_item < 0) {
1651 return;
1652 }
1653
1654 m_sound_controller->play_sound("click");
1655
1656 if (m_last_clicked > get_ticks() - DOUBLE_CLICK_TIME) {
1657 connect_to_server(selected_item);
1658 m_server_browser->set_visible(false);
1659 m_server_browser->deselect();
1660 m_game_state = SHOW_MENUS;
1661 }
1662 }
1663 break;
1664 case GAME_IN_PROGRESS: {
1665 if (!m_overlay_background->is_invisible()) {
1666 // Do nothing.
1667 } else if (!m_chat_log->is_invisible()) {
1668 // Do nothing.
1669 } else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
1670 if (event.button.button == SDL_BUTTON_LEFT) {
1671 // Fire the gun if it's ready.
1672 if (m_players.empty() || m_players[m_player_id].is_frozen() || m_players[m_player_id].is_dead() || !m_current_weapon) {
1673 return;
1674 }
1675
1676 m_current_weapon->mouse_button_event(event);
1677
1678 if (event.type != SDL_MOUSEBUTTONDOWN) {
1679 return;
1680 }
1681
1682 double x_dist = (event.button.x + m_offset_x) - m_players[m_player_id].get_x();
1683 double y_dist = (event.button.y + m_offset_y) - m_players[m_player_id].get_y();
1684 double direction = atan2(y_dist, x_dist);
1685 if (m_last_weapon_switch == 0 || (get_ticks() - m_last_weapon_switch) > m_params.weapon_switch_delay) {
1686 m_current_weapon->fire(m_players[m_player_id], *this, m_players[m_player_id].get_position(), direction);
1687 }
1688 } else if (event.type == SDL_MOUSEBUTTONUP) {
1689 if (event.button.button == SDL_BUTTON_WHEELUP) {
1690 next_weapon();
1691 } else if (event.button.button == SDL_BUTTON_WHEELDOWN) {
1692 previous_weapon();
1693 }
1694 }
1695 }
1696 }
1697 }
1698 m_last_clicked = get_ticks();
1699 }
1700
1701 /*
1702 * Reset the options menu, without applying the changes.
1703 */
reset_options()1704 void GameController::reset_options() {
1705 m_text_manager->set_active_font(m_menu_font);
1706 m_text_manager->set_active_color(TEXT_COLOR);
1707 m_options_form.reset();
1708 }
1709
1710 /*
1711 * Do the movement of objects in a certain time scale.
1712 */
move_objects(float timescale)1713 void GameController::move_objects(float timescale) {
1714 if (timescale > 1.0) {
1715 while (timescale > 1.0) {
1716 timescale -= 1.0;
1717 move_objects(1.0);
1718 }
1719 }
1720 if (m_players.empty()) {
1721 return;
1722 }
1723
1724 GraphicalPlayer& player(m_players[m_player_id]);
1725
1726 Point oldpos(player.get_x(), player.get_y());
1727
1728 player.update_position(timescale);
1729
1730 double half_width = player.get_radius();
1731 double half_height = player.get_radius();
1732
1733 // Check if the player is hitting a map edge.
1734 bool is_frozen = player.is_frozen() || player.is_dead();
1735 if (player.get_x() - half_width < 0) {
1736 player.set_x(half_width);
1737 player.set_y(oldpos.y);
1738 if (is_frozen && !player.is_invisible()) {
1739 player.bounce(0, 0.9);
1740 } else {
1741 player.stop();
1742 player.set_is_grabbing_obstacle(true);
1743 }
1744 } else if (player.get_x() + half_width > m_map_width) {
1745 player.set_x(m_map_width - half_width);
1746 player.set_y(oldpos.y);
1747 if (is_frozen && !player.is_invisible()) {
1748 player.bounce(180, 0.9);
1749 } else {
1750 player.stop();
1751 player.set_is_grabbing_obstacle(true);
1752 }
1753 }
1754
1755 if (player.get_y() - half_height < 0) {
1756 player.set_x(oldpos.x);
1757 player.set_y(half_height);
1758 if (is_frozen && !player.is_invisible()) {
1759 player.bounce(90, 0.9);
1760 } else {
1761 player.stop();
1762 player.set_is_grabbing_obstacle(true);
1763 }
1764 } else if (player.get_y() + half_height > m_map_height) {
1765 player.set_x(oldpos.x);
1766 player.set_y(m_map_height - half_height);
1767 if (is_frozen && !player.is_invisible()) {
1768 player.bounce(270, 0.9);
1769 } else {
1770 player.stop();
1771 player.set_is_grabbing_obstacle(true);
1772 }
1773 }
1774
1775 Point newpos(player.get_x(), player.get_y());
1776 double radius = player.get_radius();
1777 Circle player_circle(newpos, radius);
1778 Circle old_player_circle(oldpos, radius);
1779
1780 // Check each object for collisions with the player.
1781 const list<BaseMapObject*>& map_objects(m_map->get_objects());
1782 for (list<BaseMapObject*>::const_iterator it(map_objects.begin()); it != map_objects.end(); it++) {
1783 BaseMapObject* thisobj = *it;
1784
1785 if (!thisobj->is_intersectable()) {
1786 continue;
1787 }
1788
1789 const Shape& shape(*thisobj->get_bounding_shape());
1790 double angle_of_incidence = 0;
1791 double newdist = -1;
1792
1793 if (thisobj->is_interactive()) {
1794 newdist = shape.solid_intersects_circle(player_circle, &angle_of_incidence);
1795 angle_of_incidence = get_normalized_angle(angle_of_incidence + 180);
1796 } else if (thisobj->is_collidable()) {
1797 newdist = shape.boundary_intersects_circle(player_circle, &angle_of_incidence);
1798 angle_of_incidence = get_normalized_angle(angle_of_incidence + 180);
1799 }
1800
1801 if (newdist != -1) {
1802 // We are intersecting with the object
1803
1804 if (thisobj->is_collidable()) {
1805 double olddist = shape.boundary_intersects_circle(old_player_circle, NULL);
1806 if (newdist < olddist) {
1807 // We're moving closer to the object... COLLISION!
1808 thisobj->collide(*this, player, oldpos, angle_of_incidence);
1809 }
1810 }
1811 if (thisobj->is_interactive()) {
1812 thisobj->interact(*this, player);
1813 }
1814
1815 } else if (thisobj->is_interactive() && thisobj->is_engaged()) {
1816 // We are no longer engaging the object, but we were previously
1817 thisobj->disengage(*this, player);
1818 }
1819 }
1820
1821 // Update player rotation
1822 player.update_rotation(timescale);
1823
1824 // Set the player position and radar position.
1825 m_radar->move_blip(m_player_id, player.get_x(), player.get_y());
1826 m_radar->recenter(player.get_x(), player.get_y());
1827
1828 // Set name sprites visible/invisible. and move players.
1829 map<uint32_t, GraphicalPlayer>::iterator it;
1830 for ( it=m_players.begin() ; it != m_players.end(); it++ ) {
1831 GraphicalPlayer& currplayer(it->second);
1832 if (currplayer.is_invisible()) {
1833 currplayer.get_name_sprite()->set_invisible(true);
1834 } else {
1835 currplayer.get_name_sprite()->set_invisible(false);
1836 m_text_manager->reposition_string(currplayer.get_name_sprite(), currplayer.get_x(), currplayer.get_y() - (currplayer.get_radius()+30), TextManager::CENTER);
1837 }
1838
1839 if (currplayer.get_id() == m_player_id) {
1840 continue;
1841 }
1842
1843 currplayer.update_position(timescale);
1844 m_radar->move_blip(currplayer.get_id(), currplayer.get_x(), currplayer.get_y());
1845 }
1846 }
1847
1848 /*
1849 * Try to jump off of an obstacle.
1850 */
attempt_jump()1851 void GameController::attempt_jump() {
1852 if (m_players.empty()) {
1853 return;
1854 }
1855
1856 GraphicalPlayer& player = m_players[m_player_id];
1857
1858 //
1859 // Make sure the player is able to jump right now
1860 //
1861 if (player.is_frozen() || player.is_dead() || player.is_invisible() || !player.is_grabbing_obstacle()) {
1862 return;
1863 }
1864
1865 double jump_angle = get_crosshairs_angle();
1866
1867 //
1868 // Calculate the new velocities
1869 //
1870 Vector new_velocity(Vector::make_from_magnitude(m_params.jump_velocity, jump_angle));
1871 double new_rotation = (((double)rand() / ((double)(RAND_MAX)+1)) - 0.5) * RANDOM_ROTATION_SCALE;
1872
1873 //
1874 // Find the nearest obstacle that we are jumping towards
1875 //
1876 Circle player_circle(player.get_position(), player.get_radius()+5);
1877 Point start_pos(player.get_position());
1878 Point end_pos(start_pos + new_velocity.get_unit_vector() * (m_map_width + m_map_height));
1879 double angle_of_incidence = 0;
1880 double shortest_dist = numeric_limits<double>::infinity();
1881
1882 const list<BaseMapObject*>& map_objects(m_map->get_objects());
1883 for (list<BaseMapObject*>::const_iterator it(map_objects.begin()); it != map_objects.end(); it++) {
1884 BaseMapObject* map_obj = *it;
1885
1886 if (!map_obj->is_jumpable() || !map_obj->is_intersectable()) {
1887 continue;
1888 }
1889
1890 // See if we're colliding with this obstacle right now
1891 double colliding_angle_of_incidence;
1892 if (map_obj->get_bounding_shape()->boundary_intersects_circle(player_circle, &colliding_angle_of_incidence) != -1) {
1893 double angle_difference = get_normalized_angle(colliding_angle_of_incidence - to_degrees(jump_angle));
1894 if (angle_difference < 90 || angle_difference > 270) {
1895 // We are jumping RIGHT INTO the obstacle that we're already colliding with
1896 // Abort the jump, since it can take us nowhere
1897 // (If the jump weren't aborted, it would cause problems with active map obstacles)
1898 return;
1899 }
1900 }
1901
1902 // See if our trajectory collides with this obstacle
1903 double new_angle = 0;
1904 Point new_point = map_obj->get_bounding_shape()->intersects_line(start_pos, end_pos, &new_angle);
1905
1906 if (new_point.x == -1 && new_point.y == -1) {
1907 // Not in the trajectory - continue to next obstacle...
1908 continue;
1909 }
1910
1911 // How far away is the obstacle from us?
1912 double new_dist = Point::distance(start_pos, new_point);
1913
1914 if (new_dist != -1 && new_dist < shortest_dist) {
1915 shortest_dist = new_dist;
1916 angle_of_incidence = new_angle;
1917 }
1918 }
1919
1920 if (isinf(shortest_dist)) {
1921 // Try the map edge
1922 double new_angle = 0;
1923 Point new_point = m_map_polygon.intersects_line(start_pos, end_pos, &new_angle);
1924
1925 if (new_point.x != -1 && new_point.y != -1) {
1926 shortest_dist = Point::distance(start_pos, new_point);
1927 angle_of_incidence = new_angle;
1928 }
1929 }
1930
1931 //
1932 // Set the player in motion
1933 //
1934 player.set_velocity(new_velocity);
1935 player.set_rotational_vel(new_rotation);
1936
1937 if (finite(shortest_dist)) {
1938 //
1939 // Set our rotational velocity so that we will land on this obstacle in a good orientation (i.e. head not hitting the wall)
1940 //
1941 angle_of_incidence = get_normalized_angle(angle_of_incidence + 180);
1942
1943 double currangle = player.get_sprite()->get_rotation();
1944 double adjusted_angle = get_normalized_angle(currangle + 90 - angle_of_incidence);
1945 double newangle = currangle;
1946 if (adjusted_angle < 110) {
1947 newangle = currangle + (110 - adjusted_angle);
1948 } else if (adjusted_angle > 250) {
1949 newangle = currangle - (adjusted_angle - 250);
1950 }
1951
1952 if (newangle != currangle) {
1953 double time_till_hit = shortest_dist / new_velocity.get_magnitude();
1954 double ideal_velocity = (newangle - currangle) / time_till_hit;
1955
1956 if (ideal_velocity < -2.0)
1957 player.set_rotational_vel(-2.0);
1958 else if (ideal_velocity > 2.0)
1959 player.set_rotational_vel(2.0);
1960 else
1961 player.set_rotational_vel(ideal_velocity);
1962 }
1963 }
1964 }
1965
show_muzzle_flash()1966 void GameController::show_muzzle_flash() {
1967 // Switch to the gun with the muzzle flash.
1968 Graphic* frontarm = m_players[m_player_id].get_sprite()->get_graphic("frontarm");
1969 frontarm->get_graphic("firing")->set_invisible(false);
1970 send_animation_packet("frontarm/firing", "invisible", false);
1971 frontarm->get_graphic("normal")->set_invisible(true);
1972 send_animation_packet("frontarm/normal", "invisible", true);
1973 m_muzzle_flash_start = get_ticks();
1974 }
1975
show_bullet_impact(Point position,const char * sprite_name)1976 void GameController::show_bullet_impact(Point position, const char* sprite_name) {
1977 if (!sprite_name[0]) {
1978 return;
1979 }
1980 Sprite* this_shot = m_graphics_cache.new_graphic<Sprite>(m_path_manager.data_path(sprite_name, "sprites"));
1981 this_shot->set_x(position.x);
1982 this_shot->set_y(position.y);
1983 this_shot->set_scale_x(.1);
1984 this_shot->set_scale_y(.1);
1985 this_shot->set_invisible(false);
1986 pair<Graphic*, unsigned int> new_shot(this_shot, get_ticks() + SHOT_DISPLAY_TIME);
1987 m_shots.push_back(new_shot);
1988 m_window->register_graphic(this_shot, GameWindow::LAYER_GAME);
1989 }
1990
1991 /*
1992 * Set the player sprites visible or invisible.
1993 */
set_players_visible(bool visible)1994 void GameController::set_players_visible(bool visible) {
1995 if (m_players.empty()) {
1996 return;
1997 }
1998
1999 map<uint32_t, GraphicalPlayer>::iterator it;
2000 for ( it=m_players.begin() ; it != m_players.end(); it++ ) {
2001 const GraphicalPlayer& currplayer = (*it).second;
2002 if (currplayer.get_sprite() == NULL) {
2003 continue;
2004 }
2005 if (visible) {
2006 currplayer.get_sprite()->set_invisible(currplayer.is_invisible());
2007 currplayer.get_name_sprite()->set_invisible(currplayer.is_invisible());
2008 m_radar->set_blip_invisible(it->first,currplayer.is_invisible());
2009 } else {
2010 currplayer.get_sprite()->set_invisible(true);
2011 currplayer.get_name_sprite()->set_invisible(true);
2012 m_radar->set_blip_invisible(it->first,true);
2013 }
2014 }
2015 }
2016
2017 /*
2018 * Show or hide the overlay.
2019 */
toggle_score_overlay(bool visible)2020 void GameController::toggle_score_overlay(bool visible) {
2021 update_individual_scores();
2022 m_overlay_background->set_invisible(!visible);
2023 m_overlay_scrollbar->set_invisible(!visible);
2024 m_overlay_scrollarea->set_invisible(!visible);
2025 map<string, Text*>::iterator it;
2026 for ( it=m_overlay_items.begin() ; it != m_overlay_items.end(); it++ ) {
2027 Graphic* thisitem = (*it).second;
2028 thisitem->set_invisible(!visible);
2029 }
2030 }
2031
2032 /*
2033 * Show or hide the main menu
2034 */
toggle_main_menu(bool visible)2035 void GameController::toggle_main_menu(bool visible) {
2036 m_main_menu.get_graphic_group()->set_invisible(!visible);
2037 }
2038
2039 /*
2040 * Show or hide the options menu
2041 */
toggle_options_menu(bool visible)2042 void GameController::toggle_options_menu(bool visible) {
2043 m_options_menu.get_graphic_group()->set_invisible(!visible);
2044 m_name_input->set_invisible(!visible);
2045 }
2046
2047 /*
2048 * Change team scores.
2049 */
change_team_scores(int bluescore,int redscore)2050 void GameController::change_team_scores(int bluescore, int redscore) {
2051 m_text_manager->set_active_font(m_menu_font);
2052 m_text_manager->set_active_color(TEXT_COLOR);
2053
2054 if (redscore != -1) {
2055 if (m_overlay_items.count("red score") != 0) {
2056 m_text_manager->remove_string(m_overlay_items["red score"]);
2057 }
2058
2059 stringstream redscoreprinter;
2060 redscoreprinter << redscore;
2061 m_overlay_items["red score"] = m_text_manager->place_string(redscoreprinter.str(), m_overlay_items["red label"]->get_x() + m_overlay_items["red label"]->get_image_width() + 10, 115, TextManager::LEFT, GameWindow::LAYER_HUD, TEXT_LAYER);
2062 }
2063
2064 if (bluescore != -1) {
2065 if (m_overlay_items.count("blue score") != 0) {
2066 m_text_manager->remove_string(m_overlay_items["blue score"]);
2067 }
2068
2069 stringstream bluescoreprinter;
2070 bluescoreprinter << bluescore;
2071 m_overlay_items["blue score"] = m_text_manager->place_string(bluescoreprinter.str(), m_overlay_items["blue label"]->get_x() + m_overlay_items["blue label"]->get_image_width() + 10, 115, TextManager::LEFT, GameWindow::LAYER_HUD, TEXT_LAYER);
2072 }
2073
2074 m_text_manager->set_active_font(m_font);
2075 }
2076
2077 /*
2078 * Update individual scores.
2079 */
update_individual_scores()2080 void GameController::update_individual_scores() {
2081 // Place all the players into one of two lists based on their team
2082 list<const GraphicalPlayer*> blue_players;
2083 list<const GraphicalPlayer*> red_players;
2084
2085 if (m_players.empty()) {
2086 return;
2087 }
2088
2089 for (map<uint32_t, GraphicalPlayer>::iterator it = m_players.begin(); it != m_players.end(); ++it) {
2090 if (it->second.get_sprite() != NULL && it->second.get_team() == 'A') {
2091 blue_players.push_back(&it->second);
2092 } else if (it->second.get_sprite() != NULL && it->second.get_team() == 'B') {
2093 red_players.push_back(&it->second);
2094 }
2095 }
2096 // Sort these lists by score
2097 blue_players.sort(Player::compare_by_score());
2098 red_players.sort(Player::compare_by_score());
2099
2100 m_text_manager->set_active_font(m_medium_font);
2101 m_text_manager->set_active_color(TEXT_COLOR);
2102
2103 int count = 0;
2104
2105 for (list<const GraphicalPlayer*>::iterator it = blue_players.begin() ; it != blue_players.end(); ++it) {
2106 update_individual_score_line(count++, **it);
2107 }
2108
2109 // Skip two lines between the two teams
2110 count += 2;
2111
2112 for (list<const GraphicalPlayer*>::iterator it = red_players.begin() ; it != red_players.end(); ++it) {
2113 update_individual_score_line(count++, **it);
2114 }
2115
2116 m_text_manager->set_active_font(m_font);
2117 }
2118
update_individual_score_line(int count,const GraphicalPlayer & currplayer)2119 void GameController::update_individual_score_line(int count, const GraphicalPlayer& currplayer) {
2120
2121 string playername = currplayer.get_name();
2122 stringstream idprinter;
2123 idprinter << currplayer.get_id();
2124 string playerid = idprinter.str();
2125 string playernameforscore = currplayer.get_name();
2126 string playerscore = playernameforscore.append("score");
2127
2128 m_text_manager->set_active_color(currplayer.get_team() == 'A' ? BLUE_COLOR : RED_COLOR);
2129 m_text_manager->set_shadow_color(currplayer.get_team() == 'A' ? BLUE_SHADOW : RED_SHADOW);
2130
2131 stringstream scoreprinter;
2132 scoreprinter << currplayer.get_score();
2133 m_overlay_items[playerid] = m_text_manager->render_string(playername, 10, count*25, TextManager::LEFT);
2134 m_overlay_items[playerscore] = m_text_manager->render_string(scoreprinter.str(), m_overlay_background->get_image_width()/2, count*25, TextManager::LEFT);
2135
2136 m_overlay_items[playerid]->set_priority(TEXT_LAYER);
2137 m_overlay_scrollarea->get_group()->remove_graphic(playerid);
2138 m_overlay_scrollarea->get_group()->add_graphic(m_overlay_items[playerid], playerid);
2139
2140 m_overlay_items[playerscore]->set_priority(TEXT_LAYER);
2141 m_overlay_scrollarea->get_group()->remove_graphic(playerscore);
2142 m_overlay_scrollarea->get_group()->add_graphic(m_overlay_items[playerscore], playerscore);
2143
2144 if (m_overlay_items[playerid]->get_y() + m_overlay_items[playerid]->get_image_height() + 2 > m_overlay_scrollarea->get_content_height()) {
2145 m_overlay_scrollarea->set_content_height(m_overlay_items[playerid]->get_y() + m_overlay_items[playerid]->get_image_height() + 2);
2146 }
2147
2148 m_text_manager->set_shadow_color(TEXT_SHADOW);
2149 }
2150
delete_individual_score(const GraphicalPlayer & currplayer)2151 void GameController::delete_individual_score(const GraphicalPlayer& currplayer) {
2152 stringstream idprinter;
2153 idprinter << currplayer.get_id();
2154 string playerid = idprinter.str();
2155 string playernameforscore = currplayer.get_name();
2156 string playerscore = playernameforscore.append("score");
2157
2158 m_overlay_scrollarea->get_group()->remove_graphic(playerid);
2159 m_overlay_scrollarea->get_group()->remove_graphic(playerscore);
2160 m_overlay_items.erase(playerscore);
2161 m_overlay_items.erase(playerid);
2162 }
2163
2164 /*
2165 * Try to connect to a server.
2166 */
connect_to_server(const IPAddress & server_address,char team)2167 void GameController::connect_to_server(const IPAddress& server_address, char team) {
2168 if (!m_network.connect(server_address)) {
2169 ostringstream errmsg;
2170 errmsg << "Error: Could not connect to server at " << server_address;
2171 display_message(errmsg.str());
2172 cerr << errmsg.str() << endl;
2173 return;
2174 }
2175
2176 update_energy_bar(0);
2177
2178 PacketWriter join_request(JOIN_PACKET);
2179 join_request << m_protocol_number;
2180 join_request << COMPAT_VERSION;
2181 join_request << m_name;
2182 if (is_valid_team(team)) {
2183 join_request << team;
2184 }
2185
2186 m_join_sent_time = get_ticks();
2187
2188 m_network.send_reliable_packet(join_request);
2189 }
2190
2191 /*
2192 * Try to connect to a server from the server list by number.
2193 */
connect_to_server(int servernum)2194 void GameController::connect_to_server(int servernum) {
2195 disconnect();
2196 connect_to_server(m_server_browser->get_server_info(servernum));
2197 }
2198
2199 /*
2200 * Send a disconnect packet.
2201 */
disconnect()2202 void GameController::disconnect() {
2203 m_item_resume->set_state(MenuItem::DISABLED);
2204 m_item_disconnect->set_state(MenuItem::DISABLED);
2205 if (!m_players.empty()) {
2206 PacketWriter leave_request(LEAVE_PACKET);
2207 leave_request << m_player_id;
2208 m_network.send_packet(leave_request);
2209
2210 clear_players();
2211 m_game_state = SHOW_MENUS;
2212 m_player_id = 0;
2213
2214 display_message("Disconnected.");
2215 }
2216 m_network.disconnect();
2217 m_map->clear();
2218 }
2219
2220 /*
2221 * When we receive a welcome packet.
2222 */
welcome(PacketReader & reader)2223 void GameController::welcome(PacketReader& reader) {
2224 int server_proto_version;
2225 int playerid;
2226 string playername;
2227 char team;
2228
2229 reader >> server_proto_version >> playerid >> playername >> team;
2230
2231 m_player_id = playerid;
2232 m_name = playername;
2233
2234 cout << "Received welcome packet. Player ID: " << playerid << ", Name: " << playername << ", Team: " << team << endl;
2235
2236 if (server_proto_version != m_protocol_number) {
2237 ostringstream serveraddress;
2238 serveraddress << "Error: Server has a different protocol. Server: ";
2239 serveraddress << server_proto_version;
2240 serveraddress << " You: ";
2241 serveraddress << m_protocol_number;
2242 if (server_proto_version > m_protocol_number) {
2243 serveraddress << ". Please upgrade your client.";
2244 } else {
2245 serveraddress << ". Your client is too new.";
2246 }
2247 display_message(serveraddress.str());
2248
2249 disconnect();
2250 }
2251
2252 ostringstream serveraddress;
2253 serveraddress << "Connected to server: ";
2254 serveraddress << format_ip_address(m_network.get_server_address(), true);
2255 display_message(serveraddress.str());
2256
2257 m_join_sent_time = 0;
2258
2259 m_item_resume->set_state(MenuItem::NORMAL);
2260 m_item_disconnect->set_state(MenuItem::NORMAL);
2261
2262 // Reset gate packet sequence numbers.
2263 m_last_gate_packet_seq_no[0] = 0;
2264 m_last_gate_packet_seq_no[1] = 0;
2265
2266 clear_players();
2267
2268 // Insert different name colors and sprites depending on team.
2269 if (team == 'A') {
2270 m_players.insert(pair<int, GraphicalPlayer>(m_player_id,GraphicalPlayer(m_name.c_str(), m_player_id, team, new GraphicGroup(blue_player), blue_sprite->get_width()/2, blue_sprite->get_height()/2)));
2271 m_text_manager->set_active_color(BLUE_COLOR);
2272 m_text_manager->set_shadow_color(BLUE_SHADOW);
2273 } else {
2274 m_players.insert(pair<int, GraphicalPlayer>(m_player_id,GraphicalPlayer(m_name.c_str(), m_player_id, team, new GraphicGroup(red_player), red_sprite->get_width()/2, red_sprite->get_height()/2)));
2275 m_text_manager->set_active_color(RED_COLOR);
2276 m_text_manager->set_shadow_color(RED_SHADOW);
2277 }
2278 m_window->register_graphic(m_players[m_player_id].get_sprite(), GameWindow::LAYER_GAME);
2279 m_radar->add_blip(m_player_id, team, 0, 0);
2280 // Because our blip never gets "activated", and if the mode is aural, we won't get any alpha, so add some
2281 m_radar->set_blip_alpha(m_player_id, 1.0);
2282
2283 m_players[m_player_id].set_radius(30);
2284 m_players[m_player_id].set_name_sprite(m_text_manager->place_string(m_players[m_player_id].get_name(), m_screen_width/2, (m_screen_height/2)-(m_players[m_player_id].get_radius()+30), TextManager::CENTER, GameWindow::LAYER_GAME));
2285 m_players[m_player_id].set_is_invisible(true);
2286
2287 send_my_player_update();
2288
2289 m_game_state = GAME_IN_PROGRESS;
2290
2291 m_text_manager->set_shadow_color(TEXT_SHADOW);
2292 }
2293
2294 /*
2295 * Add a player when we get an announce packet.
2296 */
announce(PacketReader & reader)2297 void GameController::announce(PacketReader& reader) {
2298 unsigned int playerid;
2299 string playername;
2300 char team;
2301
2302 if (m_players.empty()) {
2303 // WELCOME packet not received yet
2304 // do NOT send an ACK for this ANNOUNCE packet, so that the server will resend it, hopefully after the WELCOME has come in.
2305 return;
2306 }
2307
2308 reader >> playerid >> playername >> team;
2309
2310 string joinmsg = "";
2311 joinmsg.append(playername);
2312 joinmsg.append(" has joined the game!");
2313 if (team == 'A') {
2314 display_message(joinmsg, BLUE_COLOR, BLUE_SHADOW);
2315 } else {
2316 display_message(joinmsg, RED_COLOR, RED_SHADOW);
2317 }
2318
2319 // Ignore announce packet for ourself, but still display join message (above)
2320 if (playerid == m_player_id) {
2321 return;
2322 }
2323
2324 // Add a different sprite and name color depending on team.
2325 if (team == 'A') {
2326 m_players.insert(pair<int, GraphicalPlayer>(playerid,GraphicalPlayer((const char*)playername.c_str(), playerid, team, new GraphicGroup(blue_player))));
2327 m_text_manager->set_active_color(BLUE_COLOR);
2328 m_text_manager->set_shadow_color(BLUE_SHADOW);
2329 } else {
2330 m_players.insert(pair<int, GraphicalPlayer>(playerid,GraphicalPlayer((const char*)playername.c_str(), playerid, team, new GraphicGroup(red_player))));
2331 m_text_manager->set_active_color(RED_COLOR);
2332 m_text_manager->set_shadow_color(RED_SHADOW);
2333 }
2334 m_radar->add_blip(playerid,team,0,0);
2335
2336 // Register the player sprite with the window
2337 m_window->register_graphic(m_players[playerid].get_sprite(), GameWindow::LAYER_GAME);
2338 m_players[playerid].set_name_sprite(m_text_manager->place_string(m_players[playerid].get_name(), m_players[playerid].get_x(), m_players[playerid].get_y()-(m_players[playerid].get_radius()+30), TextManager::CENTER, GameWindow::LAYER_GAME));
2339 m_players[playerid].set_radius(40);
2340 m_text_manager->set_shadow_color(TEXT_SHADOW);
2341 }
2342
2343 /*
2344 * When we receive a player update.
2345 */
player_update(PacketReader & reader)2346 void GameController::player_update(PacketReader& reader) {
2347 if (m_players.empty()) {
2348 return;
2349 }
2350
2351 uint32_t player_id;
2352 reader >> player_id;
2353
2354 if (player_id == m_player_id) {
2355 // If the player update packet is for this player, send an ACK for it
2356 }
2357
2358 GraphicalPlayer* currplayer = get_player_by_id(player_id);
2359 if (currplayer == NULL) {
2360 cerr << "Error: Received update packet for non-existent player " << player_id << endl;
2361 return;
2362 }
2363
2364 bool wasfrozen = currplayer->is_frozen();
2365 string old_weapon_id = currplayer->get_current_weapon_id();
2366
2367 currplayer->read_update_packet(reader);
2368
2369 if (wasfrozen != currplayer->is_frozen()) {
2370 recreate_name(currplayer);
2371 }
2372
2373 if (player_id == m_player_id) {
2374 update_energy_bar();
2375 }
2376
2377 // Update the radar and name sprite
2378 m_radar->move_blip(player_id, currplayer->get_x(), currplayer->get_y());
2379 m_radar->set_blip_invisible(player_id, currplayer->is_invisible());
2380 currplayer->get_name_sprite()->set_invisible(currplayer->is_invisible());
2381
2382 // If invisible or frozen, set these things appropriately and show/hide the sprite.
2383 if (currplayer->is_invisible()) {
2384 currplayer->set_velocity(0, 0);
2385 } else {
2386 // Reposition the name sprite to reflect the player's new position
2387 m_text_manager->reposition_string(currplayer->get_name_sprite(), currplayer->get_x(), currplayer->get_y() - (currplayer->get_radius()+30), TextManager::CENTER);
2388 }
2389
2390 if (old_weapon_id != currplayer->get_current_weapon_id()) {
2391 // TODO: make this triggered by GraphicalPlayer::set_current_weapon_id()
2392 if (Weapon* new_weapon = get_weapon(currplayer->get_current_weapon_id())) {
2393 new_weapon->select(*currplayer, *this);
2394 }
2395 }
2396
2397 if (m_radar->get_mode() == RADAR_ON) {
2398 m_radar->set_blip_alpha(player_id, currplayer->is_frozen() ? 0.5 : 1.0);
2399 }
2400 }
2401
2402 /*
2403 * Send a player update packet.
2404 */
send_my_player_update()2405 void GameController::send_my_player_update() {
2406 if (m_players.empty()) {
2407 return;
2408 }
2409
2410 PacketWriter player_update(PLAYER_UPDATE_PACKET);
2411 m_players[m_player_id].write_update_packet(player_update);
2412 m_network.send_packet(player_update);
2413 }
2414
2415 /*
2416 * Send a message packet.
2417 */
send_message(string message)2418 void GameController::send_message(string message) {
2419 if (m_players.empty()) {
2420 return;
2421 }
2422 strip_leading_trailing_spaces(message);
2423 if (message.empty()) {
2424 return;
2425 }
2426
2427 if (message.find_first_of(':') != string::npos) {
2428 // Message to individual player
2429 string recipient_name;
2430 string message_part;
2431 StringTokenizer(message, ':', 2) >> recipient_name >> message_part;
2432
2433 const GraphicalPlayer* player = get_player_by_name(recipient_name.c_str());
2434 strip_leading_trailing_spaces(message_part);
2435
2436 if (player && !message_part.empty()) {
2437 PacketWriter message_writer(MESSAGE_PACKET);
2438 message_writer << m_player_id << player->get_id() << message_part;
2439 m_network.send_packet(message_writer);
2440 return;
2441 }
2442 }
2443
2444 // Broadcast message to all players
2445 PacketWriter message_writer(MESSAGE_PACKET);
2446 message_writer << m_player_id << "" << message;
2447 m_network.send_packet(message_writer);
2448 }
2449
2450 /*
2451 * Send a team message packet.
2452 */
send_team_message(string message)2453 void GameController::send_team_message(string message) {
2454 if (m_players.empty()) {
2455 return;
2456 }
2457 strip_leading_trailing_spaces(message);
2458 if (message.empty()) {
2459 return;
2460 }
2461
2462 PacketWriter message_writer(MESSAGE_PACKET);
2463 message_writer << m_player_id << m_players[m_player_id].get_team() << message;
2464 m_network.send_packet(message_writer);
2465 }
2466
2467
2468 /*
2469 * Deal with receiving a leave packet.
2470 */
leave(PacketReader & reader)2471 void GameController::leave(PacketReader& reader) {
2472 uint32_t playerid;
2473 string leave_message;
2474 reader >> playerid >> leave_message;
2475
2476 if (GraphicalPlayer* player = get_player_by_id(playerid)) {
2477 // If it's for ourselves, we were kicked. Quit with a message.
2478 if (playerid == m_player_id) {
2479 string leavemsg = "You were kicked! Reason: ";
2480 leavemsg.append(leave_message);
2481 display_message(leavemsg);
2482 cout << "You were kicked! Reason: " << leave_message << endl;
2483 disconnect();
2484 m_game_state = SHOW_MENUS;
2485 return;
2486 }
2487
2488 string leavemsg = "";
2489 leavemsg.append(player->get_name());
2490 leavemsg.append(" has left the game.");
2491
2492 // Display a message based on their team.
2493 if (player->get_team() == 'A') {
2494 display_message(leavemsg, BLUE_COLOR, BLUE_SHADOW);
2495 } else {
2496 display_message(leavemsg, RED_COLOR, RED_SHADOW);
2497 }
2498
2499 m_text_manager->remove_string(player->get_name_sprite());
2500 m_window->unregister_graphic(player->get_sprite(), GameWindow::LAYER_GAME);
2501 m_radar->remove_blip(playerid);
2502 delete_individual_score(*player);
2503 delete player->get_sprite();
2504 m_players.erase(playerid);
2505 }
2506 }
2507
2508 /*
2509 * Called when a gun fired packet is received.
2510 */
weapon_discharged(PacketReader & reader)2511 void GameController::weapon_discharged(PacketReader& reader) {
2512 uint32_t player_id;
2513 string weapon_id;
2514
2515 reader >> player_id >> weapon_id;
2516
2517 if (player_id == m_player_id) {
2518 // Ignore discharge packets from ourself
2519 return;
2520 }
2521
2522 Weapon* weapon = get_weapon(weapon_id);
2523 Player* player = get_player_by_id(player_id);
2524
2525 if (!weapon || !player) {
2526 return;
2527 }
2528
2529 weapon->discharged(*player, *this, reader);
2530 }
2531
2532 /*
2533 * Called when a player shot packet is received.
2534 */
player_hit(PacketReader & reader)2535 void GameController::player_hit(PacketReader& reader) {
2536 uint32_t shooter_id;
2537 string weapon_id;
2538 uint32_t shot_player_id;
2539 bool has_effect;
2540
2541 reader >> shooter_id >> weapon_id >> shot_player_id >> has_effect;
2542
2543 GraphicalPlayer* shooter = get_player_by_id(shooter_id);
2544 GraphicalPlayer* shot_player = get_player_by_id(shot_player_id);
2545
2546 if (!shooter || !shot_player) {
2547 return;
2548 }
2549
2550 if (shot_player_id == m_player_id) {
2551 if (Weapon* weapon = get_weapon(weapon_id)) {
2552 weapon->hit(*shot_player, *shooter, has_effect, *this, reader);
2553 }
2554 }
2555 }
2556
player_died(PacketReader & reader)2557 void GameController::player_died(PacketReader& reader) {
2558 uint32_t dead_player_id;
2559 uint32_t killer_id;
2560 uint64_t time_to_unfreeze;
2561
2562 reader >> dead_player_id >> killer_id >> time_to_unfreeze;
2563
2564 GraphicalPlayer* dead_player = get_player_by_id(dead_player_id);
2565 GraphicalPlayer* killer = killer_id ? get_player_by_id(killer_id) : NULL;
2566
2567 if (!dead_player) {
2568 return;
2569 }
2570
2571 if (time_to_unfreeze != 0) {
2572 ostringstream message;
2573 bool bold = false;
2574 if (killer_id == m_player_id) {
2575 message << "You";
2576 bold = true;
2577 } else if (killer) {
2578 message << killer->get_name();
2579 } else {
2580 message << "A map hazard";
2581 }
2582
2583 if (killer && killer->get_team() == dead_player->get_team()) { // TODO (when we finish game modes): only say "betrayed" if team-play is in effect.
2584 message << " betrayed ";
2585 } else {
2586 message << " froze ";
2587 }
2588
2589 if (dead_player_id == m_player_id) {
2590 message << "you";
2591 bold = true;
2592 } else {
2593 message << dead_player->get_name();
2594 }
2595
2596 message << ".";
2597
2598 if (killer && killer->get_team() == 'A') {
2599 display_message(message.str(), BLUE_COLOR, BLUE_SHADOW, bold);
2600 } else if (killer && killer->get_team() == 'B') {
2601 display_message(message.str(), RED_COLOR, RED_SHADOW, bold);
2602 } else {
2603 display_message(message.str());
2604 }
2605 }
2606
2607 // If we were killed, freeze
2608 if (dead_player_id == m_player_id) {
2609 if (time_to_unfreeze) {
2610 freeze(time_to_unfreeze);
2611 } else {
2612 m_players[m_player_id].reset_energy();
2613 update_energy_bar();
2614 }
2615 }
2616 }
2617
2618 /*
2619 * Called when a message is receieved.
2620 */
message(PacketReader & reader)2621 void GameController::message(PacketReader& reader) {
2622 uint32_t sender_id;
2623 string recipient;
2624 string message_text;
2625
2626 reader >> sender_id >> recipient >> message_text;
2627
2628 if (sender_id == 0) {
2629 // sender_id = 0 ==> From the server
2630 string message("[Server]: ");
2631 message.append(message_text);
2632
2633 display_message(message);
2634
2635 } else if (const GraphicalPlayer* sender = get_player_by_id(sender_id)) {
2636 string message(sender->get_name());
2637 message.append(": ");
2638 if (is_valid_team(recipient[0])) {
2639 // Team chat
2640 message.append("[TEAM]: ");
2641 }
2642 message.append(message_text);
2643
2644 // Show the message in a color depending on the sender's team.
2645 if (sender->get_team() == 'A') {
2646 display_message(message, BLUE_COLOR, BLUE_SHADOW, true);
2647 } else {
2648 display_message(message, RED_COLOR, RED_SHADOW, true);
2649 }
2650 }
2651 }
2652
2653 /*
2654 * Called when a gate update packet is received.
2655 */
gate_update(PacketReader & reader)2656 void GameController::gate_update(PacketReader& reader) {
2657 uint32_t acting_player_id; // Who triggered the gate change?
2658 char team; // Which team's gate is being updated
2659 double progress; // How much has the gate opened? 0 == fully closed .. 1 == fully open
2660 int change_in_players; // {-1,0,1} = the change in the # of players engaging the gate
2661 size_t new_nbr_players; // How many players are now engaging the gate
2662 uint64_t sequence_no; // This should be greater than the last received gate update.
2663
2664 reader >> acting_player_id >> team >> progress >> change_in_players >> new_nbr_players >> sequence_no;
2665
2666 GraphicalPlayer* myplayer = get_player_by_id(m_player_id);
2667 GraphicalPlayer* actingplayer = get_player_by_id(acting_player_id);
2668 if (myplayer == NULL) {
2669 return;
2670 }
2671
2672 // If the sequence is earlier than something we've already received, ignore it.
2673 if (m_last_gate_packet_seq_no[team - 'A'] > sequence_no) {
2674 return;
2675 }
2676
2677 // Uncomment this code to return gate raise/lower messages.
2678 /*ostringstream message;
2679
2680 if (actingplayer != NULL) {
2681 message << actingplayer->get_name();
2682 if (change_in_players == 1) {
2683 message << " engaged the ";
2684 } else if (change_in_players == -1) {
2685 message << " disengaged from the ";
2686 }
2687 }
2688
2689 if (change_in_players != 0) {
2690 if (team == 'A') {
2691 message << "blue gate!";
2692 display_message(message.str(), RED_COLOR, RED_SHADOW);
2693 } else if (team == 'B') {
2694 message << "red gate!";
2695 display_message(message.str(), BLUE_COLOR, BLUE_SHADOW);
2696 }
2697 }*/
2698
2699 if (change_in_players == 1 && new_nbr_players == 1) {
2700 // The gate just started opening.
2701 // Play a sound and set the warning visible.
2702 string soundname = "";
2703 if (team == myplayer->get_team()) {
2704 soundname = "gatelower";
2705 } else {
2706 soundname = "positivegatelower";
2707 }
2708
2709 m_gate_lower_sounds[team - 'A'] = m_sound_controller->play_sound(soundname);
2710 if (team == m_players[m_player_id].get_team()) {
2711 m_gate_warning->set_invisible(false);
2712 m_gate_warning_time = get_ticks();
2713 }
2714 } else if (change_in_players == -1 && new_nbr_players == 0) {
2715 // The gate just started closing.
2716 // Hide the warning and stop the sounds.
2717 if (m_gate_lower_sounds[team - 'A'] != -1) {
2718 if (team == myplayer->get_team()) {
2719 m_gate_warning_time = 0;
2720 m_gate_warning->set_invisible(true);
2721 }
2722 m_sound_controller->halt_sound(m_gate_lower_sounds[team - 'A']);
2723 }
2724 }
2725
2726 m_map->set_gate_progress(team, progress);
2727
2728 double width = ((1-progress) * (GATE_STATUS_RECT_WIDTH-2)) + 2;
2729 if (team == 'A') {
2730 m_blue_gate_status_rect->set_image_width(width);
2731 } else if (team == 'B') {
2732 m_red_gate_status_rect->set_image_width(width);
2733 }
2734 }
2735
2736 /*
2737 * When a new round packet is received.
2738 */
new_round(PacketReader & reader)2739 void GameController::new_round(PacketReader& reader) {
2740 if (m_players.empty()) {
2741 // WELCOME packet not received yet
2742 // do NOT send an ACK for this NEW_ROUND packet, so that the server will resend it, hopefully after the WELCOME has come in.
2743 return;
2744 }
2745
2746 /*
2747 * Process the packet
2748 */
2749 string map_name;
2750 int map_revision;
2751 int map_width;
2752 int map_height;
2753 bool game_started;
2754 uint64_t time_until_start;
2755 reader >> map_name >> map_revision >> map_width >> map_height >> game_started >> time_until_start;
2756
2757 /*
2758 * Tell the player what's going on
2759 */
2760 ostringstream message;
2761 if (game_started) {
2762 if (m_game_state == GAME_OVER) {
2763 m_game_state = GAME_IN_PROGRESS;
2764 }
2765 if (time_until_start == numeric_limits<uint64_t>::max()) {
2766 message << "Game in progress on map " << map_name << ". You will spawn when the next game starts.";
2767 } else if (time_until_start/1000 > 0) {
2768 message << "Game in progress on map " << map_name << ". " << time_until_start / 1000 << " seconds until spawn.";
2769 }
2770 } else if (time_until_start/1000 > 0) {
2771 message << "Game starts in " << time_until_start/1000 << " seconds on map " << map_name << ".";
2772 }
2773
2774 if (!message.str().empty()) {
2775 display_message(message.str().c_str());
2776 }
2777
2778 /*
2779 * Load the map, if it has changed
2780 */
2781 if (m_map->is_loaded(map_name.c_str(), map_revision)) {
2782 // Map already loaded - just reset it
2783 m_map->reset();
2784 } else {
2785 // This map/revision is not already loaded - let's try to load it
2786 if (!load_map(map_name.c_str(), map_revision)) {
2787 init_map(map_width, map_height);
2788 // Couldn't load - request it from the server
2789 request_map();
2790 }
2791 }
2792
2793 /*
2794 * Reset the game state (TODO: add more stuff here)
2795 */
2796 m_round_end_time = 0;
2797 clear_weapons();
2798 m_last_damage_time = 0;
2799 m_last_recharge_time = 0;
2800 m_last_weapon_switch = 0;
2801 }
2802
2803 /*
2804 * Called when the game stop packet is received.
2805 */
round_over(PacketReader & reader)2806 void GameController::round_over(PacketReader& reader) {
2807 char winningteam;
2808 int teamascore;
2809 int teambscore;
2810
2811 reader >> winningteam >> teamascore >> teambscore;
2812
2813 m_game_state = GAME_OVER;
2814
2815 if (winningteam == '-') {
2816 display_message("DRAW");
2817 } else if (winningteam == m_players[m_player_id].get_team()) {
2818 display_message("VICTORY!");
2819 m_sound_controller->play_sound("victory");
2820 } else {
2821 display_message("DEFEAT!");
2822 m_sound_controller->play_sound("defeat");
2823 }
2824
2825 change_team_scores(teamascore, teambscore);
2826 toggle_score_overlay(true);
2827
2828 // Temporary score display
2829 ostringstream score_msg;
2830 score_msg << "Blue: " << teamascore << " / Red: " << teambscore;
2831 display_message(score_msg.str().c_str());
2832 // End temporary score display
2833
2834 // Reset the gates and set yourself invisible and frozen until respawn.
2835 m_map->reset_gates();
2836 m_blue_gate_status_rect->set_image_width(GATE_STATUS_RECT_WIDTH);
2837 m_red_gate_status_rect->set_image_width(GATE_STATUS_RECT_WIDTH);
2838 m_players[m_player_id].set_is_invisible(true);
2839 m_radar->set_blip_invisible(m_player_id,true);
2840 m_players[m_player_id].set_is_frozen(true);
2841 m_players[m_player_id].set_is_grabbing_obstacle(false);
2842 m_time_to_unfreeze = 0;
2843 m_total_time_frozen = 0;
2844 update_energy_bar(0);
2845 reset_weapons();
2846 }
2847
round_start(PacketReader & reader)2848 void GameController::round_start(PacketReader& reader) {
2849 // ACK TODO: Only accept this packet if we have gotten a related new_round packet
2850
2851 if (m_game_state == GAME_OVER) {
2852 m_game_state = GAME_IN_PROGRESS;
2853 }
2854
2855 uint64_t time_left_in_game;
2856 reader >> time_left_in_game;
2857
2858 if (time_left_in_game == numeric_limits<uint64_t>::max()) {
2859 m_round_end_time = 0;
2860 } else {
2861 m_round_end_time = get_ticks() + time_left_in_game;
2862 }
2863
2864 toggle_score_overlay(false);
2865 m_sound_controller->play_sound("begin");
2866 display_message("Game started!");
2867 }
2868
2869 /*
2870 * Called when a score update packet is received.
2871 */
score_update(PacketReader & reader)2872 void GameController::score_update(PacketReader& reader) {
2873 if (m_players.empty()) {
2874 // WELCOME packet not received yet
2875 // do NOT send an ACK for this SCORE_UPDATE packet, so that the server will resend it, hopefully after the WELCOME has come in.
2876 return;
2877 }
2878
2879 std::string subject;
2880 int score;
2881 reader >> subject >> score;
2882
2883 if (is_valid_team(subject[0])) {
2884 char team = subject[0];
2885 if (team == 'A') {
2886 change_team_scores(score, -1);
2887 } else {
2888 change_team_scores(-1, score);
2889 }
2890 toggle_score_overlay(!m_overlay_background->is_invisible());
2891 } else if (GraphicalPlayer* player = get_player_by_id(atoi(subject.c_str()))) {
2892 player->set_score(score);
2893 }
2894 }
2895
2896 /*
2897 * Called when an animation packet is received.
2898 */
animation_packet(PacketReader & reader)2899 void GameController::animation_packet(PacketReader& reader) {
2900 uint32_t player_id;
2901 string spritelist;
2902 string field;
2903 int value;
2904
2905 reader >> player_id >> spritelist >> field >> value;
2906
2907 StringTokenizer tokenizer(spritelist, '/');
2908
2909 if (m_players.count(player_id) == 0) {
2910 return;
2911 }
2912
2913 // Get the sprite to modify by going through the path separated by slashes.
2914 Graphic* the_sprite = NULL;
2915 while (tokenizer.has_more()) {
2916 string spritename = tokenizer.get_next();
2917
2918 if (spritename == "all") {
2919 if (the_sprite == NULL) {
2920 the_sprite = m_players[player_id].get_sprite();
2921 }
2922 } else {
2923 if (the_sprite == NULL) {
2924 the_sprite = m_players[player_id].get_sprite()->get_graphic(spritename);
2925 } else {
2926 the_sprite = the_sprite->get_graphic(spritename);
2927 }
2928 }
2929
2930 if (the_sprite == NULL) {
2931 return;
2932 }
2933 }
2934
2935 if (field == "rotation") {
2936 the_sprite->set_rotation(value);
2937 } else if (field == "scale_x") {
2938 the_sprite->set_scale_x(value);
2939 } else if (field == "scale_y") {
2940 the_sprite->set_scale_y(value);
2941 } else if (field == "x") {
2942 the_sprite->set_x(value);
2943 } else if (field == "y") {
2944 the_sprite->set_y(value);
2945 } else if (field == "center_x") {
2946 the_sprite->set_center_x(value);
2947 } else if (field == "center_y") {
2948 the_sprite->set_center_y(value);
2949 } else if (field == "invisible") {
2950 the_sprite->set_invisible(value);
2951 m_players[player_id].get_sprite()->set_invisible(m_players[player_id].get_sprite()->is_invisible());
2952 }
2953 }
2954
2955 /*
2956 * Called when a request denied packet is received.
2957 */
request_denied(PacketReader & reader)2958 void GameController::request_denied(PacketReader& reader) {
2959 int packet_type;
2960 string reason;
2961 reader >> packet_type >> reason;
2962
2963 if (packet_type == JOIN_PACKET) {
2964 string message = "Join denied! Reason: ";
2965 message.append(reason);
2966 display_message(message);
2967 cout << "Join denied! Reason: " << reason << endl;
2968 disconnect();
2969 m_game_state = SHOW_MENUS;
2970 }
2971 }
2972
2973 /*
2974 * Called when a player name change packet is received.
2975 */
name_change(PacketReader & reader)2976 void GameController::name_change(PacketReader& reader) {
2977 uint32_t player_id;
2978 string new_name;
2979 reader >> player_id >> new_name;
2980
2981 if (GraphicalPlayer* player = get_player_by_id(player_id)) {
2982 ostringstream msg;
2983 msg << player->get_name() << " is now known as " << new_name;
2984
2985 delete_individual_score(m_players[player_id]);
2986
2987 player->set_name(new_name.c_str());
2988 if (player_id == m_player_id) {
2989 m_configuration->set_string_value("name", new_name);
2990 m_name = new_name;
2991 }
2992
2993 if (player->get_team() == 'A') {
2994 display_message(msg.str().c_str(), BLUE_COLOR, BLUE_SHADOW);
2995 } else {
2996 display_message(msg.str().c_str(), RED_COLOR, RED_SHADOW);
2997 }
2998
2999 // Re-create the name sprite.
3000 recreate_name(player);
3001 update_individual_scores();
3002 }
3003 }
3004
3005 /*
3006 * Called when a team change packet is received.
3007 */
team_change(PacketReader & reader)3008 void GameController::team_change(PacketReader& reader) {
3009 uint32_t playerid;
3010 char team;
3011 reader >> playerid >> team;
3012
3013 if (GraphicalPlayer* player = get_player_by_id(playerid)) {
3014 delete_individual_score(m_players[playerid]);
3015
3016 player->set_team(team);
3017
3018 ostringstream msg;
3019 msg << player->get_name() << " has switched teams";
3020
3021 // Remove the name and sprite.
3022 m_text_manager->remove_string(player->get_name_sprite());
3023 m_window->unregister_graphic(player->get_sprite(), GameWindow::LAYER_GAME);
3024 m_radar->remove_blip(playerid);
3025 delete player->get_sprite();
3026
3027 // Generate new graphics for it.
3028 if (team == 'A') {
3029 player->set_sprite(new GraphicGroup(blue_player));
3030 display_message(msg.str().c_str(), BLUE_COLOR, BLUE_SHADOW);
3031 m_text_manager->set_shadow_color(BLUE_SHADOW);
3032 } else {
3033 player->set_sprite(new GraphicGroup(red_player));
3034 display_message(msg.str().c_str(), RED_COLOR, RED_SHADOW);
3035 m_text_manager->set_shadow_color(RED_SHADOW);
3036 }
3037 m_radar->add_blip(playerid,team,0,0);
3038
3039 if (Weapon* new_weapon = get_weapon(player->get_current_weapon_id())) {
3040 new_weapon->select(*player, *this);
3041 }
3042
3043 player->get_sprite()->set_invisible(false);
3044 m_window->register_graphic(player->get_sprite(), GameWindow::LAYER_GAME);
3045 player->set_name_sprite(m_text_manager->place_string(player->get_name(), player->get_x(), player->get_y()-(player->get_radius()+30), TextManager::CENTER, GameWindow::LAYER_GAME));
3046 update_individual_scores();
3047 }
3048 }
3049
3050 /*
3051 * Send an animation packet.
3052 */
send_animation_packet(string sprite,string field,int value)3053 void GameController::send_animation_packet(string sprite, string field, int value) {
3054 PacketWriter animation_packet(PLAYER_ANIMATION_PACKET);
3055 animation_packet << m_player_id << sprite << field << value;
3056
3057 m_network.send_packet(animation_packet);
3058 }
3059
3060 /*
3061 * Send a gate hold packet.
3062 */
send_gate_hold(bool holding)3063 void GameController::send_gate_hold(bool holding) {
3064 PacketWriter gate_hold(GATE_UPDATE_PACKET);
3065 if (holding) {
3066 gate_hold << m_player_id << get_other_team(m_players[m_player_id].get_team()) << 1;
3067 } else {
3068 gate_hold << m_player_id << get_other_team(m_players[m_player_id].get_team()) << 0;
3069 }
3070 m_network.send_reliable_packet(gate_hold);
3071 }
3072
set_gate_hold(bool holding_gate)3073 void GameController::set_gate_hold(bool holding_gate) {
3074 if (m_holding_gate != holding_gate) {
3075 m_holding_gate = holding_gate;
3076 send_gate_hold(m_holding_gate);
3077 }
3078
3079 }
3080
3081 /*
3082 * Send a name change packet.
3083 */
send_name_change_packet(const char * new_name)3084 void GameController::send_name_change_packet(const char* new_name) {
3085 PacketWriter packet(NAME_CHANGE_PACKET);
3086 packet << m_player_id << new_name;
3087 m_network.send_reliable_packet(packet);
3088 }
3089
3090 /*
3091 * Recreate the player name sprite.
3092 */
recreate_name(GraphicalPlayer * player)3093 void GameController::recreate_name(GraphicalPlayer* player) {
3094 // Re-create the name sprite.
3095 if (player->get_team() == 'A') {
3096 m_text_manager->set_active_color(BLUE_COLOR);
3097 m_text_manager->set_shadow_color(BLUE_SHADOW);
3098 } else {
3099 m_text_manager->set_active_color(RED_COLOR);
3100 m_text_manager->set_shadow_color(RED_SHADOW);
3101 }
3102 m_text_manager->remove_string(player->get_name_sprite());
3103 m_text_manager->set_active_font(m_font);
3104 string name_string = player->get_name();
3105 if (player->is_frozen()) {
3106 m_font->set_font_style(false, true);
3107 name_string = "[" + name_string + "]";
3108 }
3109 player->set_name_sprite(m_text_manager->place_string(name_string, player->get_x(), player->get_y()-(player->get_radius()+30), TextManager::CENTER, GameWindow::LAYER_GAME));
3110 m_font->set_font_style(false, false);
3111 m_text_manager->set_shadow_color(TEXT_SHADOW);
3112 }
3113
3114 /*
3115 * Send a team change packet.
3116 */
send_team_change_packet(char new_team)3117 void GameController::send_team_change_packet(char new_team) {
3118 PacketWriter packet(TEAM_CHANGE_PACKET);
3119 packet << m_player_id << new_team;
3120 m_network.send_reliable_packet(packet);
3121 }
3122
3123 /*
3124 * Display a message on the screen.
3125 */
display_message(string message,Color color,Color shadow,bool bold)3126 void GameController::display_message(string message, Color color, Color shadow, bool bold) {
3127 m_text_manager->set_active_color(color);
3128 m_text_manager->set_shadow_color(shadow);
3129 if (bold) {
3130 m_text_manager->set_active_font(m_bold_font);
3131 } else {
3132 m_text_manager->set_active_font(m_font);
3133 }
3134 int y = 20 + (m_font->ascent() + m_font->descent() + 5) * m_messages.size();
3135 Text* message_sprite = m_text_manager->place_string(message, 20, y, TextManager::LEFT, GameWindow::LAYER_SUPER);
3136 if (!message_sprite) {
3137 return;
3138 }
3139 uint64_t currframe = get_ticks();
3140 Message new_message;
3141 new_message.message = message_sprite;
3142 new_message.timeout = currframe + MESSAGE_DISPLAY_TIME;
3143 if (m_chat_window_transition_x != NULL) {
3144 new_message.transition = new Transition(new_message.message, &Graphic::set_y, new LinearCurve(y, y), currframe, 1);
3145 m_transition_manager.add_transition(new_message.transition, false, TransitionManager::KEEP);
3146 m_chat_window_transition_y->change_curve(currframe, new LinearCurve(0, y + message_sprite->get_image_height() + 6 - m_chat_window_back->get_y()), 1);
3147 double max_w = m_chat_window_transition_x->get_curve()->get_end();
3148 if (max_w < message_sprite->get_image_width() + 6) {
3149 m_chat_window_transition_x->change_curve(currframe, new LinearCurve(0, message_sprite->get_image_width() + 6), 1);
3150 }
3151 } else {
3152 new_message.transition = NULL;
3153 m_chat_window_back->set_row_height(0, y + message_sprite->get_image_height() + 6 - m_chat_window_back->get_y());
3154 double max_w = m_chat_window_back->get_image_width();
3155 if (max_w < message_sprite->get_image_width() + 6) {
3156 m_chat_window_back->set_width(message_sprite->get_image_width() + 6);
3157 }
3158 }
3159 m_messages.push_back(new_message);
3160 m_chat_log->add_message(message, color, shadow);
3161 if (m_configuration->get_bool_value("text_background")) {
3162 m_chat_window_back->set_invisible(false);
3163 }
3164 }
3165
3166 /*
3167 * Get a player by their ID.
3168 */
get_player_by_id(uint32_t player_id)3169 GraphicalPlayer* GameController::get_player_by_id(uint32_t player_id) {
3170 map<uint32_t, GraphicalPlayer>::iterator it(m_players.find(player_id));
3171 return it == m_players.end() ? NULL : &it->second;
3172 }
get_player_by_id(uint32_t player_id) const3173 const GraphicalPlayer* GameController::get_player_by_id(uint32_t player_id) const {
3174 map<uint32_t, GraphicalPlayer>::const_iterator it(m_players.find(player_id));
3175 return it == m_players.end() ? NULL : &it->second;
3176 }
3177
3178 /*
3179 * Get a player by name.
3180 */
get_player_by_name(const char * name)3181 GraphicalPlayer* GameController::get_player_by_name(const char* name) {
3182 for (map<uint32_t, GraphicalPlayer>::iterator it(m_players.begin()); it != m_players.end(); ++it) {
3183 if (it->second.compare_name(name)) {
3184 return &it->second;
3185 }
3186 }
3187 return NULL;
3188 }
3189
3190 /*
3191 * Send an ack packet.
3192 */
server_info(const IPAddress & server_address,PacketReader & info_packet)3193 void GameController::server_info(const IPAddress& server_address, PacketReader& info_packet) {
3194 uint32_t request_packet_id;
3195 uint64_t scan_start_time;
3196 info_packet >> request_packet_id >> scan_start_time;
3197
3198 if (request_packet_id != m_current_scan_id && request_packet_id != m_current_ping_id) {
3199 // From an old scan - ignore it
3200 return;
3201 }
3202
3203 if (server_address == m_metaserver_address) {
3204 // A response from the meta server
3205 // Now send an info packet to the server specified in this packet, to measure ping time and get the most up-to-date information
3206 IPAddress server_address;
3207 info_packet >> server_address;
3208 scan_server(server_address);
3209 } else {
3210 // A response from an actual server
3211 // Get the info on the server, and present it to the user
3212 int server_protocol_version;
3213 string current_map_name;
3214 int team_count[2];
3215 int max_players;
3216 uint64_t uptime;
3217 uint64_t time_left_in_game;
3218 string server_name;
3219 string server_location;
3220 Version server_compat_version;
3221 info_packet >> server_protocol_version >> server_compat_version >> current_map_name >> team_count[0] >> team_count[1] >> max_players >> uptime >> time_left_in_game >> server_name >> server_location;
3222
3223 m_ping = get_ticks() - scan_start_time;
3224 if (request_packet_id == m_current_ping_id) {
3225 //cerr << "Ping: " << m_ping << " Framerate: " << m_framerate << endl;
3226 return;
3227 }
3228
3229 //cerr << "Received INFO packet from " << format_ip_address(server_address, true) << ": Protocol=" << server_protocol_version << "; Map=" << current_map_name << "; Blue players=" << team_count[0] << "; Red players=" << team_count[1] << "; Ping time=" << get_ticks() - scan_start_time << "ms" << "; Uptime=" << uptime << endl;
3230
3231 if (server_protocol_version != m_protocol_number || server_compat_version != COMPAT_VERSION) {
3232 //cerr << "Server with different protocol found: " << format_ip_address(server_address, true) << ": Protocol=" << server_protocol_version << "; Map=" << current_map_name << "; Blue players=" << team_count[0] << "; Red players=" << team_count[1] << "; Ping time=" << get_ticks() - scan_start_time << "ms" << endl;
3233 return;
3234 }
3235
3236 if (m_server_browser->is_invisible()) {
3237 return;
3238 }
3239
3240 if (!m_server_browser->contains_ip(server_address)) {
3241 m_server_browser->add_entry(server_address, current_map_name, team_count, max_players, uptime, m_ping, server_name, server_location);
3242 }
3243 }
3244 }
3245
3246
hole_punch_packet(const IPAddress & server_address,PacketReader & packet)3247 void GameController::hole_punch_packet(const IPAddress& server_address, PacketReader& packet) {
3248 uint32_t scan_id;
3249 packet >> scan_id;
3250
3251 if (scan_id != m_current_scan_id || m_server_browser->contains_ip(server_address)) {
3252 return;
3253 }
3254
3255 scan_server(server_address);
3256 }
3257
3258
upgrade_available(const IPAddress & server_address,PacketReader & packet)3259 void GameController::upgrade_available(const IPAddress& server_address, PacketReader& packet) {
3260 string latest_version;
3261 packet >> latest_version;
3262 ostringstream message;
3263 message << "Version: " << latest_version;
3264
3265 if (m_version_nag1 != NULL) {
3266 m_main_menu.remove_item(m_version_nag1);
3267 delete m_version_nag1;
3268 m_version_nag1 = NULL;
3269 }
3270 if (m_version_nag2 != NULL) {
3271 m_main_menu.remove_item(m_version_nag2);
3272 delete m_version_nag2;
3273 m_version_nag2 = NULL;
3274 }
3275 m_text_manager->set_active_font(m_menu_font);
3276 m_text_manager->set_active_color(TEXT_COLOR);
3277 Text *nag1 = m_text_manager->render_string("There is an upgrade available!", m_window->get_width() - 30, 200, TextManager::RIGHT);
3278 Text *nag2 = m_text_manager->render_string(message.str(), m_window->get_width() - 30, 240, TextManager::RIGHT);
3279 m_version_nag1 = new TextMenuItem(nag1, "", MenuItem::STATIC);
3280 m_version_nag2 = new TextMenuItem(nag2, "", MenuItem::STATIC);
3281 m_main_menu.add_item(m_version_nag1);
3282 m_main_menu.add_item(m_version_nag2);
3283 }
3284
scan_all()3285 void GameController::scan_all() {
3286 PacketWriter info_request_packet(INFO_PACKET);
3287 m_current_scan_id = get_next_scan_id();
3288 info_request_packet << m_protocol_number << m_current_scan_id << get_ticks() << m_client_version;
3289 m_network.broadcast_packet(DEFAULT_PORTNO, info_request_packet);
3290 IPAddress localhostip;
3291 if (resolve_hostname(localhostip, "localhost", DEFAULT_PORTNO)) {
3292 m_network.send_packet_to(localhostip, info_request_packet);
3293 }
3294 if (!m_offline_mode) {
3295 m_network.send_packet_to(m_metaserver_address, info_request_packet);
3296 }
3297 }
3298
check_for_upgrade()3299 void GameController::check_for_upgrade() {
3300 PacketWriter packet(UPGRADE_AVAILABLE_PACKET);
3301 packet << m_client_version;
3302 m_network.send_packet_to(m_metaserver_address, packet);
3303 }
3304
scan_local_network()3305 void GameController::scan_local_network() {
3306 PacketWriter info_request_packet(INFO_PACKET);
3307 m_current_scan_id = get_next_scan_id();
3308 info_request_packet << m_protocol_number << m_current_scan_id << get_ticks();
3309 m_network.broadcast_packet(DEFAULT_PORTNO, info_request_packet);
3310 }
3311
contact_metaserver()3312 void GameController::contact_metaserver() {
3313 PacketWriter info_request_packet(INFO_PACKET);
3314 m_current_scan_id = get_next_scan_id();
3315 info_request_packet << m_protocol_number << m_current_scan_id << get_ticks() << m_client_version;
3316 m_network.send_packet_to(m_metaserver_address, info_request_packet);
3317 }
3318
ping_server(const IPAddress & server_address)3319 void GameController::ping_server(const IPAddress& server_address) {
3320 PacketWriter info_request_packet(INFO_PACKET);
3321 m_current_scan_id = get_next_scan_id();
3322 info_request_packet << m_protocol_number << m_current_ping_id << get_ticks();
3323 m_network.send_packet_to(server_address, info_request_packet);
3324 }
3325
scan_server(const IPAddress & server_address)3326 void GameController::scan_server(const IPAddress& server_address) {
3327 PacketWriter info_request_packet(INFO_PACKET);
3328 info_request_packet << m_protocol_number << m_current_scan_id << get_ticks();
3329 m_network.send_packet_to(server_address, info_request_packet);
3330 }
3331
set_player_name(string name)3332 void GameController::set_player_name(string name) {
3333 m_name = name;
3334 m_configuration->set_string_value("name", name);
3335 if (m_network.is_connected()) {
3336 send_name_change_packet(m_name.c_str());
3337 }
3338 m_text_manager->set_active_font(m_menu_font);
3339 m_name_input->set_default_value(name);
3340 m_name_input->reset();
3341 }
3342
clear_players()3343 void GameController::clear_players() {
3344 if (!m_players.empty()) {
3345 map<uint32_t, GraphicalPlayer>::iterator it;
3346 for ( it=m_players.begin() ; it != m_players.end(); it++ ) {
3347 const GraphicalPlayer& currplayer = (*it).second;
3348 m_text_manager->remove_string(m_players[currplayer.get_id()].get_name_sprite());
3349 m_window->unregister_graphic(m_players[currplayer.get_id()].get_sprite(), GameWindow::LAYER_GAME);
3350 m_radar->remove_blip(currplayer.get_id());
3351 delete_individual_score(m_players[currplayer.get_id()]);
3352 delete m_players[currplayer.get_id()].get_sprite();
3353 }
3354 }
3355 m_players.clear();
3356 }
3357
sound_finished(int channel)3358 void GameController::sound_finished(int channel) {
3359 for (unsigned int i = 0; i < (sizeof(m_gate_lower_sounds)/sizeof(m_gate_lower_sounds[0])); i++) {
3360 if (m_gate_lower_sounds[i] == channel) {
3361 m_gate_lower_sounds[i] = -1;
3362 }
3363 }
3364 }
3365
wants_restart()3366 bool GameController::wants_restart() {
3367 return m_restart;
3368 }
3369
get_server_address()3370 string GameController::get_server_address() {
3371 if (m_network.is_connected() && m_join_sent_time == 0) {
3372 return format_ip_address(m_network.get_server_address(), true);
3373 } else {
3374 return "";
3375 }
3376 }
3377
format_time_from_millis(uint64_t milliseconds)3378 string GameController::format_time_from_millis(uint64_t milliseconds) {
3379 unsigned int uptimesecs = (milliseconds/1000);
3380 unsigned int uptimedays = uptimesecs/86400;
3381 uptimesecs -= uptimedays * 86400;
3382 unsigned int uptimehours = uptimesecs/3600;
3383 uptimesecs -= uptimehours * 3600;
3384 unsigned int uptimeminutes = uptimesecs/60;
3385 uptimesecs -= uptimeminutes * 60;
3386 ostringstream uptimestr;
3387 if (uptimedays != 0) {
3388 uptimestr << uptimedays << "d ";
3389 }
3390 uptimestr << uptimehours << ":";
3391 if (uptimeminutes < 10) {
3392 uptimestr << "0";
3393 }
3394 uptimestr << uptimeminutes << ":";
3395 if (uptimesecs < 10) {
3396 uptimestr << "0";
3397 }
3398 uptimestr << uptimesecs;
3399 return uptimestr.str();
3400 }
3401
map_info_packet(PacketReader & reader)3402 void GameController::map_info_packet(PacketReader& reader) {
3403 if (m_map_receiver.get() && m_map_receiver->map_info(reader)) {
3404 init_map(m_map->get_width(), m_map->get_height());
3405 if (m_map_receiver->is_done()) {
3406 m_map_receiver.reset();
3407 }
3408 }
3409 }
3410
map_object_packet(PacketReader & reader)3411 void GameController::map_object_packet(PacketReader& reader) {
3412 if (m_map_receiver.get() && m_map_receiver->map_object(reader)) {
3413 if (m_map_receiver->is_done()) {
3414 m_map_receiver.reset();
3415 }
3416 }
3417 }
3418
request_map()3419 void GameController::request_map() {
3420 m_map->clear();
3421 m_map_receiver.reset(new MapReceiver(*m_map));
3422
3423 PacketWriter packet(MAP_INFO_PACKET);
3424 packet << m_player_id << m_map_receiver->transmission_id();
3425 m_network.send_reliable_packet(packet);
3426 }
3427
load_map(const char * map_name,int map_revision)3428 bool GameController::load_map(const char* map_name, int map_revision) {
3429 if (strpbrk(map_name, "/\\") != NULL) {
3430 return false;
3431 }
3432
3433 string map_filename(map_name);
3434 map_filename += ".map";
3435
3436 if (!m_map->load_file(m_path_manager.data_path(map_filename.c_str(), "maps"))) {
3437 return false;
3438 }
3439
3440 if (m_map->get_revision() != map_revision) {
3441 return false;
3442 }
3443
3444 init_map(m_map->get_width(), m_map->get_height());
3445 return true;
3446 }
3447
init_map(int map_width,int map_height)3448 void GameController::init_map(int map_width, int map_height) {
3449 m_map_width = map_width;
3450 m_map_height = map_height;
3451 m_map_polygon.make_rectangle(m_map_width, m_map_height);
3452 }
3453
display_legalese()3454 void GameController::display_legalese() {
3455 const char* legalese[] = {
3456 "This is Leges Motus, a networked, 2D shooter set in zero gravity.",
3457 " ",
3458 "Copyright 2009-2010 Andrew Ayer, Nathan Partlan, Jeffrey Pfau",
3459 " ",
3460 "Leges Motus is free and open source software. You may redistribute it and/or",
3461 "modify it under the terms of version 2, or (at your option) version 3, of the",
3462 "GNU General Public License (GPL), as published by the Free Software Foundation.",
3463 " ",
3464 "Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY",
3465 "WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A",
3466 "PARTICULAR PURPOSE. See the full text of the GNU General Public License for",
3467 "further detail.",
3468 " ",
3469 "For a full copy of the GNU General Public License, please see the COPYING file",
3470 "in the root of the source code tree. You may also retrieve a copy from",
3471 "<http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the",
3472 "Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA",
3473 "02111-1307 USA",
3474 " "
3475 };
3476
3477 for (size_t i = 0; i < sizeof(legalese) / sizeof(legalese[0]); ++i) {
3478 display_message(legalese[i]);
3479 }
3480 }
3481
game_param_packet(PacketReader & packet)3482 void GameController::game_param_packet(PacketReader& packet) {
3483 if (!m_params.process_param_packet(packet)) {
3484 // Parameter not recognized - reject the packet
3485 return;
3486 }
3487
3488 set_radar_mode(m_params.radar_mode);
3489 m_radar->set_scale(m_params.radar_scale);
3490 }
3491
set_radar_mode(RadarMode mode)3492 void GameController::set_radar_mode(RadarMode mode) {
3493 if (mode == m_radar->get_mode()) {
3494 return;
3495 }
3496
3497 m_radar->set_mode(mode);
3498
3499 if (mode == RADAR_AURAL) {
3500 // Hide all the radar blips, because we're entering aural mode
3501 for (std::map<uint32_t, GraphicalPlayer>::const_iterator it(m_players.begin()); it != m_players.end(); ++it) {
3502 if (it->second.get_id() != m_player_id) {
3503 m_radar->set_blip_alpha(it->second.get_id(), 0.0);
3504 }
3505 }
3506 }
3507 }
3508
next_weapon()3509 void GameController::next_weapon() {
3510 // Change to the next weapon in the list.
3511 map<string, Weapon*>::iterator it(m_weapons.begin());
3512 for (int i = 0; it != m_weapons.end(); it++, i++) {
3513 if (it->second == m_current_weapon) {
3514 change_weapon(i+1);
3515 break;
3516 }
3517 }
3518 }
3519
previous_weapon()3520 void GameController::previous_weapon() {
3521 // Change to the previous weapon in the list.
3522 map<string, Weapon*>::iterator it(m_weapons.begin());
3523 for (int i = 0; it != m_weapons.end(); it++, i++) {
3524 if (it->second == m_current_weapon) {
3525 if (i == 0) {
3526 i = m_weapons.size();
3527 }
3528 i--;
3529 change_weapon(i);
3530 break;
3531 }
3532 }
3533 }
3534
change_weapon(const char * weapon_id)3535 void GameController::change_weapon(const char* weapon_id) {
3536 if (Weapon* weapon = get_weapon(weapon_id)) {
3537 change_weapon(weapon);
3538 }
3539 }
3540
change_weapon(unsigned int n)3541 void GameController::change_weapon(unsigned int n) {
3542 if (n >= m_weapons.size()) {
3543 return;
3544 }
3545
3546 map<string, Weapon*>::iterator it(m_weapons.begin());
3547 advance(it, n);
3548 change_weapon(it->second);
3549 }
3550
change_weapon(Weapon * weapon)3551 void GameController::change_weapon(Weapon* weapon) {
3552 if (m_current_weapon != weapon) {
3553 m_current_weapon = weapon;
3554 m_last_weapon_switch = get_ticks();
3555 update_curr_weapon_image();
3556 }
3557 }
3558
get_crosshairs_angle() const3559 double GameController::get_crosshairs_angle() const {
3560 if (m_players.empty()) {
3561 return 0;
3562 }
3563
3564 Point crosshairs_location(m_crosshairs->get_x() + m_offset_x, m_crosshairs->get_y() + m_offset_y);
3565 return Vector(crosshairs_location - get_player_by_id(m_player_id)->get_position()).get_angle();
3566 }
3567
freeze(uint64_t time_to_unfreeze)3568 void GameController::freeze(uint64_t time_to_unfreeze) {
3569 if (GraphicalPlayer* player = get_player_by_id(m_player_id)) {
3570 if (!player->is_frozen() && time_to_unfreeze != 0) {
3571 m_frozen_status_rect->set_y(m_screen_height/2 + player->get_radius() + 15);
3572 m_frozen_status_rect_back->set_y(m_frozen_status_rect->get_y());
3573 m_frozen_status_text->set_y(m_frozen_status_rect->get_y());
3574 m_sound_controller->play_sound("freeze");
3575 player->set_is_frozen(true);
3576 if (m_radar->get_mode() == RADAR_ON) {
3577 m_radar->set_blip_alpha(m_player_id, 0.5);
3578 }
3579 recreate_name(player);
3580 m_time_to_unfreeze = get_ticks() + time_to_unfreeze;
3581 m_total_time_frozen = time_to_unfreeze;
3582 }
3583 }
3584 }
3585
unfreeze()3586 void GameController::unfreeze() {
3587 if (GraphicalPlayer* player = get_player_by_id(m_player_id)) {
3588 m_sound_controller->play_sound("unfreeze");
3589 if (player->is_dead()) {
3590 // If we were dead (i.e. at 0 energy), reset our energy
3591 // Otherwise, we continue with the energy we had before getting killed
3592 player->reset_energy();
3593 update_energy_bar();
3594 m_last_damage_time = 0;
3595 }
3596 player->set_is_frozen(false);
3597 m_last_recharge_time = 0;
3598 recreate_name(player);
3599 if (m_radar->get_mode() == RADAR_ON) {
3600 m_radar->set_blip_alpha(m_player_id, 1.0);
3601 }
3602 m_time_to_unfreeze = 0;
3603 m_total_time_frozen = 0;
3604 }
3605 }
3606
reduce_freeze_time(uint64_t milliseconds)3607 void GameController::reduce_freeze_time(uint64_t milliseconds) {
3608 if (GraphicalPlayer* player = get_player_by_id(m_player_id)) {
3609 if (player->is_frozen()) {
3610 if (m_time_to_unfreeze <= milliseconds || m_total_time_frozen <= milliseconds) {
3611 unfreeze();
3612 }
3613
3614 m_time_to_unfreeze -= milliseconds;
3615
3616 if (get_ticks() > m_time_to_unfreeze) {
3617 unfreeze();
3618 }
3619
3620 m_total_time_frozen -= milliseconds;
3621 }
3622 }
3623 }
3624
play_sound(const char * sound_name)3625 void GameController::play_sound(const char* sound_name) {
3626 m_sound_controller->play_sound(sound_name);
3627 }
3628
get_weapon(const string & name)3629 Weapon* GameController::get_weapon(const string& name) {
3630 map<string, Weapon*>::iterator it(m_weapons.find(name));
3631 return it == m_weapons.end() ? NULL : it->second;
3632 }
3633
update_curr_weapon_image()3634 void GameController::update_curr_weapon_image() {
3635 if (m_current_weapon == NULL) {
3636 if (m_curr_weapon_image != NULL) {
3637 m_curr_weapon_image->set_invisible(true);
3638 }
3639 return;
3640 }
3641 m_window->unregister_graphic(m_curr_weapon_image, GameWindow::LAYER_HUD);
3642 delete m_curr_weapon_image;
3643 m_curr_weapon_image = m_graphics_cache.new_graphic<Sprite>(m_path_manager.data_path(m_current_weapon->hud_graphic(), "sprites"));
3644 m_curr_weapon_image->set_x(m_cooldown_bar->get_x());
3645 m_curr_weapon_image->set_y(m_cooldown_bar->get_y() - m_curr_weapon_image->get_image_height()/2 - 5);
3646 m_curr_weapon_image->set_invisible(false);
3647 m_window->register_graphic(m_curr_weapon_image, GameWindow::LAYER_HUD);
3648
3649 if (Player* curr_player = get_player_by_id(m_player_id)) {
3650 curr_player->set_current_weapon_id(m_current_weapon->get_id());
3651 m_current_weapon->select(*curr_player, *this); // TODO: make this triggered by GraphicalPlayer::set_current_weapon_id()
3652 }
3653 }
3654
reset_weapons()3655 void GameController::reset_weapons() {
3656 for (map<string, Weapon*>::iterator it(m_weapons.begin()); it != m_weapons.end(); ++it) {
3657 it->second->reset();
3658 }
3659 }
3660
clear_weapons()3661 void GameController::clear_weapons() {
3662 for (map<string, Weapon*>::iterator it(m_weapons.begin()); it != m_weapons.end(); ++it) {
3663 delete it->second;
3664 }
3665 m_weapons.clear();
3666 m_current_weapon = NULL;
3667 }
3668
activate_radar_blip(const Player & player)3669 void GameController::activate_radar_blip(const Player& player) {
3670 m_radar->activate_blip(player.get_id(), get_ticks(), m_params.radar_blip_duration);
3671 }
3672
damage(int amount,const Player * aggressor)3673 bool GameController::damage (int amount, const Player* aggressor) {
3674 if (amount == 0 || m_players.empty() || m_players[m_player_id].is_frozen() || m_players[m_player_id].is_dead()) {
3675 // No effect on already frozen players
3676 return false;
3677 }
3678
3679 m_players[m_player_id].change_energy(-amount);
3680 m_last_damage_time = get_ticks();
3681 update_energy_bar();
3682 if (m_players[m_player_id].is_dead()) {
3683 // Inform the server that we died
3684 PacketWriter died_packet(PLAYER_DIED_PACKET);
3685 died_packet << m_player_id << (aggressor ? aggressor->get_id() : 0);
3686 m_network.send_reliable_packet(died_packet);
3687
3688 return true;
3689 }
3690 return false;
3691 }
3692
HitObject(double arg_distance,Point arg_point,BaseMapObject * arg_map_object)3693 GameController::HitObject::HitObject (double arg_distance, Point arg_point, BaseMapObject* arg_map_object) {
3694 distance = arg_distance;
3695 point = arg_point;
3696 map_object = arg_map_object;
3697 player = 0;
3698 }
3699
HitObject(double arg_distance,Point arg_point,Player * arg_player)3700 GameController::HitObject::HitObject (double arg_distance, Point arg_point, Player* arg_player) {
3701 distance = arg_distance;
3702 point = arg_point;
3703 map_object = 0;
3704 player = arg_player;
3705 }
3706
3707 // Starting at a given pivot point, find all players that are in a given region, populate
3708 // the given set with the players that are hit. Returns a list of hit points.
3709 // TODO: add and make use of bool penetrate_players, bool penetrate_obstacles
shoot_in_region(const Shape & shape,const Point & pivot,std::list<Player * > & hit_players)3710 list<Point> GameController::shoot_in_region(const Shape& shape, const Point& pivot, std::list<Player*>& hit_players) {
3711 list<Point> hit_points;
3712
3713 // Find the players in this region
3714 for (map<uint32_t, GraphicalPlayer>::iterator it(m_players.begin()); it != m_players.end(); ++it) {
3715 GraphicalPlayer& thisplayer = it->second;
3716 if (thisplayer.get_id() == m_player_id) {
3717 continue;
3718 }
3719
3720 Point playerpos = thisplayer.get_position();
3721
3722 Circle player_circle(playerpos, thisplayer.get_radius());
3723
3724 double angle = 0;
3725 double intersecting = shape.solid_intersects_circle(player_circle, &angle);
3726 if (intersecting == -1) {
3727 // Not intersecting
3728 continue;
3729 }
3730
3731 double distance = Point::distance(pivot, playerpos);
3732 if (distance != -1) {
3733 HitObject thishit = HitObject(distance, playerpos, &thisplayer);
3734
3735 // Don't add the player if it's occluded.
3736 Point actualhit = is_occluded(pivot, playerpos, thishit);
3737 if (actualhit.x == -1 && actualhit.y == -1) {
3738 // Not actually a hit
3739 continue;
3740 }
3741
3742 hit_players.push_back(&thisplayer);
3743 hit_points.push_back(actualhit);
3744 }
3745 }
3746 return hit_points;
3747 }
3748
3749 // Check if any objects are in the way of this object starting at a given point
3750 // If occluded, return (-1, -1). If not, return the hit point.
is_occluded(const Point & startpos,Point & objectcenter,HitObject & object)3751 Point GameController::is_occluded(const Point& startpos, Point& objectcenter, HitObject& object) {
3752 double x_dist = objectcenter.x - startpos.x;
3753 double y_dist = objectcenter.y - startpos.y;
3754 double direction = atan2(y_dist, x_dist);
3755
3756 multiset<HitObject> hit_objects;
3757 shoot_in_line(startpos, direction, hit_objects);
3758
3759 // Find the nearest hit object, check if it's the same as the given object
3760 const HitObject& nearest_hit(*hit_objects.begin());
3761
3762 if ((nearest_hit.map_object != NULL && object.map_object != NULL &&
3763 nearest_hit.map_object == object.map_object)
3764 || (nearest_hit.player != NULL && object.player != NULL &&
3765 nearest_hit.player->get_id() == object.player->get_id())) {
3766 return nearest_hit.point;
3767 } else {
3768 return Point(-1, -1);
3769 }
3770 }
3771
3772 // Starting from the given point, shoot in a STRAIGHT LINE in the given direction,
3773 // and populate the given set with the objects that are hit.
shoot_in_line(Point startpos,double direction,multiset<HitObject> & hit_objects)3774 void GameController::shoot_in_line(Point startpos, double direction, multiset<HitObject>& hit_objects)
3775 {
3776 Point endpos(startpos + Vector::make_from_magnitude(m_map_width + m_map_height, direction));
3777
3778 //
3779 // First, find what map objects this line hits
3780 //
3781 const list<BaseMapObject*>& map_objects(m_map->get_objects());
3782 for (list<BaseMapObject*>::const_iterator it(map_objects.begin()); it != map_objects.end(); ++it) {
3783 BaseMapObject* thisobj = *it;
3784 if (!thisobj->is_shootable() || !thisobj->is_intersectable()) {
3785 continue;
3786 }
3787
3788 Point intersection = thisobj->get_bounding_shape()->intersects_line(startpos, endpos, NULL);
3789 if (intersection.x == -1 && intersection.y == -1) {
3790 // Not intersecting line
3791 continue;
3792 }
3793
3794 double distance = Point::distance(startpos, intersection);
3795 if (distance != -1) {
3796 hit_objects.insert(HitObject(distance, intersection, thisobj));
3797 }
3798 }
3799
3800 //
3801 // Now, find what players this line hits
3802 //
3803 for (map<uint32_t, GraphicalPlayer>::iterator it(m_players.begin()); it != m_players.end(); ++it) {
3804 GraphicalPlayer& thisplayer = it->second;
3805 if (thisplayer.get_id() == m_player_id) {
3806 continue;
3807 }
3808
3809 Circle player_circle(thisplayer.get_position(), thisplayer.get_radius());
3810
3811 Point intersection = player_circle.intersects_line(startpos, endpos, NULL);
3812 if (intersection.x == -1 && intersection.y == -1) {
3813 // Not intersecting line
3814 continue;
3815 }
3816
3817 double distance = Point::distance(startpos, intersection);
3818 if (distance != -1) {
3819 hit_objects.insert(HitObject(distance, intersection, &thisplayer));
3820 }
3821 }
3822
3823 //
3824 // Finally, find the nearest map edge where the shot will hit.
3825 //
3826 Point edge_intersection = m_map_polygon.intersects_line(startpos, endpos, NULL);
3827
3828 if (edge_intersection.x != -1 || edge_intersection.y != -1) {
3829 double distance = Point::distance(startpos, edge_intersection);
3830 if (distance != -1) {
3831 hit_objects.insert(HitObject(distance, edge_intersection));
3832 }
3833 }
3834 }
3835
3836 // This is a COMPATIBILITY WRAPPER around the more general shoot_in_line() function above.
3837 // Code should be migrated to use the new function.
find_shootable_object(Point startpos,double direction,BaseMapObject * & hit_map_object,Player * & hit_player)3838 Point GameController::find_shootable_object(Point startpos, double direction, BaseMapObject*& hit_map_object, Player*& hit_player) {
3839 multiset<HitObject> hit_objects;
3840 shoot_in_line(startpos, direction, hit_objects);
3841
3842 if (hit_objects.empty()) {
3843 return Point(-1, -1);
3844 }
3845
3846 const HitObject& nearest_hit(*hit_objects.begin());
3847 hit_map_object = nearest_hit.map_object;
3848 hit_player = nearest_hit.player;
3849 return nearest_hit.point;
3850 }
3851
3852
3853
weapon_info_packet(PacketReader & reader)3854 void GameController::weapon_info_packet(PacketReader& reader) {
3855 size_t weapon_index;
3856 WeaponReader weapon_data;
3857 reader >> weapon_index;
3858 reader >> weapon_data;
3859
3860 if (!m_weapons.count(weapon_data.get_id())) {
3861 if (Weapon* weapon = Weapon::new_weapon(weapon_data)) {
3862 m_weapons.insert(std::make_pair(weapon->get_id(), weapon));
3863 if (weapon_index == 0) {
3864 m_current_weapon = weapon;
3865 update_curr_weapon_image();
3866 }
3867 init_weapon_selector();
3868 }
3869 }
3870 }
3871
spawn_packet(PacketReader & reader)3872 void GameController::spawn_packet(PacketReader& reader) {
3873 // ACK TODO: ignore packet if we have already spawned, or if round has not started yet
3874 Point position;
3875 Vector velocity;
3876 bool is_grabbing_obstacle;
3877 bool is_alive;
3878 uint64_t freeze_time;
3879
3880 reader >> position >> velocity >> is_grabbing_obstacle >> is_alive >> freeze_time;
3881
3882 GraphicalPlayer* player = get_player_by_id(m_player_id);
3883
3884 if (!player) {
3885 return;
3886 }
3887
3888 player->set_position(position);
3889 player->set_velocity(velocity);
3890 player->set_is_grabbing_obstacle(is_grabbing_obstacle);
3891
3892 if (is_alive) {
3893 player->reset_energy();
3894 player->set_is_invisible(false);
3895 player->set_is_frozen(false);
3896 if (freeze_time) {
3897 freeze(freeze_time);
3898 }
3899 } else {
3900 player->set_energy(0);
3901 player->set_is_invisible(true);
3902 player->set_is_frozen(true);
3903 }
3904
3905 update_energy_bar();
3906
3907 // TODO: Move all this code below into a helper function!
3908
3909 // Update the radar and name sprite
3910 recreate_name(player);
3911 m_radar->move_blip(player->get_id(), player->get_x(), player->get_y());
3912 m_radar->set_blip_invisible(player->get_id(), player->is_invisible());
3913 player->get_name_sprite()->set_invisible(player->is_invisible());
3914
3915 // If invisible or frozen, set these things appropriately and show/hide the sprite.
3916 if (player->is_invisible()) {
3917 player->set_velocity(0, 0);
3918 } else {
3919 // Reposition the name sprite to reflect the player's new position
3920 m_text_manager->reposition_string(player->get_name_sprite(), player->get_x(), player->get_y() - (player->get_radius()+30), TextManager::CENTER);
3921 }
3922
3923 if (m_radar->get_mode() == RADAR_ON) {
3924 m_radar->set_blip_alpha(player->get_id(), player->is_frozen() ? 0.5 : 1.0);
3925 }
3926 }
3927
populate_graphic_group(GraphicGroup & group,const char * str)3928 void GameController::populate_graphic_group(GraphicGroup& group, const char* str) {
3929 StringTokenizer item_tokenizer(str, ';');
3930 while (item_tokenizer) {
3931 string item_string;
3932 item_tokenizer >> item_string;
3933
3934 Point position;
3935 double rotation;
3936 int priority;
3937 string sprite_name;
3938
3939 StringTokenizer(item_string, ':') >> position >> rotation >> priority >> sprite_name;
3940
3941 Sprite* sprite = m_graphics_cache.new_graphic<Sprite>(m_path_manager.data_path(sprite_name.c_str(), "sprites"));
3942 sprite->set_x(position.x);
3943 sprite->set_y(position.y);
3944 sprite->set_rotation(rotation);
3945 sprite->set_priority(priority);
3946
3947 group.add_graphic(sprite);
3948 }
3949 }
3950
add_front_arm(GraphicGroup & group,const char * sprite_name)3951 void GameController::add_front_arm(GraphicGroup& group, const char* sprite_name) {
3952 Sprite* sprite = m_graphics_cache.new_graphic<Sprite>(m_path_manager.data_path(sprite_name, "sprites"));
3953 sprite->set_x(0);
3954 sprite->set_y(0);
3955 sprite->set_rotation(0);
3956 sprite->set_priority(0);
3957 group.add_graphic(sprite);
3958 }
3959
register_front_arm_graphic(Player & base_player,const char * normal_str,const char * firing_str)3960 void GameController::register_front_arm_graphic(Player& base_player, const char* normal_str, const char* firing_str) {
3961 if (GraphicalPlayer* player = get_player_by_id(base_player.get_id())) {
3962 make_front_arm_graphic(*player->get_sprite(), player->get_team() == 'A' ? "blue_frontarm.png" : "red_frontarm.png", normal_str, firing_str);
3963 player->set_is_frozen(player->is_frozen());
3964 }
3965 }
3966
make_front_arm_graphic(GraphicGroup & player_sprite,const char * arm_sprite,const char * normal_str,const char * firing_str)3967 void GameController::make_front_arm_graphic(GraphicGroup& player_sprite, const char* arm_sprite, const char* normal_str, const char* firing_str) {
3968 GraphicGroup normal_group;
3969 GraphicGroup firing_group;
3970
3971 if (normal_str) {
3972 populate_graphic_group(normal_group, normal_str);
3973 }
3974 if (firing_str) {
3975 populate_graphic_group(firing_group, firing_str);
3976 }
3977
3978 if (arm_sprite) {
3979 add_front_arm(normal_group, arm_sprite);
3980 add_front_arm(firing_group, arm_sprite);
3981 }
3982
3983 firing_group.set_invisible(true);
3984
3985 GraphicGroup frontarm;
3986 frontarm.add_graphic(&normal_group, "normal");
3987 frontarm.add_graphic(&firing_group, "firing");
3988 frontarm.set_x(13);
3989 frontarm.set_y(-18);
3990 frontarm.set_center_x(13);
3991 frontarm.set_center_y(-18);
3992 if (Graphic* oldarm = player_sprite.get_graphic("frontarm")) {
3993 frontarm.set_rotation(oldarm->get_rotation());
3994 }
3995
3996 player_sprite.remove_graphic("frontarm");
3997 player_sprite.add_graphic(&frontarm, "frontarm");
3998 }
3999
4000
send_packet(const PacketWriter & packet)4001 void GameController::send_packet(const PacketWriter& packet) {
4002 m_network.send_packet(packet);
4003 }
4004
send_reliable_packet(const PacketWriter & packet)4005 void GameController::send_reliable_packet(const PacketWriter& packet) {
4006 m_network.send_reliable_packet(packet);
4007 }
4008
get_next_scan_id()4009 uint32_t GameController::get_next_scan_id() {
4010 static uint32_t next_scan_id = 1L;
4011 return next_scan_id++;
4012 }
4013
excessive_packet_drop()4014 void GameController::excessive_packet_drop() {
4015 display_message("Too much packet loss to server.", RED_COLOR, RED_SHADOW);
4016 disconnect();
4017 }
4018
4019