/** * @file cl_basemanagement.c * @brief Handles everything that is located in or accessed trough a base. * * See "base/ufos/basemanagement.ufo", "base/ufos/menu_bases.ufo" and "base/ufos/menu_buildings.ufo" for the underlying content. * @todo New game does not reset basemangagement */ /* Copyright (C) 2002-2007 UFO: Alien Invasion team. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "client.h" #include "cl_global.h" vec3_t newBasePos; static cvar_t *mn_base_title; static cvar_t *mn_base_count; static int BuildingConstructionList[MAX_BUILDINGS]; static int numBuildingConstructionList; static void B_BuildingInit(void); /** * @brief Count all employees (hired) in the given base */ extern int B_GetEmployeeCount (const base_t* const base) { int cnt = 0; employeeType_t type; for (type = EMPL_SOLDIER; type < MAX_EMPL; type++) cnt += E_CountHired(base, type); Com_DPrintf("B_GetEmployeeCount: %i\n", cnt); return cnt; } /** * @brief Searches the base for a given building type with the given status * @param[in] base Base to search * @param[in] type Building type to search * @param[in] status The status the building should have * @param[out] cnt This is a pointer to an int value which will hold the building * count of that type with the status you are searching - might also be NULL * if you are not interested in this value * @note If you are searching for a quarter (e.g.) you should perform a * if (base->hasQuarters) check - this is not available for all * building types but should speed things up a lot * @return true if building with status was found */ extern qboolean B_CheckBuildingTypeStatus (const base_t* const base, buildingType_t type, buildingStatus_t status, int *cnt) { int cntlocal = 0, i; for (i = 0; i < gd.numBuildings[base->idx]; i++) { if (gd.buildings[base->idx][i].buildingType == type && gd.buildings[base->idx][i].buildingStatus == status) { cntlocal++; /* don't count any further - the caller doesn't want to know the value */ if (!cnt) return qtrue; } } /* set the cnt pointer if the caller wants to know this value */ if (cnt) *cnt = cntlocal; return cntlocal ? qtrue : qfalse; } /** * @brief Sums up max_employees quarter values * @param[in] base The base to count the free space in. * @return int The total number of space in quarters. */ extern int B_GetAvailableQuarterSpace (const base_t* const base) { int cnt = 0, i; if (base->hasQuarters) for (i = 0; i < gd.numBuildings[base->idx]; i++) { if (gd.buildings[base->idx][i].buildingType == B_QUARTERS && gd.buildings[base->idx][i].buildingStatus != B_STATUS_NOT_SET) cnt += gd.buildings[base->idx][i].maxEmployees; } Com_DPrintf("B_GetAvailableQuarterSpace: %i\n", cnt); return cnt; } /** * @brief Sums up max_employees laboratories values * @param[in] base The base to count the free space in. * @return int The total number of in the labs. */ extern int B_GetAvailableLabSpace (const base_t* const base) { int cnt = 0, i; if (base->hasLab) for (i = 0; i < gd.numBuildings[base->idx]; i++) { if (gd.buildings[base->idx][i].buildingType == B_LAB && gd.buildings[base->idx][i].buildingStatus != B_STATUS_NOT_SET) cnt += gd.buildings[base->idx][i].maxEmployees; } Com_DPrintf("B_GetAvailableLabSpace: %i\n", cnt); return cnt; } /** * @brief */ static void B_ResetBuildingCurrent (void) { if (baseCurrent) { baseCurrent->buildingCurrent = NULL; baseCurrent->buildingToBuild = -1; } } /** * @brief Resets the currently selected building. * * Is called e.g. when leaving the build-menu but also several times from cl_basemanagement.c. */ static void B_ResetBuildingCurrent_f (void) { if (Cmd_Argc() == 2) gd.instant_build = atoi(Cmd_Argv(1)); B_ResetBuildingCurrent(); } /** * @brief Holds the names of valid entries in the basemanagement.ufo file. * * The valid definition names for BUILDINGS (building_t) in the basemagagement.ufo file. * to the appropriate values in the corresponding struct */ static const value_t valid_vars[] = { {"map_name", V_STRING, offsetof(building_t, mapPart)}, /**< Name of the map file for generating basemap. */ {"more_than_one", V_BOOL, offsetof(building_t, moreThanOne)}, /**< Is the building allowed to be build more the one time? */ {"name", V_TRANSLATION_STRING, offsetof(building_t, name)}, /**< The displayed building name. */ {"pedia", V_STRING, offsetof(building_t, pedia)}, /**< The pedia-id string for the associated pedia entry. */ {"status", V_INT, offsetof(building_t, buildingStatus)}, /**< The current status of the building. */ {"image", V_STRING, offsetof(building_t, image)}, /**< Identifies the image for the building. */ {"visible", V_BOOL, offsetof(building_t, visible)}, /**< Determines whether a building should be listed in the construction list. Set the first part of a building to 1 all others to 0 otherwise all building-parts will be on the list */ {"needs", V_STRING, offsetof(building_t, needs)}, /**< For buildings with more than one part; the other parts of the building needed.*/ {"fixcosts", V_FLOAT, offsetof(building_t, fixCosts)}, /**< Cost to build. */ {"varcosts", V_FLOAT, offsetof(building_t, varCosts)}, /**< Costs that will come up by using the building. */ {"build_time", V_INT, offsetof(building_t, buildTime)}, /**< How many days it takes to construct the building. */ {"max_employees", V_INT, offsetof(building_t, maxEmployees)}, /**< How many employees to hire on construction in the first base. */ {"capacity", V_INT, offsetof(building_t, capacity)}, /**< A size value that is used by many buldings in a different way. */ /*event handler functions */ {"onconstruct", V_STRING, offsetof(building_t, onConstruct)}, /**< Event handler. */ {"onattack", V_STRING, offsetof(building_t, onAttack)}, /**< Event handler. */ {"ondestroy", V_STRING, offsetof(building_t, onDestroy)}, /**< Event handler. */ {"onupgrade", V_STRING, offsetof(building_t, onUpgrade)}, /**< Event handler. */ {"onrepair", V_STRING, offsetof(building_t, onRepair)}, /**< Event handler. */ {"onclick", V_STRING, offsetof(building_t, onClick)}, /**< Event handler. */ {"pos", V_POS, offsetof(building_t, pos)}, /**< Place of a building. Needed for flag autobuild */ {"autobuild", V_BOOL, offsetof(building_t, autobuild)}, /**< Automatically construct this building when a base is set up. Must also set the pos-flag. */ {"firstbase", V_BOOL, offsetof(building_t, firstbase)}, /**< Automatically construct this building for the first base you build. Must also set the pos-flag. */ {NULL, 0, 0} }; /** * @brief Sets a sensor. * * inc_sensor and dec_sensor are script commands that increase the amount * of the radar width for a given base */ extern void B_SetSensor_f (void) { int i = 0; int amount = 0; base_t* base; if (Cmd_Argc() < 3) { Com_Printf("Usage: %s \n", Cmd_Argv(0)); return; } i = atoi(Cmd_Argv(2)); if (i >= gd.numBases) { Com_Printf("invalid baseID (%s)\n", Cmd_Argv(2)); return; } amount = atoi(Cmd_Argv(1)); base = gd.bases + i; if (!Q_strncmp(Cmd_Argv(0), "inc", 3)) RADAR_ChangeRange(&(base->radar), amount); /* inc_sensor */ else if (!Q_strncmp(Cmd_Argv(0), "dec", 3)) RADAR_ChangeRange(&(base->radar), -amount); /* dec_sensor */ } /** * @brief We are doing the real destroy of a buliding here * @sa B_BuildingDestroy * @sa B_NewBuilding */ static void B_BuildingDestroy_f (void) { building_t *b1 = NULL; #if 0 building_t *b2 = NULL; #endif if (!baseCurrent || !baseCurrent->buildingCurrent) return; b1 = baseCurrent->buildingCurrent; if (baseCurrent->map[(int)b1->pos[0]][(int)b1->pos[1]] >= 0) { if (*b1->needs) { #if 0 b2 = B_GetBuildingType(b1->needs); assert(b2); #endif /* "Child" building is always right to the "parent" building". */ baseCurrent->map[(int)b1->pos[0]][((int)b1->pos[1])+1] = -1; } baseCurrent->map[(int)b1->pos[0]][(int)b1->pos[1]] = -1; } b1->buildingStatus = B_STATUS_NOT_SET; #if 0 if (b2) b2->buildingStatus = B_STATUS_NOT_SET; #endif #if 0 /* this would be a clean approach - but we have to fix all the linkage */ gd.numBuildings[baseCurrent->idx]--; if (b1->idx == gd.numBuildings[baseCurrent->idx]) { memset(b1, 0, sizeof(building_t)); } else { for (i = b1->idx; i < gd.numBuildings[baseCurrent->idx]; i++) { Com_Printf("Move building %i to pos %i (%i)\n", i+1, i, gd.numBuildings[baseCurrent->idx]); memmove(&gd.buildings[baseCurrent->idx][i], &gd.buildings[baseCurrent->idx][i+1], sizeof(building_t)); gd.buildings[baseCurrent->idx][i].idx = i; } } #endif switch (b1->buildingType) { case B_WORKSHOP: if (!B_GetNumberOfBuildingsInBaseByType(baseCurrent->idx, b1->buildingType)) baseCurrent->hasWorkshop = qfalse; break; case B_STORAGE: if (!B_GetNumberOfBuildingsInBaseByType(baseCurrent->idx, b1->buildingType)) baseCurrent->hasStorage = qfalse; break; case B_ALIEN_CONTAINMENT: if (!B_GetNumberOfBuildingsInBaseByType(baseCurrent->idx, b1->buildingType)) baseCurrent->hasAlienCont = qfalse; break; case B_LAB: if (!B_GetNumberOfBuildingsInBaseByType(baseCurrent->idx, b1->buildingType)) baseCurrent->hasLab = qfalse; break; case B_HOSPITAL: if (!B_GetNumberOfBuildingsInBaseByType(baseCurrent->idx, b1->buildingType)) baseCurrent->hasHospital = qfalse; break; case B_HANGAR: /* the Dropship Hangar */ if (!B_GetNumberOfBuildingsInBaseByType(baseCurrent->idx, b1->buildingType)) baseCurrent->hasHangar = qfalse; break; case B_QUARTERS: if (!B_GetNumberOfBuildingsInBaseByType(baseCurrent->idx, b1->buildingType)) baseCurrent->hasQuarters = qfalse; break; case B_MISC: break; default: Com_Printf("B_BuildingDestroy_f: Unknown bulding type: %i.\n", b1->buildingType); break; } /* call ondestroy trigger */ if (*b1->onDestroy) { Com_DPrintf("B_BuildingDestroy: %s %i;\n", baseCurrent->buildingCurrent->onDestroy, baseCurrent->idx); Cbuf_AddText(va("%s %i;", baseCurrent->buildingCurrent->onDestroy, baseCurrent->idx)); } B_ResetBuildingCurrent(); B_BuildingStatus(); } /** * @brief Mark a building for destruction - you only have to confirm it now * @note Also calls the ondestroy trigger */ extern void B_BuildingDestroy (building_t* building, base_t* base) { assert(base); assert(building); base->buildingCurrent = building; MN_PushMenu("building_destroy"); } /** * @brief Displays the status of a building for baseview. * * updates the cvar mn_building_status which is used in some menus to display * the building status * @note also script command function binding for 'building_status' */ extern void B_BuildingStatus (void) { int daysLeft; int NumberOfBuildings = 0; /*maybe someone call this command before the buildings are parsed?? */ if (!baseCurrent || !baseCurrent->buildingCurrent) { Com_DPrintf("B_BuildingStatus: No Base or no Building set\n"); return; } daysLeft = baseCurrent->buildingCurrent->timeStart + baseCurrent->buildingCurrent->buildTime - ccs.date.day; Cvar_Set("mn_building_status", _("Not set")); switch (baseCurrent->buildingCurrent->buildingStatus) { case B_STATUS_NOT_SET: NumberOfBuildings = B_GetNumberOfBuildingsInBaseByTypeIDX(baseCurrent->idx, baseCurrent->buildingCurrent->type_idx); if (NumberOfBuildings) Cvar_Set("mn_building_status", va(_("Already %i in base"), NumberOfBuildings)); break; case B_STATUS_UNDER_CONSTRUCTION: Cvar_Set("mn_building_status", ""); break; case B_STATUS_CONSTRUCTION_FINISHED: Cvar_Set("mn_building_status", _("Construction finished")); break; case B_STATUS_WORKING: Cvar_Set("mn_building_status", _("Working 100%")); break; case B_STATUS_DOWN: Cvar_Set("mn_building_status", _("Down")); break; default: break; } } /** * @brief Hires some employees of appropriate type for a building * @param building in which building * @param num how many employees, if -1, hire building->maxEmployees * * @sa B_SetUpBase */ static void B_HireForBuilding (base_t* base, building_t * building, int num) { employeeType_t employeeType; assert(base); if (num < 0) num = building->maxEmployees; if (num) { switch (building->buildingType) { case B_WORKSHOP: employeeType = EMPL_WORKER; break; case B_ALIEN_CONTAINMENT: case B_LAB: employeeType = EMPL_SCIENTIST; break; case B_HOSPITAL: employeeType = EMPL_MEDIC; break; case B_HANGAR: /* the Dropship Hangar */ employeeType = EMPL_SOLDIER; break; case B_QUARTERS: return; case B_MISC: Com_DPrintf("B_HireForBuilding: Misc bulding type: %i with employees: %i.\n", building->buildingType, num); return; default: Com_DPrintf("B_HireForBuilding: Unknown bulding type: %i.\n", building->buildingType); return; } if (num > gd.numEmployees[employeeType]) num = gd.numEmployees[employeeType]; for (;num--;) if (!E_HireEmployee(base, employeeType, -1)) { Com_DPrintf("B_HireForBuilding: Hiring %i employee(s) of type %i failed.\n", num, employeeType); return; } } } /** * @brief Checks whether a building as status B_STATUS_WORKING and sets hasLab, hasHospital and so on */ static void B_UpdateBaseBuildingStatus (building_t* building, base_t* base, buildingStatus_t status) { assert(base); assert(building); building->buildingStatus = status; switch (building->buildingType) { case B_ALIEN_CONTAINMENT: if (building->buildingStatus == B_STATUS_WORKING) base->hasAlienCont = qtrue; break; case B_QUARTERS: if (building->buildingStatus == B_STATUS_WORKING) base->hasQuarters = qtrue; break; case B_STORAGE: if (building->buildingStatus == B_STATUS_WORKING) base->hasStorage = qtrue; break; case B_LAB: if (building->buildingStatus == B_STATUS_WORKING) base->hasLab = qtrue; break; case B_WORKSHOP: if (building->buildingStatus == B_STATUS_WORKING) base->hasWorkshop = qtrue; break; case B_HOSPITAL: if (building->buildingStatus == B_STATUS_WORKING) base->hasHospital = qtrue; break; case B_HANGAR: if (building->buildingStatus == B_STATUS_WORKING) base->hasHangar = qtrue; break; default: break; } } /** * @brief Setup new base * @sa CL_NewBase */ extern void B_SetUpBase (base_t* base) { int i; building_t *building = NULL; assert(base); /* update the building-list */ B_BuildingInit(); Com_DPrintf("Set up for %i\n", base->idx); /* this cvar is used for disabling the base build button on geoscape if MAX_BASES (8) was reached */ Cvar_SetValue("mn_base_count", mn_base_count->value + 1.0f); for (i = 0; i < gd.numBuildingTypes; i++) { if (gd.buildingTypes[i].autobuild || (gd.numBases == 1 && gd.buildingTypes[i].firstbase && cl_start_buildings->value)) { /* TODO: implement check for moreThanOne */ building = &gd.buildings[base->idx][gd.numBuildings[base->idx]]; memcpy(building, &gd.buildingTypes[i], sizeof(building_t)); /* self-link to building-list in base */ building->idx = gd.numBuildings[base->idx]; gd.numBuildings[base->idx]++; /* Link to the base. */ building->base_idx = base->idx; Com_DPrintf("Base %i new building:%s (%i) at (%.0f:%.0f)\n", base->idx, building->id, i, building->pos[0], building->pos[1]); base->buildingCurrent = building; /* fake a click to basemap */ B_SetBuildingByClick((int) building->pos[0], (int) building->pos[1]); B_UpdateBaseBuildingStatus(building, base, B_STATUS_WORKING); /* now call the onconstruct trigger */ if (*building->onConstruct) { base->buildingCurrent = building; Com_DPrintf("B_SetUpBase: %s %i;\n", building->onConstruct, base->idx); Cbuf_AddText(va("%s %i;", building->onConstruct, base->idx)); } /* if ( building->moreThanOne && building->howManyOfThisType < BASE_SIZE*BASE_SIZE ) building->howManyOfThisType++; */ /* update the building-list */ B_BuildingInit(); if (cl_start_employees->value) B_HireForBuilding(base, building, -1); } } /* if no autobuild, set up zero build time for the first base */ if (gd.numBases == 1 && !cl_start_buildings->value) gd.instant_build = 1; } /** * @brief Returns the building in the global building-types list that has the unique name buildingID. * * @param[in] buildingName The unique id of the building (building_t->id). * * @return building_t If a building was found it is returned, if no id was give the current building is returned, otherwise->NULL. */ extern building_t *B_GetBuildingType (const char *buildingName) { int i = 0; if (!buildingName) return baseCurrent->buildingCurrent; for (i = 0; i < gd.numBuildingTypes; i++) if (!Q_strcasecmp(gd.buildingTypes[i].id, buildingName)) return &gd.buildingTypes[i]; Com_Printf("Building %s not found\n", buildingName); return NULL; } /** * @brief Checks whether you have enough credits to build this building * @param costs buildcosts of the building * @return qboolean true - enough credits * @return qboolean false - not enough credits * * @sa B_ConstructBuilding * @sa B_NewBuilding * Checks whether the given costs are bigger than the current available credits */ static qboolean B_CheckCredits (int costs) { if (costs > ccs.credits) return qfalse; return qtrue; } /** * @brief Builds new building. * @sa B_BuildingDestroy * @sa B_CheckCredits * @sa CL_UpdateCredits * @return qboolean * @sa B_NewBuilding * Checks whether the player has enough credits to construct the current selected * building before starting construction. */ static qboolean B_ConstructBuilding (void) { building_t *building_to_build = NULL; /*maybe someone call this command before the buildings are parsed?? */ if (!baseCurrent || !baseCurrent->buildingCurrent) return qfalse; /*enough credits to build this? */ if (!B_CheckCredits(baseCurrent->buildingCurrent->fixCosts)) { Com_DPrintf("B_ConstructBuilding: Not enough credits to build: '%s'\n", baseCurrent->buildingCurrent->id); B_ResetBuildingCurrent(); return qfalse; } Com_DPrintf("Construction of %s is starting\n", baseCurrent->buildingCurrent->id); /* second building part */ if (baseCurrent->buildingToBuild >= 0) { building_to_build = &gd.buildingTypes[baseCurrent->buildingToBuild]; building_to_build->buildingStatus = B_STATUS_UNDER_CONSTRUCTION; baseCurrent->buildingToBuild = -1; } if (!gd.instant_build) { baseCurrent->buildingCurrent->buildingStatus = B_STATUS_UNDER_CONSTRUCTION; baseCurrent->buildingCurrent->timeStart = ccs.date.day; } else { B_UpdateBaseBuildingStatus(baseCurrent->buildingCurrent, baseCurrent, B_STATUS_WORKING); } CL_UpdateCredits(ccs.credits - baseCurrent->buildingCurrent->fixCosts); return qtrue; } /** * @brief Build new building. * @sa B_BuildingDestroy * @sa B_ConstructBuilding */ static void B_NewBuilding (void) { /*maybe someone call this command before the buildings are parsed?? */ if (!baseCurrent || !baseCurrent->buildingCurrent) return; if (baseCurrent->buildingCurrent->buildingStatus < B_STATUS_UNDER_CONSTRUCTION) /* credits are updated in the construct function */ if (B_ConstructBuilding()) { B_BuildingStatus(); Com_DPrintf("B_NewBuilding: buildingCurrent->buildingStatus = %i\n", baseCurrent->buildingCurrent->buildingStatus); } } /** * @brief Set the currently selected building. * * @param[in] row Set building (baseCurrent->buildingCurrent) to row * @param[in] col Set building (baseCurrent->buildingCurrent) to col */ extern void B_SetBuildingByClick (int row, int col) { int j; qboolean freeSlot = qfalse; building_t *building = NULL; building_t *secondBuildingPart = NULL; #ifdef DEBUG if (!baseCurrent) Sys_Error("no current base\n"); if (!baseCurrent->buildingCurrent) Sys_Error("no current building\n"); #endif if (!B_CheckCredits(baseCurrent->buildingCurrent->fixCosts)) { MN_Popup(_("Notice"), _("Not enough credits to build this\n")); return; } /*TODO: this is bad style (baseCurrent->buildingCurrent shouldn't link to gd.buildingTypes at all ... it's just not logical) */ /* if the building is in gd.buildingTypes[] */ if (baseCurrent->buildingCurrent->base_idx < 0) { /* search for a free slot - in case of building destruction */ for (j = 0; j < gd.numBuildings[baseCurrent->idx]; j++) { if (gd.buildings[baseCurrent->idx][j].buildingStatus == B_STATUS_NOT_SET) { building = &gd.buildings[baseCurrent->idx][j]; freeSlot = qtrue; break; } } if (!building) building = &gd.buildings[baseCurrent->idx][gd.numBuildings[baseCurrent->idx]]; /* copy building from type-list to base-buildings-list */ memcpy(building, &gd.buildingTypes[baseCurrent->buildingCurrent->type_idx], sizeof(building_t)); if (!freeSlot) { /* self-link to building-list in base */ building->idx = gd.numBuildings[baseCurrent->idx]; gd.numBuildings[baseCurrent->idx]++; } else { /* use existing slot */ building->idx = j; } /* Link to the base. */ building->base_idx = baseCurrent->idx; baseCurrent->buildingCurrent = building; } if (0 <= row && row < BASE_SIZE && 0 <= col && col < BASE_SIZE) { if (baseCurrent->map[row][col] < 0) { if (*baseCurrent->buildingCurrent->needs) secondBuildingPart = B_GetBuildingType(baseCurrent->buildingCurrent->needs); if (secondBuildingPart) { if (col + 1 == BASE_SIZE) { if (baseCurrent->map[row][col-1] >= 0) { Com_DPrintf("Can't place this building here - the second part overlapped with another building\n"); return; } col--; } else if (baseCurrent->map[row][col+1] >= 0) { if (baseCurrent->map[row][col-1] >= 0 || !col) { Com_DPrintf("Can't place this building here - the second part overlapped with another building\n"); return; } col--; } baseCurrent->map[row][col + 1] = baseCurrent->buildingCurrent->idx; baseCurrent->buildingToBuild = secondBuildingPart->idx; /* where is this building located in our base? */ secondBuildingPart->pos[1] = col + 1; secondBuildingPart->pos[0] = row; } else { baseCurrent->buildingToBuild = -1; } /* credits are updated here, too */ B_NewBuilding(); baseCurrent->map[row][col] = baseCurrent->buildingCurrent->idx; /* where is this building located in our base? */ baseCurrent->buildingCurrent->pos[0] = row; baseCurrent->buildingCurrent->pos[1] = col; B_ResetBuildingCurrent(); B_BuildingInit(); /* update the building-list */ } else { Com_DPrintf("There is already a building\n"); Com_DPrintf("Building: %i at (row:%i, col:%i)\n", baseCurrent->map[row][col], row, col); } } else Com_DPrintf("Invalid coordinates\n"); } /** * @brief Places the current building in the base (x/y give via console). */ static void B_SetBuilding_f (void) { int row, col; if (Cmd_Argc() < 3) { Com_Printf("Usage: set_building \n"); return; } /*maybe someone call this command before the buildings are parsed?? */ if (!baseCurrent || !baseCurrent->buildingCurrent) return; row = atoi(Cmd_Argv(1)); col = atoi(Cmd_Argv(2)); /*emulate the mouseclick with the given coordinates */ B_SetBuildingByClick(row, col); } /** * @brief Build building from the list of those available. */ static void B_NewBuildingFromList_f (void) { /*maybe someone call this command before the buildings are parsed?? */ if (!baseCurrent || !baseCurrent->buildingCurrent) return; if (baseCurrent->buildingCurrent->buildingStatus < B_STATUS_UNDER_CONSTRUCTION) B_NewBuilding(); } /** * @brief Draws a building. */ static void B_DrawBuilding (void) { building_t *building = NULL; /*maybe someone call this command before the buildings are parsed?? */ if (!baseCurrent || !baseCurrent->buildingCurrent) return; *buildingText = '\0'; building = baseCurrent->buildingCurrent; B_BuildingStatus(); Com_sprintf(buildingText, sizeof(buildingText), va("%s\n", building->name)); if (building->buildingStatus < B_STATUS_UNDER_CONSTRUCTION && building->fixCosts) Com_sprintf(buildingText, sizeof(buildingText), _("Costs:\t%1.0f c\n"), building->fixCosts); if (building->buildingStatus == B_STATUS_UNDER_CONSTRUCTION) Q_strcat(buildingText, va(ngettext("%i Day to build\n", "%i Days to build\n", building->buildTime), building->buildTime), sizeof(buildingText)); if (building->varCosts) Q_strcat(buildingText, va(_("Running Costs:\t%1.0f c\n"), building->varCosts), sizeof(buildingText)); /* if (employees_in_building->numEmployees) Q_strcat(menuText[TEXT_BUILDING_INFO], va(_("Employees:\t%i\n"), employees_in_building->numEmployees), MAX_LIST_CHAR);*/ /* FIXME: Rename mn_building_name and mn_building_title */ if (building->id) Cvar_Set("mn_building_name", building->id); if (building->name) Cvar_Set("mn_building_title", building->name); /* link into menu text array */ menuText[TEXT_BUILDING_INFO] = buildingText; } /** * @brief Handles the list of constructable buildings. * * @param[in] building Add this building to the construction list * Called everytime a building was constructed and thus maybe other buildings get available. * menuText[TEXT_BUILDINGS] is a pointer to baseCurrent->allBuildingsList which will be displayed in the build-screen. * This way every base can hold its own building list. * The content is updated everytime B_BuildingInit is called (i.e everytime the buildings-list is dispplayed/updated) */ static void B_BuildingAddToList (building_t * building) { assert(baseCurrent); Q_strcat(baseCurrent->allBuildingsList, va("%s\n", _(building->name)), MAX_LIST_CHAR); BuildingConstructionList[numBuildingConstructionList] = building->type_idx; numBuildingConstructionList++; } /** * @brief Counts the number of buildings of a particular type in a base. * * @param[in] base_idx Which base * @param[in] type_idx Which buildingtype * @sa B_GetNumberOfBuildingsInBaseByType */ extern int B_GetNumberOfBuildingsInBaseByTypeIDX (int base_idx, int type_idx) { int i; int NumberOfBuildings = 0; if (base_idx < 0 || base_idx >= gd.numBases) { Com_Printf("B_GetNumberOfBuildingsInBaseByTypeIDX: Bad base-index given: %i (numbases %i)\n", base_idx, gd.numBases); return -1; } for (i = 0; i < gd.numBuildings[base_idx]; i++) { if (gd.buildings[base_idx][i].type_idx == type_idx && gd.buildings[base_idx][i].buildingStatus != B_STATUS_NOT_SET) NumberOfBuildings++; } Com_DPrintf("B_GetNumOfBuildType: base: '%s' - num_b: %i - type_idx: %s\n", gd.bases[base_idx].name, NumberOfBuildings, gd.buildingTypes[type_idx].id); return NumberOfBuildings; } /** * @brief Counts the number of buildings of a particular type in a base. * * @param[in] base_idx Which base * @param[in] type Building type value * @sa B_GetNumberOfBuildingsInBaseByTypeIDX */ extern int B_GetNumberOfBuildingsInBaseByType (int base_idx, buildingType_t type) { int i; int NumberOfBuildings = 0; if (base_idx < 0 || base_idx >= gd.numBases) { Com_Printf("B_GetNumberOfBuildingsInBaseByType: Bad base-index given: %i (numbases: %i)\n", base_idx, gd.numBases); return -1; } for (i = 0; i < gd.numBuildings[base_idx]; i++) { if (gd.buildings[base_idx][i].buildingType == type && gd.buildings[base_idx][i].buildingStatus != B_STATUS_NOT_SET) NumberOfBuildings++; } return NumberOfBuildings; } /** * @brief Get the maximum status of a building. * * This function is mostly used to check if the construction of a building with a given type is finished. * e.g.: "if (B_GetMaximumBuildingStatus(base_idx, B_LAB) >= B_STATUS_CONSTRUCTION_FINISHED) { ... }" * * @param[in] base_idx Which base * @param[in] buildingType Which buildingtype * @return The max./highest building status found. */ static buildingStatus_t B_GetMaximumBuildingStatus (int base_idx, buildingType_t buildingType) { int i; buildingStatus_t status = B_STATUS_NOT_SET; if (base_idx < 0) { Com_Printf("B_GetMaximumBuildingStatus: Bad base-index given: %i (numbases %i)\n", base_idx, gd.numBases); return -1; } for (i = 0; i < gd.numBuildings[base_idx]; i++) { if (gd.buildings[base_idx][i].buildingType == buildingType) if (gd.buildings[base_idx][i].buildingStatus > status) status = gd.buildings[base_idx][i].buildingStatus; } return status; } /** * @brief Update the building-list. */ static void B_BuildingInit (void) { int i; int numSameBuildings; building_t *buildingType = NULL; /* maybe someone call this command before the bases are parsed?? */ if (!baseCurrent) return; Com_DPrintf("B_BuildingInit: Updating b-list for '%s' (%i)\n", baseCurrent->name, baseCurrent->idx); Com_DPrintf("B_BuildingInit: Buildings in base: %i\n", gd.numBuildings[baseCurrent->idx]); /* initialising the vars used in B_BuildingAddToList */ memset(baseCurrent->allBuildingsList, 0, sizeof(baseCurrent->allBuildingsList)); menuText[TEXT_BUILDINGS] = baseCurrent->allBuildingsList; numBuildingConstructionList = 0; /* ------------------ */ for (i = 0; i < gd.numBuildingTypes; i++) { buildingType = &gd.buildingTypes[i]; /*make an entry in list for this building */ if (buildingType->visible) { numSameBuildings = B_GetNumberOfBuildingsInBaseByTypeIDX(baseCurrent->idx, buildingType->type_idx); if (buildingType->moreThanOne) { /* skip if limit of BASE_SIZE*BASE_SIZE exceeded */ if (numSameBuildings >= BASE_SIZE * BASE_SIZE) continue; } else if (numSameBuildings > 0) { continue; } /* if the building is researched add it to the list */ if (RS_IsResearched_idx(buildingType->tech)) { if (buildingType->dependsBuilding < 0 || B_GetMaximumBuildingStatus(baseCurrent->idx, buildingType->buildingType) >= B_STATUS_CONSTRUCTION_FINISHED) { B_BuildingAddToList(buildingType); } } else { Com_DPrintf("Building not researched yet %s\n", buildingType->id); } } } if (baseCurrent->buildingCurrent) { B_DrawBuilding(); } } /** * @brief Gets the type of building by its index. * * @param[in] base Pointer to base_t (base has to be founded already) * @param[in] idx The index of the building in gd.buildings[] * @return buildings_t pointer to gd.buildings[idx] */ extern building_t *B_GetBuildingByIdx (base_t* base, int idx) { if (base) return &gd.buildings[base->idx][idx]; else Sys_Error("B_GetBuildingByIdx: Base not initialized\n"); /*just that there are no warnings */ return NULL; } /** * @brief Gets the building in a given base by its index * * @param[in] base Pointer to base_t (base has to be founded already) * @param[in] buildingID Pointer to char * @return buildings_t pointer to gd.buildings */ #if 0 static building_t *B_GetBuildingInBase (base_t* base, char* buildingID) { int row, col; if (base || !base->founded) return NULL; for (row = 0; row < BASE_SIZE; row++) for (col = 0; col < BASE_SIZE; col++) if (!Q_strncmp(gd.buildings[base->idx][base->map[row][col]].id, buildingID, MAX_VAR)) return &gd.buildings[base->idx][base->map[row][col]]; Com_Printf("B_GetBuildingInBase: Building '%s' not found\n", buildingID); /* just that there are no warnings */ return NULL; } #endif /** * @brief Opens up the 'pedia if you right click on a building in the list. * * @todo Really only do this on rightclick. * @todo Left click should show building-status. */ static void B_BuildingInfoClick_f (void) { if (baseCurrent && baseCurrent->buildingCurrent) { Com_DPrintf("B_BuildingInfoClick_f: %s - %i\n", baseCurrent->buildingCurrent->id, baseCurrent->buildingCurrent->buildingStatus); UP_OpenWith(baseCurrent->buildingCurrent->pedia); } } /** * @brief Script function for clicking the building list text field. */ static void B_BuildingClick_f (void) { int num; building_t *building = NULL; if (Cmd_Argc() < 2 || !baseCurrent) { Com_Printf("Usage: %s \n", Cmd_Argv(0)); return; } /* which building? */ num = atoi(Cmd_Argv(1)); Com_DPrintf("B_BuildingClick_f: listnumber %i base %i\n", num, baseCurrent->idx); if (num > numBuildingConstructionList || num < 0) { Com_DPrintf("B_BuildingClick_f: max exceeded %i/%i\n", num, numBuildingConstructionList); return; } building = &gd.buildingTypes[BuildingConstructionList[num]]; baseCurrent->buildingCurrent = building; B_DrawBuilding(); } /** * @brief Copies an entry from the building description file into the list of building types. * * Parses one "building" entry in the basemanagement.ufo file and writes it into the next free entry in bmBuildings[0], which is the list of buildings in the first base (building_t). * * @param[in] id Unique test-id of a building_t. This is parsed from "building xxx" -> id=xxx. * @param[in] text TODO: document this ... It appears to be the whole following text that is part of the "building" item definition in .ufo. * @param[in] link Bool value that decides whether to link the tech pointer in or not */ extern void B_ParseBuildings (const char *name, char **text, qboolean link) { building_t *building = NULL; building_t *dependsBuilding = NULL; technology_t *tech_link = NULL; const value_t *edp = NULL; const char *errhead = "B_ParseBuildings: unexpected end of file (names "; char *token = NULL; #if 0 char *split = NULL; int employeesAmount = 0, i; employee_t* employee; #endif /* get id list body */ token = COM_Parse(text); if (!*text || *token != '{') { Com_Printf("B_ParseBuildings: building \"%s\" without body ignored\n", name); return; } if (gd.numBuildingTypes >= MAX_BUILDINGS) { Com_Printf("B_ParseBuildings: too many buildings\n"); gd.numBuildingTypes = MAX_BUILDINGS; /* just in case it's bigger. */ return; } if (!link) { /* new entry */ building = &gd.buildingTypes[gd.numBuildingTypes]; memset(building, 0, sizeof(building_t)); Q_strncpyz(building->id, name, sizeof(building->id)); Com_DPrintf("...found building %s\n", building->id); /*set standard values */ building->type_idx = gd.numBuildingTypes; building->idx = -1; building->base_idx = -1; building->tech = -1; building->dependsBuilding = -1; building->visible = qtrue; gd.numBuildingTypes++; do { /* get the name type */ token = COM_EParse(text, errhead, name); if (!*text) break; if (*token == '}') break; /* get values */ if (!Q_strncmp(token, "type", MAX_VAR)) { token = COM_EParse(text, errhead, name); if (!*text) return; if (!Q_strncmp(token, "lab", MAX_VAR)) { building->buildingType = B_LAB; } else if (!Q_strncmp(token, "hospital", MAX_VAR)) { building->buildingType = B_HOSPITAL; } else if (!Q_strncmp(token, "aliencont", MAX_VAR)) { building->buildingType = B_ALIEN_CONTAINMENT; } else if (!Q_strncmp(token, "workshop", MAX_VAR)) { building->buildingType = B_WORKSHOP; } else if (!Q_strncmp(token, "storage", MAX_VAR)) { building->buildingType = B_STORAGE; } else if (!Q_strncmp(token, "hangar", MAX_VAR)) { building->buildingType = B_HANGAR; } else if (!Q_strncmp(token, "quarters", MAX_VAR)) { building->buildingType = B_QUARTERS; } else if (!Q_strncmp(token, "workshop", MAX_VAR)) { building->buildingType = B_WORKSHOP; } /* } else if (!Q_strncmp(token, "max_employees", MAX_VAR)) { token = COM_EParse(text, errhead, name); if (!*text) return; employees_in_building = &building->assigned_employees; if (*token) employees_in_building->maxEmployees = atoi(token); else { employees_in_building->maxEmployees = MAX_EMPLOYEES_IN_BUILDING; Com_Printf("Set max employees to %i for building '%s'\n", MAX_EMPLOYEES_IN_BUILDING, building->id); }*/ } else /* no linking yet */ if (!Q_strncmp(token, "depends", MAX_VAR)) { token = COM_EParse(text, errhead, name); if (!*text) return; } else { for (edp = valid_vars; edp->string; edp++) if (!Q_strncmp(token, edp->string, sizeof(edp->string))) { /* found a definition */ token = COM_EParse(text, errhead, name); if (!*text) return; Com_ParseValue(building, token, edp->type, edp->ofs); break; } } if (!edp->string) Com_Printf("B_ParseBuildings: unknown token \"%s\" ignored (building %s)\n", token, name); } while (*text); } else { building = B_GetBuildingType(name); if (!building) /* i'm paranoid */ Sys_Error("B_ParseBuildings: Could not find building with id %s\n", name); tech_link = RS_GetTechByProvided(name); if (tech_link) { building->tech = tech_link->idx; } else { if (building->visible) /* TODO: are the techs already parsed? */ Com_DPrintf("B_ParseBuildings: Could not find tech that provides %s\n", name); } do { /* get the name type */ token = COM_EParse(text, errhead, name); if (!*text) break; if (*token == '}') break; /* get values */ if (!Q_strncmp(token, "depends", MAX_VAR)) { dependsBuilding = B_GetBuildingType(COM_EParse(text, errhead, name)); if (!dependsBuilding) Sys_Error("Could not find building depend of %s\n", building->id); building->dependsBuilding = dependsBuilding->idx; if (!*text) return; } } while (*text); } } /** * @brief Gets a free (with no assigned workers) building in the given base of the given type. * * @param[in] base_id The number/id of the base to search in. * @param[in] type Which type of building to search for. * * @return The (empty) building. */ #if 0 building_t *B_GetFreeBuilding (int base_idx, buildingType_t type) { int i; building_t *building = NULL; employees_t *employees_in_building = NULL; for (i = 0; i < gd.numBuildings[base_idx]; i++) { building = &gd.buildings[base_idx][i]; if (building->buildingType == type) { /* found correct building-type */ employees_in_building = &building->assigned_employees; if (employees_in_building->numEmployees < employees_in_building->maxEmployees) { /* the bulding has free space for employees */ return building; } } } /* no buildings available at all, no correct building type found or no building free */ return NULL; } #endif /** * @brief Gets a free (with no assigned workers) building of the given type. * * @param[in] type Which type of building to search for. * * @return The (empty) building. */ #if 0 static building_t *B_GetFreeBuildingType (buildingType_t type) { int i; building_t *building = NULL; for (i = 0; i < gd.numBuildingTypes; i++) { building = &gd.buildingTypes[i]; if (building->buildingType == type) { /* found correct building-type */ /* employees_in_building = &building->assigned_employees; if (employees_in_building->numEmployees < employees_in_building->maxEmployees) {*/ /* the bulding has free space for employees */ /*return building; }*/ } } /* no buildings available at all, no correct building type found or no building free */ return NULL; } #endif /** * @brief Gets a lab in the given base * @note You can run more than one research in a lab * * @param[in] base_id The number/id of the base to search in. * * @return The lab or NULL if base has no lab */ extern building_t *B_GetLab (int base_idx) { int i; building_t *building = NULL; for (i = 0; i < gd.numBuildings[base_idx]; i++) { building = &gd.buildings[base_idx][i]; if (building->buildingType == B_LAB) return building; } return NULL; } /** * @brief Clears a base with all its characters * @sa CL_ResetCharacters * @sa CL_GenerateCharacter */ extern void B_ClearBase (base_t *const base) { int row, col, i; CL_ResetCharacters(base); memset(base, 0, sizeof(base_t)); /* only go further if we have a active campaign */ if (!curCampaign) return; /* setup team */ if (!E_CountUnhired(EMPL_SOLDIER)) { /* should be multiplayer (campaignmode TODO) or singleplayer */ Com_DPrintf("B_ClearBase: create %i soldiers\n", curCampaign->soldiers); for (i = 0; i < curCampaign->soldiers; i++) E_CreateEmployee(EMPL_SOLDIER); Com_DPrintf("B_ClearBase: create %i scientists\n", curCampaign->scientists); for (i = 0; i < curCampaign->scientists; i++) E_CreateEmployee(EMPL_SCIENTIST); Com_DPrintf("B_ClearBase: create %i robots\n", curCampaign->ugvs); for (i = 0; i < curCampaign->ugvs; i++) E_CreateEmployee(EMPL_ROBOT); Com_DPrintf("B_ClearBase: create %i workers\n", curCampaign->workers); for (i = 0; i < curCampaign->workers; i++) E_CreateEmployee(EMPL_WORKER); Com_DPrintf("B_ClearBase: create %i medics\n", curCampaign->medics); for (i = 0; i < curCampaign->medics; i++) E_CreateEmployee(EMPL_MEDIC); } for (row = BASE_SIZE - 1; row >= 0; row--) for (col = BASE_SIZE - 1; col >= 0; col--) base->map[row][col] = -1; } /** * @brief Reads information about bases. * @sa CL_ParseScriptFirst */ extern void B_ParseBases (const char *name, char **text) { const char *errhead = "B_ParseBases: unexpected end of file (names "; char *token; base_t *base; gd.numBaseNames = 0; /* get token */ token = COM_Parse(text); if (!*text || *token != '{') { Com_Printf("B_ParseBases: base \"%s\" without body ignored\n", name); return; } do { /* add base */ if (gd.numBaseNames > MAX_BASES) { Com_Printf("B_ParseBases: too many bases\n"); return; } /* get the name */ token = COM_EParse(text, errhead, name); if (!*text) break; if (*token == '}') break; base = &gd.bases[gd.numBaseNames]; memset(base, 0, sizeof(base_t)); base->idx = gd.numBaseNames; base->buildingToBuild = -1; memset(base->map, -1, sizeof(int) * BASE_SIZE * BASE_SIZE); /* get the title */ token = COM_EParse(text, errhead, name); if (!*text) break; if (*token == '}') break; if (*token == '_') token++; Q_strncpyz(base->name, _(token), sizeof(base->name)); Com_DPrintf("Found base %s\n", base->name); B_ResetBuildingCurrent(); gd.numBaseNames++; } while (*text); mn_base_title = Cvar_Get("mn_base_title", "", 0, NULL); } /** * @brief Draws a base. * @sa MN_DrawMenus */ extern void B_DrawBase (menuNode_t * node) { float x, y; int mx, my, width, height, row, col, time; qboolean hover = qfalse; static vec4_t color = { 0.5f, 1.0f, 0.5f, 1.0 }; char image[MAX_QPATH]; building_t *building = NULL, *secondBuilding = NULL, *hoverBuilding = NULL; if (!baseCurrent) Cbuf_ExecuteText(EXEC_NOW, "mn_pop"); width = node->size[0] / BASE_SIZE; height = (node->size[1] + BASE_SIZE * 20) / BASE_SIZE; IN_GetMousePos(&mx, &my); for (row = 0; row < BASE_SIZE; row++) { for (col = 0; col < BASE_SIZE; col++) { /* 20 is the height of the part where the images overlap */ x = node->pos[0] + col * width; y = node->pos[1] + row * height - row * 20; baseCurrent->posX[row][col] = x; baseCurrent->posY[row][col] = y; if (baseCurrent->map[row][col] >= 0) { building = B_GetBuildingByIdx(baseCurrent, baseCurrent->map[row][col]); secondBuilding = NULL; if (!building) Sys_Error("Error in DrawBase - no building with id %i\n", baseCurrent->map[row][col]); if (!building->used) { if (*building->needs) building->used = 1; if (*building->image) { /* TODO:DEBUG */ Q_strncpyz(image, building->image, sizeof(image)); } else { /*Com_DPrintf( "B_DrawBase: no image found for building %s / %i\n",building->id ,building->idx ); */ } } else if (*building->needs) { secondBuilding = B_GetBuildingType(building->needs); if (!secondBuilding) Sys_Error("Error in ufo-scriptfile - could not find the needed building\n"); Q_strncpyz(image, secondBuilding->image, sizeof(image)); building->used = 0; } } else { building = NULL; Q_strncpyz(image, "base/grid", sizeof(image)); } if (mx > x && mx < x + width && my > y && my < y + height - 20) { hover = qtrue; if (baseCurrent->map[row][col] >= 0) hoverBuilding = building; } else hover = qfalse; if (*image) re.DrawNormPic(x, y, width, height, 0, 0, 0, 0, 0, qfalse, image); /* only draw for first part of building */ if (building && !secondBuilding) { switch (building->buildingStatus) { case B_STATUS_DOWN: case B_STATUS_CONSTRUCTION_FINISHED: break; case B_STATUS_UNDER_CONSTRUCTION: time = building->buildTime - (ccs.date.day - building->timeStart); re.FontDrawString("f_small", 0, x + 10, y + 10, x + 10, y + 10, node->size[0], 0, node->texh[0], va(ngettext("%i day left", "%i days left", time), time), 0, 0, NULL, qfalse); break; default: break; } } } } if (hoverBuilding) { re.DrawColor(color); re.FontDrawString("f_small", 0, mx + 3, my, mx + 3, my, node->size[0], 0, node->texh[0], hoverBuilding->name, 0, 0, NULL, qfalse); re.DrawColor(NULL); } } /** * @brief Initialises base. */ static void B_BaseInit_f (void) { int baseID = Cvar_VariableInteger("mn_base_id"); baseCurrent = &gd.bases[baseID]; Cvar_SetValue("mn_medics_in_base", E_CountHired(baseCurrent, EMPL_MEDIC)); Cvar_SetValue("mn_soldiers_in_base", E_CountHired(baseCurrent, EMPL_SOLDIER)); Cvar_SetValue("mn_scientists_in_base", E_CountHired(baseCurrent, EMPL_SCIENTIST)); Cvar_Set("mn_credits", va(_("%i c"), ccs.credits)); } /** * @brief Renames a base. */ static void B_RenameBase_f (void) { if (Cmd_Argc() < 2) { Com_Printf("Usage: rename_base \n"); return; } if (baseCurrent) Q_strncpyz(baseCurrent->name, Cmd_Argv(1), sizeof(baseCurrent->name)); } /** * @brief Cycles to the next base. * @sa B_PrevBase * @sa B_SelectBase_f */ static void B_NextBase_f (void) { int baseID = Cvar_VariableInteger("mn_base_id"); Com_DPrintf("cur-base=%i num-base=%i\n", baseID, gd.numBases); if (baseID < gd.numBases - 1) baseID++; else baseID = 0; Com_DPrintf("new-base=%i\n", baseID); if (!gd.bases[baseID].founded) return; else { Cbuf_AddText(va("mn_select_base %i\n", baseID)); Cbuf_Execute(); } } /** * @brief Cycles to the previous base. * @sa B_NextBase * @sa B_SelectBase_f */ static void B_PrevBase_f (void) { int baseID = Cvar_VariableInteger("mn_base_id"); Com_DPrintf("cur-base=%i num-base=%i\n", baseID, gd.numBases); if (baseID > 0) baseID--; else baseID = gd.numBases - 1; Com_DPrintf("new-base=%i\n", baseID); /* this must be false - but i'm paranoid' */ if (!gd.bases[baseID].founded) return; else { Cbuf_AddText(va("mn_select_base %i\n", baseID)); Cbuf_Execute(); } } /** * @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 (Cmd_Argc() < 2) { Com_Printf("Usage: mn_select_base \n"); return; } baseID = atoi(Cmd_Argv(1)); /* set up a new base */ /* called from *.ufo with -1 */ if (baseID < 0) { gd.mapAction = MA_NEWBASE; baseID = gd.numBases; Com_DPrintf("B_SelectBase_f: new baseID is %i\n", baseID); if (baseID < MAX_BASES) { baseCurrent = &gd.bases[baseID]; baseCurrent->idx = baseID; Com_DPrintf("B_SelectBase_f: baseID is valid for base: %s\n", baseCurrent->name); Cbuf_ExecuteText(EXEC_NOW, "set_base_to_normal"); } else { Com_Printf("MaxBases reached\n"); /* select the first base in list */ baseCurrent = &gd.bases[0]; gd.mapAction = MA_NONE; } } else if (baseID < MAX_BASES) { Com_DPrintf("B_SelectBase_f: select base with id %i\n", baseID); baseCurrent = &gd.bases[baseID]; if (baseCurrent->founded) { gd.mapAction = MA_NONE; MN_PushMenu("bases"); CL_AircraftSelect(NULL); switch (baseCurrent->baseStatus) { case BASE_UNDER_ATTACK: Cvar_Set("mn_base_status_name", _("Base is under attack")); Cbuf_ExecuteText(EXEC_NOW, "set_base_under_attack"); break; default: Cbuf_ExecuteText(EXEC_NOW, "set_base_to_normal"); break; } } else { gd.mapAction = MA_NEWBASE; } } else return; /** * this is only needed when we are going to be show up the base * in our base view port */ if (gd.mapAction != MA_NEWBASE) { /* activate or deactivate the aircraft button */ if (baseCurrent->numAircraftInBase <= 0) Cbuf_ExecuteText(EXEC_NOW, "set_base_no_aircraft"); else Cbuf_ExecuteText(EXEC_NOW, "set_base_aircraft"); Cvar_SetValue("mn_base_status_id", baseCurrent->baseStatus); Cvar_SetValue("mn_base_prod_allowed", PR_ProductionAllowed()); Cvar_SetValue("mn_base_num_aircraft", baseCurrent->numAircraftInBase); Cvar_SetValue("mn_base_id", baseCurrent->idx); Cvar_SetValue("mn_numbases", gd.numBases); if (gd.numBases > 1) { Cbuf_AddText("set_base_transfer;"); } else { Cbuf_AddText("set_base_no_transfer;"); } Cvar_Set("mn_base_title", baseCurrent->name); } } #undef RIGHT #undef HOLSTER #define RIGHT(e) ((e)->inv->c[csi.idRight]) #define HOLSTER(e) ((e)->inv->c[csi.idHolster]) #define MAX(a,b) ((a)>(b)?(a):(b)) #define MIN(a,b) ((a)<(b)?(a):(b)) /** * @brief Swaps skills of the initial team of soldiers so that they match inventories * @todo This currently always uses exactly the first two firemodes (see fmode1+fmode2) for calculation. This needs to be adapted to support less (1) or more 3+ firemodes. I think the function will even break on only one firemode .. never tested it. * @todo i think currently also the different ammo/firedef types for each weapon (different weaponr_fd_idx and weaponr_fd_idx values) are ignored. */ static void CL_SwapSkills (character_t *team[], int num) { int j, i1, i2, skill, no1, no2, tmp1, tmp2; character_t *cp1, *cp2; int weaponr_fd_idx, weaponh_fd_idx; const byte fmode1 = 0; const byte fmode2 = 1; j = num; while (j--) { /* running the loops below is not enough, we need transitive closure */ /* I guess num times is enough --- could anybody prove this? */ /* or perhaps 2 times is enough as long as weapons have 1 skill? */ for (skill = ABILITY_NUM_TYPES; skill < SKILL_NUM_TYPES; skill++) { for (i1 = 0; i1 < num - 1; i1++) { cp1 = team[i1]; weaponr_fd_idx = -1; weaponh_fd_idx = -1; if (RIGHT(cp1) && RIGHT(cp1)->item.m != NONE && RIGHT(cp1)->item.t != NONE) weaponr_fd_idx = INV_FiredefsIDXForWeapon(&csi.ods[RIGHT(cp1)->item.m], RIGHT(cp1)->item.t); if (HOLSTER(cp1) && HOLSTER(cp1)->item.m != NONE && HOLSTER(cp1)->item.t != NONE) weaponh_fd_idx = INV_FiredefsIDXForWeapon(&csi.ods[HOLSTER(cp1)->item.m], HOLSTER(cp1)->item.t); /* disregard left hand, or dual-wielding guys are too good */ if (weaponr_fd_idx < 0 || weaponh_fd_idx < 0) { /* TODO: Is there a better way to check for this case? */ Com_DPrintf("CL_SwapSkills: Bad or no firedef indices found (weaponr_fd_idx=%i and weaponh_fd_idx=%i)... skipping\n", weaponr_fd_idx, weaponh_fd_idx); } else { no1 = 2 * (RIGHT(cp1) && skill == csi.ods[RIGHT(cp1)->item.m].fd[weaponr_fd_idx][fmode1].weaponSkill) + 2 * (RIGHT(cp1) && skill == csi.ods[RIGHT(cp1)->item.m].fd[weaponr_fd_idx][fmode2].weaponSkill) + (HOLSTER(cp1) && csi.ods[HOLSTER(cp1)->item.t].reload && skill == csi.ods[HOLSTER(cp1)->item.m].fd[weaponh_fd_idx][fmode1].weaponSkill) + (HOLSTER(cp1) && csi.ods[HOLSTER(cp1)->item.t].reload && skill == csi.ods[HOLSTER(cp1)->item.m].fd[weaponh_fd_idx][fmode2].weaponSkill); for (i2 = i1 + 1 ; i2 < num; i2++) { cp2 = team[i2]; weaponr_fd_idx = -1; weaponh_fd_idx = -1; if (RIGHT(cp2) && RIGHT(cp2)->item.m != NONE && RIGHT(cp2)->item.t != NONE) weaponr_fd_idx = INV_FiredefsIDXForWeapon(&csi.ods[RIGHT(cp2)->item.m], RIGHT(cp2)->item.t); if (HOLSTER(cp2) && HOLSTER(cp2)->item.m != NONE && HOLSTER(cp2)->item.t != NONE) weaponh_fd_idx = INV_FiredefsIDXForWeapon(&csi.ods[HOLSTER(cp2)->item.m], HOLSTER(cp2)->item.t); if (weaponr_fd_idx < 0 || weaponh_fd_idx < 0) { /* TODO: Is there a better way to check for this case? */ Com_DPrintf("CL_SwapSkills: Bad or no firedef indices found (weaponr_fd_idx=%i and weaponh_fd_idx=%i)... skipping\n", weaponr_fd_idx, weaponh_fd_idx); } else { /* FIXME This will crash if weaponh_fd_idx or weaponr_fd_idx is -1 */ no2 = 2 * (RIGHT(cp2) && skill == csi.ods[RIGHT(cp2)->item.m].fd[weaponr_fd_idx][fmode1].weaponSkill) + 2 * (RIGHT(cp2) && skill == csi.ods[RIGHT(cp2)->item.m].fd[weaponr_fd_idx][fmode2].weaponSkill) + (HOLSTER(cp2) && csi.ods[HOLSTER(cp2)->item.t].reload && skill == csi.ods[HOLSTER(cp2)->item.m].fd[weaponh_fd_idx][fmode1].weaponSkill) + (HOLSTER(cp2) && csi.ods[HOLSTER(cp2)->item.t].reload && skill == csi.ods[HOLSTER(cp2)->item.m].fd[weaponh_fd_idx][fmode2].weaponSkill); if ( no1 > no2 /* more use of this skill */ || (no1 && no1 == no2) ) { /* or earlier on list */ tmp1 = cp1->skills[skill]; tmp2 = cp2->skills[skill]; cp1->skills[skill] = MAX(tmp1, tmp2); cp2->skills[skill] = MIN(tmp1, tmp2); switch (skill) { case SKILL_CLOSE: tmp1 = cp1->skills[ABILITY_SPEED]; tmp2 = cp2->skills[ABILITY_SPEED]; cp1->skills[ABILITY_SPEED] = MAX(tmp1, tmp2); cp2->skills[ABILITY_SPEED] = MIN(tmp1, tmp2); break; case SKILL_HEAVY: tmp1 = cp1->skills[ABILITY_POWER]; tmp2 = cp2->skills[ABILITY_POWER]; cp1->skills[ABILITY_POWER] = MAX(tmp1, tmp2); cp2->skills[ABILITY_POWER] = MIN(tmp1, tmp2); break; case SKILL_ASSAULT: /* no related basic attribute */ break; case SKILL_SNIPER: tmp1 = cp1->skills[ABILITY_ACCURACY]; tmp2 = cp2->skills[ABILITY_ACCURACY]; cp1->skills[ABILITY_ACCURACY] = MAX(tmp1, tmp2); cp2->skills[ABILITY_ACCURACY] = MIN(tmp1, tmp2); break; case SKILL_EXPLOSIVE: tmp1 = cp1->skills[ABILITY_MIND]; tmp2 = cp2->skills[ABILITY_MIND]; cp1->skills[ABILITY_MIND] = MAX(tmp1, tmp2); cp2->skills[ABILITY_MIND] = MIN(tmp1, tmp2); break; default: Sys_Error("CL_SwapSkills: illegal skill %i.\n", skill); } } else if (no1 < no2) { tmp1 = cp1->skills[skill]; tmp2 = cp2->skills[skill]; cp2->skills[skill] = MAX(tmp1, tmp2); cp1->skills[skill] = MIN(tmp1, tmp2); switch (skill) { case SKILL_CLOSE: tmp1 = cp1->skills[ABILITY_SPEED]; tmp2 = cp2->skills[ABILITY_SPEED]; cp2->skills[ABILITY_SPEED] = MAX(tmp1, tmp2); cp1->skills[ABILITY_SPEED] = MIN(tmp1, tmp2); break; case SKILL_HEAVY: tmp1 = cp1->skills[ABILITY_POWER]; tmp2 = cp2->skills[ABILITY_POWER]; cp2->skills[ABILITY_POWER] = MAX(tmp1, tmp2); cp1->skills[ABILITY_POWER] = MIN(tmp1, tmp2); break; case SKILL_ASSAULT: break; case SKILL_SNIPER: tmp1 = cp1->skills[ABILITY_ACCURACY]; tmp2 = cp2->skills[ABILITY_ACCURACY]; cp2->skills[ABILITY_ACCURACY] = MAX(tmp1, tmp2); cp1->skills[ABILITY_ACCURACY] = MIN(tmp1, tmp2); break; case SKILL_EXPLOSIVE: tmp1 = cp1->skills[ABILITY_MIND]; tmp2 = cp2->skills[ABILITY_MIND]; cp2->skills[ABILITY_MIND] = MAX(tmp1, tmp2); cp1->skills[ABILITY_MIND] = MIN(tmp1, tmp2); break; default: Sys_Error("CL_SwapSkills: illegal skill %i.\n", skill); } } } /* if xx_fd_xx < 0 */ } /* for */ } /* if xx_fd_xx < 0 */ } } } } /** * @brief Assigns initial team of soldiers with equipment to aircraft * @note If assign_initial is called with one parameter (e.g. a 1), this is for * multiplayer - assign_initial with no parameters is for singleplayer */ static void B_AssignInitial_f (void) { int i; /* check syntax */ if (Cmd_Argc() > 2) { Com_Printf("Usage: assign_initial []\n"); return; } if (!baseCurrent) return; if (Cmd_Argc() == 2) { CL_ResetTeamInBase(); Cvar_Set("mn_teamname", _("NewTeam")); Cbuf_AddText("gennames;"); } for (i = MAX_TEAMLIST; --i >= 0;) Cbuf_AddText(va("team_hire %i;", i)); Cbuf_AddText("pack_initial;"); if (Cmd_Argc() == 2) { MN_PushMenu("team"); } } /** * @brief Assigns initial soldier equipment for the first base */ static void B_PackInitialEquipment_f (void) { int i, price = 0; equipDef_t *ed; character_t *chr; const char *name = curCampaign ? cl_initial_equipment->string : Cvar_VariableString("equip"); /* check syntax */ if (Cmd_Argc() > 1) { Com_Printf("Usage: pack_initial\n"); return; } if (!baseCurrent) return; for (i = 0, ed = csi.eds; i < csi.numEDs; i++, ed++) if (!Q_strncmp(name, ed->name, MAX_VAR)) break; if (i == csi.numEDs) { Com_DPrintf("B_PackInitialEquipment_f: Initial Phalanx equipment %s not found.\n", name); } else if ((baseCurrent->aircraftCurrent >= 0) && (baseCurrent->aircraftCurrent < baseCurrent->numAircraftInBase)) { for (i = 0; i < baseCurrent->teamNum[baseCurrent->aircraftCurrent]; i++) { chr = baseCurrent->curTeam[i]; /* pack equipment */ Com_DPrintf("B_PackInitialEquipment_f: Packing initial equipment for %s.\n", chr->name); Com_EquipActor(chr->inv, ed->num, name, chr); Com_DPrintf("B_PackInitialEquipment_f: armor: %i, weapons: %i\n", chr->armor, chr->weapons); } CL_AddCarriedToEq(&baseCurrent->storage); CL_SwapSkills(baseCurrent->curTeam, baseCurrent->teamNum[baseCurrent->aircraftCurrent]); /* pay for the initial equipment */ for (i = 0; i < csi.numODs; i++) price += baseCurrent->storage.num[i] * csi.ods[i].price; CL_UpdateCredits(ccs.credits - price); } } /* FIXME: This value is in menu_geoscape, too */ /* make this variable?? */ #define BASE_COSTS 100000 /** * @brief Constructs a new base. * @sa CL_NewBase */ static void B_BuildBase_f (void) { assert(baseCurrent); assert(!baseCurrent->founded); assert(ccs.singleplayer); if (ccs.credits - BASE_COSTS > 0) { if (CL_NewBase(baseCurrent, newBasePos)) { Com_DPrintf("B_BuildBase_f: numBases: %i\n", gd.numBases); baseCurrent->idx = gd.numBases - 1; baseCurrent->founded = qtrue; baseCurrent->numAircraftInBase = 0; stats.basesBuild++; gd.mapAction = MA_NONE; CL_UpdateCredits(ccs.credits - BASE_COSTS); Q_strncpyz(baseCurrent->name, mn_base_title->string, sizeof(baseCurrent->name)); Q_strncpyz(messageBuffer, va(_("A new base has been built: %s."), mn_base_title->string), sizeof(messageBuffer)); MN_AddNewMessage(_("Base built"), messageBuffer, qfalse, MSG_CONSTRUCTION, NULL); Radar_Initialise(&(baseCurrent->radar), 0); AL_FillInContainment(); /* initial base equipment */ if (gd.numBases == 1) { int i, price = 0; equipDef_t *ed; for (i = 0, ed = csi.eds; i < csi.numEDs; i++, ed++) if (!Q_strncmp(curCampaign->equipment, ed->name, sizeof(curCampaign->equipment))) break; if (i != csi.numEDs) baseCurrent->storage = *ed; /* copied, including arrays! */ /* initial soldiers and their equipment */ if (cl_start_employees->value) { Cbuf_AddText("assign_initial;"); } else { const char *name = cl_initial_equipment->string; for (i = 0, ed = csi.eds; i < csi.numEDs; i++, ed++) if (!Q_strncmp(name, ed->name, MAX_VAR)) break; if (i == csi.numEDs) { Com_DPrintf("B_BuildBase_f: Initial Phalanx equipment %s not found.\n", name); } else { for (i = 0; i < csi.numODs; i++) baseCurrent->storage.num[i] += ed->num[i] / 5; } } /* pay for the initial equipment */ for (i = 0; i < csi.numODs; i++) price += baseCurrent->storage.num[i] * csi.ods[i].price; CL_UpdateCredits(ccs.credits - price); CL_GameTimeFast(); CL_GameTimeFast(); } Cbuf_AddText(va("mn_select_base %i;", baseCurrent->idx)); return; } } else { Q_strncpyz(messageBuffer, _("Not enough credits to set up a new base."), sizeof(messageBuffer)); MN_AddNewMessage(_("Base built"), messageBuffer, qfalse, MSG_CONSTRUCTION, NULL); } } /** * @brief Sets the baseStatus to BASE_NOT_USED * @param[in] base Which base should be resetted? * @sa CL_CampaignRemoveMission */ extern void B_BaseResetStatus (base_t* const base) { assert(base); base->baseStatus = BASE_NOT_USED; if (gd.mapAction == MA_BASEATTACK) gd.mapAction = MA_NONE; } /** * @brief Initiates an attack on a base. * @sa B_BaseAttack_f * @sa CL_CampaignAddMission * @param[in] base Which base is under attack? */ extern void B_BaseAttack (base_t* const base) { assert(base); base->baseStatus = BASE_UNDER_ATTACK; #if 0 /*TODO: run eventhandler for each building in base */ if (b->onAttack) Cbuf_AddText(va("%s %i", b->onAttack, b->id)); #endif } /** * @brief Initiates an attack on a base. * @sa B_BaseAttack */ static void B_BaseAttack_f (void) { int whichBaseID; if (Cmd_Argc() < 2) { Com_Printf("Usage: base_attack \n"); return; } whichBaseID = atoi(Cmd_Argv(1)); if (whichBaseID >= 0 && whichBaseID < gd.numBases) { B_BaseAttack(&gd.bases[whichBaseID]); } } /** * @brief Builds a base map for tactical combat. * @sa SV_AssembleMap * @sa B_BaseAttack * @note Do we need day and night maps here, too? Sure! * @todo Search a empty field and add a alien craft there, also add alien * spawn points around the craft, also some trees, etc. for their cover * @todo Add soldier spawn points, the best place is quarters. * @todo We need to get rid of the tunnels to nirvana. */ static void B_AssembleMap_f (void) { int row, col; char baseMapPart[1024]; building_t *entry; char maps[2024]; char coords[2048]; int setUnderAttack = 0, baseID = 0; base_t* base = baseCurrent; if (Cmd_Argc() < 2) Com_DPrintf("Usage: %s \n", Cmd_Argv(0)); else { if (Cmd_Argc() == 3) setUnderAttack = atoi(Cmd_Argv(2)); baseID = atoi(Cmd_Argv(1)); if (baseID < 0 || baseID >= gd.numBases) { Com_DPrintf("Invalid baseID: %i\n", baseID); return; } base = &gd.bases[baseID]; } if (!base) { Com_Printf("B_AssembleMap_f: No base to assemble\n"); return; } if (setUnderAttack) { base->baseStatus = BASE_UNDER_ATTACK; gd.mapAction = MA_BASEATTACK; Com_DPrintf("Set base %i under attack\n", base->idx); } /* reset menu text */ menuText[TEXT_STANDARD] = NULL; *maps = '\0'; *coords = '\0'; /* reset the used flag */ for (row = 0; row < BASE_SIZE; row++) for (col = 0; col < BASE_SIZE; col++) { if (base->map[row][col] != -1) { entry = B_GetBuildingByIdx(base, base->map[row][col]); entry->used = 0; } } /* TODO: If a building is still under construction, it will be assembled as a finished part */ /* otherwise we need mapparts for all the maps under construction */ for (row = 0; row < BASE_SIZE; row++) for (col = 0; col < BASE_SIZE; col++) { baseMapPart[0] = '\0'; if (base->map[row][col] != -1) { entry = B_GetBuildingByIdx(base, base->map[row][col]); /* basemaps with needs are not (like the images in B_DrawBase) two maps - but one */ /* this is why we check the used flag and continue if it was set already */ if (!entry->used && *entry->needs) { entry->used = 1; } else if (*entry->needs) { Com_DPrintf("B_AssembleMap_f: '%s' needs '%s' (used: %i)\n", entry->id, entry->needs, entry->used ); entry->used = 0; continue; } if (*entry->mapPart) Q_strncpyz(baseMapPart, va("b/%c/%s", base->mapChar, entry->mapPart), sizeof(baseMapPart)); else Com_Printf("B_AssembleMap_f: Error - map has no mapPart set. Building '%s'\n'", entry->id); } else Q_strncpyz(baseMapPart, va("b/%c/empty", base->mapChar), sizeof(baseMapPart)); if (*baseMapPart) { Q_strcat(maps, baseMapPart, sizeof(maps)); Q_strcat(maps, " ", sizeof(maps)); /* basetiles are 16 units in each direction */ Q_strcat(coords, va("%i %i ", col * 16, (BASE_SIZE - row - 1) * 16), sizeof(coords)); } } /* set maxlevel for base attacks to 5 */ map_maxlevel_base = 5; Cbuf_AddText(va("map \"%s\" \"%s\"\n", maps, coords)); } /** * @brief Cleans all bases but restart the base names * @sa CL_GameNew */ extern void B_NewBases (void) { /* reset bases */ int i; char title[MAX_VAR]; for (i = 0; i < MAX_BASES; i++) { Q_strncpyz(title, gd.bases[i].name, sizeof(title)); B_ClearBase(&gd.bases[i]); Q_strncpyz(gd.bases[i].name, title, sizeof(title)); } } /** * @brief Builds a random base * * call B_AssembleMap with a random base over script command 'base_assemble_rand' */ static void B_AssembleRandomBase_f (void) { int setUnderAttack = 0; int randomBase = rand() % gd.numBases; if (Cmd_Argc() < 2) Com_DPrintf("Usage: %s \n", Cmd_Argv(0)); else setUnderAttack = atoi(Cmd_Argv(1)); if (!gd.bases[randomBase].founded) { Com_Printf("Base with id %i was not founded or already destroyed\n", randomBase); return; } Cbuf_AddText(va("base_assemble %i %i\n", randomBase, setUnderAttack)); } /** * @brief Just lists all buildings with their data * * Just for debugging purposes - not needed in game * @todo To be extended for load/save purposes */ static void B_BuildingList_f (void) { int i, j, k; base_t *base; building_t *building; /*maybe someone call this command before the buildings are parsed?? */ if (!baseCurrent) { Com_Printf("No base selected\n"); return; } for (i = 0, base = gd.bases; i < gd.numBases; i++, base++) { if (base->founded == qfalse) continue; building = &gd.buildings[base->idx][i]; Com_Printf("\nBase id %i: %s\n", i, base->name); for (j = 0; j < gd.numBuildings[base->idx]; j++) { Com_Printf("...Building: %s #%i - id: %i\n", building->id, B_GetNumberOfBuildingsInBaseByTypeIDX(baseCurrent->idx, building->buildingType), building->idx); Com_Printf("...image: %s\n", building->image); Com_Printf(".....Status:\n"); for (k = 0; k < BASE_SIZE * BASE_SIZE; k++) { if (k > 1 && k % BASE_SIZE == 0) Com_Printf("\n"); Com_Printf("%i ", building->buildingStatus); if (!building->buildingStatus) break; } Com_Printf("\n"); building++; } } } /** * @brief Just lists all bases with their data * * Just for debugging purposes - not needed in game * @todo To be extended for load/save purposes */ static void B_BaseList_f (void) { int i, row, col, j; base_t *base; for (i = 0, base = gd.bases; i < MAX_BASES; i++, base++) { Com_Printf("Base id %i\n", base->idx); Com_Printf("Base title %s\n", base->name); Com_Printf("Base founded %i\n", base->founded); Com_Printf("Base numAircraftInBase %i\n", base->numAircraftInBase); Com_Printf("Base sensorWidth %i\n", base->radar.range); Com_Printf("Base numSensoredAircraft %i\n", base->radar.numUfos); Com_Printf("Base aircraft %i\n", base->numAircraftInBase); for (j = 0; j < base->numAircraftInBase; j++) { Com_Printf("Base aircraft-team %i\n", *(base->aircraft[j].teamSize)); } Com_Printf("Base pos %f:%f\n", base->pos[0], base->pos[1]); Com_Printf("Base map:\n"); for (row = 0; row < BASE_SIZE; row++) { if (row) Com_Printf("\n"); for (col = 0; col < BASE_SIZE; col++) Com_Printf("%i ", base->map[row][col]); } Com_Printf("\n"); } } /** * @brief Sets the title of the base. */ static void B_SetBaseTitle_f (void) { Com_DPrintf("B_SetBaseTitle_f: #bases: %i\n", gd.numBases); if (gd.numBases < MAX_BASES) Cvar_Set("mn_base_title", gd.bases[gd.numBases].name); else { MN_AddNewMessage(_("Notice"), _("You've reached the base limit."), qfalse, MSG_STANDARD, NULL); MN_PopMenu(qfalse); /* remove the new base popup */ } } /** * @brief Creates console command to change the name of a base. * Copies the value of the cvar mn_base_title over as the name of the * current selected base */ static void B_ChangeBaseName_f (void) { /* maybe called without base initialized or active */ if (!baseCurrent) return; Q_strncpyz(baseCurrent->name, Cvar_VariableString("mn_base_title"), sizeof(baseCurrent->name)); } /** * @brief Checks whether the building menu or the pedia entry should be called when baseCurrent->buildingCurrent is set */ static void B_BuildingOpen_f (void) { if (baseCurrent && baseCurrent->buildingCurrent) { if (baseCurrent->buildingCurrent->buildingStatus != B_STATUS_WORKING) { UP_OpenWith(baseCurrent->buildingCurrent->pedia); } else switch (baseCurrent->buildingCurrent->buildingType) { case B_LAB: MN_PushMenu("research"); break; case B_HOSPITAL: MN_PushMenu("hospital"); break; case B_ALIEN_CONTAINMENT: MN_PushMenu("aliencont"); break; case B_QUARTERS: MN_PushMenu("employees"); break; case B_WORKSHOP: MN_PushMenu("production"); break; case B_HANGAR: if (baseCurrent->numAircraftInBase) MN_PushMenu("aircraft"); else { MN_PushMenu("buyaircraft"); /* transfer is only possible when there are at least two bases */ if (gd.numBases > 1) MN_Popup(_("Note"), _("No aircraft in this base - You first have to purchase or transfer an aircraft\n")); else MN_Popup(_("Note"), _("No aircraft in this base - You first have to purchase an aircraft\n")); } break; default: UP_OpenWith(baseCurrent->buildingCurrent->pedia); break; } } else { Com_Printf("Usage: Only call me from baseview\n"); } } /** * @brief This function checks whether a user build the max allowed amount of bases * if yes, the MN_PopMenu will pop the base build menu from the stack */ static void B_CheckMaxBases_f (void) { if (gd.numBases >= MAX_BASES) { MN_PopMenu(qfalse); } } /** * @brief Resets console commands. * @sa MN_ResetMenus */ extern void B_ResetBaseManagement (void) { Com_DPrintf("Reset basemanagement\n"); /* add commands and cvars */ Cmd_AddCommand("mn_prev_base", B_PrevBase_f, NULL); Cmd_AddCommand("mn_next_base", B_NextBase_f, NULL); Cmd_AddCommand("mn_select_base", B_SelectBase_f, NULL); Cmd_AddCommand("mn_build_base", B_BuildBase_f, NULL); Cmd_AddCommand("new_building", B_NewBuildingFromList_f, NULL); Cmd_AddCommand("set_building", B_SetBuilding_f, NULL); Cmd_AddCommand("mn_setbasetitle", B_SetBaseTitle_f, NULL); Cmd_AddCommand("bases_check_max", B_CheckMaxBases_f, NULL); Cmd_AddCommand("rename_base", B_RenameBase_f, NULL); Cmd_AddCommand("base_attack", B_BaseAttack_f, NULL); Cmd_AddCommand("base_changename", B_ChangeBaseName_f, "Called after editing the cvar base name"); Cmd_AddCommand("base_init", B_BaseInit_f, NULL); Cmd_AddCommand("base_assemble", B_AssembleMap_f, "Called to assemble the current selected base"); Cmd_AddCommand("base_assemble_rand", B_AssembleRandomBase_f, NULL); Cmd_AddCommand("building_open", B_BuildingOpen_f, NULL); Cmd_AddCommand("building_init", B_BuildingInit, NULL); Cmd_AddCommand("building_status", B_BuildingStatus, NULL); Cmd_AddCommand("building_destroy", B_BuildingDestroy_f, "Function to destroy a buliding (select via right click in baseview first)"); Cmd_AddCommand("buildinginfo_click", B_BuildingInfoClick_f, NULL); Cmd_AddCommand("buildings_click", B_BuildingClick_f, NULL); Cmd_AddCommand("reset_building_current", B_ResetBuildingCurrent_f, NULL); Cmd_AddCommand("baselist", B_BaseList_f, NULL); Cmd_AddCommand("buildinglist", B_BuildingList_f, NULL); Cmd_AddCommand("pack_initial", B_PackInitialEquipment_f, NULL); Cmd_AddCommand("assign_initial", B_AssignInitial_f, NULL); mn_base_count = Cvar_Get("mn_base_count", "0", 0, NULL); } /** * @brief Counts the number of bases. * @return The number of founded bases. */ int B_GetCount (void) { int i, cnt = 0; for (i = 0; i < MAX_BASES; i++) { if (!gd.bases[i].founded) continue; cnt++; } return cnt; } /** * @brief Updates base data * @sa CL_CampaignRun * @note called every "day" */ void B_UpdateBaseData (void) { building_t *b = NULL; int i, j; int newBuilding = 0, new; for (i = 0; i < MAX_BASES; i++) { if (!gd.bases[i].founded) continue; for (j = 0; j < gd.numBuildings[i]; j++) { b = &gd.buildings[i][j]; if (!b) continue; new = B_CheckBuildingConstruction(b, i); newBuilding += new; if (new) { Com_sprintf(messageBuffer, MAX_MESSAGE_TEXT, _("Construction of %s building finished in base %s."), b->name, gd.bases[i].name); MN_AddNewMessage(_("Building finished"), messageBuffer, qfalse, MSG_CONSTRUCTION, NULL); } } } } /** * @brief Checks whether the construction of a building is finished. * * Calls the onConstruct functions and assign workers, too. */ int B_CheckBuildingConstruction (building_t * building, int base_idx) { int newBuilding = 0; if (building->buildingStatus == B_STATUS_UNDER_CONSTRUCTION) { if (building->timeStart && (building->timeStart + building->buildTime) <= ccs.date.day) { B_UpdateBaseBuildingStatus(building, &gd.bases[base_idx], B_STATUS_WORKING); if (*building->onConstruct) { gd.bases[base_idx].buildingCurrent = building; Com_DPrintf("B_CheckBuildingConstruction: %s %i;\n", building->onConstruct, base_idx); Cbuf_AddText(va("%s %i;", building->onConstruct, base_idx)); } newBuilding++; } } if (newBuilding) /*update the building-list */ B_BuildingInit(); return newBuilding; } /** * @brief Selects a base by its index. * @param[in] base_idx Index of the base - see gd.bases array and gd.numBases */ base_t *B_GetBase (int base_idx) { int i; for (i = 0; i < MAX_BASES; i++) { if (gd.bases[i].idx == base_idx) return &gd.bases[i]; } return NULL; } /** * @brief Counts the number of soldiers in a current aircraft/team. */ int B_GetNumOnTeam (void) { if (!baseCurrent || baseCurrent->aircraftCurrent < 0) return 0; return baseCurrent->teamNum[baseCurrent->aircraftCurrent]; } /** * @brief Returns the aircraft pointer from the given base and perform some sanity checks * @param[in] base Base the to get the aircraft from - may not be null * @param[in] index Base aircraft index */ aircraft_t *B_GetAircraftFromBaseByIndex (base_t* base, int index) { assert(base); if (index < base->numAircraftInBase) { return &base->aircraft[index]; } else { Com_DPrintf("B_GetAircraftFromBaseByIndex: error: index bigger then number of aircrafts in this base\n"); return NULL; } } /** * @brief Do anything when dropship returns to base * @param[in] aircraft Returning aircraft. * @param[in] base Base, where aircraft lands. * @note Place here any stuff, which should be called * @note when Drophip returns to base. * @sa CL_CampaignRunAircraft */ void CL_DropshipReturned (base_t* base, aircraft_t* aircraft) { /* Don't call cargo functions if aircraft is not a transporter. */ if (aircraft->type != AIRCRAFT_TRANSPORTER) return; baseCurrent = base; AL_AddAliens(); /**< Add aliens to Alien Containment. */ AL_CountAll(); /**< Count all alive aliens. */ CL_SellOrAddItems(); /**< Sell collected items or add them to storage. */ RS_MarkResearchable(qfalse); /**< Mark new technologies researchable. */ /* Now empty alien/item cargo just in case. */ memset(aircraft->aliencargo, 0, sizeof(aircraft->aliencargo)); memset(aircraft->itemcargo, 0, sizeof(aircraft->itemcargo)); } /** * @brief Check if the item has been collected (i.e it is in the storage .. and currently market) in the given base. * @param[in] item_idx The index of the item in the item-list. * @param[in] base The base to search in. * @return amount Number of available items in base * @note Formerly known as RS_ItemInBase. */ int B_ItemInBase (int item_idx, base_t *base) { equipDef_t *ed = NULL; if (!base) return -1; ed = &base->storage; if (!ed) return -1; /* Com_DPrintf("B_ItemInBase: DEBUG idx %s\n", csi.ods[item_idx].id); */ return ed->num[item_idx]; } /** * @brief Save callback for savegames * @sa B_Load * @sa SAV_GameSave */ extern qboolean B_Save (sizebuf_t* sb, void* data) { int i, k, l; base_t *b, *transferBase; aircraft_t *aircraft; building_t *building; MSG_WriteShort(sb, gd.numAircraft); MSG_WriteByte(sb, gd.numBases); for (i = 0; i < gd.numBases; i++) { b = &gd.bases[i]; MSG_WriteString(sb, b->name); MSG_WriteChar(sb, b->mapChar); MSG_WritePos(sb, b->pos); MSG_WriteByte(sb, b->founded); MSG_WriteByte(sb, b->hasHangar); MSG_WriteByte(sb, b->hasLab); MSG_WriteByte(sb, b->hasHospital); MSG_WriteByte(sb, b->hasAlienCont); MSG_WriteByte(sb, b->hasStorage); MSG_WriteByte(sb, b->hasQuarters); MSG_WriteByte(sb, b->hasWorkshop); for (k = 0; k < BASE_SIZE; k++) for (l = 0; l < BASE_SIZE; l++) { MSG_WriteShort(sb, b->map[k][l]); MSG_WriteShort(sb, b->posX[k][l]); MSG_WriteShort(sb, b->posY[k][l]); } for (k = 0; k < MAX_BUILDINGS; k++) { building = &gd.buildings[i][k]; MSG_WriteByte(sb, building->type_idx); MSG_WriteByte(sb, building->buildingStatus); MSG_WriteLong(sb, building->timeStart); MSG_WriteLong(sb, building->buildTime); MSG_Write2Pos(sb, building->pos); } MSG_WriteShort(sb, gd.numBuildings[i]); MSG_WriteByte(sb, b->condition); MSG_WriteByte(sb, b->baseStatus); MSG_WriteShort(sb, b->aircraftCurrent); /* might be -1 */ MSG_WriteByte(sb, b->numAircraftInBase); for (k = 0; k < b->numAircraftInBase; k++) { aircraft = &b->aircraft[k]; MSG_WriteString(sb, aircraft->id); MSG_WriteShort(sb, aircraft->idx); MSG_WriteByte(sb, aircraft->status); MSG_WriteFloat(sb, aircraft->speed); MSG_WriteLong(sb, aircraft->fuel); MSG_WriteLong(sb, aircraft->fuelSize); transferBase = (base_t*)aircraft->transferBase; MSG_WriteShort(sb, transferBase ? transferBase->idx : -1); MSG_WritePos(sb, aircraft->pos); MSG_WriteShort(sb, aircraft->time); MSG_WriteShort(sb, aircraft->point); MSG_WriteString(sb, aircraft->weapon_string); MSG_WriteString(sb, aircraft->shield_string); MSG_WriteString(sb, aircraft->item_string); for (l = 0; l < MAX_ACTIVETEAM; l++) MSG_WriteShort(sb, aircraft->teamIdxs[l]); MSG_WriteShort(sb, aircraft->numUpgrades); MSG_WriteShort(sb, aircraft->radar.range); MSG_WriteShort(sb, aircraft->route.n); MSG_WriteFloat(sb, aircraft->route.dist); for (l = 0; l < aircraft->route.n; l++) MSG_Write2Pos(sb, aircraft->route.p[l]); MSG_WriteShort(sb, aircraft->alientypes); MSG_WriteShort(sb, aircraft->itemtypes); /* Save only needed if aircraft returns from a mission. */ if (aircraft->status == AIR_RETURNING) { /* aliencargo */ for (l = 0; l < aircraft->alientypes; l++) { MSG_WriteString(sb, aircraft->aliencargo[l].alientype); MSG_WriteShort(sb, aircraft->aliencargo[l].amount_alive); MSG_WriteShort(sb, aircraft->aliencargo[l].amount_dead); } /* itemcargo */ for (l = 0; l < aircraft->itemtypes; l++) { MSG_WriteShort(sb, aircraft->itemcargo[l].idx); MSG_WriteShort(sb, aircraft->itemcargo[l].amount); } } else if (aircraft->status == AIR_TRANSPORT) { MSG_WriteByte(sb, gd.alltransfers[aircraft->idx].type); MSG_WriteByte(sb, gd.alltransfers[aircraft->idx].destBase); for (l = 0; l < MAX_OBJDEFS; l++) MSG_WriteShort(sb, gd.alltransfers[aircraft->idx].itemAmount[l]); for (l = 0; l < MAX_CARGO; l++) MSG_WriteShort(sb, gd.alltransfers[aircraft->idx].alienLiveAmount[l]); for (l = 0; l < MAX_CARGO; l++) MSG_WriteShort(sb, gd.alltransfers[aircraft->idx].alienBodyAmount[l]); for (l = 0; l < MAX_EMPLOYEES; l++) MSG_WriteShort(sb, gd.alltransfers[aircraft->idx].employees[l]); } } MSG_WriteShort(sb, MAX_AIRCRAFT); for (k = 0; k < MAX_AIRCRAFT; k++) MSG_WriteByte(sb, b->teamNum[k]); MSG_WriteByte(sb, b->equipType); /* store equipment */ for (k = 0; k < MAX_OBJDEFS; k++) { MSG_WriteShort(sb, b->storage.num[k]); MSG_WriteByte(sb, b->storage.num_loose[k]); } MSG_WriteShort(sb, b->radar.range); /* Alien Containment. */ MSG_WriteByte(sb, numTeamDesc); for (k = 0; k < numTeamDesc; k++) { if (!teamDesc[k].alien) continue; MSG_WriteByte(sb, b->alienscont[k].idx); MSG_WriteString(sb, b->alienscont[k].alientype); MSG_WriteShort(sb, b->alienscont[k].amount_alive); MSG_WriteShort(sb, b->alienscont[k].amount_dead); MSG_WriteShort(sb, b->alienscont[k].techIdx); } } return qtrue; } /** * @brief Load callback for savegames * @sa B_Save * @sa SAV_GameLoad */ extern qboolean B_Load (sizebuf_t* sb, void* data) { int i, bases, k, l; base_t *b; aircraft_t *aircraft; building_t *building; gd.numAircraft = MSG_ReadShort(sb); bases = MSG_ReadByte(sb); for (i = 0; i < bases; i++) { b = &gd.bases[i]; Q_strncpyz(b->name, MSG_ReadStringRaw(sb), sizeof(b->name)); b->mapChar = MSG_ReadChar(sb); MSG_ReadPos(sb, b->pos); b->founded = MSG_ReadByte(sb); b->hasHangar = MSG_ReadByte(sb); b->hasLab = MSG_ReadByte(sb); b->hasHospital = MSG_ReadByte(sb); b->hasAlienCont = MSG_ReadByte(sb); b->hasStorage = MSG_ReadByte(sb); b->hasQuarters = MSG_ReadByte(sb); b->hasWorkshop = MSG_ReadByte(sb); for (k = 0; k < BASE_SIZE; k++) for (l = 0; l < BASE_SIZE; l++) { b->map[k][l] = MSG_ReadShort(sb); b->posX[k][l] = MSG_ReadShort(sb); b->posY[k][l] = MSG_ReadShort(sb); } for (k = 0; k < MAX_BUILDINGS; k++) { building = &gd.buildings[i][k]; memcpy(building, &gd.buildingTypes[MSG_ReadByte(sb)], sizeof(building_t)); building->idx = k; building->base_idx = i; building->buildingStatus = MSG_ReadByte(sb); building->timeStart = MSG_ReadLong(sb); building->buildTime = MSG_ReadLong(sb); MSG_Read2Pos(sb, building->pos); } gd.numBuildings[i] = MSG_ReadShort(sb); b->condition = MSG_ReadByte(sb); b->baseStatus = MSG_ReadByte(sb); b->aircraftCurrent = MSG_ReadShort(sb); b->numAircraftInBase = MSG_ReadByte(sb); for (k = 0; k < b->numAircraftInBase; k++) { aircraft = CL_GetAircraft(MSG_ReadString(sb)); if (!aircraft) return qfalse; memcpy(&b->aircraft[k], aircraft, sizeof(aircraft_t)); aircraft = &b->aircraft[k]; aircraft->idx = MSG_ReadShort(sb); aircraft->homebase = b; /* for saving and loading a base */ aircraft->idxBase = b->idx; /* this is the aircraft array id in current base */ aircraft->idxInBase = k; /* link the teamSize pointer in */ aircraft->teamSize = &b->teamNum[k]; aircraft->status = MSG_ReadByte(sb); aircraft->speed = MSG_ReadFloat(sb); aircraft->fuel = MSG_ReadLong(sb); aircraft->fuelSize = MSG_ReadLong(sb); l = MSG_ReadShort(sb); if (l >= 0) aircraft->transferBase = &gd.bases[l]; MSG_ReadPos(sb, aircraft->pos); aircraft->time = MSG_ReadShort(sb); aircraft->point = MSG_ReadShort(sb); Q_strncpyz(aircraft->weapon_string, MSG_ReadString(sb), sizeof(aircraft->weapon_string)); Q_strncpyz(aircraft->shield_string, MSG_ReadString(sb), sizeof(aircraft->shield_string)); Q_strncpyz(aircraft->item_string, MSG_ReadString(sb), sizeof(aircraft->item_string)); for (l = 0; l < MAX_ACTIVETEAM; l++) aircraft->teamIdxs[l] = MSG_ReadShort(sb); aircraft->numUpgrades = MSG_ReadShort(sb); aircraft->radar.range = MSG_ReadShort(sb); aircraft->route.n = MSG_ReadShort(sb); aircraft->route.dist = MSG_ReadFloat(sb); for (l = 0; l < aircraft->route.n; l++) MSG_Read2Pos(sb, aircraft->route.p[l]); /* Load only needed if aircraft returns from a mission. */ aircraft->alientypes = MSG_ReadShort(sb); aircraft->itemtypes = MSG_ReadShort(sb); if (aircraft->status == AIR_RETURNING) { /* aliencargo */ for (l = 0; l < aircraft->alientypes; l++) { Q_strncpyz(aircraft->aliencargo[l].alientype, MSG_ReadString(sb), sizeof(aircraft->aliencargo[l].alientype)); aircraft->aliencargo[l].amount_alive = MSG_ReadShort(sb); aircraft->aliencargo[l].amount_dead = MSG_ReadShort(sb); } /* itemcargo */ for (l = 0; l < aircraft->itemtypes; l++) { aircraft->itemcargo[l].idx = MSG_ReadShort(sb); aircraft->itemcargo[l].amount = MSG_ReadShort(sb); } } else if (aircraft->status == AIR_TRANSPORT) { gd.alltransfers[aircraft->idx].type = MSG_ReadByte(sb); gd.alltransfers[aircraft->idx].destBase = MSG_ReadByte(sb); for (l = 0; l < MAX_OBJDEFS; l++) gd.alltransfers[aircraft->idx].itemAmount[l] = MSG_ReadShort(sb); for (l = 0; l < MAX_CARGO; l++) gd.alltransfers[aircraft->idx].alienLiveAmount[l] = MSG_ReadShort(sb); for (l = 0; l < MAX_CARGO; l++) gd.alltransfers[aircraft->idx].alienBodyAmount[l] = MSG_ReadShort(sb); for (l = 0; l < MAX_EMPLOYEES; l++) gd.alltransfers[aircraft->idx].employees[l] = MSG_ReadShort(sb); } #if 0 struct actMis_s* mission; /**< The mission the aircraft is moving to */ #endif } l = MSG_ReadShort(sb); for (k = 0; k < l; k++) b->teamNum[k] = MSG_ReadByte(sb); b->equipType = MSG_ReadByte(sb); /* read equipment */ for (k = 0; k < MAX_OBJDEFS; k++) { b->storage.num[k] = MSG_ReadShort(sb); b->storage.num_loose[k] = MSG_ReadByte(sb); } b->radar.range = MSG_ReadShort(sb); /* Alien Containment. */ l = MSG_ReadByte(sb); for (k = 0; k < l; k++) { b->alienscont[k].idx = MSG_ReadByte(sb); Q_strncpyz(b->alienscont[k].alientype, MSG_ReadString(sb), sizeof(b->alienscont[k].alientype)); b->alienscont[k].amount_alive = MSG_ReadShort(sb); b->alienscont[k].amount_dead = MSG_ReadShort(sb); b->alienscont[k].techIdx = MSG_ReadShort(sb); } /* clear the mess of stray loaded pointers */ memset(&b->equipByBuyType, 0, sizeof(inventory_t)); /* some functions needs the baseCurrent pointer set */ baseCurrent = b; /* fix aircraft tech pointers */ for (k = 0, aircraft = b->aircraft; k < b->numAircraftInBase; k++, aircraft++) { aircraft->shield = RS_GetTechByID(aircraft->shield_string); aircraft->weapon = RS_GetTechByID(aircraft->weapon_string); aircraft->item = RS_GetTechByID(aircraft->item_string); } /* initalize team to null */ for (k = 0; k < MAX_ACTIVETEAM; k++) b->curTeam[k] = NULL; } gd.numBases = bases; return qtrue; }