1 /**
2  * @file
3  * @brief HUD related routines.
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "../client.h"
27 #include "cl_localentity.h"
28 #include "cl_actor.h"
29 #include "cl_hud.h"
30 #include "cl_hud_callbacks.h"
31 #include "cl_view.h"
32 #include "../cgame/cl_game.h"
33 #include "../ui/ui_main.h"
34 #include "../ui/ui_popup.h"
35 #include "../ui/ui_nodes.h"
36 #include "../ui/ui_draw.h"
37 #include "../ui/ui_render.h"
38 #include "../ui/ui_tooltip.h"
39 #include "../renderer/r_mesh_anim.h"
40 #include "../renderer/r_draw.h"
41 #include "../../common/grid.h"
42 
43 static cvar_t* cl_hud_message_timeout;
44 static cvar_t* cl_show_cursor_tooltips;
45 static cvar_t* cl_hud;
46 cvar_t* cl_worldlevel;
47 
48 enum {
49 	REMAINING_TU_RELOAD_RIGHT,
50 	REMAINING_TU_RELOAD_LEFT,
51 	REMAINING_TU_CROUCH,
52 
53 	REMAINING_TU_MAX
54 };
55 static bool displayRemainingTus[REMAINING_TU_MAX];
56 
57 typedef enum {
58 	BT_RIGHT_FIRE,
59 	BT_REACTION,
60 	BT_LEFT_FIRE,
61 	BT_RIGHT_RELOAD,
62 	BT_LEFT_RELOAD,
63 	BT_STAND,
64 	BT_CROUCH,
65 	BT_HEADGEAR,
66 
67 	BT_NUM_TYPES
68 } buttonTypes_t;
69 
70 typedef enum {
71 	FIRE_RIGHT,
72 	FIRE_LEFT,
73 	RELOAD_RIGHT,
74 	RELOAD_LEFT
75 } actionType_t;
76 
77 /** @brief a cbuf string for each button_types_t */
78 static char const* const shootTypeStrings[] = {
79 	"primaryright",
80 	"reaction",
81 	"primaryleft",
82 	"reloadright",
83 	"reloadleft",
84 	"stand",
85 	"crouch",
86 	"headgear"
87 };
88 CASSERT(lengthof(shootTypeStrings) == BT_NUM_TYPES);
89 
90 /**
91  * @brief Defines the various states of a button.
92  * @note Not all buttons do have all of these states (e.g. "unusable" is not very common).
93  */
94 typedef enum {
95 	BT_STATE_UNINITIALZED,
96 	BT_STATE_DISABLE,		/**< 'Disabled' display (grey) */
97 	BT_STATE_DESELECT,		/**< Normal display (blue) */
98 	BT_STATE_SELECT			/**< Selected display (blue) */
99 } weaponButtonState_t;
100 
101 /** @note Order of elements here must correspond to order of elements in walkType_t. */
102 static char const* const moveModeDescriptions[] = {
103 	N_("Crouch walk"),
104 	N_("Autostand"),
105 	N_("Walk"),
106 	N_("Crouch walk")
107 };
108 CASSERT(lengthof(moveModeDescriptions) == WALKTYPE_MAX);
109 
110 typedef struct reserveShot_s {
111 	actorHands_t hand;
112 	int fireModeIndex;
113 	int weaponIndex;
114 	int TUs;
115 } reserveShot_t;
116 
117 typedef enum {
118 	BUTTON_TURESERVE_UNINITIALIZED,
119 
120 	BUTTON_TURESERVE_SHOT_RESERVED,
121 	BUTTON_TURESERVE_SHOT_DISABLED,
122 	BUTTON_TURESERVE_SHOT_CLEAR,
123 
124 	BUTTON_TURESERVE_CROUCH_RESERVED,
125 	BUTTON_TURESERVE_CROUCH_DISABLED,
126 	BUTTON_TURESERVE_CROUCH_CLEAR
127 } reserveButtonState_t;
128 
129 static weaponButtonState_t buttonStates[BT_NUM_TYPES];
130 static reserveButtonState_t shotReserveButtonState;
131 static reserveButtonState_t crouchReserveButtonState;
132 
133 /**
134  * @brief Displays a message on the hud.
135  * @sa UI_DisplayNotice
136  * @param[in] text text is already translated here
137  */
HUD_DisplayMessage(const char * text)138 void HUD_DisplayMessage (const char* text)
139 {
140 	assert(text);
141 	UI_DisplayNotice(text, cl_hud_message_timeout->integer, cl_hud->string);
142 }
143 
HUD_UpdateActorStats(const le_t * le)144 void HUD_UpdateActorStats (const le_t* le)
145 {
146 	if (LE_IsDead(le))
147 		return;
148 
149 	const Item* item = le->getRightHandItem();
150 	if ((!item || !item->def() || !item->isHeldTwoHanded()) && le->getLeftHandItem())
151 		item = le->getLeftHandItem();
152 
153 	const character_t* chr = CL_ActorGetChr(le);
154 	assert(chr);
155 	const char* tooltip = va(_("%s\nHP: %i/%i TU: %i\n%s"),
156 		chr->name, le->HP, le->maxHP, le->TU, (item && item->def()) ? _(item->def()->name) : "");
157 
158 	const int idx = CL_ActorGetNumber(le);
159 	UI_ExecuteConfunc("updateactorvalues %i \"%s\" \"%i\" \"%i\" \"%i\" \"%i\" \"%i\" \"%i\" \"%i\" \"%s\"",
160 			idx, le->model2->name, le->HP, le->maxHP, le->TU, le->maxTU, le->morale, le->maxMorale, le->STUN, tooltip);
161 }
162 
163 /**
164  * @brief Sets the display for a single weapon/reload HUD button.
165  * @todo This should be a confunc which also sets the tooltips
166  */
HUD_SetWeaponButton(buttonTypes_t button,weaponButtonState_t state)167 static void HUD_SetWeaponButton (buttonTypes_t button, weaponButtonState_t state)
168 {
169 	if (buttonStates[button] == state)
170 		return;
171 
172 	const char* prefix;
173 
174 	switch (state) {
175 	case BT_STATE_DESELECT:
176 		prefix = "deselect_";
177 		break;
178 	case BT_STATE_DISABLE:
179 		prefix = "disable_";
180 		break;
181 	default:
182 		prefix = "";
183 		break;
184 	}
185 
186 	/* Connect confunc strings to the ones as defined in "menu hud_nohud". */
187 	UI_ExecuteConfunc("%s%s", prefix, shootTypeStrings[button]);
188 	buttonStates[button] = state;
189 }
190 
191 /**
192  * @brief Returns the amount of usable "reaction fire" TUs for this actor (depends on active/inactive RF)
193  * @param[in] le The actor to check.
194  * @return The remaining/usable TUs for this actor
195  * @return -1 on error (this includes bad [very large] numbers stored in the struct).
196  * @todo Maybe only return "reaction" value if reaction-state is active? The value _should_ be 0, but one never knows :)
197  */
HUD_UsableReactionTUs(const le_t * le)198 static int HUD_UsableReactionTUs (const le_t* le)
199 {
200 	/* Get the amount of usable TUs depending on the state (i.e. is RF on or off?) */
201 	if (le->state & STATE_REACTION)
202 		/* CL_ActorUsableTUs DOES NOT return the stored value for "reaction" here. */
203 		return CL_ActorUsableTUs(le) + CL_ActorReservedTUs(le, RES_REACTION);
204 
205 	/* CL_ActorUsableTUs DOES return the stored value for "reaction" here. */
206 	return CL_ActorUsableTUs(le);
207 }
208 
209 /**
210  * @brief Check if at least one firemode is available for reservation.
211  * @return true if there is at least one firemode - false otherwise.
212  * @sa HUD_UpdateButtons
213  * @sa HUD_PopupFiremodeReservation_f
214  */
HUD_CheckFiremodeReservation(void)215 static bool HUD_CheckFiremodeReservation (void)
216 {
217 	if (!selActor)
218 		return false;
219 
220 	actorHands_t hand;
221 	foreachhand(hand) {
222 		/* Get weapon (and its ammo) from the hand. */
223 		const fireDef_t* fireDef = HUD_GetFireDefinitionForHand(selActor, hand);
224 		if (!fireDef)
225 			continue;
226 
227 		const objDef_t* ammo = fireDef->obj;
228 		for (int i = 0; i < ammo->numFiredefs[fireDef->weapFdsIdx]; i++) {
229 			/* Check if at least one firemode is available for reservation. */
230 			if (CL_ActorUsableTUs(selActor) + CL_ActorReservedTUs(selActor, RES_SHOT) >= CL_ActorTimeForFireDef(selActor, &ammo->fd[fireDef->weapFdsIdx][i]))
231 				return true;
232 		}
233 	}
234 
235 	/* No reservation possible */
236 	return false;
237 }
238 
239 
240 /**
241  * @brief Sets TU-reservation and firemode
242  * @param[in] le The local entity of the actor to change the tu reservation for.
243  * @param[in] tus How many TUs to set.
244  * @param[in] hand Store the given hand.
245  * @param[in] fireModeIndex Store the given firemode for this hand.
246  * @param[in] weapon Pointer to weapon in the hand.
247  */
HUD_SetShootReservation(const le_t * le,const int tus,const actorHands_t hand,const int fireModeIndex,const objDef_t * weapon)248 static void HUD_SetShootReservation (const le_t* le, const int tus, const actorHands_t hand, const int fireModeIndex, const objDef_t* weapon)
249 {
250 	character_t* chr = CL_ActorGetChr(le);
251 	assert(chr);
252 
253 	CL_ActorReserveTUs(le, RES_SHOT, tus);
254 	chr->reservedTus.shotSettings.set(hand, fireModeIndex, weapon);
255 }
256 
257 static linkedList_t* popupListData;
258 static uiNode_t* popupListNode;
259 
260 /**
261  * @brief Creates a (text) list of all firemodes of the currently selected actor.
262  * @param[in] le The actor local entity
263  * @param[in] popupReload Prevent firemode reservation popup from being closed if
264  * no firemode is available because of insufficient TUs.
265  * @sa HUD_PopupFiremodeReservation_f
266  * @sa HUD_CheckFiremodeReservation
267  * @todo use components and confuncs here
268  */
HUD_PopupFiremodeReservation(const le_t * le,bool popupReload)269 static void HUD_PopupFiremodeReservation (const le_t* le, bool popupReload)
270 {
271 	int i;
272 	static char text[MAX_VAR];
273 	int selectedEntry;
274 	linkedList_t* popupListText = nullptr;
275 	reserveShot_t reserveShotData;
276 
277 	/* reset the list */
278 	UI_ResetData(TEXT_LIST);
279 
280 	LIST_Delete(&popupListData);
281 
282 	/* Add list-entry for deactivation of the reservation. */
283 	LIST_AddPointer(&popupListText, (void*)(_("[0 TU] No reservation")));
284 	reserveShotData.hand = ACTOR_HAND_NOT_SET;
285 	reserveShotData.fireModeIndex = -1;
286 	reserveShotData.weaponIndex = NONE;
287 	reserveShotData.TUs = -1;
288 	LIST_Add(&popupListData, reserveShotData);
289 	selectedEntry = 0;
290 
291 	actorHands_t hand;
292 	foreachhand(hand) {
293 		const fireDef_t* fd = HUD_GetFireDefinitionForHand(le, hand);
294 		if (!fd)
295 			continue;
296 		character_t* chr = CL_ActorGetChr(le);
297 		assert(chr);
298 
299 		const objDef_t* ammo = fd->obj;
300 		for (i = 0; i < ammo->numFiredefs[fd->weapFdsIdx]; i++) {
301 			const fireDef_t* ammoFD = &ammo->fd[fd->weapFdsIdx][i];
302 			const int time = CL_ActorTimeForFireDef(le, ammoFD);
303 			if (CL_ActorUsableTUs(le) + CL_ActorReservedTUs(le, RES_SHOT) >= time) {
304 				/* Get firemode name and TUs. */
305 				Com_sprintf(text, lengthof(text), _("[%i TU] %s"), time, _(ammoFD->name));
306 
307 				/* Store text for popup */
308 				LIST_AddString(&popupListText, text);
309 
310 				/* Store Data for popup-callback. */
311 				reserveShotData.hand = hand;
312 				reserveShotData.fireModeIndex = i;
313 				reserveShotData.weaponIndex = ammo->weapons[fd->weapFdsIdx]->idx;
314 				reserveShotData.TUs = time;
315 				LIST_Add(&popupListData, reserveShotData);
316 
317 				/* Remember the line that is currently selected (if any). */
318 				if (chr->reservedTus.shotSettings.getHand() == hand
319 				 && chr->reservedTus.shotSettings.getFmIdx() == i
320 				 && chr->reservedTus.shotSettings.getWeapon() == ammo->weapons[fd->weapFdsIdx])
321 					selectedEntry = LIST_Count(popupListData) - 1;
322 			}
323 		}
324 	}
325 
326 	if (LIST_Count(popupListData) > 1 || popupReload) {
327 		/* We have more entries than the "0 TUs" one
328 		 * or we want to simply refresh/display the popup content (no matter how many TUs are left). */
329 		popupListNode = UI_PopupList(_("Shot Reservation"), _("Reserve TUs for firing/using."), popupListText, "hud_shotreserve <lineselected>");
330 		/* Set color for selected entry. */
331 		VectorSet(popupListNode->selectedColor, 0.0, 0.78, 0.0);
332 		popupListNode->selectedColor[3] = 1.0;
333 		UI_TextNodeSelectLine(popupListNode, selectedEntry);
334 	}
335 }
336 
337 /**
338  * @brief Creates a (text) list of all firemodes of the currently selected actor.
339  * @sa HUD_PopupFiremodeReservation
340  */
HUD_PopupFiremodeReservation_f(void)341 static void HUD_PopupFiremodeReservation_f (void)
342 {
343 	if (!selActor)
344 		return;
345 
346 	/* A second parameter (the value itself will be ignored) was given.
347 	 * This is used to reset the shot-reservation.*/
348 	if (Cmd_Argc() == 2) {
349 		HUD_SetShootReservation(selActor, 0, ACTOR_HAND_NOT_SET, -1, nullptr);
350 	} else {
351 		HUD_PopupFiremodeReservation(selActor, false);
352 	}
353 }
354 
355 /**
356  * @brief Get selected firemode in the list of the currently selected actor.
357  * @sa HUD_PopupFiremodeReservation_f
358  */
HUD_ShotReserve_f(void)359 static void HUD_ShotReserve_f (void)
360 {
361 	if (Cmd_Argc() < 2) {
362 		Com_Printf("Usage: %s <popupindex>\n", Cmd_Argv(0));
363 		return;
364 	}
365 
366 	if (!selActor)
367 		return;
368 
369 	/* read and range check */
370 	const int selectedPopupIndex = atoi(Cmd_Argv(1));
371 	if (selectedPopupIndex < 0 || selectedPopupIndex >= LIST_Count(popupListData))
372 		return;
373 
374 	const reserveShot_t* reserveShotData = (const reserveShot_t*)LIST_GetByIdx(popupListData, selectedPopupIndex);
375 	if (!reserveShotData)
376 		return;
377 
378 	if (reserveShotData->weaponIndex == NONE) {
379 		HUD_SetShootReservation(selActor, 0, ACTOR_HAND_NOT_SET, -1, nullptr);
380 		return;
381 	}
382 
383 	/** @todo do this on the server */
384 	/* Check if we have enough TUs (again) */
385 	if (CL_ActorUsableTUs(selActor) + CL_ActorReservedTUs(selActor, RES_SHOT) >= reserveShotData->TUs) {
386 		const objDef_t* od = INVSH_GetItemByIDX(reserveShotData->weaponIndex);
387 		if (GAME_ItemIsUseable(od)) {
388 			HUD_SetShootReservation(selActor, std::max(0, reserveShotData->TUs), reserveShotData->hand, reserveShotData->fireModeIndex, od);
389 			if (popupListNode)
390 				UI_TextNodeSelectLine(popupListNode, selectedPopupIndex);
391 		}
392 	}
393 }
394 
395 /**
396  * @brief Sets the display for a single weapon/reload HUD button.
397  * @param callback confunc callback
398  * @param actor The actor local entity
399  * @param ammo The ammo used by this firemode
400  * @param weapFdsIdx the weapon index in the fire definition array
401  * @param hand What list to display
402  * @param index Index of the actual firemode
403  */
HUD_DisplayFiremodeEntry(const char * callback,const le_t * actor,const objDef_t * ammo,const weaponFireDefIndex_t weapFdsIdx,const actorHands_t hand,const int index)404 static void HUD_DisplayFiremodeEntry (const char* callback, const le_t* actor, const objDef_t* ammo, const weaponFireDefIndex_t weapFdsIdx, const actorHands_t hand, const int index)
405 {
406 	if (index >= ammo->numFiredefs[weapFdsIdx])
407 		return;
408 
409 	/* We have a defined fd ... */
410 	const fireDef_t* fd = &ammo->fd[weapFdsIdx][index];
411 	const int time = CL_ActorTimeForFireDef(actor, fd);
412 
413 	assert(actor);
414 	assert(hand == ACTOR_HAND_RIGHT || hand == ACTOR_HAND_LEFT);
415 
416 	const bool status = time <= CL_ActorUsableTUs(actor);
417 	const int usableTusForRF = HUD_UsableReactionTUs(actor);
418 
419 	char tuString[MAX_VAR];
420 	const char* tooltip;
421 	if (usableTusForRF > time) {
422 		Com_sprintf(tuString, sizeof(tuString), _("Remaining TUs: %i"), usableTusForRF - time);
423 		tooltip = tuString;
424 	} else
425 		tooltip = _("No remaining TUs left after shot.");
426 
427 	/* unique identifier of the action */
428 	/* @todo use this id as action callback instead of hand and index (we can extend it with any other soldier action we need (open door, reload...)) */
429 	char id[32];
430 	Com_sprintf(id, sizeof(id), "fire_hand%c_i%i", ACTOR_GET_HAND_CHAR(hand), index);
431 
432 	UI_ExecuteConfunc("%s firemode %s %c %i %i %i \"%s\" \"%i\" \"%i\" \"%s\"", callback, id, ACTOR_GET_HAND_CHAR(hand),
433 			fd->fdIdx, fd->reaction, status, _(fd->name), time, fd->ammo, tooltip);
434 
435 	/* Display checkbox for reaction firemode */
436 	if (fd->reaction) {
437 		character_t* chr = CL_ActorGetChr(actor);
438 		const bool active = THIS_FIREMODE(&chr->RFmode, hand, fd->fdIdx);
439 		/* Change the state of the checkbox. */
440 		UI_ExecuteConfunc("%s reaction %s %c %i", callback, id, ACTOR_GET_HAND_CHAR(hand), active);
441 	}
442 }
443 
444 /**
445  * @brief List actions from a soldier to a callback confunc
446  * @param callback confunc callback
447  * @param actor actor who can do the actions
448  * @param type The action to display
449  */
HUD_DisplayActions(const char * callback,const le_t * actor,actionType_t type)450 static void HUD_DisplayActions (const char* callback, const le_t* actor, actionType_t type)
451 {
452 	int i;
453 
454 	if (!actor)
455 		return;
456 
457 	if (!cls.isOurRound()) {	/**< Not our turn */
458 		return;
459 	}
460 
461 	const ScopedCommand c(callback, "begin", "end");
462 
463 	switch (type) {
464 	case FIRE_RIGHT: {
465 		const actorHands_t hand = ACTOR_HAND_RIGHT;
466 		const fireDef_t* fd = HUD_GetFireDefinitionForHand(actor, hand);
467 		if (fd == nullptr) {
468 			UI_PopWindow();
469 			return;
470 		}
471 
472 		const objDef_t* ammo = fd->obj;
473 		if (!ammo) {
474 			Com_DPrintf(DEBUG_CLIENT, "HUD_DisplayFiremodes: no weapon or ammo found.\n");
475 			return;
476 		}
477 
478 		for (i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) {
479 			/* Display the firemode information (image + text). */
480 			HUD_DisplayFiremodeEntry(callback, actor, ammo, fd->weapFdsIdx, hand, i);
481 		}
482 	}
483 	break;
484 
485 	case RELOAD_RIGHT: {
486 		Item* weapon = actor->getRightHandItem();
487 
488 		/* Reloeadable item in hand. */
489 		if (weapon && weapon->def() && weapon->isReloadable()) {
490 			int tus;
491 			containerIndex_t container = CID_RIGHT;
492 			bool noAmmo;
493 			bool noTU;
494 			const char* actionId = "reload_handr";
495 
496 			tus = HUD_CalcReloadTime(actor, weapon->def(), container);
497 			noAmmo = tus == -1;
498 			noTU = actor->TU < tus;
499 			UI_ExecuteConfunc("%s reload %s %c %i %i %i", callback, actionId, 'r', tus, !noAmmo, !noTU);
500 		}
501 	}
502 	break;
503 
504 	case FIRE_LEFT: {
505 		const actorHands_t hand = ACTOR_HAND_LEFT;
506 		const fireDef_t* fd = HUD_GetFireDefinitionForHand(actor, hand);
507 		if (fd == nullptr) {
508 			UI_PopWindow();
509 			return;
510 		}
511 
512 		const objDef_t* ammo = fd->obj;
513 		if (!ammo) {
514 			Com_DPrintf(DEBUG_CLIENT, "HUD_DisplayFiremodes: no weapon or ammo found.\n");
515 			return;
516 		}
517 
518 		for (i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) {
519 			/* Display the firemode information (image + text). */
520 			HUD_DisplayFiremodeEntry(callback, actor, ammo, fd->weapFdsIdx, hand, i);
521 		}
522 	}
523 	break;
524 
525 	case RELOAD_LEFT: {
526 		Item* weapon = actor->getLeftHandItem();
527 
528 		/* Reloadable item in hand. */
529 		if (weapon && weapon->def() && weapon->isReloadable()) {
530 			containerIndex_t container = CID_LEFT;
531 			const char* actionId = "reload_handl";
532 			const int tus = HUD_CalcReloadTime(actor, weapon->def(), container);
533 			const bool noAmmo = tus == -1;
534 			const bool noTU = actor->TU < tus;
535 			UI_ExecuteConfunc("%s reload %s %c %i %i %i", callback, actionId, 'l', tus, !noAmmo, !noTU);
536 		}
537 	}
538 	break;
539 	}
540 }
541 
542 /**
543  * @brief Displays the firemodes for the given hand.
544  * @todo we can extend it with short cut equip action, more reload, action on the map (like open doors)...
545  */
HUD_DisplayActions_f(void)546 static void HUD_DisplayActions_f (void)
547 {
548 	if (Cmd_Argc() < 3) {
549 		Com_Printf("Usage: %s callback [l|r|L|R]\n", Cmd_Argv(0));
550 		return;
551 	}
552 
553 	if (!selActor)
554 		return;
555 
556 	actionType_t type;
557 	if (strchr(Cmd_Argv(2), 'r') != nullptr) {
558 		type = FIRE_RIGHT;
559 	} else if (strchr(Cmd_Argv(2), 'l') != nullptr) {
560 		type = FIRE_LEFT;
561 	} else if (strchr(Cmd_Argv(2), 'R') != nullptr) {
562 		type = RELOAD_RIGHT;
563 	} else if (strchr(Cmd_Argv(2), 'L') != nullptr) {
564 		type = RELOAD_LEFT;
565 	} else {
566 		return;
567 	}
568 
569 	char callback[32];
570 	Q_strncpyz(callback, Cmd_Argv(1), sizeof(callback));
571 	HUD_DisplayActions(callback, selActor, type);
572 }
573 
574 /**
575  * @brief Displays the firemodes for the given hand.
576  */
HUD_DisplayFiremodes_f(void)577 static void HUD_DisplayFiremodes_f (void)
578 {
579 	if (Cmd_Argc() < 3) {
580 		Com_Printf("Usage: %s callback [l|r]\n", Cmd_Argv(0));
581 		return;
582 	}
583 
584 	if (!selActor)
585 		return;
586 
587 	if (selActor->isMoving())
588 		return;
589 
590 	const actorHands_t hand = ACTOR_GET_HAND_INDEX(Cmd_Argv(2)[0]);
591 	const actionType_t type = hand == ACTOR_HAND_RIGHT ? FIRE_RIGHT : FIRE_LEFT;
592 	char callback[32];
593 	Q_strncpyz(callback, Cmd_Argv(1), sizeof(callback));
594 	HUD_DisplayActions(callback, selActor, type);
595 }
596 
597 /**
598  * @brief Updates the information in RFmode for the selected actor with the given data from the parameters.
599  * @param[in] actor The actor we want to update the reaction-fire firemode for.
600  * @param[in] hand Which weapon(-hand) to use.
601  * @param[in] firemodeActive Set this to the firemode index you want to activate or set it to -1 if the default one (currently the first one found) should be used.
602  */
HUD_UpdateReactionFiremodes(const le_t * actor,const actorHands_t hand,fireDefIndex_t firemodeActive)603 static void HUD_UpdateReactionFiremodes (const le_t* actor, const actorHands_t hand, fireDefIndex_t firemodeActive)
604 {
605 	const fireDef_t* fd;
606 	const objDef_t* ammo, *od;
607 
608 	assert(actor);
609 
610 	fd = HUD_GetFireDefinitionForHand(actor, hand);
611 	if (fd == nullptr)
612 		return;
613 
614 	ammo = fd->obj;
615 	od = ammo->weapons[fd->weapFdsIdx];
616 
617 	if (!GAME_ItemIsUseable(od))
618 		return;
619 
620 	MSG_Write_PA(PA_REACT_SELECT, actor->entnum, hand, firemodeActive, od ? od->idx : NONE);
621 }
622 
623 /**
624  * @brief Checks if the selected firemode checkbox is ok as a reaction firemode and updates data+display.
625  */
HUD_SelectReactionFiremode_f(void)626 static void HUD_SelectReactionFiremode_f (void)
627 {
628 	if (Cmd_Argc() < 3) { /* no argument given */
629 		Com_Printf("Usage: %s [l|r] <num>   num=firemode number\n", Cmd_Argv(0));
630 		return;
631 	}
632 
633 	if (!selActor)
634 		return;
635 
636 	const fireDefIndex_t firemode = atoi(Cmd_Argv(2));
637 	if (firemode >= MAX_FIREDEFS_PER_WEAPON || firemode < 0) {
638 		Com_Printf("HUD_SelectReactionFiremode_f: Firemode out of bounds (%i).\n", firemode);
639 		return;
640 	}
641 
642 	const actorHands_t hand = ACTOR_GET_HAND_INDEX(Cmd_Argv(1)[0]);
643 	HUD_UpdateReactionFiremodes(selActor, hand, firemode);
644 }
645 
646 /**
647  * @brief Remember if we hover over a button that would cost some TUs when pressed.
648  * @note this is used in HUD_Update to update the "remaining TUs" bar correctly.
649  */
HUD_RemainingTUs_f(void)650 static void HUD_RemainingTUs_f (void)
651 {
652 	if (Cmd_Argc() < 3) {
653 		Com_Printf("Usage: %s <type> <popupindex>\n", Cmd_Argv(0));
654 		return;
655 	}
656 
657 	const char* type = Cmd_Argv(1);
658 	const bool state = Com_ParseBoolean(Cmd_Argv(2));
659 
660 	OBJZERO(displayRemainingTus);
661 
662 	if (Q_streq(type, "reload_r")) {
663 		displayRemainingTus[REMAINING_TU_RELOAD_RIGHT] = state;
664 	} else if (Q_streq(type, "reload_l")) {
665 		displayRemainingTus[REMAINING_TU_RELOAD_LEFT] = state;
666 	} else if (Q_streq(type, "crouch")) {
667 		displayRemainingTus[REMAINING_TU_CROUCH] = state;
668 	}
669 }
670 
671 /**
672  * @return The minimum time needed to fire the weapon
673  */
HUD_GetMinimumTUsForUsage(const Item * item)674 static int HUD_GetMinimumTUsForUsage (const Item* item)
675 {
676 	/** @todo what is this 100? replace with constant please - MAX_TUS? */
677 	int time = 100;
678 
679 	assert(item->def());
680 
681 	const fireDef_t* fdArray = item->getFiredefs();
682 	if (fdArray == nullptr)
683 		return time;
684 
685 	/* Search for the smallest TU needed to shoot. */
686 	for (int i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) {
687 		if (!fdArray[i].time)
688 			continue;
689 		if (fdArray[i].time < time)
690 			time = fdArray[i].time;
691 	}
692 
693 	return time;
694 }
695 
696 /**
697  * @brief Checks every case for reload buttons on the HUD.
698  * @param[in] le Pointer of local entity being an actor.
699  * @param[in] containerID of the container to reload the weapon in. Used to get the movement TUs for moving something into the container.
700  * @param[out] reason The reason why the reload didn't work - only set if @c -1 is the return value
701  * @return TU units needed for reloading or -1 if weapon cannot be reloaded.
702  */
HUD_WeaponCanBeReloaded(const le_t * le,containerIndex_t containerID,const char ** reason)703 static int HUD_WeaponCanBeReloaded (const le_t* le, containerIndex_t containerID, const char** reason)
704 {
705 	const Item* invList = le->inv.getContainer2(containerID);
706 
707 	/* No weapon in hand. */
708 	if (!invList) {
709 		*reason = _("No weapon.");
710 		return -1;
711 	}
712 
713 	const objDef_t* weapon = invList->def();
714 	assert(weapon);
715 
716 	/* This weapon cannot be reloaded. */
717 	if (!weapon->isReloadable()) {
718 		*reason = _("Weapon cannot be reloaded.");
719 		return -1;
720 	}
721 
722 	/* Weapon is fully loaded. */
723 	if (invList->ammoDef() && weapon->ammo == invList->getAmmoLeft()) {
724 		*reason = _("No reload possible, already fully loaded.");
725 		return -1;
726 	}
727 
728 	/* Weapon is empty or not fully loaded, find ammo of any type loadable to this weapon. */
729 	if (!invList->ammoDef() || weapon->ammo > invList->getAmmoLeft()) {
730 		const int tuCosts = HUD_CalcReloadTime(le, weapon, containerID);
731 		if (tuCosts >= 0) {
732 			const int tu = CL_ActorUsableTUs(le);
733 			if (tu >= tuCosts)
734 				return tuCosts;
735 			*reason = _("Not enough TUs for reloading weapon.");
736 		} else {
737 			/* Found no ammo which could be used for this weapon. */
738 			*reason = _("No reload possible, you don't have backup ammo.");
739 		}
740 	}
741 
742 	return -1;
743 }
744 
745 /**
746  * @brief Display 'impossible' (red) reaction buttons.
747  * @param[in] actor the actor to check for his reaction state.
748  * @return true if nothing changed message was sent otherwise false.
749  * @note this is called when reaction is enabled with no suitable weapon.
750  * @todo at the moment of writing, this should be impossible: reaction
751  * should be disabled whenever the actor loses its reaction weapon.
752  */
HUD_DisplayImpossibleReaction(const le_t * actor)753 static bool HUD_DisplayImpossibleReaction (const le_t* actor)
754 {
755 	if (!actor)
756 		return false;
757 
758 	/* Given actor does not equal the currently selected actor. */
759 	if (!LE_IsSelected(actor))
760 		return false;
761 
762 	if (buttonStates[BT_REACTION] == BT_STATE_UNINITIALZED)
763 		return false;
764 
765 	/* Display 'impossible" (red) reaction buttons */
766 	if (actor->state & STATE_REACTION) {
767 		Com_Printf("HUD_DisplayImpossibleReaction: Warning reaction fire enable and no suitable weapon found!\n");
768 		UI_ExecuteConfunc("startreaction_impos");
769 		buttonStates[BT_REACTION] = BT_STATE_UNINITIALZED;
770 		return false;
771 	}
772 
773 	return true;
774 }
775 
776 /**
777  * @brief Display 'usable' (blue) reaction buttons.
778  * @param[in] actor the actor to check for his reaction state.
779  */
HUD_DisplayPossibleReaction(const le_t * actor)780 static void HUD_DisplayPossibleReaction (const le_t* actor)
781 {
782 	if (!actor)
783 		return;
784 	/* Given actor does not equal the currently selected actor. This normally only happens on game-start. */
785 	if (!LE_IsSelected(actor))
786 		return;
787 
788 	if (buttonStates[BT_REACTION] == BT_STATE_SELECT)
789 		return;
790 
791 	/* Display 'usable" (blue) reaction buttons */
792 	if (actor->state & STATE_REACTION) {
793 		UI_ExecuteConfunc("startreaction");
794 		buttonStates[BT_REACTION] = BT_STATE_SELECT;
795 	}
796 }
797 
798 /**
799  * @brief Get the weapon firing TUs for reaction fire.
800  * @param[in] actor the actor to check for his reaction TUs.
801  * @return The TUs needed for the reaction fireDef for this actor or -1 if no valid reaction settings
802  */
HUD_ReactionFireGetTUs(const le_t * actor)803 int HUD_ReactionFireGetTUs (const le_t* actor)
804 {
805 	if (!actor)
806 		return -1;
807 
808 	const FiremodeSettings &fmSetting = CL_ActorGetChr(actor)->RFmode;
809 	const Item* weapon = actor->getHandItem(fmSetting.getHand());
810 
811 	if (!weapon)
812 		weapon = actor->getRightHandItem();
813 	if (!weapon)
814 		weapon = actor->getLeftHandItem();
815 
816 	if (weapon && weapon->ammoDef() && weapon->isWeapon()) {
817 		const fireDef_t* fdArray = weapon->getFiredefs();
818 		if (fdArray == nullptr)
819 			return -1;
820 
821 		const fireDefIndex_t fmIdx = fmSetting.getFmIdx();
822 		if (fmIdx >= 0 && fmIdx < MAX_FIREDEFS_PER_WEAPON) {
823 			return CL_ActorTimeForFireDef(actor, &fdArray[fmIdx], true);
824 		}
825 	}
826 
827 	return -1;
828 }
829 
830 /**
831  * @brief Refreshes the weapon/reload buttons on the HUD.
832  * @param[in] le Pointer to local entity for which we refresh HUD buttons.
833  */
HUD_UpdateButtons(const le_t * le)834 static void HUD_UpdateButtons (const le_t* le)
835 {
836 	if (!le)
837 		return;
838 
839 	Item* weaponR = le->getRightHandItem();
840 	Item* headgear = le->inv.getHeadgear();
841 
842 	Item* weaponL;
843 	/* check for two-handed weapon - if not, also define weaponL */
844 	if (!weaponR || !weaponR->isHeldTwoHanded())
845 		weaponL = le->getLeftHandItem();
846 	else
847 		weaponL = nullptr;
848 
849 	const int time = CL_ActorUsableTUs(le);
850 	/* Crouch/stand button. */
851 	if (LE_IsCrouched(le)) {
852 		buttonStates[BT_STAND] =  BT_STATE_UNINITIALZED;
853 		if (time + CL_ActorReservedTUs(le, RES_CROUCH) < TU_CROUCH) {
854 			Cvar_Set("mn_crouchstand_tt", _("Not enough TUs for standing up."));
855 			HUD_SetWeaponButton(BT_CROUCH, BT_STATE_DISABLE);
856 		} else {
857 			Cvar_Set("mn_crouchstand_tt", _("Stand up (%i TU)"), TU_CROUCH);
858 			HUD_SetWeaponButton(BT_CROUCH, BT_STATE_DESELECT);
859 		}
860 	} else {
861 		buttonStates[BT_CROUCH] =  BT_STATE_UNINITIALZED;
862 		if (time + CL_ActorReservedTUs(le, RES_CROUCH) < TU_CROUCH) {
863 			Cvar_Set("mn_crouchstand_tt", _("Not enough TUs for crouching."));
864 			HUD_SetWeaponButton(BT_STAND, BT_STATE_DISABLE);
865 		} else {
866 			Cvar_Set("mn_crouchstand_tt", _("Crouch (%i TU)"), TU_CROUCH);
867 			HUD_SetWeaponButton(BT_STAND, BT_STATE_DESELECT);
868 		}
869 	}
870 
871 	/* Crouch/stand reservation checkbox. */
872 	if (CL_ActorReservedTUs(le, RES_CROUCH) >= TU_CROUCH) {
873 		if (crouchReserveButtonState != BUTTON_TURESERVE_CROUCH_RESERVED) {
874 			UI_ExecuteConfunc("crouch_checkbox_check");
875 			Cvar_Set("mn_crouch_reservation_tt", _("%i TUs reserved for crouching/standing up.\nClick to clear."),
876 					CL_ActorReservedTUs(le, RES_CROUCH));
877 			crouchReserveButtonState = BUTTON_TURESERVE_CROUCH_RESERVED;
878 		}
879 	} else if (time >= TU_CROUCH) {
880 		if (crouchReserveButtonState != BUTTON_TURESERVE_CROUCH_CLEAR) {
881 			UI_ExecuteConfunc("crouch_checkbox_clear");
882 			Cvar_Set("mn_crouch_reservation_tt", _("Reserve %i TUs for crouching/standing up."), TU_CROUCH);
883 			crouchReserveButtonState = BUTTON_TURESERVE_CROUCH_CLEAR;
884 		}
885 	} else {
886 		if (crouchReserveButtonState != BUTTON_TURESERVE_CROUCH_DISABLED) {
887 			UI_ExecuteConfunc("crouch_checkbox_disable");
888 			Cvar_Set("mn_crouch_reservation_tt", _("Not enough TUs left to reserve for crouching/standing up."));
889 			crouchReserveButtonState = BUTTON_TURESERVE_CROUCH_DISABLED;
890 		}
891 	}
892 
893 	/* Shot reservation button. mn_shot_reservation_tt is the tooltip text */
894 	if (CL_ActorReservedTUs(le, RES_SHOT)) {
895 		if (shotReserveButtonState != BUTTON_TURESERVE_SHOT_RESERVED) {
896 			UI_ExecuteConfunc("reserve_shot_check");
897 			Cvar_Set("mn_shot_reservation_tt", _("%i TUs reserved for shooting.\nClick to change.\nRight-Click to clear."),
898 					CL_ActorReservedTUs(le, RES_SHOT));
899 			shotReserveButtonState = BUTTON_TURESERVE_SHOT_RESERVED;
900 		}
901 	} else if (HUD_CheckFiremodeReservation()) {
902 		if (shotReserveButtonState != BUTTON_TURESERVE_SHOT_CLEAR) {
903 			UI_ExecuteConfunc("reserve_shot_clear");
904 			Cvar_Set("mn_shot_reservation_tt", _("Reserve TUs for shooting."));
905 			shotReserveButtonState = BUTTON_TURESERVE_SHOT_CLEAR;
906 		}
907 	} else {
908 		if (shotReserveButtonState != BUTTON_TURESERVE_SHOT_DISABLED) {
909 			UI_ExecuteConfunc("reserve_shot_disable");
910 			Cvar_Set("mn_shot_reservation_tt", _("Reserving TUs for shooting not possible."));
911 			shotReserveButtonState = BUTTON_TURESERVE_SHOT_DISABLED;
912 		}
913 	}
914 
915 	/* reaction-fire button */
916 	if (!(le->state & STATE_REACTION)) {
917 		if (le->inv.holdsReactionFireWeapon() && time >= HUD_ReactionFireGetTUs(le))
918 			HUD_SetWeaponButton(BT_REACTION, BT_STATE_DESELECT);
919 		else
920 			HUD_SetWeaponButton(BT_REACTION, BT_STATE_DISABLE);
921 	} else {
922 		if (le->inv.holdsReactionFireWeapon()) {
923 			HUD_DisplayPossibleReaction(le);
924 		} else {
925 			HUD_DisplayImpossibleReaction(le);
926 		}
927 	}
928 
929 	const char* reason;
930 
931 	/* Reload buttons */
932 	const int rightCanBeReloaded = HUD_WeaponCanBeReloaded(le, CID_RIGHT, &reason);
933 	if (rightCanBeReloaded != -1) {
934 		HUD_SetWeaponButton(BT_RIGHT_RELOAD, BT_STATE_DESELECT);
935 		Cvar_Set("mn_reloadright_tt", _("Reload weapon (%i TU)."), rightCanBeReloaded);
936 	} else {
937 		Cvar_Set("mn_reloadright_tt", "%s", reason);
938 		HUD_SetWeaponButton(BT_RIGHT_RELOAD, BT_STATE_DISABLE);
939 	}
940 
941 	const int leftCanBeReloaded = HUD_WeaponCanBeReloaded(le, CID_LEFT, &reason);
942 	if (leftCanBeReloaded != -1) {
943 		HUD_SetWeaponButton(BT_LEFT_RELOAD, BT_STATE_DESELECT);
944 		Cvar_Set("mn_reloadleft_tt", _("Reload weapon (%i TU)."), leftCanBeReloaded);
945 	} else {
946 		Cvar_Set("mn_reloadleft_tt", "%s", reason);
947 		HUD_SetWeaponButton(BT_LEFT_RELOAD, BT_STATE_DISABLE);
948 	}
949 
950 	const float shootingPenalty = CL_ActorInjuryModifier(le, MODIFIER_SHOOTING);
951 	/* Headgear button */
952 	if (headgear) {
953 		const int minheadgeartime = HUD_GetMinimumTUsForUsage(headgear) * shootingPenalty;
954 		if (time < minheadgeartime)
955 			HUD_SetWeaponButton(BT_HEADGEAR, BT_STATE_DISABLE);
956 		else
957 			HUD_SetWeaponButton(BT_HEADGEAR, BT_STATE_DESELECT);
958 	} else {
959 		HUD_SetWeaponButton(BT_HEADGEAR, BT_STATE_DISABLE);
960 	}
961 
962 	/* Weapon firing buttons. */
963 	if (weaponR) {
964 		const int minweaponrtime = HUD_GetMinimumTUsForUsage(weaponR) * shootingPenalty;
965 		if (time < minweaponrtime)
966 			HUD_SetWeaponButton(BT_RIGHT_FIRE, BT_STATE_DISABLE);
967 		else
968 			HUD_SetWeaponButton(BT_RIGHT_FIRE, BT_STATE_DESELECT);
969 	} else {
970 		HUD_SetWeaponButton(BT_RIGHT_FIRE, BT_STATE_DISABLE);
971 	}
972 
973 	if (weaponL) {
974 		const int minweaponltime = HUD_GetMinimumTUsForUsage(weaponL) * shootingPenalty;
975 		if (time < minweaponltime)
976 			HUD_SetWeaponButton(BT_LEFT_FIRE, BT_STATE_DISABLE);
977 		else
978 			HUD_SetWeaponButton(BT_LEFT_FIRE, BT_STATE_DESELECT);
979 	} else {
980 		HUD_SetWeaponButton(BT_LEFT_FIRE, BT_STATE_DISABLE);
981 	}
982 
983 	/* Check if the firemode reservation popup is shown and refresh its content. (i.e. close&open it) */
984 	{
985 		const char* menuName = UI_GetActiveWindowName();
986 		if (menuName[0] != '\0' && strstr(UI_GetActiveWindowName(), POPUPLIST_NODE_NAME)) {
987 			/* Update firemode reservation popup. */
988 			/** @todo this is called every frames... is this really needed? */
989 			HUD_PopupFiremodeReservation(le, true);
990 		}
991 	}
992 }
993 
994 /**
995  * @brief Draw the mouse cursor tooltips in battlescape
996  * @param xOffset
997  * @param yOffset
998  * @param textId The text id to get the tooltip string from.
999  */
HUD_DrawMouseCursorText(int xOffset,int yOffset,int textId)1000 static void HUD_DrawMouseCursorText (int xOffset, int yOffset, int textId)
1001 {
1002 	const char* string = UI_GetText(textId);
1003 	if (string && cl_show_cursor_tooltips->integer)
1004 		UI_DrawTooltip(string, mousePosX + xOffset, mousePosY - yOffset, viddef.virtualWidth - mousePosX);
1005 }
1006 
1007 /**
1008  * @brief Updates the cursor texts when in battlescape
1009  */
HUD_UpdateCursor(void)1010 void HUD_UpdateCursor (void)
1011 {
1012 	/* Offset of the first icon on the x-axis. */
1013 	const int iconOffsetX = 16;
1014 	le_t* le = selActor;
1015 	if (le) {
1016 		/* Offset of the first icon on the y-axis. */
1017 		int iconOffsetY = 16;
1018 		/* the space between different icons. */
1019 		const int iconSpacing = 2;
1020 		image_t* image;
1021 		/* icon width */
1022 		const int iconW = 16;
1023 		/* icon height. */
1024 		const int iconH = 16;
1025 		int width = 0;
1026 		int bgX = mousePosX + iconOffsetX / 2 - 2;
1027 
1028 		/* checks if icons should be drawn */
1029 		if (!(LE_IsCrouched(le) || (le->state & STATE_REACTION)))
1030 			/* make place holder for icons */
1031 			bgX += iconW + 4;
1032 
1033 		/* if exists gets width of player name */
1034 		if (UI_GetText(TEXT_MOUSECURSOR_PLAYERNAMES))
1035 			R_FontTextSize("f_verysmall", UI_GetText(TEXT_MOUSECURSOR_PLAYERNAMES), viddef.virtualWidth - bgX, LONGLINES_WRAP, &width, nullptr, nullptr, nullptr);
1036 
1037 		/* gets width of background */
1038 		if (width == 0 && UI_GetText(TEXT_MOUSECURSOR_RIGHT)) {
1039 			R_FontTextSize("f_verysmall", UI_GetText(TEXT_MOUSECURSOR_RIGHT), viddef.virtualWidth - bgX, LONGLINES_WRAP, &width, nullptr, nullptr, nullptr);
1040 		}
1041 
1042 		/* Display 'crouch' icon if actor is crouched. */
1043 		if (LE_IsCrouched(le)) {
1044 			image = R_FindImage("pics/cursors/ducked", it_pic);
1045 			if (image)
1046 				R_DrawImage(mousePosX - image->width / 2 + iconOffsetX, mousePosY - image->height / 2 + iconOffsetY, image);
1047 		}
1048 
1049 		/* Height of 'crouched' icon. */
1050 		iconOffsetY += 16;
1051 		iconOffsetY += iconSpacing;
1052 
1053 		/* Display 'Reaction shot' icon if actor has it activated. */
1054 		if (le->state & STATE_REACTION)
1055 			image = R_FindImage("pics/cursors/reactionfire", it_pic);
1056 		else
1057 			image = nullptr;
1058 
1059 		if (image)
1060 			R_DrawImage(mousePosX - image->width / 2 + iconOffsetX, mousePosY - image->height / 2 + iconOffsetY, image);
1061 
1062 		/* Height of 'reaction fire' icon. ... just in case we add further icons below.*/
1063 		iconOffsetY += iconH;
1064 		iconOffsetY += iconSpacing;
1065 
1066 		/* Display weaponmode (text) heR_ */
1067 		HUD_DrawMouseCursorText(iconOffsetX + iconW, -10, TEXT_MOUSECURSOR_RIGHT);
1068 	}
1069 
1070 	/* playernames */
1071 	HUD_DrawMouseCursorText(iconOffsetX + 16, -26, TEXT_MOUSECURSOR_PLAYERNAMES);
1072 	UI_ResetData(TEXT_MOUSECURSOR_PLAYERNAMES);
1073 
1074 	if (cl_map_debug->integer & MAPDEBUG_TEXT) {
1075 		/* Display ceiling text */
1076 		HUD_DrawMouseCursorText(0, -64, TEXT_MOUSECURSOR_TOP);
1077 		/* Display floor text */
1078 		HUD_DrawMouseCursorText(0, 64, TEXT_MOUSECURSOR_BOTTOM);
1079 		/* Display left text */
1080 		HUD_DrawMouseCursorText(-64, 0, TEXT_MOUSECURSOR_LEFT);
1081 	}
1082 }
1083 
1084 /**
1085  * @brief Shows map pathfinding debugging parameters (if activated)
1086  * @param[in] le The current selected actors entity
1087  */
HUD_MapDebugCursor(const le_t * le)1088 static void HUD_MapDebugCursor (const le_t* le)
1089 {
1090 	if (!(cl_map_debug->integer & MAPDEBUG_TEXT))
1091 		return;
1092 
1093 	/* Display the floor and ceiling values for the current cell. */
1094 	static char topText[UI_MAX_SMALLTEXTLEN];
1095 	Com_sprintf(topText, lengthof(topText), "%u-(%i,%i,%i)\n",
1096 			Grid_Ceiling(cl.mapData->routing, ACTOR_GET_FIELDSIZE(le), truePos), truePos[0], truePos[1], truePos[2]);
1097 	/* Save the text for later display next to the cursor. */
1098 	UI_RegisterText(TEXT_MOUSECURSOR_TOP, topText);
1099 
1100 	/* Display the floor and ceiling values for the current cell. */
1101 	static char bottomText[UI_MAX_SMALLTEXTLEN];
1102 	Com_sprintf(bottomText, lengthof(bottomText), "%i-(%i,%i,%i)\n",
1103 			Grid_Floor(cl.mapData->routing, ACTOR_GET_FIELDSIZE(le), truePos), mousePos[0], mousePos[1], mousePos[2]);
1104 	/* Save the text for later display next to the cursor. */
1105 	UI_RegisterText(TEXT_MOUSECURSOR_BOTTOM, bottomText);
1106 
1107 	/* Display the floor and ceiling values for the current cell. */
1108 	const int dvec = Grid_MoveNext(&cl.pathMap, mousePos, 0);
1109 	static char leftText[UI_MAX_SMALLTEXTLEN];
1110 	Com_sprintf(leftText, lengthof(leftText), "%i-%i\n", getDVdir(dvec), getDVz(dvec));
1111 	/* Save the text for later display next to the cursor. */
1112 	UI_RegisterText(TEXT_MOUSECURSOR_LEFT, leftText);
1113 }
1114 
1115 /**
1116  * @param actor The actor to update the hud for
1117  * @return The amount of TUs needed for the current pending action
1118  */
HUD_UpdateActorFireMode(le_t * actor)1119 static int HUD_UpdateActorFireMode (le_t* actor)
1120 {
1121 	const Item* selWeapon;
1122 
1123 	/* get weapon */
1124 	if (IS_MODE_FIRE_HEADGEAR(actor->actorMode)) {
1125 		selWeapon = actor->inv.getHeadgear();
1126 	} else if (IS_MODE_FIRE_LEFT(actor->actorMode)) {
1127 		selWeapon = HUD_GetLeftHandWeapon(actor, nullptr);
1128 	} else {
1129 		selWeapon = actor->getRightHandItem();
1130 	}
1131 
1132 	UI_ResetData(TEXT_MOUSECURSOR_RIGHT);
1133 
1134 	if (!selWeapon) {
1135 		CL_ActorSetMode(actor, M_MOVE);
1136 		return 0;
1137 	}
1138 
1139 	static char infoText[UI_MAX_SMALLTEXTLEN];
1140 	int time = 0;
1141 	const objDef_t* def = selWeapon->def();
1142 	if (!def) {
1143 		/* No valid weapon in the hand. */
1144 		CL_ActorSetFireDef(actor, nullptr);
1145 	} else {
1146 		/* Check whether this item uses/has ammo. */
1147 		if (!selWeapon->ammoDef()) {
1148 			const fireDef_t* old = nullptr;
1149 			/* This item does not use ammo, check for existing firedefs in this item. */
1150 			/* This is supposed to be a weapon or other usable item. */
1151 			if (def->numWeapons > 0) {
1152 				if (selWeapon->isWeapon() || def->weapons[0] == def) {
1153 					const fireDef_t* fdArray = selWeapon->getFiredefs();
1154 					if (fdArray != nullptr) {
1155 						/* Get firedef from the weapon (or other usable item) entry instead. */
1156 						old = FIRESH_GetFiredef(def, fdArray->weapFdsIdx, actor->currentSelectedFiremode);
1157 					}
1158 				}
1159 			}
1160 			CL_ActorSetFireDef(actor, old);
1161 		} else {
1162 			const fireDef_t* fdArray = selWeapon->getFiredefs();
1163 			if (fdArray != nullptr) {
1164 				const fireDef_t* old = FIRESH_GetFiredef(selWeapon->ammoDef(), fdArray->weapFdsIdx, actor->currentSelectedFiremode);
1165 				/* reset the align if we switched the firemode */
1166 				CL_ActorSetFireDef(actor, old);
1167 			}
1168 		}
1169 	}
1170 
1171 	if (!GAME_ItemIsUseable(def)) {
1172 		HUD_DisplayMessage(_("You cannot use this unknown item.\nYou need to research it first."));
1173 		CL_ActorSetMode(actor, M_MOVE);
1174 	} else if (actor->fd) {
1175 		const int hitProbability = CL_GetHitProbability(actor);
1176 		static char mouseText[UI_MAX_SMALLTEXTLEN];
1177 
1178 		Com_sprintf(infoText, lengthof(infoText),
1179 					"%s\n%s (%i) [%i%%] %i\n", _(def->name), _(actor->fd->name),
1180 					actor->fd->ammo, hitProbability, CL_ActorTimeForFireDef(actor, actor->fd));
1181 
1182 		/* Save the text for later display next to the cursor. */
1183 		Q_strncpyz(mouseText, infoText, lengthof(mouseText));
1184 		UI_RegisterText(TEXT_MOUSECURSOR_RIGHT, mouseText);
1185 
1186 		time = CL_ActorTimeForFireDef(actor, actor->fd);
1187 		/* if no TUs left for this firing action
1188 		 * or if the weapon is reloadable and out of ammo,
1189 		 * then change to move mode */
1190 		if (selWeapon->mustReload() || CL_ActorUsableTUs(actor) < time)
1191 			CL_ActorSetMode(actor, M_MOVE);
1192 	} else if (selWeapon) {
1193 		Com_sprintf(infoText, lengthof(infoText), _("%s\n(empty)\n"), _(def->name));
1194 	}
1195 
1196 	UI_RegisterText(TEXT_STANDARD, infoText);
1197 	return time;
1198 }
1199 
1200 /**
1201  * @param[in] actor The actor to update the hud for
1202  * @return The amount of TUs needed for the current pending action
1203  */
HUD_UpdateActorMove(const le_t * actor)1204 static int HUD_UpdateActorMove (const le_t* actor)
1205 {
1206 	const int reservedTUs = CL_ActorReservedTUs(actor, RES_ALL_ACTIVE);
1207 	static char infoText[UI_MAX_SMALLTEXTLEN];
1208 	if (actor->actorMoveLength == ROUTING_NOT_REACHABLE) {
1209 		UI_ResetData(TEXT_MOUSECURSOR_RIGHT);
1210 		if (reservedTUs > 0)
1211 			Com_sprintf(infoText, lengthof(infoText), _("Morale  %i | Reserved TUs: %i\n"), actor->morale, reservedTUs);
1212 		else
1213 			Com_sprintf(infoText, lengthof(infoText), _("Morale  %i"), actor->morale);
1214 	} else {
1215 		static char mouseText[UI_MAX_SMALLTEXTLEN];
1216 		const int moveMode = CL_ActorMoveMode(actor);
1217 		if (reservedTUs > 0)
1218 			Com_sprintf(infoText, lengthof(infoText), _("Morale  %i | Reserved TUs: %i\n%s %i (%i|%i TUs left)\n"),
1219 				actor->morale, reservedTUs, _(moveModeDescriptions[moveMode]), actor->actorMoveLength,
1220 				actor->TU - actor->actorMoveLength, actor->TU - reservedTUs - actor->actorMoveLength);
1221 		else
1222 			Com_sprintf(infoText, lengthof(infoText), _("Morale  %i\n%s %i (%i TUs left)\n"), actor->morale,
1223 				_(moveModeDescriptions[moveMode]), actor->actorMoveLength, actor->TU - actor->actorMoveLength);
1224 
1225 		if (actor->actorMoveLength <= CL_ActorUsableTUs(actor))
1226 			Com_sprintf(mouseText, lengthof(mouseText), "%i (%i)\n", actor->actorMoveLength, CL_ActorUsableTUs(actor));
1227 		else
1228 			Com_sprintf(mouseText, lengthof(mouseText), "- (-)\n");
1229 
1230 		UI_RegisterText(TEXT_MOUSECURSOR_RIGHT, mouseText);
1231 	}
1232 
1233 	UI_RegisterText(TEXT_STANDARD, infoText);
1234 
1235 	return actor->actorMoveLength;
1236 }
1237 
HUD_UpdateActorCvar(const le_t * actor)1238 static void HUD_UpdateActorCvar (const le_t* actor)
1239 {
1240 	Cvar_SetValue("mn_hp", actor->HP);
1241 	Cvar_SetValue("mn_hpmax", actor->maxHP);
1242 	Cvar_SetValue("mn_tu", actor->TU);
1243 	Cvar_SetValue("mn_tumax", actor->maxTU);
1244 	Cvar_SetValue("mn_tureserved", CL_ActorReservedTUs(actor, RES_ALL_ACTIVE));
1245 	Cvar_SetValue("mn_morale", actor->morale);
1246 	Cvar_SetValue("mn_moralemax", actor->maxMorale);
1247 	Cvar_SetValue("mn_stun", actor->STUN);
1248 
1249 	Cvar_Set("mn_tu_tooltips", _("Time Units\n- Available: %i (of %i)\n- Reserved:  %i\n- Remaining: %i\n"),
1250 			actor->TU, actor->maxTU, CL_ActorReservedTUs(actor, RES_ALL_ACTIVE), CL_ActorUsableTUs(actor));
1251 
1252 	/* animation and weapons */
1253 	const char* animName = R_AnimGetName(&actor->as, actor->model1);
1254 	if (animName)
1255 		Cvar_Set("mn_anim", "%s", animName);
1256 	if (actor->getRightHandItem()) {
1257 		const Item* item = actor->getRightHandItem();
1258 		Cvar_Set("mn_rweapon", "%s", item->def()->model);
1259 		Cvar_Set("mn_rweapon_item", "%s", item->def()->id);
1260 	} else {
1261 		Cvar_Set("mn_rweapon", "");
1262 		Cvar_Set("mn_rweapon_item", "");
1263 	}
1264 	if (actor->getLeftHandItem()) {
1265 		const Item* item = actor->getLeftHandItem();
1266 		Cvar_Set("mn_lweapon", "%s", item->def()->model);
1267 		Cvar_Set("mn_lweapon_item", "%s", item->def()->id);
1268 	} else {
1269 		Cvar_Set("mn_lweapon", "");
1270 		Cvar_Set("mn_lweapon_item", "");
1271 	}
1272 
1273 	/* print ammo */
1274 	const Item* itemRight = actor->getRightHandItem();
1275 	if (itemRight)
1276 		Cvar_SetValue("mn_ammoright", itemRight->getAmmoLeft());
1277 	else
1278 		Cvar_Set("mn_ammoright", "");
1279 
1280 	const Item* itemLeft = HUD_GetLeftHandWeapon(actor, nullptr);
1281 	if (itemLeft)
1282 		Cvar_SetValue("mn_ammoleft", itemLeft->getAmmoLeft());
1283 	else
1284 		Cvar_Set("mn_ammoleft", "");
1285 }
1286 
HUD_GetPenaltyString(const int type)1287 static const char* HUD_GetPenaltyString (const int type)
1288 {
1289 	switch (type) {
1290 	case MODIFIER_ACCURACY:
1291 		return _("accuracy");
1292 	case MODIFIER_SHOOTING:
1293 		return _("shooting speed");
1294 	case MODIFIER_MOVEMENT:
1295 		return _("movement speed");
1296 	case MODIFIER_SIGHT:
1297 		return _("sight range");
1298 	case MODIFIER_REACTION:
1299 		return _("reaction speed");
1300 	case MODIFIER_TU:
1301 		return _("TU");
1302 	default:
1303 		return "";
1304 	}
1305 }
1306 
HUD_ActorWoundData_f(void)1307 static void HUD_ActorWoundData_f (void)
1308 {
1309 	if (!CL_BattlescapeRunning())
1310 		return;
1311 
1312 	/* check if actor exists */
1313 	if (!selActor)
1314 		return;
1315 
1316 	int bodyPart;
1317 	woundInfo_t* wounds = &selActor->wounds;
1318 	const character_t* chr = CL_ActorGetChr(selActor);
1319 	const BodyData* bodyData = chr->teamDef->bodyTemplate;
1320 
1321 	for (bodyPart = 0; bodyPart < bodyData->numBodyParts(); ++bodyPart) {
1322 		const int woundThreshold = selActor->maxHP * bodyData->woundThreshold(bodyPart);
1323 
1324 		if (wounds->woundLevel[bodyPart] + wounds->treatmentLevel[bodyPart] * 0.5 > woundThreshold) {
1325 			const int bleeding = wounds->woundLevel[bodyPart] * (wounds->woundLevel[bodyPart] > woundThreshold
1326 								 ? bodyData->bleedingFactor(bodyPart) : 0);
1327 			char text[256];
1328 			int penalty;
1329 
1330 			Com_sprintf(text, lengthof(text), CHRSH_IsTeamDefRobot(chr->teamDef) ?
1331 					_("Damaged %s (deterioration: %i)\n") : _("Wounded %s (bleeding: %i)\n"), _(bodyData->name(bodyPart)), bleeding);
1332 			for (penalty = MODIFIER_ACCURACY; penalty < MODIFIER_MAX; penalty++)
1333 				if (bodyData->penalty(bodyPart, static_cast<modifier_types_t>(penalty)) != 0)
1334 					Q_strcat(text, lengthof(text), _("- Reduced %s\n"), HUD_GetPenaltyString(penalty));
1335 			UI_ExecuteConfunc("actor_wounds %s %i \"%s\"", bodyData->id(bodyPart), bleeding, text);
1336 		}
1337 	}
1338 }
1339 
1340 /**
1341  * @brief Update the equipment weight for the selected actor.
1342  */
HUD_UpdateActorLoad_f(void)1343 static void HUD_UpdateActorLoad_f (void)
1344 {
1345 	if (!CL_BattlescapeRunning())
1346 		return;
1347 
1348 	/* check if actor exists */
1349 	if (!selActor)
1350 		return;
1351 
1352 	const character_t* chr = CL_ActorGetChr(selActor);
1353 	const float invWeight = selActor->inv.getWeight();
1354 	const int maxWeight = GAME_GetChrMaxLoad(chr);
1355 	const float penalty = GET_ENCUMBRANCE_PENALTY(invWeight, chr->score.skills[ABILITY_POWER]);
1356 	const int normalTU = GET_TU(chr->score.skills[ABILITY_SPEED], 1.0f - WEIGHT_NORMAL_PENALTY);
1357 	const int tus = GET_TU(chr->score.skills[ABILITY_SPEED], penalty);
1358 	const int tuPenalty = tus - normalTU;
1359 	int count = 0;
1360 
1361 	const Container* cont = nullptr;
1362 	while ((cont = chr->inv.getNextCont(cont))) {
1363 		Item* item = nullptr;
1364 		while ((item = cont->getNextItem(item))) {
1365 			const fireDef_t* fireDef = item->getFiredefs();
1366 			if (fireDef == nullptr)
1367 				continue;
1368 			for (int i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) {
1369 				const fireDef_t &fd = fireDef[i];
1370 				if (fd.time > 0 && fd.time > tus) {
1371 					if (count <= 0)
1372 						Com_sprintf(popupText, sizeof(popupText), _("This soldier no longer has enough TUs to use the following items:\n\n"));
1373 					Q_strcat(popupText, sizeof(popupText), "%s: %s (%i)\n", _(item->def()->name), _(fd.name), fd.time);
1374 					++count;
1375 				}
1376 			}
1377 		}
1378 	}
1379 
1380 	if (count > 0)
1381 		UI_Popup(_("Warning"), popupText);
1382 
1383 	char label[MAX_VAR];
1384 	char tooltip[MAX_VAR];
1385 	Com_sprintf(label, sizeof(label), "%g/%i %s", invWeight, maxWeight, _("Kg"));
1386 	Com_sprintf(tooltip, sizeof(tooltip), "%s %i (%+i)", _("TU:"), tus, tuPenalty);
1387 	UI_ExecuteConfunc("inv_actorload \"%s\" \"%s\" %f %i", label, tooltip, WEIGHT_NORMAL_PENALTY - (1.0f - penalty), count);
1388 }
1389 
1390 /**
1391  * @brief Updates the hud for one actor
1392  * @param actor The actor to update the hud values for
1393  */
HUD_UpdateActor(le_t * actor)1394 static void HUD_UpdateActor (le_t* actor)
1395 {
1396 	int time;
1397 
1398 	HUD_UpdateActorCvar(actor);
1399 	/* write info */
1400 	time = 0;
1401 
1402 	/* handle actor in a panic */
1403 	if (LE_IsPanicked(actor)) {
1404 		UI_RegisterText(TEXT_STANDARD, _("Currently panics!\n"));
1405 	} else if (displayRemainingTus[REMAINING_TU_CROUCH]) {
1406 		if (CL_ActorUsableTUs(actor) >= TU_CROUCH)
1407 			time = TU_CROUCH;
1408 	} else if (displayRemainingTus[REMAINING_TU_RELOAD_RIGHT]
1409 	 || displayRemainingTus[REMAINING_TU_RELOAD_LEFT]) {
1410 		const Item* item;
1411 		containerIndex_t container;
1412 
1413 		if (displayRemainingTus[REMAINING_TU_RELOAD_RIGHT] && actor->getRightHandItem()) {
1414 			container = CID_RIGHT;
1415 			item = actor->getRightHandItem();
1416 		} else if (displayRemainingTus[REMAINING_TU_RELOAD_LEFT] && actor->getLeftHandItem()) {
1417 			container = NONE;
1418 			item = HUD_GetLeftHandWeapon(actor, &container);
1419 		} else {
1420 			container = NONE;
1421 			item = nullptr;
1422 		}
1423 
1424 		if (item && item->def() && item->ammoDef() && item->isReloadable()) {
1425 			const int reloadtime = HUD_CalcReloadTime(actor, item->def(), container);
1426 			if (reloadtime != -1 && reloadtime <= CL_ActorUsableTUs(actor))
1427 				time = reloadtime;
1428 		}
1429 	} else if (CL_ActorFireModeActivated(actor->actorMode)) {
1430 		time = HUD_UpdateActorFireMode(actor);
1431 	} else {
1432 		/* If the mouse is outside the world, and we haven't placed the cursor in pend
1433 		 * mode already */
1434 		if (IN_GetMouseSpace() != MS_WORLD && actor->actorMode < M_PEND_MOVE)
1435 			actor->actorMoveLength = ROUTING_NOT_REACHABLE;
1436 		time = HUD_UpdateActorMove(actor);
1437 	}
1438 
1439 	/* Calculate remaining TUs. */
1440 	/* We use the full count of TUs since the "reserved" bar is overlaid over this one. */
1441 	time = std::max(0, actor->TU - time);
1442 	Cvar_Set("mn_turemain", "%i", time);
1443 
1444 	HUD_MapDebugCursor(actor);
1445 }
1446 
1447 /**
1448  * @brief Updates console vars for an actor.
1449  *
1450  * This function updates the cvars for the hud (battlefield)
1451  * unlike CL_ActorCvars and CL_UGVCvars which updates them for
1452  * displaying the data in the menu system
1453  *
1454  * @sa CL_ActorCvars
1455  * @sa CL_UGVCvars
1456  */
HUD_Update(void)1457 void HUD_Update (void)
1458 {
1459 	if (cls.state != ca_active)
1460 		return;
1461 
1462 	/* worldlevel */
1463 	if (cl_worldlevel->modified) {
1464 		int i;
1465 		for (i = 0; i < PATHFINDING_HEIGHT; i++) {
1466 			int status = 0;
1467 			if (i == cl_worldlevel->integer)
1468 				status = 2;
1469 			else if (i < cl.mapMaxLevel)
1470 				status = 1;
1471 			UI_ExecuteConfunc("updateLevelStatus %i %i", i, status);
1472 		}
1473 		cl_worldlevel->modified = false;
1474 	}
1475 
1476 	/* force them empty first */
1477 	Cvar_Set("mn_anim", "stand0");
1478 	Cvar_Set("mn_rweapon", "");
1479 	Cvar_Set("mn_lweapon", "");
1480 
1481 	if (selActor) {
1482 		HUD_UpdateActor(selActor);
1483 	} else if (!cl.numTeamList) {
1484 		/* This will stop the drawing of the bars over the whole screen when we test maps. */
1485 		Cvar_SetValue("mn_hp", 0);
1486 		Cvar_SetValue("mn_hpmax", 100);
1487 		Cvar_SetValue("mn_tu", 0);
1488 		Cvar_SetValue("mn_tumax", 100);
1489 		Cvar_SetValue("mn_tureserved", 0);
1490 		Cvar_SetValue("mn_morale", 0);
1491 		Cvar_SetValue("mn_moralemax", 100);
1492 		Cvar_SetValue("mn_stun", 0);
1493 	}
1494 }
1495 
1496 /**
1497  * @brief Callback that is called when the cl_selected cvar was changed
1498  * @param cvarName The cvar name (cl_selected)
1499  * @param oldValue The old value of the cvar (a sane actor idx)
1500  * @param newValue The new value of the cvar (a sane actor idx)
1501  * @param data Unused here, but required by cvarChangeListenerFunc_t
1502  */
HUD_ActorSelectionChangeListener(const char * cvarName,const char * oldValue,const char * newValue,void * data)1503 static void HUD_ActorSelectionChangeListener (const char* cvarName, const char* oldValue, const char* newValue, void* data)
1504 {
1505 	if (!CL_OnBattlescape())
1506 		return;
1507 
1508 	if (newValue[0] != '\0') {
1509 		const int actorIdx = atoi(newValue);
1510 		const size_t size = lengthof(cl.teamList);
1511 		if (actorIdx >= 0 && actorIdx < size)
1512 			UI_ExecuteConfunc("hudselect %s", newValue);
1513 	}
1514 }
1515 
1516 /**
1517  * @brief Callback that is called when the right hand weapon of the current selected actor changed
1518  * @param cvarName The cvar name
1519  * @param oldValue The old value of the cvar
1520  * @param newValue The new value of the cvar
1521  * @param data Unused here, but required by cvarChangeListenerFunc_t
1522  */
HUD_RightHandChangeListener(const char * cvarName,const char * oldValue,const char * newValue,void * data)1523 static void HUD_RightHandChangeListener (const char* cvarName, const char* oldValue, const char* newValue, void* data)
1524 {
1525 	if (!CL_OnBattlescape())
1526 		return;
1527 
1528 	if (Q_streq(oldValue, newValue))
1529 		return;
1530 
1531 	HUD_UpdateButtons(selActor);
1532 }
1533 
1534 /**
1535  * @brief Callback that is called when the left hand weapon of the current selected actor changed
1536  * @param cvarName The cvar name
1537  * @param oldValue The old value of the cvar
1538  * @param newValue The new value of the cvar
1539  * @param data Unused here, but required by cvarChangeListenerFunc_t
1540  */
HUD_LeftHandChangeListener(const char * cvarName,const char * oldValue,const char * newValue,void * data)1541 static void HUD_LeftHandChangeListener (const char* cvarName, const char* oldValue, const char* newValue, void* data)
1542 {
1543 	if (!CL_OnBattlescape())
1544 		return;
1545 
1546 	if (Q_streq(oldValue, newValue))
1547 		return;
1548 
1549 	HUD_UpdateButtons(selActor);
1550 }
1551 
1552 /**
1553  * @brief Callback that is called when the remaining TUs for the current selected actor changed
1554  * @param cvarName The cvar name
1555  * @param oldValue The old value of the cvar
1556  * @param newValue The new value of the cvar
1557  * @param data Unused here, but required by cvarChangeListenerFunc_t
1558  */
HUD_TUChangeListener(const char * cvarName,const char * oldValue,const char * newValue,void * data)1559 static void HUD_TUChangeListener (const char* cvarName, const char* oldValue, const char* newValue, void* data)
1560 {
1561 	if (!CL_OnBattlescape())
1562 		return;
1563 
1564 	if (Q_streq(oldValue, newValue))
1565 		return;
1566 
1567 	HUD_UpdateButtons(selActor);
1568 }
1569 
CL_CvarWorldLevel(cvar_t * cvar)1570 static bool CL_CvarWorldLevel (cvar_t* cvar)
1571 {
1572 	const int maxLevel = cl.mapMaxLevel ? cl.mapMaxLevel - 1 : PATHFINDING_HEIGHT - 1;
1573 	return Cvar_AssertValue(cvar, 0, maxLevel, true);
1574 }
1575 
1576 /**
1577  * @brief Checks that the given cvar is a valid hud cvar
1578  * @param cvar The cvar to check
1579  * @return @c true if cvar is valid, @c false otherwise
1580  */
HUD_CheckCLHud(cvar_t * cvar)1581 static bool HUD_CheckCLHud (cvar_t* cvar)
1582 {
1583 	const uiNode_t* window = UI_GetWindow(cvar->string);
1584 	if (window == nullptr) {
1585 		return false;
1586 	}
1587 
1588 	if (window->super == nullptr) {
1589 		return false;
1590 	}
1591 
1592 	/**
1593 	 * @todo check for multiple base classes
1594 	 */
1595 	return Q_streq(window->super->name, "hud");
1596 }
1597 
1598 /**
1599  * @brief Display the user interface
1600  * @param optionWindowName Name of the window used to display options, else nullptr if nothing
1601  */
HUD_InitUI(const char * optionWindowName)1602 void HUD_InitUI (const char* optionWindowName)
1603 {
1604 	OBJZERO(buttonStates);
1605 	if (!HUD_CheckCLHud(cl_hud)) {
1606 		Cvar_Set("cl_hud", "hud_default");
1607 	}
1608 	UI_InitStack(cl_hud->string, optionWindowName);
1609 
1610 	UI_ExecuteConfunc("hudinit");
1611 }
1612 
1613 /**
1614  * @brief Checks that the given cvar is a valid hud cvar
1615  * @param cvar The cvar to check and to modify if the value is invalid
1616  * @return @c true if the valid is invalid, @c false otherwise
1617  */
HUD_CvarCheckMNHud(cvar_t * cvar)1618 static bool HUD_CvarCheckMNHud (cvar_t* cvar)
1619 {
1620 	if (!HUD_CheckCLHud(cl_hud)) {
1621 		Cvar_Reset(cvar);
1622 		return true;
1623 	}
1624 	return false;
1625 }
1626 
HUD_InitStartup(void)1627 void HUD_InitStartup (void)
1628 {
1629 	HUD_InitCallbacks();
1630 
1631 	Cmd_AddCommand("hud_remainingtus", HUD_RemainingTUs_f, "Define if remaining TUs should be displayed in the TU-bar for some hovered-over button.");
1632 	Cmd_AddCommand("hud_shotreserve", HUD_ShotReserve_f, "Reserve TUs for the selected entry in the popup.");
1633 	Cmd_AddCommand("hud_shotreservationpopup", HUD_PopupFiremodeReservation_f, "Pop up a list of possible firemodes for reservation in the current turn.");
1634 	Cmd_AddCommand("hud_selectreactionfiremode", HUD_SelectReactionFiremode_f, "Change/Select firemode used for reaction fire.");
1635 	Cmd_AddCommand("hud_listfiremodes", HUD_DisplayFiremodes_f, "Display a list of firemodes for a weapon+ammo.");
1636 	Cmd_AddCommand("hud_listactions", HUD_DisplayActions_f, "Display a list of action from the selected soldier.");
1637 	Cmd_AddCommand("hud_updateactorwounds", HUD_ActorWoundData_f, "Update info on actor wounds.");
1638 	Cmd_AddCommand("hud_updateactorload", HUD_UpdateActorLoad_f, "Update the HUD with the selected actor inventory load.");
1639 
1640 	/** @note We can't check the value at startup cause scripts are not yet loaded */
1641 	cl_hud = Cvar_Get("cl_hud", "hud_default", CVAR_ARCHIVE | CVAR_LATCH, "Current selected HUD.");
1642 	Cvar_SetCheckFunction("cl_hud", HUD_CvarCheckMNHud);
1643 
1644 	cl_worldlevel = Cvar_Get("cl_worldlevel", "0", 0, "Current worldlevel in tactical mode.");
1645 	Cvar_SetCheckFunction("cl_worldlevel", CL_CvarWorldLevel);
1646 	cl_worldlevel->modified = false;
1647 
1648 	Cvar_Get("mn_ammoleft", "", 0, "The remaining amount of ammunition in the left hand weapon.");
1649 	Cvar_Get("mn_lweapon", "", 0, "The left hand weapon model of the current selected actor - empty if no weapon.");
1650 	Cvar_RegisterChangeListener("mn_ammoleft", HUD_LeftHandChangeListener);
1651 	Cvar_RegisterChangeListener("mn_lweapon", HUD_LeftHandChangeListener);
1652 
1653 	Cvar_Get("mn_ammoright", "", 0, "The remaining amount of ammunition in the right hand weapon.");
1654 	Cvar_Get("mn_rweapon", "", 0, "The right hand weapon model of the current selected actor - empty if no weapon.");
1655 	Cvar_RegisterChangeListener("mn_ammoright", HUD_RightHandChangeListener);
1656 	Cvar_RegisterChangeListener("mn_rweapon", HUD_RightHandChangeListener);
1657 
1658 	Cvar_Get("mn_turemain", "", 0, "Remaining TUs for the current selected actor.");
1659 	Cvar_RegisterChangeListener("mn_turemain", HUD_TUChangeListener);
1660 
1661 	Cvar_RegisterChangeListener("cl_selected", HUD_ActorSelectionChangeListener);
1662 
1663 	cl_hud_message_timeout = Cvar_Get("cl_hud_message_timeout", "2000", CVAR_ARCHIVE, "Timeout for HUD messages (milliseconds).");
1664 	cl_show_cursor_tooltips = Cvar_Get("cl_show_cursor_tooltips", "1", CVAR_ARCHIVE, "Show cursor tooltips in tactical game mode.");
1665 }
1666