/** * @brief Make sure equipment definitions used to generate teams are proper. * @note Check that the sum of all probabilities is smaller or equal to 100 for a weapon type. * @sa INVSH_EquipActor */ static bool INV_EquipmentDefSanityCheck (void) { int i, j; int sum; bool result = true; for (i = 0; i < csi.numEDs; i++) { const equipDef_t *const ed = &csi.eds[i]; /* only check definitions used for generating teams */ if (!Q_strstart(ed->id, "alien") && !Q_strstart(ed->id, "phalanx")) continue; /* Check primary */ sum = 0; for (j = 0; j < csi.numODs; j++) { const objDef_t *const obj = INVSH_GetItemByIDX(j); if (obj->weapon && obj->fireTwoHanded && (INV_ItemMatchesFilter(obj, FILTER_S_PRIMARY) || INV_ItemMatchesFilter(obj, FILTER_S_HEAVY))) sum += ed->numItems[j]; } if (sum > 100) { Com_Printf("INV_EquipmentDefSanityCheck: Equipment Def '%s' has a total probability for primary weapons greater than 100\n", ed->id); result = false; } /* Check secondary */ sum = 0; for (j = 0; j < csi.numODs; j++) { const objDef_t *const obj = INVSH_GetItemByIDX(j); if (obj->weapon && obj->reload && !obj->deplete && INV_ItemMatchesFilter(obj, FILTER_S_SECONDARY)) sum += ed->numItems[j]; } if (sum > 100) { Com_Printf("INV_EquipmentDefSanityCheck: Equipment Def '%s' has a total probability for secondary weapons greater than 100\n", ed->id); result = false; } /* Check armour */ sum = 0; for (j = 0; j < csi.numODs; j++) { const objDef_t *const obj = INVSH_GetItemByIDX(j); if (INV_ItemMatchesFilter(obj, FILTER_S_ARMOUR)) sum += ed->numItems[j]; } if (sum > 100) { Com_Printf("INV_EquipmentDefSanityCheck: Equipment Def '%s' has a total probability for armours greater than 100\n", ed->id); result = false; } /* Don't check misc: the total probability can be greater than 100 */ } return result; }
/** * @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 * @sa BS_MarketClick_f * @sa BS_AddToList */ static void BS_MarketScroll_f (void) { int i; base_t *base = B_GetCurrentSelectedBase(); if (!base || buyCat >= MAX_FILTERTYPES || buyCat < 0) return; if (Cmd_Argc() < 2) { Com_Printf("Usage: %s <scrollpos>\n", Cmd_Argv(0)); return; } buyList.scroll = atoi(Cmd_Argv(1)); assert(buyList.scroll >= 0); assert(!((buyList.length > MAX_MARKET_MENU_ENTRIES && buyList.scroll >= buyList.length - MAX_MARKET_MENU_ENTRIES))); /* now update the menu pics */ for (i = 0; i < MAX_MARKET_MENU_ENTRIES; i++) { UI_ExecuteConfunc("buy_autoselli %i", i); } /* get item list */ for (i = buyList.scroll; i < buyList.length - buyList.scroll; i++) { const objDef_t *od = BS_GetObjectDefition(&buyList.l[i]); if (i >= MAX_MARKET_MENU_ENTRIES) break; /* Check whether the item matches the proper filter, storage in current base and market. */ if (od && (B_ItemInBase(od, base) > 0 || ccs.eMarket.numItems[od->idx]) && INV_ItemMatchesFilter(od, buyCat)) { const technology_t *tech = RS_GetTechForItem(od); UI_ExecuteConfunc("buy_show %i", i - buyList.scroll); BS_UpdateItem(base, i - buyList.scroll); /* autosell setting */ if (!RS_IsResearched_ptr(tech)) continue; if (ccs.eMarket.autosell[od->idx]) UI_ExecuteConfunc("buy_autoselle %i", i - buyList.scroll); else UI_ExecuteConfunc("buy_autoselld %i", i - buyList.scroll); } } }
/** * @brief Searches if there is an item at location (x/y) in a scrollable container. You can also provide an item to search for directly (x/y is ignored in that case). * @note x = x-th item in a row, y = row. i.e. x/y does not equal the "grid" coordinates as used in those containers. * @param[in] i Pointer to the inventory where we will search. * @param[in] container Container in the inventory. * @param[in] x/y Position in the scrollable container that you want to check. Ignored if "item" is set. * @param[in] item The item to search. Will ignore "x" and "y" if set, it'll also search invisible items. * @param[in] filterType Enum definition of type (types of items for filtering purposes). * @return @c invList_t Pointer to the invList_t/item that is located at x/y or equals "item". * @sa INVSH_SearchInInventory */ invList_t *INV_SearchInInventoryWithFilter (const inventory_t* const i, const invDef_t * container, const objDef_t *item, const itemFilterTypes_t filterType) { invList_t *ic; if (i == NULL) return NULL; if (item == NULL) return NULL; for (ic = i->c[container->id]; ic; ic = ic->next) { /* Search only in the items that could get displayed. */ if (ic && ic->item.item && (filterType == MAX_FILTERTYPES || INV_ItemMatchesFilter(ic->item.item, filterType))) { /* We search _everything_, no matter what location it is (i.e. x/y are ignored). */ if (item == ic->item.item) return ic; } } /* No item with these coordinates (or matching item) found. */ return NULL; }
/** * @brief Searches if there is an item at location (x/y) in a scrollable container. You can also provide an item to search for directly (x/y is ignored in that case). * @note x = x-th item in a row, y = row. i.e. x/y does not equal the "grid" coordinates as used in those containers. * @param[in] inv Pointer to the inventory where we will search. * @param[in] container Container in the inventory. * @param[in] itemType The item to search. Will ignore "x" and "y" if set, it'll also search invisible items. * @param[in] filterType Enum definition of type (types of items for filtering purposes). * @return @c invList_t Pointer to the invList_t/item that is located at x/y or equals "item". * @sa inventory_t::getItemAtPos */ invList_t *INV_SearchInInventoryWithFilter (const inventory_t* const inv, const invDef_t *container, const objDef_t *itemType, const itemFilterTypes_t filterType) { invList_t *ic; if (inv == nullptr) return nullptr; if (itemType == nullptr) return nullptr; for (ic = inv->getContainer3(container->id); ic; ic = ic->getNext()) { /* Search only in the items that could get displayed. */ if (ic && ic->def() && (filterType == MAX_FILTERTYPES || INV_ItemMatchesFilter(ic->def(), filterType))) { /* We search _everything_, no matter what location it is (i.e. x/y are ignored). */ if (itemType == ic->def()) return ic; } } /* No item with these coordinates (or matching item) found. */ return nullptr; }
/** * @brief Updates the Buy/Sell menu list. * @param[in] base Pointer to the base to buy/sell at * @sa BS_BuyType_f */ static void BS_BuyType (const base_t *base) { const objDef_t *od; int i, j = 0; char tmpbuf[MAX_VAR]; if (!base || buyCat >= MAX_FILTERTYPES || buyCat < 0) return; CP_UpdateCredits(ccs.credits); bsMarketNames = NULL; bsMarketStorage = NULL; bsMarketMarket = NULL; bsMarketPrices = NULL; UI_ResetData(TEXT_ITEMDESCRIPTION); /* hide autosell checkboxes by default */ for (i = 0; i < MAX_MARKET_MENU_ENTRIES; i++) { UI_ExecuteConfunc("buy_autoselli %i", i); } switch (buyCat) { case FILTER_AIRCRAFT: /* Aircraft */ { const aircraft_t *aircraftTemplate; for (i = 0, j = 0, aircraftTemplate = ccs.aircraftTemplates; i < ccs.numAircraftTemplates; i++, aircraftTemplate++) { if (!BS_AircraftIsOnMarket(aircraftTemplate)) continue; assert(aircraftTemplate->tech); if (BS_GetStorageAmountInBase(base, aircraftTemplate->id) + BS_GetAircraftOnMarket(aircraftTemplate) > 0) { if (j >= buyList.scroll && j < MAX_MARKET_MENU_ENTRIES) { UI_ExecuteConfunc("buy_show %i", j - buyList.scroll); } BS_AddToList(aircraftTemplate->name, BS_GetStorageAmountInBase(base, aircraftTemplate->id), BS_GetAircraftOnMarket(aircraftTemplate), BS_GetAircraftBuyingPrice(aircraftTemplate)); if (j >= MAX_BUYLIST) Com_Error(ERR_DROP, "Increase the MAX_BUYLIST value to handle that much items\n"); buyList.l[j].item = NULL; buyList.l[j].aircraft = aircraftTemplate; buyList.length = j + 1; BS_UpdateItem(base, j - buyList.scroll); j++; } } } break; case FILTER_CRAFTITEM: /* Aircraft items */ /* get item list */ for (i = 0, j = 0, od = csi.ods; i < csi.numODs; i++, od++) { if (!BS_IsOnMarket(od)) continue; /* Check whether the item matches the proper filter, storage in current base and market. */ if ((B_ItemInBase(od, base) || ccs.eMarket.numItems[i]) && INV_ItemMatchesFilter(od, FILTER_CRAFTITEM)) { if (j >= buyList.scroll && j < MAX_MARKET_MENU_ENTRIES) { const technology_t *tech = RS_GetTechForItem(od); UI_ExecuteConfunc("buy_show %i", j - buyList.scroll); if (RS_IsResearched_ptr(tech)) { if (ccs.eMarket.autosell[i]) UI_ExecuteConfunc("buy_autoselle %i", j - buyList.scroll); else UI_ExecuteConfunc("buy_autoselld %i", j - buyList.scroll); } } BS_AddToList(od->name, B_ItemInBase(od, base), ccs.eMarket.numItems[i], BS_GetItemBuyingPrice(od)); if (j >= MAX_BUYLIST) Com_Error(ERR_DROP, "Increase the MAX_FILTERLIST value to handle that much items\n"); buyList.l[j].item = od; buyList.l[j].aircraft = NULL; buyList.length = j + 1; BS_UpdateItem(base, j - buyList.scroll); j++; } } break; default: /* Normal items */ if (buyCat < MAX_SOLDIER_FILTERTYPES || buyCat == FILTER_DUMMY) { /* get item list */ for (i = 0, j = 0, od = csi.ods; i < csi.numODs; i++, od++) { if (!BS_IsOnMarket(od)) continue; /* Check whether the item matches the proper filter, storage in current base and market. */ if ((B_ItemInBase(od, base) || ccs.eMarket.numItems[i]) && INV_ItemMatchesFilter(od, buyCat)) { BS_AddToList(od->name, B_ItemInBase(od, base), ccs.eMarket.numItems[i], BS_GetItemBuyingPrice(od)); /* Set state of Autosell button. */ if (j >= buyList.scroll && j < MAX_MARKET_MENU_ENTRIES) { const technology_t *tech = RS_GetTechForItem(od); UI_ExecuteConfunc("buy_show %i", j - buyList.scroll); if (RS_IsResearched_ptr(tech)) { if (ccs.eMarket.autosell[i]) UI_ExecuteConfunc("buy_autoselle %i", j - buyList.scroll); else UI_ExecuteConfunc("buy_autoselld %i", j - buyList.scroll); } } if (j >= MAX_BUYLIST) Com_Error(ERR_DROP, "Increase the MAX_BUYLIST value to handle that much items\n"); buyList.l[j].item = od; buyList.l[j].aircraft = NULL; buyList.length = j + 1; BS_UpdateItem(base, j - buyList.scroll); j++; } } } break; } for (; j < MAX_MARKET_MENU_ENTRIES; j++) { /* Hide the rest of the entries. */ UI_ExecuteConfunc("buy_hide %i", j); } /* Update some menu cvars. */ /* Set up base capacities. */ Com_sprintf(tmpbuf, sizeof(tmpbuf), "%i/%i", CAP_GetCurrent(base, CAP_ITEMS), CAP_GetMax(base, CAP_ITEMS)); Cvar_Set("mn_bs_storage", tmpbuf); /* select first item */ if (buyList.length) { switch (buyCat) { /** @sa BS_MarketClick_f */ case FILTER_AIRCRAFT: BS_MarketAircraftDescription(buyList.l[0].aircraft); break; case FILTER_CRAFTITEM: Cvar_Set("mn_aircraftname", ""); /** @todo Use craftitem name here? See also BS_MarketClick_f */ /* Select current item or first one. */ if (currentSelectedMenuEntry) UP_AircraftItemDescription(currentSelectedMenuEntry); else UP_AircraftItemDescription(buyList.l[0].item); break; default: assert(buyCat != MAX_FILTERTYPES); /* Select current item or first one. */ if (currentSelectedMenuEntry) INV_ItemDescription(currentSelectedMenuEntry); else INV_ItemDescription(buyList.l[0].item); break; } } else { /* reset description */ INV_ItemDescription(NULL); } UI_RegisterLinkedListText(TEXT_MARKET_NAMES, bsMarketNames); UI_RegisterLinkedListText(TEXT_MARKET_STORAGE, bsMarketStorage); UI_RegisterLinkedListText(TEXT_MARKET_MARKET, bsMarketMarket); UI_RegisterLinkedListText(TEXT_MARKET_PRICES, bsMarketPrices); }
/** * @brief Checks if the given object/item matched the given filter type. * @param[in] obj A pointer to an objDef_t item. * @param[in] filterType Filter type to check against. * @return @c true if obj is in filterType */ bool INV_ItemMatchesFilter (const objDef_t *obj, const itemFilterTypes_t filterType) { int i; if (!obj) return false; switch (filterType) { case FILTER_S_PRIMARY: if (obj->isPrimary && !obj->isHeavy) return true; /* Check if one of the items that uses this ammo matches this filter type. */ for (i = 0; i < obj->numWeapons; i++) { const objDef_t *weapon = obj->weapons[i]; if (weapon && weapon != obj && INV_ItemMatchesFilter(weapon, filterType)) return true; } break; case FILTER_S_SECONDARY: if (obj->isSecondary && !obj->isHeavy) return true; /* Check if one of the items that uses this ammo matches this filter type. */ for (i = 0; i < obj->numWeapons; i++) { const objDef_t *weapon = obj->weapons[i]; if (weapon && weapon != obj && INV_ItemMatchesFilter(weapon, filterType)) return true; } break; case FILTER_S_HEAVY: if (obj->isHeavy) return true; /* Check if one of the items that uses this ammo matches this filter type. */ for (i = 0; i < obj->numWeapons; i++) { const objDef_t *weapon = obj->weapons[i]; if (weapon && weapon != obj && INV_ItemMatchesFilter(weapon, filterType)) return true; } break; case FILTER_S_ARMOUR: return INV_IsArmour(obj); case FILTER_S_MISC: return obj->isMisc; case FILTER_CRAFTITEM: /** @todo Should we handle FILTER_AIRCRAFT here as well? */ return INV_IsCraftItem(obj); case FILTER_UGVITEM: return obj->isUGVitem; case FILTER_DUMMY: return obj->isDummy; case FILTER_AIRCRAFT: return Q_streq(obj->type, "aircraft"); case FILTER_DISASSEMBLY: /** @todo I guess we should search for components matching this item here. */ break; case MAX_SOLDIER_FILTERTYPES: case MAX_FILTERTYPES: case FILTER_ENSURE_32BIT: Com_Printf("INV_ItemMatchesFilter: Unknown filter type for items: %i\n", filterType); break; } /* The given filter type is unknown. */ return false; }
/** * @brief Fill market item list */ static void BS_FillMarket_f (void) { const base_t *base = B_GetCurrentSelectedBase(); itemFilterTypes_t type; if (cgi->Cmd_Argc() < 2) { Com_Printf("Usage: %s <category>\n", cgi->Cmd_Argv(0)); return; } if (cgi->Cmd_Argc() >= 3) base = B_GetFoundedBaseByIDX(atoi(cgi->Cmd_Argv(2))); if (!base) { Com_Printf("No/invalid base selected.\n"); return; } type = INV_GetFilterTypeID(cgi->Cmd_Argv(1)); cgi->UI_ExecuteConfunc("ui_market_clear"); switch (type) { case FILTER_UGVITEM: /* show own UGV */ E_Foreach(EMPL_ROBOT, robot) { const ugv_t *ugv = robot->ugv; const technology_t* tech = RS_GetTechByProvided(ugv->id); if (!E_IsInBase(robot, base)) continue; cgi->UI_ExecuteConfunc("ui_market_add \"ugv-%d\" \"%s\" 1 0 0 %d - \"%s\"", robot->chr.ucn, _(tech->name), ugv->price, E_IsAwayFromBase(robot) ? _("UGV is away from home") : "-"); } /* show buyable UGV */ for (int i = 0; i < cgi->csi->numUGV; i++) { const ugv_t *ugv = &cgi->csi->ugvs[i]; const technology_t* tech = RS_GetTechByProvided(ugv->id); const objDef_t *ugvWeapon = INVSH_GetItemByID(ugv->weapon); const int buyable = std::min(E_CountUnhiredRobotsByType(ugv), BS_GetItemOnMarket(ugvWeapon)); assert(tech); if (!RS_IsResearched_ptr(tech)) continue; if (buyable <= 0) continue; cgi->UI_ExecuteConfunc("ui_market_add %s \"%s\" 0 %d %d %d - -", ugv->id, _(tech->name), buyable, ugv->price, ugv->price); } /* show (UGV) items */ case FILTER_S_PRIMARY: case FILTER_S_SECONDARY: case FILTER_S_HEAVY: case FILTER_S_MISC: case FILTER_S_ARMOUR: case FILTER_DUMMY: case FILTER_CRAFTITEM: case MAX_FILTERTYPES: { for (int i = 0; i < cgi->csi->numODs; i++) { const objDef_t *od = &cgi->csi->ods[i]; const technology_t *tech = RS_GetTechForItem(od); if (!BS_IsOnMarket(od)) continue; if (B_ItemInBase(od, base) + BS_GetItemOnMarket(od) <= 0) continue; if (type != MAX_FILTERTYPES && !INV_ItemMatchesFilter(od, type)) continue; cgi->UI_ExecuteConfunc("ui_market_add %s \"%s\" %d %d %d %d %s -", od->id, _(od->name), B_ItemInBase(od, base), BS_GetItemOnMarket(od), BS_GetItemBuyingPrice(od), BS_GetItemSellingPrice(od), RS_IsResearched_ptr(tech) ? va("%d", ccs.eMarket.autosell[i]) : "-"); } break; } case FILTER_AIRCRAFT: { AIR_ForeachFromBase(aircraft, base) { cgi->UI_ExecuteConfunc("ui_market_add \"aircraft-%d\" \"%s\" 1 0 0 %d - \"%s\"", aircraft->idx, aircraft->name, BS_GetAircraftSellingPrice(aircraft), AIR_IsAircraftInBase(aircraft) ? "-" : _("Aircraft is away from home")); } for (int i = 0; i < ccs.numAircraftTemplates; i++) { const aircraft_t *aircraft = &ccs.aircraftTemplates[i]; if (!BS_AircraftIsOnMarket(aircraft)) continue; if (!RS_IsResearched_ptr(aircraft->tech)) continue; cgi->UI_ExecuteConfunc("ui_market_add \"%s\" \"%s\" 0 %d %d %d - -", aircraft->id, _(aircraft->tech->name), BS_GetAircraftOnMarket(aircraft), BS_GetAircraftBuyingPrice(aircraft), BS_GetAircraftSellingPrice(aircraft)); } break; } default: break; }