/** * @brief Decreases the number of the firemode to display (for ammo) or the ammo to display (for weapon) * @sa UP_ItemDescription */ static void INV_DecreaseItem_f (void) { const objDef_t* od = currentDisplayedObject; if (!od) return; if (od->numWeapons) { const int current = itemIndex; do { itemIndex--; if (itemIndex < 0) { itemIndex = od->numWeapons - 1; } } while (itemIndex != current && !GAME_ItemIsUseable(od->weapons[itemIndex])); } else if (od->numAmmos) { const int current = itemIndex; do { itemIndex--; if (itemIndex < 0) { itemIndex = od->numAmmos - 1; } } while (itemIndex != current && !GAME_ItemIsUseable(od->ammos[itemIndex])); } INV_ItemDescription(od); }
/** * @brief Fills the ground container of the ui_inventory with unused items from a given * equipment definition * @note Keep in mind that @c ed is changed here - so items are removed and the ground container * of a inventory definition is in general a temp container - that means you should make a copy * of the @c equipDef_t you want to add to the temp ground container of the given @c inv * @todo it's not obvious for the caller that @c ui_inventory pointer must be set * @param[in,out] inv The inventory to add the unused items from @c ed to * @param[in,out] ed The equipment definition to get the used items from that should be added * to the ground container of @c inv * @todo not used nor called by the container node; should be move somewhere else */ void UI_ContainerNodeUpdateEquipment (inventory_t *inv, const equipDef_t *ed) { int i; int *const numItems = Mem_Dup(int, ed->numItems, lengthof(ed->numItems)); /* a 'tiny hack' to add the remaining equipment (not carried) * correctly into buy categories, reloading at the same time; * it is valid only due to the following property: */ assert(MAX_CONTAINERS >= FILTER_AIRCRAFT); for (i = 0; i < csi.numODs; i++) { const objDef_t *od = INVSH_GetItemByIDX(i); /* Don't allow to show unuseable items. */ if (!GAME_ItemIsUseable(od)) continue; while (numItems[i]) { const item_t item = {NONE_AMMO, NULL, od, 0, 0}; if (!cls.i.AddToInventory(&cls.i, inv, &item, INVDEF(csi.idEquip), NONE, NONE, 1)) { /* no space left in the inventory */ break; } numItems[item.item->idx]--; } } Mem_Free(numItems); /* First-time linking of ui_inventory. */ if (ui_inventory && !ui_inventory->c[csi.idEquip]) { ui_inventory->c[csi.idEquip] = inv->c[csi.idEquip]; } }
/** * @brief Generate tooltip text for an item. * @param[in] item The item we want to generate the tooltip text for. * @param[in,out] tooltipText Pointer to a string the information should be written into. * @param[in] stringMaxLength Max. string size of @c tooltipText. * @return Number of lines */ static void UI_GetItemTooltip (item_t item, char *tooltipText, size_t stringMaxLength) { const objDef_t *weapon; assert(item.item); if (item.amount > 1) Com_sprintf(tooltipText, stringMaxLength, "%i x %s\n", item.amount, _(item.item->name)); else Com_sprintf(tooltipText, stringMaxLength, "%s\n", _(item.item->name)); /* Only display further info if item.t is researched */ if (GAME_ItemIsUseable(item.item)) { if (item.item->weapon) { /* Get info about used ammo (if there is any) */ if (item.item == item.ammo) { /* Item has no ammo but might have shot-count */ if (item.ammoLeft) { Q_strcat(tooltipText, va(_("Ammo: %i\n"), item.ammoLeft), stringMaxLength); } } else if (item.ammo) { /* Search for used ammo and display name + ammo count */ Q_strcat(tooltipText, va(_("%s loaded\n"), _(item.ammo->name)), stringMaxLength); Q_strcat(tooltipText, va(_("Ammo: %i\n"), item.ammoLeft), stringMaxLength); } } else if (item.item->numWeapons) { /* Check if this is a non-weapon and non-ammo item */ if (!(item.item->numWeapons == 1 && item.item->weapons[0] == item.item)) { int i; /* If it's ammo get the weapon names it can be used in */ Q_strcat(tooltipText, _("Usable in:\n"), stringMaxLength); for (i = 0; i < item.item->numWeapons; i++) { weapon = item.item->weapons[i]; if (GAME_ItemIsUseable(weapon)) { Q_strcat(tooltipText, va("* %s\n", _(weapon->name)), stringMaxLength); } } } } } }
/** * @brief Compute the next itemID * @note If something found, item type can be find with iterator->itemID * @note If item is available into the container iterator->itemFound point to this element * @note If nothing found (no next element) then iterator->itemID >= csi.numODs */ static void UI_ContainerItemIteratorNext (containerItemIterator_t* iterator) { assert(iterator->groupSteps[iterator->groupID] != CII_END); /* iterate each groups */ for (; iterator->groupSteps[iterator->groupID] != CII_END; iterator->groupID++) { int filter = iterator->groupSteps[iterator->groupID]; /* next */ iterator->itemID++; /* iterate all item type*/ for (;iterator->itemID < csi.numODs; iterator->itemID++) { const objDef_t* obj = INVSH_GetItemByIDX(iterator->itemID); /* gameplay filter */ if (!GAME_ItemIsUseable(obj)) continue; /* type filter */ /** @todo not sure its the right check */ const bool isArmour = obj->isArmour(); const bool isAmmo = obj->numWeapons != 0 && obj->isAmmo(); const bool isWeapon = obj->weapon || obj->isMisc || isArmour; const bool isImplant = obj->implant; if ((filter & CII_WEAPONONLY) && !isWeapon) continue; if ((filter & CII_AMMOONLY) && !isAmmo) continue; if ((filter & CII_IMPLANTONLY) && !isImplant) continue; if (!INV_ItemMatchesFilter(obj, iterator->filterEquipType)) continue; /* exists in inventory filter */ iterator->itemFound = UI_ContainerNodeGetExistingItem(iterator->node, obj, iterator->filterEquipType); if ((filter & CII_AVAILABLEONLY) && iterator->itemFound == nullptr) continue; if ((filter & CII_NOTAVAILABLEONLY) && iterator->itemFound != nullptr) continue; /* we found something */ return; } /* can we search into another group? */ if (iterator->groupSteps[iterator->groupID + 1] != CII_END) iterator->itemID = -1; } /* clean up */ iterator->itemFound = nullptr; }
/** * @brief Prints the description for items (weapons, armour, ...) * @param[in] od The object definition of the item * @note Not only called from UFOpaedia but also from other places to display * weapon and ammo statistics * @todo Do we need to add checks for @c od->isDummy here somewhere? */ void INV_ItemDescription (const objDef_t* od) { static char itemText[UI_MAX_SMALLTEXTLEN]; int i; int count; currentDisplayedObject = od; Cvar_Set("mn_firemodename", ""); Cvar_Set("mn_linkname", ""); if (!od) { /* If nothing selected return */ Cvar_Set("mn_itemname", ""); Cvar_Set("mn_item", ""); UI_ResetData(TEXT_ITEMDESCRIPTION); itemIndex = fireModeIndex = 0; UI_ExecuteConfunc("itemdesc_view 0 0;"); return; } /* select item */ Cvar_Set("mn_itemname", "%s", _(od->name)); Cvar_Set("mn_item", "%s", od->id); count = 0; if (GAME_ItemIsUseable(od)) { if (od->isAmmo()) { /* We display the pre/next buttons for changing weapon only if there are at least 2 researched weapons * we are counting the number of weapons that are usable with this ammo */ for (i = 0; i < od->numWeapons; i++) if (GAME_ItemIsUseable(od->weapons[i])) count++; if (itemIndex >= od->numWeapons || itemIndex < 0) itemIndex = 0; if (count > 0) { while (!GAME_ItemIsUseable(od->weapons[itemIndex])) { itemIndex++; if (itemIndex >= od->numWeapons) itemIndex = 0; } Cvar_ForceSet("mn_linkname", _(od->weapons[itemIndex]->name)); } } else if (od->weapon) { /* We display the pre/next buttons for changing ammo only if there are at least 2 researched ammo * we are counting the number of ammo that is usable with this weapon */ for (i = 0; i < od->numAmmos; i++) if (GAME_ItemIsUseable(od->ammos[i])) count++; if (itemIndex >= od->numAmmos || itemIndex < 0) itemIndex = 0; /* Only display ammos if at least one has been researched */ if (count > 0) { /* We have a weapon that uses ammos */ while (!GAME_ItemIsUseable(od->ammos[itemIndex])) { itemIndex++; if (itemIndex >= od->numAmmos) itemIndex = 0; } Cvar_ForceSet("mn_linkname", _(od->ammos[itemIndex]->name)); } } else { Cvar_ForceSet("mn_linkname", ""); } } /* set description text if item has been researched or one of its ammo/weapon has been researched */ if (count > 0 || GAME_ItemIsUseable(od)) { int numFiredefs = 0; *itemText = '\0'; if (od->isArmour()) { Com_sprintf(itemText, sizeof(itemText), _("Size:\t%i\n"), od->size); Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight); Q_strcat(itemText, sizeof(itemText), "\n"); Q_strcat(itemText, sizeof(itemText), _("^BDamage type:\tProtection:\n")); for (i = 0; i < csi.numDTs; i++) { const damageType_t* dt = &csi.dts[i]; if (!dt->showInMenu) continue; Q_strcat(itemText, sizeof(itemText), _("%s\t%i\n"), _(dt->id), od->ratings[i]); } } else if ((od->weapon && od->numAmmos) || od->isAmmo()) { const objDef_t* odAmmo; if (count > 0) { int weaponIndex; if (od->weapon) { Com_sprintf(itemText, sizeof(itemText), _("%s weapon\n"), (od->fireTwoHanded ? _("Two-handed") : _("One-handed"))); if (od->ammo > 0) Q_strcat(itemText, sizeof(itemText), _("Max ammo:\t%i\n"), od->ammo); odAmmo = (od->numAmmos) ? od->ammos[itemIndex] : od; assert(odAmmo); for (weaponIndex = 0; (weaponIndex < odAmmo->numWeapons) && (odAmmo->weapons[weaponIndex] != od); weaponIndex++) {} } else { odAmmo = od; weaponIndex = itemIndex; } Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight); /** @todo is there ammo with no firedefs? */ if (GAME_ItemIsUseable(odAmmo) && odAmmo->numFiredefs[weaponIndex] > 0) { const fireDef_t* fd; numFiredefs = odAmmo->numFiredefs[weaponIndex]; /* This contains everything common for weapons and ammos */ /* We check if the wanted firemode to display exists. */ if (fireModeIndex > numFiredefs - 1) fireModeIndex = 0; if (fireModeIndex < 0) fireModeIndex = numFiredefs - 1; fd = &odAmmo->fd[weaponIndex][fireModeIndex]; /* We always display the name of the firemode for an ammo */ Cvar_Set("mn_firemodename", "%s", _(fd->name)); /* We display the characteristics of this firemode */ Q_strcat(itemText, sizeof(itemText), _("Skill:\t%s\n"), CL_WeaponSkillToName(fd->weaponSkill)); Q_strcat(itemText, sizeof(itemText), _("Damage:\t%i\n"), (int) (fd->damage[0] + fd->spldmg[0]) * fd->shots); Q_strcat(itemText, sizeof(itemText), _("Time units:\t%i\n"), fd->time); Q_strcat(itemText, sizeof(itemText), _("Range:\t%g\n"), fd->range / UNIT_SIZE); Q_strcat(itemText, sizeof(itemText), _("Spreads:\t%g\n"), (fd->spread[0] + fd->spread[1]) / 2); } } else { Com_sprintf(itemText, sizeof(itemText), _("%s. No detailed info available.\n"), od->isAmmo() ? _("Ammunition") : _("Weapon")); Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight); } } else if (od->weapon) { Com_sprintf(itemText, sizeof(itemText), _("%s ammo-less weapon\n"), (od->fireTwoHanded ? _("Two-handed") : _("One-handed"))); Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight); } else { /* just an item - only primary definition */ Com_sprintf(itemText, sizeof(itemText), _("%s auxiliary equipment\n"), (od->fireTwoHanded ? _("Two-handed") : _("One-handed"))); Q_strcat(itemText, sizeof(itemText), _("Weight:\t%g Kg\n"), od->weight); if (od->numWeapons > 0 && od->numFiredefs[0] > 0) { const fireDef_t* fd = &od->fd[0][0]; Q_strcat(itemText, sizeof(itemText), _("Action:\t%s\n"), _(fd->name)); Q_strcat(itemText, sizeof(itemText), _("Time units:\t%i\n"), fd->time); Q_strcat(itemText, sizeof(itemText), _("Range:\t%g\n"), fd->range / UNIT_SIZE); } } UI_RegisterText(TEXT_ITEMDESCRIPTION, itemText); UI_ExecuteConfunc("itemdesc_view %i %i;", count, numFiredefs); } else { Com_sprintf(itemText, sizeof(itemText), _("Unknown - not useable")); UI_RegisterText(TEXT_ITEMDESCRIPTION, itemText); UI_ExecuteConfunc("itemdesc_view 0 0;"); } }
/** * @note this function is a copy-paste of UI_ContainerNodeDrawItems (with remove of unneeded code) */ static Item* UI_BaseInventoryNodeGetItem (const uiNode_t* const node, int mouseX, int mouseY, int* contX, int* contY) { bool outOfNode = false; vec2_t nodepos; int items = 0; int rowHeight = 0; const int cellWidth = node->box.size[0] / EXTRADATACONST(node).columns; int tempX, tempY; containerItemIterator_t iterator; int currentHeight = 0; if (!contX) contX = &tempX; if (!contY) contY = &tempY; UI_GetNodeAbsPos(node, nodepos); UI_ContainerItemIteratorInit(&iterator, node); for (; iterator.itemID < csi.numODs; UI_ContainerItemIteratorNext(&iterator)) { const int id = iterator.itemID; const objDef_t* obj = INVSH_GetItemByIDX(id); vec2_t pos; vec2_t ammopos; const int col = items % EXTRADATACONST(node).columns; int cellHeight = 0; Item* icItem = iterator.itemFound; int height; /* skip items over and bellow the node view */ if (outOfNode || currentHeight < EXTRADATACONST(node).scrollY.viewPos) { int outHeight; R_FontTextSize("f_verysmall", _(obj->name), cellWidth - 5, LONGLINES_WRAP, nullptr, &outHeight, nullptr, nullptr); outHeight += obj->sy * C_UNIT + 10; if (outHeight > rowHeight) rowHeight = outHeight; if (outOfNode || currentHeight + rowHeight < EXTRADATACONST(node).scrollY.viewPos) { if (col == EXTRADATACONST(node).columns - 1) { currentHeight += rowHeight; rowHeight = 0; } items++; continue; } } Vector2Copy(nodepos, pos); pos[0] += cellWidth * col; pos[1] += currentHeight - EXTRADATACONST(node).scrollY.viewPos; /* check item */ if (mouseY < pos[1]) break; if (mouseX >= pos[0] && mouseX < pos[0] + obj->sx * C_UNIT && mouseY >= pos[1] && mouseY < pos[1] + obj->sy * C_UNIT) { if (icItem) { *contX = icItem->getX(); *contY = icItem->getY(); return icItem; } return nullptr; } pos[1] += obj->sy * C_UNIT; cellHeight += obj->sy * C_UNIT; /* save position for ammo */ Vector2Copy(pos, ammopos); ammopos[0] += obj->sx * C_UNIT + 10; /* draw the item name. */ R_FontTextSize("f_verysmall", _(obj->name), cellWidth - 5, LONGLINES_WRAP, nullptr, &height, nullptr, nullptr); cellHeight += height; /* draw ammos of weapon */ if (obj->weapon && EXTRADATACONST(node).displayAmmoOfWeapon) { for (int ammoIdx = 0; ammoIdx < obj->numAmmos; ammoIdx++) { const objDef_t* objammo = obj->ammos[ammoIdx]; /* skip unusable ammo */ if (!GAME_ItemIsUseable(objammo)) continue; /* find and skip none existing ammo */ icItem = UI_ContainerNodeGetExistingItem(node, objammo, (itemFilterTypes_t) EXTRADATACONST(node).filterEquipType); if (!icItem) continue; /* check ammo (ammopos in on the left-lower corner) */ if (mouseX < ammopos[0] || mouseY >= ammopos[1]) break; if (mouseX >= ammopos[0] && mouseX < ammopos[0] + objammo->sx * C_UNIT && mouseY >= ammopos[1] - objammo->sy * C_UNIT && mouseY < ammopos[1]) { *contX = icItem->getX(); *contY = icItem->getY(); return icItem; } ammopos[0] += objammo->sx * C_UNIT; } } cellHeight += 10; if (cellHeight > rowHeight) { rowHeight = cellHeight; } /* add a margin between rows */ if (col == EXTRADATACONST(node).columns - 1) { currentHeight += rowHeight; rowHeight = 0; if (currentHeight - EXTRADATACONST(node).scrollY.viewPos >= node->box.size[1]) return nullptr; } /* count items */ items++; } *contX = NONE; *contY = NONE; return nullptr; }
/** * @brief Draw the base inventory * @return The full height requested by the current view (not the node height) */ static int UI_BaseInventoryNodeDrawItems (uiNode_t* node, const objDef_t* highlightType) { bool outOfNode = false; vec2_t nodepos; int items = 0; int rowHeight = 0; const int cellWidth = node->box.size[0] / EXTRADATA(node).columns; containerItemIterator_t iterator; int currentHeight = 0; UI_GetNodeAbsPos(node, nodepos); UI_ContainerItemIteratorInit(&iterator, node); for (; iterator.itemID < csi.numODs; UI_ContainerItemIteratorNext(&iterator)) { const int id = iterator.itemID; const objDef_t* obj = INVSH_GetItemByIDX(id); Item tempItem(obj, nullptr, 1); vec3_t pos; vec3_t ammopos; const float* color; bool isHighlight = false; int amount; const int col = items % EXTRADATA(node).columns; int cellHeight = 0; const Item* icItem = iterator.itemFound; /* skip items over and bellow the node view */ if (outOfNode || currentHeight < EXTRADATA(node).scrollY.viewPos) { int height; R_FontTextSize("f_verysmall", _(obj->name), cellWidth - 5, LONGLINES_WRAP, nullptr, &height, nullptr, nullptr); height += obj->sy * C_UNIT + 10; if (height > rowHeight) rowHeight = height; if (outOfNode || currentHeight + rowHeight < EXTRADATA(node).scrollY.viewPos) { if (col == EXTRADATA(node).columns - 1) { currentHeight += rowHeight; rowHeight = 0; } items++; continue; } } Vector2Copy(nodepos, pos); pos[0] += cellWidth * col; pos[1] += currentHeight - EXTRADATA(node).scrollY.viewPos; pos[2] = 0; if (highlightType) { if (obj->isAmmo()) isHighlight = obj->isLoadableInWeapon(highlightType); else isHighlight = highlightType->isLoadableInWeapon(obj); } if (icItem != nullptr) { if (isHighlight) color = colorLoadable; else color = colorDefault; } else { if (isHighlight) color = colorDisabledLoadable; else color = colorDisabledHiden; } if (icItem) amount = icItem->getAmount(); else amount = 0; /* draw item */ pos[0] += obj->sx * C_UNIT / 2.0; pos[1] += obj->sy * C_UNIT / 2.0; UI_DrawItem(node, pos, &tempItem, -1, -1, scale, color); UI_DrawString("f_verysmall", ALIGN_LC, pos[0] + obj->sx * C_UNIT / 2.0, pos[1] + obj->sy * C_UNIT / 2.0, pos[0] + obj->sx * C_UNIT / 2.0, cellWidth - 5, /* maxWidth */ 0, va("x%i", amount)); pos[0] -= obj->sx * C_UNIT / 2.0; pos[1] += obj->sy * C_UNIT / 2.0; cellHeight += obj->sy * C_UNIT; /* save position for ammo */ Vector2Copy(pos, ammopos); ammopos[2] = 0; ammopos[0] += obj->sx * C_UNIT + 10; /* draw the item name. */ cellHeight += UI_DrawString("f_verysmall", ALIGN_UL, pos[0], pos[1], pos[0], cellWidth - 5, /* max width */ 0, _(obj->name)); /* draw ammos of weapon */ if (obj->weapon && EXTRADATA(node).displayAmmoOfWeapon) { for (int ammoIdx = 0; ammoIdx < obj->numAmmos; ammoIdx++) { tempItem.setDef(obj->ammos[ammoIdx]); /* skip weapos that are their own ammo -- oneshot and such */ if (obj == tempItem.def()) continue; /* skip unusable ammo */ if (!GAME_ItemIsUseable(tempItem.def())) continue; /* find and skip none existing ammo */ icItem = UI_ContainerNodeGetExistingItem(node, tempItem.def(), (itemFilterTypes_t) EXTRADATA(node).filterEquipType); if (!icItem) continue; /* Calculate the center of the item model/image. */ ammopos[0] += icItem->def()->sx * C_UNIT / 2.0; ammopos[1] -= icItem->def()->sy * C_UNIT / 2.0; UI_DrawItem(node, ammopos, &tempItem, -1, -1, scale, colorDefault); UI_DrawString("f_verysmall", ALIGN_LC, ammopos[0] + icItem->def()->sx * C_UNIT / 2.0, ammopos[1] + icItem->def()->sy * C_UNIT / 2.0, ammopos[0] + icItem->def()->sx * C_UNIT / 2.0, cellWidth - 5 - ammopos[0], /* maxWidth */ 0, va("x%i", icItem->getAmount())); ammopos[0] += icItem->def()->sx * C_UNIT / 2.0; ammopos[1] += icItem->def()->sy * C_UNIT / 2.0; } } cellHeight += 10; if (cellHeight > rowHeight) { rowHeight = cellHeight; } /* add a marge between rows */ if (col == EXTRADATA(node).columns - 1) { currentHeight += rowHeight; rowHeight = 0; if (currentHeight - EXTRADATA(node).scrollY.viewPos >= node->box.size[1]) outOfNode = true; } /* count items */ items++; } if (rowHeight != 0) { currentHeight += rowHeight; } return currentHeight; }