/** * @brief Console command binding for save function * @sa SAV_GameSave * @note called via 'game_save' command */ static void SAV_GameSave_f (void) { char comment[MAX_VAR] = ""; char *error = NULL; bool result; /* get argument */ if (Cmd_Argc() < 2) { Com_Printf("Usage: %s <filename> <comment|*cvar>\n", Cmd_Argv(0)); return; } if (!CP_IsRunning()) { Com_Printf("No running game - no saving...\n"); return; } /* get comment */ if (Cmd_Argc() > 2) { const char *arg = Cmd_Argv(2); Q_strncpyz(comment, arg, sizeof(comment)); } result = SAV_GameSave(Cmd_Argv(1), comment, &error); if (!result) { if (error) CP_Popup(_("Note"), "%s\n%s", _("Error saving game."), error); else CP_Popup(_("Note"), "%s\n%s", "%s\n", _("Error saving game.")); } }
/** * @brief Sells aircraft or craftitem. * @sa BS_BuyAircraft_f */ static void BS_SellAircraft_f (void) { int num; base_t *base = B_GetCurrentSelectedBase(); if (Cmd_Argc() < 2) { Com_Printf("Usage: %s <num>\n", Cmd_Argv(0)); return; } if (!base) return; num = atoi(Cmd_Argv(1)); if (num < 0 || num >= buyList.length) return; if (buyCat == FILTER_AIRCRAFT) { const aircraft_t *aircraftTemplate = buyList.l[num].aircraft; qboolean aircraftOutNote = qfalse; qboolean teamNote = qfalse; aircraft_t *aircraft = NULL; if (!aircraftTemplate) return; AIR_ForeachFromBase(a, base) { if (Q_streq(a->id, aircraftTemplate->id)) { if (AIR_GetTeamSize(a) > 0) { teamNote = qtrue; continue; } if (!AIR_IsAircraftInBase(a)) { /* aircraft is not in base */ aircraftOutNote = qtrue; continue; } aircraft = a; break; } } if (aircraft) { BS_SellAircraft(aircraft); /* reinit the menu */ BS_BuyType(base); } else { if (teamNote) CP_Popup(_("Note"), _("You can't sell an aircraft if it still has a team assigned")); else if (aircraftOutNote) CP_Popup(_("Note"), _("You can't sell an aircraft that is not in base")); else Com_DPrintf(DEBUG_CLIENT, "BS_SellAircraft_f: There are no aircraft available (with no team assigned) for selling\n"); } } }
/** * @brief Starts an aircraft or stops the current mission and let the aircraft idle around. */ static void AIM_AircraftStart_f (void) { aircraft_t *aircraft; base_t *base = B_GetCurrentSelectedBase(); if (!base) return; if (!base->aircraftCurrent) { Com_DPrintf(DEBUG_CLIENT, "Error - there is no current aircraft in this base\n"); return; } /* Aircraft cannot start without Command Centre operational. */ if (!B_GetBuildingStatus(base, B_COMMAND)) { CP_Popup(_("Notice"), _("No operational Command Centre in this base.\n\nAircraft can not start.\n")); return; } aircraft = base->aircraftCurrent; /* Aircraft cannot start without a pilot. */ if (!AIR_GetPilot(aircraft)) { CP_Popup(_("Notice"), _("There is no pilot assigned to this aircraft.\n\nAircraft can not start.\n")); return; } if (AIR_IsAircraftInBase(aircraft)) { /* reload its ammunition */ AII_ReloadAircraftWeapons(aircraft); } MS_AddNewMessage(_("Notice"), _("Aircraft started"), qfalse, MSG_STANDARD, NULL); aircraft->status = AIR_IDLE; MAP_SelectAircraft(aircraft); /* Return to geoscape. */ UI_PopWindow(qfalse); UI_PopWindow(qfalse); }
/** * @brief Constructs a new installation. */ static void INS_BuildInstallation_f (void) { const installationTemplate_t* installationTemplate; if (cgi->Cmd_Argc() < 1) { Com_Printf("Usage: %s <installationType>\n", cgi->Cmd_Argv(0)); return; } /* We shouldn't build more installations than the actual limit */ if (B_GetInstallationLimit() <= INS_GetCount()) return; installationTemplate = INS_GetInstallationTemplateByID(cgi->Cmd_Argv(1)); if (!installationTemplate) { Com_Printf("The installation type %s passed for %s is not valid.\n", cgi->Cmd_Argv(1), cgi->Cmd_Argv(0)); return; } assert(installationTemplate->cost >= 0); if (ccs.credits - installationTemplate->cost > 0) { /* set up the installation */ installation_t* installation = INS_Build(installationTemplate, ccs.newBasePos, cgi->Cvar_GetString("mn_installation_title")); CP_UpdateCredits(ccs.credits - installationTemplate->cost); /* this cvar is used for disabling the installation build button on geoscape if MAX_INSTALLATIONS was reached */ cgi->Cvar_SetValue("mn_installation_count", INS_GetCount()); const nation_t* nation = GEO_GetNation(installation->pos); if (nation) Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new installation has been built: %s (nation: %s)"), installation->name, _(nation->name)); else Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("A new installation has been built: %s"), installation->name); MSO_CheckAddNewMessage(NT_INSTALLATION_BUILDSTART, _("Installation building"), cp_messageBuffer, MSG_CONSTRUCTION); } else { if (installationTemplate->type == INSTALLATION_RADAR) { if (GEO_IsRadarOverlayActivated()) GEO_SetOverlay("radar"); } if (ccs.mapAction == MA_NEWINSTALLATION) ccs.mapAction = MA_NONE; CP_Popup(_("Notice"), _("Not enough credits to set up a new installation.")); } ccs.mapAction = MA_NONE; }
/** * @brief Loads the quick save slot * @sa SAV_GameQuickSave_f */ static void SAV_GameQuickLoad_f (void) { const char *error = NULL; if (cgi->CL_OnBattlescape()) { Com_Printf("Could not load the campaign while you are on the battlefield\n"); return; } if (!SAV_GameLoad("slotquick", &error)) { Cbuf_Execute(); /* wipe outstanding campaign commands */ CP_Popup(_("Error"), "%s\n%s", _("Error loading game."), error ? error : ""); } else { MS_AddNewMessage(_("Campaign loaded"), _("Quicksave campaign was successfully loaded."), MSG_INFO); CP_CheckBaseAttacks(); } }
/** * @brief Checks why a button in base menu is disabled, and create a popup to inform player */ static void B_CheckBuildingStatusForMenu_f (void) { int num; const char *buildingID; const building_t *building; const base_t *base = B_GetCurrentSelectedBase(); if (cgi->Cmd_Argc() != 2) { Com_Printf("Usage: %s <buildingID>\n", cgi->Cmd_Argv(0)); return; } buildingID = cgi->Cmd_Argv(1); building = B_GetBuildingTemplate(buildingID); if (!building || !base) return; /* Maybe base is under attack ? */ if (B_IsUnderAttack(base)) { CP_Popup(_("Notice"), _("Base is under attack, you can't access this building !")); return; } if (building->buildingType == B_HANGAR) { /* this is an exception because you must have a small or large hangar to enter aircraft menu */ CP_Popup(_("Notice"), _("You need at least one Hangar (and its dependencies) to use aircraft.")); return; } num = B_GetNumberOfBuildingsInBaseByBuildingType(base, building->buildingType); if (num > 0) { int numUnderConstruction; /* maybe all buildings of this type are under construction ? */ B_CheckBuildingTypeStatus(base, building->buildingType, B_STATUS_UNDER_CONSTRUCTION, &numUnderConstruction); if (numUnderConstruction == num) { int minDay = 99999; building_t *b = NULL; while ((b = B_GetNextBuildingByType(base, b, building->buildingType))) { if (b->buildingStatus == B_STATUS_UNDER_CONSTRUCTION) { const float remaining = B_GetConstructionTimeRemain(b); minDay = std::min(minDay, (int)std::max(0.0f, remaining)); } } CP_Popup(_("Notice"), ngettext("Construction of building will be over in %i day.\nPlease wait to enter.", "Construction of building will be over in %i days.\nPlease wait to enter.", minDay), minDay); return; } if (!B_CheckBuildingDependencesStatus(building)) { const building_t *dependenceBuilding = building->dependsBuilding; assert(building->dependsBuilding); if (B_GetNumberOfBuildingsInBaseByBuildingType(base, dependenceBuilding->buildingType) <= 0) { /* the dependence of the building is not built */ CP_Popup(_("Notice"), _("You need a building %s to make building %s functional."), _(dependenceBuilding->name), _(building->name)); return; } else { /* maybe the dependence of the building is under construction * note that we can't use B_STATUS_UNDER_CONSTRUCTION here, because this value * is not use for every building (for exemple Command Centre) */ building_t *b = NULL; while ((b = B_GetNextBuildingByType(base, b, dependenceBuilding->buildingType))) { if (!B_IsBuildingBuiltUp(b)) { CP_Popup(_("Notice"), _("Building %s is not finished yet, and is needed to use building %s."), _(dependenceBuilding->name), _(building->name)); return; } } /* the dependence is built but doesn't work - must be because of their dependendes */ CP_Popup(_("Notice"), _("Make sure that the dependencies of building %s (%s) are operational, so that building %s may be used."), _(dependenceBuilding->name), _(dependenceBuilding->dependsBuilding->name), _(building->name)); return; } } /* all buildings are OK: employees must be missing */ if (building->buildingType == B_WORKSHOP && E_CountHired(base, EMPL_WORKER) <= 0) { CP_Popup(_("Notice"), _("You need to recruit %s to use building %s."), E_GetEmployeeString(EMPL_WORKER, 2), _(building->name)); return; } else if (building->buildingType == B_LAB && E_CountHired(base, EMPL_SCIENTIST) <= 0) { CP_Popup(_("Notice"), _("You need to recruit %s to use building %s."), E_GetEmployeeString(EMPL_SCIENTIST, 2), _(building->name)); return; } } else { CP_Popup(_("Notice"), _("Build a %s first."), _(building->name)); return; } }
/** * @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"); }
/** * @brief Buys aircraft or craftitem. * @sa BS_SellAircraft_f */ static void BS_BuyAircraft_f (void) { int num; const aircraft_t *aircraftTemplate; base_t *base = B_GetCurrentSelectedBase(); if (Cmd_Argc() < 2) { Com_Printf("Usage: %s <num>\n", Cmd_Argv(0)); return; } if (!base) return; num = atoi(Cmd_Argv(1)); if (num < 0 || num >= buyList.length) return; if (buyCat == FILTER_AIRCRAFT) { int freeSpace; 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; } aircraftTemplate = buyList.l[num].aircraft; freeSpace = AIR_CalculateHangarStorage(aircraftTemplate, base, 0); /* Check free space in hangars. */ if (freeSpace < 0) { Com_Printf("BS_BuyAircraft_f: something bad happened, AIR_CalculateHangarStorage returned -1!\n"); return; } if (freeSpace == 0) { CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough space in hangars.\n")); return; } else { const int price = BS_GetAircraftBuyingPrice(aircraftTemplate); if (ccs.credits < price) { CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough credits.\n")); return; } else { /* Hangar capacities are being updated in AIR_NewAircraft().*/ BS_RemoveAircraftFromMarket(aircraftTemplate, 1); CP_UpdateCredits(ccs.credits - price); AIR_NewAircraft(base, aircraftTemplate); Cmd_ExecuteString(va("buy_type %s", INV_GetFilterType(FILTER_AIRCRAFT))); } } } }
/** * @brief Adds a new message to message stack * @note These are the messages that are displayed at geoscape * @param[in] title Already translated message/mail title * @param[in] text Already translated message/mail body * @param[in] popup Show this as a popup, too? * @param[in] type The message type * @param[in] pedia Pointer to technology (only if needed) * @param[in] playSound Whether the sound associated with the message type should be played * @return message_t pointer * @sa UP_OpenMail_f * @sa CL_EventAddMail_f * @note this method forwards to @c MS_AddNewMessageSound with @code playSound = true @endcode */ uiMessageListNodeMessage_t* MS_AddNewMessage (const char* title, const char* text, messageType_t type, technology_t* pedia, bool popup, bool playSound) { assert(type < MSG_MAX); /* allocate memory for new message - delete this with every new game */ uiMessageListNodeMessage_t* const mess = Mem_PoolAllocType(uiMessageListNodeMessage_t, cp_campaignPool); switch (type) { case MSG_DEBUG: /**< only save them in debug mode */ mess->iconName = "icons/message_debug"; break; case MSG_INFO: /**< don't save these messages */ mess->iconName = "icons/message_info"; break; case MSG_STANDARD: mess->iconName = "icons/message_info"; break; case MSG_RESEARCH_PROPOSAL: mess->iconName = "icons/message_research"; break; case MSG_RESEARCH_HALTED: mess->iconName = "icons/message_research"; break; case MSG_RESEARCH_FINISHED: mess->iconName = "icons/message_research"; break; case MSG_CONSTRUCTION: mess->iconName = "icons/message_construction"; break; case MSG_UFOSPOTTED: mess->iconName = "icons/message_ufo"; break; case MSG_TERRORSITE: mess->iconName = "icons/message_ufo"; break; case MSG_BASEATTACK: mess->iconName = "icons/message_ufo"; break; case MSG_TRANSFERFINISHED: mess->iconName = "icons/message_transfer"; break; case MSG_PROMOTION: mess->iconName = "icons/message_promotion"; break; case MSG_PRODUCTION: mess->iconName = "icons/message_production"; break; case MSG_DEATH: mess->iconName = "icons/message_death"; break; case MSG_CRASHSITE: mess->iconName = "icons/message_ufo"; break; case MSG_EVENT: mess->iconName = "icons/message_info"; break; default: mess->iconName = "icons/message_info"; break; } /* push the new message at the beginning of the stack */ cgi->UI_MessageAddStack(mess); mess->type = type; mess->pedia = pedia; /* pointer to UFOpaedia entry */ mess->date = ccs.date; Q_strncpyz(mess->title, title, sizeof(mess->title)); mess->text = cgi->PoolStrDup(text, cp_campaignPool, 0); /* get formatted date text */ MS_TimestampedText(mess->timestamp, mess, sizeof(mess->timestamp)); /* they need to be translated already */ if (popup) { CP_GameTimeStop(); CP_Popup(mess->title, "%s", mess->text); } if (playSound) { const char* sound = nullptr; switch (type) { case MSG_DEBUG: break; case MSG_STANDARD: sound = "geoscape/standard"; break; case MSG_INFO: case MSG_TRANSFERFINISHED: case MSG_DEATH: case MSG_CONSTRUCTION: case MSG_PRODUCTION: sound = "geoscape/info"; break; case MSG_RESEARCH_PROPOSAL: case MSG_RESEARCH_FINISHED: assert(pedia); case MSG_RESEARCH_HALTED: case MSG_EVENT: case MSG_NEWS: /* reread the new mails in UP_GetUnreadMails */ ccs.numUnreadMails = -1; sound = "geoscape/mail"; break; case MSG_UFOLOST: sound = "geoscape/ufolost"; break; case MSG_UFOSPOTTED: sound = "geoscape/ufospotted"; break; case MSG_BASEATTACK: sound = "geoscape/basealert"; break; case MSG_TERRORSITE: sound = "geoscape/alien-activity"; break; case MSG_CRASHSITE: sound = "geoscape/newmission"; break; case MSG_PROMOTION: sound = "geoscape/promotion"; break; case MSG_MAX: break; } cgi->S_StartLocalSample(sound, 1.0f); } return mess; }