/** * @file * @brief HUD related routines. */ /* Copyright (C) 2002-2013 UFO: Alien Invasion. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "../client.h" #include "cl_localentity.h" #include "cl_actor.h" #include "cl_hud.h" #include "cl_hud_callbacks.h" #include "cl_view.h" #include "../cgame/cl_game.h" #include "../ui/ui_main.h" #include "../ui/ui_popup.h" #include "../ui/ui_nodes.h" #include "../ui/ui_draw.h" #include "../ui/ui_render.h" #include "../ui/ui_tooltip.h" #include "../renderer/r_mesh_anim.h" #include "../renderer/r_draw.h" #include "../../common/grid.h" static cvar_t* cl_hud_message_timeout; static cvar_t* cl_show_cursor_tooltips; static cvar_t* cl_hud; cvar_t* cl_worldlevel; enum { REMAINING_TU_RELOAD_RIGHT, REMAINING_TU_RELOAD_LEFT, REMAINING_TU_CROUCH, REMAINING_TU_MAX }; static bool displayRemainingTus[REMAINING_TU_MAX]; typedef enum { BT_RIGHT_FIRE, BT_REACTION, BT_LEFT_FIRE, BT_RIGHT_RELOAD, BT_LEFT_RELOAD, BT_STAND, BT_CROUCH, BT_HEADGEAR, BT_NUM_TYPES } buttonTypes_t; typedef enum { FIRE_RIGHT, FIRE_LEFT, RELOAD_RIGHT, RELOAD_LEFT } actionType_t; /** @brief a cbuf string for each button_types_t */ static char const* const shootTypeStrings[] = { "primaryright", "reaction", "primaryleft", "reloadright", "reloadleft", "stand", "crouch", "headgear" }; CASSERT(lengthof(shootTypeStrings) == BT_NUM_TYPES); /** * @brief Defines the various states of a button. * @note Not all buttons do have all of these states (e.g. "unusable" is not very common). */ typedef enum { BT_STATE_UNINITIALZED, BT_STATE_DISABLE, /**< 'Disabled' display (grey) */ BT_STATE_DESELECT, /**< Normal display (blue) */ BT_STATE_SELECT /**< Selected display (blue) */ } weaponButtonState_t; /** @note Order of elements here must correspond to order of elements in walkType_t. */ static char const* const moveModeDescriptions[] = { N_("Crouch walk"), N_("Autostand"), N_("Walk"), N_("Crouch walk") }; CASSERT(lengthof(moveModeDescriptions) == WALKTYPE_MAX); typedef struct reserveShot_s { actorHands_t hand; int fireModeIndex; int weaponIndex; int TUs; } reserveShot_t; typedef enum { BUTTON_TURESERVE_UNINITIALIZED, BUTTON_TURESERVE_SHOT_RESERVED, BUTTON_TURESERVE_SHOT_DISABLED, BUTTON_TURESERVE_SHOT_CLEAR, BUTTON_TURESERVE_CROUCH_RESERVED, BUTTON_TURESERVE_CROUCH_DISABLED, BUTTON_TURESERVE_CROUCH_CLEAR } reserveButtonState_t; static weaponButtonState_t buttonStates[BT_NUM_TYPES]; static reserveButtonState_t shotReserveButtonState; static reserveButtonState_t crouchReserveButtonState; /** * @brief Displays a message on the hud. * @sa UI_DisplayNotice * @param[in] text text is already translated here */ void HUD_DisplayMessage (const char* text) { assert(text); UI_DisplayNotice(text, cl_hud_message_timeout->integer, cl_hud->string); } void HUD_UpdateActorStats (const le_t* le) { if (LE_IsDead(le)) return; const Item* item = le->getRightHandItem(); if ((!item || !item->def() || !item->isHeldTwoHanded()) && le->getLeftHandItem()) item = le->getLeftHandItem(); const character_t* chr = CL_ActorGetChr(le); assert(chr); const char* tooltip = va(_("%s\nHP: %i/%i TU: %i\n%s"), chr->name, le->HP, le->maxHP, le->TU, (item && item->def()) ? _(item->def()->name) : ""); const int idx = CL_ActorGetNumber(le); UI_ExecuteConfunc("updateactorvalues %i \"%s\" \"%i\" \"%i\" \"%i\" \"%i\" \"%i\" \"%i\" \"%i\" \"%s\"", idx, le->model2->name, le->HP, le->maxHP, le->TU, le->maxTU, le->morale, le->maxMorale, le->STUN, tooltip); } /** * @brief Sets the display for a single weapon/reload HUD button. * @todo This should be a confunc which also sets the tooltips */ static void HUD_SetWeaponButton (buttonTypes_t button, weaponButtonState_t state) { if (buttonStates[button] == state) return; const char* prefix; switch (state) { case BT_STATE_DESELECT: prefix = "deselect_"; break; case BT_STATE_DISABLE: prefix = "disable_"; break; default: prefix = ""; break; } /* Connect confunc strings to the ones as defined in "menu hud_nohud". */ UI_ExecuteConfunc("%s%s", prefix, shootTypeStrings[button]); buttonStates[button] = state; } /** * @brief Returns the amount of usable "reaction fire" TUs for this actor (depends on active/inactive RF) * @param[in] le The actor to check. * @return The remaining/usable TUs for this actor * @return -1 on error (this includes bad [very large] numbers stored in the struct). * @todo Maybe only return "reaction" value if reaction-state is active? The value _should_ be 0, but one never knows :) */ static int HUD_UsableReactionTUs (const le_t* le) { /* Get the amount of usable TUs depending on the state (i.e. is RF on or off?) */ if (le->state & STATE_REACTION) /* CL_ActorUsableTUs DOES NOT return the stored value for "reaction" here. */ return CL_ActorUsableTUs(le) + CL_ActorReservedTUs(le, RES_REACTION); /* CL_ActorUsableTUs DOES return the stored value for "reaction" here. */ return CL_ActorUsableTUs(le); } /** * @brief Check if at least one firemode is available for reservation. * @return true if there is at least one firemode - false otherwise. * @sa HUD_UpdateButtons * @sa HUD_PopupFiremodeReservation_f */ static bool HUD_CheckFiremodeReservation (void) { if (!selActor) return false; actorHands_t hand; foreachhand(hand) { /* Get weapon (and its ammo) from the hand. */ const fireDef_t* fireDef = HUD_GetFireDefinitionForHand(selActor, hand); if (!fireDef) continue; const objDef_t* ammo = fireDef->obj; for (int i = 0; i < ammo->numFiredefs[fireDef->weapFdsIdx]; i++) { /* Check if at least one firemode is available for reservation. */ if (CL_ActorUsableTUs(selActor) + CL_ActorReservedTUs(selActor, RES_SHOT) >= CL_ActorTimeForFireDef(selActor, &ammo->fd[fireDef->weapFdsIdx][i])) return true; } } /* No reservation possible */ return false; } /** * @brief Sets TU-reservation and firemode * @param[in] le The local entity of the actor to change the tu reservation for. * @param[in] tus How many TUs to set. * @param[in] hand Store the given hand. * @param[in] fireModeIndex Store the given firemode for this hand. * @param[in] weapon Pointer to weapon in the hand. */ static void HUD_SetShootReservation (const le_t* le, const int tus, const actorHands_t hand, const int fireModeIndex, const objDef_t* weapon) { character_t* chr = CL_ActorGetChr(le); assert(chr); CL_ActorReserveTUs(le, RES_SHOT, tus); chr->reservedTus.shotSettings.set(hand, fireModeIndex, weapon); } static linkedList_t* popupListData; static uiNode_t* popupListNode; /** * @brief Creates a (text) list of all firemodes of the currently selected actor. * @param[in] le The actor local entity * @param[in] popupReload Prevent firemode reservation popup from being closed if * no firemode is available because of insufficient TUs. * @sa HUD_PopupFiremodeReservation_f * @sa HUD_CheckFiremodeReservation * @todo use components and confuncs here */ static void HUD_PopupFiremodeReservation (const le_t* le, bool popupReload) { int i; static char text[MAX_VAR]; int selectedEntry; linkedList_t* popupListText = nullptr; reserveShot_t reserveShotData; /* reset the list */ UI_ResetData(TEXT_LIST); LIST_Delete(&popupListData); /* Add list-entry for deactivation of the reservation. */ LIST_AddPointer(&popupListText, (void*)(_("[0 TU] No reservation"))); reserveShotData.hand = ACTOR_HAND_NOT_SET; reserveShotData.fireModeIndex = -1; reserveShotData.weaponIndex = NONE; reserveShotData.TUs = -1; LIST_Add(&popupListData, reserveShotData); selectedEntry = 0; actorHands_t hand; foreachhand(hand) { const fireDef_t* fd = HUD_GetFireDefinitionForHand(le, hand); if (!fd) continue; character_t* chr = CL_ActorGetChr(le); assert(chr); const objDef_t* ammo = fd->obj; for (i = 0; i < ammo->numFiredefs[fd->weapFdsIdx]; i++) { const fireDef_t* ammoFD = &ammo->fd[fd->weapFdsIdx][i]; const int time = CL_ActorTimeForFireDef(le, ammoFD); if (CL_ActorUsableTUs(le) + CL_ActorReservedTUs(le, RES_SHOT) >= time) { /* Get firemode name and TUs. */ Com_sprintf(text, lengthof(text), _("[%i TU] %s"), time, _(ammoFD->name)); /* Store text for popup */ LIST_AddString(&popupListText, text); /* Store Data for popup-callback. */ reserveShotData.hand = hand; reserveShotData.fireModeIndex = i; reserveShotData.weaponIndex = ammo->weapons[fd->weapFdsIdx]->idx; reserveShotData.TUs = time; LIST_Add(&popupListData, reserveShotData); /* Remember the line that is currently selected (if any). */ if (chr->reservedTus.shotSettings.getHand() == hand && chr->reservedTus.shotSettings.getFmIdx() == i && chr->reservedTus.shotSettings.getWeapon() == ammo->weapons[fd->weapFdsIdx]) selectedEntry = LIST_Count(popupListData) - 1; } } } if (LIST_Count(popupListData) > 1 || popupReload) { /* We have more entries than the "0 TUs" one * or we want to simply refresh/display the popup content (no matter how many TUs are left). */ popupListNode = UI_PopupList(_("Shot Reservation"), _("Reserve TUs for firing/using."), popupListText, "hud_shotreserve "); /* Set color for selected entry. */ VectorSet(popupListNode->selectedColor, 0.0, 0.78, 0.0); popupListNode->selectedColor[3] = 1.0; UI_TextNodeSelectLine(popupListNode, selectedEntry); } } /** * @brief Creates a (text) list of all firemodes of the currently selected actor. * @sa HUD_PopupFiremodeReservation */ static void HUD_PopupFiremodeReservation_f (void) { if (!selActor) return; /* A second parameter (the value itself will be ignored) was given. * This is used to reset the shot-reservation.*/ if (Cmd_Argc() == 2) { HUD_SetShootReservation(selActor, 0, ACTOR_HAND_NOT_SET, -1, nullptr); } else { HUD_PopupFiremodeReservation(selActor, false); } } /** * @brief Get selected firemode in the list of the currently selected actor. * @sa HUD_PopupFiremodeReservation_f */ static void HUD_ShotReserve_f (void) { if (Cmd_Argc() < 2) { Com_Printf("Usage: %s \n", Cmd_Argv(0)); return; } if (!selActor) return; /* read and range check */ const int selectedPopupIndex = atoi(Cmd_Argv(1)); if (selectedPopupIndex < 0 || selectedPopupIndex >= LIST_Count(popupListData)) return; const reserveShot_t* reserveShotData = (const reserveShot_t*)LIST_GetByIdx(popupListData, selectedPopupIndex); if (!reserveShotData) return; if (reserveShotData->weaponIndex == NONE) { HUD_SetShootReservation(selActor, 0, ACTOR_HAND_NOT_SET, -1, nullptr); return; } /** @todo do this on the server */ /* Check if we have enough TUs (again) */ if (CL_ActorUsableTUs(selActor) + CL_ActorReservedTUs(selActor, RES_SHOT) >= reserveShotData->TUs) { const objDef_t* od = INVSH_GetItemByIDX(reserveShotData->weaponIndex); if (GAME_ItemIsUseable(od)) { HUD_SetShootReservation(selActor, std::max(0, reserveShotData->TUs), reserveShotData->hand, reserveShotData->fireModeIndex, od); if (popupListNode) UI_TextNodeSelectLine(popupListNode, selectedPopupIndex); } } } /** * @brief Sets the display for a single weapon/reload HUD button. * @param callback confunc callback * @param actor The actor local entity * @param ammo The ammo used by this firemode * @param weapFdsIdx the weapon index in the fire definition array * @param hand What list to display * @param index Index of the actual firemode */ 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) { if (index >= ammo->numFiredefs[weapFdsIdx]) return; /* We have a defined fd ... */ const fireDef_t* fd = &ammo->fd[weapFdsIdx][index]; const int time = CL_ActorTimeForFireDef(actor, fd); assert(actor); assert(hand == ACTOR_HAND_RIGHT || hand == ACTOR_HAND_LEFT); const bool status = time <= CL_ActorUsableTUs(actor); const int usableTusForRF = HUD_UsableReactionTUs(actor); char tuString[MAX_VAR]; const char* tooltip; if (usableTusForRF > time) { Com_sprintf(tuString, sizeof(tuString), _("Remaining TUs: %i"), usableTusForRF - time); tooltip = tuString; } else tooltip = _("No remaining TUs left after shot."); /* unique identifier of the action */ /* @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...)) */ char id[32]; Com_sprintf(id, sizeof(id), "fire_hand%c_i%i", ACTOR_GET_HAND_CHAR(hand), index); UI_ExecuteConfunc("%s firemode %s %c %i %i %i \"%s\" \"%i\" \"%i\" \"%s\"", callback, id, ACTOR_GET_HAND_CHAR(hand), fd->fdIdx, fd->reaction, status, _(fd->name), time, fd->ammo, tooltip); /* Display checkbox for reaction firemode */ if (fd->reaction) { character_t* chr = CL_ActorGetChr(actor); const bool active = THIS_FIREMODE(&chr->RFmode, hand, fd->fdIdx); /* Change the state of the checkbox. */ UI_ExecuteConfunc("%s reaction %s %c %i", callback, id, ACTOR_GET_HAND_CHAR(hand), active); } } /** * @brief List actions from a soldier to a callback confunc * @param callback confunc callback * @param actor actor who can do the actions * @param type The action to display */ static void HUD_DisplayActions (const char* callback, const le_t* actor, actionType_t type) { int i; if (!actor) return; if (!cls.isOurRound()) { /**< Not our turn */ return; } const ScopedCommand c(callback, "begin", "end"); switch (type) { case FIRE_RIGHT: { const actorHands_t hand = ACTOR_HAND_RIGHT; const fireDef_t* fd = HUD_GetFireDefinitionForHand(actor, hand); if (fd == nullptr) { UI_PopWindow(); return; } const objDef_t* ammo = fd->obj; if (!ammo) { Com_DPrintf(DEBUG_CLIENT, "HUD_DisplayFiremodes: no weapon or ammo found.\n"); return; } for (i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) { /* Display the firemode information (image + text). */ HUD_DisplayFiremodeEntry(callback, actor, ammo, fd->weapFdsIdx, hand, i); } } break; case RELOAD_RIGHT: { Item* weapon = actor->getRightHandItem(); /* Reloeadable item in hand. */ if (weapon && weapon->def() && weapon->isReloadable()) { int tus; containerIndex_t container = CID_RIGHT; bool noAmmo; bool noTU; const char* actionId = "reload_handr"; tus = HUD_CalcReloadTime(actor, weapon->def(), container); noAmmo = tus == -1; noTU = actor->TU < tus; UI_ExecuteConfunc("%s reload %s %c %i %i %i", callback, actionId, 'r', tus, !noAmmo, !noTU); } } break; case FIRE_LEFT: { const actorHands_t hand = ACTOR_HAND_LEFT; const fireDef_t* fd = HUD_GetFireDefinitionForHand(actor, hand); if (fd == nullptr) { UI_PopWindow(); return; } const objDef_t* ammo = fd->obj; if (!ammo) { Com_DPrintf(DEBUG_CLIENT, "HUD_DisplayFiremodes: no weapon or ammo found.\n"); return; } for (i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) { /* Display the firemode information (image + text). */ HUD_DisplayFiremodeEntry(callback, actor, ammo, fd->weapFdsIdx, hand, i); } } break; case RELOAD_LEFT: { Item* weapon = actor->getLeftHandItem(); /* Reloadable item in hand. */ if (weapon && weapon->def() && weapon->isReloadable()) { containerIndex_t container = CID_LEFT; const char* actionId = "reload_handl"; const int tus = HUD_CalcReloadTime(actor, weapon->def(), container); const bool noAmmo = tus == -1; const bool noTU = actor->TU < tus; UI_ExecuteConfunc("%s reload %s %c %i %i %i", callback, actionId, 'l', tus, !noAmmo, !noTU); } } break; } } /** * @brief Displays the firemodes for the given hand. * @todo we can extend it with short cut equip action, more reload, action on the map (like open doors)... */ static void HUD_DisplayActions_f (void) { if (Cmd_Argc() < 3) { Com_Printf("Usage: %s callback [l|r|L|R]\n", Cmd_Argv(0)); return; } if (!selActor) return; actionType_t type; if (strchr(Cmd_Argv(2), 'r') != nullptr) { type = FIRE_RIGHT; } else if (strchr(Cmd_Argv(2), 'l') != nullptr) { type = FIRE_LEFT; } else if (strchr(Cmd_Argv(2), 'R') != nullptr) { type = RELOAD_RIGHT; } else if (strchr(Cmd_Argv(2), 'L') != nullptr) { type = RELOAD_LEFT; } else { return; } char callback[32]; Q_strncpyz(callback, Cmd_Argv(1), sizeof(callback)); HUD_DisplayActions(callback, selActor, type); } /** * @brief Displays the firemodes for the given hand. */ static void HUD_DisplayFiremodes_f (void) { if (Cmd_Argc() < 3) { Com_Printf("Usage: %s callback [l|r]\n", Cmd_Argv(0)); return; } if (!selActor) return; if (selActor->isMoving()) return; const actorHands_t hand = ACTOR_GET_HAND_INDEX(Cmd_Argv(2)[0]); const actionType_t type = hand == ACTOR_HAND_RIGHT ? FIRE_RIGHT : FIRE_LEFT; char callback[32]; Q_strncpyz(callback, Cmd_Argv(1), sizeof(callback)); HUD_DisplayActions(callback, selActor, type); } /** * @brief Updates the information in RFmode for the selected actor with the given data from the parameters. * @param[in] actor The actor we want to update the reaction-fire firemode for. * @param[in] hand Which weapon(-hand) to use. * @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. */ static void HUD_UpdateReactionFiremodes (const le_t* actor, const actorHands_t hand, fireDefIndex_t firemodeActive) { const fireDef_t* fd; const objDef_t* ammo, *od; assert(actor); fd = HUD_GetFireDefinitionForHand(actor, hand); if (fd == nullptr) return; ammo = fd->obj; od = ammo->weapons[fd->weapFdsIdx]; if (!GAME_ItemIsUseable(od)) return; MSG_Write_PA(PA_REACT_SELECT, actor->entnum, hand, firemodeActive, od ? od->idx : NONE); } /** * @brief Checks if the selected firemode checkbox is ok as a reaction firemode and updates data+display. */ static void HUD_SelectReactionFiremode_f (void) { if (Cmd_Argc() < 3) { /* no argument given */ Com_Printf("Usage: %s [l|r] num=firemode number\n", Cmd_Argv(0)); return; } if (!selActor) return; const fireDefIndex_t firemode = atoi(Cmd_Argv(2)); if (firemode >= MAX_FIREDEFS_PER_WEAPON || firemode < 0) { Com_Printf("HUD_SelectReactionFiremode_f: Firemode out of bounds (%i).\n", firemode); return; } const actorHands_t hand = ACTOR_GET_HAND_INDEX(Cmd_Argv(1)[0]); HUD_UpdateReactionFiremodes(selActor, hand, firemode); } /** * @brief Remember if we hover over a button that would cost some TUs when pressed. * @note this is used in HUD_Update to update the "remaining TUs" bar correctly. */ static void HUD_RemainingTUs_f (void) { if (Cmd_Argc() < 3) { Com_Printf("Usage: %s \n", Cmd_Argv(0)); return; } const char* type = Cmd_Argv(1); const bool state = Com_ParseBoolean(Cmd_Argv(2)); OBJZERO(displayRemainingTus); if (Q_streq(type, "reload_r")) { displayRemainingTus[REMAINING_TU_RELOAD_RIGHT] = state; } else if (Q_streq(type, "reload_l")) { displayRemainingTus[REMAINING_TU_RELOAD_LEFT] = state; } else if (Q_streq(type, "crouch")) { displayRemainingTus[REMAINING_TU_CROUCH] = state; } } /** * @return The minimum time needed to fire the weapon */ static int HUD_GetMinimumTUsForUsage (const Item* item) { /** @todo what is this 100? replace with constant please - MAX_TUS? */ int time = 100; assert(item->def()); const fireDef_t* fdArray = item->getFiredefs(); if (fdArray == nullptr) return time; /* Search for the smallest TU needed to shoot. */ for (int i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) { if (!fdArray[i].time) continue; if (fdArray[i].time < time) time = fdArray[i].time; } return time; } /** * @brief Checks every case for reload buttons on the HUD. * @param[in] le Pointer of local entity being an actor. * @param[in] containerID of the container to reload the weapon in. Used to get the movement TUs for moving something into the container. * @param[out] reason The reason why the reload didn't work - only set if @c -1 is the return value * @return TU units needed for reloading or -1 if weapon cannot be reloaded. */ static int HUD_WeaponCanBeReloaded (const le_t* le, containerIndex_t containerID, const char** reason) { const Item* invList = le->inv.getContainer2(containerID); /* No weapon in hand. */ if (!invList) { *reason = _("No weapon."); return -1; } const objDef_t* weapon = invList->def(); assert(weapon); /* This weapon cannot be reloaded. */ if (!weapon->isReloadable()) { *reason = _("Weapon cannot be reloaded."); return -1; } /* Weapon is fully loaded. */ if (invList->ammoDef() && weapon->ammo == invList->getAmmoLeft()) { *reason = _("No reload possible, already fully loaded."); return -1; } /* Weapon is empty or not fully loaded, find ammo of any type loadable to this weapon. */ if (!invList->ammoDef() || weapon->ammo > invList->getAmmoLeft()) { const int tuCosts = HUD_CalcReloadTime(le, weapon, containerID); if (tuCosts >= 0) { const int tu = CL_ActorUsableTUs(le); if (tu >= tuCosts) return tuCosts; *reason = _("Not enough TUs for reloading weapon."); } else { /* Found no ammo which could be used for this weapon. */ *reason = _("No reload possible, you don't have backup ammo."); } } return -1; } /** * @brief Display 'impossible' (red) reaction buttons. * @param[in] actor the actor to check for his reaction state. * @return true if nothing changed message was sent otherwise false. * @note this is called when reaction is enabled with no suitable weapon. * @todo at the moment of writing, this should be impossible: reaction * should be disabled whenever the actor loses its reaction weapon. */ static bool HUD_DisplayImpossibleReaction (const le_t* actor) { if (!actor) return false; /* Given actor does not equal the currently selected actor. */ if (!LE_IsSelected(actor)) return false; if (buttonStates[BT_REACTION] == BT_STATE_UNINITIALZED) return false; /* Display 'impossible" (red) reaction buttons */ if (actor->state & STATE_REACTION) { Com_Printf("HUD_DisplayImpossibleReaction: Warning reaction fire enable and no suitable weapon found!\n"); UI_ExecuteConfunc("startreaction_impos"); buttonStates[BT_REACTION] = BT_STATE_UNINITIALZED; return false; } return true; } /** * @brief Display 'usable' (blue) reaction buttons. * @param[in] actor the actor to check for his reaction state. */ static void HUD_DisplayPossibleReaction (const le_t* actor) { if (!actor) return; /* Given actor does not equal the currently selected actor. This normally only happens on game-start. */ if (!LE_IsSelected(actor)) return; if (buttonStates[BT_REACTION] == BT_STATE_SELECT) return; /* Display 'usable" (blue) reaction buttons */ if (actor->state & STATE_REACTION) { UI_ExecuteConfunc("startreaction"); buttonStates[BT_REACTION] = BT_STATE_SELECT; } } /** * @brief Get the weapon firing TUs for reaction fire. * @param[in] actor the actor to check for his reaction TUs. * @return The TUs needed for the reaction fireDef for this actor or -1 if no valid reaction settings */ int HUD_ReactionFireGetTUs (const le_t* actor) { if (!actor) return -1; const FiremodeSettings &fmSetting = CL_ActorGetChr(actor)->RFmode; const Item* weapon = actor->getHandItem(fmSetting.getHand()); if (!weapon) weapon = actor->getRightHandItem(); if (!weapon) weapon = actor->getLeftHandItem(); if (weapon && weapon->ammoDef() && weapon->isWeapon()) { const fireDef_t* fdArray = weapon->getFiredefs(); if (fdArray == nullptr) return -1; const fireDefIndex_t fmIdx = fmSetting.getFmIdx(); if (fmIdx >= 0 && fmIdx < MAX_FIREDEFS_PER_WEAPON) { return CL_ActorTimeForFireDef(actor, &fdArray[fmIdx], true); } } return -1; } /** * @brief Refreshes the weapon/reload buttons on the HUD. * @param[in] le Pointer to local entity for which we refresh HUD buttons. */ static void HUD_UpdateButtons (const le_t* le) { if (!le) return; Item* weaponR = le->getRightHandItem(); Item* headgear = le->inv.getHeadgear(); Item* weaponL; /* check for two-handed weapon - if not, also define weaponL */ if (!weaponR || !weaponR->isHeldTwoHanded()) weaponL = le->getLeftHandItem(); else weaponL = nullptr; const int time = CL_ActorUsableTUs(le); /* Crouch/stand button. */ if (LE_IsCrouched(le)) { buttonStates[BT_STAND] = BT_STATE_UNINITIALZED; if (time + CL_ActorReservedTUs(le, RES_CROUCH) < TU_CROUCH) { Cvar_Set("mn_crouchstand_tt", _("Not enough TUs for standing up.")); HUD_SetWeaponButton(BT_CROUCH, BT_STATE_DISABLE); } else { Cvar_Set("mn_crouchstand_tt", _("Stand up (%i TU)"), TU_CROUCH); HUD_SetWeaponButton(BT_CROUCH, BT_STATE_DESELECT); } } else { buttonStates[BT_CROUCH] = BT_STATE_UNINITIALZED; if (time + CL_ActorReservedTUs(le, RES_CROUCH) < TU_CROUCH) { Cvar_Set("mn_crouchstand_tt", _("Not enough TUs for crouching.")); HUD_SetWeaponButton(BT_STAND, BT_STATE_DISABLE); } else { Cvar_Set("mn_crouchstand_tt", _("Crouch (%i TU)"), TU_CROUCH); HUD_SetWeaponButton(BT_STAND, BT_STATE_DESELECT); } } /* Crouch/stand reservation checkbox. */ if (CL_ActorReservedTUs(le, RES_CROUCH) >= TU_CROUCH) { if (crouchReserveButtonState != BUTTON_TURESERVE_CROUCH_RESERVED) { UI_ExecuteConfunc("crouch_checkbox_check"); Cvar_Set("mn_crouch_reservation_tt", _("%i TUs reserved for crouching/standing up.\nClick to clear."), CL_ActorReservedTUs(le, RES_CROUCH)); crouchReserveButtonState = BUTTON_TURESERVE_CROUCH_RESERVED; } } else if (time >= TU_CROUCH) { if (crouchReserveButtonState != BUTTON_TURESERVE_CROUCH_CLEAR) { UI_ExecuteConfunc("crouch_checkbox_clear"); Cvar_Set("mn_crouch_reservation_tt", _("Reserve %i TUs for crouching/standing up."), TU_CROUCH); crouchReserveButtonState = BUTTON_TURESERVE_CROUCH_CLEAR; } } else { if (crouchReserveButtonState != BUTTON_TURESERVE_CROUCH_DISABLED) { UI_ExecuteConfunc("crouch_checkbox_disable"); Cvar_Set("mn_crouch_reservation_tt", _("Not enough TUs left to reserve for crouching/standing up.")); crouchReserveButtonState = BUTTON_TURESERVE_CROUCH_DISABLED; } } /* Shot reservation button. mn_shot_reservation_tt is the tooltip text */ if (CL_ActorReservedTUs(le, RES_SHOT)) { if (shotReserveButtonState != BUTTON_TURESERVE_SHOT_RESERVED) { UI_ExecuteConfunc("reserve_shot_check"); Cvar_Set("mn_shot_reservation_tt", _("%i TUs reserved for shooting.\nClick to change.\nRight-Click to clear."), CL_ActorReservedTUs(le, RES_SHOT)); shotReserveButtonState = BUTTON_TURESERVE_SHOT_RESERVED; } } else if (HUD_CheckFiremodeReservation()) { if (shotReserveButtonState != BUTTON_TURESERVE_SHOT_CLEAR) { UI_ExecuteConfunc("reserve_shot_clear"); Cvar_Set("mn_shot_reservation_tt", _("Reserve TUs for shooting.")); shotReserveButtonState = BUTTON_TURESERVE_SHOT_CLEAR; } } else { if (shotReserveButtonState != BUTTON_TURESERVE_SHOT_DISABLED) { UI_ExecuteConfunc("reserve_shot_disable"); Cvar_Set("mn_shot_reservation_tt", _("Reserving TUs for shooting not possible.")); shotReserveButtonState = BUTTON_TURESERVE_SHOT_DISABLED; } } /* reaction-fire button */ if (!(le->state & STATE_REACTION)) { if (le->inv.holdsReactionFireWeapon() && time >= HUD_ReactionFireGetTUs(le)) HUD_SetWeaponButton(BT_REACTION, BT_STATE_DESELECT); else HUD_SetWeaponButton(BT_REACTION, BT_STATE_DISABLE); } else { if (le->inv.holdsReactionFireWeapon()) { HUD_DisplayPossibleReaction(le); } else { HUD_DisplayImpossibleReaction(le); } } const char* reason; /* Reload buttons */ const int rightCanBeReloaded = HUD_WeaponCanBeReloaded(le, CID_RIGHT, &reason); if (rightCanBeReloaded != -1) { HUD_SetWeaponButton(BT_RIGHT_RELOAD, BT_STATE_DESELECT); Cvar_Set("mn_reloadright_tt", _("Reload weapon (%i TU)."), rightCanBeReloaded); } else { Cvar_Set("mn_reloadright_tt", "%s", reason); HUD_SetWeaponButton(BT_RIGHT_RELOAD, BT_STATE_DISABLE); } const int leftCanBeReloaded = HUD_WeaponCanBeReloaded(le, CID_LEFT, &reason); if (leftCanBeReloaded != -1) { HUD_SetWeaponButton(BT_LEFT_RELOAD, BT_STATE_DESELECT); Cvar_Set("mn_reloadleft_tt", _("Reload weapon (%i TU)."), leftCanBeReloaded); } else { Cvar_Set("mn_reloadleft_tt", "%s", reason); HUD_SetWeaponButton(BT_LEFT_RELOAD, BT_STATE_DISABLE); } const float shootingPenalty = CL_ActorInjuryModifier(le, MODIFIER_SHOOTING); /* Headgear button */ if (headgear) { const int minheadgeartime = HUD_GetMinimumTUsForUsage(headgear) * shootingPenalty; if (time < minheadgeartime) HUD_SetWeaponButton(BT_HEADGEAR, BT_STATE_DISABLE); else HUD_SetWeaponButton(BT_HEADGEAR, BT_STATE_DESELECT); } else { HUD_SetWeaponButton(BT_HEADGEAR, BT_STATE_DISABLE); } /* Weapon firing buttons. */ if (weaponR) { const int minweaponrtime = HUD_GetMinimumTUsForUsage(weaponR) * shootingPenalty; if (time < minweaponrtime) HUD_SetWeaponButton(BT_RIGHT_FIRE, BT_STATE_DISABLE); else HUD_SetWeaponButton(BT_RIGHT_FIRE, BT_STATE_DESELECT); } else { HUD_SetWeaponButton(BT_RIGHT_FIRE, BT_STATE_DISABLE); } if (weaponL) { const int minweaponltime = HUD_GetMinimumTUsForUsage(weaponL) * shootingPenalty; if (time < minweaponltime) HUD_SetWeaponButton(BT_LEFT_FIRE, BT_STATE_DISABLE); else HUD_SetWeaponButton(BT_LEFT_FIRE, BT_STATE_DESELECT); } else { HUD_SetWeaponButton(BT_LEFT_FIRE, BT_STATE_DISABLE); } /* Check if the firemode reservation popup is shown and refresh its content. (i.e. close&open it) */ { const char* menuName = UI_GetActiveWindowName(); if (menuName[0] != '\0' && strstr(UI_GetActiveWindowName(), POPUPLIST_NODE_NAME)) { /* Update firemode reservation popup. */ /** @todo this is called every frames... is this really needed? */ HUD_PopupFiremodeReservation(le, true); } } } /** * @brief Draw the mouse cursor tooltips in battlescape * @param xOffset * @param yOffset * @param textId The text id to get the tooltip string from. */ static void HUD_DrawMouseCursorText (int xOffset, int yOffset, int textId) { const char* string = UI_GetText(textId); if (string && cl_show_cursor_tooltips->integer) UI_DrawTooltip(string, mousePosX + xOffset, mousePosY - yOffset, viddef.virtualWidth - mousePosX); } /** * @brief Updates the cursor texts when in battlescape */ void HUD_UpdateCursor (void) { /* Offset of the first icon on the x-axis. */ const int iconOffsetX = 16; le_t* le = selActor; if (le) { /* Offset of the first icon on the y-axis. */ int iconOffsetY = 16; /* the space between different icons. */ const int iconSpacing = 2; image_t* image; /* icon width */ const int iconW = 16; /* icon height. */ const int iconH = 16; int width = 0; int bgX = mousePosX + iconOffsetX / 2 - 2; /* checks if icons should be drawn */ if (!(LE_IsCrouched(le) || (le->state & STATE_REACTION))) /* make place holder for icons */ bgX += iconW + 4; /* if exists gets width of player name */ if (UI_GetText(TEXT_MOUSECURSOR_PLAYERNAMES)) R_FontTextSize("f_verysmall", UI_GetText(TEXT_MOUSECURSOR_PLAYERNAMES), viddef.virtualWidth - bgX, LONGLINES_WRAP, &width, nullptr, nullptr, nullptr); /* gets width of background */ if (width == 0 && UI_GetText(TEXT_MOUSECURSOR_RIGHT)) { R_FontTextSize("f_verysmall", UI_GetText(TEXT_MOUSECURSOR_RIGHT), viddef.virtualWidth - bgX, LONGLINES_WRAP, &width, nullptr, nullptr, nullptr); } /* Display 'crouch' icon if actor is crouched. */ if (LE_IsCrouched(le)) { image = R_FindImage("pics/cursors/ducked", it_pic); if (image) R_DrawImage(mousePosX - image->width / 2 + iconOffsetX, mousePosY - image->height / 2 + iconOffsetY, image); } /* Height of 'crouched' icon. */ iconOffsetY += 16; iconOffsetY += iconSpacing; /* Display 'Reaction shot' icon if actor has it activated. */ if (le->state & STATE_REACTION) image = R_FindImage("pics/cursors/reactionfire", it_pic); else image = nullptr; if (image) R_DrawImage(mousePosX - image->width / 2 + iconOffsetX, mousePosY - image->height / 2 + iconOffsetY, image); /* Height of 'reaction fire' icon. ... just in case we add further icons below.*/ iconOffsetY += iconH; iconOffsetY += iconSpacing; /* Display weaponmode (text) heR_ */ HUD_DrawMouseCursorText(iconOffsetX + iconW, -10, TEXT_MOUSECURSOR_RIGHT); } /* playernames */ HUD_DrawMouseCursorText(iconOffsetX + 16, -26, TEXT_MOUSECURSOR_PLAYERNAMES); UI_ResetData(TEXT_MOUSECURSOR_PLAYERNAMES); if (cl_map_debug->integer & MAPDEBUG_TEXT) { /* Display ceiling text */ HUD_DrawMouseCursorText(0, -64, TEXT_MOUSECURSOR_TOP); /* Display floor text */ HUD_DrawMouseCursorText(0, 64, TEXT_MOUSECURSOR_BOTTOM); /* Display left text */ HUD_DrawMouseCursorText(-64, 0, TEXT_MOUSECURSOR_LEFT); } } /** * @brief Shows map pathfinding debugging parameters (if activated) * @param[in] le The current selected actors entity */ static void HUD_MapDebugCursor (const le_t* le) { if (!(cl_map_debug->integer & MAPDEBUG_TEXT)) return; /* Display the floor and ceiling values for the current cell. */ static char topText[UI_MAX_SMALLTEXTLEN]; Com_sprintf(topText, lengthof(topText), "%u-(%i,%i,%i)\n", Grid_Ceiling(cl.mapData->routing, ACTOR_GET_FIELDSIZE(le), truePos), truePos[0], truePos[1], truePos[2]); /* Save the text for later display next to the cursor. */ UI_RegisterText(TEXT_MOUSECURSOR_TOP, topText); /* Display the floor and ceiling values for the current cell. */ static char bottomText[UI_MAX_SMALLTEXTLEN]; Com_sprintf(bottomText, lengthof(bottomText), "%i-(%i,%i,%i)\n", Grid_Floor(cl.mapData->routing, ACTOR_GET_FIELDSIZE(le), truePos), mousePos[0], mousePos[1], mousePos[2]); /* Save the text for later display next to the cursor. */ UI_RegisterText(TEXT_MOUSECURSOR_BOTTOM, bottomText); /* Display the floor and ceiling values for the current cell. */ const int dvec = Grid_MoveNext(&cl.pathMap, mousePos, 0); static char leftText[UI_MAX_SMALLTEXTLEN]; Com_sprintf(leftText, lengthof(leftText), "%i-%i\n", getDVdir(dvec), getDVz(dvec)); /* Save the text for later display next to the cursor. */ UI_RegisterText(TEXT_MOUSECURSOR_LEFT, leftText); } /** * @param actor The actor to update the hud for * @return The amount of TUs needed for the current pending action */ static int HUD_UpdateActorFireMode (le_t* actor) { const Item* selWeapon; /* get weapon */ if (IS_MODE_FIRE_HEADGEAR(actor->actorMode)) { selWeapon = actor->inv.getHeadgear(); } else if (IS_MODE_FIRE_LEFT(actor->actorMode)) { selWeapon = HUD_GetLeftHandWeapon(actor, nullptr); } else { selWeapon = actor->getRightHandItem(); } UI_ResetData(TEXT_MOUSECURSOR_RIGHT); if (!selWeapon) { CL_ActorSetMode(actor, M_MOVE); return 0; } static char infoText[UI_MAX_SMALLTEXTLEN]; int time = 0; const objDef_t* def = selWeapon->def(); if (!def) { /* No valid weapon in the hand. */ CL_ActorSetFireDef(actor, nullptr); } else { /* Check whether this item uses/has ammo. */ if (!selWeapon->ammoDef()) { const fireDef_t* old = nullptr; /* This item does not use ammo, check for existing firedefs in this item. */ /* This is supposed to be a weapon or other usable item. */ if (def->numWeapons > 0) { if (selWeapon->isWeapon() || def->weapons[0] == def) { const fireDef_t* fdArray = selWeapon->getFiredefs(); if (fdArray != nullptr) { /* Get firedef from the weapon (or other usable item) entry instead. */ old = FIRESH_GetFiredef(def, fdArray->weapFdsIdx, actor->currentSelectedFiremode); } } } CL_ActorSetFireDef(actor, old); } else { const fireDef_t* fdArray = selWeapon->getFiredefs(); if (fdArray != nullptr) { const fireDef_t* old = FIRESH_GetFiredef(selWeapon->ammoDef(), fdArray->weapFdsIdx, actor->currentSelectedFiremode); /* reset the align if we switched the firemode */ CL_ActorSetFireDef(actor, old); } } } if (!GAME_ItemIsUseable(def)) { HUD_DisplayMessage(_("You cannot use this unknown item.\nYou need to research it first.")); CL_ActorSetMode(actor, M_MOVE); } else if (actor->fd) { const int hitProbability = CL_GetHitProbability(actor); static char mouseText[UI_MAX_SMALLTEXTLEN]; Com_sprintf(infoText, lengthof(infoText), "%s\n%s (%i) [%i%%] %i\n", _(def->name), _(actor->fd->name), actor->fd->ammo, hitProbability, CL_ActorTimeForFireDef(actor, actor->fd)); /* Save the text for later display next to the cursor. */ Q_strncpyz(mouseText, infoText, lengthof(mouseText)); UI_RegisterText(TEXT_MOUSECURSOR_RIGHT, mouseText); time = CL_ActorTimeForFireDef(actor, actor->fd); /* if no TUs left for this firing action * or if the weapon is reloadable and out of ammo, * then change to move mode */ if (selWeapon->mustReload() || CL_ActorUsableTUs(actor) < time) CL_ActorSetMode(actor, M_MOVE); } else if (selWeapon) { Com_sprintf(infoText, lengthof(infoText), _("%s\n(empty)\n"), _(def->name)); } UI_RegisterText(TEXT_STANDARD, infoText); return time; } /** * @param[in] actor The actor to update the hud for * @return The amount of TUs needed for the current pending action */ static int HUD_UpdateActorMove (const le_t* actor) { const int reservedTUs = CL_ActorReservedTUs(actor, RES_ALL_ACTIVE); static char infoText[UI_MAX_SMALLTEXTLEN]; if (actor->actorMoveLength == ROUTING_NOT_REACHABLE) { UI_ResetData(TEXT_MOUSECURSOR_RIGHT); if (reservedTUs > 0) Com_sprintf(infoText, lengthof(infoText), _("Morale %i | Reserved TUs: %i\n"), actor->morale, reservedTUs); else Com_sprintf(infoText, lengthof(infoText), _("Morale %i"), actor->morale); } else { static char mouseText[UI_MAX_SMALLTEXTLEN]; const int moveMode = CL_ActorMoveMode(actor); if (reservedTUs > 0) Com_sprintf(infoText, lengthof(infoText), _("Morale %i | Reserved TUs: %i\n%s %i (%i|%i TUs left)\n"), actor->morale, reservedTUs, _(moveModeDescriptions[moveMode]), actor->actorMoveLength, actor->TU - actor->actorMoveLength, actor->TU - reservedTUs - actor->actorMoveLength); else Com_sprintf(infoText, lengthof(infoText), _("Morale %i\n%s %i (%i TUs left)\n"), actor->morale, _(moveModeDescriptions[moveMode]), actor->actorMoveLength, actor->TU - actor->actorMoveLength); if (actor->actorMoveLength <= CL_ActorUsableTUs(actor)) Com_sprintf(mouseText, lengthof(mouseText), "%i (%i)\n", actor->actorMoveLength, CL_ActorUsableTUs(actor)); else Com_sprintf(mouseText, lengthof(mouseText), "- (-)\n"); UI_RegisterText(TEXT_MOUSECURSOR_RIGHT, mouseText); } UI_RegisterText(TEXT_STANDARD, infoText); return actor->actorMoveLength; } static void HUD_UpdateActorCvar (const le_t* actor) { Cvar_SetValue("mn_hp", actor->HP); Cvar_SetValue("mn_hpmax", actor->maxHP); Cvar_SetValue("mn_tu", actor->TU); Cvar_SetValue("mn_tumax", actor->maxTU); Cvar_SetValue("mn_tureserved", CL_ActorReservedTUs(actor, RES_ALL_ACTIVE)); Cvar_SetValue("mn_morale", actor->morale); Cvar_SetValue("mn_moralemax", actor->maxMorale); Cvar_SetValue("mn_stun", actor->STUN); Cvar_Set("mn_tu_tooltips", _("Time Units\n- Available: %i (of %i)\n- Reserved: %i\n- Remaining: %i\n"), actor->TU, actor->maxTU, CL_ActorReservedTUs(actor, RES_ALL_ACTIVE), CL_ActorUsableTUs(actor)); /* animation and weapons */ const char* animName = R_AnimGetName(&actor->as, actor->model1); if (animName) Cvar_Set("mn_anim", "%s", animName); if (actor->getRightHandItem()) { const Item* item = actor->getRightHandItem(); Cvar_Set("mn_rweapon", "%s", item->def()->model); Cvar_Set("mn_rweapon_item", "%s", item->def()->id); } else { Cvar_Set("mn_rweapon", ""); Cvar_Set("mn_rweapon_item", ""); } if (actor->getLeftHandItem()) { const Item* item = actor->getLeftHandItem(); Cvar_Set("mn_lweapon", "%s", item->def()->model); Cvar_Set("mn_lweapon_item", "%s", item->def()->id); } else { Cvar_Set("mn_lweapon", ""); Cvar_Set("mn_lweapon_item", ""); } /* print ammo */ const Item* itemRight = actor->getRightHandItem(); if (itemRight) Cvar_SetValue("mn_ammoright", itemRight->getAmmoLeft()); else Cvar_Set("mn_ammoright", ""); const Item* itemLeft = HUD_GetLeftHandWeapon(actor, nullptr); if (itemLeft) Cvar_SetValue("mn_ammoleft", itemLeft->getAmmoLeft()); else Cvar_Set("mn_ammoleft", ""); } static const char* HUD_GetPenaltyString (const int type) { switch (type) { case MODIFIER_ACCURACY: return _("accuracy"); case MODIFIER_SHOOTING: return _("shooting speed"); case MODIFIER_MOVEMENT: return _("movement speed"); case MODIFIER_SIGHT: return _("sight range"); case MODIFIER_REACTION: return _("reaction speed"); case MODIFIER_TU: return _("TU"); default: return ""; } } static void HUD_ActorWoundData_f (void) { if (!CL_BattlescapeRunning()) return; /* check if actor exists */ if (!selActor) return; int bodyPart; woundInfo_t* wounds = &selActor->wounds; const character_t* chr = CL_ActorGetChr(selActor); const BodyData* bodyData = chr->teamDef->bodyTemplate; for (bodyPart = 0; bodyPart < bodyData->numBodyParts(); ++bodyPart) { const int woundThreshold = selActor->maxHP * bodyData->woundThreshold(bodyPart); if (wounds->woundLevel[bodyPart] + wounds->treatmentLevel[bodyPart] * 0.5 > woundThreshold) { const int bleeding = wounds->woundLevel[bodyPart] * (wounds->woundLevel[bodyPart] > woundThreshold ? bodyData->bleedingFactor(bodyPart) : 0); char text[256]; int penalty; Com_sprintf(text, lengthof(text), CHRSH_IsTeamDefRobot(chr->teamDef) ? _("Damaged %s (deterioration: %i)\n") : _("Wounded %s (bleeding: %i)\n"), _(bodyData->name(bodyPart)), bleeding); for (penalty = MODIFIER_ACCURACY; penalty < MODIFIER_MAX; penalty++) if (bodyData->penalty(bodyPart, static_cast(penalty)) != 0) Q_strcat(text, lengthof(text), _("- Reduced %s\n"), HUD_GetPenaltyString(penalty)); UI_ExecuteConfunc("actor_wounds %s %i \"%s\"", bodyData->id(bodyPart), bleeding, text); } } } /** * @brief Update the equipment weight for the selected actor. */ static void HUD_UpdateActorLoad_f (void) { if (!CL_BattlescapeRunning()) return; /* check if actor exists */ if (!selActor) return; const character_t* chr = CL_ActorGetChr(selActor); const float invWeight = selActor->inv.getWeight(); const int maxWeight = GAME_GetChrMaxLoad(chr); const float penalty = GET_ENCUMBRANCE_PENALTY(invWeight, chr->score.skills[ABILITY_POWER]); const int normalTU = GET_TU(chr->score.skills[ABILITY_SPEED], 1.0f - WEIGHT_NORMAL_PENALTY); const int tus = GET_TU(chr->score.skills[ABILITY_SPEED], penalty); const int tuPenalty = tus - normalTU; int count = 0; const Container* cont = nullptr; while ((cont = chr->inv.getNextCont(cont))) { Item* item = nullptr; while ((item = cont->getNextItem(item))) { const fireDef_t* fireDef = item->getFiredefs(); if (fireDef == nullptr) continue; for (int i = 0; i < MAX_FIREDEFS_PER_WEAPON; i++) { const fireDef_t &fd = fireDef[i]; if (fd.time > 0 && fd.time > tus) { if (count <= 0) Com_sprintf(popupText, sizeof(popupText), _("This soldier no longer has enough TUs to use the following items:\n\n")); Q_strcat(popupText, sizeof(popupText), "%s: %s (%i)\n", _(item->def()->name), _(fd.name), fd.time); ++count; } } } } if (count > 0) UI_Popup(_("Warning"), popupText); char label[MAX_VAR]; char tooltip[MAX_VAR]; Com_sprintf(label, sizeof(label), "%g/%i %s", invWeight, maxWeight, _("Kg")); Com_sprintf(tooltip, sizeof(tooltip), "%s %i (%+i)", _("TU:"), tus, tuPenalty); UI_ExecuteConfunc("inv_actorload \"%s\" \"%s\" %f %i", label, tooltip, WEIGHT_NORMAL_PENALTY - (1.0f - penalty), count); } /** * @brief Updates the hud for one actor * @param actor The actor to update the hud values for */ static void HUD_UpdateActor (le_t* actor) { int time; HUD_UpdateActorCvar(actor); /* write info */ time = 0; /* handle actor in a panic */ if (LE_IsPanicked(actor)) { UI_RegisterText(TEXT_STANDARD, _("Currently panics!\n")); } else if (displayRemainingTus[REMAINING_TU_CROUCH]) { if (CL_ActorUsableTUs(actor) >= TU_CROUCH) time = TU_CROUCH; } else if (displayRemainingTus[REMAINING_TU_RELOAD_RIGHT] || displayRemainingTus[REMAINING_TU_RELOAD_LEFT]) { const Item* item; containerIndex_t container; if (displayRemainingTus[REMAINING_TU_RELOAD_RIGHT] && actor->getRightHandItem()) { container = CID_RIGHT; item = actor->getRightHandItem(); } else if (displayRemainingTus[REMAINING_TU_RELOAD_LEFT] && actor->getLeftHandItem()) { container = NONE; item = HUD_GetLeftHandWeapon(actor, &container); } else { container = NONE; item = nullptr; } if (item && item->def() && item->ammoDef() && item->isReloadable()) { const int reloadtime = HUD_CalcReloadTime(actor, item->def(), container); if (reloadtime != -1 && reloadtime <= CL_ActorUsableTUs(actor)) time = reloadtime; } } else if (CL_ActorFireModeActivated(actor->actorMode)) { time = HUD_UpdateActorFireMode(actor); } else { /* If the mouse is outside the world, and we haven't placed the cursor in pend * mode already */ if (IN_GetMouseSpace() != MS_WORLD && actor->actorMode < M_PEND_MOVE) actor->actorMoveLength = ROUTING_NOT_REACHABLE; time = HUD_UpdateActorMove(actor); } /* Calculate remaining TUs. */ /* We use the full count of TUs since the "reserved" bar is overlaid over this one. */ time = std::max(0, actor->TU - time); Cvar_Set("mn_turemain", "%i", time); HUD_MapDebugCursor(actor); } /** * @brief Updates console vars for an actor. * * This function updates the cvars for the hud (battlefield) * unlike CL_ActorCvars and CL_UGVCvars which updates them for * displaying the data in the menu system * * @sa CL_ActorCvars * @sa CL_UGVCvars */ void HUD_Update (void) { if (cls.state != ca_active) return; /* worldlevel */ if (cl_worldlevel->modified) { int i; for (i = 0; i < PATHFINDING_HEIGHT; i++) { int status = 0; if (i == cl_worldlevel->integer) status = 2; else if (i < cl.mapMaxLevel) status = 1; UI_ExecuteConfunc("updateLevelStatus %i %i", i, status); } cl_worldlevel->modified = false; } /* force them empty first */ Cvar_Set("mn_anim", "stand0"); Cvar_Set("mn_rweapon", ""); Cvar_Set("mn_lweapon", ""); if (selActor) { HUD_UpdateActor(selActor); } else if (!cl.numTeamList) { /* This will stop the drawing of the bars over the whole screen when we test maps. */ Cvar_SetValue("mn_hp", 0); Cvar_SetValue("mn_hpmax", 100); Cvar_SetValue("mn_tu", 0); Cvar_SetValue("mn_tumax", 100); Cvar_SetValue("mn_tureserved", 0); Cvar_SetValue("mn_morale", 0); Cvar_SetValue("mn_moralemax", 100); Cvar_SetValue("mn_stun", 0); } } /** * @brief Callback that is called when the cl_selected cvar was changed * @param cvarName The cvar name (cl_selected) * @param oldValue The old value of the cvar (a sane actor idx) * @param newValue The new value of the cvar (a sane actor idx) * @param data Unused here, but required by cvarChangeListenerFunc_t */ static void HUD_ActorSelectionChangeListener (const char* cvarName, const char* oldValue, const char* newValue, void* data) { if (!CL_OnBattlescape()) return; if (newValue[0] != '\0') { const int actorIdx = atoi(newValue); const size_t size = lengthof(cl.teamList); if (actorIdx >= 0 && actorIdx < size) UI_ExecuteConfunc("hudselect %s", newValue); } } /** * @brief Callback that is called when the right hand weapon of the current selected actor changed * @param cvarName The cvar name * @param oldValue The old value of the cvar * @param newValue The new value of the cvar * @param data Unused here, but required by cvarChangeListenerFunc_t */ static void HUD_RightHandChangeListener (const char* cvarName, const char* oldValue, const char* newValue, void* data) { if (!CL_OnBattlescape()) return; if (Q_streq(oldValue, newValue)) return; HUD_UpdateButtons(selActor); } /** * @brief Callback that is called when the left hand weapon of the current selected actor changed * @param cvarName The cvar name * @param oldValue The old value of the cvar * @param newValue The new value of the cvar * @param data Unused here, but required by cvarChangeListenerFunc_t */ static void HUD_LeftHandChangeListener (const char* cvarName, const char* oldValue, const char* newValue, void* data) { if (!CL_OnBattlescape()) return; if (Q_streq(oldValue, newValue)) return; HUD_UpdateButtons(selActor); } /** * @brief Callback that is called when the remaining TUs for the current selected actor changed * @param cvarName The cvar name * @param oldValue The old value of the cvar * @param newValue The new value of the cvar * @param data Unused here, but required by cvarChangeListenerFunc_t */ static void HUD_TUChangeListener (const char* cvarName, const char* oldValue, const char* newValue, void* data) { if (!CL_OnBattlescape()) return; if (Q_streq(oldValue, newValue)) return; HUD_UpdateButtons(selActor); } static bool CL_CvarWorldLevel (cvar_t* cvar) { const int maxLevel = cl.mapMaxLevel ? cl.mapMaxLevel - 1 : PATHFINDING_HEIGHT - 1; return Cvar_AssertValue(cvar, 0, maxLevel, true); } /** * @brief Checks that the given cvar is a valid hud cvar * @param cvar The cvar to check * @return @c true if cvar is valid, @c false otherwise */ static bool HUD_CheckCLHud (cvar_t* cvar) { const uiNode_t* window = UI_GetWindow(cvar->string); if (window == nullptr) { return false; } if (window->super == nullptr) { return false; } /** * @todo check for multiple base classes */ return Q_streq(window->super->name, "hud"); } /** * @brief Display the user interface * @param optionWindowName Name of the window used to display options, else nullptr if nothing */ void HUD_InitUI (const char* optionWindowName) { OBJZERO(buttonStates); if (!HUD_CheckCLHud(cl_hud)) { Cvar_Set("cl_hud", "hud_default"); } UI_InitStack(cl_hud->string, optionWindowName); UI_ExecuteConfunc("hudinit"); } /** * @brief Checks that the given cvar is a valid hud cvar * @param cvar The cvar to check and to modify if the value is invalid * @return @c true if the valid is invalid, @c false otherwise */ static bool HUD_CvarCheckMNHud (cvar_t* cvar) { if (!HUD_CheckCLHud(cl_hud)) { Cvar_Reset(cvar); return true; } return false; } void HUD_InitStartup (void) { HUD_InitCallbacks(); Cmd_AddCommand("hud_remainingtus", HUD_RemainingTUs_f, "Define if remaining TUs should be displayed in the TU-bar for some hovered-over button."); Cmd_AddCommand("hud_shotreserve", HUD_ShotReserve_f, "Reserve TUs for the selected entry in the popup."); Cmd_AddCommand("hud_shotreservationpopup", HUD_PopupFiremodeReservation_f, "Pop up a list of possible firemodes for reservation in the current turn."); Cmd_AddCommand("hud_selectreactionfiremode", HUD_SelectReactionFiremode_f, "Change/Select firemode used for reaction fire."); Cmd_AddCommand("hud_listfiremodes", HUD_DisplayFiremodes_f, "Display a list of firemodes for a weapon+ammo."); Cmd_AddCommand("hud_listactions", HUD_DisplayActions_f, "Display a list of action from the selected soldier."); Cmd_AddCommand("hud_updateactorwounds", HUD_ActorWoundData_f, "Update info on actor wounds."); Cmd_AddCommand("hud_updateactorload", HUD_UpdateActorLoad_f, "Update the HUD with the selected actor inventory load."); /** @note We can't check the value at startup cause scripts are not yet loaded */ cl_hud = Cvar_Get("cl_hud", "hud_default", CVAR_ARCHIVE | CVAR_LATCH, "Current selected HUD."); Cvar_SetCheckFunction("cl_hud", HUD_CvarCheckMNHud); cl_worldlevel = Cvar_Get("cl_worldlevel", "0", 0, "Current worldlevel in tactical mode."); Cvar_SetCheckFunction("cl_worldlevel", CL_CvarWorldLevel); cl_worldlevel->modified = false; Cvar_Get("mn_ammoleft", "", 0, "The remaining amount of ammunition in the left hand weapon."); Cvar_Get("mn_lweapon", "", 0, "The left hand weapon model of the current selected actor - empty if no weapon."); Cvar_RegisterChangeListener("mn_ammoleft", HUD_LeftHandChangeListener); Cvar_RegisterChangeListener("mn_lweapon", HUD_LeftHandChangeListener); Cvar_Get("mn_ammoright", "", 0, "The remaining amount of ammunition in the right hand weapon."); Cvar_Get("mn_rweapon", "", 0, "The right hand weapon model of the current selected actor - empty if no weapon."); Cvar_RegisterChangeListener("mn_ammoright", HUD_RightHandChangeListener); Cvar_RegisterChangeListener("mn_rweapon", HUD_RightHandChangeListener); Cvar_Get("mn_turemain", "", 0, "Remaining TUs for the current selected actor."); Cvar_RegisterChangeListener("mn_turemain", HUD_TUChangeListener); Cvar_RegisterChangeListener("cl_selected", HUD_ActorSelectionChangeListener); cl_hud_message_timeout = Cvar_Get("cl_hud_message_timeout", "2000", CVAR_ARCHIVE, "Timeout for HUD messages (milliseconds)."); cl_show_cursor_tooltips = Cvar_Get("cl_show_cursor_tooltips", "1", CVAR_ARCHIVE, "Show cursor tooltips in tactical game mode."); }