warzone2100/src/structure.c

7985 lines
214 KiB
C

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2007 Warzone Resurrection Project
Warzone 2100 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.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*!
* \file structure.c
*
* Store Structure stats.
* WARNING!!!!!!
* By the picking of these code-bombs, something wicked this way comes. This
* file is almost as evil as hci.c
*/
#include <string.h>
#include "lib/framework/frame.h"
#include "lib/framework/strres.h"
#include "lib/framework/frameresource.h"
#include "objects.h"
#include "ai.h"
#include "map.h"
#include "lib/gamelib/gtime.h"
#include "visibility.h"
#include "structure.h"
#include "research.h"
#include "hci.h"
#include "power.h"
#include "miscimd.h"
#include "effects.h"
#include "combat.h"
#include "lib/sound/audio.h"
#include "lib/sound/audio_id.h"
#include "stats.h"
#include "lib/framework/math-help.h"
#include "edit3d.h"
#include "anim_id.h"
#include "lib/gamelib/anim.h"
#include "display3d.h"
#include "geometry.h"
// FIXME Direct iVis implementation include!
#include "lib/ivis_opengl/piematrix.h"
#include "lib/ivis_common/piefixedpoint.h"
#include "order.h"
#include "droid.h"
#include "lib/script/script.h"
#include "scripttabs.h"
#include "scriptcb.h"
#include "formationdef.h"
#include "formation.h"
#include "text.h"
#include "action.h"
#include "group.h"
#include "transporter.h"
#include "fpath.h"
#include "mission.h"
#include "levels.h"
#include "console.h"
#include "cmddroid.h"
#include "feature.h"
#include "mapgrid.h"
#include "projectile.h"
#include "cluster.h"
#include "intdisplay.h"
#include "display.h"
#include "difficulty.h"
#include "scriptextern.h"
#include "keymap.h"
#include "game.h"
#include "advvis.h"
#include "multiplay.h"
#include "lib/netplay/netplay.h"
#include "multigifts.h"
#include "loop.h"
#include "scores.h"
#include "gateway.h"
// Possible types of wall to build
#define WALL_HORIZ 0
#define WALL_VERT 1
#define WALL_CORNER 2
#define STR_RECOIL_TIME (GAME_TICKS_PER_SEC/4)
// Maximum Distance allowed between a friendly structure and an assembly point.
#define ASSEMBLY_RANGE (10*TILE_UNITS)
//Maximium slope of the terrin for building a structure
#define MAX_INCLINE 50//80//40
/* percentage of repair points cost to player power */
#define REPAIR_FACILITY_COST_PERCENT 10
/* droid construction smoke cloud constants */
#define DROID_CONSTRUCTION_SMOKE_OFFSET 30
#define DROID_CONSTRUCTION_SMOKE_HEIGHT 20
//used to calculate how often to increase the resistance level of a structure
#define RESISTANCE_INTERVAL 2000
//used to calculate the time required for rearming
#define REARM_FACTOR 10
//used to calculate the time required for repairing
#define VTOL_REPAIR_FACTOR 10
//holds the IMD pointers for the modules - ONLY VALID for player0
iIMDShape * factoryModuleIMDs[NUM_FACTORY_MODULES][NUM_FACMOD_TYPES];
iIMDShape * researchModuleIMDs[NUM_RESEARCH_MODULES];
iIMDShape * powerModuleIMDs[NUM_POWER_MODULES];
//Value is stored for easy access to this structure stat
UDWORD factoryModuleStat;
UDWORD powerModuleStat;
UDWORD researchModuleStat;
//holder for all StructureStats
STRUCTURE_STATS *asStructureStats;
UDWORD numStructureStats;
//holder for the limits of each structure per map
STRUCTURE_LIMITS *asStructLimits[MAX_PLAYERS];
//holds the upgrades attained through research for structure stats
STRUCTURE_UPGRADE asStructureUpgrade[MAX_PLAYERS];
WALLDEFENCE_UPGRADE asWallDefenceUpgrade[MAX_PLAYERS];
//holds the upgrades for the functionality of structures through research
RESEARCH_UPGRADE asResearchUpgrade[MAX_PLAYERS];
POWER_UPGRADE asPowerUpgrade[MAX_PLAYERS];
REPAIR_FACILITY_UPGRADE asRepairFacUpgrade[MAX_PLAYERS];
PRODUCTION_UPGRADE asProductionUpgrade[MAX_PLAYERS][NUM_FACTORY_TYPES];
REARM_UPGRADE asReArmUpgrade[MAX_PLAYERS];
//used to hold the modifiers cross refd by weapon effect and structureStrength
STRUCTSTRENGTH_MODIFIER asStructStrengthModifier[WE_NUMEFFECTS][NUM_STRUCT_STRENGTH];
//specifies which numbers have been allocated for the assembly points for the factories
UBYTE factoryNumFlag[MAX_PLAYERS][NUM_FLAG_TYPES];
// the number of different (types of) droids that can be put into a production run
#define MAX_IN_RUN 9
//the list of what to build - only for selectedPlayer
PRODUCTION_RUN asProductionRun[NUM_FACTORY_TYPES][MAX_FACTORY][MAX_PROD_RUN];
//stores which player the production list has been set up for
SBYTE productionPlayer;
/* destroy building construction droid stat pointer */
static STRUCTURE_STATS *g_psStatDestroyStruct = NULL;
// the structure that was last hit
STRUCTURE *psLastStructHit;
//flag for drawing radar
static UBYTE hqExists[MAX_PLAYERS];
//flag for drawing all sat uplink sees
static UBYTE satUplinkExists[MAX_PLAYERS];
//flag for when the player has one built - either completely or partially
static UBYTE lasSatExists[MAX_PLAYERS];
static BOOL setFunctionality(STRUCTURE* psBuilding, STRUCTURE_TYPE functionType);
static void setFlagPositionInc(FUNCTIONALITY* pFunctionality, UDWORD player, UBYTE factoryType);
static void informPowerGen(STRUCTURE *psStruct);
static BOOL electronicReward(STRUCTURE *psStructure, UBYTE attackPlayer);
static void factoryReward(UBYTE losingPlayer, UBYTE rewardPlayer);
static void repairFacilityReward(UBYTE losingPlayer, UBYTE rewardPlayer);
static void findAssemblyPointPosition(UDWORD *pX, UDWORD *pY, UDWORD player);
static void removeStructFromMap(STRUCTURE *psStruct);
static void structUpdateRecoil( STRUCTURE *psStruct );
static void resetResistanceLag(STRUCTURE *psBuilding);
static void revealAll(UBYTE player);
static void cbNewDroid(STRUCTURE *psFactory, DROID *psDroid);
// last time the maximum units message was displayed
static UDWORD lastMaxUnitMessage;
#define MAX_UNIT_MESSAGE_PAUSE 20000
/* New function from Alex M */
/* Tells you if a point is inside the footprint of a building */
BOOL ptInStructure(STRUCTURE *psStruct, UDWORD x, UDWORD y)
{
UDWORD tlX, tlY, brX, brY;
UDWORD width, height;
CHECK_STRUCTURE(psStruct);
width = (psStruct->pStructureType->baseWidth * TILE_UNITS);
height = (psStruct->pStructureType->baseBreadth * TILE_UNITS);
tlX = psStruct->pos.x - (width/2);
tlY = psStruct->pos.y - (height/2);
brX = psStruct->pos.x + (width/2);
brY = psStruct->pos.y + (height/2);
if (x > tlX && x < brX && y > tlY && y < brY)
return(true);
return(false);
}
/*
Check to see if the stats is some kind of expansion module
... this replaces the thousands of occurance that is spread through out the code
... There were a couple of places where it skipping around a routine if the stat was a expansion module
(loadSaveStructureV7 & 9) this code seemed suspect, and to clarify it we replaced it with the routine below
... the module stuff seemed to work though ... TJC (& AB) 8-DEC-98
*/
BOOL IsStatExpansionModule(STRUCTURE_STATS *psStats)
{
// If the stat is any of the 3 expansion types ... then return true
if( psStats->type == REF_POWER_MODULE ||
psStats->type == REF_FACTORY_MODULE ||
psStats->type == REF_RESEARCH_MODULE )
{
return true;
}
else
{
return false;
}
}
void structureInitVars(void)
{
int i, j;
for(i=0; i<NUM_FACTORY_MODULES ; i++) {
factoryModuleIMDs[i][0] = NULL;
factoryModuleIMDs[i][1] = NULL;
}
for(i=0; i<NUM_RESEARCH_MODULES ; i++) {
researchModuleIMDs[i] = NULL;
}
for(i=0; i<NUM_POWER_MODULES ; i++) {
powerModuleIMDs[i] = NULL;
}
asStructureStats = NULL;
numStructureStats = 0;
factoryModuleStat = 0;
powerModuleStat = 0;
researchModuleStat = 0;
lastMaxUnitMessage = 0;
for (i = 0; i < MAX_PLAYERS; i++)
{
asStructLimits[i] = NULL;
for (j = 0; j < NUM_FLAG_TYPES; j++)
{
factoryNumFlag[i][j] = 0;
}
}
for (i = 0; i < MAX_PLAYERS; i++)
{
hqExists[i] = false;
satUplinkExists[i] = false;
lasSatExists[i] = false;
}
//initialise the selectedPlayer's production run
memset(&asProductionRun, 0, sizeof(PRODUCTION_RUN) * NUM_FACTORY_TYPES *
MAX_FACTORY * MAX_PROD_RUN);
//set up at beginning of game which player will have a production list
productionPlayer = (SBYTE)selectedPlayer;
}
/*Initialise the production list and set up the production player*/
void changeProductionPlayer(UBYTE player)
{
//clear the production run
memset(&asProductionRun, 0, sizeof(PRODUCTION_RUN) * NUM_FACTORY_TYPES *
MAX_FACTORY * MAX_PROD_RUN);
//set this player to have the production list
productionPlayer = player;
}
/*initialises the flag before a new data set is loaded up*/
void initFactoryNumFlag(void)
{
UDWORD i, j;
for(i=0; i< MAX_PLAYERS; i++)
{
//initialise the flag
for (j=0; j < NUM_FLAG_TYPES; j++)
{
factoryNumFlag[i][j] = (UBYTE)0;
}
}
}
//called at start of missions
void resetFactoryNumFlag(void)
{
STRUCTURE* psStruct;
uint8_t mask = 0;
unsigned int i;
for(i = 0; i < MAX_PLAYERS; i++)
{
//look throu the list of structures to see which have been used
for (psStruct = apsStructLists[i]; psStruct != NULL; psStruct = psStruct->psNext)
{
if (psStruct->pStructureType->type == REF_FACTORY
|| psStruct->pStructureType->type == REF_CYBORG_FACTORY
|| psStruct->pStructureType->type == REF_VTOL_FACTORY)
{
FACTORY* psFactory = &psStruct->pFunctionality->factory;
if (psFactory->psAssemblyPoint)
{
mask = (1 << psFactory->psAssemblyPoint->factoryInc);
}
}
if (psStruct->pStructureType->type == REF_FACTORY)
factoryNumFlag[i][FACTORY_FLAG] |= mask;
else if (psStruct->pStructureType->type == REF_CYBORG_FACTORY)
factoryNumFlag[i][CYBORG_FLAG] |= mask;
else if (psStruct->pStructureType->type == REF_VTOL_FACTORY)
factoryNumFlag[i][VTOL_FLAG] |= mask;
}
}
}
static const struct
{
const char* typeName;
STRUCTURE_TYPE type;
} structureTypeNames[] =
{
{ "HQ", REF_HQ },
{ "FACTORY", REF_FACTORY },
{ "FACTORY MODULE", REF_FACTORY_MODULE },
{ "RESEARCH", REF_RESEARCH },
{ "RESEARCH MODULE", REF_RESEARCH_MODULE },
{ "POWER GENERATOR", REF_POWER_GEN },
{ "POWER MODULE", REF_POWER_MODULE },
{ "RESOURCE EXTRACTOR", REF_RESOURCE_EXTRACTOR },
{ "DEFENSE", REF_DEFENSE },
{ "WALL", REF_WALL },
{ "CORNER WALL", REF_WALLCORNER },
{ "REPAIR FACILITY", REF_REPAIR_FACILITY },
{ "COMMAND RELAY", REF_COMMAND_CONTROL },
{ "DEMOLISH", REF_DEMOLISH },
{ "CYBORG FACTORY", REF_CYBORG_FACTORY },
{ "VTOL FACTORY", REF_VTOL_FACTORY },
{ "LAB", REF_LAB },
{ "DOOR", REF_BLASTDOOR },
{ "REARM PAD", REF_REARM_PAD },
{ "MISSILE SILO", REF_MISSILE_SILO },
{ "SAT UPLINK", REF_SAT_UPLINK },
};
static STRUCTURE_TYPE structureType(const char* typeName)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(structureTypeNames); ++i)
{
if (strcmp(typeName, structureTypeNames[i].typeName) == 0)
{
return structureTypeNames[i].type;
}
}
ASSERT(!"unknown structure type", "Unknown Structure Type (%s)", typeName);
}
static const char* getStructName(const STRUCTURE_STATS* psStruct)
{
return getName(psStruct->pName);
}
/*returns the structure strength based on the string name passed in */
static UBYTE getStructStrength(const char *pStrength)
{
if (!strcmp(pStrength, "SOFT"))
{
return STRENGTH_SOFT;
}
else if (!strcmp(pStrength, "MEDIUM"))
{
return STRENGTH_MEDIUM;
}
else if (!strcmp(pStrength, "HARD"))
{
return STRENGTH_HARD;
}
else if (!strcmp(pStrength, "BUNKER"))
{
return STRENGTH_BUNKER;
}
return INVALID_STRENGTH;
}
static void initModulePIEs(char *PIEName,UDWORD i,STRUCTURE_STATS *psStructure)
{
char GfxFile[MAX_STR_LENGTH];
char charNum[2];
UDWORD length, module = 0;
strcpy(GfxFile,PIEName);
//need to work out the IMD's for the modules - HACK!
if (psStructure->type == REF_FACTORY_MODULE)
{
length = strlen(GfxFile) - 5;
for (module = 1; module < NUM_FACTORY_MODULES+1; module++)
{
sprintf(charNum,"%d",module);
GfxFile[length] = *charNum;
factoryModuleIMDs[module-1][0] = (iIMDShape*) resGetData("IMD",
GfxFile);
if (factoryModuleIMDs[module-1][0] == NULL)
{
debug( LOG_ERROR, "Cannot find the PIE for factory module %d - %s", module, GfxFile );
abort();
return;
}
}
//store the stat for easy access later on
factoryModuleStat = i;
}
if (psStructure->type == REF_VTOL_FACTORY)
{
length = strlen(GfxFile) - 5;
for (module = 1; module < NUM_FACTORY_MODULES+1; module++)
{
sprintf(charNum,"%d",module);
GfxFile[length] = *charNum;
factoryModuleIMDs[module-1][1] = (iIMDShape*) resGetData("IMD",
GfxFile);
if (factoryModuleIMDs[module-1][1] == NULL)
{
debug( LOG_ERROR, "Cannot find the PIE for vtol factory module %d - %s", module, GfxFile );
abort();
return;
}
}
}
// Setup the PIE's for the research modules.
if (psStructure->type == REF_RESEARCH_MODULE)
{
length = strlen(GfxFile) - 5;
GfxFile[length] = '4';
researchModuleIMDs[0] = (iIMDShape*) resGetData("IMD", GfxFile);
if (researchModuleIMDs[0] == NULL)
{
debug( LOG_ERROR, "Cannot find the PIE for research module %d - %s", module, GfxFile );
abort();
return;
}
researchModuleIMDs[1] = researchModuleIMDs[0];
researchModuleIMDs[2] = researchModuleIMDs[0];
researchModuleIMDs[3] = researchModuleIMDs[0];
//store the stat for easy access later on
researchModuleStat = i;
}
// Setup the PIE's for the power modules.
if (psStructure->type == REF_POWER_MODULE)
{
length = strlen(GfxFile) - 5;
GfxFile[length] = '4';
powerModuleIMDs[0] = (iIMDShape*) resGetData("IMD", GfxFile);
if (powerModuleIMDs[0] == NULL)
{
debug( LOG_ERROR, "Cannot find the PIE for power module %d - %s", module, GfxFile );
abort();
return;
}
powerModuleIMDs[1] = powerModuleIMDs[0];
powerModuleIMDs[2] = powerModuleIMDs[0];
powerModuleIMDs[3] = powerModuleIMDs[0];
//store the stat for easy access later on
powerModuleStat = i;
}
}
/* load the Structure stats from the Access database */
BOOL loadStructureStats(const char *pStructData, UDWORD bufferSize)
{
const unsigned int NumStructures = numCR(pStructData, bufferSize);
UDWORD i, inc, player, numWeaps, weapSlots;
char StructureName[MAX_STR_LENGTH], foundation[MAX_STR_LENGTH],
type[MAX_STR_LENGTH], dummy[MAX_STR_LENGTH],
strength[MAX_STR_LENGTH];
char GfxFile[MAX_STR_LENGTH], baseIMD[MAX_STR_LENGTH];
char ecmType[MAX_STR_LENGTH], sensorType[MAX_STR_LENGTH];
STRUCTURE_STATS *psStructure, *pStartStats;
ECM_STATS* pECMType;
SENSOR_STATS* pSensorType;
UDWORD module;
UDWORD iID;
UDWORD dummyVal;
#if (MAX_PLAYERS != 8)
char NotUsedString[MAX_STR_LENGTH];
#endif
//initialise the module IMD structs
for (module = 0; module < NUM_FACTORY_MODULES; module++)
{
factoryModuleIMDs[module][0] = NULL;
factoryModuleIMDs[module][1] = NULL;
}
for (module = 0; module < NUM_RESEARCH_MODULES; module++)
{
researchModuleIMDs[module] = NULL;
}
for (module = 0; module < NUM_POWER_MODULES; module++)
{
powerModuleIMDs[module] = NULL;
}
asStructureStats = (STRUCTURE_STATS*)malloc(sizeof(STRUCTURE_STATS)* NumStructures);
numStructureStats = NumStructures;
if (asStructureStats == NULL)
{
debug( LOG_ERROR, "Structure Stats - Out of memory" );
abort(); // FIXME exit(EXIT_FAILURE)?
return false;
}
//save the starting address
pStartStats = asStructureStats;
//get the start of the structure_stats storage
psStructure = asStructureStats;
for (i = 0; i < NumStructures; i++)
{
memset(psStructure, 0, sizeof(STRUCTURE_STATS));
//read the data into the storage - the data is delimeted using comma's
GfxFile[0] = '\0';
StructureName[0] = '\0';
type[0] = '\0';
strength[0] = '\0';
foundation[0] = '\0';
ecmType[0] = '\0';
sensorType[0] = '\0';
baseIMD[0] = '\0';
sscanf(pStructData,"%[^','],%[^','],%[^','],%[^','],%d,%d,%d,%[^','],\
%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%[^','],%[^','],%d,%[^','],%[^','],\
%d,%d",
StructureName, type, dummy, strength, &psStructure->terrainType,
&psStructure->baseWidth, &psStructure->baseBreadth, foundation,
&psStructure->buildPoints, &psStructure->height,
&psStructure->armourValue, &psStructure->bodyPoints,
&psStructure->repairSystem, &psStructure->powerToBuild,
&dummyVal, &psStructure->resistance,
&dummyVal, &psStructure->sizeModifier,
ecmType, sensorType, &weapSlots, GfxFile,
baseIMD, &psStructure->numFuncs, &numWeaps);
#if MAX_PLAYERS != 4 && MAX_PLAYERS != 8
# error Invalid number of players
#endif
//allocate storage for the name
psStructure->pName = allocateName(StructureName);
if (!psStructure->pName)
{
return false;
}
psStructure->ref = REF_STRUCTURE_START + i;
//determine the structure type
psStructure->type = structureType(type);
//set the struct strength
psStructure->strength = getStructStrength(strength);
if (psStructure->strength == INVALID_STRENGTH)
{
debug(LOG_ERROR, "Unknown structure strength for %s", getStatName(psStructure) );
abort();
return false;
}
//get the ecm stats pointer
if (!strcmp(ecmType,"0"))
{
psStructure->pECM = NULL;
}
else
{
pECMType = asECMStats;
if (!getResourceName(ecmType))
{
return false;
}
for (inc=0; inc < numECMStats; inc++)
{
//compare the names
if (!strcmp(ecmType, pECMType->pName))
{
psStructure->pECM = pECMType;
break;
}
pECMType++;
}
}
//get the sensor stats pointer
if (!strcmp(sensorType,"0"))
{
psStructure->pSensor = NULL;
}
else
{
if (!getResourceName(sensorType))
{
return false;
}
pSensorType = asSensorStats;
for (inc=0; inc < numSensorStats; inc++)
{
//compare the names
if (!strcmp(sensorType, pSensorType->pName))
{
psStructure->pSensor = pSensorType;
break;
}
pSensorType++;
}
//check not allocating a turret sensor if have weapons attached
ASSERT( psStructure->pSensor != NULL,
"loadStructureStats: should have a sensor attached to %s!", StructureName );
if (psStructure->pSensor->location == LOC_TURRET && numWeaps)
{
debug(LOG_ERROR, "a Turret Sensor and weapon have been assigned to %s", StructureName);
abort();
}
}
//get the IMD for the structure
psStructure->pIMD = (iIMDShape *) resGetData("IMD", GfxFile);
if (psStructure->pIMD == NULL)
{
debug( LOG_ERROR, "Cannot find the structure PIE for record %s", getStructName(psStructure) );
abort();
return false;
}
if (strcmp(baseIMD, "0"))
{
psStructure->pBaseIMD = (iIMDShape *) resGetData("IMD", baseIMD);
if (psStructure->pIMD == NULL)
{
debug( LOG_ERROR, "Cannot find the structure base PIE for record %s", getStructName(psStructure) );
abort();
return false;
}
}
else
{
psStructure->pBaseIMD = NULL;
}
initModulePIEs(GfxFile,i,psStructure);
//Only having one weapon per structure now...AB 24/01/99
if (weapSlots > STRUCT_MAXWEAPS || numWeaps > weapSlots)
{
debug( LOG_ERROR, "Allocated more weapons than allowed for Structure" );
abort();
return false;
}
//Watermelon:I need numWeaps to draw multiple weapons
psStructure->numWeaps = numWeaps;
//allocate storage for the functions - if any
psStructure->defaultFunc = -1;
if (psStructure->numFuncs > 0)
{
psStructure->asFuncList = (FUNCTION **)malloc(psStructure->numFuncs *
sizeof(FUNCTION*));
if (psStructure->asFuncList == NULL)
{
debug( LOG_ERROR, "Out of memory assigning structure Functions" );
abort();
return false;
}
}
//increment the pointer to the start of the next record
pStructData = strchr(pStructData,'\n') + 1;
//increment the list to the start of the next storage block
psStructure++;
}
asStructureStats = pStartStats;
/* get global dummy stat pointer - GJ */
for (iID = 0; iID < numStructureStats; iID++)
{
if (asStructureStats[iID].type == REF_DEMOLISH)
{
break;
}
}
if (iID > numStructureStats)
{
debug(LOG_ERROR, "destroy structure stat not found");
abort();
}
g_psStatDestroyStruct = asStructureStats + iID;
//allocate the structureLimits structure
for (player = 0; player < MAX_PLAYERS; player++)
{
asStructLimits[player] = malloc(sizeof(STRUCTURE_LIMITS) * numStructureStats);
if (asStructLimits[player] == NULL)
{
debug( LOG_ERROR, "Unable to allocate structure limits" );
abort();
return false;
}
}
initStructLimits();
//initialise the structure upgrade arrays
memset(asStructureUpgrade, 0, MAX_PLAYERS * sizeof(STRUCTURE_UPGRADE));
memset(asWallDefenceUpgrade, 0, MAX_PLAYERS * sizeof(WALLDEFENCE_UPGRADE));
memset(asResearchUpgrade, 0, MAX_PLAYERS * sizeof(RESEARCH_UPGRADE));
memset(asPowerUpgrade, 0, MAX_PLAYERS * sizeof(POWER_UPGRADE));
memset(asRepairFacUpgrade, 0, MAX_PLAYERS * sizeof(REPAIR_FACILITY_UPGRADE));
memset(asProductionUpgrade, 0, MAX_PLAYERS * NUM_FACTORY_TYPES *
sizeof(PRODUCTION_UPGRADE));
memset(asReArmUpgrade, 0, MAX_PLAYERS * sizeof(REARM_UPGRADE));
return true;
}
//initialise the structure limits structure
void initStructLimits(void)
{
UDWORD i, player;
for (player = 0; player < MAX_PLAYERS; player++)
{
STRUCTURE_LIMITS *psStructLimits = asStructLimits[player];
STRUCTURE_STATS *psStat = asStructureStats;
for (i = 0; i < numStructureStats; i++)
{
psStructLimits[i].limit = LOTS_OF;
psStructLimits[i].currentQuantity = 0;
psStructLimits[i].globalLimit = LOTS_OF;
if (isLasSat(psStat) || psStat->type == REF_SAT_UPLINK)
{
psStructLimits[i].limit = 1;
psStructLimits[i].globalLimit = 1;
}
psStat++;
}
}
}
/* set the current number of structures of each type built */
void setCurrentStructQuantity(BOOL displayError)
{
UDWORD player, inc;
for (player = 0; player < MAX_PLAYERS; player++)
{
STRUCTURE_LIMITS *psStructLimits = asStructLimits[player];
STRUCTURE *psCurr;
//initialise the current quantity for all structures
for (inc = 0; inc < numStructureStats; inc++)
{
psStructLimits[inc].currentQuantity = 0;
}
for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr =
psCurr->psNext)
{
inc = psCurr->pStructureType - asStructureStats;
psStructLimits[inc].currentQuantity++;
if (displayError)
{
//check quantity never exceeds the limit
if (psStructLimits[inc].currentQuantity > psStructLimits[inc].limit)
{
ASSERT( false, "There appears to be too many %s on this map!",
getStructName(&asStructureStats[inc] ) );
}
}
}
}
}
//Load the weapons assigned to Structure in the Access database
BOOL loadStructureWeapons(const char *pWeaponData, UDWORD bufferSize)
{
const unsigned int NumToAlloc = numCR(pWeaponData, bufferSize);
UDWORD i, incS, incW;
char StructureName[MAX_STR_LENGTH];//, WeaponName[MAX_STR_LENGTH];
char WeaponName[STRUCT_MAXWEAPS][MAX_STR_LENGTH];
STRUCTURE_STATS *pStructure = asStructureStats;
WEAPON_STATS *pWeapon = asWeaponStats;
BOOL weaponFound, structureFound;
UBYTE j;
for (i=0; i < NumToAlloc; i++)
{
//read the data into the storage - the data is delimeted using comma's
StructureName[0] = '\0';
for (j = 0;j < STRUCT_MAXWEAPS;j++)
{
WeaponName[j][0] = '\0';
}
sscanf(pWeaponData, "%[^','],%[^','],%[^','],%[^','],%[^','],%*d", StructureName, WeaponName[0], WeaponName[1], WeaponName[2], WeaponName[3]);
if (!getResourceName(StructureName))
{
return false;
}
if (!getResourceName(WeaponName[0]))
{
return false;
}
weaponFound = structureFound = false;
//loop through each Structure_Stat to compare the name
for (incS=0; incS < numStructureStats; incS++)
{
if (!(strcmp(StructureName, pStructure[incS].pName)))
{
//Structure found, so loop through each weapon
structureFound = true;
for (j = 0;j < pStructure[incS].numWeaps;j++)
{
for (incW=0; incW < numWeaponStats; incW++)
{
if (!(strcmp(WeaponName[j], pWeapon[incW].pName)))
{
weaponFound = true;
//Watermelon:read and store multiple weapon Stats
pStructure[incS].psWeapStat[j] = &pWeapon[incW];
break;
}
}
//if weapon not found - error
if (!weaponFound)
{
debug( LOG_ERROR, "Unable to find stats for weapon %s for structure %s", WeaponName[i], StructureName );
abort();
return false;
}
}
}
}
//if structure not found - error
if (!structureFound)
{
debug( LOG_ERROR, "Unable to find stats for structure %s", StructureName );
abort();
return false;
}
//increment the pointer to the start of the next record
pWeaponData = strchr(pWeaponData,'\n') + 1;
}
return true;
}
//Load the programs assigned to Droids in the Access database
BOOL loadStructureFunctions(const char *pFunctionData, UDWORD bufferSize)
{
const unsigned int NumToAlloc = numCR(pFunctionData, bufferSize);
UDWORD i, incS, incF;
char StructureName[MAX_STR_LENGTH], FunctionName[MAX_STR_LENGTH];
STRUCTURE_STATS *pStructure = asStructureStats;
FUNCTION *pFunction, **pStartFunctions = asFunctions;
BOOL functionFound, structureFound;
for (i=0; i < NumToAlloc; i++)
{
//read the data into the storage - the data is delimeted using comma's
StructureName[0] = '\0';
FunctionName[0] = '\0';
sscanf(pFunctionData, "%[^','],%[^','],%*d", StructureName, FunctionName);
functionFound = structureFound = false;
if (!getResourceName(StructureName))
{
return false;
}
//loop through each Structure_Stat to compare the name
for (incS=0; incS < numStructureStats; incS++)
{
if (!(strcmp(StructureName, pStructure[incS].pName)))
{
//Structure found, so loop through each Function
structureFound = true;
pStartFunctions = asFunctions;
for (incF=0; incF < numFunctions; incF++)
{
pFunction = *pStartFunctions;
if (!(strcmp(FunctionName, pFunction->pName)))
{
//function found alloc this function to the current Structure
functionFound = true;
pStructure[incS].defaultFunc++;
//check not allocating more than allowed
if (pStructure[incS].defaultFunc >
(SDWORD)pStructure[incS].numFuncs)
{
debug( LOG_ERROR, "Trying to allocate more functions than allowed for Structure" );
abort();
return false;
}
pStructure[incS].asFuncList[pStructure[incS].defaultFunc] =
pFunction;
break;
}
pStartFunctions++;
}
//if function not found - error
if (!functionFound)
{
debug( LOG_ERROR, "Unable to find stats for function %s for structure %s", FunctionName, StructureName );
abort();
return false;
}
}
}
//if structure not found - error
if (!structureFound)
{
debug( LOG_ERROR, "Unable to find stats for structure %s", StructureName );
abort();
return false;
}
//increment the pointer to the start of the next record
pFunctionData = strchr(pFunctionData,'\n') + 1;
}
/**************************************************************************/
//Wall Function requires a structure stat so can allocate it now
pStartFunctions = asFunctions;
for (incF=0; incF < numFunctions; incF++)
{
pFunction = *pStartFunctions;
if (pFunction->type == WALL_TYPE)
{
//loop through all structures to find the stat
pStructure = asStructureStats;
((WALL_FUNCTION *)pFunction)->pCornerStat = NULL;
for (i=0; i < numStructureStats; i++)
{
//compare the names
if (!strcmp(((WALL_FUNCTION *)pFunction)->pStructName, pStructure->pName))
{
((WALL_FUNCTION *)pFunction)->pCornerStat = pStructure;
break;
}
pStructure++;
}
//if haven't found the STRUCTURE STAT, then problem
if (!((WALL_FUNCTION *)pFunction)->pCornerStat)
{
debug( LOG_ERROR, "Unknown Corner Wall stat for function %s", pFunction->pName );
abort();
return false;
}
}
pStartFunctions++;
}
return true;
}
/*Load the Structure Strength Modifiers from the file exported from Access*/
BOOL loadStructureStrengthModifiers(const char *pStrengthModData, UDWORD bufferSize)
{
const unsigned int NumRecords = numCR(pStrengthModData, bufferSize);
STRUCT_STRENGTH strengthInc;
WEAPON_EFFECT effectInc;
UDWORD i, j, modifier;
char weaponEffectName[MAX_STR_LENGTH], strengthName[MAX_STR_LENGTH];
//initialise to 100%
for (i=0; i < WE_NUMEFFECTS; i++)
{
for (j=0; j < NUM_STRUCT_STRENGTH; j++)
{
asStructStrengthModifier[i][j] = 100;
}
}
for (i=0; i < NumRecords; i++)
{
//read the data into the storage - the data is delimeted using comma's
sscanf(pStrengthModData,"%[^','],%[^','],%d",
weaponEffectName, strengthName, &modifier);
//get the weapon effect inc
if (!getWeaponEffect(weaponEffectName, &effectInc))
{
debug(LOG_ERROR, "Invalid Weapon Effect - %s", weaponEffectName);
abort();
return false;
}
//get the propulsion inc
strengthInc = getStructStrength(strengthName);
if (strengthInc == INVALID_STRENGTH)
{
debug(LOG_ERROR, "Invalid Strength type - %s", strengthName);
abort();
return false;
}
if (modifier > UWORD_MAX)
{
debug(LOG_ERROR, "modifier for effect %s, strength %s is too large", weaponEffectName, strengthName);
abort();
return false;
}
//store in the appropriate index
asStructStrengthModifier[effectInc][strengthInc] = (UWORD)modifier;
//increment the pointer to the start of the next record
pStrengthModData = strchr(pStrengthModData,'\n') + 1;
}
return true;
}
BOOL structureStatsShutDown(void)
{
UDWORD inc;
STRUCTURE_STATS* pStructure = asStructureStats;
for(inc=0; inc < numStructureStats; inc++, pStructure++)
{
if (pStructure->numFuncs > 0)
{
free(pStructure->asFuncList);
pStructure->asFuncList = NULL;
}
}
if(numStructureStats) {
free(asStructureStats);
asStructureStats = NULL;
}
//free up the structLimits structure
for (inc = 0; inc < MAX_PLAYERS ; inc++)
{
if (asStructLimits[inc])
{
free(asStructLimits[inc]);
asStructLimits[inc] = NULL;
}
}
return true;
}
// TODO: The abandoned code needs to be factored out, see: saveMissionData
void handleAbandonedStructures()
{
// FIXME: We should control the calling frequency externally from this
// function, rather than controlling the amount of times this
// function performs work internally.
static int lastHandled = 0;
int player;
// We only need to run once every two seconds (2000ms)
if (gameTime - lastHandled < 2000)
{
return;
}
// Update when we last ran
lastHandled = gameTime;
for (player = 0; player < MAX_PLAYERS; ++player)
{
STRUCTURE *psCurr, *psNext;
for (psCurr = apsStructLists[player]; psCurr; psCurr = psNext)
{
// Save the next structure in the list
psNext = psCurr->psNext;
/*
* We are only interested in structures accruing that are not
* structures which can have modules (factory, research, power).
*/
if (psCurr->status == SS_BEING_BUILT
&& psCurr->currentPowerAccrued < structPowerToBuild(psCurr)
&& psCurr->pStructureType->type != REF_FACTORY
&& psCurr->pStructureType->type != REF_VTOL_FACTORY
&& psCurr->pStructureType->type != REF_RESEARCH
&& psCurr->pStructureType->type != REF_POWER_GEN)
{
DROID *psDroid;
bool beingBuilt = false;
// See is there are any droids building it
for (psDroid = apsDroidLists[player];
psDroid;
psDroid = psDroid->psNext)
{
// The droid is working on it and therefore not abandoned
if ((STRUCTURE *) orderStateObj(psDroid, DORDER_BUILD) == psCurr)
{
beingBuilt = true;
break;
}
}
// Being worked on, nothing to see here
if (beingBuilt)
{
continue;
}
// Abandoned
else
{
int reductionAmount = 8;
// Work out how much power to deduct
CLIP(reductionAmount, 0, psCurr->currentPowerAccrued);
// Do the reduction
psCurr->currentPowerAccrued -= reductionAmount;
addPower(player, reductionAmount);
// Remove the structure if no power is accrued
if (!psCurr->currentPowerAccrued)
{
removeStruct(psCurr, true);
}
}
}
}
}
}
/* Deals damage to a Structure.
* \param psStructure structure to deal damage to
* \param damage amount of damage to deal
* \param weaponClass the class of the weapon that deals the damage
* \param weaponSubClass the subclass of the weapon that deals the damage
* \return < 0 when the dealt damage destroys the structure, > 0 when the structure survives
*/
float structureDamage(STRUCTURE *psStructure, UDWORD damage, UDWORD weaponClass,
UDWORD weaponSubClass, HIT_SIDE impactSide)
{
float relativeDamage;
CHECK_STRUCTURE(psStructure);
debug(LOG_ATTACK, "structure %d, body %d, armour %d, damage: %d",
psStructure->id, psStructure->body, psStructure->armour[impactSide][weaponClass], damage);
relativeDamage = objDamage((BASE_OBJECT *)psStructure, damage, structureBody(psStructure), weaponClass, weaponSubClass, impactSide);
// If the shell did sufficient damage to destroy the structure
if (relativeDamage < 0.0f)
{
debug(LOG_ATTACK, "DESTROYED");
destroyStruct(psStructure);
}
else
{
// Survived
CHECK_STRUCTURE(psStructure);
}
return relativeDamage;
}
float getStructureDamage(const STRUCTURE* psStructure)
{
float health;
CHECK_STRUCTURE(psStructure);
health = (float)psStructure->body / (float)structureBody(psStructure);
CLIP(health, 0., 1.f);
return 1. - health;
}
/* Set the type of droid for a factory to build */
BOOL structSetManufacture(STRUCTURE *psStruct, DROID_TEMPLATE *psTempl, UBYTE quantity)
{
FACTORY *psFact;
CHECK_STRUCTURE(psStruct);
ASSERT( psStruct != NULL && psStruct->type == OBJ_STRUCTURE &&
(psStruct->pStructureType->type == REF_FACTORY ||
psStruct->pStructureType->type == REF_CYBORG_FACTORY ||
psStruct->pStructureType->type == REF_VTOL_FACTORY),
"structSetManufacture: invalid Factory pointer" );
/* psTempl might be NULL if the build is being cancelled in the middle */
//assign it to the Factory
psFact = &psStruct->pFunctionality->factory;
psFact->psSubject = (BASE_STATS *)psTempl;
//set up the start time and build time
if (psTempl != NULL)
{
//only use this for non selectedPlayer
if (psStruct->player != selectedPlayer)
{
//set quantity to produce
psFact->quantity = quantity;
}
psFact->timeStarted = ACTION_START_TIME;//gameTime;
psFact->powerAccrued = 0;
psFact->timeStartHold = 0;
psFact->timeToBuild = psTempl->buildPoints / psFact->productionOutput;
//check for zero build time - usually caused by 'silly' data!
if (psFact->timeToBuild == 0)
{
//set to 1/1000th sec - ie very fast!
psFact->timeToBuild = 1;
}
}
return true;
}
/*****************************************************************************/
/*
* All this wall type code is horrible, but I really can't think of a better way to do it.
* John.
*/
// look at where other walls are to decide what type of wall to build
static SDWORD structWallScan(BOOL aWallPresent[5][5], SDWORD x,SDWORD y)
{
SDWORD cx,cy, numWalls;
numWalls = 0;
for(cx = x-1; cx <= x+1; cx += 1)
{
for(cy = y-1; cy <= y+1; cy += 1)
{
// do not look at walls diagonally from this wall
if (((cx == x && cy != y) ||
(cx != x && cy == y))
&& aWallPresent[cx][cy])
{
numWalls += 1;
}
}
}
// Now decide what type of wall is needed
if (numWalls == 1)
{
if (aWallPresent[x-1][y] || aWallPresent[x+1][y])
{
// there is a wall horizontally adjacent
return WALL_HORIZ;
}
else
{
return WALL_VERT;
}
}
else if (numWalls == 2)
{
if (aWallPresent[x-1][y] && aWallPresent[x+1][y])
{
// there is a wall horizontally adjacent
return WALL_HORIZ;
}
else if (aWallPresent[x][y-1] && aWallPresent[x][y+1])
{
// there is a wall vertically adjacent
return WALL_VERT;
}
else
{
return WALL_CORNER;
}
}
else if (numWalls > 2)
{
// definately need a corner wall
return WALL_CORNER;
}
return WALL_VERT;
}
// Choose a type of wall for a location - and update any neighbouring walls
static SDWORD structChooseWallType(UDWORD player, UDWORD mapX, UDWORD mapY)
{
BOOL aWallPresent[5][5];
SDWORD xdiff,ydiff, x,y;
STRUCTURE *psStruct;
STRUCTURE *apsStructs[5][5];
SDWORD nayborType, scanType;
STRUCTURE_STATS *psStats;
UDWORD sx,sy;
UDWORD oldBuildPoints;
// scan around the location looking for walls
memset(aWallPresent, 0, sizeof(aWallPresent));
for(psStruct=apsStructLists[player]; psStruct; psStruct=psStruct->psNext)
{
xdiff = (SDWORD)mapX - map_coord((SDWORD)psStruct->pos.x);
ydiff = (SDWORD)mapY - map_coord((SDWORD)psStruct->pos.y);
if (xdiff >= -2 && xdiff <= 2 &&
ydiff >= -2 && ydiff <= 2 &&
(psStruct->pStructureType->type == REF_WALL ||
psStruct->pStructureType->type == REF_WALLCORNER ||
psStruct->pStructureType->type == REF_DEFENSE))
{
aWallPresent[xdiff+2][ydiff+2] = true;
apsStructs[xdiff+2][ydiff+2] = psStruct;
}
}
// add in the wall about to be built
aWallPresent[2][2] = true;
// now make sure that all the walls around this one are OK
for(x=1; x<=3; x+=1)
{
for(y=1; y<=3; y+=1)
{
// do not look at walls diagonally from this wall
if (((x == 2 && y != 2) ||
(x != 2 && y == 2)) &&
aWallPresent[x][y])
{
// figure out what type the wall currently is
psStruct = apsStructs[x][y];
if (psStruct->pStructureType->type == REF_WALL)
{
if ( (int)psStruct->direction == 90 )
{
nayborType = WALL_VERT;
}
else
{
nayborType = WALL_HORIZ;
}
}
else
{
// do not need to adjust anything apart from walls
continue;
}
// see what type the wall should be
scanType = structWallScan(aWallPresent, x,y);
if (nayborType != scanType)
{
// Got to change the wall
if (scanType == WALL_CORNER)
{
// change to a corner
if (psStruct->pStructureType->asFuncList[0]->type == WALL_TYPE)
{
/* Still being built - so save and load build points */
if(psStruct->status == SS_BEING_BUILT)
{
oldBuildPoints = psStruct->currentBuildPts;
psStats = ((WALL_FUNCTION *)psStruct
->pStructureType->asFuncList[0])
->pCornerStat;
sx = psStruct->pos.x; sy = psStruct->pos.y;
removeStruct(psStruct, true);
powerCalc(false);
psStruct = buildStructure(psStats, sx,sy, player, true);
powerCalc(true);
if(psStruct !=NULL)
{
psStruct->status = SS_BEING_BUILT;
psStruct->currentBuildPts = (SWORD)oldBuildPoints;
}
}
else
{
psStats = ((WALL_FUNCTION *)psStruct
->pStructureType->asFuncList[0])
->pCornerStat;
sx = psStruct->pos.x; sy = psStruct->pos.y;
removeStruct(psStruct, true);
powerCalc(false);
psStruct = buildStructure(psStats, sx,sy, player, true);
powerCalc(true);
if(psStruct !=NULL)
{
psStruct->status = SS_BUILT;
buildingComplete(psStruct);
}
}
}
}
else if (scanType == WALL_HORIZ)
{
// change to a horizontal wall
psStruct->direction = 0;
}
else
{
// change to a vertical wall
psStruct->direction = 90;
}
}
}
}
}
// finally return the type for this wall
return structWallScan(aWallPresent, 2,2);
}
static void buildFlatten(STRUCTURE_STATS *pStructureType, UDWORD x, UDWORD y, UDWORD h)
{
UBYTE width;
UBYTE breadth;
for (breadth=0; breadth <= (UBYTE)(pStructureType->baseBreadth/* + 1*/); breadth++)
{
for (width=0; width <= (UBYTE)(pStructureType->baseWidth /*+ 1*/); width++)
{
if ( (pStructureType->type != REF_WALL) &&
(pStructureType->type != REF_WALLCORNER) )
{
setTileHeight(x + width, y + breadth, h);//-1
// We need to raise features on raised tiles to the new height
if(TileHasFeature(mapTile(x+width,y+breadth)))
{
getTileFeature(x+width, y+breadth)->pos.z = (UWORD)h;
}
}
}
}
}
void alignStructure(STRUCTURE *psBuilding)
{
int width, breadth;
int x = psBuilding->pos.x;
int y = psBuilding->pos.y;
int mapX = map_coord(x) - (psBuilding->pStructureType->baseWidth / 2);
int mapY = map_coord(y) - (psBuilding->pStructureType->baseBreadth / 2);
/* DEFENSIVE structures are pulled to the terrain */
if (psBuilding->pStructureType->type != REF_DEFENSE)
{
int mapH = buildFoundation(psBuilding->pStructureType, x, y);
buildFlatten(psBuilding->pStructureType, mapX, mapY, mapH);
psBuilding->pos.z = mapH;
}
else
{
psBuilding->pos.z = TILE_MIN_HEIGHT;
/* Set it at the higher coord */
for (width = 0; width < psBuilding->pStructureType->baseWidth; width++)
{
for (breadth = 0; breadth < psBuilding->pStructureType->baseBreadth; breadth++)
{
UDWORD tmpMax, tmpMin;
getTileMaxMin(map_coord(x) + width, map_coord(y) + breadth, &tmpMax, &tmpMin);
psBuilding->pos.z = MAX(tmpMax, psBuilding->pos.z);
}
}
}
}
/*Builds an instance of a Structure - the x/y passed in are in world coords. */
STRUCTURE* buildStructure(STRUCTURE_STATS* pStructureType, UDWORD x, UDWORD y, UDWORD player, BOOL FromSave)
{
UDWORD mapX, mapY;
UDWORD width, breadth, weapon, capacity;
float bodyDiff = 0.f;
SDWORD wallType = 0, preScrollMinX = 0, preScrollMinY = 0, preScrollMaxX = 0, preScrollMaxY = 0;
int i;
STRUCTURE *psBuilding = NULL;
assert(pStructureType);
ASSERT(pStructureType->type != REF_DEMOLISH, "You cannot build demolition!");
if (IsStatExpansionModule(pStructureType)==false)
{
//some prelim tests...
UDWORD max = pStructureType - asStructureStats;
if (max > numStructureStats)
{
ASSERT(!"invalid structure type", "buildStructure: Invalid structure type");
return NULL;
}
// Don't allow more than interface limits
if (asStructLimits[player][max].currentQuantity + 1 > asStructLimits[player][max].limit)
{
debug(LOG_ERROR, "Player %u: Building %s could not be built due to building limits (has %d, max %d)!",
player, pStructureType->pName, asStructLimits[player][max].currentQuantity,
asStructLimits[player][max].limit);
return NULL;
}
// snap the coords to a tile
x = ((pStructureType->baseWidth % 2) == 0) ? (x & ~TILE_MASK) : (x & ~TILE_MASK) + TILE_UNITS/2;
y = ((pStructureType->baseBreadth % 2) == 0) ? (y & ~TILE_MASK) : (y & ~TILE_MASK) + TILE_UNITS/2;
//check not trying to build too near the edge
if (map_coord(x) < TOO_NEAR_EDGE
|| map_coord(x) > (mapWidth - TOO_NEAR_EDGE))
{
debug(LOG_ERROR, "attempting to build too closely to map-edge, "
"x coord (%u) too near edge (req. distance is %u)", x, TOO_NEAR_EDGE);
return NULL;
}
if (map_coord(y) < TOO_NEAR_EDGE
|| map_coord(y) > (mapHeight - TOO_NEAR_EDGE))
{
debug(LOG_ERROR, "attempting to build too closely to map-edge, "
"y coord (%u) too near edge (req. distance is %u)", y, TOO_NEAR_EDGE);
return NULL;
}
if (!FromSave && pStructureType->type == REF_WALL)
{
wallType = structChooseWallType(player, map_coord(x), map_coord(y));
if (wallType == WALL_CORNER)
{
if (pStructureType->asFuncList[0]->type == WALL_TYPE)
{
pStructureType = ((WALL_FUNCTION *)pStructureType->asFuncList[0])->pCornerStat;
}
}
}
// allocate memory for and initialize a structure object
psBuilding = createStruct(player);
if (psBuilding == NULL)
{
return NULL;
}
psBuilding->psCurAnim = NULL;
//fill in other details
psBuilding->pStructureType = pStructureType;
psBuilding->pos.x = (UWORD)x;
psBuilding->pos.y = (UWORD)y;
//This needs to be done before the functionality bit...
//load into the map data and structure list if not an upgrade
mapX = map_coord(x) - (pStructureType->baseWidth/2);
mapY = map_coord(y) - (pStructureType->baseBreadth/2);
//set up the imd to use for the display
psBuilding->sDisplay.imd = pStructureType->pIMD;
/* if resource extractor - need to remove oil feature first, but do not do any
* consistency checking here - save games do not have any feature to remove
* to remove when placing oil derricks! */
if (pStructureType->type == REF_RESOURCE_EXTRACTOR)
{
FEATURE *psFeature = getTileFeature(map_coord(x), map_coord(y));
if (psFeature && psFeature->psStats->subType == FEAT_OIL_RESOURCE)
{
// remove it from the map
turnOffMultiMsg(true); // dont send this one!
removeFeature(psFeature);
turnOffMultiMsg(false);
}
}
for (width = 0; width < pStructureType->baseWidth; width++)
{
for (breadth = 0; breadth < pStructureType->baseBreadth; breadth++)
{
MAPTILE *psTile = mapTile(mapX+width,mapY+breadth);
/* Remove any walls underneath the building. You can build defense buildings on top
* of walls, you see. This is not the place to test whether we own it! */
if (pStructureType->type == REF_DEFENSE && TileHasWall(psTile))
{
removeStruct((STRUCTURE *)psTile->psObject, true);
}
// don't really think this should be done here, but dont know otherwise.alexl
if(pStructureType->type == REF_WALLCORNER || pStructureType->type == REF_WALL)
{
if(TileHasStructure(mapTile(mapX+width,mapY+breadth)))
{
if(getTileStructure(mapX+width,mapY+breadth)->pStructureType->type == REF_WALLCORNER)
{
return NULL; // dont build.
}
}
}
// end of dodgy stuff
else if (TileHasStructure(psTile))
{
debug(LOG_ERROR,
"building %s at (%d, %d) but found %s already at (%d, %d)",
pStructureType->pName, mapX, mapY,
getTileStructure(mapX + width, mapY + breadth)->pStructureType->pName,
mapX + width, mapY + breadth);
free(psBuilding);
return NULL;
}
psTile->psObject = (BASE_OBJECT*)psBuilding;
// if it's a tall structure then flag it in the map.
if (psBuilding->sDisplay.imd->max.y > TALLOBJECT_YMAX)
{
SET_TILE_TALLSTRUCTURE(psTile);
}
}
}
alignStructure(psBuilding);
//set up the rest of the data
for (i = 0;i < STRUCT_MAXWEAPS;i++)
{
psBuilding->turretRotation[i] = 0;
psBuilding->turretPitch[i] = 0;
psBuilding->psTarget[i] = NULL;
}
psBuilding->targetted = 0;
psBuilding->lastEmission = 0;
psBuilding->timeLastHit = 0;
psBuilding->lastHitWeapon = UDWORD_MAX; // no such weapon
psBuilding->inFire = false;
psBuilding->burnStart = 0;
psBuilding->burnDamage = 0;
psBuilding->direction = 0;
psBuilding->pitch = 0;
psBuilding->roll = 0;
psBuilding->selected = false;
psBuilding->status = SS_BEING_BUILT;
psBuilding->currentBuildPts = 0;
psBuilding->currentPowerAccrued = 0;
psBuilding->cluster = 0;
// rotate a wall if necessary
if (!FromSave && pStructureType->type == REF_WALL && wallType == WALL_VERT)
{
psBuilding->direction = 90;
}
//set up the sensor stats
if (psBuilding->pStructureType->pSensor)
{
psBuilding->sensorRange = (UWORD)sensorRange(psBuilding->pStructureType->
pSensor, (UBYTE)player);
psBuilding->sensorPower = (UWORD)sensorPower(psBuilding->pStructureType->
pSensor, (UBYTE)player);
}
else
{
//give them the default sensor for droids if not
psBuilding->sensorRange = (UWORD)sensorRange(asSensorStats +
aDefaultSensor[player], (UBYTE)player);
psBuilding->sensorPower = (UWORD)sensorPower(asSensorStats +
aDefaultSensor[player], (UBYTE)player);
}
//set up the ecm stat
if (psBuilding->pStructureType->pECM)
{
psBuilding->ECMMod = (UWORD)ecmPower(psBuilding->pStructureType->pECM,
psBuilding->player);
}
else
{
psBuilding->ECMMod = 0;
}
/* Store the weapons */
memset(psBuilding->asWeaps, 0, sizeof(WEAPON));
//Watermelon:can only have the STRUCT_MAXWEAPS weapon now
psBuilding->numWeaps = 0;
if (pStructureType->numWeaps > 0)
{
for(weapon=0; weapon < pStructureType->numWeaps; weapon++)
{
//can only have the one weapon now - AB 24/01/99
if (pStructureType->psWeapStat[weapon])
{
psBuilding->asWeaps[weapon].lastFired = 0;
//in multiPlayer make the Las-Sats require re-loading from the start
if (bMultiPlayer && pStructureType->psWeapStat[0]->
weaponSubClass == WSC_LAS_SAT)
{
psBuilding->asWeaps[0].lastFired = gameTime;
}
psBuilding->asWeaps[weapon].nStat = pStructureType->psWeapStat[weapon] - asWeaponStats;
psBuilding->asWeaps[weapon].ammo = (asWeaponStats + psBuilding->
asWeaps[weapon].nStat)->numRounds;
psBuilding->asWeaps[weapon].recoilValue = 0;
psBuilding->numWeaps++;
}
}
}
else
{
if (pStructureType->psWeapStat[0])
{
psBuilding->asWeaps[0].lastFired = 0;
//in multiPlayer make the Las-Sats require re-loading from the start
if (bMultiPlayer && pStructureType->psWeapStat[0]->
weaponSubClass == WSC_LAS_SAT)
{
psBuilding->asWeaps[0].lastFired = gameTime;
}
psBuilding->asWeaps[0].nStat = pStructureType->psWeapStat[0] - asWeaponStats;
psBuilding->asWeaps[0].ammo = (asWeaponStats + psBuilding->
asWeaps[0].nStat)->numRounds;
psBuilding->asWeaps[0].recoilValue = 0;
}
}
// Structures currently do not have varied armour
for (i = 0; i < NUM_HIT_SIDES; i++)
{
int j;
for (j = 0; j < WC_NUM_WEAPON_CLASSES; j++)
{
psBuilding->armour[i][j] = (UWORD)structureArmour(pStructureType, (UBYTE)player);
}
}
psBuilding->resistance = (UWORD)structureResistance(pStructureType, (UBYTE)player);
psBuilding->lastResistance = ACTION_START_TIME;
//do the visiblilty stiff before setFunctionality - so placement of DP's can work
memset(psBuilding->visible, 0, (sizeof(UBYTE) * MAX_PLAYERS) );
/* Structure is trivially visible to the builder (owner) */
psBuilding->visible[player] = UBYTE_MAX;
// Reveal any tiles that can be seen by the structure
visTilesUpdate((BASE_OBJECT *)psBuilding, rayTerrainCallback);
/*if we're coming from a SAVEGAME and we're on an Expand_Limbo mission,
any factories that were built previously for the selectedPlayer will
have DP's in an invalid location - the scroll limits will have been
changed to not include them. This is the only HACK I can think of to
enable them to be loaded up. So here goes...*/
if (FromSave && player == selectedPlayer && missionLimboExpand())
{
//save the current values
preScrollMinX = scrollMinX;
preScrollMinY = scrollMinY;
preScrollMaxX = scrollMaxX;
preScrollMaxY = scrollMaxY;
//set the current values to mapWidth/mapHeight
scrollMinX = 0;
scrollMinY = 0;
scrollMaxX = mapWidth;
scrollMaxY = mapHeight;
}
//set the functionality dependant on the type of structure
if(!setFunctionality(psBuilding, pStructureType->type))
{
removeStructFromMap(psBuilding);
free(psBuilding);
//better reset these if you couldn't build the structure!
if (FromSave && player == selectedPlayer && missionLimboExpand())
{
//reset the current values
scrollMinX = preScrollMinX;
scrollMinY = preScrollMinY;
scrollMaxX = preScrollMaxX;
scrollMaxY = preScrollMaxY;
}
return NULL;
}
//reset the scroll values if adjusted
if (FromSave && player == selectedPlayer && missionLimboExpand())
{
//reset the current values
scrollMinX = preScrollMinX;
scrollMinY = preScrollMinY;
scrollMaxX = preScrollMaxX;
scrollMaxY = preScrollMaxY;
}
psBuilding->body = (UWORD)structureBody(psBuilding);
//add the structure to the list - this enables it to be drawn whilst being built
addStructure(psBuilding);
gridAddObject((BASE_OBJECT *)psBuilding);
clustNewStruct(psBuilding);
asStructLimits[player][max].currentQuantity++;
}
else //its an upgrade
{
BOOL bUpgraded = false;
psBuilding = getTileStructure(map_coord(x), map_coord(y));
//don't create the Structure use existing one
if (psBuilding == NULL)
{
ASSERT(!"module has no owning structure", "No owning structure for this module - %s", getStructName(pStructureType));
return false;
}
if (pStructureType->type == REF_FACTORY_MODULE)
{
if (psBuilding->pStructureType->type != REF_FACTORY &&
psBuilding->pStructureType->type != REF_VTOL_FACTORY)
{
return NULL;
}
//increment the capacity and output for the owning structure
if (psBuilding->pFunctionality->factory.capacity < SIZE_SUPER_HEAVY)
{
//store the % difference in body points before upgrading
bodyDiff = 1. - getStructureDamage(psBuilding);
++psBuilding->pFunctionality->factory.capacity;
bUpgraded = true;
//put any production on hold
holdProduction(psBuilding);
//quick check not trying to add too much
ASSERT(psBuilding->pFunctionality->factory.productionOutput +
((PRODUCTION_FUNCTION*)pStructureType->asFuncList[0])->productionOutput < UBYTE_MAX,
"building factory module - productionOutput too big");
psBuilding->pFunctionality->factory.productionOutput += ((
PRODUCTION_FUNCTION*)pStructureType->asFuncList[0])->productionOutput;
//need to change which IMD is used for player 0
//Need to do a check its not Barbarian really!
if ( (bMultiPlayer && isHumanPlayer(psBuilding->player))
|| (bMultiPlayer && (game.type == SKIRMISH) && (psBuilding->player < game.maxPlayers))
|| !bMultiPlayer)
{
capacity = psBuilding->pFunctionality->factory.capacity;
if (capacity < NUM_FACTORY_MODULES)
{
if (psBuilding->pStructureType->type == REF_FACTORY)
{
psBuilding->sDisplay.imd = factoryModuleIMDs[
capacity-1][0];
}
else
{
psBuilding->sDisplay.imd = factoryModuleIMDs[
capacity-1][1];
}
}
else
{
if (psBuilding->pStructureType->type == REF_FACTORY)
{
psBuilding->sDisplay.imd = factoryModuleIMDs[
NUM_FACTORY_MODULES-1][0];
}
else
{
psBuilding->sDisplay.imd = factoryModuleIMDs[
NUM_FACTORY_MODULES-1][1];
}
}
}
}
}
if (pStructureType->type == REF_RESEARCH_MODULE)
{
if (psBuilding->pStructureType->type != REF_RESEARCH)
{
return NULL;
}
//increment the capacity and research points for the owning structure
if (psBuilding->pFunctionality->researchFacility.capacity < NUM_RESEARCH_MODULES)
{
//store the % difference in body points before upgrading
bodyDiff = 1. - getStructureDamage(psBuilding);
//add all the research modules in one go AB 24/06/98
//((RESEARCH_FACILITY*)psBuilding->pFunctionality)->capacity++;
psBuilding->pFunctionality->researchFacility.capacity = NUM_RESEARCH_MODULES;
psBuilding->pFunctionality->researchFacility.researchPoints += ((
RESEARCH_FUNCTION*)pStructureType->asFuncList[0])->
researchPoints;
bUpgraded = true;
//cancel any research - put on hold now
if (psBuilding->pFunctionality->researchFacility.psSubject)
{
//cancel the topic
holdResearch(psBuilding);
}
//need to change which IMD is used for player 0
//Need to do a check its not Barbarian really!
if ( (bMultiPlayer && isHumanPlayer(psBuilding->player))
|| (bMultiPlayer && (game.type == SKIRMISH) && (psBuilding->player < game.maxPlayers))
|| !bMultiPlayer)
{
capacity = psBuilding->pFunctionality->researchFacility.capacity;
if (capacity < NUM_RESEARCH_MODULES)
{
psBuilding->sDisplay.imd = researchModuleIMDs[capacity-1];
}
else
{
psBuilding->sDisplay.imd = researchModuleIMDs[NUM_RESEARCH_MODULES-1];
}
}
}
}
if (pStructureType->type == REF_POWER_MODULE)
{
if (psBuilding->pStructureType->type != REF_POWER_GEN)
{
return NULL;
}
//increment the capacity and research points for the owning structure
if (psBuilding->pFunctionality->powerGenerator.capacity < NUM_POWER_MODULES)
{
//store the % difference in body points before upgrading
bodyDiff = 1. - getStructureDamage(psBuilding);
//increment the power output, multiplier and capacity
//add all the research modules in one go AB 24/06/98
psBuilding->pFunctionality->powerGenerator.capacity = NUM_POWER_MODULES;
psBuilding->pFunctionality->powerGenerator.power += ((
POWER_GEN_FUNCTION*)pStructureType->asFuncList[0])->
powerOutput;
psBuilding->pFunctionality->powerGenerator.multiplier += ((
POWER_GEN_FUNCTION*)pStructureType->asFuncList[0])->
powerMultiplier;
bUpgraded = true;
//need to change which IMD is used for player 0
//Need to do a check its not Barbarian really!
if ( (bMultiPlayer && isHumanPlayer(psBuilding->player))
|| (bMultiPlayer && (game.type == SKIRMISH) && (psBuilding->player < game.maxPlayers))
|| !bMultiPlayer)
{
capacity = psBuilding->pFunctionality->powerGenerator.capacity;
if (capacity < NUM_POWER_MODULES)
{
psBuilding->sDisplay.imd = powerModuleIMDs[capacity-1];
}
else
{
psBuilding->sDisplay.imd = powerModuleIMDs[NUM_POWER_MODULES-1];
}
}
//need to inform any res Extr associated that not digging until complete
releasePowerGen(psBuilding);
}
}
if (bUpgraded)
{
//calculate the new body points of the owning structure
psBuilding->body = (UWORD)(structureBody(psBuilding) * bodyDiff);
//initialise the build points
psBuilding->currentBuildPts = 0;
psBuilding->currentPowerAccrued = 0;
//start building again
psBuilding->status = SS_BEING_BUILT;
if (psBuilding->player == selectedPlayer && !FromSave)
{
intRefreshScreen();
}
//inform power system that won't be needing power until built
powerDestroyObject((BASE_OBJECT *)psBuilding);
}
}
if(pStructureType->type!=REF_WALL && pStructureType->type!=REF_WALLCORNER)
{
if(player == selectedPlayer)
{
scoreUpdateVar(WD_STR_BUILT);
}
}
/* why is this necessary - it makes tiles under the structure visible */
setUnderTilesVis((BASE_OBJECT*)psBuilding,player);
return psBuilding;
}
BOOL setFunctionality(STRUCTURE *psBuilding, STRUCTURE_TYPE functionType)
{
CHECK_STRUCTURE(psBuilding);
switch (functionType)
{
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
case REF_RESEARCH:
case REF_POWER_GEN:
case REF_RESOURCE_EXTRACTOR:
case REF_REPAIR_FACILITY:
case REF_REARM_PAD:
// Allocate space for the buildings functionality
psBuilding->pFunctionality = calloc(1, sizeof(*psBuilding->pFunctionality));
if (psBuilding->pFunctionality == NULL)
{
debug(LOG_ERROR, "Out of memory");
abort();
return false;
}
break;
default:
psBuilding->pFunctionality = NULL;
break;
}
switch (functionType)
{
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
{
FACTORY* psFactory = &psBuilding->pFunctionality->factory;
unsigned int x, y;
psFactory->capacity = (UBYTE) ((PRODUCTION_FUNCTION*)psBuilding->pStructureType->asFuncList[0])->capacity;
psFactory->productionOutput = (UBYTE) ((PRODUCTION_FUNCTION*)psBuilding->pStructureType->asFuncList[0])->productionOutput;
psFactory->psSubject = NULL;
// Default the secondary order - AB 22/04/99
psFactory->secondaryOrder = DSS_ARANGE_DEFAULT | DSS_REPLEV_NEVER
| DSS_ALEV_ALWAYS | DSS_HALT_GUARD;
// Create the assembly point for the factory
if (!createFlagPosition(&psFactory->psAssemblyPoint, psBuilding->player))
{
return false;
}
// initialise the assembly point position
x = map_coord(psBuilding->pos.x + 256);
y = map_coord(psBuilding->pos.y + 256);
// Set the assembly point
setAssemblyPoint(psFactory->psAssemblyPoint, world_coord(x), world_coord(y), psBuilding->player, true);
// Add the flag to the list
addFlagPosition(psFactory->psAssemblyPoint);
switch (functionType)
{
case REF_FACTORY:
setFlagPositionInc(psBuilding->pFunctionality, psBuilding->player, FACTORY_FLAG);
break;
case REF_CYBORG_FACTORY:
setFlagPositionInc(psBuilding->pFunctionality, psBuilding->player, CYBORG_FLAG);
break;
case REF_VTOL_FACTORY:
setFlagPositionInc(psBuilding->pFunctionality, psBuilding->player, VTOL_FLAG);
break;
default:
ASSERT(!"invalid factory type", "Invalid factory type");
}
// Take advantage of upgrades
structureProductionUpgrade(psBuilding);
break;
}
case REF_RESEARCH:
{
RESEARCH_FACILITY* psResFac = &psBuilding->pFunctionality->researchFacility;
psResFac->researchPoints = ((RESEARCH_FUNCTION *) psBuilding->pStructureType->asFuncList[0])->researchPoints;
// Take advantage of upgrades
structureResearchUpgrade(psBuilding);
break;
}
case REF_POWER_GEN:
{
POWER_GEN* psPowerGen = &psBuilding->pFunctionality->powerGenerator;
psPowerGen->power = ((POWER_GEN_FUNCTION *) psBuilding->pStructureType->asFuncList[0])->powerOutput;
psPowerGen->multiplier = ((POWER_GEN_FUNCTION *) psBuilding->pStructureType->asFuncList[0])->powerMultiplier;
psPowerGen->capacity = 0;
// Take advantage of upgrades
structurePowerUpgrade(psBuilding);
break;
}
case REF_RESOURCE_EXTRACTOR:
{
RES_EXTRACTOR* psResExtracter = &psBuilding->pFunctionality->resourceExtractor;
psResExtracter->power = ((RESOURCE_FUNCTION*)psBuilding->pStructureType->asFuncList[0])->maxPower;
// Make the structure inactive
psResExtracter->active = false;
psResExtracter->psPowerGen = NULL;
break;
}
case REF_HQ:
{
// If an HQ has just been built make sure the radar is displayed!
radarOnScreen = true;
break;
}
case REF_REPAIR_FACILITY:
{
REPAIR_FACILITY* psRepairFac = &psBuilding->pFunctionality->repairFacility;
REPAIR_DROID_FUNCTION* pFuncRepair = (REPAIR_DROID_FUNCTION*)psBuilding->pStructureType->asFuncList[0];
unsigned int x, y;
psRepairFac->power = pFuncRepair->repairPoints;
psRepairFac->psObj = NULL;
psRepairFac->droidQueue = 0;
if (!grpCreate(&((REPAIR_FACILITY*)psBuilding->pFunctionality)->psGroup))
{
debug(LOG_NEVER, "couldn't create repair facility group");
}
else
{
// Add NULL droid to the group
grpJoin(psRepairFac->psGroup, NULL);
}
// Take advantage of upgrades
structureRepairUpgrade(psBuilding);
// Create an assembly point for repaired droids
if (!createFlagPosition(&psRepairFac->psDeliveryPoint, psBuilding->player))
{
return false;
}
// Initialise the assembly point
x = map_coord(psBuilding->pos.x+256);
y = map_coord(psBuilding->pos.y+256);
// Set the assembly point
setAssemblyPoint(psRepairFac->psDeliveryPoint, world_coord(x),
world_coord(y), psBuilding->player, true);
// Add the flag (triangular marker on the ground) at the delivery point
addFlagPosition(psRepairFac->psDeliveryPoint);
setFlagPositionInc(psBuilding->pFunctionality, psBuilding->player, REPAIR_FLAG);
break;
}
case REF_REARM_PAD:
{
REARM_PAD* psReArmPad = &psBuilding->pFunctionality->rearmPad;
psReArmPad->reArmPoints = ((REARM_PAD *)psBuilding->pStructureType->asFuncList[0])->reArmPoints;
// Take advantage of upgrades
structureReArmUpgrade(psBuilding);
break;
}
// Structure types without a FUNCTIONALITY
default:
break;
}
return true;
}
// Set the command droid that factory production should go to
void assignFactoryCommandDroid(STRUCTURE *psStruct, DROID *psCommander)
{
FACTORY *psFact;
FLAG_POSITION *psFlag, *psNext, *psPrev;
SDWORD factoryInc,typeFlag;
CHECK_STRUCTURE(psStruct);
ASSERT( StructIsFactory(psStruct),"assignFactoryCommandUnit: structure not a factory" );
psFact = &psStruct->pFunctionality->factory;
switch(psStruct->pStructureType->type)
{
case REF_FACTORY:
typeFlag = FACTORY_FLAG;
break;
case REF_VTOL_FACTORY:
typeFlag = VTOL_FLAG;
break;
case REF_CYBORG_FACTORY:
typeFlag = CYBORG_FLAG;
break;
default:
ASSERT(!"unknown factory type", "assignfactorycommandUnit: unknown factory type");
typeFlag = FACTORY_FLAG;
break;
}
// removing a commander from a factory
if ( psFact->psCommander != NULL )
{
if (typeFlag == FACTORY_FLAG)
{
secondarySetState(psFact->psCommander, DSO_CLEAR_PRODUCTION,
(SECONDARY_STATE)(1 << ( psFact->psAssemblyPoint->factoryInc + DSS_ASSPROD_SHIFT)) );
}
else if (typeFlag == CYBORG_FLAG)
{
secondarySetState(psFact->psCommander, DSO_CLEAR_PRODUCTION,
(SECONDARY_STATE)(1 << ( psFact->psAssemblyPoint->factoryInc + DSS_ASSPROD_CYBORG_SHIFT)) );
}
else
{
secondarySetState(psFact->psCommander, DSO_CLEAR_PRODUCTION,
(SECONDARY_STATE)(1 << ( psFact->psAssemblyPoint->factoryInc + DSS_ASSPROD_VTOL_SHIFT)) );
}
psFact->psCommander = NULL;
if (!missionIsOffworld())
{
addFlagPosition(psFact->psAssemblyPoint); // add the assembly point back into the list
}
else
{
psFact->psAssemblyPoint->psNext = mission.apsFlagPosLists[psFact->psAssemblyPoint->player];
mission.apsFlagPosLists[psFact->psAssemblyPoint->player] = psFact->psAssemblyPoint;
}
}
if ( psCommander != NULL )
{
ASSERT( !missionIsOffworld(), "assignFactoryCommandDroid: cannot assign a commander to a factory when off world" );
factoryInc = psFact->psAssemblyPoint->factoryInc;
psPrev = NULL;
for (psFlag = apsFlagPosLists[psStruct->player]; psFlag; psFlag = psNext)
{
psNext = psFlag->psNext;
if ( (psFlag->factoryInc == factoryInc) && (psFlag->factoryType == typeFlag) )
{
if ( psFlag != psFact->psAssemblyPoint )
{
removeFlagPosition(psFlag);
}
else
{
// need to keep the assembly point(s) for the factory
// but remove it(the primary) from the list so it doesn't get
// displayed
if ( psPrev == NULL )
{
apsFlagPosLists[psStruct->player] = psFlag->psNext;
}
else
{
psPrev->psNext = psFlag->psNext;
}
psFlag->psNext = NULL;
}
}
else
{
psPrev = psFlag;
}
}
psFact->psCommander = psCommander;
}
}
// remove all factories from a command droid
void clearCommandDroidFactory(DROID *psDroid)
{
STRUCTURE *psCurr;
for(psCurr = apsStructLists[selectedPlayer]; psCurr; psCurr=psCurr->psNext)
{
if ( (psCurr->pStructureType->type == REF_FACTORY) ||
(psCurr->pStructureType->type == REF_CYBORG_FACTORY) ||
(psCurr->pStructureType->type == REF_VTOL_FACTORY) )
{
if (psCurr->pFunctionality->factory.psCommander == psDroid)
{
assignFactoryCommandDroid(psCurr, NULL);
}
}
}
for(psCurr = mission.apsStructLists[selectedPlayer]; psCurr; psCurr=psCurr->psNext)
{
if ( (psCurr->pStructureType->type == REF_FACTORY) ||
(psCurr->pStructureType->type == REF_CYBORG_FACTORY) ||
(psCurr->pStructureType->type == REF_VTOL_FACTORY) )
{
if (psCurr->pFunctionality->factory.psCommander == psDroid)
{
assignFactoryCommandDroid(psCurr, NULL);
}
}
}
}
/* Check that a tile is vacant for a droid to be placed */
static BOOL structClearTile(UWORD x, UWORD y)
{
UDWORD player;
DROID *psCurr;
/* Check for a structure */
if (fpathBlockingTile(x, y, PROPULSION_TYPE_WHEELED))
{
debug(LOG_NEVER, "failed - blocked");
return false;
}
/* Check for a droid */
for(player=0; player< MAX_PLAYERS; player++)
{
for(psCurr = apsDroidLists[player]; psCurr; psCurr=psCurr->psNext)
{
if (map_coord(psCurr->pos.x) == x
&& map_coord(psCurr->pos.y) == y)
{
debug(LOG_NEVER, "failed - not vacant");
return false;
}
}
}
debug(LOG_NEVER, "succeeded");
return true;
}
/*find a location near to a structure to start the droid of*/
BOOL placeDroid(STRUCTURE *psStructure, UDWORD *droidX, UDWORD *droidY)
{
SWORD sx,sy, xmin,xmax, ymin,ymax, x,y, xmid;
BOOL placed;
CHECK_STRUCTURE(psStructure);
/* Get the tile coords for the top left of the structure */
sx = (SWORD)(psStructure->pos.x - psStructure->pStructureType->baseWidth * TILE_UNITS/2);
sx = map_coord(sx);
sy = (SWORD)(psStructure->pos.y - psStructure->pStructureType->baseBreadth * TILE_UNITS/2);
sy = map_coord(sy);
/* Find the four corners of the square */
xmin = (SWORD)(sx - 1);
xmax = (SWORD)(sx + psStructure->pStructureType->baseWidth);
xmid = (SWORD)(sx + (psStructure->pStructureType->baseWidth-1)/2);
ymin = (SWORD)(sy - 1);
ymax = (SWORD)(sy + psStructure->pStructureType->baseBreadth);
if (xmin < 0)
{
xmin = 0;
}
if (xmax > (SDWORD)mapWidth)
{
xmax = (SWORD)mapWidth;
}
if (ymin < 0)
{
ymin = 0;
}
if (ymax > (SDWORD)mapHeight)
{
ymax = (SWORD)mapHeight;
}
/* Look for a clear location for the droid across the bottom */
/* start in the middle */
placed = false;
y = ymax;
/* middle to right */
for(x = xmid; x < xmax; x++)
{
if (structClearTile(x, y))
{
placed = true;
break;
}
}
/* left to middle */
if (!placed)
{
for(x = xmin; x < xmid; x++)
{
if (structClearTile(x, y))
{
placed = true;
break;
}
}
}
/* across the top */
if (!placed)
{
y = ymin;
for(x = xmin; x < xmax; x++)
{
if (structClearTile(x, y))
{
placed = true;
break;
}
}
}
/* the left */
if (!placed)
{
x = xmin;
for(y = ymin; y < ymax; y++)
{
if (structClearTile(x, y))
{
placed = true;
break;
}
}
}
/* the right */
if (!placed)
{
x = xmax;
for(y = ymin; y < ymax; y++)
{
if (structClearTile(x, y))
{
placed = true;
break;
}
}
}
*droidX = x;
*droidY = y;
return placed;
}
/* Place a newly manufactured droid next to a factory and then send if off
to the assembly point, returns true if droid was placed successfully */
static BOOL structPlaceDroid(STRUCTURE *psStructure, DROID_TEMPLATE *psTempl,
DROID **ppsDroid)
{
UDWORD x,y;
BOOL placed;//bTemp = false;
DROID *psNewDroid;
FACTORY *psFact;
SDWORD apx,apy;
FLAG_POSITION *psFlag;
Vector3i iVecEffect;
UBYTE factoryType;
BOOL assignCommander;
CHECK_STRUCTURE(psStructure);
placed = placeDroid(psStructure, &x, &y);
if (placed)
{
//create a droid near to the structure
psNewDroid = buildDroid(psTempl, world_coord(x), world_coord(y),
psStructure->player, false);
if (!psNewDroid)
{
*ppsDroid = NULL;
return false;
}
//set the droids order to that of the factory - AB 22/04/99
psNewDroid->secondaryOrder = psStructure->pFunctionality->factory.secondaryOrder;
if(bMultiPlayer)
{
sendDroidSecondaryAll(psNewDroid);
}
if(psStructure->visible[selectedPlayer])
{
/* add smoke effect to cover the droid's emergence from the factory */
iVecEffect.x = psNewDroid->pos.x;
iVecEffect.y = map_Height( psNewDroid->pos.x, psNewDroid->pos.y ) + DROID_CONSTRUCTION_SMOKE_HEIGHT;
iVecEffect.z = psNewDroid->pos.y;
addEffect( &iVecEffect,EFFECT_CONSTRUCTION,CONSTRUCTION_TYPE_DRIFTING,false,NULL,0 );
iVecEffect.x = psNewDroid->pos.x - DROID_CONSTRUCTION_SMOKE_OFFSET;
iVecEffect.z = psNewDroid->pos.y - DROID_CONSTRUCTION_SMOKE_OFFSET;
addEffect( &iVecEffect,EFFECT_CONSTRUCTION,CONSTRUCTION_TYPE_DRIFTING,false,NULL,0 );
iVecEffect.z = psNewDroid->pos.y + DROID_CONSTRUCTION_SMOKE_OFFSET;
addEffect( &iVecEffect,EFFECT_CONSTRUCTION,CONSTRUCTION_TYPE_DRIFTING,false,NULL,0 );
iVecEffect.x = psNewDroid->pos.x + DROID_CONSTRUCTION_SMOKE_OFFSET;
addEffect( &iVecEffect,EFFECT_CONSTRUCTION,CONSTRUCTION_TYPE_DRIFTING,false,NULL,0 );
iVecEffect.z = psNewDroid->pos.y - DROID_CONSTRUCTION_SMOKE_OFFSET;
addEffect( &iVecEffect,EFFECT_CONSTRUCTION,CONSTRUCTION_TYPE_DRIFTING,false,NULL,0 );
}
/* add the droid to the list */
addDroid(psNewDroid, apsDroidLists);
*ppsDroid = psNewDroid;
if ( psNewDroid->player == selectedPlayer )
{
audio_QueueTrack( ID_SOUND_DROID_COMPLETED );
intRefreshScreen(); // update any interface implications.
}
// update the droid counts
incNumDroids(psNewDroid->player);
switch (psNewDroid->droidType)
{
case DROID_COMMAND:
incNumCommandDroids(psNewDroid->player);
break;
case DROID_CONSTRUCT:
case DROID_CYBORG_CONSTRUCT:
incNumConstructorDroids(psNewDroid->player);
break;
default:
break;
}
psFact = &psStructure->pFunctionality->factory;
apx = psFact->psAssemblyPoint->coords.x;
apy = psFact->psAssemblyPoint->coords.y;
// if we've built a command droid - make sure that it isn't assigned to another commander
assignCommander = false;
if ((psNewDroid->droidType == DROID_COMMAND) &&
(psFact->psCommander != NULL))
{
assignFactoryCommandDroid(psStructure, NULL);
assignCommander = true;
}
if ( psFact->psCommander != NULL )
{
if (idfDroid(psNewDroid) ||
isVtolDroid(psNewDroid))
{
orderDroidObj(psNewDroid, DORDER_FIRESUPPORT, (BASE_OBJECT *)psFact->psCommander);
moveToRearm(psNewDroid);
}
else
{
cmdDroidAddDroid(psFact->psCommander, psNewDroid);
}
}
else
{
//check flag against factory type
factoryType = FACTORY_FLAG;
if (psStructure->pStructureType->type == REF_CYBORG_FACTORY)
{
factoryType = CYBORG_FLAG;
}
else if (psStructure->pStructureType->type == REF_VTOL_FACTORY)
{
factoryType = VTOL_FLAG;
}
//if vtol droid - send it to ReArm Pad if one exists
placed = false;
if (isVtolDroid(psNewDroid))
{
moveToRearm(psNewDroid);
}
if (!placed)
{
//find flag in question.
for(psFlag = apsFlagPosLists[psFact->psAssemblyPoint->player];
!( (psFlag->factoryInc == psFact->psAssemblyPoint->factoryInc) // correct fact.
&&(psFlag->factoryType == factoryType)); // correct type
psFlag = psFlag->psNext);
if (psNewDroid->droidType == DROID_TRANSPORTER)
{
UDWORD droidX = psFlag->coords.x;
UDWORD droidY = psFlag->coords.y;
//find a suitable location near the delivery point
actionVTOLLandingPos(psNewDroid, &droidX, &droidY);
actionDroidLoc(psNewDroid, DACTION_MOVE, droidX,droidY);
}
else
{
orderDroidLoc(psNewDroid,DORDER_MOVE,psFlag->coords.x,psFlag->coords.y);
}
}
}
if (assignCommander)
{
assignFactoryCommandDroid(psStructure, psNewDroid);
}
if ( psNewDroid->player == selectedPlayer )
{
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_DROIDBUILT);
}
return true;
}
else
{
*ppsDroid = NULL;
}
return false;
}
static BOOL IsFactoryCommanderGroupFull(FACTORY *psFactory)
{
SDWORD DroidsInGroup;
// If we don't have a commander return false (group not full)
if (psFactory->psCommander==NULL) return false;
// allow any number of IDF droids
if (templateIsIDF((DROID_TEMPLATE *)psFactory->psSubject))
{
return false;
}
// Get the number of droids in the commanders group
DroidsInGroup = psFactory->psCommander->psGroup ? grpNumMembers( psFactory->psCommander->psGroup ) : 0;
// if the number in group is less than the maximum allowed then return false (group not full)
if (DroidsInGroup < cmdDroidMaxGroup( psFactory->psCommander )) return false;
// the number in group has reached the maximum
return true;
}
// Disallow manufacture of units once these limits are reached,
// dos'nt mean that these numbers can't be exceeded if units are
// put down in the editor or by the scripts.
static UWORD MaxDroidsAllowedPerPlayer[MAX_PLAYERS] = {100, 999, 999, 999, 999, 999, 999, 999};
static UWORD MaxDroidsAllowedPerPlayerMultiPlayer[MAX_PLAYERS] = {300, 300, 300, 300, 300, 300, 300, 300};
BOOL IsPlayerStructureLimitReached(UDWORD PlayerNumber)
{
// PC currently doesn't limit number of structures a player can build.
return false;
}
UDWORD getMaxDroids(UDWORD PlayerNumber)
{
return (bMultiPlayer ? MaxDroidsAllowedPerPlayerMultiPlayer[PlayerNumber] : MaxDroidsAllowedPerPlayer[PlayerNumber] );
}
BOOL IsPlayerDroidLimitReached(UDWORD PlayerNumber)
{
unsigned int numDroids = getNumDroids(PlayerNumber) + getNumMissionDroids(PlayerNumber) + getNumTransporterDroids(PlayerNumber);
if (bMultiPlayer)
{
if ( numDroids >= MaxDroidsAllowedPerPlayerMultiPlayer[PlayerNumber] )
return true;
}
else
{
if( numDroids >= MaxDroidsAllowedPerPlayer[PlayerNumber] )
return true;
}
return false;
}
static BOOL maxDroidsByTypeReached(STRUCTURE *psStructure)
{
FACTORY *psFact = &psStructure->pFunctionality->factory;
CHECK_STRUCTURE(psStructure);
if (droidTemplateType((DROID_TEMPLATE *)psFact->psSubject) == DROID_COMMAND
&& getNumCommandDroids(psStructure->player) >= MAX_COMMAND_DROIDS)
{
return true;
}
if ((droidTemplateType((DROID_TEMPLATE *)psFact->psSubject) == DROID_CONSTRUCT
|| droidTemplateType((DROID_TEMPLATE *)psFact->psSubject) == DROID_CYBORG_CONSTRUCT)
&& getNumConstructorDroids(psStructure->player) >= MAX_CONSTRUCTOR_DROIDS)
{
return true;
}
return false;
}
// Check for max number of units reached and halt production.
//
BOOL CheckHaltOnMaxUnitsReached(STRUCTURE *psStructure)
{
CHECK_STRUCTURE(psStructure);
// if the players that owns the factory has reached his (or hers) droid limit
// then put production on hold & return - we need a message to be displayed here !!!!!!!
if (IsPlayerDroidLimitReached(psStructure->player) ||
maxDroidsByTypeReached(psStructure))
{
if ((psStructure->player == selectedPlayer) &&
(lastMaxUnitMessage + MAX_UNIT_MESSAGE_PAUSE < gameTime))
{
addConsoleMessage(_("Command Control Limit Reached - Production Halted"),DEFAULT_JUSTIFY,SYSTEM_MESSAGE);
lastMaxUnitMessage = gameTime;
}
return true;
}
return false;
}
static void aiUpdateStructure(STRUCTURE *psStructure)
{
BASE_STATS *pSubject = NULL;
UDWORD pointsToAdd;//, iPower;
PLAYER_RESEARCH *pPlayerRes = asPlayerResList[psStructure->player];
UDWORD structureMode = 0;
DROID *psDroid;
BASE_OBJECT *psChosenObjs[STRUCT_MAXWEAPS] = {NULL};
BASE_OBJECT *psChosenObj = NULL;
UBYTE Quantity;
SDWORD iDt;
FACTORY *psFactory;
REPAIR_FACILITY *psRepairFac = NULL;
RESEARCH_FACILITY *psResFacility;
REARM_PAD *psReArmPad;
Vector3i iVecEffect;
BOOL bFinishAction,bDroidPlaced;
WEAPON_STATS *psWStats;
SDWORD xdiff,ydiff, mindist, currdist;
#ifdef INCLUDE_FACTORYLISTS
DROID_TEMPLATE *psNextTemplate;
#endif
UDWORD i;
CHECK_STRUCTURE(psStructure);
for (i = 0; i < DROID_MAXWEAPS; i++)
{
if (psStructure->psTarget[i] && psStructure->psTarget[i]->died)
{
setStructureTarget(psStructure, NULL, i);
}
}
// Will go out into a building EVENT stats/text file
/* Spin round yer sensors! */
if (psStructure->numWeaps == 0)
{
if ((psStructure->asWeaps[0].nStat == 0) &&
(psStructure->pStructureType->type != REF_REPAIR_FACILITY))
{
//////
// - radar should rotate every three seconds ... 'cause we timed it at Heathrow !
// gameTime is in milliseconds - one rotation every 3 seconds = 1 rotation event 3000 millisecs
psStructure->turretRotation[0] = (UWORD)(((gameTime*360)/3000)%360);
psStructure->turretPitch[0] = 0;
}
}
structUpdateRecoil(psStructure);
/* See if there is an enemy to attack */
if (psStructure->numWeaps > 0)
{
//structures always update their targets
for (i = 0;i < psStructure->numWeaps;i++)
{
if (psStructure->asWeaps[i].nStat > 0 &&
asWeaponStats[psStructure->asWeaps[i].nStat].weaponSubClass != WSC_LAS_SAT)
{
if ((psStructure->id % 20) == (frameGetFrameNumber() % 20))
{
if ( aiChooseTarget((BASE_OBJECT *)psStructure, &psChosenObjs[i], i, true) )
{
debug( LOG_ATTACK, "Struct(%d) attacking : %d\n",
psStructure->id, psChosenObjs[i]->id );
setStructureTarget(psStructure, psChosenObjs[i], i);
}
else
{
if ( aiChooseTarget((BASE_OBJECT *)psStructure, &psChosenObjs[0], 0, true) )
{
if (psChosenObjs[0])
{
debug( LOG_ATTACK, "Struct(%d) attacking : %d\n",
psStructure->id, psChosenObjs[0]->id );
setStructureTarget(psStructure, psChosenObjs[0], i);
psChosenObjs[i] = psChosenObjs[0];
}
else
{
setStructureTarget(psStructure, NULL, i);
psChosenObjs[i] = NULL;
}
}
else
{
setStructureTarget(psStructure, NULL, i);
psChosenObjs[i] = NULL;
}
}
}
else
{
psChosenObjs[i] = psStructure->psTarget[0];
}
if (psChosenObjs[i] != NULL)
{
// get the weapon stat to see if there is a visible turret to rotate
psWStats = asWeaponStats + psStructure->asWeaps[i].nStat;
//if were going to shoot at something move the turret first then fire when locked on
if (psWStats->pMountGraphic == NULL)//no turret so lock on whatever
{
psStructure->turretRotation[i] = (UWORD)calcDirection(psStructure->pos.x,
psStructure->pos.y, psChosenObjs[i]->pos.x, psChosenObjs[i]->pos.y);
combFire(&psStructure->asWeaps[i], (BASE_OBJECT *)psStructure, psChosenObjs[i], i);
}
else if(actionTargetTurret((BASE_OBJECT*)psStructure, psChosenObjs[i],
&(psStructure->turretRotation[i]),
&(psStructure->turretPitch[i]),
psWStats, false, i))
{
combFire(&psStructure->asWeaps[i], (BASE_OBJECT *)psStructure, psChosenObjs[i], i);
}
}
else
{
// realign the turret
if ( ((psStructure->turretRotation[i] % 90) != 0) ||
(psStructure->turretPitch[i] != 0) )
{
actionAlignTurret((BASE_OBJECT *)psStructure, i);
}
}
}
}
}
/* See if there is an enemy to attack for Sensor Towers that have weapon droids attached*/
else if (psStructure->pStructureType->pSensor)
{
if (structStandardSensor(psStructure) || structVTOLSensor(psStructure))
{
if ((psStructure->id % 20) == (frameGetFrameNumber() % 20))
{
if (aiChooseSensorTarget((BASE_OBJECT *)psStructure, &psChosenObj))
{
debug( LOG_ATTACK, "Struct(%d) attacking : %d\n",
psStructure->id, psChosenObj->id );
setStructureTarget(psStructure, psChosenObj, 0);
}
else
{
setStructureTarget(psStructure, NULL, 0);
}
}
psChosenObj = psStructure->psTarget[0];
}
else
{
psChosenObj = psStructure->psTarget[0];
}
// you can always see anything that a CB sensor is targeting
// Anyone commenting this out again will get a knee capping from John.
// You have been warned!!
if ((structCBSensor(psStructure) || structVTOLCBSensor(psStructure)) &&
psStructure->psTarget[0] != NULL)
{
psStructure->psTarget[0]->visible[psStructure->player] = UBYTE_MAX;
}
}
//only interested if the Structure "does" something!
if (psStructure->pFunctionality == NULL)
{
return;
}
//check if any power available
if (structUsesPower(psStructure))
{
if (checkPower(psStructure->player, POWER_PER_CYCLE, false))
{
//check if this structure is due some power
if (getLastPowered((BASE_OBJECT *)psStructure))
{
//get some power if necessary
if (accruePower((BASE_OBJECT *)psStructure))
{
updateLastPowered((BASE_OBJECT *)psStructure, psStructure->player);
}
}
}
}
/* Process the functionality according to type
* determine the subject stats (for research or manufacture)
* or base object (for repair) or update power levels for resourceExtractor
*/
switch (psStructure->pStructureType->type)
{
case REF_RESEARCH:
{
pSubject = psStructure->pFunctionality->researchFacility.psSubject;
structureMode = REF_RESEARCH;
break;
}
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
{
pSubject = psStructure->pFunctionality->factory.psSubject;
structureMode = REF_FACTORY;
//check here to see if the factory's commander has died
if (psStructure->pFunctionality->factory.psCommander &&
psStructure->pFunctionality->factory.psCommander->died)
{
//remove the commander from the factory
assignFactoryCommandDroid(psStructure, NULL);
}
break;
}
case REF_REPAIR_FACILITY:
{
psRepairFac = &psStructure->pFunctionality->repairFacility;
psChosenObj = psRepairFac->psObj;
structureMode = REF_REPAIR_FACILITY;
psDroid = (DROID *)psChosenObj;
// skip droids that are doing anything else
if (psDroid != NULL
&& (!orderState(psDroid, DORDER_RTR)
|| psDroid->psTarget != (BASE_OBJECT *)psStructure))
{
objTrace(psStructure->id, "Dropping repair target %d; wrong order=%d, wrong target=%d",
(int)psChosenObj->id, (int)(!orderState(psDroid, DORDER_RTR)), (int)(psDroid->psTarget != (BASE_OBJECT *)psStructure));
psChosenObj = NULL;
psDroid = NULL;
psRepairFac->psObj = NULL;
}
/* select next droid if none being repaired */
if ( psChosenObj == NULL )
{
ASSERT( psRepairFac->psGroup != NULL,
"aiUpdateStructure: invalid repair facility group pointer" );
mindist = SDWORD_MAX;
psRepairFac->droidQueue = 0;
for(psDroid = apsDroidLists[psStructure->player]; psDroid; psDroid = psDroid->psNext)
{
BASE_OBJECT * const psTarget = orderStateObj(psDroid, DORDER_RTR);
if (psTarget && psTarget == (BASE_OBJECT *)psStructure && psDroid->action == DACTION_WAITFORREPAIR)
{
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psStructure->pos.x;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psStructure->pos.y;
currdist = xdiff*xdiff + ydiff*ydiff;
if (currdist < mindist)
{
mindist = currdist;
psChosenObj = (BASE_OBJECT *)psDroid;
}
psRepairFac->droidQueue++;
}
}
psDroid = (DROID *)psChosenObj;
if (psDroid)
{
objTrace(psStructure->id, "Chose to repair droid %d", (int)psDroid->id);
objTrace(psDroid->id, "Chosen to be repaired by repair structure %d", (int)psStructure->id);
}
}
/* Steal droid from another repair facility */
if (psChosenObj == NULL)
{
mindist = SDWORD_MAX;
psRepairFac->droidQueue = 0;
for(psDroid = apsDroidLists[psStructure->player]; psDroid; psDroid = psDroid->psNext)
{
BASE_OBJECT *const psTarget = orderStateObj(psDroid, DORDER_RTR);
if (psTarget != (BASE_OBJECT *)psStructure && psDroid->action == DACTION_WAITFORREPAIR)
{
REPAIR_FACILITY *stealFrom = &((STRUCTURE *)psTarget)->pFunctionality->repairFacility;
// make a wild guess about what is a good distance
int distLimit = world_coord(stealFrom->droidQueue) * world_coord(stealFrom->droidQueue) * 10;
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psStructure->pos.x;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psStructure->pos.y;
currdist = xdiff * xdiff + ydiff * ydiff;
if (currdist < mindist && currdist < distLimit)
{
mindist = currdist;
psChosenObj = (BASE_OBJECT *)psDroid;
psRepairFac->droidQueue++; // shared queue
}
}
}
psDroid = (DROID *)psChosenObj;
if (psDroid)
{
objTrace(psStructure->id, "Chose to steal droid %d from another repair queue to repair it", (int)psDroid->id);
objTrace(psDroid->id, "Stolen by repair structure %d because current queue too long", (int)psStructure->id);
}
}
// send the droid to be repaired
if (psDroid && psDroid->action == DACTION_WAITFORREPAIR)
{
/* set chosen object */
psChosenObj = (BASE_OBJECT *)psDroid;
psRepairFac->psObj = (BASE_OBJECT *)psDroid;
psDroid->psTarget = (BASE_OBJECT *)psStructure;
/* move droid to repair point at rear of facility */
objTrace(psStructure->id, "Requesting droid %d to come to us", (int)psDroid->id);
actionDroidObjLoc( psDroid, DACTION_MOVETOREPAIRPOINT,
(BASE_OBJECT *) psStructure, psStructure->pos.x, psStructure->pos.y);
/* reset repair started */
psRepairFac->timeStarted = ACTION_START_TIME;
psRepairFac->currentPtsAdded = 0;
break;
}
break;
}
case REF_REARM_PAD:
{
psReArmPad = &psStructure->pFunctionality->rearmPad;
psChosenObj = psReArmPad->psObj;
structureMode = REF_REARM_PAD;
psDroid = NULL;
/* select next droid if none being rearmed*/
if (psChosenObj == NULL)
{
for(psDroid = apsDroidLists[psStructure->player]; psDroid;
psDroid = psDroid->psNext)
{
// move next droid waiting on ground to rearm pad
if (vtolReadyToRearm(psDroid, psStructure) &&
(psChosenObj == NULL || (((DROID *)psChosenObj)->actionStarted > psDroid->actionStarted)) )
{
psChosenObj = (BASE_OBJECT *)psDroid;
}
}
psDroid = (DROID *)psChosenObj;
if (psDroid != NULL)
{
actionDroidObj( psDroid, DACTION_MOVETOREARMPOINT, (BASE_OBJECT *)psStructure);
}
}
else
{
psDroid = (DROID *) psChosenObj;
if ( (psDroid->sMove.Status == MOVEINACTIVE ||
psDroid->sMove.Status == MOVEHOVER ) &&
psDroid->action == DACTION_WAITFORREARM )
{
actionDroidObj( psDroid, DACTION_MOVETOREARMPOINT, (BASE_OBJECT *)psStructure);
}
}
// if found a droid to rearm assign it to the rearm pad
if (psDroid != NULL)
{
/* set chosen object */
psChosenObj = (BASE_OBJECT *)psDroid;
psReArmPad->psObj = psChosenObj;
if ( psDroid->action == DACTION_MOVETOREARMPOINT )
{
/* reset rearm started */
psReArmPad->timeStarted = ACTION_START_TIME;
psReArmPad->currentPtsAdded = 0;
}
}
break;
}
default:
break;
}
/* check subject stats (for research or manufacture) */
if (pSubject != NULL)
{
//if subject is research...
if (structureMode == REF_RESEARCH)
{
psResFacility = &psStructure->pFunctionality->researchFacility;
//if on hold don't do anything
if (psResFacility->timeStartHold)
{
return;
}
//electronic warfare affects the functionality of some structures in multiPlayer
if (bMultiPlayer)
{
if (psStructure->resistance < (SWORD)structureResistance(psStructure->
pStructureType, psStructure->player))
{
return;
}
}
pPlayerRes += (pSubject->ref - REF_RESEARCH_START);
//check research has not already been completed by another structure
if (IsResearchCompleted(pPlayerRes)==0)
{
//check to see if enough power to research has accrued
if (psResFacility->powerAccrued < ((RESEARCH *)pSubject)->researchPower)
{
//wait until enough power
return;
}
// don't update if not responsible (106)
if(bMultiPlayer && !myResponsibility(psStructure->player))
{
return;
}
if (psResFacility->timeStarted == ACTION_START_TIME)
{
//set the time started
psResFacility->timeStarted = gameTime;
}
pointsToAdd = (psResFacility->researchPoints * (gameTime -
psResFacility->timeStarted)) / GAME_TICKS_PER_SEC;
//check if Research is complete
if ((pointsToAdd + pPlayerRes->currentPoints) > (
(RESEARCH *)pSubject)->researchPoints)
{
if(bMultiPlayer)
{
SendResearch(psStructure->player,pSubject->ref - REF_RESEARCH_START);
}
//store the last topic researched - if its the best
if (psResFacility->psBestTopic == NULL)
{
psResFacility->psBestTopic = psResFacility->psSubject;
}
else
{
if (((RESEARCH *)psResFacility->psSubject)->researchPoints >
((RESEARCH *)psResFacility->psBestTopic)->researchPoints)
{
psResFacility->psSubject = psResFacility->psSubject;
}
}
psResFacility->psSubject = NULL;
intResearchFinished(psStructure);
researchResult(pSubject->ref - REF_RESEARCH_START,
psStructure->player, true,psStructure);
//check if this result has enabled another topic
intCheckResearchButton();
}
}
else
{
//cancel this Structure's research since now complete
psResFacility->psSubject = NULL;
intResearchFinished(psStructure);
}
}
//check for manufacture
else if (structureMode == REF_FACTORY)
{
psFactory = &psStructure->pFunctionality->factory;
Quantity = psFactory->quantity;
//if on hold don't do anything
if (psFactory->timeStartHold)
{
return;
}
//electronic warfare affects the functionality of some structures in multiPlayer
if (bMultiPlayer)
{
if (psStructure->resistance < (SWORD)structureResistance(psStructure->
pStructureType, psStructure->player))
{
return;
}
}
if (psFactory->timeStarted == ACTION_START_TIME)
{
// also need to check if a command droid's group is full
// If the factory commanders group is full - return
if (IsFactoryCommanderGroupFull(psFactory)==true)
{
return;
}
if(CheckHaltOnMaxUnitsReached(psStructure) == true) {
return;
}
}
//check enough power has accrued to build the droid
if (psFactory->powerAccrued < ((DROID_TEMPLATE *)pSubject)->
powerPoints)
{
//wait until enough power
return;
}
/*must be enough power so subtract that required to build*/
if (psFactory->timeStarted == ACTION_START_TIME)
{
//set the time started
psFactory->timeStarted = gameTime;
}
pointsToAdd = (gameTime - psFactory->timeStarted) / GAME_TICKS_PER_SEC;
//check for manufacture to be complete
if ((pointsToAdd > psFactory->timeToBuild) &&
!IsFactoryCommanderGroupFull(psFactory) &&
!CheckHaltOnMaxUnitsReached(psStructure))
{
/* Place the droid on the map */
bDroidPlaced = structPlaceDroid(psStructure, (DROID_TEMPLATE *)pSubject, &psDroid);
//reset the start time
psFactory->timeStarted = ACTION_START_TIME;
psFactory->powerAccrued = 0;
#ifdef INCLUDE_FACTORYLISTS
//next bit for productionPlayer only
if (productionPlayer == psStructure->player)
{
psNextTemplate = factoryProdUpdate(psStructure,(DROID_TEMPLATE *)pSubject);
if (psNextTemplate)
{
structSetManufacture(psStructure, psNextTemplate,Quantity);
return;
}
else
{
//nothing more to manufacture - reset the Subject and Tab on HCI Form
intManufactureFinished(psStructure);
psFactory->psSubject = NULL;
//script callback, must be called after factory was flagged as idle
if(bDroidPlaced)
{
cbNewDroid(psStructure, psDroid);
}
}
}
else
#endif
{
//decrement the quantity to manufacture if not set to infinity
if (Quantity && Quantity != NON_STOP_PRODUCTION)
{
psFactory->quantity--;
Quantity--;
}
// If quantity not 0 then kick of another manufacture.
if(Quantity)
{
// Manufacture another.
structSetManufacture(psStructure, (DROID_TEMPLATE*)pSubject,Quantity);
return;
}
else
{
//when quantity = 0, reset the Subject and Tab on HCI Form
psFactory->psSubject = NULL;
intManufactureFinished(psStructure);
//script callback, must be called after factory was flagged as idle
if(bDroidPlaced)
{
cbNewDroid(psStructure, psDroid);
}
}
}
}
}
}
/* check base object (for repair / rearm) */
if ( psChosenObj != NULL )
{
if ( structureMode == REF_REPAIR_FACILITY )
{
UDWORD powerCost;
psDroid = (DROID *) psChosenObj;
ASSERT( psDroid != NULL,
"aiUpdateStructure: invalid droid pointer" );
psRepairFac = &psStructure->pFunctionality->repairFacility;
if ( psDroid->action == DACTION_WAITDURINGREPAIR &&
actionTargetTurret((BASE_OBJECT*)psStructure, psChosenObj,
&(psStructure->turretRotation[0]),
&(psStructure->turretPitch[0]),
NULL, false, 0))
{
//check droid is not healthy
if (psDroid->body < psDroid->originalBody)
{
//if in multiPlayer, and a Transporter - make sure its on the ground before repairing
if (bMultiPlayer && psDroid->droidType == DROID_TRANSPORTER)
{
if (!(psDroid->sMove.Status == MOVEINACTIVE &&
psDroid->sMove.iVertSpeed == 0))
{
objTrace(psStructure->id, "Waiting for transporter to land");
return;
}
}
//don't do anything if the resistance is low in multiplayer
if (bMultiPlayer)
{
if (psStructure->resistance < (SWORD)structureResistance(psStructure->
pStructureType, psStructure->player))
{
objTrace(psStructure->id, "Resistance too low for repair");
return;
}
}
//check if enough power to do any
powerCost = powerReqForDroidRepair(psDroid);
if (powerCost > psDroid->powerAccrued)
{
powerCost = (powerCost - psDroid->powerAccrued) / POWER_FACTOR;
}
else
{
powerCost = 0;
}
//if the power cost is 0 (due to rounding) then do for free!
if (powerCost)
{
if (!psDroid->powerAccrued)
{
//reset the actionStarted time and actionPoints added so the correct
//amount of points are added when there is more power
psRepairFac->timeStarted = gameTime;
//init so repair points to add won't be huge when start up again
psRepairFac->currentPtsAdded = 0;
return;
}
}
if (psRepairFac->timeStarted == ACTION_START_TIME)
{
//set the time started
psRepairFac->timeStarted = gameTime;
//reset the points added
psRepairFac->currentPtsAdded = 0;
}
/* do repairing */
iDt = gameTime - psRepairFac->timeStarted;
//- this was a bit exponential ...
pointsToAdd = (iDt * psRepairFac->power / GAME_TICKS_PER_SEC) -
psRepairFac->currentPtsAdded;
bFinishAction = false;
//do some repair
if (pointsToAdd)
{
//just add the points if the power cost is negligable
//if these points would make the droid healthy again then just add
if (!powerCost || (psDroid->body + pointsToAdd >= psDroid->originalBody))
{
//anothe HACK but sorts out all the rounding errors when values get small
psDroid->body += pointsToAdd;
psRepairFac->currentPtsAdded += pointsToAdd;
}
else
{
//see if we have enough power to do this amount of repair
powerCost = pointsToAdd * repairPowerPoint(psDroid);
if (powerCost <= psDroid->powerAccrued)
{
psDroid->body += pointsToAdd;
psRepairFac->currentPtsAdded += pointsToAdd;
//subtract the power cost for these points
psDroid->powerAccrued -= powerCost;
}
else
{
/*reset the actionStarted time and actionPoints added so the correct
amount of points are added when there is more power*/
psRepairFac->timeStarted = gameTime;
psRepairFac->currentPtsAdded = 0;
}
}
}
}
if ( psDroid->body >= psDroid->originalBody )
{
objTrace(psStructure->id, "Repair complete of droid %d", (int)psDroid->id);
psRepairFac->psObj = NULL;
/* set droid points to max */
psDroid->body = psDroid->originalBody;
//reset the power accrued
psDroid->powerAccrued = 0;
// if completely repaired reset order
secondarySetState(psDroid, DSO_RETURN_TO_LOC, DSS_NONE);
if (hasCommander(psDroid))
{
// return a droid to it's command group
DROID *psCommander = psDroid->psGroup->psCommander;
orderDroidObj(psDroid, DORDER_GUARD, (BASE_OBJECT *)psCommander);
}
else if (psRepairFac->psDeliveryPoint != NULL)
{
// move the droid out the way
orderDroidLoc( psDroid, DORDER_MOVE,
psRepairFac->psDeliveryPoint->coords.x,
psRepairFac->psDeliveryPoint->coords.y );
}
}
if (psStructure->visible[selectedPlayer] && psDroid->visible[selectedPlayer])
{
/* add plasma repair effect whilst being repaired */
iVecEffect.x = psDroid->pos.x + (10-rand()%20);
iVecEffect.y = psDroid->pos.z + (10-rand()%20);
iVecEffect.z = psDroid->pos.y + (10-rand()%20);
effectSetSize(100);
addEffect( &iVecEffect,EFFECT_EXPLOSION,EXPLOSION_TYPE_SPECIFIED,
true,getImdFromIndex(MI_FLAME),0 );
}
}
}
//check for rearming
else if (structureMode == REF_REARM_PAD)
{
psReArmPad = &psStructure->pFunctionality->rearmPad;
psDroid = (DROID *)psChosenObj;
ASSERT( psDroid != NULL,
"aiUpdateStructure: invalid droid pointer" );
ASSERT( isVtolDroid(psDroid),"aiUpdateStructure: invalid droid type" );
//check hasn't died whilst waiting to be rearmed
// also clear out any previously repaired droid
if ( psDroid->died ||
( psDroid->action != DACTION_MOVETOREARMPOINT &&
psDroid->action != DACTION_WAITDURINGREARM ) )
{
psReArmPad->psObj = NULL;
return;
}
//if waiting to be rearmed
if ( psDroid->action == DACTION_WAITDURINGREARM &&
psDroid->sMove.Status == MOVEINACTIVE )
{
if (psReArmPad->timeStarted == ACTION_START_TIME)
{
//set the time started
psReArmPad->timeStarted = gameTime;
}
bFinishAction = false;
// dont rearm on remote pcs.
if(!bMultiPlayer || myResponsibility(psDroid->player))
{
/* do rearming */
if (psDroid->sMove.iAttackRuns != 0)
{
UDWORD pointsRequired;
//amount required is a factor of the droids' weight
pointsRequired = psDroid->weight / REARM_FACTOR;
//Watermelon:take numWeaps into consideration
pointsToAdd = psReArmPad->reArmPoints * (gameTime - psReArmPad->timeStarted) * psDroid->numWeaps /
GAME_TICKS_PER_SEC;
//if ((SDWORD)(psDroid->sMove.iAttackRuns - pointsToAdd) <= 0)
if (pointsToAdd >= pointsRequired)
{
for (i = 0;i < psDroid->numWeaps;i++)
{
/* set rearm value to no runs made */
psDroid->sMove.iAttackRuns[i] = 0;
//reset ammo and lastTimeFired
psDroid->asWeaps[i].ammo = asWeaponStats[psDroid->
asWeaps[i].nStat].numRounds;
psDroid->asWeaps[i].lastFired = 0;
}
}
else
{
for (i = 0;i < psDroid->numWeaps;i++)
{
//Watermelon:make sure that slot is depleted and dont divide integer by zero
if ( psDroid->sMove.iAttackRuns[i] > 0 )
{
if (pointsToAdd >= pointsRequired/psDroid->sMove.iAttackRuns[i])
{
psDroid->sMove.iAttackRuns[i]--;
}
}
}
}
}
}
/* do repairing */
if (psDroid->body < psDroid->originalBody)
{
pointsToAdd = VTOL_REPAIR_FACTOR * (gameTime -
psReArmPad->timeStarted) / GAME_TICKS_PER_SEC;
//this was exponential...
if ((pointsToAdd - psReArmPad->currentPtsAdded) > 0)
{
psDroid->body += (pointsToAdd - psReArmPad->currentPtsAdded);
psReArmPad->currentPtsAdded = pointsToAdd;
}
if (psDroid->body >= psDroid->originalBody)
{
/* set droid points to max */
psDroid->body = psDroid->originalBody;
}
}
//check for fully armed and fully repaired
if (vtolHappy(psDroid))
{
if( bMultiPlayer)
{
sendHappyVtol(psDroid);
}
//clear the rearm pad
psDroid->action = DACTION_NONE;
bFinishAction = true;
psReArmPad->psObj = NULL;
}
}
}
}
}
/* Decides whether a structure should emit smoke when it's damaged */
static BOOL canSmoke(STRUCTURE *psStruct)
{
if(psStruct->pStructureType->type == REF_WALL ||
psStruct->pStructureType->type == REF_WALLCORNER)
{
return(false);
}
else
{
return(true);
}
}
static float CalcStructureSmokeInterval(float damage)
{
return (((1. - damage) + 0.1) * 10) * STRUCTURE_DAMAGE_SCALING;
}
/* The main update routine for all Structures */
void structureUpdate(STRUCTURE *psBuilding)
{
UDWORD widthScatter,breadthScatter;
UDWORD emissionInterval, iPointsToAdd, iPointsRequired;
Vector3i dv;
//update the manufacture/research of the building once complete
if (psBuilding->status == SS_BUILT)
{
aiUpdateStructure(psBuilding);
}
// must be after aiUpdateStructure because this is where we clean out dead targets
CHECK_STRUCTURE(psBuilding);
if(psBuilding->status!=SS_BUILT)
{
if(psBuilding->selected)
{
psBuilding->selected = false;
}
}
/* Only add smoke if they're visible and they can 'burn' */
if(psBuilding->visible[selectedPlayer] && canSmoke(psBuilding))
{
const float damage = getStructureDamage(psBuilding);
// Is there any damage?
if (damage > 0.)
{
emissionInterval = CalcStructureSmokeInterval(damage);
if(gameTime > (psBuilding->lastEmission + emissionInterval))
{
widthScatter = ((psBuilding->pStructureType->baseWidth) * TILE_UNITS/2)/3;
breadthScatter = ((psBuilding->pStructureType->baseBreadth) * TILE_UNITS/2)/3;
dv.x = psBuilding->pos.x + widthScatter - rand()%(2*widthScatter);
dv.z = psBuilding->pos.y + breadthScatter - rand()%(2*breadthScatter);
dv.y = psBuilding->pos.z;
dv.y += (psBuilding->sDisplay.imd->max.y * 3) / 4;
addEffect(&dv,EFFECT_SMOKE,SMOKE_TYPE_DRIFTING_HIGH,false,NULL,0);
psBuilding->lastEmission = gameTime;
}
}
}
if ((psBuilding->id % 10) == (frameGetFrameNumber() % 10))
{
processVisibility((BASE_OBJECT*)psBuilding);
}
/* Update the fire damage data */
if (psBuilding->inFire & IN_FIRE)
{
/* Still in a fire, reset the fire flag to see if we get out this turn */
psBuilding->inFire = 0;
}
else
{
/* The fire flag has not been set so we must be out of the fire */
psBuilding->burnStart = 0;
psBuilding->burnDamage = 0;
}
//check the resistance level of the structure
iPointsRequired = structureResistance(psBuilding->pStructureType,
psBuilding->player);
if (psBuilding->resistance < (SWORD)iPointsRequired)
{
//start the resistance increase
if (psBuilding->lastResistance == ACTION_START_TIME)
{
psBuilding->lastResistance = gameTime;
}
//increase over time if low
if ((gameTime - psBuilding->lastResistance) > RESISTANCE_INTERVAL)
{
psBuilding->resistance++;
//in multiplayer, certain structures do not function whilst low resistance
if (bMultiPlayer)
{
resetResistanceLag(psBuilding);
}
psBuilding->lastResistance = gameTime;
//once the resistance is back up reset the last time increased
if (psBuilding->resistance >= (SWORD)iPointsRequired)
{
psBuilding->lastResistance = ACTION_START_TIME;
}
}
}
else
{
//if selfrepair has been researched then check the health level of the
//structure once resistance is fully up
iPointsRequired = structureBody(psBuilding);
if (selfRepairEnabled(psBuilding->player) && (psBuilding->body < (SWORD)
iPointsRequired))
{
//start the self repair off
if (psBuilding->lastResistance == ACTION_START_TIME)
{
psBuilding->lastResistance = gameTime;
}
/*since self repair, then add half repair points depending on the time delay for the stat*/
iPointsToAdd = (repairPoints(asRepairStats + aDefaultRepair[
psBuilding->player], psBuilding->player) / 4) * ((gameTime -
psBuilding->lastResistance) / (asRepairStats +
aDefaultRepair[psBuilding->player])->time);
//add the blue flashing effect for multiPlayer
if(bMultiPlayer && ONEINTEN)
{
Vector3i position;
Vector3f *point;
SDWORD realY;
UDWORD pointIndex;
pointIndex = rand()%(psBuilding->sDisplay.imd->npoints-1);
point = &(psBuilding->sDisplay.imd->points[pointIndex]);
position.x = psBuilding->pos.x + point->x;
realY = structHeightScale(psBuilding) * point->y;
position.y = psBuilding->pos.z + realY;
position.z = psBuilding->pos.y - point->z;
effectSetSize(30);
addEffect(&position, EFFECT_EXPLOSION, EXPLOSION_TYPE_SPECIFIED, true,
getImdFromIndex(MI_PLASMA), 0);
}
if (iPointsToAdd)
{
psBuilding->body = (UWORD)(psBuilding->body + iPointsToAdd);
psBuilding->lastResistance = gameTime;
if ( psBuilding->body > iPointsRequired)
{
psBuilding->body = (UWORD)iPointsRequired;
psBuilding->lastResistance = ACTION_START_TIME;
}
}
}
}
CHECK_STRUCTURE(psBuilding);
}
/* Release all resources associated with a structure */
void structureRelease(STRUCTURE *psBuilding)
{
/* remove animation if present */
if ( psBuilding->psCurAnim != NULL )
{
animObj_Remove( &psBuilding->psCurAnim, psBuilding->psCurAnim->psAnim->uwID );
psBuilding->psCurAnim = NULL;
}
// free up the space used by the functionality array
free(psBuilding->pFunctionality);
psBuilding->pFunctionality = NULL;
}
/*
fills the list with Structure that can be built. There is a limit on how many can
be built at any one time. Pass back the number available.
There is now a limit of how many of each type of structure are allowed per mission
*/
UDWORD fillStructureList(STRUCTURE_STATS **ppList, UDWORD selectedPlayer, UDWORD limit)
{
UDWORD inc, count;
BOOL researchModule, factoryModule, powerModule;
STRUCTURE *psCurr;
STRUCTURE_STATS *psBuilding;
//check to see if able to build research/factory modules
researchModule = factoryModule = powerModule = false;
//if currently on a mission can't build factory/research/power/derricks
if (!missionIsOffworld())
{
for (psCurr = apsStructLists[selectedPlayer]; psCurr != NULL; psCurr =
psCurr->psNext)
{
if (psCurr->pStructureType->type == REF_RESEARCH && psCurr->status ==
SS_BUILT)
{
researchModule = true;
}
else if (psCurr->pStructureType->type == REF_FACTORY && psCurr->status ==
SS_BUILT)
{
factoryModule = true;
}
else if (psCurr->pStructureType->type == REF_POWER_GEN && psCurr->status == SS_BUILT)
{
powerModule = true;
}
}
}
count = 0;
//set the list of Structures to build
for (inc=0; inc < numStructureStats; inc++)
{
//if the structure is flagged as available, add it to the list
if (apStructTypeLists[selectedPlayer][inc] & AVAILABLE)
{
//check not built the maximum allowed already
if (asStructLimits[selectedPlayer][inc].currentQuantity < asStructLimits[selectedPlayer][inc].limit)
{
psBuilding = asStructureStats + inc;
//don't want corner wall to appear in list
if (psBuilding->type == REF_WALLCORNER)
{
continue;
}
// Remove the demolish stat from the list for tutorial
// tjc 4-dec-98 ...
if (bInTutorial)
{
if (psBuilding->type == REF_DEMOLISH) continue;
}
//can't build list when offworld
if (missionIsOffworld())
{
if (psBuilding->type == REF_FACTORY ||
psBuilding->type == REF_POWER_GEN ||
psBuilding->type == REF_RESOURCE_EXTRACTOR ||
psBuilding->type == REF_RESEARCH ||
psBuilding->type == REF_CYBORG_FACTORY ||
psBuilding->type == REF_VTOL_FACTORY)
{
continue;
}
}
if (psBuilding->type == REF_RESEARCH_MODULE)
{
//don't add to list if Research Facility not presently built
if (!researchModule)
{
continue;
}
}
else if (psBuilding->type == REF_FACTORY_MODULE)
{
//don't add to list if Factory not presently built
if (!factoryModule)
{
continue;
}
}
else if (psBuilding->type == REF_POWER_MODULE)
{
//don't add to list if Power Gen not presently built
if (!powerModule)
{
continue;
}
}
//paranoid check!!
if (psBuilding->type == REF_FACTORY ||
psBuilding->type == REF_CYBORG_FACTORY ||
psBuilding->type == REF_VTOL_FACTORY)
{
//NEVER EVER EVER WANT MORE THAN 5 FACTORIES
if (asStructLimits[selectedPlayer][inc].currentQuantity >= MAX_FACTORY)
{
continue;
}
}
//HARD_CODE don't ever want more than one Las Sat structure
if (isLasSat(psBuilding) && getLasSatExists(selectedPlayer))
{
continue;
}
//HARD_CODE don't ever want more than one Sat Uplink structure
if (psBuilding->type == REF_SAT_UPLINK)
{
if (asStructLimits[selectedPlayer][inc].currentQuantity >= 1)
{
continue;
}
}
debug(LOG_NEVER, "adding %s (%x)", psBuilding->pName, apStructTypeLists[selectedPlayer][inc]);
ppList[count++] = psBuilding;
if (count == limit)
{
return count;
}
}
}
}
return count;
}
/* checks that the location is a valid one to build on and sets the outline colour
x and y in tile-coords*/
BOOL validLocation(BASE_STATS *psStats, UDWORD x, UDWORD y, UDWORD player,
BOOL bCheckBuildQueue)
{
STRUCTURE *psStruct;
STRUCTURE_STATS *psBuilding;
BOOL valid = true;
SDWORD i, j;
UDWORD min, max;
HIGHLIGHT site;
FLAG_POSITION *psCurrFlag;
//make sure we are not too near map edge and not going to go over it
if( !tileInsideBuildRange((SDWORD)x, (SDWORD)y) )
{
return false;
}
psBuilding = (STRUCTURE_STATS *)psStats;
// initialise the buildsite structure
// gets rid of the nasty bug when the GLOBAL buildSite was
// used in here
// Now now...we can quite easily hack this a bit more...
if (psStats->ref >= REF_STRUCTURE_START &&
psStats->ref < (REF_STRUCTURE_START + REF_RANGE))
{
site.xTL = (UWORD)x;
site.yTL = (UWORD)y;
site.xBR = (UWORD)(x + psBuilding->baseWidth - 1);
site.yBR = (UWORD)(y + psBuilding->baseBreadth - 1);
// increase the size of a repair facility
if (psBuilding->type == REF_REPAIR_FACILITY)
{
site.xTL -= 1;
site.yTL -= 1;
site.xBR += 1;
site.yBR += 1;
}
//if we're dragging the wall/defense we need to check along the current dragged size
if (wallDrag.status != DRAG_INACTIVE
&& (psBuilding->type == REF_WALL || psBuilding->type == REF_DEFENSE)
&& !isLasSat(psBuilding))
{
UWORD dx,dy;
wallDrag.x2 = mouseTileX;
wallDrag.y2 = mouseTileY;
dx = (UWORD)(abs(mouseTileX - wallDrag.x1));
dy = (UWORD)(abs(mouseTileY - wallDrag.y1));
if(dx >= dy)
{
//build in x direction
site.xTL = (UWORD)wallDrag.x1;
site.xBR = (UWORD)wallDrag.x2;
site.yTL = (UWORD)wallDrag.y1;
if (dy > 0)
{
site.yBR = (UWORD)(site.yTL+1);
}
else
{
site.yBR = (UWORD)(site.yTL);
}
if (site.xTL > site.xBR)
{
dx = site.xBR;
site.xBR = site.xTL;
site.xTL = dx;
}
}
else if(dx < dy)
{
//build in y direction
site.yTL = (UWORD)wallDrag.y1;
site.yBR = (UWORD)wallDrag.y2;
site.xTL = (UWORD)wallDrag.x1;
if (dx > 0)
{
site.xBR = (UWORD)(site.xTL+1);
}
else
{
site.xBR = (UWORD)(site.xTL);
}
if (site.yTL > site.yBR)
{
dy = site.yBR;
site.yBR = site.yTL;
site.yTL = dy;
}
}
}
}
else
{
// If the stat's not a structure then assume it's size is 1x1.
site.xTL = (UWORD)x;
site.yTL = (UWORD)y;
site.xBR = (UWORD)x;
site.yBR = (UWORD)y;
}
for (i = site.xTL; i <= site.xBR && valid; i++) {
for (j = site.yTL; j <= site.yBR && valid; j++) {
// Can't build outside of scroll limits.
if( ((SDWORD)i < scrollMinX+2) || ((SDWORD)i > scrollMaxX-5) ||
((SDWORD)j < scrollMinY+2) || ((SDWORD)j > scrollMaxY-5)) {
valid = false;
goto failed;
}
// check i or j off map.
if(i<=2 || j<=2 || i>=(SDWORD)mapWidth-2 || j>=(SDWORD)mapHeight-2)
{
valid = false;
goto failed;
}
//don't check tile is visible for placement of a delivery point
if (psStats->ref >= REF_STRUCTURE_START &&
psStats->ref < (REF_STRUCTURE_START + REF_RANGE))
{
//allow us to do so in debug mode!
if (!getDebugMappingStatus() && !bMultiPlayer)
{
// Can't build where we haven't been yet.
if(!TEST_TILE_VISIBLE(player,mapTile(i,j))) {
valid = false;
goto failed;
}
}
}
}
}
// cant place on top of a delivery point...
for (psCurrFlag = apsFlagPosLists[selectedPlayer]; psCurrFlag; psCurrFlag = psCurrFlag->psNext)
{
ASSERT(psCurrFlag->coords.x != ~0, "flag has invalid position");
i = map_coord(psCurrFlag->coords.x);
j = map_coord(psCurrFlag->coords.y);
if (i >= site.xTL && i <= site.xBR &&
j >= site.yTL && j <= site.yBR)
{
valid = false;
goto failed;
}
}
// can't build next to a repair facility
for (psStruct = apsStructLists[player]; psStruct; psStruct = psStruct->psNext)
{
if (psStruct->pStructureType->type == REF_REPAIR_FACILITY)
{
// get the top left of the struct
i = map_coord(psStruct->pos.x) - 1;
j = map_coord(psStruct->pos.y) - 1;
// see if the x extents overlap
if ((site.xTL >= i && site.xTL <= (i+2)) ||
(site.xBR >= i && site.xBR <= (i+2)))
{
// now see if y extents overlap
if ((site.yTL >= j && site.yTL <= (j+2)) ||
(site.yBR >= j && site.yBR <= (j+2)))
{
valid = false;
goto failed;
}
}
}
}
if (psStats->ref >= REF_STRUCTURE_START &&
psStats->ref < (REF_STRUCTURE_START + REF_RANGE))
{
MAPTILE *psTile;
switch(psBuilding->type)
{
case REF_DEMOLISH:
break;
case NUM_DIFF_BUILDINGS:
case REF_BRIDGE:
ASSERT(!"invalid structure type", "validLocation: Bad structure type %u", psBuilding->type);
break;
case REF_HQ:
case REF_FACTORY:
case REF_LAB:
case REF_RESEARCH:
case REF_POWER_GEN:
case REF_WALL:
case REF_WALLCORNER:
case REF_DEFENSE:
case REF_REPAIR_FACILITY:
case REF_COMMAND_CONTROL:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
case REF_BLASTDOOR:
case REF_REARM_PAD:
case REF_MISSILE_SILO:
case REF_SAT_UPLINK:
/*need to check each tile the structure will sit on is not water*/
for (i = site.xTL; i <= site.xBR && valid; i++)
{
for (j = site.yTL; j <= site.yBR && valid; j++)
{
psTile = mapTile(i,j);
if ((terrainType(psTile) == TER_WATER) ||
(terrainType(psTile) == TER_CLIFFFACE) )
{
valid = false;
}
}
}
//don't bother checking if already found a problem
if (valid)
{
//check not within landing zone
for (i = site.xTL; i <= site.xBR && valid; i++)
{
for (j = site.yTL; j <= site.yBR && valid; j++)
{
if (withinLandingZone(i, j))
{
valid = false;
}
}
}
}
// special droid/max-min test for repair facility
if ( valid && (psBuilding->type == REF_REPAIR_FACILITY))
{
getTileMaxMin(x, y, &max, &min);
if ((max - min) > MAX_INCLINE)
{
valid = false;
}
if (valid &&
!noDroid(x,y))
{
valid = false;
}
}
if (valid && // only do if necessary
(psBuilding->type != REF_REPAIR_FACILITY))
{
for (i = site.xTL; i <= site.xBR && valid; i++)
{
for (j = site.yTL; j <= site.yBR && valid; j++)
{
// This really needs to check to see if the droid that's in the way is the droid that wants to build
// in which case it should'nt invalidate the location.
if(noDroid(i,j) == false)
{
valid = false;
}
}
}
}
//walls/defensive structures can be built along any ground
if (valid && // only do if necessary
(!(psBuilding->type == REF_REPAIR_FACILITY ||
psBuilding->type == REF_DEFENSE ||
psBuilding->type == REF_WALL)))
{
/*cannot build on ground that is too steep*/
min = 0;
max = 0;
for (i = site.xTL; i <= site.xBR && valid; i++)
{
for (j = site.yTL; j <= site.yBR && valid; j++)
{
getTileMaxMin(i, j, &max, &min);
if ((max - min) > MAX_INCLINE)
{
valid = false;
}
}
}
}
//don't bother checking if already found a problem
if (valid)
{
//on PC - defence structures can be built next to anything now- AB 22/09/98
//and the Missile_Silo (special case) - AB 01/03/99
if (!(psBuilding->type == REF_DEFENSE ||
psBuilding->type == REF_WALL ||
psBuilding->type == REF_WALLCORNER ||
psBuilding->type == REF_MISSILE_SILO))
{
/*need to check there is one tile between buildings*/
for (i = (UWORD)(site.xTL-1); i <= (UWORD)(site.xBR+1); i++)
{
for (j = (UWORD)(site.yTL-1); j <= (UWORD)(site.yBR+1); j++)
{
//skip the actual area the structure will cover
if (i < site.xTL || i > site.xBR ||
j < site.yTL || j > site.yBR)
{
if (TileHasStructure(mapTile(i,j)))
{
psStruct = getTileStructure(i,j);
if (psStruct)
{
//you can build anything next to a defensive structure
if ((psStruct->pStructureType->type != REF_DEFENSE) &&
(psStruct->pStructureType->type != REF_WALL) &&
(psStruct->pStructureType->type != REF_WALLCORNER)
)
{
//Walls can be built next to walls and defenses - AB 03/03/99
if (psBuilding->type == REF_WALL)
{
if (!(psStruct->pStructureType->type == REF_WALL ||
psStruct->pStructureType->type == REF_WALLCORNER))
{
valid = false;
}
}
else
{
valid = false;
}
}
else // is a defense.
{ // skirmish players don't build defensives next to each other.(route hack)
if( bMultiPlayer && game.type == SKIRMISH && !isHumanPlayer(player) )
{
valid = false;
}
}
}
}
//cannot build within one tile of a oil resource
if(TileHasFeature(mapTile(i,j)))
{
FEATURE *psFeat = getTileFeature(i, j);
if (psFeat && psFeat->psStats->subType ==
FEAT_OIL_RESOURCE)
{
valid = false;
}
}
}
}
}
}
}
//don't bother checking if already found a problem
if (valid)
{
/*need to check each tile the structure will sit on*/
for (i = site.xTL; i <= site.xBR && valid; i++)
{
for (j = site.yTL; j <= site.yBR && valid; j++)
{
psTile = mapTile(i,j);
if (TileIsOccupied(psTile))
{
if (TileHasWall(psTile)
&& (psBuilding->type == REF_DEFENSE ||
- psBuilding->type == REF_WALL))
{
psStruct = getTileStructure(i,j);
if (psStruct != NULL &&
psStruct->player != player)
{
valid = false;
}
}
else
{
valid = false;
}
}
}
}
}
break;
case REF_FACTORY_MODULE:
valid = false;
if(TileHasStructure(mapTile(x,y)))
{
psStruct = getTileStructure(x,y);
if(psStruct && (
psStruct->pStructureType->type == REF_FACTORY ||
psStruct->pStructureType->type == REF_VTOL_FACTORY) &&
psStruct->status == SS_BUILT)
{
valid = true;
}
}
break;
case REF_RESEARCH_MODULE:
valid = false;
//check that there is a research facility at the location
if(TileHasStructure(mapTile(x,y)))
{
psStruct = getTileStructure(x,y);
if(psStruct && psStruct->pStructureType->type == REF_RESEARCH &&
psStruct->status == SS_BUILT)
{
valid = true;
}
}
break;
case REF_POWER_MODULE:
valid = false;
if(TileHasStructure(mapTile(x,y)))
{
psStruct = getTileStructure(x,y);
if(psStruct && psStruct->pStructureType->type == REF_POWER_GEN &&
psStruct->status == SS_BUILT)
{
valid = true;
}
}
break;
case REF_RESOURCE_EXTRACTOR:
valid = false;
//check that there is a oil resource at the location
if(TileHasFeature(mapTile(x,y)))
{
FEATURE *psFeat = getTileFeature(x, y);
if(psFeat && psFeat->psStats->subType == FEAT_OIL_RESOURCE)
{
valid = true;
}
}
break;
}
//if setting up a build queue need to check against future sites as well - AB 4/5/99
if (ctrlShiftDown() && player == selectedPlayer && bCheckBuildQueue)
{
DROID *psDroid;
SDWORD order,left,right,up,down,size;
BOOL validCombi;
//defense and missile silo's can be built next to anything so don't need to check
if (!(psBuilding->type == REF_DEFENSE || psBuilding->type ==
REF_MISSILE_SILO))
{
for (psDroid = apsDroidLists[player]; psDroid; psDroid = psDroid->psNext)
{
//once its invalid stop checking
if (valid == false)
{
break;
}
if (psDroid->droidType == DROID_CONSTRUCT ||
psDroid->droidType == DROID_CYBORG_CONSTRUCT)
{
//look thru' the list of orders to see if more building sites
for (order = 0; order < psDroid->listSize; order++)
{
if (psDroid->asOrderList[order].order == DORDER_BUILD)
{
validCombi = false;
if (((STRUCTURE_STATS *)psDroid->asOrderList[order].
psOrderTarget)->type == REF_DEFENSE ||
((STRUCTURE_STATS *)psDroid->asOrderList[order].
psOrderTarget)->type == REF_MISSILE_SILO)
{
validCombi = true;
}
//walls can be built next to walls and defence
if ((psBuilding->type == REF_WALL || psBuilding->type == REF_WALLCORNER)
&& (((STRUCTURE_STATS *)psDroid->asOrderList[order].psOrderTarget)->type == REF_WALL
|| ((STRUCTURE_STATS *)psDroid->asOrderList[order].psOrderTarget)->type == REF_WALLCORNER))
{
validCombi = true;
}
//don't bother checking if valid combination of building types
if (!validCombi)
{
/*need to check there is one tile between buildings*/
//check if any corner is within the build site
size = ((STRUCTURE_STATS *)psDroid->asOrderList[order].
psOrderTarget)->baseWidth;
left = map_coord(psDroid->asOrderList[order].x) - size/2;
right = left + size;
size = ((STRUCTURE_STATS *)psDroid->asOrderList[order].
psOrderTarget)->baseBreadth;
up = map_coord(psDroid->asOrderList[order].y) - size/2;
down = up + size;
// increase the size of a repair facility
if (((STRUCTURE_STATS *)psDroid->asOrderList[
order].psOrderTarget)->type == REF_REPAIR_FACILITY)
{
left -= 1;
up -= 1;
right += 1;
down += 1;
}
if (((left > site.xTL-1 && left <= site.xBR+1) &&
(up > site.yTL-1 && up <= site.yBR+1)) ||
((right > site.xTL-1 && right <= site.xBR+1) &&
(up > site.yTL-1 && up <= site.yBR+1)) ||
((left > site.xTL-1 && left <= site.xBR+1) &&
(down > site.yTL-1 && down <= site.yBR+1)) ||
((right > site.xTL-1 && right <= site.xBR+1) &&
(down > site.yTL-1 && down <= site.yBR+1)))
{
valid = false;
break;
}
}
}
}
}
}
}
}
}
else if (psStats->ref >= REF_TEMPLATE_START
&& psStats->ref < REF_TEMPLATE_START + REF_RANGE)
{
DROID_TEMPLATE *psTemplate = (DROID_TEMPLATE *)psStats;
PROPULSION_STATS *psPropStats = asPropulsionStats + psTemplate->asParts[COMP_PROPULSION];
valid = !fpathBlockingTile(x, y, psPropStats->propulsionType);
}
else
{
// not positioning a structure or droid, ie positioning a feature
valid = !fpathBlockingTile(x, y, PROPULSION_TYPE_WHEELED);
}
failed:
if (!valid)
{
// Only set the hilight colour if it's the selected player.
if(player == selectedPlayer)
{
outlineTile = false;
}
return false;
}
// Only set the hilight colour if it's the selected player.
if (player == selectedPlayer)
{
outlineTile = true;
}
return true;
}
/*
for a new structure, find a location along an edge which the droid can get
to and return this as the destination for the droid.
*/
BOOL getDroidDestination(BASE_STATS *psStats, UDWORD structX,
UDWORD structY, UDWORD *pDroidX, UDWORD *pDroidY)
{
UWORD start;
UDWORD structTileX, structTileY, width = 0, breadth = 0;
if (StatIsStructure(psStats))
{
width = ((STRUCTURE_STATS *)psStats)->baseWidth;
breadth = ((STRUCTURE_STATS *)psStats)->baseBreadth;
}
else if (StatIsFeature(psStats))
{
width = ((FEATURE_STATS *)psStats)->baseWidth;
breadth = ((FEATURE_STATS *)psStats)->baseBreadth;
}
//get a random starting place 0=top left
start = (UWORD)(rand() % ((width + breadth) * 2));
//search in a clockwise direction around the structure from the starting point
if (start == 0 || start < width)
{
//top side first
structTileX = map_coord(structX);
structTileY = map_coord(structY) - 1;
if (checkWidth(width, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
structTileX += width;
structTileY += 1;
if (checkLength(breadth, structTileX, structTileY,pDroidX, pDroidY))
{
return true;
}
structTileX = map_coord(structX);
structTileY += breadth;
if (checkWidth(width, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
structTileX -= 1;
structTileY = map_coord(structY);
if (checkLength(breadth, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
}
else if (start == width || start < (width + breadth))
{
//right side first
structTileX = (map_coord(structX)) + width;
structTileY = map_coord(structY);
if (checkLength(breadth, structTileX, structTileY,pDroidX, pDroidY))
{
return true;
}
structTileX = map_coord(structX);
structTileY += breadth;
if (checkWidth(width, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
structTileX -= 1;
structTileY = map_coord(structY);
if (checkLength(breadth, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
structTileX += 1;
structTileY -= 1;
if (checkWidth(width, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
}
else if (start == (width + breadth) || start < (width * breadth))
{
//bottom first
structTileX = map_coord(structX);
structTileY = map_coord(structY) + breadth;
if (checkWidth(width, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
structTileX -= 1;
structTileY = map_coord(structY);
if (checkLength(breadth, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
structTileX += 1;
structTileY -= 1;
if (checkWidth(width, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
structTileX += width;
structTileY += 1;
if (checkLength(breadth, structTileX, structTileY,pDroidX, pDroidY))
{
return true;
}
}
else
{
//left side first
structTileX = (map_coord(structX)) - 1;
structTileY = map_coord(structY);
if (checkLength(breadth, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
structTileX += 1;
structTileY -= 1;
if (checkWidth(width, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
structTileX += width;
structTileY += 1;
if (checkLength(breadth, structTileX, structTileY,pDroidX, pDroidY))
{
return true;
}
structTileX = map_coord(structX);
structTileY += breadth;
if (checkWidth(width, structTileX, structTileY, pDroidX, pDroidY))
{
return true;
}
}
//not found a valid location so return false
return false;
}
/* check along the width of a structure for an empty space */
BOOL checkWidth(UDWORD maxRange, UDWORD x, UDWORD y, UDWORD *pDroidX, UDWORD *pDroidY)
{
UDWORD side;
for (side = 0; side < maxRange; side++)
{
if( x+side < mapWidth && y < mapHeight && !TileIsOccupied(mapTile(x+side,y)) )
{
*pDroidX = world_coord(x + side);
*pDroidY = world_coord(y);
ASSERT( worldOnMap(*pDroidX,*pDroidY),"checkWidth : Insane droid position" );
return true;
}
}
return false;
}
/* check along the length of a structure for an empty space */
BOOL checkLength(UDWORD maxRange, UDWORD x, UDWORD y, UDWORD *pDroidX, UDWORD *pDroidY)
{
UDWORD side;
for (side = 0; side < maxRange; side++)
{
if(y+side < mapHeight && x < mapWidth && !TileIsOccupied(mapTile(x,y+side)) )
{
*pDroidX = world_coord(x);
*pDroidY = world_coord(y + side);
ASSERT( worldOnMap(*pDroidX,*pDroidY),"checkHeight : Insane droid position" );
return true;
}
}
return false;
}
//remove a structure from the map
static void removeStructFromMap(STRUCTURE *psStruct)
{
UDWORD i,j;
UDWORD mapX, mapY;
MAPTILE *psTile;
/* set tiles drawing */
mapX = map_coord(psStruct->pos.x - psStruct->pStructureType->baseWidth * TILE_UNITS / 2);
mapY = map_coord(psStruct->pos.y - psStruct->pStructureType->baseBreadth * TILE_UNITS / 2);
for (i = 0; i < psStruct->pStructureType->baseWidth; i++)
{
for (j = 0; j < psStruct->pStructureType->baseBreadth; j++)
{
psTile = mapTile(mapX+i, mapY+j);
psTile->psObject = NULL;
CLEAR_TILE_TALLSTRUCTURE(psTile);
CLEAR_TILE_NOTBLOCKING(psTile);
}
}
}
// remove a structure from a game without any visible effects
// bDestroy = true if the object is to be destroyed
// (for example used to change the type of wall at a location)
BOOL removeStruct(STRUCTURE *psDel, BOOL bDestroy)
{
BOOL resourceFound = false;
UBYTE mask;
FACTORY *psFactory;
SDWORD cluster;
FLAG_POSITION *psAssemblyPoint=NULL;
ASSERT(psDel != NULL, "invalid structure pointer");
if (bDestroy)
{
removeStructFromMap(psDel);
}
//tell the power system its gone
powerDestroyObject((BASE_OBJECT *)psDel);
if (bDestroy)
{
//if the structure is a resource extractor, need to put the resource back in the map
/*ONLY IF ANY POWER LEFT - HACK HACK HACK!!!! OIL POOLS NEED TO KNOW
HOW MUCH IS THERE && NOT RES EXTRACTORS */
if (psDel->pStructureType->type == REF_RESOURCE_EXTRACTOR)
{
if (psDel->pFunctionality->resourceExtractor.power)
{
buildFeature(&asFeatureStats[oilResFeature], psDel->pos.x, psDel->pos.y, false);
resourceFound = true;
}
}
}
if (psDel->pStructureType->type == REF_RESOURCE_EXTRACTOR)
{
//tell associated Power Gen
releaseResExtractor(psDel);
}
if (psDel->pStructureType->type == REF_POWER_GEN)
{
//tell associated Res Extractors
releasePowerGen(psDel);
}
//check for a research topic currently under way
if (psDel->pStructureType->type == REF_RESEARCH)
{
if (psDel->pFunctionality->researchFacility.psSubject)
{
//cancel the topic
cancelResearch(psDel);
}
}
//subtract one from the structLimits list so can build another - don't allow to go less than zero!
if (asStructLimits[psDel->player][psDel->pStructureType - asStructureStats].currentQuantity)
{
asStructLimits[psDel->player][psDel->pStructureType - asStructureStats].currentQuantity--;
}
//if it is a factory - need to reset the factoryNumFlag
if (StructIsFactory(psDel))
{
psFactory = &psDel->pFunctionality->factory;
//need to initialise the production run as well
cancelProduction(psDel);
psAssemblyPoint = psFactory->psAssemblyPoint;
}
else if (psDel->pStructureType->type == REF_REPAIR_FACILITY)
{
psAssemblyPoint = psDel->pFunctionality->repairFacility.psDeliveryPoint;
}
if (psAssemblyPoint != NULL)
{
mask = (UBYTE)(1 << psAssemblyPoint->factoryInc);
factoryNumFlag[psDel->player][psAssemblyPoint->factoryType] ^= mask;
//need to cancel the repositioning of the DP if selectedPlayer and currently moving
if (psDel->player == selectedPlayer)
{
//if currently trying to place a DP
if (tryingToGetLocation())
{
//need to check if this factory's DP is trying to be re-positioned
if (psAssemblyPoint == sBuildDetails.UserData)
{
kill3DBuilding();
}
}
}
}
// remove the structure from the grid
gridRemoveObject((BASE_OBJECT *)psDel);
// remove the structure from the cluster
cluster = psDel->cluster;
clustRemoveObject((BASE_OBJECT *)psDel);
if (bDestroy)
{
killStruct(psDel);
}
/* remove animation if present */
if ( psDel->psCurAnim != NULL )
{
animObj_Remove( &psDel->psCurAnim, psDel->psCurAnim->psAnim->uwID );
psDel->psCurAnim = NULL;
}
clustUpdateCluster((BASE_OBJECT *)apsStructLists[psDel->player], cluster);
if(psDel->player==selectedPlayer)
{
intRefreshScreen();
}
return resourceFound;
}
/* Remove a structure */
BOOL destroyStruct(STRUCTURE *psDel)
{
UDWORD mapX, mapY, width,breadth;
UDWORD i;
UDWORD widthScatter,breadthScatter,heightScatter;
Vector3i pos;
BOOL resourceFound = false;
MAPTILE *psTile;
BOOL bMinor = false;
CHECK_STRUCTURE(psDel);
if (bMultiPlayer)
{
SendDestroyStructure(psDel);
}
//---------------------------------------
/* Only add if visible */
if(psDel->visible[selectedPlayer])
{
/* Firstly, are we dealing with a wall section */
if(psDel->pStructureType->type == REF_WALL || psDel->pStructureType->type == REF_WALLCORNER)
{
bMinor = true;
}
//--------------------------------------- Do we add immediate explosions?
/* Set off some explosions, but not for walls */
/* First Explosions */
widthScatter = TILE_UNITS;
breadthScatter = TILE_UNITS;
heightScatter = TILE_UNITS;
for(i=0; i<(UDWORD)(bMinor ? 2 : 4); i++) // only add two for walls - gets crazy otherwise
{
pos.x = psDel->pos.x + widthScatter - rand()%(2*widthScatter);
pos.z = psDel->pos.y + breadthScatter - rand()%(2*breadthScatter);
pos.y = psDel->pos.z + 32 + rand()%heightScatter;
addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_MEDIUM,false,NULL,0);
}
/* Get coordinates for everybody! */
pos.x = psDel->pos.x;
pos.z = psDel->pos.y;
pos.y = map_Height((UWORD)pos.x,(UWORD)pos.z);
//--------------------------------------- Do we add a fire?
// Set off a fire, provide dimensions for the fire
if(bMinor)
{
effectGiveAuxVar(world_coord(psDel->pStructureType->baseWidth) / 4);
}
else
{
effectGiveAuxVar(world_coord(psDel->pStructureType->baseWidth) / 3);
}
if(bMinor) // walls
{
/* Give a duration */
effectGiveAuxVarSec(1000);
/* Normal fire - no smoke */
addEffect(&pos,EFFECT_FIRE,FIRE_TYPE_LOCALISED,false,NULL,0);
}
else if(psDel->pStructureType->type == REF_RESOURCE_EXTRACTOR) // oil resources
{
/* Oil resources burn AND puff out smoke AND for longer*/
effectGiveAuxVarSec(60000);
addEffect(&pos,EFFECT_FIRE,FIRE_TYPE_SMOKY,false,NULL,0);
}
else // everything else
{
/* Give a duration */
effectGiveAuxVarSec(10000);
addEffect(&pos,EFFECT_FIRE,FIRE_TYPE_LOCALISED,false,NULL,0);
}
//--------------------------------------- Do we add a destruction seq, and if so, which?
/* Power stations have their own desctruction sequence */
if(psDel->pStructureType->type == REF_POWER_GEN)
{
addEffect(&pos,EFFECT_DESTRUCTION,DESTRUCTION_TYPE_POWER_STATION,false,NULL,0);
pos.y += SHOCK_WAVE_HEIGHT;
addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_SHOCKWAVE,false,NULL,0);
// give some power back to the player.
addPower(psDel->player, structPowerToBuild(psDel));
//if it had a module attached, need to add the power for the base struct as well
if (psDel->pFunctionality->powerGenerator.capacity)
{
addPower(psDel->player, psDel->pStructureType->powerToBuild);
}
}
/* As do wall sections */
else if(bMinor)
{
addEffect(&pos,EFFECT_DESTRUCTION,DESTRUCTION_TYPE_WALL_SECTION,false,NULL,0);
}
else // and everything else goes here.....
{
addEffect(&pos,EFFECT_DESTRUCTION,DESTRUCTION_TYPE_STRUCTURE,false,NULL,0);
}
//--------------------------------------- Start an earthquake...!
/* shake the screen if we're near enough */
if(clipXY(pos.x,pos.z))
{
shakeStart();
}
//--------------------------------------- And finally, add a boom sound!!!!
/* and add a sound effect */
audio_PlayStaticTrack( psDel->pos.x, psDel->pos.y, ID_SOUND_EXPLOSION );
}
//---------------------------------------------------------------------------------------
resourceFound = removeStruct(psDel, true);
//once a struct is destroyed - it leaves a wrecked struct FEATURE in its place
// Wall's don't leave wrecked features
if(psDel->visible[selectedPlayer])
{
if (!resourceFound && !(psDel->pStructureType->type == REF_WALL) &&
!(psDel->pStructureType->type == REF_WALLCORNER))
{
mapX = map_coord(psDel->pos.x - psDel->pStructureType->baseWidth * TILE_UNITS / 2);
mapY = map_coord(psDel->pos.y - psDel->pStructureType->baseBreadth * TILE_UNITS / 2);
for (width = 0; width < psDel->pStructureType->baseWidth; width++)
{
for (breadth = 0; breadth < psDel->pStructureType->baseBreadth; breadth++)
{
psTile = mapTile(mapX+width,mapY+breadth);
if(TEST_TILE_VISIBLE(selectedPlayer,psTile))
{
psTile->illumination /= 2;
if(psTile->bMaxed && psTile->level > 0) //only do one's already seen
{
psTile->level/=2;
}
}
}
}
}
}
/* remove animation if present */
if ( psDel->psCurAnim != NULL )
{
animObj_Remove( &psDel->psCurAnim, psDel->psCurAnim->psAnim->uwID );
psDel->psCurAnim = NULL;
}
// updates score stats only if not wall
if(psDel->pStructureType->type != REF_WALL && psDel->pStructureType->type != REF_WALLCORNER)
{
if(psDel->player == selectedPlayer)
{
scoreUpdateVar(WD_STR_LOST);
}
else
{
scoreUpdateVar(WD_STR_KILLED);
}
}
return true;
}
/* For now all this does is work out what height the terrain needs to be set to
An actual foundation structure may end up being placed down
The x and y passed in are the CENTRE of the structure*/
SWORD buildFoundation(STRUCTURE_STATS *psStructStats, UDWORD x, UDWORD y)
{
UDWORD width, breadth;
UDWORD startX, startY;
SWORD height,foundationMin, foundationMax;
startX = map_coord(x) - psStructStats->baseWidth/2;
startY = map_coord(y) - psStructStats->baseBreadth/2;
//check the terrain is the correct type return -1 if not
//shouldn't need to do this but doesn't take long hey?!
/*check if there is a structure next to the new one - return the height of the
structure if found*/
for (breadth = 0; breadth <= psStructStats->baseBreadth; breadth++)
{
for (width = 0; width <= psStructStats->baseWidth; width++)
{
if(TileHasStructure(mapTile(startX+width,startY+breadth)))
{
return((SWORD)map_TileHeight(startX+width,startY+breadth));
}
}
}
//may also have to check that overlapping terrain can be set to the average height
//eg water - don't want it to 'flow' into the structure if this effect is coded!
startX = map_coord(x) - psStructStats->baseWidth/2;
startY = map_coord(y) - psStructStats->baseBreadth/2;
//initialise the starting values so they get set in loop
foundationMin = TILE_MAX_HEIGHT;
foundationMax = TILE_MIN_HEIGHT;
for (breadth = 0; breadth <= psStructStats->baseBreadth; breadth++)
{
for (width = 0; width <= psStructStats->baseWidth; width++)
{
height = map_TileHeight(startX+width,startY+breadth);
if (foundationMin > height)
{
foundationMin = height;
}
if (foundationMax < height)
{
foundationMax = height;
}
}
}
//return the average of max/min height
return ((SWORD)((foundationMin + foundationMax) / 2));
}
/* gets a structure stat from its name - relies on the name being unique (or it will
return the first one it finds!! */
SDWORD getStructStatFromName(char *pName)
{
UDWORD inc;
STRUCTURE_STATS *psStat;
for (inc = 0; inc < numStructureStats; inc++)
{
psStat = &asStructureStats[inc];
if (!strcmp(psStat->pName, pName))
{
return inc;
}
}
return -1;
}
/*check to see if the structure is 'doing' anything - return true if idle*/
BOOL structureIdle(STRUCTURE *psBuilding)
{
BASE_STATS *pSubject = NULL;
CHECK_STRUCTURE(psBuilding);
if (psBuilding->pFunctionality == NULL)
return true;
//determine the Subject
switch (psBuilding->pStructureType->type)
{
case REF_RESEARCH:
{
pSubject = psBuilding->pFunctionality->researchFacility.psSubject;
break;
}
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
{
pSubject = psBuilding->pFunctionality->factory.psSubject;
break;
}
default:
break;
}
if (pSubject != NULL)
{
return false;
}
return true;
}
/*checks to see if any structure exists of a specified type with a specified status */
BOOL checkStructureStatus( STRUCTURE_STATS *psStats, UDWORD player, UDWORD status)
{
STRUCTURE *psStructure;
BOOL found = false;
for (psStructure = apsStructLists[player]; psStructure != NULL;
psStructure = psStructure->psNext)
{
if (psStructure->pStructureType->type == psStats->type)
{
//need to check if THIS instance of the type has the correct status
if (psStructure->status == status)
{
found = true;
break;
}
}
}
return found;
}
/*checks to see if a specific structure type exists -as opposed to a structure
stat type*/
BOOL checkSpecificStructExists(UDWORD structInc, UDWORD player)
{
STRUCTURE *psStructure;
BOOL found = false;
ASSERT( structInc < numStructureStats,
"checkSpecificStructExists: invalid structure inc" );
for (psStructure = apsStructLists[player]; psStructure != NULL;
psStructure = psStructure->psNext)
{
if (psStructure->status == SS_BUILT)
{
if ((psStructure->pStructureType->ref - REF_STRUCTURE_START) ==
structInc)
{
found = true;
break;
}
}
}
return found;
}
/*finds a suitable position for the assembly point based on one passed in*/
void findAssemblyPointPosition(UDWORD *pX, UDWORD *pY, UDWORD player)
{
//set up a dummy stat pointer
STRUCTURE_STATS sStats;
UDWORD passes = 0;
SDWORD i,j,startX,endX,startY,endY;
sStats.ref = 0;
sStats.baseWidth = 1;
sStats.baseBreadth = 1;
/* Initial box dimensions and set iteration count to zero */
startX = endX = *pX; startY = endY = *pY;
passes = 0;
//if the value passed in is not a valid location - find one!
if (!validLocation((BASE_STATS *)&sStats, *pX, *pY, player, false))
{
/* Keep going until we get a tile or we exceed distance */
while(passes < LOOK_FOR_EMPTY_TILE)
{
/* Process whole box */
for(i = startX; i <= endX; i++)
{
for(j = startY; j<= endY; j++)
{
/* Test only perimeter as internal tested previous iteration */
if(i==startX || i==endX || j==startY || j==endY)
{
/* Good enough? */
if(validLocation((BASE_STATS *)&sStats, i, j, player, false))
{
/* Set exit conditions and get out NOW */
*pX = i;
*pY = j;
//jump out of the loop
return;
}
}
}
}
/* Expand the box out in all directions - off map handled by validLocation() */
startX--; startY--; endX++; endY++; passes++;
}
}
else
{
//the first location was valid
return;
}
/* If we got this far, then we failed - passed in values will be unchanged */
ASSERT(!"unable to find a valid location", "findAssemblyPointPosition: unable to find a valid location!");
}
/*sets the point new droids go to - x/y in world coords for a Factory
bCheck is set to true for initial placement of the Assembly Point*/
void setAssemblyPoint(FLAG_POSITION *psAssemblyPoint, UDWORD x, UDWORD y,
UDWORD player, BOOL bCheck)
{
ASSERT( psAssemblyPoint != NULL,
"setAssemblyPoint: invalid AssemblyPoint pointer" );
//check its valid
x = map_coord(x);
y = map_coord(y);
if (bCheck)
{
findAssemblyPointPosition(&x, &y, player);
}
//add half a tile so the centre is in the middle of the tile
x = world_coord(x) + TILE_UNITS/2;
y = world_coord(y) + TILE_UNITS/2;
psAssemblyPoint->coords.x = x;
psAssemblyPoint->coords.y = y;
// Deliv Point sits at the height of the tile it's centre is on + arbitary amount!
psAssemblyPoint->coords.z = map_Height(x, y) + ASSEMBLY_POINT_Z_PADDING;
}
/*sets the factory Inc for the Assembly Point*/
void setFlagPositionInc(FUNCTIONALITY* pFunctionality, UDWORD player, UBYTE factoryType)
{
UBYTE inc;
UBYTE mask = 1;
FACTORY *psFactory;
REPAIR_FACILITY *psRepair;
ASSERT( player < MAX_PLAYERS, "setFlagPositionInc: invalid player number" );
//find the first vacant slot
for (inc = 0; inc < MAX_FACTORY; inc++)
{
if ((factoryNumFlag[player][factoryType] & mask) != mask)
{
break;
}
mask <<= 1;
}
if (inc >= MAX_FACTORY)
{
//this may happen now with electronic warfare
#ifdef DEBUG
const char* pType;
switch (factoryType)
{
case FACTORY_FLAG:
pType = "Factory";
break;
case CYBORG_FLAG:
pType = "Cyborg Factory";
break;
case VTOL_FLAG:
pType = "VTOL Factory";
break;
case REPAIR_FLAG:
pType = "Repair Facility";
break;
default:
pType = "";
break;
}
ASSERT(!"building more factories than allowed", "Building more than %d %s for player %d", MAX_FACTORY, pType, player);
#endif
inc = 1;
}
if (factoryType == REPAIR_FLAG)
{
psRepair = &pFunctionality->repairFacility;
psRepair->psDeliveryPoint->factoryInc = 0;
psRepair->psDeliveryPoint->factoryType = factoryType;
}
else
{
psFactory = &pFunctionality->factory;
psFactory->psAssemblyPoint->factoryInc = inc;
psFactory->psAssemblyPoint->factoryType = factoryType;
factoryNumFlag[player][factoryType] |= mask;
}
}
/* called from order.c.. delivery/assembly point handler*/
/*now called from display.c */
void processDeliveryPoint(UDWORD player, UDWORD x, UDWORD y)
{
FLAG_POSITION *psCurrFlag;//,*psFlag;//,*psNewFlag
for (psCurrFlag = apsFlagPosLists[player]; psCurrFlag; psCurrFlag = psCurrFlag->psNext)
{
// must be selected and have a valid pos.
if (psCurrFlag->selected)
{
setAssemblyPoint(psCurrFlag, x, y, player, true);
//deselect once moved
psCurrFlag->selected = false;
return; //will want to break if more than one can be selected?
}
}
}
/*called when a structure has been built - checks through the list of callbacks
for the scripts*/
void structureCompletedCallback(STRUCTURE_STATS *psStructType)
{
if (psStructType->type == REF_POWER_GEN)
{
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_POWERGEN_BUILT);
}
if (psStructType->type == REF_RESOURCE_EXTRACTOR)
{
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_RESEX_BUILT);
}
if (psStructType->type == REF_RESEARCH)
{
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_RESEARCH_BUILT);
}
if (psStructType->type == REF_FACTORY ||
psStructType->type == REF_CYBORG_FACTORY ||
psStructType->type == REF_VTOL_FACTORY)
{
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_FACTORY_BUILT);
}
}
STRUCTURE_STATS * structGetDemolishStat( void )
{
ASSERT(g_psStatDestroyStruct != NULL , "stat not initialised");
return g_psStatDestroyStruct;
}
/*sets the flag to indicate a HQ Exists - so draw Radar*/
void setHQExists(BOOL state, UDWORD player)
{
hqExists[player] = (UBYTE)state;
}
/*returns the status of the flag*/
BOOL getHQExists(UDWORD player)
{
return hqExists[player];
}
/*sets the flag to indicate a SatUplink Exists - so draw everything!*/
void setSatUplinkExists(BOOL state, UDWORD player)
{
satUplinkExists[player] = (UBYTE)state;
}
/*returns the status of the flag*/
BOOL getSatUplinkExists(UDWORD player)
{
return satUplinkExists[player];
}
/*sets the flag to indicate a Las Sat Exists - ONLY EVER WANT ONE*/
void setLasSatExists(BOOL state, UDWORD player)
{
lasSatExists[player] = (UBYTE)state;
}
/*returns the status of the flag*/
BOOL getLasSatExists(UDWORD player)
{
return lasSatExists[player];
}
/* calculate muzzle tip location in 3d world */
BOOL calcStructureMuzzleLocation(STRUCTURE *psStructure, Vector3f *muzzle, int weapon_slot)
{
iIMDShape *psShape = psStructure->pStructureType->pIMD, *psWeaponImd = NULL;
CHECK_STRUCTURE(psStructure);
if (psStructure->asWeaps[weapon_slot].nStat > 0)
{
psWeaponImd = asWeaponStats[psStructure->asWeaps[weapon_slot].nStat].pIMD;
}
if(psShape && psShape->nconnectors)
{
Vector3f barrel = {0.0f, 0.0f, 0.0f};
pie_MatBegin();
pie_TRANSLATE(psStructure->pos.x, -psStructure->pos.z, psStructure->pos.y);
//matrix = the center of droid
pie_MatRotY( DEG( psStructure->direction ) );
pie_MatRotX( DEG( psStructure->pitch ) );
pie_MatRotZ( DEG( -psStructure->roll ) );
pie_TRANSLATE( psShape->connectors[weapon_slot].x, -psShape->connectors[weapon_slot].z,
-psShape->connectors[weapon_slot].y);//note y and z flipped
//matrix = the gun and turret mount on the body
pie_MatRotY(DEG(psStructure->turretRotation[weapon_slot]));//+ve anticlockwise
pie_MatRotX(DEG(psStructure->turretPitch[weapon_slot]));//+ve up
pie_MatRotZ(DEG(0));
//matrix = the muzzle mount on turret
if( psWeaponImd && psWeaponImd->nconnectors )
{
barrel = Vector3f_New(psWeaponImd->connectors->x, -psWeaponImd->connectors->y, -psWeaponImd->connectors->z);
}
pie_RotateTranslate3f(&barrel, muzzle);
muzzle->z = -muzzle->z;
pie_MatEnd();
}
else
{
*muzzle = Vector3f_New(psStructure->pos.x, psStructure->pos.y, psStructure->pos.z + psStructure->sDisplay.imd->max.y);
}
return true;
}
/*Looks through the list of structures to see if there are any inactive
resource extractors*/
void checkForResExtractors(STRUCTURE *psBuilding)
{
STRUCTURE *psCurr;
POWER_GEN *psPowerGen;
RES_EXTRACTOR *psResExtractor;
UDWORD i;
SDWORD slot;
if (psBuilding->pStructureType->type != REF_POWER_GEN)
{
ASSERT(!"invalid structure type", "checkForResExtractors: invalid structure type");
return;
}
psPowerGen = &psBuilding->pFunctionality->powerGenerator;
//count the number of allocated slots
slot = 0;//-1;
for (i=0; i < NUM_POWER_MODULES; i++)
{
if (psPowerGen->apResExtractors[i] != NULL)
{
slot++;
//make sure the derrrick is active if any oil left
psResExtractor = &psPowerGen->apResExtractors[i]->pFunctionality->resourceExtractor;
if (psResExtractor->power)
{
psResExtractor->active = true;
}
}
}
psResExtractor = NULL;
//each Power Gen can cope with 4 Extractors now - 9/6/98 AB
//check capacity against number of filled slots
if (slot < NUM_POWER_MODULES)
{
for (psCurr = apsStructLists[psBuilding->player]; psCurr != NULL;
psCurr = psCurr->psNext)
{
if (psCurr->pStructureType->type == REF_RESOURCE_EXTRACTOR)
{
psResExtractor = &psCurr->pFunctionality->resourceExtractor;
//check not connected and power left and built!
if (!psResExtractor->active
&& psCurr->status == SS_BUILT
&& psResExtractor->power)
{
//assign the extractor to the power generator - use first vacant slot
for (i = 0; i < NUM_POWER_MODULES; i++)
{
if (psPowerGen->apResExtractors[i] == NULL)
{
psPowerGen->apResExtractors[i] = psCurr;
break;
}
}
//set the owning power gen up for the resource extractor
psResExtractor->psPowerGen = psBuilding;
//set the res Extr to active
psResExtractor->active = true;
psResExtractor->timeLastUpdated = gameTime;
slot++;
//each Power Gen can cope with 4 Extractors now - 9/6/98 AB
//check to see if any more vacant slots
if (slot >= NUM_POWER_MODULES)
{
//full up so quit out
break;
}
}
}
}
}
}
/*Looks through the list of structures to see if there are any Power Gens
with available slots for the new Res Ext*/
void checkForPowerGen(STRUCTURE *psBuilding)
{
STRUCTURE *psCurr;
UDWORD i;
SDWORD slot;
POWER_GEN *psPG;
RES_EXTRACTOR *psRE;
if (psBuilding->pStructureType->type != REF_RESOURCE_EXTRACTOR)
{
ASSERT(!"invalid structure type", "checkForPowerGen: invalid structure type");
return;
}
psRE = &psBuilding->pFunctionality->resourceExtractor;
if (psRE->active)
{
return;
}
//loop thru the current structures
for (psCurr = apsStructLists[psBuilding->player]; psCurr != NULL;
psCurr = psCurr->psNext)
{
if (psCurr->pStructureType->type == REF_POWER_GEN && psCurr->status == SS_BUILT)
{
psPG = &psCurr->pFunctionality->powerGenerator;
//check capacity against number of filled slots
slot = 0;//-1;
for (i=0; i < NUM_POWER_MODULES; i++)
{
if (psPG->apResExtractors[i] != NULL)
{
slot++;
}
}
//each Power Gen can cope with 4 Extractors now - 9/6/98 AB
//each Power Gen can cope with 4 extractors
if (slot < NUM_POWER_MODULES )
{
//find the first vacant slot
for (i=0; i < NUM_POWER_MODULES; i++)
{
if (psPG->apResExtractors[i] == NULL)
{
psPG->apResExtractors[i] = psBuilding;
psRE->psPowerGen = psCurr;
psRE->active = true;
psRE->timeLastUpdated = gameTime;
return;
}
}
}
}
}
}
/*initialise the slot the Resource Extractor filled in the owning Power Gen*/
void informPowerGen(STRUCTURE *psStruct)
{
UDWORD i;
POWER_GEN *psPowerGen;
if (psStruct->pStructureType->type != REF_RESOURCE_EXTRACTOR)
{
ASSERT(!"invalid structure type", "informPowerGen: invalid structure type");
return;
}
//get the owning power generator
psPowerGen = &psStruct->pFunctionality->resourceExtractor.psPowerGen->pFunctionality->powerGenerator;
if (psPowerGen)
{
for (i=0; i < NUM_POWER_MODULES; i++)
{
if (psPowerGen->apResExtractors[i] == psStruct)
{
//initialise the 'slot'
psPowerGen->apResExtractors[i] = NULL;
break;
}
}
}
}
/*called when a Res extractor is destroyed or runs out of power or is disconnected
adjusts the owning Power Gen so that it can link to a different Res Extractor if one
is available*/
void releaseResExtractor(STRUCTURE *psRelease)
{
STRUCTURE *psCurr;
if (psRelease->pStructureType->type != REF_RESOURCE_EXTRACTOR)
{
ASSERT(!"invalid structure type", "releaseResExtractor:Invalid structure type");
return;
}
//tell associated Power Gen
if (psRelease->pFunctionality->resourceExtractor.psPowerGen)
{
informPowerGen(psRelease);
}
psRelease->pFunctionality->resourceExtractor.psPowerGen = NULL;
//there may be spare resource extractors
for (psCurr = apsStructLists[psRelease->player]; psCurr != NULL; psCurr =
psCurr->psNext)
{
//check not connected and power left and built!
if (psCurr->pStructureType->type == REF_RESOURCE_EXTRACTOR
&& psCurr != psRelease
&& !psCurr->pFunctionality->resourceExtractor.active
&& psCurr->pFunctionality->resourceExtractor.power
&& psCurr->status == SS_BUILT)
{
checkForPowerGen(psCurr);
}
}
}
/*called when a Power Gen is destroyed or is disconnected
adjusts the associated Res Extractors so that they can link to different Power
Gens if any are available*/
void releasePowerGen(STRUCTURE *psRelease)
{
STRUCTURE *psCurr;
POWER_GEN *psPowerGen;
UDWORD i;
if (psRelease->pStructureType->type != REF_POWER_GEN)
{
ASSERT(!"invalid structure type", "releasePowerGen: Invalid structure type");
return;
}
psPowerGen = &psRelease->pFunctionality->powerGenerator;
//go through list of res extractors, setting them to inactive
for (i=0; i < NUM_POWER_MODULES; i++)
{
if (psPowerGen->apResExtractors[i])
{
psPowerGen->apResExtractors[i]->pFunctionality->resourceExtractor.active = false;
psPowerGen->apResExtractors[i]->pFunctionality->resourceExtractor.psPowerGen = NULL;
psPowerGen->apResExtractors[i] = NULL;
}
}
//may have a power gen with spare capacity
for (psCurr = apsStructLists[psRelease->player]; psCurr != NULL; psCurr =
psCurr->psNext)
{
if (psCurr->pStructureType->type == REF_POWER_GEN &&
psCurr != psRelease && psCurr->status == SS_BUILT)
{
checkForResExtractors(psCurr);
}
}
}
/*this is called whenever a structure has finished building*/
void buildingComplete(STRUCTURE *psBuilding)
{
CHECK_STRUCTURE(psBuilding);
psBuilding->currentBuildPts = (SWORD)psBuilding->pStructureType->buildPoints;
psBuilding->status = SS_BUILT;
switch (psBuilding->pStructureType->type)
{
case REF_POWER_GEN:
checkForResExtractors(psBuilding);
if(selectedPlayer == psBuilding->player)
{
audio_PlayObjStaticTrack( (void *) psBuilding, ID_SOUND_POWER_HUM );
}
break;
case REF_RESOURCE_EXTRACTOR:
checkForPowerGen(psBuilding);
/* GJ HACK! - add anim to deriks */
if (psBuilding->psCurAnim == NULL)
{
psBuilding->psCurAnim = animObj_Add(psBuilding, ID_ANIM_DERIK, 0, 0);
}
break;
case REF_RESEARCH:
intCheckResearchButton();
//this deals with researc facilities that are upgraded whilst mid-research
releaseResearch(psBuilding);
break;
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
//this deals with factories that are upgraded whilst mid-production
releaseProduction(psBuilding);
break;
case REF_SAT_UPLINK:
revealAll(psBuilding->player);
default:
//do nothing
break;
}
}
/*for a given structure, return a pointer to its module stat */
STRUCTURE_STATS* getModuleStat(STRUCTURE *psStruct)
{
STRUCTURE_STATS *psStat;
ASSERT( psStruct != NULL,
"getModuleStat: Invalid structure pointer" );
psStat = NULL;
switch (psStruct->pStructureType->type)
{
case REF_POWER_GEN:
psStat = &asStructureStats[powerModuleStat];
break;
case REF_FACTORY:
case REF_VTOL_FACTORY:
psStat = &asStructureStats[factoryModuleStat];
break;
case REF_RESEARCH:
psStat = &asStructureStats[researchModuleStat];
break;
default:
//no other structures can have modules attached
break;
}
return psStat;
}
/* count the artillery droids assigned to a structure (only makes sence by sensor towers and headquarters) */
unsigned int countAssignedDroids(STRUCTURE *psStructure)
{
DROID *psCurr;
SDWORD weapontype, hasindirect;
unsigned int num;
if(psStructure == NULL)
return 0;
for (num = 0, psCurr = apsDroidLists[selectedPlayer]; psCurr; psCurr = psCurr->psNext)
{
if (psCurr->psTarget && psCurr->player == psStructure->player)
{
hasindirect = 0;
weapontype = asWeaponStats[psCurr->asWeaps[0].nStat].movementModel;
if(weapontype == MM_INDIRECT || weapontype == MM_HOMINGINDIRECT)
{
hasindirect = 1;
}
if (psCurr->psTarget->id == psStructure->id && hasindirect)
{
num++;
}
}
}
return num;
}
//print some info at the top of the screen dependant on the structure
void printStructureInfo(STRUCTURE *psStructure)
{
unsigned int numConnected, i;
POWER_GEN *psPowerGen;
ASSERT( psStructure != NULL, "printStructureInfo: Invalid Structure pointer" );
switch (psStructure->pStructureType->type)
{
case REF_HQ:
#ifdef DEBUG
if (getDebugMappingStatus())
{
CONPRINTF(ConsoleString, (ConsoleString, "%s - %d Units assigned - ID %d - sensor range %hu power %hu - ECM %u",
getStatName(psStructure->pStructureType), countAssignedDroids(psStructure),
psStructure->id, structSensorRange(psStructure), structSensorPower(psStructure), structConcealment(psStructure)));
}
else
#endif
{
unsigned int assigned_droids = countAssignedDroids(psStructure);
CONPRINTF(ConsoleString, (ConsoleString, ngettext("%s - %u Unit assigned", "%s - %u Units assigned", assigned_droids),
getStatName(psStructure->pStructureType), assigned_droids));
}
break;
case REF_DEFENSE:
if (psStructure->pStructureType->pSensor == NULL)
{
break;
}
#ifdef DEBUG
else if (getDebugMappingStatus())
{
CONPRINTF(ConsoleString, (ConsoleString, "%s - %d Units assigned - ID %d - armour %d|%d - sensor range %hu power %hu - ECM %u",
getStatName(psStructure->pStructureType), countAssignedDroids(psStructure),
psStructure->id, psStructure->armour[0][WC_KINETIC], psStructure->armour[0][WC_HEAT],
structSensorRange(psStructure), structSensorPower(psStructure), structConcealment(psStructure)));
}
#endif
else
{
unsigned int assigned_droids = countAssignedDroids(psStructure);
CONPRINTF(ConsoleString, (ConsoleString, ngettext("%s - %u Unit assigned", "%s - %u Units assigned", assigned_droids),
getStatName(psStructure->pStructureType), assigned_droids));
}
break;
case REF_REPAIR_FACILITY:
#ifdef DEBUG
if (getDebugMappingStatus())
{
CONPRINTF(ConsoleString,(ConsoleString, "%s - Unique ID %d - Queue %d",
getStatName(psStructure->pStructureType), psStructure->id, psStructure->pFunctionality->repairFacility.droidQueue));
}
else
#endif
{
CONPRINTF(ConsoleString, (ConsoleString, getStatName(psStructure->pStructureType)));
}
break;
case REF_RESOURCE_EXTRACTOR:
#ifdef DEBUG
if (getDebugMappingStatus())
{
CONPRINTF(ConsoleString,(ConsoleString, "%s - Unique ID %d",
getStatName(psStructure->pStructureType), psStructure->id));
}
else
#endif
{
CONPRINTF(ConsoleString, (ConsoleString, getStatName(psStructure->pStructureType)));
}
break;
case REF_POWER_GEN:
psPowerGen = &psStructure->pFunctionality->powerGenerator;
numConnected = 0;
for (i = 0; i < NUM_POWER_MODULES; i++)
{
if (psPowerGen->apResExtractors[i])
{
numConnected++;
}
}
#ifdef DEBUG
if (getDebugMappingStatus())
{
CONPRINTF(ConsoleString, (ConsoleString, "%s - Connected %u of %u - Unique ID %u",
getStatName(psStructure->pStructureType), numConnected, NUM_POWER_MODULES,
psStructure->id));
}
else
#endif
{
CONPRINTF(ConsoleString, (ConsoleString, _("%s - Connected %u of %u"),
getStatName(psStructure->pStructureType), numConnected, NUM_POWER_MODULES));
}
break;
default:
#ifdef DEBUG
if (getDebugMappingStatus())
{
CONPRINTF(ConsoleString, (ConsoleString, "%s - Damage % 3.2f%% - Unique ID %u",
getStatName(psStructure->pStructureType), getStructureDamage(psStructure) * 100.f, psStructure->id));
}
else
#endif
{
CONPRINTF(ConsoleString, (ConsoleString, _("%s - Damage %3.0f%%"),
getStatName(psStructure->pStructureType), getStructureDamage(psStructure) * 100.f));
}
break;
}
}
/*Checks the template type against the factory type - returns false
if not a good combination!*/
BOOL validTemplateForFactory(DROID_TEMPLATE *psTemplate, STRUCTURE *psFactory)
{
//not in multiPlayer! - AB 26/5/99
if (!bMultiPlayer)
{
//ignore Transporter Droids
if (psTemplate->droidType == DROID_TRANSPORTER)
{
return false;
}
}
//check if droid is a cyborg
if (psTemplate->droidType == DROID_CYBORG ||
psTemplate->droidType == DROID_CYBORG_SUPER ||
psTemplate->droidType == DROID_CYBORG_CONSTRUCT ||
psTemplate->droidType == DROID_CYBORG_REPAIR)
{
if (psFactory->pStructureType->type != REF_CYBORG_FACTORY)
{
return false;
}
}
//check for VTOL droid
else if ((asPropulsionStats + psTemplate->asParts[COMP_PROPULSION])->
propulsionType == PROPULSION_TYPE_LIFT)
{
if (psFactory->pStructureType->type != REF_VTOL_FACTORY)
{
return false;
}
}
//check if cyborg factory
if (psFactory->pStructureType->type == REF_CYBORG_FACTORY)
{
//if (psTemplate->droidType != DROID_CYBORG)
if (!(psTemplate->droidType == DROID_CYBORG ||
psTemplate->droidType == DROID_CYBORG_SUPER ||
psTemplate->droidType == DROID_CYBORG_CONSTRUCT ||
psTemplate->droidType == DROID_CYBORG_REPAIR))
{
return false;
}
}
//check if vtol factory
else if (psFactory->pStructureType->type == REF_VTOL_FACTORY)
{
if ((asPropulsionStats + psTemplate->asParts[COMP_PROPULSION])->
propulsionType != PROPULSION_TYPE_LIFT)
{
return false;
}
}
//got through all the tests...
return true;
}
/*calculates the damage caused to the resistance levels of structures - returns
true when captured*/
BOOL electronicDamage(BASE_OBJECT *psTarget, UDWORD damage, UBYTE attackPlayer)
{
STRUCTURE *psStructure;
DROID *psDroid;
BOOL bCompleted = true;
Vector3i pos;
UDWORD i;
ASSERT(attackPlayer < MAX_PLAYERS, "electronicDamage: invalid player id %d", (int)attackPlayer);
ASSERT(psTarget != NULL, "electronicDamage: target is NULL");
if (attackPlayer >= MAX_PLAYERS || psTarget == NULL)
{
return false;
}
//structure electronic damage
if (psTarget->type == OBJ_STRUCTURE)
{
psStructure = (STRUCTURE *)psTarget;
bCompleted = false;
if (psStructure->pStructureType->resistance == 0)
{
return false; // this structure type cannot be taken over
}
//if resistance is already less than 0 don't do any more
if (psStructure->resistance < 0)
{
bCompleted = true;
}
else
{
//store the time it was hit
psStructure->timeLastHit = gameTime;
psStructure->lastHitWeapon = WSC_ELECTRONIC;
// tell the cluster system it has been attacked
clustObjectAttacked((BASE_OBJECT *)psStructure);
psStructure->resistance = (SWORD)(psStructure->resistance - damage);
if (psStructure->resistance < 0)
{
//add a console message for the selected Player
if (psStructure->player == selectedPlayer)
{
CONPRINTF(ConsoleString,(ConsoleString,
_("%s - Electronically Damaged"),
getStatName(psStructure->pStructureType)));
//tell the scripts if selectedPlayer has lost a structure
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_ELECTRONIC_TAKEOVER);
}
bCompleted = true;
//give the structure to the attacking player
(void)giftSingleStructure(psStructure, attackPlayer, false);
}
}
}
//droid electronic damage
else if (psTarget->type == OBJ_DROID)
{
psDroid = (DROID *)psTarget;
bCompleted = false;
//in multiPlayer cannot attack a Transporter with EW
if (bMultiPlayer)
{
if (psDroid->droidType == DROID_TRANSPORTER)
{
ASSERT(!"can't attack a Transporter while in multiplayer", "electronicDamage: Cannot attack a Transporter in multiPlayer");
return true;
}
}
if (psDroid->resistance == ACTION_START_TIME)
{
//need to set the current resistance level since not been previously attacked (by EW)
psDroid->resistance = droidResistance(psDroid);
}
if (psDroid->resistance < 0)
{
bCompleted = true;
}
else
{
// tell the cluster system it has been attacked
clustObjectAttacked((BASE_OBJECT *)psDroid);
psDroid->resistance = (SWORD)(psDroid->resistance - damage);
if (psDroid->resistance <= 0)
{
//add a console message for the selected Player
if (psDroid->player == selectedPlayer)
{
CONPRINTF(ConsoleString, (ConsoleString, _("%s - Electronically Damaged"), "Unit"));
//tell the scripts if selectedPlayer has lost a droid
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_ELECTRONIC_TAKEOVER);
}
bCompleted = true;
//give the droid to the attacking player
if(psDroid->visible[selectedPlayer])
{
for(i=0; i<5; i++)
{
pos.x = psDroid->pos.x + (30-rand()%60);
pos.z = psDroid->pos.y + (30-rand()%60);
pos.y = psDroid->pos.z + (rand()%8);
effectGiveAuxVar(80);
addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_FLAMETHROWER,false,NULL,0);
}
}
(void)giftSingleDroid(psDroid, attackPlayer);
// tell the world!
if (bMultiPlayer)
{
uint8_t giftType = DROID_GIFT, droid_count = 1;
NETbeginEncode(NET_GIFT, NET_ALL_PLAYERS);
{
// We need to distinguish between gift types
NETuint8_t(&giftType);
// send the player originally owning this droid
// and the new owner
NETuint8_t(&psDroid->player);
NETuint8_t(&attackPlayer);
// the amount of droids (1 in this case)
// followed by the droid's ID
NETuint8_t(&droid_count);
NETuint32_t(&psDroid->id);
}
NETend();
}
//check to see if droid limit reached, if so recycle.
// don't check for transporter/mission coz multiplayer only issue.
if( (getNumDroids(attackPlayer)+1 ) > getMaxDroids(attackPlayer) )
{
recycleDroid(psDroid);
}
}
}
}
return bCompleted;
}
/* EW works differently in multiplayer mode compared with single player.*/
BOOL validStructResistance(STRUCTURE *psStruct)
{
BOOL bTarget = false;
ASSERT( psStruct != NULL, "invalidStructResistance: invalid structure pointer" );
if (psStruct->pStructureType->resistance != 0)
{
/*certain structures will only provide rewards in multiplayer so
before they can become valid targets their resistance must be at least
half the base value*/
if (bMultiPlayer)
{
switch(psStruct->pStructureType->type)
{
case REF_RESEARCH:
case REF_FACTORY:
case REF_VTOL_FACTORY:
case REF_CYBORG_FACTORY:
case REF_HQ:
case REF_REPAIR_FACILITY:
if (psStruct->resistance >= (SDWORD) (structureResistance(psStruct->
pStructureType, psStruct->player) / 2))
{
bTarget = true;
}
break;
default:
bTarget = true;
}
}
else
{
bTarget = true;
}
}
return bTarget;
}
/*Access functions for the upgradeable stats of a structure*/
UDWORD structureBody(const STRUCTURE *psStructure)
{
const STRUCTURE_STATS *psStats = psStructure->pStructureType;
const UBYTE player = psStructure->player;
switch(psStats->type)
{
// wall/defence structures:
case REF_DEFENSE:
case REF_WALL:
case REF_WALLCORNER:
case REF_BLASTDOOR:
return psStats->bodyPoints + (psStats->bodyPoints * asWallDefenceUpgrade[player].body)/100;
// all other structures:
default:
return structureBaseBody(psStructure) + (structureBaseBody(psStructure) * asStructureUpgrade[player].body)/100;
}
}
/*this returns the Base Body points of a structure - regardless of upgrade*/
UDWORD structureBaseBody(const STRUCTURE *psStructure)
{
const STRUCTURE_STATS *psStats = psStructure->pStructureType;
ASSERT( psStructure != NULL, "structureBaseBody: invalid structure pointer" );
switch(psStats->type)
{
// modules may be attached:
case REF_FACTORY:
case REF_VTOL_FACTORY:
ASSERT(psStructure->pFunctionality != NULL, "structureBaseBody: invalid structure functionality pointer");
if (psStructure->pFunctionality->factory.capacity > 0)
{
//add on the default for the factory
return psStats->bodyPoints + asStructureStats[factoryModuleStat].bodyPoints * psStructure->pFunctionality->factory.capacity;
}
else
{
//no modules
return psStats->bodyPoints;
}
case REF_RESEARCH:
ASSERT(psStructure->pFunctionality != NULL, "structureBaseBody: invalid structure functionality pointer");
if (psStructure->pFunctionality->researchFacility.capacity > 0)
{
//add on the default for the factory
return psStats->bodyPoints + asStructureStats[researchModuleStat].bodyPoints;
}
else
{
//no modules
return psStats->bodyPoints;
}
case REF_POWER_GEN:
ASSERT(psStructure->pFunctionality != NULL, "structureBaseBody: invalid structure functionality pointer");
if (psStructure->pFunctionality->powerGenerator.capacity > 0)
{
//add on the default for the factory
return psStats->bodyPoints + asStructureStats[powerModuleStat].bodyPoints;
}
else
{
//no modules
return psStats->bodyPoints;
}
// all other structures:
default:
return psStats->bodyPoints;
}
}
UDWORD structureArmour(STRUCTURE_STATS *psStats, UBYTE player)
{
switch(psStats->type)
{
case REF_DEFENSE:
case REF_WALL:
case REF_WALLCORNER:
case REF_BLASTDOOR:
return (psStats->armourValue + (psStats->armourValue *
asWallDefenceUpgrade[player].armour)/100);
break;
default:
return (psStats->armourValue + (psStats->armourValue *
asStructureUpgrade[player].armour)/100);
break;
}
}
UDWORD structureResistance(STRUCTURE_STATS *psStats, UBYTE player)
{
switch(psStats->type)
{
case REF_WALL:
case REF_WALLCORNER:
case REF_BLASTDOOR:
//not upgradeable
return psStats->resistance;
break;
default:
return (psStats->resistance + (psStats->resistance *
asStructureUpgrade[player].resistance)/100);
break;
}
}
/*gives the attacking player a reward based on the type of structure that has
been attacked*/
BOOL electronicReward(STRUCTURE *psStructure, UBYTE attackPlayer)
{
BOOL bRewarded = false;
switch(psStructure->pStructureType->type)
{
case REF_RESEARCH:
researchReward(psStructure->player, attackPlayer);
bRewarded = true;
break;
case REF_FACTORY:
case REF_VTOL_FACTORY:
case REF_CYBORG_FACTORY:
factoryReward(psStructure->player, attackPlayer);
bRewarded = true;
break;
case REF_HQ:
hqReward(psStructure->player,attackPlayer);
if (attackPlayer == selectedPlayer)
{
addConsoleMessage(_("Electronic Reward - Visibility Report"), DEFAULT_JUSTIFY,SYSTEM_MESSAGE);
}
bRewarded = true;
break;
case REF_REPAIR_FACILITY:
repairFacilityReward(psStructure->player,attackPlayer);
bRewarded = true;
break;
default:
bRewarded = false;
}
return bRewarded;
}
/*find the 'best' prop/body/weapon component the losing player has and
'give' it to the reward player*/
void factoryReward(UBYTE losingPlayer, UBYTE rewardPlayer)
{
UDWORD inc, comp = 0;
//search through the propulsions first
for (inc=0; inc < numPropulsionStats; inc++)
{
if (apCompLists[losingPlayer][COMP_PROPULSION][inc] == AVAILABLE &&
apCompLists[rewardPlayer][COMP_PROPULSION][inc] != AVAILABLE)
{
if (asPropulsionStats[inc].buildPower > asPropulsionStats[comp].buildPower)
{
comp = inc;
}
}
}
if (comp != 0)
{
apCompLists[rewardPlayer][COMP_PROPULSION][comp] = AVAILABLE;
if (rewardPlayer == selectedPlayer)
{
CONPRINTF(ConsoleString,(ConsoleString,"%s :- %s",
_("Factory Reward - Propulsion"),
getName(asPropulsionStats[comp].pName)));
}
return;
}
//haven't found a propulsion - look for a body
for (inc=0; inc < numBodyStats; inc++)
{
if (apCompLists[losingPlayer][COMP_BODY][inc] == AVAILABLE &&
apCompLists[rewardPlayer][COMP_BODY][inc] != AVAILABLE)
{
if (asBodyStats[inc].buildPower > asBodyStats[comp].buildPower)
{
comp = inc;
}
}
}
if (comp != 0)
{
apCompLists[rewardPlayer][COMP_BODY][comp] = AVAILABLE;
if (rewardPlayer == selectedPlayer)
{
CONPRINTF(ConsoleString,(ConsoleString,"%s :- %s",
_("Factory Reward - Body"),
getName(asBodyStats[comp].pName)));
}
return;
}
//haven't found a body - look for a weapon
for (inc=0; inc < numWeaponStats; inc++)
{
if (apCompLists[losingPlayer][COMP_WEAPON][inc] == AVAILABLE &&
apCompLists[rewardPlayer][COMP_WEAPON][inc] != AVAILABLE)
{
if (asWeaponStats[inc].buildPower > asWeaponStats[comp].buildPower)
{
comp = inc;
}
}
}
if (comp != 0)
{
apCompLists[rewardPlayer][COMP_WEAPON][comp] = AVAILABLE;
if (rewardPlayer == selectedPlayer)
{
CONPRINTF(ConsoleString,(ConsoleString,"%s :- %s",
_("Factory Reward - Weapon"),
getName(asWeaponStats[comp].pName)));
}
return;
}
//losing Player hasn't got anything better so don't gain anything!
if (rewardPlayer == selectedPlayer)
{
addConsoleMessage(_("Factory Reward - Nothing"), DEFAULT_JUSTIFY,SYSTEM_MESSAGE);
}
}
/*find the 'best' repair component the losing player has and
'give' it to the reward player*/
void repairFacilityReward(UBYTE losingPlayer, UBYTE rewardPlayer)
{
UDWORD inc, comp = 0;
//search through the repair stats
for (inc=0; inc < numRepairStats; inc++)
{
if (apCompLists[losingPlayer][COMP_REPAIRUNIT][inc] == AVAILABLE &&
apCompLists[rewardPlayer][COMP_REPAIRUNIT][inc] != AVAILABLE)
{
if (asRepairStats[inc].buildPower > asRepairStats[comp].buildPower)
{
comp = inc;
}
}
}
if (comp != 0)
{
apCompLists[rewardPlayer][COMP_REPAIRUNIT][comp] = AVAILABLE;
if (rewardPlayer == selectedPlayer)
{
CONPRINTF(ConsoleString,(ConsoleString,"%s :- %s",
_("Repair Facility Award - Repair"),
getName(asRepairStats[comp].pName)));
}
return;
}
if (rewardPlayer == selectedPlayer)
{
addConsoleMessage(_("Repair Facility Award - Nothing"), DEFAULT_JUSTIFY,SYSTEM_MESSAGE);
}
}
/*makes the losing players tiles/structures/features visible to the reward player*/
void hqReward(UBYTE losingPlayer, UBYTE rewardPlayer)
{
STRUCTURE *psStruct;
FEATURE *psFeat;
DROID *psDroid;
UDWORD x,y,i;
MAPTILE *psTile;
//tiles
for(x = 0; x < mapWidth; x++)
{
for(y = 0; y < mapHeight; y++)
{
psTile = mapTile(x,y);
if(TEST_TILE_VISIBLE(losingPlayer,psTile))
{
SET_TILE_VISIBLE(rewardPlayer,psTile);
if(getRevealStatus())
{
if(rewardPlayer == selectedPlayer)
{
avInformOfChange(x,y);
}
}
}
}
}
//struct
for(i=0;i<MAX_PLAYERS;i++)
{
for(psStruct = apsStructLists[i]; psStruct != NULL; psStruct = psStruct->psNext)
{
if( psStruct->visible[losingPlayer] && !psStruct->died)
{
psStruct->visible[rewardPlayer] = psStruct->visible[losingPlayer];
}
}
//feature
for(psFeat = apsFeatureLists[i]; psFeat != NULL; psFeat = psFeat->psNext)
{
if(psFeat->visible[losingPlayer] )
{
psFeat->visible[rewardPlayer] = psFeat->visible[losingPlayer];
}
}
//droids.
for(psDroid = apsDroidLists[i]; psDroid != NULL; psDroid = psDroid->psNext)
{
if(psDroid->visible[losingPlayer] || psDroid->player == losingPlayer)
{
psDroid->visible[rewardPlayer] =UBYTE_MAX;
}
}
}
}
// Return true if structure is a factory of any type.
//
BOOL StructIsFactory(STRUCTURE *Struct)
{
if( (Struct->pStructureType->type == REF_FACTORY) ||
(Struct->pStructureType->type == REF_CYBORG_FACTORY) ||
(Struct->pStructureType->type == REF_VTOL_FACTORY) )
{
return true;
}
return false;
}
// Return true if flag is a delivery point for a factory.
//
BOOL FlagIsFactory(FLAG_POSITION *psCurrFlag)
{
if( (psCurrFlag->factoryType == FACTORY_FLAG) || (psCurrFlag->factoryType == CYBORG_FLAG) ||
(psCurrFlag->factoryType == VTOL_FLAG) )
{
return true;
}
return false;
}
// Find a structure's delivery point , only if it's a factory.
// Returns NULL if not found or the structure isn't a factory.
//
FLAG_POSITION *FindFactoryDelivery(STRUCTURE *Struct)
{
FLAG_POSITION *psCurrFlag;
if(StructIsFactory(Struct))
{
// Find the factories delivery point.
for (psCurrFlag = apsFlagPosLists[selectedPlayer]; psCurrFlag;
psCurrFlag = psCurrFlag->psNext)
{
if(FlagIsFactory(psCurrFlag)
&& Struct->pFunctionality->factory.psAssemblyPoint->factoryInc == psCurrFlag->factoryInc
&& Struct->pFunctionality->factory.psAssemblyPoint->factoryType == psCurrFlag->factoryType)
{
return psCurrFlag;
}
}
}
return NULL;
}
//Find the factory associated with the delivery point - returns NULL if none exist
STRUCTURE *findDeliveryFactory(FLAG_POSITION *psDelPoint)
{
STRUCTURE *psCurr;
FACTORY *psFactory;
REPAIR_FACILITY *psRepair;
for (psCurr = apsStructLists[psDelPoint->player]; psCurr != NULL; psCurr =
psCurr->psNext)
{
if(StructIsFactory(psCurr))
{
psFactory = &psCurr->pFunctionality->factory;
if (psFactory->psAssemblyPoint->factoryInc == psDelPoint->factoryInc &&
psFactory->psAssemblyPoint->factoryType == psDelPoint->factoryType)
{
return psCurr;
}
}
else if (psCurr->pStructureType->type == REF_REPAIR_FACILITY)
{
psRepair = &psCurr->pFunctionality->repairFacility;
if (psRepair->psDeliveryPoint == psDelPoint)
{
return psCurr;
}
}
}
return NULL;
}
/*cancels the production run for the factory and returns any power that was
accrued but not used*/
void cancelProduction(STRUCTURE *psBuilding)
{
FACTORY *psFactory;
ASSERT(StructIsFactory(psBuilding), "structure not a factory");
psFactory = &psBuilding->pFunctionality->factory;
//check its the correct factory
if (psBuilding->player == productionPlayer && psFactory->psSubject)
{
//clear the production run for this factory
memset(asProductionRun[psFactory->psAssemblyPoint->factoryType][
psFactory->psAssemblyPoint->factoryInc], 0, sizeof(PRODUCTION_RUN) *
MAX_PROD_RUN);
//return any accrued power
if (psFactory->powerAccrued)
{
addPower(psBuilding->player, psFactory->powerAccrued);
}
//clear the factories subject and quantity
psFactory->psSubject = NULL;
psFactory->quantity = 0;
//tell the interface
intManufactureFinished(psBuilding);
}
}
/*set a factory's production run to hold*/
void holdProduction(STRUCTURE *psBuilding)
{
FACTORY *psFactory;
ASSERT( StructIsFactory(psBuilding), "holdProduction: structure not a factory" );
psFactory = &psBuilding->pFunctionality->factory;
if (psFactory->psSubject)
{
//set the time the factory was put on hold
psFactory->timeStartHold = gameTime;
//play audio to indicate on hold
if (psBuilding->player == selectedPlayer)
{
audio_PlayTrack(ID_SOUND_WINDOWCLOSE);
}
}
}
/*release a factory's production run from hold*/
void releaseProduction(STRUCTURE *psBuilding)
{
FACTORY *psFactory;
ASSERT( StructIsFactory(psBuilding),
"releaseProduction: structure not a factory" );
psFactory = &psBuilding->pFunctionality->factory;
if (psFactory->psSubject && psFactory->timeStartHold)
{
//adjust the start time for the current subject
if (psFactory->timeStarted != ACTION_START_TIME)
{
psFactory->timeStarted += (gameTime - psFactory->timeStartHold);
}
psFactory->timeStartHold = 0;
}
}
/*this is called when a factory produces a droid. The Template returned is the next
one to build - if any*/
DROID_TEMPLATE * factoryProdUpdate(STRUCTURE *psStructure, DROID_TEMPLATE *psTemplate)
{
UDWORD inc, factoryType, factoryInc;
FACTORY *psFactory;
CHECK_STRUCTURE(psStructure);
ASSERT( psStructure->player == productionPlayer,
"factoryProdUpdate: called for incorrect player" );
psFactory = &psStructure->pFunctionality->factory;
factoryType = psFactory->psAssemblyPoint->factoryType;
factoryInc = psFactory->psAssemblyPoint->factoryInc;
if (psTemplate != NULL)
{
//find the entry in the array for this template
for (inc=0; inc < MAX_PROD_RUN; inc++)
{
if (asProductionRun[factoryType][factoryInc][inc].psTemplate == psTemplate)
{
asProductionRun[factoryType][factoryInc][inc].built++;
if (asProductionRun[factoryType][factoryInc][inc].built <
asProductionRun[factoryType][factoryInc][inc].quantity)
{
return psTemplate;
}
break;
}
}
}
//find the next template to build - this just looks for the first uncompleted run
for (inc=0; inc < MAX_PROD_RUN; inc++)
{
if (asProductionRun[factoryType][factoryInc][inc].built <
asProductionRun[factoryType][factoryInc][inc].quantity)
{
return asProductionRun[factoryType][factoryInc][inc].psTemplate;
}
}
/*If you've got here there's nothing left to build unless factory is
on loop production*/
if (psFactory->quantity)
{
//reduce the loop count if not infinite
if (psFactory->quantity != INFINITE_PRODUCTION)
{
psFactory->quantity--;
}
//need to reset the quantity built for each entry in the production list
for (inc=0; inc < MAX_PROD_RUN; inc++)
{
if (asProductionRun[factoryType][factoryInc][inc].psTemplate)
{
asProductionRun[factoryType][factoryInc][inc].built = 0;
}
}
//get the first to build again
return (factoryProdUpdate(psStructure, NULL));
}
//if got to here then nothing left to produce so clear the array
memset(asProductionRun[factoryType][factoryInc], 0,
sizeof(PRODUCTION_RUN) * MAX_PROD_RUN);
return NULL;
}
//adjust the production run for this template type
void factoryProdAdjust(STRUCTURE *psStructure, DROID_TEMPLATE *psTemplate, BOOL add)
{
SDWORD spare = -1;
UDWORD inc, factoryType, factoryInc;
FACTORY *psFactory;
BOOL bAssigned = false, bCheckForCancel = false;
UBYTE built, quantity, remaining;
CHECK_STRUCTURE(psStructure);
ASSERT( psStructure->player == productionPlayer,
"factoryProdAdjust: called for incorrect player" );
psFactory = &psStructure->pFunctionality->factory;
factoryType = psFactory->psAssemblyPoint->factoryType;
factoryInc = psFactory->psAssemblyPoint->factoryInc;
//see if the template is already in the list
for (inc=0; inc < MAX_PROD_RUN; inc++)
{
if (asProductionRun[factoryType][factoryInc][inc].psTemplate == psTemplate)
{
//adjust the prod run
if (add) //user left clicked, so increase # in queue
{
// Allows us to queue up more units up to MAX_IN_RUN instead of ignoring how many we have built from that queue
quantity = ++asProductionRun[factoryType][factoryInc][inc].quantity;
built = asProductionRun[factoryType][factoryInc][inc].built;
remaining = quantity - built;
// check to see if user canceled all orders in queue
if (remaining > MAX_IN_RUN)
{
asProductionRun[factoryType][factoryInc][inc].quantity = 0;
//initialise the template
asProductionRun[factoryType][factoryInc][inc].psTemplate = NULL;
bCheckForCancel = true;
//add power back if we were working on this one
if (psFactory->psSubject == (BASE_STATS *)psTemplate)
{
addPower(psStructure->player, psFactory->powerAccrued);
//set the factory's power accrued back to zero
psFactory->powerAccrued = 0;
}
}
}
else //user right clicked, so we subtract form queue
{
if (asProductionRun[factoryType][factoryInc][inc].quantity == 0)
{
asProductionRun[factoryType][factoryInc][inc].quantity = MAX_IN_RUN;
}
else
{
asProductionRun[factoryType][factoryInc][inc].quantity--;
//add power back if we were working on this one
if (psFactory->psSubject == (BASE_STATS *)psTemplate)
{
addPower(psStructure->player, psFactory->powerAccrued);
//set the factory's power accrued back to zero
psFactory->powerAccrued = 0;
}
if (asProductionRun[factoryType][factoryInc][inc].quantity == 0)
{
//initialise the template
asProductionRun[factoryType][factoryInc][inc].psTemplate = NULL;
bCheckForCancel = true;
}
}
}
bAssigned = true;
break;
}
//check to see if any empty slots
if (spare == -1 && asProductionRun[factoryType][factoryInc][inc].quantity == 0)
{
spare = inc;
}
}
if (!bAssigned && spare != -1)
{
//start off a new template
asProductionRun[factoryType][factoryInc][spare].psTemplate = psTemplate;
if (add)
{
asProductionRun[factoryType][factoryInc][spare].quantity = 1;
}
else
{
//wrap around to max value
asProductionRun[factoryType][factoryInc][spare].quantity = MAX_IN_RUN;
}
}
//if nothing is allocated then the current factory may have been cancelled
if (bCheckForCancel)
{
for (inc=0; inc < MAX_PROD_RUN; inc++)
{
if (asProductionRun[factoryType][factoryInc][inc].psTemplate != NULL)
{
break;
}
}
if (inc == MAX_PROD_RUN)
{
//must have cancelled eveything - so tell the struct
psFactory->quantity = 0;
}
}
}
//returns the quantity of a specific template in the production list
UDWORD getProductionQuantity(STRUCTURE *psStructure, DROID_TEMPLATE *psTemplate)
{
UDWORD inc, factoryType, factoryInc;
FACTORY *psFactory;
if (psStructure == NULL) return 0;
if (psStructure->player == productionPlayer)
{
psFactory = &psStructure->pFunctionality->factory;
factoryType = psFactory->psAssemblyPoint->factoryType;
factoryInc = psFactory->psAssemblyPoint->factoryInc;
//see if the template is in the list
for (inc=0; inc < MAX_PROD_RUN; inc++)
{
if (asProductionRun[factoryType][factoryInc][inc].psTemplate == psTemplate)
{
return asProductionRun[factoryType][factoryInc][inc].quantity;
}
}
}
//not in the list so none being produced
return 0;
}
/*returns the quantity of a specific template in the production list that
have already been built*/
UDWORD getProductionBuilt(STRUCTURE *psStructure, DROID_TEMPLATE *psTemplate)
{
UDWORD inc, factoryType, factoryInc;
FACTORY *psFactory;
if (psStructure == NULL) return 0;
if (psStructure->player == productionPlayer)
{
psFactory = &psStructure->pFunctionality->factory;
factoryType = psFactory->psAssemblyPoint->factoryType;
factoryInc = psFactory->psAssemblyPoint->factoryInc;
//see if the template is in the list
for (inc=0; inc < MAX_PROD_RUN; inc++)
{
if (asProductionRun[factoryType][factoryInc][inc].psTemplate == psTemplate)
{
return asProductionRun[factoryType][factoryInc][inc].built;
}
}
}
//not in the list so none being produced
return 0;
}
/*looks through a players production list to see how many command droids
are being built*/
UBYTE checkProductionForCommand(UBYTE player)
{
UBYTE factoryInc, inc, factoryType;
UBYTE mask = 1, quantity = 0;
if (player == productionPlayer)
{
//assumes Cyborg or VTOL droids are not Command types!
factoryType = FACTORY_FLAG;
for (factoryInc = 0; factoryInc < MAX_FACTORY; factoryInc++)
{
//check to see if there is a factory
if ((factoryNumFlag[player][factoryType] & mask) == mask)
{
for (inc=0; inc < MAX_PROD_RUN; inc++)
{
if (asProductionRun[factoryType][factoryInc][inc].psTemplate)
{
if (asProductionRun[factoryType][factoryInc][inc].psTemplate->
droidType == DROID_COMMAND)
{
quantity = (UBYTE)(quantity + (asProductionRun[factoryType][
factoryInc][inc].quantity - asProductionRun[
factoryType][factoryInc][inc].built));
}
}
}
}
mask <<= 1;
}
}
return quantity;
}
// Count number of factories assignable to a command droid.
//
UWORD countAssignableFactories(UBYTE player,UWORD factoryType)
{
UWORD factoryInc;
UBYTE mask = 1, quantity = 0;
ASSERT( player == selectedPlayer,
"countAssignableFactories: should only be called for selectedPlayer" );
for (factoryInc = 0; factoryInc < MAX_FACTORY; factoryInc++)
{
//check to see if there is a factory
if ((factoryNumFlag[player][factoryType] & mask) == mask)
{
quantity++;
}
mask <<= 1;
}
return quantity;
}
// check whether a factory of a certain number and type exists
BOOL checkFactoryExists(UDWORD player, UDWORD factoryType, UDWORD inc)
{
ASSERT( player < MAX_PLAYERS,
"checkFactoryExists: invalid player" );
ASSERT( factoryType < NUM_FACTORY_TYPES,
"checkFactoryExists: invalid factoryType" );
return (factoryNumFlag[player][factoryType] & (1 << inc)) != 0;
}
//check that delivery points haven't been put down in invalid location
void checkDeliveryPoints(UDWORD version)
{
UBYTE inc;
STRUCTURE *psStruct;
FACTORY *psFactory;
REPAIR_FACILITY *psRepair;
UDWORD x, y;
//find any factories
for (inc = 0; inc < MAX_PLAYERS; inc++)
{
//don't bother checking selectedPlayer's - causes problems when try and use
//validLocation since it finds that the DP is on itself! And validLocation
//will have been called to put in down in the first place
if (inc != selectedPlayer)
{
for (psStruct = apsStructLists[inc]; psStruct != NULL; psStruct =
psStruct->psNext)
{
if(StructIsFactory(psStruct))
{
//check the DP
psFactory = &psStruct->pFunctionality->factory;
if (psFactory->psAssemblyPoint == NULL)//need to add one
{
ASSERT( psFactory->psAssemblyPoint != NULL,
"checkDeliveryPoints: no delivery point for factory" );
}
else
{
setAssemblyPoint(psFactory->psAssemblyPoint, psFactory->psAssemblyPoint->
coords.x, psFactory->psAssemblyPoint->coords.y, inc, true);
}
}
else if (psStruct->pStructureType->type == REF_REPAIR_FACILITY)
{
psRepair = &psStruct->pFunctionality->repairFacility;
if (psRepair->psDeliveryPoint == NULL)//need to add one
{
if (version >= VERSION_19)
{
ASSERT( psRepair->psDeliveryPoint != NULL,"checkDeliveryPoints: no delivery point for repair facility" );
}
else
{
// add an assembly point
if (!createFlagPosition(&psRepair->psDeliveryPoint, psStruct->player))
{
ASSERT(!"can't create new deilivery point for repair facility", "checkDeliveryPoints: unable to create new delivery point for repair facility");
return;
}
addFlagPosition(psRepair->psDeliveryPoint);
setFlagPositionInc(psStruct->pFunctionality, psStruct->player, REPAIR_FLAG);
//initialise the assembly point position
x = map_coord(psStruct->pos.x + 256);
y = map_coord(psStruct->pos.y + 256);
// Belt and braces - shouldn't be able to build too near edge
setAssemblyPoint( psRepair->psDeliveryPoint, world_coord(x),
world_coord(y), inc, true);
}
}
else//check existing one
{
setAssemblyPoint(psRepair->psDeliveryPoint, psRepair->psDeliveryPoint->
coords.x, psRepair->psDeliveryPoint->coords.y, inc, true);
}
}
}
}
}
}
//adjust the loop quantity for this factory
void factoryLoopAdjust(STRUCTURE *psStruct, BOOL add)
{
FACTORY *psFactory;
ASSERT( StructIsFactory(psStruct),
"factoryLoopAdjust: structure is not a factory" );
ASSERT( psStruct->player == selectedPlayer,
"factoryLoopAdjust: should only be called for selectedPlayer" );
psFactory = &psStruct->pFunctionality->factory;
if (add)
{
//check for wrapping to infinite production
if (psFactory->quantity == MAX_IN_RUN)
{
psFactory->quantity = 0;
}
else
{
//increment the count
psFactory->quantity++;
//check for limit - this caters for when on infinite production and want to wrap around
if (psFactory->quantity > MAX_IN_RUN)
{
psFactory->quantity = INFINITE_PRODUCTION;
}
}
}
else
{
//decrement the count
if (psFactory->quantity == 0)
{
psFactory->quantity = INFINITE_PRODUCTION;
}
else
{
psFactory->quantity--;
}
}
}
/*This function is called after a game is loaded so that any resource extractors
that are active are initialised for when to start*/
void checkResExtractorsActive(void)
{
STRUCTURE *psStruct;
UBYTE inc;
for (inc = 0; inc < MAX_PLAYERS; inc++)
{
for (psStruct = apsStructLists[inc]; psStruct != NULL; psStruct =
psStruct->psNext)
{
if (psStruct->pStructureType->type == REF_RESOURCE_EXTRACTOR)
{
if (psStruct->pFunctionality->resourceExtractor.active)
{
psStruct->pFunctionality->resourceExtractor.timeLastUpdated = gameTime;
}
}
}
}
}
/*Used for determining how much of the structure to draw as being built or demolished*/
float structHeightScale(STRUCTURE *psStruct)
{
float retVal = (float)psStruct->currentBuildPts / (float)psStruct->pStructureType->buildPoints;
if(retVal < 0.05f)
retVal = 0.05f;
return retVal;
}
/*compares the structure sensor type with the droid weapon type to see if the
FIRE_SUPPORT order can be assigned*/
BOOL structSensorDroidWeapon(STRUCTURE *psStruct, DROID *psDroid)
{
//Watermelon:another crash when nStat is marked as 0xcd... FIXME: Why is nStat not initialized properly?
//Added a safety check: Only units with weapons can be assigned.
if (psDroid->numWeaps > 0)
{
//Standard Sensor Tower + indirect weapon droid (non VTOL)
//else if (structStandardSensor(psStruct) && (psDroid->numWeaps &&
if (structStandardSensor(psStruct) && (psDroid->asWeaps[0].nStat > 0 &&
!proj_Direct(asWeaponStats + psDroid->asWeaps[0].nStat)) &&
!isVtolDroid(psDroid))
{
return true;
}
//CB Sensor Tower + indirect weapon droid (non VTOL)
//if (structCBSensor(psStruct) && (psDroid->numWeaps &&
else if (structCBSensor(psStruct) && (psDroid->asWeaps[0].nStat > 0 &&
!proj_Direct(asWeaponStats + psDroid->asWeaps[0].nStat)) &&
!isVtolDroid(psDroid))
{
return true;
}
//VTOL Intercept Sensor Tower + any weapon VTOL droid
//else if (structVTOLSensor(psStruct) && psDroid->numWeaps &&
else if (structVTOLSensor(psStruct) && psDroid->asWeaps[0].nStat > 0 &&
isVtolDroid(psDroid))
{
return true;
}
//VTOL CB Sensor Tower + any weapon VTOL droid
//else if (structVTOLCBSensor(psStruct) && psDroid->numWeaps &&
else if (structVTOLCBSensor(psStruct) && psDroid->asWeaps[0].nStat > 0 &&
isVtolDroid(psDroid))
{
return true;
}
}
//case not matched
return false;
}
/*checks if the structure has a Counter Battery sensor attached - returns
true if it has*/
BOOL structCBSensor(const STRUCTURE* psStruct)
{
// Super Sensor works as any type
if (psStruct->pStructureType->pSensor
&& (psStruct->pStructureType->pSensor->type == INDIRECT_CB_SENSOR
|| psStruct->pStructureType->pSensor->type == SUPER_SENSOR)
&& psStruct->pStructureType->pSensor->location == LOC_TURRET)
{
return true;
}
return false;
}
/*checks if the structure has a Standard Turret sensor attached - returns
true if it has*/
BOOL structStandardSensor(const STRUCTURE* psStruct)
{
// Super Sensor works as any type
if (psStruct->pStructureType->pSensor
&& (psStruct->pStructureType->pSensor->type == STANDARD_SENSOR
|| psStruct->pStructureType->pSensor->type == SUPER_SENSOR)
&& psStruct->pStructureType->pSensor->location == LOC_TURRET)
{
return true;
}
return false;
}
/*checks if the structure has a VTOL Intercept sensor attached - returns
true if it has*/
BOOL structVTOLSensor(const STRUCTURE* psStruct)
{
// Super Sensor works as any type
if (psStruct->pStructureType->pSensor
&& (psStruct->pStructureType->pSensor->type == VTOL_INTERCEPT_SENSOR
|| psStruct->pStructureType->pSensor->type == SUPER_SENSOR)
&& psStruct->pStructureType->pSensor->location == LOC_TURRET)
{
return true;
}
return false;
}
/*checks if the structure has a VTOL Counter Battery sensor attached - returns
true if it has*/
BOOL structVTOLCBSensor(const STRUCTURE* psStruct)
{
// Super Sensor works as any type
if (psStruct->pStructureType->pSensor
&& (psStruct->pStructureType->pSensor->type == VTOL_CB_SENSOR
|| psStruct->pStructureType->pSensor->type == SUPER_SENSOR)
&& psStruct->pStructureType->pSensor->location == LOC_TURRET)
{
return true;
}
return false;
}
// check whether a rearm pad is clear
BOOL clearRearmPad(STRUCTURE *psStruct)
{
if (psStruct->pStructureType->type == REF_REARM_PAD
&& (psStruct->pFunctionality->rearmPad.psObj == NULL
|| vtolHappy((DROID*)psStruct->pFunctionality->rearmPad.psObj)))
{
return true;
}
return false;
}
// return the nearest rearm pad
// if bClear is true it tries to find the nearest clear rearm pad in
// the same cluster as psTarget
// psTarget can be NULL
STRUCTURE * findNearestReArmPad(DROID *psDroid, STRUCTURE *psTarget, BOOL bClear)
{
STRUCTURE *psStruct, *psNearest, *psTotallyClear;
SDWORD xdiff,ydiff, mindist, currdist, totallyDist;
SDWORD cx,cy;
if (psTarget != NULL)
{
cx = (SDWORD)psTarget->pos.x;
cy = (SDWORD)psTarget->pos.y;
}
else
{
cx = (SDWORD)psDroid->pos.x;
cy = (SDWORD)psDroid->pos.y;
}
mindist = SDWORD_MAX;
totallyDist = SDWORD_MAX;
psNearest = NULL;
psTotallyClear = NULL;
for(psStruct = apsStructLists[psDroid->player]; psStruct; psStruct=psStruct->psNext)
{
if ((psStruct->pStructureType->type == REF_REARM_PAD) &&
(psTarget == NULL || psTarget->cluster == psStruct->cluster) &&
(!bClear || clearRearmPad(psStruct)))
{
xdiff = (SDWORD)psStruct->pos.x - cx;
ydiff = (SDWORD)psStruct->pos.y - cy;
currdist = xdiff*xdiff + ydiff*ydiff;
if (bClear && !vtolOnRearmPad(psStruct, psDroid))
{
if (currdist < totallyDist)
{
totallyDist = currdist;
psTotallyClear = psStruct;
}
}
else
{
if (currdist < mindist)
{
mindist = currdist;
psNearest = psStruct;
}
}
}
}
if (bClear && (psTotallyClear != NULL))
{
psNearest = psTotallyClear;
}
return psNearest;
}
// clear a rearm pad for a droid to land on it
void ensureRearmPadClear(STRUCTURE *psStruct, DROID *psDroid)
{
DROID *psCurr;
SDWORD tx,ty;
tx = map_coord(psStruct->pos.x);
ty = map_coord(psStruct->pos.y);
for(psCurr = apsDroidLists[psDroid->player]; psCurr; psCurr=psCurr->psNext)
{
if (psCurr != psDroid
&& map_coord(psCurr->pos.x) == tx
&& map_coord(psCurr->pos.y) == ty
&& isVtolDroid(psCurr))
{
actionDroidObj(psCurr, DACTION_CLEARREARMPAD, (BASE_OBJECT *)psStruct);
}
}
}
// return whether a rearm pad has a vtol on it
BOOL vtolOnRearmPad(STRUCTURE *psStruct, DROID *psDroid)
{
DROID *psCurr;
SDWORD tx,ty;
BOOL found;
tx = map_coord(psStruct->pos.x);
ty = map_coord(psStruct->pos.y);
found = false;
for(psCurr = apsDroidLists[psDroid->player]; psCurr; psCurr=psCurr->psNext)
{
if (psCurr != psDroid
&& map_coord(psCurr->pos.x) == tx
&& map_coord(psCurr->pos.y) == ty)
{
found = true;
break;
}
}
return found;
}
/* Just returns true if the structure's present body points aren't as high as the original*/
BOOL structIsDamaged(STRUCTURE *psStruct)
{
if(psStruct->body < structureBody(psStruct))
{
return(true);
}
else
{
return(false);
}
}
// give a structure from one player to another - used in Electronic Warfare
//returns pointer to the new structure
STRUCTURE * giftSingleStructure(STRUCTURE *psStructure, UBYTE attackPlayer, BOOL bFromScript)
{
STRUCTURE *psNewStruct, *psStruct;
DROID *psCurr;
STRUCTURE_STATS *psType, *psModule;
UDWORD x, y;
UBYTE capacity = 0, originalPlayer;
SWORD buildPoints = 0, i;
BOOL bPowerOn;
UWORD direction;
CHECK_STRUCTURE(psStructure);
//this is not the case for EW in multiPlayer mode
if (!bMultiPlayer)
{
//added 'selectedPlayer == 0' to be able to test the game by changing player...
//in this version of Warzone, the attack Player can NEVER be the selectedPlayer (unless from the script)
if (!bFromScript && selectedPlayer == 0 && attackPlayer == selectedPlayer)
{
ASSERT(bFromScript || selectedPlayer != 0 || attackPlayer != selectedPlayer, "giftSingleStructure: EW attack by selectedPlayer on a structure");
return NULL;
}
}
//don't want the hassle in multiplayer either
//and now we do! - AB 13/05/99
if (bMultiPlayer)
{
//certain structures give specific results - the rest swap sides!
if (!electronicReward(psStructure, attackPlayer))
{
originalPlayer = psStructure->player;
//tell the system the structure no longer exists
(void)removeStruct(psStructure, false);
// remove structure from one list
removeStructureFromList(psStructure, apsStructLists);
psStructure->selected = false;
// change player id
psStructure->player = (UBYTE)attackPlayer;
//restore the resistance value
psStructure->resistance = (UWORD)structureResistance(psStructure->
pStructureType, psStructure->player);
// add to other list.
addStructure(psStructure);
//check through the 'attackPlayer' players list of droids to see if any are targetting it
for (psCurr = apsDroidLists[attackPlayer]; psCurr != NULL; psCurr = psCurr->psNext)
{
if (psCurr->psTarget == (BASE_OBJECT *)psStructure)
{
orderDroid(psCurr, DORDER_STOP);
break;
}
for (i = 0;i < psCurr->numWeaps;i++)
{
if (psCurr->psActionTarget[i] == (BASE_OBJECT *)psStructure)
{
orderDroid(psCurr, DORDER_STOP);
break;
}
}
//check through order list
for (i = 0; i < psCurr->listSize; i++)
{
if (psCurr->asOrderList[i].psOrderTarget == (BASE_OBJECT *)psStructure)
{
removeDroidOrderTarget(psCurr, i);
// move the rest of the list down
memmove(&psCurr->asOrderList[i], &psCurr->asOrderList[i] + 1,
(psCurr->listSize - i) * sizeof(ORDER_LIST));
//adjust list size
psCurr->listSize -= 1;
//initialise the empty last slot
memset(psCurr->asOrderList + psCurr->listSize, 0,
sizeof(ORDER_LIST));
}
}
}
//check through the 'attackPlayer' players list of structures to see if any are targetting it
for (psStruct = apsStructLists[attackPlayer]; psStruct != NULL; psStruct =
psStruct->psNext)
{
if (psStruct->psTarget[0] == (BASE_OBJECT *)psStructure)
{
setStructureTarget(psStruct, NULL, 0);
}
}
//add back into cluster system
clustNewStruct(psStructure);
//add back into the grid system
gridAddObject((BASE_OBJECT *)psStructure);
if (psStructure->status == SS_BUILT)
{
buildingComplete(psStructure);
}
//since the structure isn't being rebuilt, the visibility code needs to be adjusted
if (attackPlayer == selectedPlayer)
{
//make sure this structure is visible to selectedPlayer
psStructure->visible[selectedPlayer] = UBYTE_MAX;
}
}
//ASSERT( false,
// "giftSingleStructure: EW attack in multiplayer" );
return NULL;
}
//save info about the structure
psType = psStructure->pStructureType;
x = psStructure->pos.x;
y = psStructure->pos.y;
direction = psStructure->direction;
originalPlayer = psStructure->player;
//save how complete the build process is
if (psStructure->status == SS_BEING_BUILT)
{
buildPoints = psStructure->currentBuildPts;
}
//check module not attached
psModule = getModuleStat(psStructure);
if (psModule)
{
switch(psType->type)
{
case REF_POWER_GEN:
capacity = (UBYTE)psStructure->pFunctionality->powerGenerator.capacity;
break;
case REF_RESEARCH:
capacity = (UBYTE)psStructure->pFunctionality->researchFacility.capacity;
break;
case REF_FACTORY:
case REF_VTOL_FACTORY:
capacity = psStructure->pFunctionality->factory.capacity;
break;
default:
break;
}
}
//get rid of the structure
(void)removeStruct(psStructure, true);
//make sure power is not used to build
bPowerOn = powerCalculated;
powerCalculated = false;
//build a new one for the attacking player - set last element to true so it doesn't adjust x/y
psNewStruct = buildStructure(psType, x, y, attackPlayer, true);
if (psNewStruct)
{
psNewStruct->direction = direction;
if (capacity)
{
switch(psType->type)
{
case REF_POWER_GEN:
case REF_RESEARCH:
//build the module for powerGen and research
buildStructure(psModule, psNewStruct->pos.x, psNewStruct->pos.y,
attackPlayer, false);
break;
case REF_FACTORY:
case REF_VTOL_FACTORY:
//build the appropriate number of modules
while (capacity)
{
buildStructure(psModule, psNewStruct->pos.x, psNewStruct->pos.y,
attackPlayer, false);
capacity--;
}
break;
default:
break;
}
}
if (buildPoints)
{
psNewStruct->status = SS_BEING_BUILT;
psNewStruct->currentBuildPts = buildPoints;
}
else
{
psNewStruct->status = SS_BUILT;
buildingComplete(psNewStruct);
}
if (!bMultiPlayer)
{
//inform selectedPlayer that takeover has happened
if (originalPlayer == selectedPlayer)
{
if (wallDefenceStruct(psNewStruct->pStructureType))
{
audio_QueueTrackPos( ID_SOUND_NEXUS_DEFENCES_ABSORBED,
psNewStruct->pos.x, psNewStruct->pos.y, psNewStruct->pos.z );
}
else
{
audio_QueueTrackPos( ID_SOUND_NEXUS_STRUCTURE_ABSORBED,
psNewStruct->pos.x, psNewStruct->pos.y, psNewStruct->pos.z );
}
//make sure this structure is visible to selectedPlayer if the structure used to be selectedPlayers'
psNewStruct->visible[selectedPlayer] = UBYTE_MAX;
}
}
}
powerCalculated = bPowerOn;
return psNewStruct;
}
/* Code to have the structure's weapon assembly rock back upon firing */
void structUpdateRecoil( STRUCTURE *psStruct )
{
UDWORD percent;
UDWORD recoil;
float fraction;
/* Check it's actually got a weapon */
if(psStruct->asWeaps[0].nStat == 0)
{
return;
}
/* We have a weapon */
if(gameTime > (psStruct->asWeaps[0].lastFired + STR_RECOIL_TIME) )
{
/* Recoil effect is over */
psStruct->asWeaps[0].recoilValue = 0;
return;
}
/* Where should the assembly be? */
percent = PERCENT((gameTime-psStruct->asWeaps[0].lastFired),STR_RECOIL_TIME);
/* Outward journey */
if(percent >= 50)
{
recoil = (100 - percent)/5;
}
/* Return journey */
else
{
recoil = percent/5;
}
fraction = (float)asWeaponStats[psStruct->asWeaps[0].nStat].recoilValue / 100.f;
recoil = (float)recoil * fraction;
/* Put it into the weapon data */
psStruct->asWeaps[0].recoilValue = recoil;
}
/*checks that the structure stats have loaded up as expected - must be done after
all StructureStats parts have been loaded*/
BOOL checkStructureStats(void)
{
UDWORD structInc, inc;
for (structInc=0; structInc < numStructureStats; structInc++)
{
if (asStructureStats[structInc].numFuncs != 0)
{
for (inc = 0; inc < asStructureStats[structInc].numFuncs; inc++)
{
ASSERT( asStructureStats[structInc].asFuncList[inc] != NULL,
"checkStructureStats: \
Invalid function for structure %s",
asStructureStats[structInc].pName );
}
}
else
{
if (asStructureStats[structInc].asFuncList != NULL)
{
ASSERT( false, "checkStructureStats:Invalid functions attached to structure %s",
asStructureStats[structInc].pName );
return false;
}
}
}
return true;
}
/*returns the power cost to build this structure*/
UDWORD structPowerToBuild(STRUCTURE *psStruct)
{
STRUCTURE_STATS *psStat;
UBYTE capacity;
//see if this structure can have a module attached
psStat = getModuleStat(psStruct);
if (psStat)
{
capacity = 0;
//are we building the module or the base structure?
switch(psStruct->pStructureType->type)
{
case REF_POWER_GEN:
capacity = (UBYTE)psStruct->pFunctionality->powerGenerator.capacity;
break;
case REF_RESEARCH:
capacity = (UBYTE)psStruct->pFunctionality->researchFacility.capacity;
break;
case REF_FACTORY:
case REF_VTOL_FACTORY:
capacity = psStruct->pFunctionality->factory.capacity;
break;
default:
break;
}
if (capacity)
{
//return the cost to build the module
return psStat->powerToBuild;
}
else
{
//no module attached so building the base structure
return psStruct->pStructureType->powerToBuild;
}
}
else
{
//not a module structure, so power cost is the one associated with the stat
return psStruct->pStructureType->powerToBuild;
}
}
//for MULTIPLAYER ONLY
//this adjusts the time the relevant action started if the building is attacked by EW weapon
void resetResistanceLag(STRUCTURE *psBuilding)
{
if (bMultiPlayer)
{
switch (psBuilding->pStructureType->type)
{
case REF_RESEARCH:
{
RESEARCH_FACILITY *psResFacility = &psBuilding->pFunctionality->researchFacility;
//if working on a topic
if (psResFacility->psSubject)
{
//adjust the start time for the current subject
if (psResFacility->timeStarted != ACTION_START_TIME)
{
psResFacility->timeStarted += (gameTime - psBuilding->lastResistance);
}
}
}
case REF_FACTORY:
case REF_VTOL_FACTORY:
case REF_CYBORG_FACTORY:
{
FACTORY *psFactory = &psBuilding->pFunctionality->factory;
//if working on a unit
if (psFactory->psSubject)
{
//adjust the start time for the current subject
if (psFactory->timeStarted != ACTION_START_TIME)
{
psFactory->timeStarted += (gameTime - psBuilding->lastResistance);
}
}
}
default: //do nothing
break;
}
}
}
/*reveals all the terrain in the map*/
void revealAll(UBYTE player)
{
UWORD i, j;
MAPTILE *psTile;
//reveal all tiles
for(i=0; i<mapWidth; i++)
{
for(j=0; j<mapHeight; j++)
{
psTile = mapTile(i,j);
SET_TILE_VISIBLE(player, psTile);
avInformOfChange(i,j);
}
}
//the objects gets revealed in processVisibility()
}
/*checks the structure passed in is a Las Sat structure which is currently
selected - returns true if valid*/
BOOL lasSatStructSelected(STRUCTURE *psStruct)
{
if ( (psStruct->selected || (bMultiPlayer && !isHumanPlayer(psStruct->player)))
&& psStruct->asWeaps[0].nStat
&& (asWeaponStats[psStruct->asWeaps[0].nStat].weaponSubClass == WSC_LAS_SAT))
{
return true;
}
return false;
}
/* Call CALL_NEWDROID script callback */
static void cbNewDroid(STRUCTURE *psFactory, DROID *psDroid)
{
ASSERT(psDroid != NULL,
"cbNewDroid: no droid assigned for CALL_NEWDROID callback");
psScrCBNewDroid = psDroid;
psScrCBNewDroidFact = psFactory;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_NEWDROID);
psScrCBNewDroid = NULL;
psScrCBNewDroidFact = NULL;
}
// Check that psVictimStruct is not referred to by any other object in the game
BOOL structureCheckReferences(STRUCTURE *psVictimStruct)
{
int plr, i;
for (plr = 0; plr < MAX_PLAYERS; plr++)
{
STRUCTURE *psStruct;
DROID *psDroid;
for (psStruct = apsStructLists[plr]; psStruct != NULL; psStruct = psStruct->psNext)
{
for (i = 0; i < psStruct->numWeaps; i++)
{
if ((STRUCTURE *)psStruct->psTarget[i] == psVictimStruct && psVictimStruct != psStruct)
{
#ifdef DEBUG
ASSERT(!"Illegal reference to structure", "Illegal reference to structure from %s line %d",
psStruct->targetFunc[i], psStruct->targetLine[i]);
#endif
return false;
}
}
}
for (psDroid = apsDroidLists[plr]; psDroid != NULL; psDroid = psDroid->psNext)
{
if ((STRUCTURE *)psDroid->psTarget == psVictimStruct)
{
#ifdef DEBUG
ASSERT(!"Illegal reference to structure", "Illegal reference to structure from %s line %d",
psDroid->targetFunc, psDroid->targetLine);
#endif
return false;
}
for (i = 0; i < psDroid->numWeaps; i++)
{
if ((STRUCTURE *)psDroid->psActionTarget[i] == psVictimStruct)
{
#ifdef DEBUG
ASSERT(!"Illegal reference to structure", "Illegal action reference to structure from %s line %d",
psDroid->actionTargetFunc[i], psDroid->actionTargetLine[i]);
#endif
return false;
}
}
if ((STRUCTURE *)psDroid->psBaseStruct == psVictimStruct)
{
#ifdef DEBUG
ASSERT(!"Illegal reference to structure", "Illegal action reference to structure from %s line %d",
psDroid->baseFunc, psDroid->baseLine);
#endif
return false;
}
}
}
return true;
}
void checkStructure(const STRUCTURE* psStructure, const char * const location_description, const char * function, const int recurse)
{
unsigned int i;
if (recurse < 0)
return;
ASSERT(psStructure != NULL, location_description, function, "CHECK_STRUCTURE: NULL pointer");
ASSERT(psStructure->type == OBJ_STRUCTURE, location_description, function, "CHECK_STRUCTURE: No structure (type num %u)", function, (unsigned int)psStructure->type);
ASSERT(psStructure->player < MAX_PLAYERS, location_description, function, "CHECK_STRUCTURE: Out of bound player num (%u)", (unsigned int)psStructure->player);
ASSERT(psStructure->pStructureType->type < NUM_DIFF_BUILDINGS, location_description, function, "CHECK_STRUCTURE: Out of bound structure type (%u)", (unsigned int)psStructure->pStructureType->type);
ASSERT(psStructure->numWeaps <= STRUCT_MAXWEAPS, location_description, function, "CHECK_STRUCTURE: Out of bound weapon count (%u)", (unsigned int)psStructure->numWeaps);
for (i = 0; i < ARRAY_SIZE(psStructure->turretRotation); ++i)
{
ASSERT(psStructure->turretRotation[i] <= 360, location_description, function, "CHECK_STRUCTURE: Out of range turret rotation (turret %u; rotation: %u)", i, (unsigned int)psStructure->turretRotation[i]);
if (psStructure->psTarget[i])
{
checkObject(psStructure->psTarget[i], location_description, function, recurse - 1);
}
}
}