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

/ports/dingux/tags/v3-20101128/Source_Files/Network/network_dialog_widgets_sdl.cpp

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