PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/branches/NIBS/Source_Files/Network/network_dialog_widgets_sdl.cpp

#
C++ | 1340 lines | 777 code | 318 blank | 245 comment | 146 complexity | 4f4de8d2204007b19be586546e36b7f1 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

Large files files are truncated, but you can click here to view the full file

  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. #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_dialog_color(ITEM_DISABLED_COLOR), font, style);
  134. } else {
  135. draw_text(s, /*&((*i)->sslps_name[1]), text_length,*/ theNameBuffer, computed_x, computed_y,
  136. selected ? get_dialog_color(ITEM_ACTIVE_COLOR) : get_dialog_color(ITEM_COLOR), font, style);
  137. }
  138. }
  139. ////// w_chat_history //////
  140. void
  141. w_chat_history::append_chat_entry(const player_info* player, const char* chat_text) {
  142. // Player name C-string, used to make copies for individual entries below.
  143. char* player_name_master_copy;
  144. uint32 player_pixel_color;
  145. uint32 team_pixel_color;
  146. if(player != NULL) {
  147. // Note: player_name_pascal and player_name_master_copy are really the same buffer.
  148. unsigned char* player_name_pascal = pstrdup(player->name);
  149. player_name_master_copy = a1_p2cstr(player_name_pascal);
  150. player_pixel_color = get_dialog_player_color(player->color);
  151. team_pixel_color = get_dialog_player_color(player->team);
  152. }
  153. else {
  154. player_name_master_copy = "Aleph One";
  155. player_pixel_color = get_dialog_color(MESSAGE_COLOR);
  156. team_pixel_color = get_dialog_color(MESSAGE_COLOR);
  157. }
  158. append_chat_entry(player_name_master_copy, player_pixel_color, team_pixel_color, chat_text);
  159. // If we allocated our own space for the master name copy, free it.
  160. if(player != NULL)
  161. free(player_name_master_copy);
  162. }
  163. void
  164. w_chat_history::append_chat_entry(const char* playerName, uint32 player_pixel_color, uint32 team_pixel_color, const char* chat_text)
  165. {
  166. char* text_buf = strdup(chat_text); // Temporary buf so we can mangle around in it. free()d at end.
  167. char* whole_text_buf = text_buf; // we modify the text_buf pointer; need to keep a pointer for free.
  168. // Fill in the "template" entry that will be copied into the entries vector from time to time below.
  169. chat_entry entry;
  170. entry.player_pixel_color = player_pixel_color;
  171. entry.team_pixel_color = team_pixel_color;
  172. entry.chat_text = NULL; // will be overwritten below as we word-wrap lines
  173. // See how much room the player's name will take up
  174. char theNameBuffer[40];
  175. // Will be safe no matter the player's name length (we'll truncate it if necessary)
  176. sprintf(theNameBuffer, "%.37s: ", playerName);
  177. int name_width = text_width(theNameBuffer, font, style);
  178. int available_width = rect.w - name_width - get_dialog_space(LIST_L_SPACE) - get_dialog_space(LIST_R_SPACE);
  179. // Badly-formed dialog (or widget, anyway) if there's not enough room to print a player's name.
  180. assert(available_width > 0);
  181. // Here comes my effort at a word-wrapping algorithm. It's probably not as smart
  182. // or as general as it could be (I'd probably write it using STL iterators if I did
  183. // it again, but someone's probably already done that anyway)... it should probably
  184. // be moved out into its own class/function/whatever as it's more generally
  185. // applicable than just to this widget.
  186. // For looping
  187. size_t characters_left;
  188. size_t characters_that_fit;
  189. size_t first_char_next_line;
  190. size_t last_char_this_line;
  191. // Intentional assignment - loop while there are characters to consider.
  192. while((characters_left = strlen(text_buf)) > 0) {
  193. // Find out how many will actually fit in the space given
  194. characters_that_fit = trunc_text(text_buf, available_width, font, style);
  195. // This is probably unnecessary
  196. if(characters_that_fit > characters_left)
  197. characters_that_fit = characters_left;
  198. // Start at the first character that doesn't fit
  199. first_char_next_line = characters_that_fit;
  200. // How we move our pointers depends on what we're looking at now.
  201. switch(text_buf[first_char_next_line]) {
  202. case '\0': // End of buffer, everything fits on this line.
  203. // Last char of this line should be last char of string (or earlier, if whitespace)
  204. last_char_this_line = first_char_next_line - 1;
  205. // First char of next line is the terminating null. Loop will exit.
  206. break;
  207. case ' ': // Middle of buffer, in a bank of whitespace (maybe just a single space)
  208. // Last char of this line should be here (or earlier, if whitespace)
  209. last_char_this_line = first_char_next_line;
  210. // Next line should start after whitespace
  211. do {
  212. first_char_next_line++;
  213. } while(text_buf[first_char_next_line] == ' ' && text_buf[first_char_next_line] != '\0');
  214. // First char of the next line is either the terminating null, OR the next non-whitespace.
  215. break;
  216. default: // Middle of buffer, in a bank of non-whitespace
  217. // We operate on last_char_this_line so we can come back to first_char_next_line if
  218. // there are no whitespace characters.
  219. last_char_this_line = first_char_next_line;
  220. // Find first whitespace (if any) going backwards
  221. do {
  222. last_char_this_line--;
  223. } while(text_buf[last_char_this_line] != ' ' && last_char_this_line >= 0);
  224. // Now we either point at the last whitespace, or are at -1. Either way, go forward one.
  225. last_char_this_line++;
  226. // Maybe we found the last piece of whitespace before the word that didn't fit, and now
  227. // we point at that word.
  228. if(last_char_this_line > 0) {
  229. first_char_next_line = last_char_this_line;
  230. }
  231. // This line should end immediately before the next one begins. That's
  232. // either at a space or the last char that would fit.
  233. last_char_this_line = first_char_next_line - 1;
  234. break;
  235. }
  236. // No matter what, walk backward discarding whitespace.
  237. while(text_buf[last_char_this_line] == ' ' && last_char_this_line >= 0)
  238. last_char_this_line--;
  239. // Either we are at -1 or we hit a non-whitespace; in either case, go forward one.
  240. // If this feels like a bug to you, read on. :)
  241. last_char_this_line++;
  242. // Now we are at one position *past* the last character we want to print.
  243. // If the position is 0, there were no non-whitespace characters. Else,
  244. // stick in a null to clip off the line, and add an entry.
  245. if(last_char_this_line > 0) {
  246. char saved_char = text_buf[last_char_this_line];
  247. text_buf[last_char_this_line] = '\0';
  248. // Copy the string and name for longer life. Destructor will free() them.
  249. entry.chat_text = strdup(text_buf);
  250. entry.player_name = strdup(playerName);
  251. append_chat_entry(entry);
  252. text_buf[last_char_this_line] = saved_char;
  253. }
  254. text_buf = &text_buf[first_char_next_line];
  255. }
  256. // Done with the temporary buffer.
  257. free(whole_text_buf);
  258. }
  259. void
  260. w_chat_history::append_chat_entry(const chat_entry& inEntry) {
  261. chat_lines.push_back(inEntry);
  262. num_items = chat_lines.size();
  263. new_items();
  264. if(num_items > shown_items) {
  265. set_top_item(num_items - shown_items);
  266. }
  267. }
  268. void
  269. w_chat_history::draw_item(vector<chat_entry>::const_iterator i, SDL_Surface* s, int16 x, int16 y, uint16 width, bool selected) const {
  270. int computed_y = y + font->get_ascent();
  271. char theNameBuffer[40];
  272. sprintf(theNameBuffer, "%.37s: ", i->player_name);
  273. int name_width = text_width(theNameBuffer, font, style);
  274. // constrain drawing to window's width
  275. // for some reason, the rectangle is specified in an odd order: y1, x1, y2, x2
  276. // - note that with properly wrapped text, this should not be needed...
  277. //set_drawing_clip_rectangle(SHRT_MIN, 0, SHRT_MAX, width);
  278. draw_text(s, theNameBuffer, x, computed_y, i->player_pixel_color, font, style);
  279. draw_text(s, i->chat_text, x + name_width, computed_y, i->team_pixel_color, font, style);
  280. //set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
  281. }
  282. w_chat_history::~w_chat_history() {
  283. vector<chat_entry>::iterator i = chat_lines.begin();
  284. vector<chat_entry>::iterator end = chat_lines.end();
  285. while(i != end) {
  286. // Free the name buffers associated with the elements.
  287. // I don't do this in a chat_entry destructor because I'm afraid of freeing the string twice
  288. // (once here, when the vector's entry is destroyed, and another time when the stack-allocated
  289. // template goes out of scope).
  290. if(i->chat_text != NULL)
  291. free(i->chat_text);
  292. if(i->player_name != NULL)
  293. free(i->player_name);
  294. i++;
  295. }
  296. chat_lines.clear();
  297. }
  298. ////// w_players_in_game2 //////
  299. // I guess these should be computed more dynamically, but it wasn't obvious the best way to do that.
  300. // These values work well for the standard player shapes, anyway.
  301. enum {
  302. kWPIG2Width = 600, // widget width
  303. kWPIG2Height = 142, // widget height (actual height will differ if postgame_layout)
  304. kMaxHeadroom = 53, // height above player origin (approx. navel) of tallest player shape
  305. kNameOffset = 80, // how far below player origin baseline of player's name should appear
  306. kNumNameOffsets = MAXIMUM_NUMBER_OF_PLAYERS, // used to resolve overlapping names
  307. kNameMargin = 6, // names overlap if their edges are fewer than this many pixels apart
  308. kNormalPlayerOffset = kMaxHeadroom,
  309. kNormalNameTotalOffset = kNormalPlayerOffset + kNameOffset,
  310. // kPostgameTopMargin = 70, // how much extra space is at the top of widget in postgame layout
  311. kPostgameTopMargin = 190, // For postgame layout without chat window, we can use a lot more space. (use 70 to coexist with full chat UI)
  312. kPostgameBottomMargin = 6, // how much extra space is at the bottom of widget in postgame layout
  313. kBarBottomOffset = 80, // how far below player origin score/kill bars should start
  314. kBarWidth = 10, // how wide a kill/score bar should be
  315. kBarOffsetX = 20, // how much to offset a bar so it won't draw directly on a player
  316. kBevelSize = 2, // how much "depth effect" (in pixels around the border) bars have
  317. kUseLegendThreshhold = 5, // with this many players or more, use legend for kills/deaths rather than print at bar labels
  318. kPostgamePlayerOffset = kPostgameTopMargin + kMaxHeadroom,
  319. kPostgameNameTotalOffset = kPostgamePlayerOffset + kNameOffset,
  320. kBarBottomTotalOffset = kPostgamePlayerOffset + kBarBottomOffset,
  321. kPostgameHeight = kPostgameTopMargin + kWPIG2Height + kPostgameBottomMargin
  322. };
  323. /*
  324. // These can't see postgame_layout. Duh. And the idea here was to avoid having the constants above
  325. // in a header file (as would be needed for making inline methods) where they would force extra
  326. // recompilation... burrito. Macros it is.
  327. static inline int
  328. get_player_y_offset() { return postgame_layout ? kPostgamePlayerOffset : kNormalPlayerOffset; }
  329. static inline int
  330. get_name_y_offset() { return postgame_layout ? kPostgameNameTotalOffset : kNormalNameTotalOffset; }
  331. */
  332. #define get_player_y_offset() (postgame_layout ? kPostgamePlayerOffset : kNormalPlayerOffset)
  333. #define get_name_y_offset() (postgame_layout ? kPostgameNameTotalOffset : kNormalNameTotalOffset)
  334. // Here I divide each piece of space into N pieces (where N is the number of things to draw)
  335. // each item is drawn in the center of its space. This pitches them a little more widely than
  336. // is used in the separately-drawn strategy.
  337. // The computation used is (I from 0 to N-1, W is width) for the center:
  338. // ((I + .5) / N) * W
  339. // == WI + .5W / N
  340. // == W*(2I + 1) / 2N
  341. static inline int
  342. get_wide_spaced_center_offset(int left_x, int available_width, size_t index, size_t num_items) {
  343. return left_x + (((2 * (int)index + 1) * available_width) / (2 * (int)num_items));
  344. }
  345. // for the left:
  346. // I/N * W
  347. // == WI/N
  348. static inline int
  349. get_wide_spaced_left_offset(int left_x, int available_width, size_t index, size_t num_items) {
  350. return left_x + (((int)index * available_width) / (int)num_items);
  351. }
  352. // width is easy...
  353. // note though that the actual distances between left_offsets may vary slightly from this width due to rounding.
  354. static inline int
  355. get_wide_spaced_width(int available_width, size_t num_items) {
  356. return available_width / (int)num_items;
  357. }
  358. // 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....
  359. // Doing (I * W) / N rather than the more natural (I/N) * W may give more accurate results with integer math.
  360. static inline int
  361. get_close_spaced_center_offset(int left_x, int available_width, size_t index, size_t num_items) {
  362. return left_x + ((((int)index + 1) * available_width) / ((int)num_items + 1));
  363. }
  364. static inline int
  365. get_close_spaced_width(int available_width, size_t num_items) {
  366. return available_width / ((int)num_items + 1);
  367. }
  368. w_players_in_game2::w_players_in_game2(bool inPostgameLayout) :
  369. widget(MESSAGE_FONT), displaying_actual_information(false), postgame_layout(inPostgameLayout),
  370. draw_carnage_graph(false), num_valid_net_rankings(0), selected_player(NONE),
  371. clump_players_by_team(false), draw_scores_not_carnage(false)
  372. {
  373. rect.w = kWPIG2Width;
  374. rect.h = postgame_layout ? kPostgameHeight : kWPIG2Height;
  375. }
  376. w_players_in_game2::~w_players_in_game2() {
  377. clear_vector();
  378. }
  379. void
  380. w_players_in_game2::update_display(bool inFromDynamicWorld /* default=false */) {
  381. // Start over - wipe out our local player-storage
  382. clear_vector();
  383. // Wipe out references to players through teams
  384. for(int i = 0; i < NUMBER_OF_TEAM_COLORS; i++)
  385. players_on_team[i].clear();
  386. // Find the number of players
  387. int num_players;
  388. if(inFromDynamicWorld)
  389. num_players = dynamic_world->player_count;
  390. else
  391. num_players = displaying_actual_information ? NetGetNumberOfPlayers() : 0;
  392. // Fill in the entries
  393. for(int i = 0; i < num_players; i++) {
  394. player_entry2 thePlayerEntry;
  395. int thePlayerTeam;
  396. int thePlayerColor;
  397. if(inFromDynamicWorld) {
  398. // Get player information from dynamic_world
  399. player_data* thePlayerData = get_player_data(i);
  400. // Copy the player name. We will store it as a cstring...
  401. strncpy(thePlayerEntry.player_name, thePlayerData->name, MAXIMUM_PLAYER_NAME_LENGTH + 1);
  402. // Look up colors
  403. thePlayerTeam = thePlayerData->team;
  404. thePlayerColor = thePlayerData->color;
  405. }
  406. else {
  407. // Get player information from topology
  408. player_info* thePlayerInfo = (player_info*)NetGetPlayerData(i);
  409. // Alias the player entry's name field as a pstring
  410. unsigned char* thePlayerEntryNameP = (unsigned char*) thePlayerEntry.player_name;
  411. // Copy the player name. We will store it as a cstring...
  412. pstrncpy(thePlayerEntryNameP, thePlayerInfo->name, MAXIMUM_PLAYER_NAME_LENGTH + 1);
  413. // In-place conversion.
  414. a1_p2cstr(thePlayerEntryNameP);
  415. // Look up colors
  416. thePlayerTeam = thePlayerInfo->team;
  417. thePlayerColor = thePlayerInfo->color;
  418. }
  419. // Set the size of the text
  420. thePlayerEntry.name_width = text_width(thePlayerEntry.player_name, font, style);
  421. // Get the pixel-color for the player's team (for drawing the name)
  422. thePlayerEntry.name_pixel_color = get_dialog_player_color(thePlayerTeam);
  423. // Set up a player image for the player (funfun)
  424. thePlayerEntry.player_image = new PlayerImage;
  425. thePlayerEntry.player_image->setRandomFlatteringView();
  426. thePlayerEntry.player_image->setPlayerColor(thePlayerColor);
  427. thePlayerEntry.player_image->setTeamColor(thePlayerTeam);
  428. // Add the player to our local storage area
  429. player_entries.push_back(thePlayerEntry);
  430. // Add a reference to the player through his team color
  431. players_on_team[thePlayerTeam].push_back(i);
  432. }
  433. dirty = true;
  434. }
  435. #if 0
  436. // this is for testing
  437. static const char* sTestingNames[] = {
  438. "Doctor Burrito",
  439. "Carnage Asada",
  440. "Bongo Bob",
  441. "The Napalm Man",
  442. "The Big Lebowski",
  443. "lala",
  444. "Prof. Windsurf",
  445. "<<<-ZED-<<<"
  446. };
  447. void
  448. w_players_in_game2::click(int, int) {
  449. player_entry2 thePlayerEntry;
  450. // make up a name
  451. /* int theNameLength = (local_random() % MAXIMUM_PLAYER_NAME_LENGTH) + 1;
  452. for(int i = 0; i < theNameLength; i++)
  453. thePlayerEntry.player_name[i] = 'a' + (local_random() % ('z' - 'a'));
  454. thePlayerEntry.player_name[theNameLength] = '\0';
  455. // strcpy(thePlayerEntry.player_name, "The Big Lebowski");
  456. */
  457. strcpy(thePlayerEntry.player_name, sTestingNames[local_random() % 8]);
  458. // Set the size of the text
  459. thePlayerEntry.name_width = text_width(thePlayerEntry.player_name, font, style);
  460. // Make up a team-color
  461. int theTeamColor = local_random() % 8;
  462. // Get the pixel-color for the player's team (for drawing the name)
  463. thePlayerEntry.name_pixel_color = get_dialog_player_color(theTeamColor);
  464. // Set up a player image for the player (funfun)
  465. thePlayerEntry.player_image = new PlayerImage;
  466. thePlayerEntry.player_image->setRandomFlatteringView();
  467. thePlayerEntry.player_image->setTeamColor(theTeamColor);
  468. player_entries.push_back(thePlayerEntry);
  469. dirty = true;
  470. }
  471. #else // NOT 0
  472. void
  473. w_players_in_game2::click(int x, int) {
  474. if(draw_carnage_graph) {
  475. if(clump_players_by_team) {
  476. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  477. if(ABS(x - get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings))
  478. < (get_wide_spaced_width(rect.w, num_valid_net_rankings) / 2))
  479. {
  480. if(element_clicked_callback != NULL)
  481. element_clicked_callback(this, clump_players_by_team, draw_carnage_graph, draw_scores_not_carnage,
  482. i, net_rankings[i].color);
  483. break;
  484. }
  485. }
  486. }
  487. else {
  488. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  489. if(ABS(x - get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings))
  490. < (get_close_spaced_width(rect.w, num_valid_net_rankings) / 2))
  491. {
  492. if(element_clicked_callback != NULL)
  493. element_clicked_callback(this, clump_players_by_team, draw_carnage_graph, draw_scores_not_carnage,
  494. i, net_rankings[i].player_index);
  495. break;
  496. }
  497. }
  498. }
  499. } // draw_carnage_graph
  500. }
  501. #endif // NOT 0
  502. // enable carnage reporting mode and set the data needed to draw a graph.
  503. void
  504. w_players_in_game2::set_graph_data(const net_rank* inRankings, int inNumRankings, int inSelectedPlayer,
  505. bool inClumpPlayersByTeam, bool inDrawScoresNotCarnage)
  506. {
  507. draw_carnage_graph = true;
  508. num_valid_net_rankings = inNumRankings;
  509. selected_player = inSelectedPlayer;
  510. clump_players_by_team = inClumpPlayersByTeam;
  511. draw_scores_not_carnage = inDrawScoresNotCarnage;
  512. memcpy(net_rankings, inRankings, inNumRankings * sizeof(net_rank));
  513. dirty = true;
  514. }
  515. void
  516. w_players_in_game2::draw_player_icon(SDL_Surface* s, size_t rank_index, int center_x) const {
  517. // Note, player images will not be re-fetched unless the brightness has *changed* since last draw.
  518. PlayerImage* theImage = player_entries[net_rankings[rank_index].player_index].player_image;
  519. if(selected_player != NONE && selected_player != rank_index)
  520. theImage->setBrightness(.4f);
  521. else
  522. theImage->setBrightness(1.0f);
  523. theImage->drawAt(s, center_x, rect.y + get_player_y_offset());
  524. }
  525. void
  526. w_players_in_game2::draw_player_icons_separately(SDL_Surface* s) const {
  527. if(draw_carnage_graph) {
  528. // Draw in sorted order (according to net_rankings)
  529. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  530. int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  531. draw_player_icon(s, i, center_x);
  532. }
  533. }
  534. else {
  535. // Draw in "natural order" (according to topology)
  536. size_t theNumPlayers = player_entries.size();
  537. for(size_t i = 0; i < theNumPlayers; i++) {
  538. int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, theNumPlayers);
  539. player_entries[i].player_image->drawAt(s, center_x, rect.y + get_player_y_offset());
  540. }
  541. }
  542. } // draw_player_icons_separately
  543. void
  544. w_players_in_game2::draw_player_icons_clumped(SDL_Surface* s) const {
  545. assert(draw_carnage_graph);
  546. int width_per_team = get_wide_spaced_width(rect.w, num_valid_net_rankings);
  547. // Walk through teams, drawing each batch.
  548. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  549. int team_left_x = get_wide_spaced_left_offset(rect.x, rect.w, i, num_valid_net_rankings);
  550. size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
  551. assert(theNumberOfPlayersOnThisTeam > 0);
  552. // Walk through players on a team to draw a batch.
  553. for(size_t j = 0; j < theNumberOfPlayersOnThisTeam; j++) {
  554. int player_center_x = get_close_spaced_center_offset(team_left_x, width_per_team, j, theNumberOfPlayersOnThisTeam);
  555. // Note, player images will not be re-fetched unless the brightness has *changed* since last draw.
  556. // Though Marathon does not let one view team vs team carnage (just total team carnage), I'm leaving
  557. // the highlighting stuff here in case team view is later added.
  558. PlayerImage* theImage = player_entries[players_on_team[net_rankings[i].color][j]].player_image;
  559. if(selected_player != NONE && selected_player != i)
  560. theImage->setBrightness(.4f);
  561. else
  562. theImage->setBrightness(1.0f);
  563. theImage->drawAt(s, player_center_x, rect.y + get_player_y_offset());
  564. } // players
  565. } // teams
  566. } // draw_player_icons_clumped
  567. void
  568. w_players_in_game2::draw_player_names_separately(SDL_Surface* s, TextLayoutHelper& ioTextLayoutHelper) const {
  569. // Now let's draw the names. Let's take care to offset names vertically if they would
  570. // overlap (or come too close as defined by kNameMargin), so it's more readable.
  571. size_t theNumPlayers = draw_carnage_graph ? num_valid_net_rankings : player_entries.size();
  572. for(size_t i = 0; i < theNumPlayers; i++) {
  573. int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, theNumPlayers);
  574. const player_entry2* theEntry = draw_carnage_graph ? &player_entries[net_rankings[i].player_index] : &player_entries[i];
  575. int name_x = center_x - (theEntry->name_width / 2);
  576. int name_y = rect.y + get_name_y_offset();
  577. // Find a suitable vertical offset
  578. name_y = ioTextLayoutHelper.reserveSpaceFor(name_x - kNameMargin / 2, theEntry->name_width + kNameMargin, name_y, font->get_line_height());
  579. draw_text(s, theEntry->player_name, name_x, name_y,
  580. get_dialog_color(LABEL_OUTLINE_COLOR), font, style | styleOutline);
  581. draw_text(s, theEntry->player_name, name_x, name_y,
  582. theEntry->name_pixel_color, font, style);
  583. }
  584. }
  585. void
  586. w_players_in_game2::draw_player_names_clumped(SDL_Surface* s, TextLayoutHelper& ioTextLayoutHelper) const {
  587. // Now let's draw the names. Let's take care to offset names vertically if they would
  588. // overlap (or come too close as defined by kNameMargin), so it's more readable.
  589. // Walk through teams, drawing each batch.
  590. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  591. int team_center_x = get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  592. size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
  593. assert(theNumberOfPlayersOnThisTeam > 0);
  594. // Walk through players on a team to draw a batch.
  595. for(size_t j = 0; j < theNumberOfPlayersOnThisTeam; j++) {
  596. const player_entry2* theEntry = &(player_entries[players_on_team[net_rankings[i].color][j]]);
  597. int name_x = team_center_x - (theEntry->name_width / 2);
  598. int name_y = rect.y + get_name_y_offset();
  599. // Find a suitable vertical offset
  600. name_y = ioTextLayoutHelper.reserveSpaceFor(name_x - kNameMargin/2, theEntry->name_width + kNameMargin,
  601. name_y, font->get_line_height());
  602. draw_text(s, theEntry->player_name, name_x, name_y,
  603. get_dialog_color(LABEL_OUTLINE_COLOR), font, style | styleOutline);
  604. draw_text(s, theEntry->player_name, name_x, name_y,
  605. theEntry->name_pixel_color, font, style);
  606. }
  607. }
  608. }
  609. int
  610. w_players_in_game2::find_maximum_bar_value() const {
  611. int theMaxValue = INT_MIN;
  612. // We track min also to handle games with negative scores.
  613. int theMinValue = INT_MAX;
  614. if(selected_player != NONE)
  615. // This way, all player vs player graphs use the same scale.
  616. theMaxValue = calculate_max_kills(num_valid_net_rankings);
  617. else {
  618. // Note this does the right thing for suicide bars as well.
  619. if(draw_scores_not_carnage) {
  620. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  621. if(net_rankings[i].game_ranking > theMaxValue)
  622. theMaxValue = net_rankings[i].game_ranking;
  623. if(net_rankings[i].game_ranking < theMinValue)
  624. theMinValue = net_rankings[i].game_ranking;
  625. }
  626. } else {
  627. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  628. if(net_rankings[i].kills > theMaxValue)
  629. theMaxValue = net_rankings[i].kills;
  630. if(net_rankings[i].deaths > theMaxValue)
  631. theMaxValue = net_rankings[i].deaths;
  632. }
  633. }
  634. }
  635. // If all values were nonpositive, and we had at least one negative, we
  636. // return the (negative) value furthest from 0.
  637. // The Mac version seems to do nothing of the sort - how can it possibly
  638. // display correct bars for games with negative scores like "Tag"??
  639. if(theMaxValue <= 0 && theMinValue < 0)
  640. theMaxValue = theMinValue;
  641. return theMaxValue;
  642. }
  643. struct bar_info {
  644. int center_x;
  645. int top_y;
  646. uint32 pixel_color;
  647. string label_text;
  648. };
  649. void
  650. 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 {
  651. // Draw score bar
  652. if(draw_scores_not_carnage) {
  653. bar_info theBarInfo;
  654. int theScore = net_rankings[rank_index].game_ranking;
  655. calculate_ranking_text_for_post_game(temporary, theScore);
  656. theBarInfo.label_text = temporary; // this makes a copy
  657. draw_bar(s, center_x, _score_color, theScore, maximum_value, theBarInfo);
  658. // Don't draw a "0" score label
  659. if(theScore != 0)
  660. outBarInfos.push_back(theBarInfo);
  661. }
  662. else {
  663. // Draw carnage bar(s)
  664. if(rank_index == selected_player) {
  665. // Draw suicides/friendly-fires
  666. bar_info theBarInfo;
  667. char* theSuicidesFormat = TS_GetCString(strNET_STATS_STRINGS, strSUICIDES_STRING);
  668. int theNumberOfSuicides = net_rankings[rank_index].kills;
  669. sprintf(temporary, theSuicidesFormat, theNumberOfSuicides);
  670. theBarInfo.label_text = temporary; // this makes a copy
  671. draw_bar(s, center_x, _suicide_color, theNumberOfSuicides, maximum_value, theBarInfo);
  672. // Don't push a "0" label.
  673. if(theNumberOfSuicides > 0)
  674. outBarInfos.push_back(theBarInfo);
  675. }
  676. else {
  677. // Draw kills and deaths
  678. int theNumKills = net_rankings[rank_index].kills;
  679. int theNumDeaths = net_rankings[rank_index].deaths;
  680. // Get strings for labelling
  681. const char* theKillsFormat;
  682. const char* theDeathsFormat;
  683. char theKillsString[32];
  684. char theDeathsString[32];
  685. // If more than threshhold bar-pairs to draw, use short form with legend rather than normal (long) form.
  686. theKillsFormat = num_valid_net_rankings >= kUseLegendThreshhold ? "%d" : TS_GetCString(strNET_STATS_STRINGS, strKILLS_STRING);
  687. theDeathsFormat = num_valid_net_rankings >= kUseLegendThreshhold ? "%d" : TS_GetCString(strNET_STATS_STRINGS, strDEATHS_STRING);
  688. // Construct labels
  689. sprintf(theKillsString, theKillsFormat, theNumKills);
  690. sprintf(theDeathsString, theDeathsFormat, theNumDeaths);
  691. // Set up bar_infos
  692. bar_info theKillsBarInfo;
  693. bar_info theDeathsBarInfo;
  694. // Copy strings into bar_infos
  695. theKillsBarInfo.label_text = theKillsString;
  696. theDeathsBarInfo.label_text = theDeathsString;
  697. // Draw shorter bar in front - looks nicer
  698. // If equal, draw kills in front
  699. // Put shorter bar_info in vector first so its label doesn't "leapfrog" the taller bar label in case of conflict.
  700. // Don't put "0"s into the vector.
  701. if(theNumKills > theNumDeaths) {
  702. // Deaths bar is shorter - draw it last
  703. draw_bar(s, center_x - kBarWidth / 3, _kill_color, theNumKills, maximum_value, theKillsBarInfo);
  704. draw_bar(s, center_x + kBarWidth / 3, _death_color, theNumDeaths, maximum_value, theDeathsBarInfo);
  705. if(theNumDeaths > 0)
  706. outBarInfos.push_back(theDeathsBarInfo);
  707. if(theNumKills > 0)
  708. outBarInfos.push_back(theKillsBarInfo);
  709. }
  710. else {
  711. // Kills bar is shorter or equal - draw it last
  712. draw_bar(s, center_x + kBarWidth / 3, _death_color, theNumDeaths, maximum_value, theDeathsBarInfo);
  713. draw_bar(s, center_x - kBarWidth / 3, _kill_color, theNumKills, maximum_value, theKillsBarInfo);
  714. if(theNumKills > 0)
  715. outBarInfos.push_back(theKillsBarInfo);
  716. if(theNumDeaths > 0)
  717. outBarInfos.push_back(theDeathsBarInfo);
  718. } // kills and deaths (not suicides)
  719. } // carnage bars
  720. } // !draw_scores_not_carnage (i.e. draw carnage)
  721. } // draw_bar_or_bars
  722. void
  723. w_players_in_game2::draw_bars_separately(SDL_Surface* s, vector<bar_info>& outBarInfos) const {
  724. // Find the largest value we'll be drawing, so we know how to scale our bars.
  725. int theMaxValue = find_maximum_bar_value();
  726. // Draw the bars.
  727. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  728. int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  729. draw_bar_or_bars(s, i, center_x + kBarOffsetX, theMaxValue, outBarInfos);
  730. } // walk through rankings
  731. } // draw_bars_separately
  732. void
  733. w_players_in_game2::draw_bars_clumped(SDL_Surface* s, vector<bar_info>& outBarInfos) const {
  734. // Find the largest value we'll be drawing, so we know how to scale our bars.
  735. int theMaxValue = find_maximum_bar_value();
  736. // Walk through teams, drawing each batch.
  737. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  738. int team_center_x = (int)(rect.x + ((2*i + 1) * rect.w) / (2 * num_valid_net_rankings));
  739. size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
  740. assert(theNumberOfPlayersOnThisTeam > 0);
  741. // We will offset if we would draw on top of a player (i.e. if num players is odd), else
  742. // we will draw right smack in the middle.
  743. int center_x = team_center_x;
  744. if(theNumberOfPlayersOnThisTeam % 2 == 1)
  745. center_x += kBarOffsetX;
  746. draw_bar_or_bars(s, i, center_x, theMaxValue, outBarInfos);
  747. } // walk through rankings
  748. } // draw_bars_clumped
  749. void
  750. w_players_in_game2::draw_carnage_totals(SDL_Surface* s) const {
  751. for(size_t i = 0; i < num_valid_net_rankings; i++) {
  752. int center_x;
  753. if(clump_players_by_team)
  754. center_x = get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  755. else
  756. center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
  757. // Draw carnage score for player/team (list -N for N suicides)
  758. int thePlayerCarnageScore = (selected_player == i) ? -net_rankings[i].kills : net_rankings[i].kills - net_rankings[i].deaths;
  759. if(thePlayerCarnageScore == 0)
  760. strcpy(temporary, "0");
  761. else
  762. sprintf(temporary, "%+d", thePlayerCarnageScore);
  763. uint16 theBiggerFontStyle = 0;
  764. const sdl_font_info* theBiggerFont = get_dialog_font(LABEL_FONT, theBiggerFontStyle);
  765. int theStringCenter = center_x - (text_width(temporary, theBiggerFont, theBiggerFontStyle) / 2);
  766. draw_text(s, temporary, theStringCenter, rect.y + rect.h - 1, SDL_MapRGB(s->format, 0, 0, 0),
  767. theBiggerFont, theBiggerFontStyle | styleOutline);
  768. draw_text(s, temporary, theStringCenter, rect.y + rect.h - 1, SDL_MapRGB(s->format, 0xff, 0xff, 0xff),
  769. theBiggerFont, theBiggerFontStyle);
  770. } // walk through rankings
  771. } // draw_carnage_totals
  772. void
  773. w_players_in_game2::draw_carnage_legend(SDL_Surface* s) const {
  774. RGBColor theBrightestColor;
  775. get_net_color(_kill_color, &theBrightestColor);
  776. RGBColor theMiddleColor;
  777. theMiddleColor.red = (theBrightestColor.red * 7) / 10;
  778. theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
  779. theMiddleColor.green = (theBrightestColor.green * 7) / 10;
  780. uint32 thePixelColor = SDL_MapRGB(s->format, theMiddleColor.red >> 8, theMiddleColor.green >> 8, theMiddleColor.blue >> 8);
  781. draw_text(s, TS_GetCString(strNET_STATS_STRINGS, strKILLS_LEGEND), rect.x, rect.y + font->get_line_height(),
  782. thePixelColor, font, style);
  783. get_net_color(_death_color, &theBrightestColor);
  784. theMiddleColor.red = (theBrightestColor.red * 7) / 10;
  785. theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
  786. theMiddleColor.green = (theBrightestColor.green * 7) / 10;
  787. thePixelColor = SDL_MapRGB(s->format, theMiddleColor.red >> 8, theMiddleColor.green >> 8, theMiddleColor.blue >> 8);
  788. draw_text(s, TS_GetCString(strNET_STATS_STRINGS, strDEATHS_LEGEND), rect.x, rect.y + 2 * font->get_line_height(),
  789. thePixelColor, font, style);
  790. }
  791. void
  792. w_players_in_game2::draw_bar_labels(SDL_Surface* s, const vector<bar_info>& inBarInfos, TextLayoutHelper& ioTextLayoutHelper) const {
  793. size_t theNumberOfLabels = inBarInfos.size();
  794. for(size_t i = 0; i < theNumberOfLabels; i++) {
  795. const bar_info& theBarInfo = inBarInfos[i];
  796. int theStringWidth = text_width(theBarInfo.label_text.c_str(), font, style);
  797. int theTextX = theBarInfo.center_x - theStringWidth / 2;
  798. int theBestY = ioTextLayoutHelper.reserveSpaceFor(theTextX - kNameMargin/2,
  799. theStringWidth + kNameMargin, theBarInfo.top_y - 1, font->get_line_height());
  800. draw_text(s, theBarInfo.label_text.c_str(), theTextX, theBestY, get_dialog_color(LABEL_OUTLINE_COLOR), font, style | styleOutline);
  801. draw_text(s, theBarInfo.label_text.c_str(), theTextX, theBestY, theBarInfo.pixel_color, font, style);
  802. }
  803. } // draw_bar_labels
  804. void
  805. w_players_in_game2::draw(SDL_Surface* s) const {
  806. // printf("widget top is %d, bottom is %d\n", rect.y, rect.y + rect.h);
  807. // Set clip rectangle so we don't color outside the lines
  808. set_drawing_clip_rectangle(rect.y, rect.x, rect.y + rect.h, rect.x + rect.w);
  809. // Did some tests here - it seems that text drawing is clipped by this rectangle, but rect-filling
  810. // and blitting are not. (at least, on the Mac OS X version of SDL. actually, in Win 98 too.)
  811. // This means that little tiny chunks of pistol fire, feet, etc. can stick around if they are drawn
  812. // outside the widget (because they won't be cleared away when the widget is redrawn). I'm surrounding
  813. // that with a "Somebody Else's Problem" field for the time being.
  814. // set_drawing_clip_rectangle(100, 300, 200, 400);
  815. // printf("clipped at <%d %d %d %d>\n", rect.y, rect.x, rect.y + rect.h, rect.x + rect.w);
  816. // theTextLayoutHelper exists for the duration of the draw operation
  817. // helps us draw bits of text that do not overlap one another.
  818. TextLayoutHelper theTextLayoutHelper;
  819. // theBarInfos exists for the duration of the draw operation
  820. // helps us plan our bar label placement early (at draw_bar time)
  821. // but draw them late (at draw_bar_labels time).
  822. vector<bar_info> theBarInfos;
  823. // We draw in this order:
  824. // Player icons
  825. // Graph bars
  826. // Player names
  827. // Carnage totals (nobody should overlap, so timing is arbitrary)
  828. // Carnage legend
  829. // Bar labels
  830. // This order is largely for back-to-front ordering. Bar labels and player names, since
  831. // they are placed using a text layout helper, will not overlap... but we draw bar labels
  832. // after player names anyway (which takes considerable extra effort, note) so that the names
  833. // have "first dibs" on the coveted low-in-the-widget screen area. Bar labels that want space
  834. // occupied by player names will have to "float up"... which looks nicer than making the names
  835. // float up to give the bar labels space.
  836. // Draw actual content
  837. if(clump_players_by_team) {
  838. // draw player icons in clumps
  839. draw_player_icons_clumped(s);
  840. if(draw_carnage_graph)
  841. draw_bars_clumped(s, theBarInfos);
  842. draw_player_names_clumped(s, theTextLayoutHelper);
  843. }
  844. else {
  845. // Draw all the player icons first, so icons don't obscure names
  846. draw_player_icons_separately(s);
  847. if(draw_carnage_graph)
  848. draw_bars_separately(s, theBarInfos);
  849. draw_player_names_separately(s, theTextLayoutHelper);
  850. }
  851. if(draw_carnage_graph && !draw_scores_not_carnage) {
  852. draw_carnage_totals(s);
  853. if(num_valid_net_rankings >= kUseLegendThreshhold)
  854. draw_carnage_legend(s);
  855. }
  856. if(draw_carnage_graph)
  857. draw_bar_labels(s, theBarInfos, theTextLayoutHelper);
  858. // Reset clipping rectangle
  859. set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
  860. }
  861. void
  862. w_players_in_game2::clear_vector() {
  863. vector<player_entry2>::const_iterator i = player_entries.begin();
  864. vector<player_entry2>::const_iterator end = player_entries.end();
  865. while(i != end) {
  866. // Free the name buffers associated with the elements.
  867. // I don't do this in a player_entry destructor because I'm afraid of freeing the name twice
  868. // (once here, when the vector's entry is destroyed, and another time when thePlayerEntry
  869. // above goes out of scope).
  870. if(i->player_image != NULL)
  871. delete i->player_image;
  872. i++;
  873. }
  874. player_entries.clear();
  875. }
  876. void
  877. w_players_in_game2::draw_bar(SDL_Surface* s, int inCenterX, int inBarColorIndex, int inBarValue, int inMaxValue, bar_info& outBarInfo) const {
  878. if(inBarValue != 0) {
  879. // Check that we'll draw a positive bar - value and max are either both positive or both negative.
  880. if(inBarValue > 0)
  881. assert(inMaxValue > 0);
  882. if(inBarValue < 0)
  883. assert(inMaxValue < 0);
  884. // "- 2" leaves room for outline style. Leave two line-heights so a kills and deaths at the top of widget resolve
  885. // (thanks to TextLayoutHelper) and still have space to live.
  886. int theMaximumBarHeight = kBarBottomTotalOffset - font->get_line_height() * 2 - 2;
  887. int theBarHeight = (theMaximumBarHeight * inBarValue) / inMaxValue;
  888. SDL_Rect theBarRect;
  889. theBarRect.y = rect.y + kBarBottomTotalOffset - theBarHeight;
  890. theBarRect.h = theBarHeight;
  891. theBarRect.w = kBarWidth;
  892. theBarRect.x = inCenterX - kBarWidth / 2;
  893. RGBColor theBrightestColor;
  894. get_net_color(inBarColorIndex, &theBrightestColor);
  895. RGBColor theMiddleColor;
  896. theMiddleColor.red = (theBrightestColor.red * 7) / 10;
  897. theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
  898. theMiddleColor.green = (theBrightestColor.green * 7) / 10;
  899. RGBColor theDarkestColor;
  900. theDarkestColor.red = (theBrightestColor.red * 2) / 10;
  901. theDarkestColor.blue = (theBrightestColor.blue * 2) / 10;
  902. theDarkestColor.green = (theBrightestColor.green * 2) / 10;
  903. RGBColor* theRGBColor;
  904. uint32 thePixelColor;
  905. // Draw the lightest part
  906. theRGBColor = &theBrightestColor;
  907. thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
  908. SDL_FillRect(s, &theBarRect, thePixelColor);
  909. // Draw the dark part
  910. theRGBColor = &theDarkestColor;
  911. thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
  912. SDL_Rect theDarkRect;
  913. theDarkRect.x = theBarRect.x + theBarRect.w - kBevelSize;
  914. theDarkRect.w = kBevelSize;
  915. theDarkRect.y = theBarRect.y + kBevelSize;
  916. theDarkRect.h = theBarRect.h - kBevelSize;
  917. if(theBarRect.h > kBevelSize)
  918. SDL_FillRect(s, &theDarkRect, thePixelColor);
  919. // Draw the middle part
  920. theRGBColor = &theMiddleColor;
  921. thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
  922. SDL_Rect theMiddleRect;
  923. theMiddleRect.x = theBarRect.x + kBevelSize;
  924. theMiddleRect.w = theBarRect.w - 2 * kBevelSize;
  925. theMiddleRect.y = theBarRect.y + kBevelSize;
  926. theMiddleRect.h = theBarRect.h - kBevelSize;
  927. if(theBarRect.h > kBevelSize)
  928. SDL_FillRect(s, &theMiddleRect, thePixelColor);
  929. // Capture bar information
  930. outBarInfo.center_x = inCenterX;
  931. outBarInfo.top_y = theBarRect.y;
  932. outBarInfo.pixel_color = thePixelColor;
  933. } // if(inBarValue > 0)
  934. } // draw_bar
  935. ////// w_entry_point_selector //////
  936. void
  937. w_entry_point_selector::validateEntryPoint() {
  938. // Get the entry-point flags from the game type.
  939. int theAppropriateLevelTypeFlags = get_entry_point_flags_for_game_type(mGameType);
  940. mEntryPoints.clear();
  941. // OK, get the vector of entry points.
  942. get_entry_points(mEntryPoints, theAppropriateLevelTypeFlags);
  943. if(mEntryPoints.size() <= 0) {
  944. mEntryPoint.level_number = NONE;
  945. strcpy(mEntryPoint.level_name, "(no valid options)");
  946. mCurrentIndex = NONE;
  947. }
  948. else {
  949. unsigned i;
  950. for(i = 0; i < mEntryPoints.size(); i++) {
  951. if(mEntryPoints[i].level_number == mEntryPoint.level_number)
  952. break;
  953. }
  954. if(i == mEntryPoints.size()) {
  955. mEntryPoint = mEntryPoints[0];
  956. mCurrentIndex = 0;
  957. }
  958. else {
  959. mEntryPoint = mEntryPoints[i];
  960. mCurrentIndex = i;
  961. }
  962. }
  963. dirty = true;
  964. }
  965. void
  966. w_entry_point_selector::gotSelectedCallback(void* arg) {
  967. ((w_entry_point_selector*) arg)->gotSelected();
  968. }
  969. void
  970. w_entry_point_selector::gotSelected() {
  971. if(mEntryPoints.size() > 1) {
  972. dialog theDialog;
  973. theDialog.add(new w_static_text("SELECT LEVEL", TITLE_FONT, TITLE_COLOR));
  974. theDialog.add(new w_spacer());
  975. FileSpecifier theFile(environment_preferences->map_file);
  976. char theName[256];
  977. theFile.GetName(theName);
  978. sprintf(temporary, "%s", theName);
  979. theDialog.add(new w_static_text(temporary, LABEL_FONT));
  980. theDialog.add(new w_spacer());
  981. w_levels* levels_w = new w_levels(mEntryPoints, &theDialog, 480, 16, mCurrentIndex, false);
  982. theDialog.add(levels_w);
  983. theDialog.add(new w_spacer());
  984. sprintf(temporary, "%d %s levels available",
  985. mEntryPoints.size(),
  986. TS_GetCString(kNetworkGameTypesStringSetID, mGameType)
  987. );
  988. theDialog.add(new w_static_text(tempora

Large files files are truncated, but you can click here to view the full file