1 /*
2 *
3 * Copyright (c) 1994, 2002, 2003, 2004 Johannes Prix
4 * Copyright (c) 1994, 2002 Reinhard Prix
5 * Copyright (c) 2004-2010 Arthur Huillet
6 *
7 *
8 * This file is part of Freedroid
9 *
10 * Freedroid is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * Freedroid is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with Freedroid; see the file COPYING. If not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
23 * MA 02111-1307 USA
24 *
25 */
26 /**
27 * This file contains all functions to update and draw the top status
28 * displays with status etc...
29 */
30
31 #define _hud_c
32
33 #include "system.h"
34
35 #include "defs.h"
36 #include "struct.h"
37 #include "proto.h"
38 #include "global.h"
39 #include "widgets/widgets.h"
40
41 #define TEXT_BANNER_DEFAULT_FONT FPS_Display_Font
42
43 /**
44 * The HUD contains several status graphs. These graphs appear as
45 * vertical columns, that are more or less filled, like liquid in a tube.
46 * Since these appear multiple times, it appears sensible to make a
47 * function to draw such bars in a convenient way, which is what this
48 * function is supposed to do.
49 */
blit_vertical_status_bar(float max_value,float current_value,Uint32 filled_color_code,Uint32 empty_color_code,int x,int y,int w,int h)50 void blit_vertical_status_bar(float max_value, float current_value, Uint32 filled_color_code,
51 Uint32 empty_color_code, int x, int y, int w, int h)
52 {
53 SDL_Rect running_power_rect;
54 SDL_Rect un_running_power_rect;
55 uint8_t r, g, b, a;
56
57 // Preconditions. Can happen when Tux dies, for instance.
58 if (max_value < 0 || current_value < 0)
59 return;
60
61 if (max_value == 0.0) {
62 max_value = 1.0;
63 current_value = 1.0;
64 }
65
66 // Now we might get the case of current value exceeding the max value by far. This
67 // does happen when we set ridiculously high energy values for invincible Tux in the
68 // course of testing and convenient debugging. To prevent arithmetic exceptions, we
69 // set precautions to allow for a maximum of 3 times the full scale to represent
70 // extremely high (energy) values.
71 //
72 if (current_value > 3 * max_value)
73 current_value = 3 * max_value;
74
75 running_power_rect.x = x;
76 running_power_rect.y = y + ((h * (max_value - current_value)) / max_value);
77 running_power_rect.w = w;
78 running_power_rect.h = (h * current_value) / max_value;
79 if (current_value < 0)
80 running_power_rect.h = 0;
81
82 un_running_power_rect.x = running_power_rect.x;
83 un_running_power_rect.y = y;
84 un_running_power_rect.w = w;
85 un_running_power_rect.h = h - ((h * current_value) / max_value);
86 if (current_value < 0)
87 un_running_power_rect.h = h;
88 if (current_value > max_value)
89 un_running_power_rect.h = 0;
90
91 // Now that all our rects are set up, we can start to display the current
92 // running power status on screen...
93 //
94 SDL_SetClipRect(Screen, NULL);
95
96 SDL_GetRGBA(filled_color_code, Screen->format, &r, &g, &b, &a);
97 draw_rectangle(&running_power_rect, r, g, b, a);
98
99 SDL_GetRGBA(empty_color_code, Screen->format, &r, &g, &b, &a);
100 draw_rectangle(&un_running_power_rect, r, g, b, a);
101 }; // void blit_vertical_status_bar ( ... )
102
103 /**
104 * This function writes the description of an item into the item description
105 * string.
106 *
107 * Note: We do not want a trailing newline, since that will make text areas
108 * larger than necessary.
109 */
append_item_description(struct auto_string * str,item * item)110 void append_item_description(struct auto_string *str, item *item)
111 {
112 if (item == NULL)
113 return;
114
115 if (item->type == (-1)) {
116 error_message(__FUNCTION__, "\
117 An item description was requested for an item, that does not seem to \n\
118 exist really (i.e. has a type = (-1) ).", PLEASE_INFORM | IS_FATAL);
119 return;
120 }
121
122 // Get the pure item name, also with font changes enabled.
123 append_item_name(item, str);
124
125 // We don't want any more information for Valuable Circuits
126 if (item_spec_eq_id(item->type, "Valuable Circuits"))
127 return;
128
129 autostr_append(str, "\n");
130
131 // Weapon damage
132 if (ItemMap[item->type].slot == WEAPON_SLOT) {
133 if (item->damage_modifier) {
134 // TRANSLATORS: On item description: range
135 autostr_append(str, _("Damage: %d to %d\n"), item->damage, item->damage_modifier + item->damage);
136 } else {
137 // TRANSLATORS: On item description
138 autostr_append(str, _("Damage: %d\n"), item->damage);
139 }
140 }
141 // Multiplicity
142 if (ItemMap[item->type].item_group_together_in_inventory) {
143 // TRANSLATORS: On item description
144 autostr_append(str, _("Multiplicity: %d\n"), item->multiplicity);
145 }
146 // Armor bonus
147 if (item->armor_class) {
148 // TRANSLATORS: On item description: Armor bonus
149 autostr_append(str, _("Armor: %d\n"), item->armor_class);
150 }
151 // Durability or indestructible status
152 if (item->max_durability != (-1)) {
153 // TRANSLATORS: On item description: 'current' of 'maximum'
154 autostr_append(str, _("Durability: %d of %d\n"), (int)item->current_durability, (int)item->max_durability);
155 } else if (ItemMap[item->type].base_item_durability != (-1)) {
156 // TRANSLATORS: On item description : durability
157 autostr_append(str, _("Indestructible\n"));
158 }
159 // Ranged weapon ammunition
160 if (ItemMap[item->type].weapon_ammo_type && ItemMap[item->type].weapon_ammo_clip_size) {
161 // TRANSLATORS: On item description: 'current' of 'maximum' amount of ammo
162 autostr_append(str, _("Ammo: %d of %d\n"), item->ammo_clip, ItemMap[item->type].weapon_ammo_clip_size);
163 }
164 // Strength, dexterity or cooling requirements
165 if ((ItemMap[item->type].item_require_strength != (-1)) || (ItemMap[item->type].item_require_dexterity != (-1))) {
166 if (ItemMap[item->type].item_require_strength != (-1)) {
167 // TRANSLATORS: On item description
168 autostr_append(str, _("Required strength: %d\n"), ItemMap[item->type].item_require_strength);
169 }
170 if (ItemMap[item->type].item_require_dexterity != (-1)) {
171 // TRANSLATORS: On item description
172 autostr_append(str, _("Required dexterity: %d\n"), ItemMap[item->type].item_require_dexterity);
173 }
174 }
175
176 // Usable items should say that they can be used via right-clicking on it
177 if (ItemMap[item->type].right_use.tooltip) {
178 if (game_status == INSIDE_GAME) {
179 autostr_append(str, "%s\n", D_(ItemMap[item->type].right_use.tooltip));
180
181 // Show text only if item is in inventory
182 if (item->inventory_position.x != -1) {
183 autostr_append(str, "\n");
184 autostr_append(str, _("Right click to use\n"));
185 }
186 } else {
187 autostr_append(str, _("Item use: %s\n"), D_(ItemMap[item->type].right_use.tooltip));
188 }
189 }
190
191 // Socket count
192 if (item->upgrade_sockets.size) {
193 // TRANSLATORS: On item description: 'used sockets' / 'number of sockets'
194 autostr_append(str, _("Sockets: used %d/%d\n"), count_used_sockets(item),
195 item->upgrade_sockets.size);
196 }
197
198 // Item bonuses
199 get_item_bonus_string(item, "\n", str);
200
201 // Add-on specific information
202 struct addon_spec *addon = get_addon_spec(item->type);
203 if (addon) {
204 print_addon_description(addon, str);
205 }
206 }
207
208 /**
209 * This function writes the description of a droid above its head,
210 * and shows the remaining energy.
211 */
show_droid_description(enemy * cur_enemy,gps * description_pos)212 static void show_droid_description(enemy *cur_enemy, gps *description_pos)
213 {
214 int text_length;
215 int bar_width; //size of the energy bar
216 int barc_width; //size of the enery bar complement (black part)
217 int bar_y;
218 int bar_x;
219 SDL_Rect rect;
220 struct font *BFont_to_use = Blue_Font;
221 Uint8 r, g, b;
222
223 text_length = text_width(BFont_to_use, D_(cur_enemy->short_description_text));
224
225 rect.h = get_font_height(BFont_to_use);
226
227 // Hostile droids' bars are shown in red, friendly in green.
228 if (!is_friendly(cur_enemy->faction, FACTION_SELF)) {
229 r = 0x99;
230 g = 0x00;
231 b = 0x00;
232 } else {
233 r = 0x00;
234 g = 0x55;
235 b = 0x00;
236 }
237
238 // Position of the bar
239 bar_x = translate_map_point_to_screen_pixel_x(description_pos->x, description_pos->y) - text_length / 2;
240 bar_y =
241 translate_map_point_to_screen_pixel_y(description_pos->x,
242 description_pos->y) + enemy_images[Droidmap[cur_enemy->type].individual_shape_nr][0][0].offset_y -
243 2.5 * get_font_height(BFont_to_use);
244
245 // Calculates the width of the remaining health bar. Rounds the
246 // width up to the nearest integer to ensure that at least one
247 // pixel of health is always shown.
248 //
249 bar_width = (int) ceil((text_length) * (cur_enemy->energy / Droidmap[cur_enemy->type].maxenergy));
250 barc_width = (int) floor((text_length) * (1.0 - cur_enemy->energy / Droidmap[cur_enemy->type].maxenergy));
251 if (bar_width < 0)
252 bar_width = 0;
253 if (barc_width < 0)
254 barc_width = 0;
255
256
257 // Draw the energy bar
258 rect.x = bar_x;
259 rect.y = bar_y;
260 rect.w = bar_width;
261 draw_rectangle(&rect, r, g, b, BACKGROUND_TEXT_RECT_ALPHA);
262
263 // Draw the energy bar complement
264 rect.x = bar_x + bar_width;
265 rect.y = bar_y;
266 rect.w = barc_width;
267 draw_rectangle(&rect, 0, 0, 0, 255);
268
269 // Display droid's short description text
270 rect.x = translate_map_point_to_screen_pixel_x(description_pos->x, description_pos->y) - text_length / 2;
271 put_string(BFont_to_use, rect.x, rect.y, D_(cur_enemy->short_description_text));
272 }
273
274 /**
275 * This function sets up the text, that is to appear in a bigger text
276 * rectangle, possibly next to the mouse cursor, e.g. when the mouse is
277 * hovering over an item or barrel or crate or teleporter.
278 */
prepare_text_window_content(struct auto_string * str,SDL_Rect * rect)279 static void prepare_text_window_content(struct auto_string *str, SDL_Rect *rect)
280 {
281 point CurPos;
282 point inv_square;
283 int InvIndex;
284 int index_of_obst_below_mouse_cursor = (-1);
285 int index_of_floor_item_below_mouse_cursor = (-1);
286
287 CurPos.x = GetMousePos_x();
288 CurPos.y = GetMousePos_y();
289
290 rect->x = CurPos.x + 20;
291 rect->y = CurPos.y;
292
293 autostr_printf(str, "");
294
295 /* If the player has an item in hand, draw the item name into the
296 * description field. If the requirements for this item are not met, we
297 * show a text. */
298 if (item_held_in_hand != NULL) {
299 autostr_printf(str, "%s%s", font_switchto_neon, D_(item_specs_get_name(item_held_in_hand->type)));
300
301 if (!ItemUsageRequirementsMet(item_held_in_hand, FALSE)) {
302 autostr_append(str, "\n%s%s", font_switchto_red, _("REQUIREMENTS NOT MET"));
303 }
304 return;
305 }
306 // in the other case however, that no item is currently held in hand, we need to
307 // work a little more: we need to find out if the cursor is currently over some
308 // inventory or other item and in case that's true, we need to give the
309 // description of this item.
310 //
311 else if (GameConfig.Inventory_Visible) {
312 // Perhaps the cursor is over some item of the inventory?
313 // let's check this case first.
314 if (MouseCursorIsInInventoryGrid(CurPos.x, CurPos.y)) {
315 inv_square.x = GetInventorySquare_x(CurPos.x);
316 inv_square.y = GetInventorySquare_y(CurPos.y);
317 InvIndex = GetInventoryItemAt(inv_square.x, inv_square.y);
318 if (InvIndex != (-1)) {
319 append_item_description(str, &(Me.Inventory[InvIndex]));
320 rect->x =
321 (Me.Inventory[InvIndex].inventory_position.x +
322 ItemMap[Me.Inventory[InvIndex].type].inv_size.x) * 30 + 16;
323 rect->y = 300;
324 }
325 } else if (MouseCursorIsOnButton(WEAPON_RECT_BUTTON, CurPos.x, CurPos.y)) {
326 if (Me.weapon_item.type > 0) {
327 append_item_description(str, &(Me.weapon_item));
328 rect->x = WEAPON_RECT_X + 30 + WEAPON_RECT_WIDTH;
329 rect->y = WEAPON_RECT_Y - 30;
330 }
331 } else if (MouseCursorIsOnButton(DRIVE_RECT_BUTTON, CurPos.x, CurPos.y)) {
332 if (Me.drive_item.type > 0) {
333 append_item_description(str, &(Me.drive_item));
334 rect->x = DRIVE_RECT_X + 30 + DRIVE_RECT_WIDTH;
335 rect->y = DRIVE_RECT_Y - 30;
336 }
337 } else if (MouseCursorIsOnButton(SHIELD_RECT_BUTTON, CurPos.x, CurPos.y)) {
338 if (Me.shield_item.type > 0) {
339 append_item_description(str, &(Me.shield_item));
340 rect->x = SHIELD_RECT_X + 30 + SHIELD_RECT_WIDTH;
341 rect->y = SHIELD_RECT_Y - 30;
342 } else if (Me.weapon_item.type > 0) {
343 if (ItemMap[Me.weapon_item.type].weapon_needs_two_hands) {
344 append_item_description(str, &(Me.weapon_item));
345 rect->x = SHIELD_RECT_X + 30 + SHIELD_RECT_WIDTH;
346 rect->y = SHIELD_RECT_Y - 30;
347 }
348 }
349 } else if (MouseCursorIsOnButton(ARMOUR_RECT_BUTTON, CurPos.x, CurPos.y)) {
350 if (Me.armour_item.type > 0) {
351 append_item_description(str, &(Me.armour_item));
352 rect->x = ARMOUR_RECT_X + 30 + ARMOUR_RECT_WIDTH;
353 rect->y = ARMOUR_RECT_Y - 30;
354 }
355 } else if (MouseCursorIsOnButton(HELMET_RECT_BUTTON, CurPos.x, CurPos.y)) {
356 if (Me.special_item.type > 0) {
357 append_item_description(str, &(Me.special_item));
358 rect->x = HELMET_RECT_X + 30 + HELMET_RECT_WIDTH;
359 rect->y = HELMET_RECT_Y - 30;
360 }
361 }
362 } // if nothing is 'held in hand' && inventory-screen visible
363
364 // Check if the crafting UI is open and the cursor is inside it.
365 // No banner should be shown if that's the case.
366 if (cursor_is_on_addon_crafting_ui(&CurPos)) {
367 return;
368 }
369
370 // Check if the item upgrade UI is open and the cursor is inside it.
371 // We show a tooltip for the item upgrade UI if that's the case.
372 if (append_item_upgrade_ui_tooltip(&CurPos, str)) {
373 return;
374 }
375
376 // If the mouse cursor is within the user rectangle, then we check if
377 // either the cursor is over an inventory item or over some other droid
378 // and in both cases, we give a description of the object in the small
379 // black rectangle in the top status banner.
380 //
381
382 if (MouseCursorIsInUserRect(CurPos.x, CurPos.y)) {
383 level *obj_lvl = NULL;
384
385 index_of_floor_item_below_mouse_cursor = get_floor_item_index_under_mouse_cursor(&obj_lvl);
386
387 if (index_of_floor_item_below_mouse_cursor != (-1) && obj_lvl != NULL) {
388 gps item_vpos;
389 update_virtual_position(&item_vpos, &(obj_lvl->ItemList[index_of_floor_item_below_mouse_cursor].pos), Me.pos.z);
390 if (item_vpos.x != -1) {
391 append_item_description(str, &(obj_lvl->ItemList[index_of_floor_item_below_mouse_cursor]));
392 rect->x = translate_map_point_to_screen_pixel_x(item_vpos.x, item_vpos.y) + 80;
393 rect->y = translate_map_point_to_screen_pixel_y(item_vpos.x, item_vpos.y) - 30;
394 }
395 }
396
397 // Display Clickable Obstacle label in the top status banner.
398 //
399 obj_lvl = NULL;
400 index_of_obst_below_mouse_cursor = clickable_obstacle_below_mouse_cursor(&obj_lvl, TRUE);
401 if (index_of_obst_below_mouse_cursor != (-1)) {
402 gps obst_vpos;
403 update_virtual_position(&obst_vpos, &(obj_lvl->obstacle_list[index_of_obst_below_mouse_cursor].pos), Me.pos.z);
404 if (obst_vpos.x != -1) {
405 char *label = D_(get_obstacle_spec(obj_lvl->obstacle_list[index_of_obst_below_mouse_cursor].type)->label);
406 if (!label) {
407 error_message(__FUNCTION__, "Obstacle type %d is clickable, and as such requires a label to be displayed on mouseover.", PLEASE_INFORM, obj_lvl->obstacle_list[index_of_obst_below_mouse_cursor].type);
408 label = _("No label for this obstacle");
409 }
410
411 autostr_printf(str, "%s", label);
412 rect->x = translate_map_point_to_screen_pixel_x(obst_vpos.x, obst_vpos.y) + 50;
413 rect->y = translate_map_point_to_screen_pixel_y(obst_vpos.x, obst_vpos.y) - 20;
414 }
415 }
416
417 // Maybe there is a teleporter event connected to the square where the mouse
418 // cursor is currently hovering. In this case we should create a message about
419 // where the teleporter connection would bring the Tux...
420 //
421 if (teleporter_square_below_mouse_cursor()) {
422 autostr_append(str, "%s", teleporter_square_below_mouse_cursor());
423 }
424 // Maybe there is a living droid below the current mouse cursor, and it is visible to the player.
425 // In this case, we'll give the description of the corresponding bot.
426 // Note : the call to GetLivingDroidBelowMouseCursor() does set the virt_pos attribute
427 // of the found droid to be the bot's position relatively to Tux current level
428 //
429 enemy *droid_below_mouse_cursor = GetLivingDroidBelowMouseCursor();
430 if (droid_below_mouse_cursor != NULL
431 && DirectLineColldet(Me.pos.x, Me.pos.y, droid_below_mouse_cursor->virt_pos.x, droid_below_mouse_cursor->virt_pos.y, Me.pos.z,
432 &VisiblePassFilter)) {
433 show_droid_description(droid_below_mouse_cursor, &droid_below_mouse_cursor->virt_pos);
434 return;
435 }
436 }
437 }
438
439 /**
440 * At various points in the game, especially when the mouse in over an
441 * interesting object inside the game, a text banner will appear, e.g.
442 * to describe the item in question.
443 */
show_current_text_banner(void)444 void show_current_text_banner(void)
445 {
446 static struct auto_string *txt = NULL;
447 if (txt == NULL)
448 txt = alloc_autostr(200);
449
450 // Prepare the string, that is to be displayed inside the text rectangle
451 SDL_Rect rect = { 0, 0, 0 };
452 prepare_text_window_content(txt, &rect);
453
454 display_tooltip(txt->value, 1, rect);
455 }
456
457 /**
458 * This function displays a tooltip in the specified rectangle.
459 * The rectangle size can be specified or left to 0, meaning the rectangle will be
460 * expanded to fit the text.
461 * @param text Text to be displayed.
462 * @param centered Flag marking whether the text will be centered or not.
463 * @param rect Rectangle in which the tooltip will be displayed.
464 */
display_tooltip(const char * text,int centered,SDL_Rect rect)465 void display_tooltip(const char *text, int centered, SDL_Rect rect)
466 {
467 // Displaying the text aligned to center modifies the string
468 // so we make a temporary copy to keep the original text untouched.
469 char buffer[strlen(text) + 1];
470 strcpy(buffer, text);
471
472 // Set font before making any font specific calculations.
473 set_current_font(TEXT_BANNER_DEFAULT_FONT);
474
475 // If the width is not specified, expand the rectangle to fit
476 // the longest line width.
477 if (!rect.w)
478 rect.w = longest_line_width(buffer) + 2 * TEXT_BANNER_HORIZONTAL_MARGIN;
479
480 // Compute the required height.
481 int lines_in_text = get_lines_needed(buffer, rect, 1.0);
482 rect.h = lines_in_text * get_font_height(get_current_font());
483
484 // Add extra correction to ensure the banner rectangle stays inside
485 // the visible screen.
486 if (rect.x < 1)
487 rect.x = 1;
488 else if (rect.x + rect.w > GameConfig.screen_width - 1)
489 rect.x = GameConfig.screen_width - rect.w - 1;
490 if (rect.y < 1)
491 rect.y = 1;
492 else if (rect.y + rect.h > GameConfig.screen_height - 1)
493 rect.y = GameConfig.screen_height - rect.h - 1;
494
495 // Compute actual coordinates for the text.
496 SDL_Rect text_rect = rect;
497 text_rect.x += TEXT_BANNER_HORIZONTAL_MARGIN;
498 text_rect.w -= 2 * TEXT_BANNER_HORIZONTAL_MARGIN;
499
500 // Draw the background rectangle
501 SDL_SetClipRect(Screen, NULL); // this unsets the clipping rectangle
502 draw_rectangle(&rect, 0, 0, 0, BACKGROUND_TEXT_RECT_ALPHA);
503
504 // Print the text.
505 if (!centered) {
506 display_text(buffer, text_rect.x, text_rect.y, &text_rect, 1.0);
507 return;
508 }
509
510 // Print the text centered.
511 int line_spacing = (text_rect.h - lines_in_text * get_font_height(get_current_font())) / (lines_in_text + 1);
512 char *ptr = buffer;
513 int i;
514 for (i = 0; i < lines_in_text; i++) {
515 char *this_line = ptr;
516 char *next_newline = strstr(ptr, "\n");
517 if (next_newline) {
518 int pos = next_newline - ptr;
519 this_line[pos] = '\0';
520 ptr += pos + 1;
521 }
522 int offset = (text_rect.w - text_width(get_current_font(), this_line)) / 2;
523 put_string(get_current_font(),
524 text_rect.x + offset,
525 text_rect.y + line_spacing + i * (line_spacing + get_font_height(get_current_font())), this_line);
526 }
527 }
528
529 /**
530 * This function derives the 'minutes' component of the time already
531 * elapsed in this game.
532 */
get_minutes_of_game_duration(float current_game_date)533 int get_minutes_of_game_duration(float current_game_date)
534 {
535 return (((int)(10 * current_game_date / (60))) % 60);
536 }; // void get_minutes_of_game_duration ( float current_game_date )
537
538 /**
539 * This function derives the 'hours' component of the time already
540 * elapsed in this game.
541 */
get_hours_of_game_duration(float current_game_date)542 int get_hours_of_game_duration(float current_game_date)
543 {
544 return (((int)(10 * current_game_date / (60 * 60))) % 24);
545 }; // void get_hours_of_game_duration ( float current_game_date )
546
547 /**
548 * This function derives the 'days' component of the time already
549 * elapsed in this game.
550 */
get_days_of_game_duration(float current_game_date)551 int get_days_of_game_duration(float current_game_date)
552 {
553 return (((int)(1 + 10 * current_game_date / (60 * 60 * 24))));
554 }; // void get_days_of_game_duration ( float current_game_date )
555
556 /**
557 * Add a new message to the game log.
558 */
append_new_game_message(const char * fmt,...)559 void append_new_game_message(const char *fmt, ...)
560 {
561 autostr_append(message_log->text, "\n* ");
562
563 va_list args;
564 va_start(args, fmt);
565 autostr_vappend(message_log->text, fmt, args);
566 va_end(args);
567
568 message_log->scroll_offset = 0;
569 }
570
571 /**
572 * Initialize or reset the message log.
573 */
init_message_log(void)574 void init_message_log(void)
575 {
576 if (!message_log)
577 message_log = widget_text_create();
578 }
579
580 /**
581 * Calculate the current FPS and return it.
582 */
get_current_fps(void)583 int get_current_fps(void)
584 {
585 static float time_since_last_fps_update = 10;
586 static int frames_counted = 0;
587 static int current_fps = 0;
588
589 time_since_last_fps_update += Frame_Time();
590 frames_counted++;
591 if (frames_counted > 50) {
592 current_fps = frames_counted / time_since_last_fps_update;
593 time_since_last_fps_update = 0;
594 frames_counted = 0;
595 }
596 return current_fps;
597 }
598
599
600 /**
601 * Show the texts that are usually shown in the top left corner e.g. the FPS.
602 */
show_top_left_text(void)603 static void show_top_left_text(void)
604 {
605 SDL_Rect clip;
606 int i;
607 int remaining_bots;
608 static struct auto_string *txt;
609 if (txt == NULL)
610 txt = alloc_autostr(200);
611 autostr_printf(txt, "");
612
613 // Show FPS
614 if (GameConfig.Draw_Framerate)
615 autostr_append(txt, _("FPS: %d\n"), get_current_fps());
616
617 // Show quest information for current level
618 for (i = 0; i < MAX_MISSIONS_IN_GAME; i++) {
619 if (!Me.AllMissions[i].MissionWasAssigned)
620 continue;
621
622 if (Me.AllMissions[i].must_clear_level == Me.pos.z) {
623 remaining_bots = 0;
624
625 enemy *erot, *nerot;
626 BROWSE_ALIVE_BOTS_SAFE(erot, nerot) {
627 if ((erot->pos.z == Me.pos.z) && (!is_friendly(erot->faction, FACTION_SELF)))
628 remaining_bots++;
629
630 }
631 autostr_append(txt, _("Bots remaining on level: %d\n"), remaining_bots);
632 }
633 }
634
635 clip.x = User_Rect.x + 1;
636 clip.y = User_Rect.y + 1;
637 clip.w = GameConfig.screen_width;
638 clip.h = GameConfig.screen_height;
639
640 set_current_font(FPS_Display_Font);
641 display_text(txt->value, clip.x, clip.y, &clip, 1.0);
642 }
643
644 /**
645 * Show the texts that is written in the top right corner, like the game time
646 * and current position.
647 */
show_top_right_text(void)648 static void show_top_right_text(void)
649 {
650 char level_name_and_time[1000];
651 char temp_text[1000];
652
653 // We display the name of the current level and the current time inside
654 // the game.
655 if (!(GameConfig.CharacterScreen_Visible || GameConfig.SkillScreen_Visible)) {
656 if (GameConfig.Draw_Position) {
657 sprintf(level_name_and_time, "%s (%03.1f:%03.1f:%d) ",
658 D_(curShip.AllLevels[Me.pos.z]->Levelname), Me.pos.x, Me.pos.y, Me.pos.z);
659 // TRANSLATORS: In-game date: Day <day number> <hour>:<minute>
660 sprintf(temp_text, _("Day %d %02d:%02d"),
661 get_days_of_game_duration(Me.current_game_date),
662 get_hours_of_game_duration(Me.current_game_date), get_minutes_of_game_duration(Me.current_game_date));
663 strcat(level_name_and_time, temp_text);
664 strcat(level_name_and_time, " ");
665 } else {
666 sprintf(level_name_and_time, "%s ", D_(curShip.AllLevels[Me.pos.z]->Levelname));
667 sprintf(temp_text, _("Day %d %02d:%02d"),
668 get_days_of_game_duration(Me.current_game_date),
669 get_hours_of_game_duration(Me.current_game_date), get_minutes_of_game_duration(Me.current_game_date));
670 strcat(level_name_and_time, temp_text);
671 strcat(level_name_and_time, " ");
672 }
673 put_string_right(FPS_Display_Font, 0.3 * get_font_height(get_current_font()), level_name_and_time);
674 }
675 }
676
677 /**
678 * Show all texts and banners that should be blitted right inside the combat
679 * window.
680 */
show_texts_and_banner(void)681 void show_texts_and_banner(void) {
682 SDL_SetClipRect(Screen, NULL);
683 show_current_text_banner();
684 show_top_left_text();
685 show_top_right_text();
686 if (GameConfig.effect_countdowns_visible) {
687 display_effect_countdowns();
688 }
689 DisplayBigScreenMessage();
690 }
691
692 /**
693 * This function should toggle the visibility of the inventory/character
694 * and skill screen. Of course, when one of them is turned on, the other
695 * ones should be turned off again. At least that was a popular request
696 * from various sources in the past, so we heed it now.
697 */
toggle_game_config_screen_visibility(int screen_visible)698 void toggle_game_config_screen_visibility(int screen_visible)
699 {
700
701 switch (screen_visible) {
702 case GAME_CONFIG_SCREEN_VISIBLE_INVENTORY:
703 GameConfig.Inventory_Visible = !GameConfig.Inventory_Visible;
704 GameConfig.skill_explanation_screen_visible = FALSE;
705 break;
706 case GAME_CONFIG_SCREEN_VISIBLE_SKILLS:
707 GameConfig.SkillScreen_Visible = !GameConfig.SkillScreen_Visible;
708 if (!GameConfig.SkillScreen_Visible)
709 GameConfig.skill_explanation_screen_visible = 0;
710 GameConfig.CharacterScreen_Visible = FALSE;
711 break;
712 case GAME_CONFIG_SCREEN_VISIBLE_CHARACTER:
713 GameConfig.CharacterScreen_Visible = !GameConfig.CharacterScreen_Visible;
714 GameConfig.SkillScreen_Visible = FALSE;
715 break;
716 case GAME_CONFIG_SCREEN_VISIBLE_SKILL_EXPLANATION:
717 GameConfig.skill_explanation_screen_visible = !GameConfig.skill_explanation_screen_visible;
718 GameConfig.Inventory_Visible = FALSE;
719 break;
720 default:
721 error_message(__FUNCTION__, "\
722 unhandled skill screen code received. something is going VERY wrong!", PLEASE_INFORM | IS_FATAL);
723 break;
724 }
725
726 }; // void toggle_game_config_screen_visibility ( int screen_visible )
727
728 #undef _hud_c
729