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