/** * @brief Remove items until everything fits in storage. * @note items will be randomly selected for removal. * @param[in] base Pointer to the base */ void CAP_RemoveItemsExceedingCapacity (base_t *base) { int i; int objIdx[MAX_OBJDEFS]; /**< Will contain idx of items that can be removed */ int num, cnt; if (CAP_GetFreeCapacity(base, CAP_ITEMS) >= 0) return; for (i = 0, num = 0; i < cgi->csi->numODs; i++) { const objDef_t *obj = INVSH_GetItemByIDX(i); if (!B_ItemIsStoredInBaseStorage(obj)) continue; /* Don't count item that we don't have in base */ if (B_ItemInBase(obj, base) <= 0) continue; objIdx[num++] = i; } cnt = E_CountHired(base, EMPL_ROBOT); /* UGV takes room in storage capacity: we store them with a value MAX_OBJDEFS that can't be used by objIdx */ for (i = 0; i < cnt; i++) { objIdx[num++] = MAX_OBJDEFS; } while (num && CAP_GetFreeCapacity(base, CAP_ITEMS) < 0) { /* Select the item to remove */ const int randNumber = rand() % num; if (objIdx[randNumber] >= MAX_OBJDEFS) { /* A UGV is destroyed: get first one */ Employee* employee = E_GetHiredRobot(base, 0); /* There should be at least a UGV */ assert(employee); E_DeleteEmployee(employee); } else { /* items are destroyed. We guess that all items of a given type are stored in the same location * => destroy all items of this type */ const int idx = objIdx[randNumber]; const objDef_t *od = INVSH_GetItemByIDX(idx); B_UpdateStorageAndCapacity(base, od, -B_ItemInBase(od, base), false); } REMOVE_ELEM(objIdx, randNumber, num); /* Make sure that we don't have an infinite loop */ if (num <= 0) break; } Com_DPrintf(DEBUG_CLIENT, "B_RemoveItemsExceedingCapacity: Remains %i in storage for a maximum of %i\n", CAP_GetCurrent(base, CAP_ITEMS), CAP_GetMax(base, CAP_ITEMS)); }
/** * @brief Buys the given UGV * @param[in] ugv The ugv template of the UGV to buy * @param[out] base Base to buy at * @return @c true if the ugv could get bought, @c false otherwise * @todo Implement this correctly once we have UGV */ bool BS_BuyUGV (const ugv_t* ugv, base_t* base) { const objDef_t* ugvWeapon; if (!ugv) cgi->Com_Error(ERR_DROP, "BS_BuyUGV: Called on nullptr UGV!"); if (!base) cgi->Com_Error(ERR_DROP, "BS_BuyUGV: Called on nullptr base!"); ugvWeapon = INVSH_GetItemByID(ugv->weapon); if (!ugvWeapon) cgi->Com_Error(ERR_DROP, "BS_BuyItem_f: Could not get weapon '%s' for ugv/tank '%s'.", ugv->weapon, ugv->id); if (ccs.credits < ugv->price) return false; if (E_CountUnhiredRobotsByType(ugv) <= 0) return false; if (BS_GetItemOnMarket(ugvWeapon) <= 0) return false; if (CAP_GetFreeCapacity(base, CAP_ITEMS) < UGV_SIZE + ugvWeapon->size) return false; if (!E_HireRobot(base, ugv)) return false; BS_RemoveItemFromMarket(ugvWeapon, 1); CP_UpdateCredits(ccs.credits - ugv->price); B_AddToStorage(base, ugvWeapon, 1); return true; }
/** * @brief Script function to add and remove a scientist to the technology entry in the research-list. */ static void RS_Change_f (void) { base_t* base = B_GetCurrentSelectedBase(); if (cgi->Cmd_Argc() < 2) { Com_Printf("Usage: %s <tech_id>\n", cgi->Cmd_Argv(0)); return; } technology_t* tech = RS_GetTechByID(cgi->Cmd_Argv(1)); if (!tech) { Com_Printf("RS_ChangeScientist_f: Invalid tech '%s'\n", cgi->Cmd_Argv(1)); return; } if (tech->base && tech->base != base) { Com_Printf("RS_ChangeScientist_f: Tech '%s' is not researched in this base\n", cgi->Cmd_Argv(1)); return; } const int diff = atoi(cgi->Cmd_Argv(2)); if (diff == 0) return; if (diff > 0) { RS_AssignScientist(tech, base); } else if (tech->base) { RS_RemoveScientist(tech, nullptr); } cgi->UI_ExecuteConfunc("ui_research_update_topic %s %d", tech->id, tech->scientists); cgi->UI_ExecuteConfunc("ui_research_update_caps %d %d %d %d", E_CountUnassigned(base, EMPL_SCIENTIST), E_CountHired(base, EMPL_SCIENTIST), CAP_GetFreeCapacity(base, CAP_LABSPACE), CAP_GetMax(base, CAP_LABSPACE)); }
/** * @brief Removes all scientists from the selected research-list entry. */ static void RS_Stop_f (void) { const base_t* base = B_GetCurrentSelectedBase(); if (cgi->Cmd_Argc() < 2) { Com_Printf("Usage: %s <tech_id>\n", cgi->Cmd_Argv(0)); return; } technology_t* tech = RS_GetTechByID(cgi->Cmd_Argv(1)); if (!tech) { Com_Printf("RS_Stop_f: Invalid tech '%s'\n", cgi->Cmd_Argv(1)); return; } if (!tech->base) { return; } if (tech->base != base) { Com_Printf("RS_Stop_f: Tech '%s' is not researched in this base\n", cgi->Cmd_Argv(1)); return; } RS_StopResearch(tech); cgi->UI_ExecuteConfunc("ui_research_update_topic %s %d", tech->id, tech->scientists); cgi->UI_ExecuteConfunc("ui_research_update_caps %d %d %d %d", E_CountUnassigned(base, EMPL_SCIENTIST), E_CountHired(base, EMPL_SCIENTIST), CAP_GetFreeCapacity(base, CAP_LABSPACE), CAP_GetMax(base, CAP_LABSPACE)); }
/** * @brief Actions to perform when destroying one hangar. * @param[in] base Pointer to the base where hangar is destroyed. * @param[in] capacity Type of hangar capacity: CAP_AIRCRAFT_SMALL or CAP_AIRCRAFT_BIG * @note called when player destroy its building or hangar is destroyed during base attack. * @note These actions will be performed after we actually remove the building. * @pre we checked before calling this function that all parameters are valid. * @pre building is not under construction. * @sa B_BuildingDestroy_f * @todo If player choose to destroy the building, a popup should ask him if he wants to sell aircraft in it. */ void CAP_RemoveAircraftExceedingCapacity (base_t* base, baseCapacities_t capacity) { linkedList_t *awayAircraft = nullptr; int numAwayAircraft; int randomNum; /* destroy aircraft only if there's not enough hangar (hangar is already destroyed) */ if (CAP_GetFreeCapacity(base, capacity) >= 0) return; /* destroy one aircraft (must not be sold: may be destroyed by aliens) */ AIR_ForeachFromBase(aircraft, base) { const int aircraftSize = aircraft->size; switch (aircraftSize) { case AIRCRAFT_SMALL: if (capacity != CAP_AIRCRAFT_SMALL) continue; break; case AIRCRAFT_LARGE: if (capacity != CAP_AIRCRAFT_BIG) continue; break; default: cgi->Com_Error(ERR_DROP, "B_RemoveAircraftExceedingCapacity: Unknown type of aircraft '%i'", aircraftSize); } /* Only aircraft in hangar will be destroyed by hangar destruction */ if (!AIR_IsAircraftInBase(aircraft)) { if (AIR_IsAircraftOnGeoscape(aircraft)) cgi->LIST_AddPointer(&awayAircraft, (void*)aircraft); continue; } /* Remove aircraft and aircraft items, but do not fire employees */ AIR_DeleteAircraft(aircraft); cgi->LIST_Delete(&awayAircraft); return; } numAwayAircraft = cgi->LIST_Count(awayAircraft); if (!numAwayAircraft) return; /* All aircraft are away from base, pick up one and change it's homebase */ randomNum = rand() % numAwayAircraft; if (!CL_DisplayHomebasePopup((aircraft_t*)cgi->LIST_GetByIdx(awayAircraft, randomNum), false)) { aircraft_t *aircraft = (aircraft_t*)cgi->LIST_GetByIdx(awayAircraft, randomNum); /* No base can hold this aircraft */ UFO_NotifyPhalanxAircraftRemoved(aircraft); if (!MapIsWater(GEO_GetColor(aircraft->pos, MAPTYPE_TERRAIN, nullptr))) CP_SpawnRescueMission(aircraft, nullptr); else { /* Destroy the aircraft and everything onboard - the aircraft pointer * is no longer valid after this point */ /* Pilot skills; really kill pilot in this case? */ AIR_DestroyAircraft(aircraft); } } cgi->LIST_Delete(&awayAircraft); }
/** * @brief Assign as many scientists to the research project as possible. */ static void RS_Max_f (void) { /* The base the tech is researched in. */ base_t* base = B_GetCurrentSelectedBase(); if (!base) return; if (cgi->Cmd_Argc() < 2) { Com_Printf("Usage: %s <tech_id>\n", cgi->Cmd_Argv(0)); return; } /* The technology you want to max out. */ technology_t* tech = RS_GetTechByID(cgi->Cmd_Argv(1)); if (!tech) { Com_Printf("RS_Max_f: Invalid tech '%s'\n", cgi->Cmd_Argv(1)); return; } if (tech->base && tech->base != base) { Com_Printf("RS_Max_f: Tech '%s' is not researched in this base\n", cgi->Cmd_Argv(1)); return; } /* Add as many scientists as possible to this tech. */ while (CAP_GetFreeCapacity(base, CAP_LABSPACE) > 0) { Employee* employee = E_GetUnassignedEmployee(base, EMPL_SCIENTIST); if (!employee) break; RS_AssignScientist(tech, base, employee); if (!employee->isAssigned()) break; } cgi->UI_ExecuteConfunc("ui_research_update_topic %s %d", tech->id, tech->scientists); cgi->UI_ExecuteConfunc("ui_research_update_caps %d %d %d %d", E_CountUnassigned(base, EMPL_SCIENTIST), E_CountHired(base, EMPL_SCIENTIST), CAP_GetFreeCapacity(base, CAP_LABSPACE), CAP_GetMax(base, CAP_LABSPACE)); }
/** * @brief Fills technology list on research UI */ static void RS_FillTechnologyList_f (void) { base_t* base = B_GetCurrentSelectedBase(); if (!base) return; RS_MarkResearchable(base); cgi->UI_ExecuteConfunc("ui_research_update_caps %d %d %d %d", E_CountUnassigned(base, EMPL_SCIENTIST), E_CountHired(base, EMPL_SCIENTIST), CAP_GetFreeCapacity(base, CAP_LABSPACE), CAP_GetMax(base, CAP_LABSPACE)); cgi->UI_ExecuteConfunc("ui_techlist_clear"); for (int i = 0; i < ccs.numTechnologies; i++) { technology_t* tech = RS_GetTechByIDX(i); /* Don't show technologies with time == 0 - those are NOT separate research topics. */ if (tech->time == 0) continue; /* hide finished research */ if (tech->statusResearch == RS_FINISH) continue; int percentage = 0; if (tech->overallTime > 0.0) { percentage = std::min(100, std::max(0, 100 - int(round(tech->time * 100.0 / tech->overallTime)))); } /* show researches that are running */ if (tech->base && tech->scientists > 0) { if (tech->base == base) { cgi->UI_ExecuteConfunc("ui_techlist_add %s \"%s\" %d %d", tech->id, _(tech->name), tech->scientists, percentage); } else { cgi->UI_ExecuteConfunc("ui_techlist_add %s \"%s\" %d %d base %d \"%s\"", tech->id, _(tech->name), tech->scientists, percentage, tech->base->idx, tech->base->name); } continue; } /* show topics that are researchable on this base */ const bool req = RS_RequirementsMet(tech, base); if (tech->statusResearchable && req) { cgi->UI_ExecuteConfunc("ui_techlist_add %s \"%s\" %d %d", tech->id, _(tech->name), tech->scientists, percentage); continue; } if (tech->statusCollected && !req) { cgi->UI_ExecuteConfunc("ui_techlist_add %s \"%s\" %d %d missing", tech->id, _(tech->name), tech->scientists, percentage); continue; } } }
/** * @brief Buys items from the market * @param[in] od pointer to the item (Object Definition record) * @param[out] base Base to buy at * @param[in ] count Number of items to buy * @return @c true if the ugv could get bought, @c false otherwise */ bool BS_BuyItem (const objDef_t* od, base_t* base, int count) { if (!od) cgi->Com_Error(ERR_DROP, "BS_BuyItem: Called on nullptr objDef!"); if (!base) cgi->Com_Error(ERR_DROP, "BS_BuyItem: Called on nullptr base!"); if (count <= 0) return false; if (!BS_IsOnMarket(od)) return false; if (ccs.credits < BS_GetItemBuyingPrice(od) * count) return false; if (BS_GetItemOnMarket(od) < count) return false; if (CAP_GetFreeCapacity(base, CAP_ITEMS) < od->size * count) return false; B_AddToStorage(base, od, count); BS_RemoveItemFromMarket(od, count); CP_UpdateCredits(ccs.credits - BS_GetItemBuyingPrice(od) * count); return true; }
/** * @brief Mark a building for destruction - you only have to confirm it now * @param[in] building Pointer to the base to destroy */ static void B_MarkBuildingDestroy (building_t* building) { baseCapacities_t cap; base_t *base = building->base; /* you can't destroy buildings if base is under attack */ if (B_IsUnderAttack(base)) { CP_Popup(_("Notice"), _("Base is under attack, you can't destroy buildings!")); return; } cap = B_GetCapacityFromBuildingType(building->buildingType); /* store the pointer to the building you wanna destroy */ base->buildingCurrent = building; /** @todo: make base destroyable by destroying entrance */ if (building->buildingType == B_ENTRANCE) { CP_Popup(_("Destroy Entrance"), _("You can't destroy the entrance of the base!")); return; } if (!B_IsBuildingDestroyable(building)) { CP_Popup(_("Notice"), _("You can't destroy this building! It is the only connection to other buildings!")); return; } if (building->buildingStatus == B_STATUS_WORKING) { const bool hasMoreBases = B_GetCount() > 1; switch (building->buildingType) { case B_HANGAR: case B_SMALL_HANGAR: if (CAP_GetFreeCapacity(base, cap) <= 0) { cgi->UI_PopupButton(_("Destroy Hangar"), _("If you destroy this hangar, you will also destroy the aircraft inside.\nAre you sure you want to destroy this building?"), "ui_pop;ui_push aircraft;aircraft_select;", _("Go to hangar"), _("Go to hangar without destroying building"), va("building_destroy %i %i confirmed; ui_pop;", base->idx, building->idx), _("Destroy"), _("Destroy the building"), hasMoreBases ? "ui_pop;ui_push transfer;" : NULL, hasMoreBases ? _("Transfer") : NULL, _("Go to transfer menu without destroying the building")); return; } break; case B_QUARTERS: if (CAP_GetFreeCapacity(base, cap) < building->capacity) { cgi->UI_PopupButton(_("Destroy Quarter"), _("If you destroy this Quarters, every employee inside will be killed.\nAre you sure you want to destroy this building?"), "ui_pop;ui_push employees;employee_list 0;", _("Dismiss"), _("Go to hiring menu without destroying building"), va("building_destroy %i %i confirmed; ui_pop;", base->idx, building->idx), _("Destroy"), _("Destroy the building"), hasMoreBases ? "ui_pop;ui_push transfer;" : NULL, hasMoreBases ? _("Transfer") : NULL, _("Go to transfer menu without destroying the building")); return; } break; case B_STORAGE: if (CAP_GetFreeCapacity(base, cap) < building->capacity) { cgi->UI_PopupButton(_("Destroy Storage"), _("If you destroy this Storage, every items inside will be destroyed.\nAre you sure you want to destroy this building?"), "ui_pop;ui_push market;buy_type *mn_itemtype", _("Go to storage"), _("Go to buy/sell menu without destroying building"), va("building_destroy %i %i confirmed; ui_pop;", base->idx, building->idx), _("Destroy"), _("Destroy the building"), hasMoreBases ? "ui_pop;ui_push transfer;" : NULL, hasMoreBases ? _("Transfer") : NULL, _("Go to transfer menu without destroying the building")); return; } break; default: break; } } cgi->UI_PopupButton(_("Destroy building"), _("Are you sure you want to destroy this building?"), NULL, NULL, NULL, va("building_destroy %i %i confirmed; ui_pop;", base->idx, building->idx), _("Destroy"), _("Destroy the building"), NULL, NULL, NULL); }
/** * @brief Buy/Sell item/aircraft/ugv on the market */ static void BS_Buy_f (void) { const char* itemid; int count; base_t* base = B_GetCurrentSelectedBase(); const aircraft_t* aircraft; const ugv_t* ugv; const objDef_t* od; if (cgi->Cmd_Argc() < 2) { cgi->Com_Printf("Usage: %s <item-id> <count> [base-idx] \nNegative count means selling. If base index is omitted buys on the currently selected base.\n", cgi->Cmd_Argv(0)); return; } itemid = cgi->Cmd_Argv(1); count = atoi(cgi->Cmd_Argv(2)); if (cgi->Cmd_Argc() >= 4) base = B_GetFoundedBaseByIDX(atoi(cgi->Cmd_Argv(3))); if (char const* const rest = Q_strstart(itemid, "aircraft_")) { /* aircraft sell - with aircraft golbal idx */ int idx = atoi(rest); aircraft_t* aircraft = AIR_AircraftGetFromIDX(idx); if (!aircraft) { cgi->Com_Printf("Invalid aircraft index!\n"); return; } AIR_RemoveEmployees(*aircraft); BS_SellAircraft(aircraft); return; } if (char const* const rest = Q_strstart(itemid, "ugv-")) { /* ugv sell - with unique character number index */ int ucn = atoi(rest); Employee* robot = E_GetEmployeeByTypeFromChrUCN(EMPL_ROBOT, ucn); if (!robot) { cgi->Com_Printf("Invalid UCN for UGV!\n"); return; } BS_SellUGV(robot); return; } if (!base) { cgi->Com_Printf("No/invalid base selected.\n"); return; } aircraft = AIR_GetAircraftSilent(itemid); if (aircraft) { if (!B_GetBuildingStatus(base, B_COMMAND)) { CP_Popup(_("Note"), _("No Command Centre in this base.\nHangars are not functional.\n")); return; } /* We cannot buy aircraft if there is no power in our base. */ if (!B_GetBuildingStatus(base, B_POWER)) { CP_Popup(_("Note"), _("No power supplies in this base.\nHangars are not functional.")); return; } /* We cannot buy aircraft without any hangar. */ if (!AIR_AircraftAllowed(base)) { CP_Popup(_("Note"), _("Build a hangar first.")); return; } /* Check free space in hangars. */ if (CAP_GetFreeCapacity(base, AIR_GetHangarCapacityType(aircraft)) <= 0) { CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough space in hangars.\n")); return; } if (ccs.credits < BS_GetAircraftBuyingPrice(aircraft)) { CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough credits.\n")); return; } BS_BuyAircraft(aircraft, base); return; } ugv = cgi->Com_GetUGVByIDSilent(itemid); if (ugv) { const objDef_t* ugvWeapon = INVSH_GetItemByID(ugv->weapon); if (!ugvWeapon) cgi->Com_Error(ERR_DROP, "BS_BuyItem_f: Could not get weapon '%s' for ugv/tank '%s'.", ugv->weapon, ugv->id); if (E_CountUnhiredRobotsByType(ugv) < 1) return; if (ccs.eMarket.numItems[ugvWeapon->idx] < 1) return; if (ccs.credits < ugv->price) { CP_Popup(_("Not enough money"), _("You cannot buy this item as you don't have enough credits.")); return; } if (CAP_GetFreeCapacity(base, CAP_ITEMS) < UGV_SIZE + ugvWeapon->size) { CP_Popup(_("Not enough storage space"), _("You cannot buy this item.\nNot enough space in storage.\nBuild more storage facilities.")); return; } BS_BuyUGV(ugv, base); return; } if (count == 0) { cgi->Com_Printf("Invalid number of items to buy/sell: %s\n", cgi->Cmd_Argv(2)); return; } /* item */ od = INVSH_GetItemByID(cgi->Cmd_Argv(1)); if (od) { if (!BS_IsOnMarket(od)) return; if (count > 0) { /* buy */ const int price = BS_GetItemBuyingPrice(od); count = std::min(count, BS_GetItemOnMarket(od)); /* no items available on market */ if (count <= 0) return; if (price <= 0) { cgi->Com_Printf("Item on market with invalid buying price: %s (%d)\n", od->id, BS_GetItemBuyingPrice(od)); return; } /** @todo warn if player can buy less item due to available credits? */ count = std::min(count, ccs.credits / price); /* not enough money for a single item */ if (count <= 0) { CP_Popup(_("Not enough money"), _("You cannot buy this item as you don't have enough credits.")); return; } if (od->size <= 0) { cgi->Com_Printf("Item on market with invalid size: %s (%d)\n", od->id, od->size); return; } count = std::min(count, CAP_GetFreeCapacity(base, CAP_ITEMS) / od->size); if (count <= 0) { CP_Popup(_("Not enough storage space"), _("You cannot buy this item.\nNot enough space in storage.\nBuild more storage facilities.")); return; } BS_BuyItem(od, base, count); } else { /* sell */ count = std::min(-1 * count, B_ItemInBase(od, base)); /* no items in storage */ if (count <= 0) return; BS_SellItem(od, base, count); } return; } cgi->Com_Printf("Invalid item ID\n"); }
const aircraft_t* aircraft = &ccs.aircraftTemplates[i]; if (!BS_AircraftIsOnMarket(aircraft)) continue; if (!RS_IsResearched_ptr(aircraft->tech)) continue; if (BS_GetAircraftOnMarket(aircraft) <= 0) 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; } /* update capacity counters */ cgi->UI_ExecuteConfunc("ui_market_update_caps %d %d %d %d %d %d", CAP_GetFreeCapacity(base, CAP_ITEMS), CAP_GetMax(base, CAP_ITEMS), CAP_GetFreeCapacity(base, CAP_AIRCRAFT_SMALL), CAP_GetMax(base, CAP_AIRCRAFT_SMALL), CAP_GetFreeCapacity(base, CAP_AIRCRAFT_BIG), CAP_GetMax(base, CAP_AIRCRAFT_BIG)); } #ifdef DEBUG static void BS_AddMarket_f (void) { if (cgi->Cmd_Argc() < 3) { cgi->Com_Printf("Usage: %s <itemid> <count>\n", cgi->Cmd_Argv(0)); return; } const objDef_t* obj = INVSH_GetItemByID(cgi->Cmd_Argv(1)); if (!obj) return;