PageRenderTime 59ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/branches/release-1.0/Source_Files/Network/network_dialog_widgets_sdl.cpp

#
C++ | 1111 lines | 664 code | 251 blank | 196 comment | 125 complexity | b0f5274e318616c50dbd395aff5f1d08 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, BSD-3-Clause, GPL-3.0, LGPL-3.0, MPL-2.0-no-copyleft-exception, Zlib, GPL-2.0
  1. /*
  2. * network_dialog_widgets_sdl.cpp
  3. Copyright (C) 2001 and beyond by Woody Zenfell, III
  4. and the "Aleph One" developers.
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. This license is contained in the file "COPYING",
  14. which is included with this source code; it is available online at
  15. http://www.gnu.org/licenses/gpl.html
  16. * Implementation of network-dialog-specific widgets in the SDL dialog system.
  17. *
  18. * Created by Woody Zenfell, III on Fri Sep 28 2001.
  19. *
  20. * Mar 1, 2002 (Woody Zenfell): Added new w_entry_point_selector widget.
  21. */
  22. #if !defined(DISABLE_NETWORKING)
  23. #include "network_dialog_widgets_sdl.h"
  24. #include "screen_drawing.h"
  25. #include "sdl_fonts.h"
  26. #include "interface.h"
  27. #include "network.h"
  28. // these next are for playing with shape-drawing
  29. #include "player.h"
  30. #include "HUDRenderer.h"
  31. #include "shell.h"
  32. #include "collection_definition.h"
  33. // here are some for w_entry_point_selector
  34. #include "preferences.h"
  35. #include "screen.h"
  36. // for TS_GetCString, get shared ref rather than copying string.
  37. #include "TextStrings.h"
  38. #include "TextLayoutHelper.h"
  39. #include <string>
  40. // jkvw: I'm putting this here because we only really want it for find_item_index_in_vecotr,
  41. // and of course we shouldn't be doing that anyway :).
  42. bool operator==(const prospective_joiner_info &left, const prospective_joiner_info &right)
  43. { return left.stream_id == right.stream_id; }
  44. ////// helper functions //////
  45. // Actually, as it turns out, there should be a generic STL algorithm that does this, I think.
  46. // Well, w_found_players ought to be using a set<> or similar anyway, much more natural.
  47. // Shrug, this was what I came up with before I knew anything about STL, and I'm too lazy to change it.
  48. template<class T>
  49. static const size_t
  50. find_item_index_in_vector(const T& inItem, const vector<T>& inVector) {
  51. typename vector<T>::const_iterator i = inVector.begin();
  52. typename vector<T>::const_iterator end = inVector.end();
  53. size_t index = 0;
  54. while(i != end) {
  55. if(*i == inItem)
  56. return index;
  57. index++;
  58. i++;
  59. }
  60. // Didn't find it
  61. return -1;
  62. }
  63. ////// w_found_players //////
  64. void
  65. w_found_players::found_player(prospective_joiner_info &player) {
  66. // Found one
  67. found_players.push_back(player);
  68. // List it
  69. list_player(player);
  70. }
  71. void
  72. w_found_players::hide_player(const prospective_joiner_info &player) {
  73. found_players.push_back(player);
  74. unlist_player(player);
  75. }
  76. void
  77. w_found_players::list_player(prospective_joiner_info &player) {
  78. listed_players.push_back(player);
  79. num_items = listed_players.size();
  80. new_items();
  81. }
  82. void w_found_players::update_player(prospective_joiner_info &player) {
  83. unlist_player(player);
  84. list_player(player);
  85. }
  86. void
  87. w_found_players::unlist_player(const prospective_joiner_info &player) {
  88. size_t theIndex = find_item_index_in_vector(player, listed_players);
  89. if(theIndex == -1)
  90. return;
  91. listed_players.erase(listed_players.begin() + theIndex);
  92. size_t old_top_item = top_item;
  93. num_items = listed_players.size();
  94. new_items();
  95. // If the element deleted was the top item or before the top item, shift view up an item to compensate (if there is anything "up").
  96. if(theIndex <= old_top_item && old_top_item > 0)
  97. old_top_item--;
  98. // Reconcile overhang, if needed.
  99. if(old_top_item + shown_items > num_items && num_items >= shown_items)
  100. set_top_item(num_items - shown_items);
  101. else
  102. set_top_item(old_top_item);
  103. }
  104. void
  105. w_found_players::item_selected() {
  106. if(player_selected_callback != NULL)
  107. player_selected_callback(this, listed_players[get_selection()]);
  108. }
  109. // ZZZ: this is pretty ugly, it assumes that the callback will remove players from the widget.
  110. // Fortunately, that's the case currently. :)
  111. void
  112. w_found_players::callback_on_all_items() {
  113. if(player_selected_callback != NULL) {
  114. for (vector<prospective_joiner_info>::iterator it = listed_players.begin(); it != listed_players.end(); it++) {
  115. player_selected_callback(this, *it);
  116. }
  117. }
  118. }
  119. void
  120. w_found_players::draw_item(vector<prospective_joiner_info>::const_iterator i, SDL_Surface *s, int16 x, int16 y, uint16 width, bool selected) const {
  121. char theNameBuffer[SSLP_MAX_NAME_LENGTH + 12];
  122. pstrncpy((unsigned char*)theNameBuffer, (unsigned char*)(*i).name, SSLP_MAX_NAME_LENGTH - 1);
  123. a1_p2cstr((unsigned char *) theNameBuffer);
  124. if ((*i).gathering) {
  125. strcat(theNameBuffer, " (gathering)");
  126. }
  127. int computed_x = x + (width - text_width(theNameBuffer, font, style)) / 2;
  128. int computed_y = y + font->get_ascent();
  129. //unsigned char text_length = (*i)->sslps_name[0];
  130. //if(text_length > SSLP_MAX_NAME_LENGTH - 1)
  131. // text_length = SSLP_MAX_NAME_LENGTH - 1;
  132. if ((*i).gathering) {
  133. draw_text(s, theNameBuffer, computed_x, computed_y, get_theme_color(ITEM_WIDGET, DISABLED_STATE), font, style);
  134. } else {
  135. draw_text(s, /*&((*i)->sslps_name[1]), text_length,*/ theNameBuffer, computed_x, computed_y,
  136. selected ? get_theme_color(ITEM_WIDGET, ACTIVE_STATE) : get_theme_color(ITEM_WIDGET, DEFAULT_STATE), font, style);
  137. }
  138. }
  139. ////// w_players_in_game2 //////
  140. // I guess these should be computed more dynamically, but it wasn't obvious the best way to do that.
  141. // These values work well for the standard player shapes, anyway.
  142. enum {
  143. kWPIG2Width = 600, // widget width
  144. kWPIG2Height = 142, // widget height (actual height will differ if postgame_layout)
  145. kMaxHeadroom = 53, // height above player origin (approx. navel) of tallest player shape
  146. kNameOffset = 80, // how far below player origin baseline of player's name should appear
  147. kNumNameOffsets = MAXIMUM_NUMBER_OF_PLAYERS, // used to resolve overlapping names
  148. kNameMargin = 6, // names overlap if their edges are fewer than this many pixels apart
  149. kNormalPlayerOffset = kMaxHeadroom,
  150. kNormalNameTotalOffset = kNormalPlayerOffset + kNameOffset,
  151. // kPostgameTopMargin = 70, // how much extra space is at the top of widget in postgame layout
  152. kPostgameTopMargin = 190, // For postgame layout without chat window, we can use a lot more space. (use 70 to coexist with full chat UI)
  153. kPostgameBottomMargin = 6, // how much extra space is at the bottom of widget in postgame layout
  154. kBarBottomOffset = 80, // how far below player origin score/kill bars should start
  155. kBarWidth = 10, // how wide a kill/score bar should be
  156. kBarOffsetX = 20, // how much to offset a bar so it won't draw directly on a player
  157. kBevelSize = 2, // how much "depth effect" (in pixels around the border) bars have
  158. kUseLegendThreshhold = 5, // with this many players or more, use legend for kills/deaths rather than print at bar labels
  159. kPostgamePlayerOffset = kPostgameTopMargin + kMaxHeadroom,
  160. kPostgameNameTotalOffset = kPostgamePlayerOffset + kNameOffset,
  161. kBarBottomTotalOffset = kPostgamePlayerOffset + kBarBottomOffset,
  162. kPostgameHeight = kPostgameTopMargin + kWPIG2Height + kPostgameBottomMargin
  163. };
  164. /*
  165. // These can't see postgame_layout. Duh. And the idea here was to avoid having the constants above
  166. // in a header file (as would be needed for making inline methods) where they would force extra
  167. // recompilation... burrito. Macros it is.
  168. static inline int
  169. get_player_y_offset() { return postgame_layout ? kPostgamePlayerOffset : kNormalPlayerOffset; }
  170. static inline int
  171. get_name_y_offset() { return postgame_layout ? kPostgameNameTotalOffset : kNormalNameTotalOffset; }
  172. */
  173. #define get_player_y_offset() (postgame_layout ? kPostgamePlayerOffset : kNormalPlayerOffset)
  174. #define get_name_y_offset() (postgame_layout ? kPostgameNameTotalOffset : kNormalNameTotalOffset)
  175. // Here I divide each piece of space into N pieces (where N is the number of things to draw)
  176. // each item is drawn in the center of its space. This pitches them a little more widely than
  177. // is used in the separately-drawn strategy.
  178. // The computation used is (I from 0 to N-1, W is width) for the center:
  179. // ((I + .5) / N) * W
  180. // == WI + .5W / N
  181. // == W*(2I + 1) / 2N
  182. static inline int
  183. get_wide_spaced_center_offset(int left_x, int available_width, size_t index, size_t num_items) {
  184. return left_x + (((2 * (int)index + 1) * available_width) / (2 * (int)num_items));
  185. }
  186. // for the left:
  187. // I/N * W
  188. // == WI/N
  189. static inline int
  190. get_wide_spaced_left_offset(int left_x, int available_width, size_t index, size_t num_items) {
  191. return left_x + (((int)index * available_width) / (int)num_items);
  192. }
  193. // width is easy...
  194. // note though that the actual distances between left_offsets may vary slightly from this width due to rounding.
  195. static inline int
  196. get_wide_spaced_width(int available_width, size_t num_items) {
  197. return available_width / (int)num_items;
  198. }
  199. // Horizontal layout centers single player at 1/2 the width; two players at 1/3 and 2/3; three at 1/4, 2/4, 3/4....
  200. // Doing (I * W) / N rather than the more natural (I/N) * W may give more accurate results with integer math.
  201. static inline int
  202. get_close_spaced_center_offset(int left_x, int available_width, size_t index, size_t num_items) {
  203. return left_x + ((((int)index + 1) * available_width) / ((int)num_items + 1));
  204. }
  205. static inline int
  206. get_close_spaced_width(int available_width, size_t num_items) {
  207. return available_width / ((int)num_items + 1);
  208. }
  209. w_players_in_game2::w_players_in_game2(bool inPostgameLayout) :
  210. widget(MESSAGE_WIDGET), displaying_actual_information(false), postgame_layout(inPostgameLayout),
  211. draw_carnage_graph(false), num_valid_net_rankings(0), selected_player(NONE),
  212. clump_players_by_team(false), draw_scores_not_carnage(false)
  213. {
  214. rect.w = kWPIG2Width;
  215. rect.h = postgame_layout ? kPostgameHeight : kWPIG2Height;
  216. saved_min_width = rect.w;
  217. saved_min_height = rect.h;
  218. }
  219. w_players_in_game2::~w_players_in_game2() {
  220. clear_vector();
  221. }
  222. void
  223. w_players_in_game2::update_display(bool inFromDynamicWorld /* default=false */) {
  224. // Start over - wipe out our local player-storage
  225. clear_vector();
  226. // Wipe out references to players through teams
  227. for(int i = 0; i < NUMBER_OF_TEAM_COLORS; i++)
  228. players_on_team[i].clear();
  229. // Find the number of players
  230. int num_players;
  231. if(inFromDynamicWorld)
  232. num_players = dynamic_world->player_count;
  233. else
  234. num_players = displaying_actual_information ? NetGetNumberOfPlayers() : 0;
  235. // Fill in the entries
  236. for(int i = 0; i < num_players; i++) {
  237. player_entry2 thePlayerEntry;
  238. int thePlayerTeam;
  239. int thePlayerColor;
  240. if(inFromDynamicWorld) {
  241. // Get player information from dynamic_world
  242. player_data* thePlayerData = get_player_data(i);
  243. // Copy the player name. We will store it as a cstring...
  244. strncpy(thePlayerEntry.player_name, thePlayerData->name, MAXIMUM_PLAYER_NAME_LENGTH + 1);
  245. // Look up colors
  246. thePlayerTeam = thePlayerData->team;
  247. thePlayerColor = thePlayerData->color;
  248. }
  249. else {
  250. // Get player information from topology
  251. player_info* thePlayerInfo = (player_info*)NetGetPlayerData(i);
  252. // Alias the player entry's name field as a pstring
  253. unsigned char* thePlayerEntryNameP = (unsigned char*) thePlayerEntry.player_name;
  254. // Copy the player name. We will store it as a cstring...
  255. pstrncpy(thePlayerEntryNameP, thePlayerInfo->name, MAXIMUM_PLAYER_NAME_LENGTH + 1);
  256. // In-place conversion.
  257. a1_p2cstr(thePlayerEntryNameP);
  258. // Look up colors
  259. thePlayerTeam = thePlayerInfo->team;
  260. thePlayerColor = thePlayerInfo->color;
  261. }
  262. // Set the size of the text
  263. thePlayerEntry.name_width = text_width(thePlayerEntry.player_name, font, style | styleShadow);
  264. // Get the pixel-color for the player's team (for drawing the name)
  265. thePlayerEntry.name_pixel_color = get_dialog_player_color(thePlayerTeam);
  266. // Set up a player image for the player (funfun)
  267. thePlayerEntry.player_image = new PlayerImage;
  268. thePlayerEntry.player_image->setRandomFlatteringView();
  269. thePlayerEntry.player_image->setPlayerColor(thePlayerColor);
  270. thePlayerEntry.player_image->setTeamColor(thePlayerTeam);
  271. // Add the player to our local storage area
  272. player_entries.push_back(thePlayerEntry);
  273. // Add a reference to the player through his team color
  274. players_on_team[thePlayerTeam].push_back(i);
  275. }
  276. dirty = true;
  277. }
  278. #if 0
  279. // this is for testing
  280. static const char* sTestingNames[] = {
  281. "Doctor Burrito",
  282. "Carnage Asada",
  283. "Bongo Bob",
  284. "The Napalm Man",
  285. "The Big Lebowski",
  286. "lala",
  287. "Prof. Windsurf",
  288. "<<<-ZED-<<<"
  289. };
  290. void
  291. w_players_in_game2::click(int, int) {
  292. player_entry2 thePlayerEntry;
  293. // make up a name
  294. /* int theNameLength = (local_random() % MAXIMUM_PLAYER_NAME_LENGTH) + 1;
  295. for(int i = 0; i < theNameLength; i++)
  296. thePlayerEntry.player_name[i] = 'a' + (local_random() % ('z' - 'a'));
  297. thePlayerEntry.player_name[theNameLength] = '\0';
  298. // strcpy(thePlayerEntry.player_name, "The Big Lebowski");
  299. */
  300. strcpy(thePlayerEntry.player_name, sTestingNames[local_random() % 8]);
  301. // Set the size of the text
  302. thePlayerEntry.name_width = text_width(thePlayerEntry.player_name, font, style);
  303. // Make up a team-color
  304. int theTeamColor = local_random() % 8;
  305. // Get the pixel-color for the player's team (for drawing the name)
  306. thePlayerEntry.name_pixel_color = get_dialog_player_color(theTeamColor);
  307. // Set up a player image for the player (funfun)
  308. thePlayerEntry.player_image = new PlayerImage;
  309. thePlayerEntry.player_image->setRandomFlatteringView();
  310. thePlayerEntry.player_image->setTeamColor(theTeamColor);
  311. player_entries.push_back(thePlayerEntry);
  312. dirty = true;
  313. }
  314. #else // NOT 0
  315. void
  316. w_players_in_game2::click(int x, int) {
  317. if(draw_carnage_graph) {
  318. if(clump_players_by_team) {
  319. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  320. if(ABS(x - get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings))
  321. < (get_wide_spaced_width(rect.w, num_valid_net_rankings) / 2))
  322. {
  323. if(element_clicked_callback != NULL)
  324. element_clicked_callback(this, clump_players_by_team, draw_carnage_graph, draw_scores_not_carnage,
  325. i, net_rankings[i].color);
  326. break;
  327. }
  328. }
  329. }
  330. else {
  331. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  332. if(ABS(x - get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings))
  333. < (get_close_spaced_width(rect.w, num_valid_net_rankings) / 2))
  334. {
  335. if(element_clicked_callback != NULL)
  336. element_clicked_callback(this, clump_players_by_team, draw_carnage_graph, draw_scores_not_carnage,
  337. i, net_rankings[i].player_index);
  338. break;
  339. }
  340. }
  341. }
  342. } // draw_carnage_graph
  343. }
  344. #endif // NOT 0
  345. // enable carnage reporting mode and set the data needed to draw a graph.
  346. void
  347. w_players_in_game2::set_graph_data(const net_rank* inRankings, int inNumRankings, int inSelectedPlayer,
  348. bool inClumpPlayersByTeam, bool inDrawScoresNotCarnage)
  349. {
  350. draw_carnage_graph = true;
  351. num_valid_net_rankings = inNumRankings;
  352. selected_player = inSelectedPlayer;
  353. clump_players_by_team = inClumpPlayersByTeam;
  354. draw_scores_not_carnage = inDrawScoresNotCarnage;
  355. memcpy(net_rankings, inRankings, inNumRankings * sizeof(net_rank));
  356. dirty = true;
  357. }
  358. void
  359. w_players_in_game2::draw_player_icon(SDL_Surface* s, size_t rank_index, int center_x) const {
  360. // Note, player images will not be re-fetched unless the brightness has *changed* since last draw.
  361. PlayerImage* theImage = player_entries[net_rankings[rank_index].player_index].player_image;
  362. if(selected_player != NONE && selected_player != rank_index)
  363. theImage->setBrightness(.4f);
  364. else
  365. theImage->setBrightness(1.0f);
  366. theImage->drawAt(s, center_x, rect.y + get_player_y_offset());
  367. }
  368. void
  369. w_players_in_game2::draw_player_icons_separately(SDL_Surface* s) const {
  370. if(draw_carnage_graph) {
  371. // Draw in sorted order (according to net_rankings)
  372. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  373. int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  374. draw_player_icon(s, i, center_x);
  375. }
  376. }
  377. else {
  378. // Draw in "natural order" (according to topology)
  379. size_t theNumPlayers = player_entries.size();
  380. for(size_t i = 0; i < theNumPlayers; i++) {
  381. int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, theNumPlayers);
  382. player_entries[i].player_image->drawAt(s, center_x, rect.y + get_player_y_offset());
  383. }
  384. }
  385. } // draw_player_icons_separately
  386. void
  387. w_players_in_game2::draw_player_icons_clumped(SDL_Surface* s) const {
  388. assert(draw_carnage_graph);
  389. int width_per_team = get_wide_spaced_width(rect.w, num_valid_net_rankings);
  390. // Walk through teams, drawing each batch.
  391. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  392. int team_left_x = get_wide_spaced_left_offset(rect.x, rect.w, i, num_valid_net_rankings);
  393. size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
  394. assert(theNumberOfPlayersOnThisTeam > 0);
  395. // Walk through players on a team to draw a batch.
  396. for(size_t j = 0; j < theNumberOfPlayersOnThisTeam; j++) {
  397. int player_center_x = get_close_spaced_center_offset(team_left_x, width_per_team, j, theNumberOfPlayersOnThisTeam);
  398. // Note, player images will not be re-fetched unless the brightness has *changed* since last draw.
  399. // Though Marathon does not let one view team vs team carnage (just total team carnage), I'm leaving
  400. // the highlighting stuff here in case team view is later added.
  401. PlayerImage* theImage = player_entries[players_on_team[net_rankings[i].color][j]].player_image;
  402. if(selected_player != NONE && selected_player != i)
  403. theImage->setBrightness(.4f);
  404. else
  405. theImage->setBrightness(1.0f);
  406. theImage->drawAt(s, player_center_x, rect.y + get_player_y_offset());
  407. } // players
  408. } // teams
  409. } // draw_player_icons_clumped
  410. void
  411. w_players_in_game2::draw_player_names_separately(SDL_Surface* s, TextLayoutHelper& ioTextLayoutHelper) const {
  412. // Now let's draw the names. Let's take care to offset names vertically if they would
  413. // overlap (or come too close as defined by kNameMargin), so it's more readable.
  414. size_t theNumPlayers = draw_carnage_graph ? num_valid_net_rankings : player_entries.size();
  415. for(size_t i = 0; i < theNumPlayers; i++) {
  416. int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, theNumPlayers);
  417. const player_entry2* theEntry = draw_carnage_graph ? &player_entries[net_rankings[i].player_index] : &player_entries[i];
  418. int name_x = center_x - (theEntry->name_width / 2);
  419. int name_y = rect.y + get_name_y_offset();
  420. // Find a suitable vertical offset
  421. name_y = ioTextLayoutHelper.reserveSpaceFor(name_x - kNameMargin / 2, theEntry->name_width + kNameMargin, name_y, font->get_line_height());
  422. draw_text(s, theEntry->player_name, name_x, name_y,
  423. theEntry->name_pixel_color, font, style | styleShadow);
  424. }
  425. }
  426. void
  427. w_players_in_game2::draw_player_names_clumped(SDL_Surface* s, TextLayoutHelper& ioTextLayoutHelper) const {
  428. // Now let's draw the names. Let's take care to offset names vertically if they would
  429. // overlap (or come too close as defined by kNameMargin), so it's more readable.
  430. // Walk through teams, drawing each batch.
  431. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  432. int team_center_x = get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  433. size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
  434. assert(theNumberOfPlayersOnThisTeam > 0);
  435. // Walk through players on a team to draw a batch.
  436. for(size_t j = 0; j < theNumberOfPlayersOnThisTeam; j++) {
  437. const player_entry2* theEntry = &(player_entries[players_on_team[net_rankings[i].color][j]]);
  438. int name_x = team_center_x - (theEntry->name_width / 2);
  439. int name_y = rect.y + get_name_y_offset();
  440. // Find a suitable vertical offset
  441. name_y = ioTextLayoutHelper.reserveSpaceFor(name_x - kNameMargin/2, theEntry->name_width + kNameMargin,
  442. name_y, font->get_line_height());
  443. draw_text(s, theEntry->player_name, name_x, name_y,
  444. theEntry->name_pixel_color, font, style | styleShadow);
  445. }
  446. }
  447. }
  448. int
  449. w_players_in_game2::find_maximum_bar_value() const {
  450. int theMaxValue = INT_MIN;
  451. // We track min also to handle games with negative scores.
  452. int theMinValue = INT_MAX;
  453. if(selected_player != NONE)
  454. // This way, all player vs player graphs use the same scale.
  455. theMaxValue = calculate_max_kills(num_valid_net_rankings);
  456. else {
  457. // Note this does the right thing for suicide bars as well.
  458. if(draw_scores_not_carnage) {
  459. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  460. if(net_rankings[i].game_ranking > theMaxValue)
  461. theMaxValue = net_rankings[i].game_ranking;
  462. if(net_rankings[i].game_ranking < theMinValue)
  463. theMinValue = net_rankings[i].game_ranking;
  464. }
  465. } else {
  466. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  467. if(net_rankings[i].kills > theMaxValue)
  468. theMaxValue = net_rankings[i].kills;
  469. if(net_rankings[i].deaths > theMaxValue)
  470. theMaxValue = net_rankings[i].deaths;
  471. }
  472. }
  473. }
  474. // If all values were nonpositive, and we had at least one negative, we
  475. // return the (negative) value furthest from 0.
  476. // The Mac version seems to do nothing of the sort - how can it possibly
  477. // display correct bars for games with negative scores like "Tag"??
  478. if(theMaxValue <= 0 && theMinValue < 0)
  479. theMaxValue = theMinValue;
  480. return theMaxValue;
  481. }
  482. struct bar_info {
  483. int center_x;
  484. int top_y;
  485. uint32 pixel_color;
  486. string label_text;
  487. };
  488. void
  489. w_players_in_game2::draw_bar_or_bars(SDL_Surface* s, size_t rank_index, int center_x, int maximum_value, vector<bar_info>& outBarInfos) const {
  490. // Draw score bar
  491. if(draw_scores_not_carnage) {
  492. bar_info theBarInfo;
  493. int theScore = net_rankings[rank_index].game_ranking;
  494. calculate_ranking_text_for_post_game(temporary, theScore);
  495. theBarInfo.label_text = temporary; // this makes a copy
  496. draw_bar(s, center_x, _score_color, theScore, maximum_value, theBarInfo);
  497. // Don't draw a "0" score label
  498. if(theScore != 0)
  499. outBarInfos.push_back(theBarInfo);
  500. }
  501. else {
  502. // Draw carnage bar(s)
  503. if(rank_index == selected_player) {
  504. // Draw suicides/friendly-fires
  505. bar_info theBarInfo;
  506. char* theSuicidesFormat = TS_GetCString(strNET_STATS_STRINGS, strSUICIDES_STRING);
  507. int theNumberOfSuicides = net_rankings[rank_index].kills;
  508. sprintf(temporary, theSuicidesFormat, theNumberOfSuicides);
  509. theBarInfo.label_text = temporary; // this makes a copy
  510. draw_bar(s, center_x, _suicide_color, theNumberOfSuicides, maximum_value, theBarInfo);
  511. // Don't push a "0" label.
  512. if(theNumberOfSuicides > 0)
  513. outBarInfos.push_back(theBarInfo);
  514. }
  515. else {
  516. // Draw kills and deaths
  517. int theNumKills = net_rankings[rank_index].kills;
  518. int theNumDeaths = net_rankings[rank_index].deaths;
  519. // Get strings for labelling
  520. const char* theKillsFormat;
  521. const char* theDeathsFormat;
  522. char theKillsString[32];
  523. char theDeathsString[32];
  524. // If more than threshhold bar-pairs to draw, use short form with legend rather than normal (long) form.
  525. theKillsFormat = num_valid_net_rankings >= kUseLegendThreshhold ? "%d" : TS_GetCString(strNET_STATS_STRINGS, strKILLS_STRING);
  526. theDeathsFormat = num_valid_net_rankings >= kUseLegendThreshhold ? "%d" : TS_GetCString(strNET_STATS_STRINGS, strDEATHS_STRING);
  527. // Construct labels
  528. sprintf(theKillsString, theKillsFormat, theNumKills);
  529. sprintf(theDeathsString, theDeathsFormat, theNumDeaths);
  530. // Set up bar_infos
  531. bar_info theKillsBarInfo;
  532. bar_info theDeathsBarInfo;
  533. // Copy strings into bar_infos
  534. theKillsBarInfo.label_text = theKillsString;
  535. theDeathsBarInfo.label_text = theDeathsString;
  536. // Draw shorter bar in front - looks nicer
  537. // If equal, draw kills in front
  538. // Put shorter bar_info in vector first so its label doesn't "leapfrog" the taller bar label in case of conflict.
  539. // Don't put "0"s into the vector.
  540. if(theNumKills > theNumDeaths) {
  541. // Deaths bar is shorter - draw it last
  542. draw_bar(s, center_x - kBarWidth / 3, _kill_color, theNumKills, maximum_value, theKillsBarInfo);
  543. draw_bar(s, center_x + kBarWidth / 3, _death_color, theNumDeaths, maximum_value, theDeathsBarInfo);
  544. if(theNumDeaths > 0)
  545. outBarInfos.push_back(theDeathsBarInfo);
  546. if(theNumKills > 0)
  547. outBarInfos.push_back(theKillsBarInfo);
  548. }
  549. else {
  550. // Kills bar is shorter or equal - draw it last
  551. draw_bar(s, center_x + kBarWidth / 3, _death_color, theNumDeaths, maximum_value, theDeathsBarInfo);
  552. draw_bar(s, center_x - kBarWidth / 3, _kill_color, theNumKills, maximum_value, theKillsBarInfo);
  553. if(theNumKills > 0)
  554. outBarInfos.push_back(theKillsBarInfo);
  555. if(theNumDeaths > 0)
  556. outBarInfos.push_back(theDeathsBarInfo);
  557. } // kills and deaths (not suicides)
  558. } // carnage bars
  559. } // !draw_scores_not_carnage (i.e. draw carnage)
  560. } // draw_bar_or_bars
  561. void
  562. w_players_in_game2::draw_bars_separately(SDL_Surface* s, vector<bar_info>& outBarInfos) const {
  563. // Find the largest value we'll be drawing, so we know how to scale our bars.
  564. int theMaxValue = find_maximum_bar_value();
  565. // Draw the bars.
  566. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  567. int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  568. draw_bar_or_bars(s, i, center_x + kBarOffsetX, theMaxValue, outBarInfos);
  569. } // walk through rankings
  570. } // draw_bars_separately
  571. void
  572. w_players_in_game2::draw_bars_clumped(SDL_Surface* s, vector<bar_info>& outBarInfos) const {
  573. // Find the largest value we'll be drawing, so we know how to scale our bars.
  574. int theMaxValue = find_maximum_bar_value();
  575. // Walk through teams, drawing each batch.
  576. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  577. int team_center_x = (int)(rect.x + ((2*i + 1) * rect.w) / (2 * num_valid_net_rankings));
  578. size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
  579. assert(theNumberOfPlayersOnThisTeam > 0);
  580. // We will offset if we would draw on top of a player (i.e. if num players is odd), else
  581. // we will draw right smack in the middle.
  582. int center_x = team_center_x;
  583. if(theNumberOfPlayersOnThisTeam % 2 == 1)
  584. center_x += kBarOffsetX;
  585. draw_bar_or_bars(s, i, center_x, theMaxValue, outBarInfos);
  586. } // walk through rankings
  587. } // draw_bars_clumped
  588. void
  589. w_players_in_game2::draw_carnage_totals(SDL_Surface* s) const {
  590. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  591. int center_x;
  592. if(clump_players_by_team)
  593. center_x = get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  594. else
  595. center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  596. // Draw carnage score for player/team (list -N for N suicides)
  597. int thePlayerCarnageScore = (selected_player == i) ? -net_rankings[i].kills : net_rankings[i].kills - net_rankings[i].deaths;
  598. if(thePlayerCarnageScore == 0)
  599. strcpy(temporary, "0");
  600. else
  601. sprintf(temporary, "%+d", thePlayerCarnageScore);
  602. uint16 theBiggerFontStyle = 0;
  603. font_info* theBiggerFont = get_theme_font(LABEL_WIDGET, theBiggerFontStyle);
  604. int theStringCenter = center_x - (text_width(temporary, theBiggerFont, theBiggerFontStyle | styleShadow) / 2);
  605. draw_text(s, temporary, theStringCenter, rect.y + rect.h - 1, SDL_MapRGB(s->format, 0xff, 0xff, 0xff),
  606. theBiggerFont, theBiggerFontStyle | styleShadow);
  607. } // walk through rankings
  608. } // draw_carnage_totals
  609. void
  610. w_players_in_game2::draw_carnage_legend(SDL_Surface* s) const {
  611. RGBColor theBrightestColor;
  612. get_net_color(_kill_color, &theBrightestColor);
  613. RGBColor theMiddleColor;
  614. theMiddleColor.red = (theBrightestColor.red * 7) / 10;
  615. theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
  616. theMiddleColor.green = (theBrightestColor.green * 7) / 10;
  617. uint32 thePixelColor = SDL_MapRGB(s->format, theMiddleColor.red >> 8, theMiddleColor.green >> 8, theMiddleColor.blue >> 8);
  618. draw_text(s, TS_GetCString(strNET_STATS_STRINGS, strKILLS_LEGEND), rect.x, rect.y + font->get_line_height(),
  619. thePixelColor, font, style);
  620. get_net_color(_death_color, &theBrightestColor);
  621. theMiddleColor.red = (theBrightestColor.red * 7) / 10;
  622. theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
  623. theMiddleColor.green = (theBrightestColor.green * 7) / 10;
  624. thePixelColor = SDL_MapRGB(s->format, theMiddleColor.red >> 8, theMiddleColor.green >> 8, theMiddleColor.blue >> 8);
  625. draw_text(s, TS_GetCString(strNET_STATS_STRINGS, strDEATHS_LEGEND), rect.x, rect.y + 2 * font->get_line_height(),
  626. thePixelColor, font, style);
  627. }
  628. void
  629. w_players_in_game2::draw_bar_labels(SDL_Surface* s, const vector<bar_info>& inBarInfos, TextLayoutHelper& ioTextLayoutHelper) const {
  630. size_t theNumberOfLabels = inBarInfos.size();
  631. for(size_t i = 0; i < theNumberOfLabels; i++) {
  632. const bar_info& theBarInfo = inBarInfos[i];
  633. int theStringWidth = text_width(theBarInfo.label_text.c_str(), font, style | styleShadow);
  634. int theTextX = theBarInfo.center_x - theStringWidth / 2;
  635. int theBestY = ioTextLayoutHelper.reserveSpaceFor(theTextX - kNameMargin/2,
  636. theStringWidth + kNameMargin, theBarInfo.top_y - 1, font->get_line_height());
  637. draw_text(s, theBarInfo.label_text.c_str(), theTextX, theBestY, theBarInfo.pixel_color, font, style | styleShadow);
  638. }
  639. } // draw_bar_labels
  640. void
  641. w_players_in_game2::draw(SDL_Surface* s) const {
  642. // printf("widget top is %d, bottom is %d\n", rect.y, rect.y + rect.h);
  643. // Set clip rectangle so we don't color outside the lines
  644. set_drawing_clip_rectangle(rect.y, rect.x, rect.y + rect.h, rect.x + rect.w);
  645. // Did some tests here - it seems that text drawing is clipped by this rectangle, but rect-filling
  646. // and blitting are not. (at least, on the Mac OS X version of SDL. actually, in Win 98 too.)
  647. // This means that little tiny chunks of pistol fire, feet, etc. can stick around if they are drawn
  648. // outside the widget (because they won't be cleared away when the widget is redrawn). I'm surrounding
  649. // that with a "Somebody Else's Problem" field for the time being.
  650. // set_drawing_clip_rectangle(100, 300, 200, 400);
  651. // printf("clipped at <%d %d %d %d>\n", rect.y, rect.x, rect.y + rect.h, rect.x + rect.w);
  652. // theTextLayoutHelper exists for the duration of the draw operation
  653. // helps us draw bits of text that do not overlap one another.
  654. TextLayoutHelper theTextLayoutHelper;
  655. // theBarInfos exists for the duration of the draw operation
  656. // helps us plan our bar label placement early (at draw_bar time)
  657. // but draw them late (at draw_bar_labels time).
  658. vector<bar_info> theBarInfos;
  659. // We draw in this order:
  660. // Player icons
  661. // Graph bars
  662. // Player names
  663. // Carnage totals (nobody should overlap, so timing is arbitrary)
  664. // Carnage legend
  665. // Bar labels
  666. // This order is largely for back-to-front ordering. Bar labels and player names, since
  667. // they are placed using a text layout helper, will not overlap... but we draw bar labels
  668. // after player names anyway (which takes considerable extra effort, note) so that the names
  669. // have "first dibs" on the coveted low-in-the-widget screen area. Bar labels that want space
  670. // occupied by player names will have to "float up"... which looks nicer than making the names
  671. // float up to give the bar labels space.
  672. // Draw actual content
  673. if(clump_players_by_team) {
  674. // draw player icons in clumps
  675. draw_player_icons_clumped(s);
  676. if(draw_carnage_graph)
  677. draw_bars_clumped(s, theBarInfos);
  678. draw_player_names_clumped(s, theTextLayoutHelper);
  679. }
  680. else {
  681. // Draw all the player icons first, so icons don't obscure names
  682. draw_player_icons_separately(s);
  683. if(draw_carnage_graph)
  684. draw_bars_separately(s, theBarInfos);
  685. draw_player_names_separately(s, theTextLayoutHelper);
  686. }
  687. if(draw_carnage_graph && !draw_scores_not_carnage) {
  688. draw_carnage_totals(s);
  689. if(num_valid_net_rankings >= kUseLegendThreshhold)
  690. draw_carnage_legend(s);
  691. }
  692. if(draw_carnage_graph)
  693. draw_bar_labels(s, theBarInfos, theTextLayoutHelper);
  694. // Reset clipping rectangle
  695. set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
  696. }
  697. void
  698. w_players_in_game2::clear_vector() {
  699. vector<player_entry2>::const_iterator i = player_entries.begin();
  700. vector<player_entry2>::const_iterator end = player_entries.end();
  701. while(i != end) {
  702. // Free the name buffers associated with the elements.
  703. // I don't do this in a player_entry destructor because I'm afraid of freeing the name twice
  704. // (once here, when the vector's entry is destroyed, and another time when thePlayerEntry
  705. // above goes out of scope).
  706. if(i->player_image != NULL)
  707. delete i->player_image;
  708. i++;
  709. }
  710. player_entries.clear();
  711. }
  712. void
  713. w_players_in_game2::draw_bar(SDL_Surface* s, int inCenterX, int inBarColorIndex, int inBarValue, int inMaxValue, bar_info& outBarInfo) const {
  714. if(inBarValue != 0) {
  715. // Check that we'll draw a positive bar - value and max are either both positive or both negative.
  716. if(inBarValue > 0)
  717. assert(inMaxValue > 0);
  718. if(inBarValue < 0)
  719. assert(inMaxValue < 0);
  720. // "- 1" leaves room for shadow style. Leave two line-heights so a kills and deaths at the top of widget resolve
  721. // (thanks to TextLayoutHelper) and still have space to live.
  722. int theMaximumBarHeight = kBarBottomTotalOffset - font->get_line_height() * 2 - 1;
  723. int theBarHeight = (theMaximumBarHeight * inBarValue) / inMaxValue;
  724. SDL_Rect theBarRect;
  725. theBarRect.y = rect.y + kBarBottomTotalOffset - theBarHeight;
  726. theBarRect.h = theBarHeight;
  727. theBarRect.w = kBarWidth;
  728. theBarRect.x = inCenterX - kBarWidth / 2;
  729. RGBColor theBrightestColor;
  730. get_net_color(inBarColorIndex, &theBrightestColor);
  731. RGBColor theMiddleColor;
  732. theMiddleColor.red = (theBrightestColor.red * 7) / 10;
  733. theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
  734. theMiddleColor.green = (theBrightestColor.green * 7) / 10;
  735. RGBColor theDarkestColor;
  736. theDarkestColor.red = (theBrightestColor.red * 2) / 10;
  737. theDarkestColor.blue = (theBrightestColor.blue * 2) / 10;
  738. theDarkestColor.green = (theBrightestColor.green * 2) / 10;
  739. RGBColor* theRGBColor;
  740. uint32 thePixelColor;
  741. // Draw the lightest part
  742. theRGBColor = &theBrightestColor;
  743. thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
  744. SDL_FillRect(s, &theBarRect, thePixelColor);
  745. // Draw the dark part
  746. theRGBColor = &theDarkestColor;
  747. thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
  748. SDL_Rect theDarkRect;
  749. theDarkRect.x = theBarRect.x + theBarRect.w - kBevelSize;
  750. theDarkRect.w = kBevelSize;
  751. theDarkRect.y = theBarRect.y + kBevelSize;
  752. theDarkRect.h = theBarRect.h - kBevelSize;
  753. if(theBarRect.h > kBevelSize)
  754. SDL_FillRect(s, &theDarkRect, thePixelColor);
  755. // Draw the middle part
  756. theRGBColor = &theMiddleColor;
  757. thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
  758. SDL_Rect theMiddleRect;
  759. theMiddleRect.x = theBarRect.x + kBevelSize;
  760. theMiddleRect.w = theBarRect.w - 2 * kBevelSize;
  761. theMiddleRect.y = theBarRect.y + kBevelSize;
  762. theMiddleRect.h = theBarRect.h - kBevelSize;
  763. if(theBarRect.h > kBevelSize)
  764. SDL_FillRect(s, &theMiddleRect, thePixelColor);
  765. // Capture bar information
  766. outBarInfo.center_x = inCenterX;
  767. outBarInfo.top_y = theBarRect.y;
  768. outBarInfo.pixel_color = thePixelColor;
  769. } // if(inBarValue > 0)
  770. } // draw_bar
  771. ////// w_entry_point_selector //////
  772. void
  773. w_entry_point_selector::validateEntryPoint() {
  774. // Get the entry-point flags from the game type.
  775. int theAppropriateLevelTypeFlags = get_entry_point_flags_for_game_type(mGameType);
  776. mEntryPoints.clear();
  777. // OK, get the vector of entry points.
  778. get_entry_points(mEntryPoints, theAppropriateLevelTypeFlags);
  779. if(mEntryPoints.size() <= 0) {
  780. mEntryPoint.level_number = NONE;
  781. strcpy(mEntryPoint.level_name, "(no valid options)");
  782. mCurrentIndex = NONE;
  783. }
  784. else {
  785. unsigned i;
  786. for(i = 0; i < mEntryPoints.size(); i++) {
  787. if(mEntryPoints[i].level_number == mEntryPoint.level_number)
  788. break;
  789. }
  790. if(i == mEntryPoints.size()) {
  791. mEntryPoint = mEntryPoints[0];
  792. mCurrentIndex = 0;
  793. }
  794. else {
  795. mEntryPoint = mEntryPoints[i];
  796. mCurrentIndex = i;
  797. }
  798. }
  799. dirty = true;
  800. }
  801. void
  802. w_entry_point_selector::gotSelectedCallback(void* arg) {
  803. ((w_entry_point_selector*) arg)->gotSelected();
  804. }
  805. void
  806. w_entry_point_selector::gotSelected() {
  807. if(mEntryPoints.size() > 1) {
  808. dialog theDialog;
  809. vertical_placer *placer = new vertical_placer;
  810. placer->dual_add(new w_title("SELECT LEVEL"), theDialog);
  811. placer->add(new w_spacer(), true);
  812. FileSpecifier theFile(environment_preferences->map_file);
  813. char theName[256];
  814. theFile.GetName(theName);
  815. sprintf(temporary, "%s", theName);
  816. placer->dual_add(new w_static_text(temporary), theDialog);
  817. placer->add(new w_spacer(), true);
  818. w_levels* levels_w = new w_levels(mEntryPoints, &theDialog, 480, 16, mCurrentIndex, false);
  819. placer->dual_add(levels_w, theDialog);
  820. placer->add(new w_spacer(), true);
  821. sprintf(temporary, "%lu %s levels available",
  822. mEntryPoints.size(),
  823. TS_GetCString(kNetworkGameTypesStringSetID, mGameType)
  824. );
  825. placer->dual_add(new w_static_text(temporary), theDialog);
  826. placer->add(new w_spacer(), true);
  827. placer->dual_add(new w_button("CANCEL", dialog_cancel, &theDialog), theDialog);
  828. theDialog.set_widget_placer(placer);
  829. clear_screen();
  830. if(theDialog.run() == 0) {
  831. mCurrentIndex = levels_w->get_selection();
  832. mEntryPoint = mEntryPoints[mCurrentIndex];
  833. dirty = true;
  834. }
  835. }
  836. }
  837. void
  838. w_entry_point_selector::event(SDL_Event &e) {
  839. if (e.type == SDL_KEYDOWN) {
  840. if (e.key.keysym.sym == SDLK_LEFT || e.key.keysym.sym == SDLK_RIGHT) {
  841. size_t theNumberOfEntryPoints = mEntryPoints.size();
  842. if(theNumberOfEntryPoints > 1) {
  843. int theDesiredOffset = (e.key.keysym.sym == SDLK_LEFT) ? -1 : 1;
  844. mCurrentIndex = (mCurrentIndex + theNumberOfEntryPoints + theDesiredOffset)
  845. % theNumberOfEntryPoints;
  846. mEntryPoint = mEntryPoints[mCurrentIndex];
  847. dirty = true;
  848. play_dialog_sound(DIALOG_CLICK_SOUND);
  849. }
  850. e.type = SDL_NOEVENT; // Swallow event
  851. }
  852. }
  853. }
  854. #endif // !defined(DISABLE_NETWORKING)