/** * @brief Load callback for savin in XML Format * @param[in] parent Parent XML node in the savegame * @todo Remove: Fallback for compatibility */ bool AC_LoadXML (xmlNode_t* parent) { xmlNode_t* aliencont = cgi->XML_GetNode(parent, SAVE_ALIENCONT_ALIENCONT); if (!aliencont) return true; FOREACH_XMLNODE(contNode, aliencont, SAVE_ALIENCONT_CONT) { const int baseIdx = cgi->XML_GetInt(contNode, SAVE_ALIENCONT_BASEIDX, MAX_BASES); base_t* base = B_GetFoundedBaseByIDX(baseIdx); if (!base) { Com_Printf("AC_LoadXML: Invalid base idx '%i'\n", baseIdx); continue; } FOREACH_XMLNODE(alienNode, contNode, SAVE_ALIENCONT_ALIEN) { const char* teamId = cgi->XML_GetString(alienNode, SAVE_ALIENCONT_TEAMID); const int alive = cgi->XML_GetInt(alienNode, SAVE_ALIENCONT_AMOUNTALIVE, 0); const int dead = cgi->XML_GetInt(alienNode, SAVE_ALIENCONT_AMOUNTDEAD, 0); if (alive == 0 && dead == 0) continue; if (!base->alienContainment) base->alienContainment = new AlienContainment(CAP_Get(base, CAP_ALIENS), nullptr); base->alienContainment->add(teamId, alive, dead); } } return true; }
/** * @brief Alien containment menu init function. * @note Command to call this: ui_aliencont_init * @note Should be called whenever the alien containment menu gets active. */ static void AC_Init_f (void) { base_t* base; if (cgi->Cmd_Argc() < 2) base = B_GetCurrentSelectedBase(); else base = B_GetFoundedBaseByIDX(atoi(cgi->Cmd_Argv(1))); if (!base) { Com_Printf("No base selected\n"); return; } cgi->UI_ExecuteConfunc("ui_aliencont_cap %d %d", CAP_GetCurrent(base, CAP_ALIENS), CAP_GetMax(base, CAP_ALIENS)); cgi->UI_ExecuteConfunc("ui_aliencont_clear"); if (!base->alienContainment) return; linkedList_t* list = base->alienContainment->list(); LIST_Foreach(list, alienCargo_t, item) { const technology_t* tech = RS_GetTechForTeam(item->teamDef); cgi->UI_ExecuteConfunc("ui_aliencont_add \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" %f %d %d", item->teamDef->id, _(item->teamDef->name), tech->id, tech->image, (RS_IsResearched_ptr(tech)) ? _("Researched") : _("Awaiting autopsy"), (1.0f - tech->time / tech->overallTime) * 100, item->alive, item->dead); } cgi->LIST_Delete(&list); }
/** * @brief onDestroy Callback for Antimatter Storage */ static void B_Destroy_AntimaterStorage_f (void) { base_t *base; const float prob = frand(); if (cgi->Cmd_Argc() < 4) { /** note: third parameter not used but we must be sure we have probability parameter */ Com_Printf("Usage: %s <probability> <baseID> <buildingType>\n", cgi->Cmd_Argv(0)); return; } base = B_GetFoundedBaseByIDX(atoi(cgi->Cmd_Argv(2))); if (!base) return; if (CAP_GetCurrent(base, CAP_ANTIMATTER) <= 0) return; CAP_RemoveAntimatterExceedingCapacity(base); if (base->baseStatus != BASE_WORKING) return; if (prob < atof(cgi->Cmd_Argv(1))) { MS_AddNewMessage(_("Notice"), va(_("%s has been destroyed by an antimatter storage breach."), base->name)); cgi->UI_PopWindow(false); B_Destroy(base); } }
/** * @brief Console command to kill all aliens on a base. * @note if the first argrument is a base index that, otherwise the current base will remove it's aliens * @sa AC_KillAll */ static void AC_KillAll_f (void) { base_t *base; if (Cmd_Argc() < 2) { base = B_GetCurrentSelectedBase(); } else { base = B_GetFoundedBaseByIDX(atoi(Cmd_Argv(1))); } /* Can be called from everywhere. */ if (!base) return; AC_KillAll(base); /* Reinit menu to display proper values. */ AC_UpdateMenu(base); }
/** * @brief User select a base in the popup_homebase * change homebase to selected base. */ static void CL_PopupChangeHomebase_f (void) { linkedList_t* data = popupListData; /**< Use this so we do not change the original popupListData pointer. */ int selectedPopupIndex; int i; base_t *base; int baseIdx; aircraft_t *aircraft = MAP_GetSelectedAircraft(); /* If popup is opened, that means an aircraft is selected */ if (!aircraft) { Com_Printf("CL_PopupChangeHomebase_f: An aircraft must be selected\n"); return; } if (Cmd_Argc() < 2) { Com_Printf("Usage: %s <popupIndex>\tpopupIndex=num in base list\n", Cmd_Argv(0)); return; } /* read and range check */ selectedPopupIndex = atoi(Cmd_Argv(1)); Com_DPrintf(DEBUG_CLIENT, "CL_PopupHomebaseClick_f (popupNum %i, selectedPopupIndex %i)\n", popupNum, selectedPopupIndex); if (selectedPopupIndex < 0 || selectedPopupIndex >= popupNum) return; /* Convert list index to base idx */ baseIdx = INVALID_BASE; for (i = 0; data; data = data->next, i++) { if (i == selectedPopupIndex) { baseIdx = *(int*)data->data; break; } } base = B_GetFoundedBaseByIDX(baseIdx); if (base == NULL) return; AIR_MoveAircraftIntoNewHomebase(aircraft, base); UI_PopWindow(qfalse); CL_DisplayHomebasePopup(aircraft, qtrue); }
/** * @brief Console command to kill all aliens on a base. * @note if the first argrument is a base index that, otherwise the current base will remove it's aliens * @sa AC_KillAll */ static void AC_KillAll_f (void) { base_t* base; if (cgi->Cmd_Argc() < 2) { base = B_GetCurrentSelectedBase(); } else { base = B_GetFoundedBaseByIDX(atoi(cgi->Cmd_Argv(1))); } if (!base) return; if (!base->alienContainment) return; linkedList_t* list = base->alienContainment->list(); LIST_Foreach(list, alienCargo_t, item) { base->alienContainment->add(item->teamDef, -item->alive, item->alive); }
/** * @brief Open menu for basesummary. */ static void BaseSummary_SelectBase_f (void) { const base_t *base; if (cgi->Cmd_Argc() >= 2) { const int i = atoi(cgi->Cmd_Argv(1)); base = B_GetFoundedBaseByIDX(i); if (base == NULL) { Com_Printf("Invalid base index given (%i).\n", i); return; } } else { base = B_GetCurrentSelectedBase(); } if (base != NULL) { BaseSummary_Init(base); cgi->UI_ExecuteConfunc("basesummary_change_color %i", base->idx); } }
/** * @brief Load callback for savin in XML Format * @sa AC_LoadXML * @sa B_SaveXML * @sa SAV_GameLoadXML */ qboolean AC_LoadXML (mxml_node_t * parent) { mxml_node_t *aliencont; mxml_node_t *contNode; int i; aliencont = mxml_GetNode(parent, SAVE_ALIENCONT_ALIENCONT); ccs.breathingMailSent = mxml_GetBool(aliencont, SAVE_ALIENCONT_BREATHINGMAILSENT, qfalse); /* Init alienContainers */ for (i = 0; i < MAX_BASES; i++) { base_t *base = B_GetBaseByIDX(i); AL_FillInContainment(base); } /* Load data */ for (contNode = mxml_GetNode(aliencont, SAVE_ALIENCONT_CONT); contNode; contNode = mxml_GetNextNode(contNode, aliencont, SAVE_ALIENCONT_CONT)) { int j = mxml_GetInt(contNode, SAVE_ALIENCONT_BASEIDX, MAX_BASES); base_t *base = B_GetFoundedBaseByIDX(j); int k; mxml_node_t *alienNode; if (!base) { Com_Printf("AC_LoadXML: Invalid base idx '%i'\n", j); continue; } for (k = 0, alienNode = mxml_GetNode(contNode, SAVE_ALIENCONT_ALIEN); alienNode && k < MAX_ALIENCONT_CAP; alienNode = mxml_GetNextNode(alienNode, contNode, SAVE_ALIENCONT_ALIEN), k++) { const char *const s = mxml_GetString(alienNode, SAVE_ALIENCONT_TEAMID); /* Fill Alien Containment with default values like the tech pointer. */ base->alienscont[k].teamDef = Com_GetTeamDefinitionByID(s); if (base->alienscont[k].teamDef) { base->alienscont[k].amountAlive = mxml_GetInt(alienNode, SAVE_ALIENCONT_AMOUNTALIVE, 0); base->alienscont[k].amountDead = mxml_GetInt(alienNode, SAVE_ALIENCONT_AMOUNTDEAD, 0); } } } return qtrue; }
/** * @brief Called when a base is opened or a new base is created on geoscape. * For a new base the baseID is -1. */ static void B_SelectBase_f (void) { int baseID; if (cgi->Cmd_Argc() < 2) { Com_Printf("Usage: %s <baseID>\n", cgi->Cmd_Argv(0)); return; } baseID = atoi(cgi->Cmd_Argv(1)); /* check against MAX_BASES here! - only -1 will create a new base * if we would check against ccs.numBases here, a click on the base summary * base nodes would try to select unfounded bases */ if (baseID >= 0 && baseID < MAX_BASES) { const base_t *base = B_GetFoundedBaseByIDX(baseID); /* don't create a new base if the index was valid */ if (base) B_SelectBase(base); } else if (baseID == CREATE_NEW_BASE_ID) { /* create a new base */ B_SelectBase(NULL); } }
/** * @brief Remove a defence system from base. * @note 1st argument is the basedefence system type to destroy (sa basedefenceType_t). * @note 2nd argument is the idx of the base in which you want the battery to be destroyed. * @note if the first argument is BASEDEF_RANDOM, the type of the battery to destroy is randomly selected * @note the building must already be removed from ccs.buildings[baseIdx][] */ static void BDEF_RemoveBattery_f (void) { basedefenceType_t basedefType; int baseIdx; base_t* base; if (cgi->Cmd_Argc() < 3) { Com_Printf("Usage: %s <basedefType> <baseIdx>", cgi->Cmd_Argv(0)); return; } else { char type[MAX_VAR]; Q_strncpyz(type, cgi->Cmd_Argv(1), sizeof(type)); if (Q_streq(type, "missile")) basedefType = BASEDEF_MISSILE; else if (Q_streq(type, "laser")) basedefType = BASEDEF_LASER; else if (Q_streq(type, "random")) basedefType = BASEDEF_RANDOM; else return; baseIdx = atoi(cgi->Cmd_Argv(2)); } /* Check that the baseIdx exists */ if (baseIdx < 0 || baseIdx >= B_GetCount()) { Com_Printf("BDEF_RemoveBattery_f: baseIdx %i doesn't exist: there is only %i bases in game.\n", baseIdx, B_GetCount()); return; } base = B_GetFoundedBaseByIDX(baseIdx); if (!base) { Com_Printf("BDEF_RemoveBattery_f: baseIdx %i is not founded.\n", baseIdx); return; } if (basedefType == BASEDEF_RANDOM) { /* Type of base defence to destroy is randomly selected */ if (base->numBatteries <= 0 && base->numLasers <= 0) { Com_Printf("No base defence to destroy\n"); return; } else if (base->numBatteries <= 0) { /* only laser battery is possible */ basedefType = BASEDEF_LASER; } else if (base->numLasers <= 0) { /* only missile battery is possible */ basedefType = BASEDEF_MISSILE; } else { /* both type are possible, choose one randomly */ basedefType = (basedefenceType_t)(rand() % 2 + BASEDEF_MISSILE); } } else { /* Check if the removed building was under construction */ buildingType_t type; int workingNum, max; building_t* building; switch (basedefType) { case BASEDEF_MISSILE: type = B_DEFENCE_MISSILE; max = base->numBatteries; break; case BASEDEF_LASER: type = B_DEFENCE_LASER; max = base->numLasers; break; default: Com_Printf("BDEF_RemoveBattery_f: base defence type %i doesn't exist.\n", basedefType); return; } building = nullptr; workingNum = 0; while ((building = B_GetNextBuildingByType(base, building, type))) if (building->buildingStatus == B_STATUS_WORKING) workingNum++; if (workingNum == max) { /* Removed building was under construction, do nothing */ return; } else if (workingNum != max - 1) { /* Should never happen, we only remove building one by one */ Com_Printf("BDEF_RemoveBattery_f: Error while checking number of batteries (%i instead of %i) in base '%s'.\n", workingNum, max, base->name); return; } /* If we reached this point, that means we are removing a working building: continue */ } BDEF_RemoveBattery(base, basedefType, -1); }
/** * @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) { cgi->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) { cgi->Com_Printf("No/invalid base selected.\n"); return; } type = cgi->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->getUGV(); const technology_t* tech = RS_GetTechByProvided(ugv->id); if (!robot->isHiredInBase(base)) continue; cgi->UI_ExecuteConfunc("ui_market_add \"ugv-%d\" \"%s\" 1 0 0 %d - \"%s\"", robot->chr.ucn, _(tech->name), ugv->price, robot->isAwayFromBase() ? _("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, fall through */ case FILTER_S_PRIMARY: case FILTER_S_SECONDARY: case FILTER_S_HEAVY: case FILTER_S_IMPLANT: 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 && !cgi->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; 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; }
/** * @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"); }