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

/ports/psp/branches/alephonepsp-old/Source_Files/Network/network_dialog_widgets_sdl.cpp

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

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