Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

7595 lines (6681 sloc) 225.301 kb
/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2012 Warzone 2100 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 <algorithm>
#include "lib/framework/frame.h"
#include "lib/framework/geometry.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_ext.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/framework/fixedpoint.h"
#include "order.h"
#include "droid.h"
#include "lib/script/script.h"
#include "scripttabs.h"
#include "scriptcb.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 "qtscript.h"
#include "advvis.h"
#include "multiplay.h"
#include "lib/netplay/netplay.h"
#include "multigifts.h"
#include "loop.h"
#include "template.h"
#include "scores.h"
#include "gateway.h"
#include "random.h"
#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
/* 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
//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
static std::vector<bool> 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
std::vector<ProductionRun> asProductionRun[NUM_FACTORY_TYPES];
//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 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 resetResistanceLag(STRUCTURE *psBuilding);
static int structureTotalReturn(STRUCTURE *psStruct);
// last time the maximum units message was displayed
static UDWORD lastMaxUnitMessage;
#define MAX_UNIT_MESSAGE_PAUSE 20000
/*
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
*/
static void auxStructureNonblocking(STRUCTURE *psStructure)
{
StructureBounds b = getStructureBounds(psStructure);
for (int i = 0; i < b.size.x; i++)
{
for (int j = 0; j < b.size.y; j++)
{
auxClearAll(b.map.x + i, b.map.y + j, AUXBITS_BLOCKING | AUXBITS_OUR_BUILDING | AUXBITS_NONPASSABLE);
}
}
}
static void auxStructureBlocking(STRUCTURE *psStructure)
{
StructureBounds b = getStructureBounds(psStructure);
for (int i = 0; i < b.size.x; i++)
{
for (int j = 0; j < b.size.y; j++)
{
auxSetAllied(b.map.x + i, b.map.y + j, psStructure->player, AUXBITS_OUR_BUILDING);
auxSetAll(b.map.x + i, b.map.y + j, AUXBITS_BLOCKING | AUXBITS_NONPASSABLE);
}
}
}
static void auxStructureOpenGate(STRUCTURE *psStructure)
{
StructureBounds b = getStructureBounds(psStructure);
for (int i = 0; i < b.size.x; i++)
{
for (int j = 0; j < b.size.y; j++)
{
auxClearAll(b.map.x + i, b.map.y + j, AUXBITS_BLOCKING);
}
}
}
static void auxStructureClosedGate(STRUCTURE *psStructure)
{
StructureBounds b = getStructureBounds(psStructure);
for (int i = 0; i < b.size.x; i++)
{
for (int j = 0; j < b.size.y; j++)
{
auxSetEnemy(b.map.x + i, b.map.y + j, psStructure->player, AUXBITS_NONPASSABLE);
auxSetAll(b.map.x + i, b.map.y + j, AUXBITS_BLOCKING);
}
}
}
bool IsStatExpansionModule(STRUCTURE_STATS const *psStats)
{
// If the stat is any of the 3 expansion types ... then return true
return psStats->type == REF_POWER_MODULE ||
psStats->type == REF_FACTORY_MODULE ||
psStats->type == REF_RESEARCH_MODULE;
}
static int numStructureModules(STRUCTURE const *psStruct)
{
switch (psStruct->pStructureType->type)
{
case REF_POWER_GEN: return psStruct->pFunctionality->powerGenerator.capacity;
case REF_VTOL_FACTORY:
case REF_FACTORY: return psStruct->pFunctionality->factory.capacity;
case REF_RESEARCH: return psStruct->pFunctionality->researchFacility.capacity;
default: return 0;
}
}
bool structureIsBlueprint(STRUCTURE *psStructure)
{
return (psStructure->status == SS_BLUEPRINT_VALID ||
psStructure->status == SS_BLUEPRINT_INVALID ||
psStructure->status == SS_BLUEPRINT_PLANNED ||
psStructure->status == SS_BLUEPRINT_PLANNED_BY_ALLY);
}
bool isBlueprint(BASE_OBJECT *psObject)
{
return psObject->type == OBJ_STRUCTURE && structureIsBlueprint((STRUCTURE *)psObject);
}
void structureInitVars(void)
{
int i, j;
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].clear();
}
}
for (i = 0; i < MAX_PLAYERS; i++)
{
satUplinkExists[i] = false;
lasSatExists[i] = false;
}
//initialise the selectedPlayer's production run
for (unsigned type = 0; type < NUM_FACTORY_TYPES; ++type)
{
asProductionRun[type].clear();
}
//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
for (unsigned type = 0; type < NUM_FACTORY_TYPES; ++type)
{
asProductionRun[type].clear();
}
//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].clear();
}
}
}
//called at start of missions
void resetFactoryNumFlag(void)
{
for(unsigned int i = 0; i < MAX_PLAYERS; i++)
{
for (int type = 0; type < NUM_FLAG_TYPES; type++)
{
if (factoryNumFlag[i][type].size())
{
// reset them all to false
for (int k = 0; k < MAX_FACTORY; k++)
{
factoryNumFlag[i][type][k] = false;
}
}
}
//look through the list of structures to see which have been used
for (STRUCTURE *psStruct = apsStructLists[i]; psStruct != NULL; psStruct = psStruct->psNext)
{
FLAG_TYPE type;
switch (psStruct->pStructureType->type)
{
case REF_FACTORY: type = FACTORY_FLAG; break;
case REF_CYBORG_FACTORY: type = CYBORG_FLAG; break;
case REF_VTOL_FACTORY: type = VTOL_FLAG; break;
case REF_REPAIR_FACILITY: type = REPAIR_FLAG; break;
default: continue;
}
if (type == REPAIR_FLAG)
{
REPAIR_FACILITY *psRepair = &psStruct->pFunctionality->repairFacility;
if (psRepair->psDeliveryPoint != NULL)
{
factoryNumFlag[i][type][psRepair->psDeliveryPoint->factoryInc] = true;
}
}
else
{
FACTORY *psFactory = &psStruct->pFunctionality->factory;
if (psFactory->psAssemblyPoint != NULL)
{
factoryNumFlag[i][type][psFactory->psAssemblyPoint->factoryInc] = true;
}
}
}
}
}
static const StringToEnum<STRUCTURE_TYPE> map_STRUCTURE_TYPE[] =
{
{ "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 },
{ "GATE", REF_GATE },
};
static const StringToEnum<STRUCT_STRENGTH> map_STRUCT_STRENGTH[] =
{
{"SOFT", STRENGTH_SOFT },
{"MEDIUM", STRENGTH_MEDIUM },
{"HARD", STRENGTH_HARD },
{"BUNKER", STRENGTH_BUNKER },
};
static void initModuleStats(unsigned i, STRUCTURE_TYPE type)
{
//need to work out the stats for the modules - HACK! - But less hacky than what was here before.
switch (type)
{
case REF_FACTORY_MODULE:
//store the stat for easy access later on
factoryModuleStat = i;
break;
case REF_RESEARCH_MODULE:
//store the stat for easy access later on
researchModuleStat = i;
break;
case REF_POWER_MODULE:
//store the stat for easy access later on
powerModuleStat = i;
break;
default:
break;
}
}
STRUCTURE_STATS::STRUCTURE_STATS(LineView line)
: BASE_STATS(REF_STRUCTURE_START + line.line(), line.s(0))
, type(line.e(1, map_STRUCTURE_TYPE))
, strength(line.e(3, map_STRUCT_STRENGTH))
, baseWidth(line.i(5, 0, 100))
, baseBreadth(line.i(6, 0, 100))
, buildPoints(line.u32(8))
, height(line.u32(9))
, armourValue(line.u32(10))
, bodyPoints(line.u32(11))
, powerToBuild(line.u32(13))
, resistance(line.u32(15))
, pIMD(line.imdShapes(21))
, pBaseIMD(line.imdShape(22, true))
, pECM(line.stats(18, asECMStats, numECMStats, true))
, pSensor(line.stats(19, asSensorStats, numSensorStats, true))
, weaponSlots(line.i(20, 0, STRUCT_MAXWEAPS)) // Is this one used anywhere?
, numWeaps(line.i(24, 0, weaponSlots))
//, psWeapStat
, defaultFunc(-1)
, asFuncList(line.i(23, 0, 1000), (FUNCTION *)NULL)
// Ignored columns: 2, 4, 7, 12, 14, 16, 17
{
int types = 0;
types += numWeaps != 0;
types += pECM != NULL && pECM->location == LOC_TURRET;
types += pSensor != NULL && pSensor->location == LOC_TURRET;
if (types > 1)
{
line.setError(-1, "Too many turret types.");
}
std::fill_n(psWeapStat, STRUCT_MAXWEAPS, (WEAPON_STATS *)NULL);
numWeaps = std::min<unsigned>(numWeaps, STRUCT_MAXWEAPS);
}
/* load the Structure stats from the Access database */
bool loadStructureStats(const char *pStructData, UDWORD bufferSize)
{
UDWORD iID;
// Skip descriptive header
if (strncmp(pStructData,"Structure ",10)==0)
{
pStructData = strchr(pStructData,'\n') + 1;
}
TableView table(pStructData, bufferSize);
asStructureStats = new STRUCTURE_STATS[table.size()];
numStructureStats = table.size();
for (unsigned i = 0; i < table.size(); ++i)
{
LineView line(table, i);
asStructureStats[i] = STRUCTURE_STATS(line);
if (table.isError())
{
debug(LOG_ERROR, "%s", table.getError().toUtf8().constData());
return false;
}
initModuleStats(i, asStructureStats[i].type); // This function looks like a hack. But slightly less hacky than before.
}
/* 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");
return false;
}
g_psStatDestroyStruct = asStructureStats + iID;
//allocate the structureLimits structure
for (unsigned player = 0; player < MAX_PLAYERS; ++player)
{
asStructLimits[player] = (STRUCTURE_LIMITS *)malloc(sizeof(STRUCTURE_LIMITS) * numStructureStats);
if (asStructLimits[player] == NULL)
{
debug( LOG_FATAL, "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
ASSERT(psStructLimits[inc].currentQuantity <= psStructLimits[inc].limit,
"There appears to be too many %s on this map!", asStructureStats[inc].pName);
}
}
}
}
//Load the weapons assigned to Structure in the Access database
bool loadStructureWeapons(const char *pWeaponData, UDWORD bufferSize)
{
TableView table(pWeaponData, bufferSize);
for (unsigned i = 0; i < table.size(); ++i)
{
LineView line(table, i);
STRUCTURE_STATS *structureStats = line.stats(0, asStructureStats, numStructureStats);
for (unsigned j = 0; !table.isError() && j < structureStats->numWeaps; ++j)
{
structureStats->psWeapStat[j] = line.stats(1 + j, asWeaponStats, numWeaponStats);
}
if (table.isError())
{
debug(LOG_ERROR, "%s", table.getError().toUtf8().constData());
return false;
}
}
return true;
}
//Load the programs assigned to Droids in the Access database
bool loadStructureFunctions(const char *pFunctionData, UDWORD bufferSize)
{
TableView table(pFunctionData, bufferSize);
for (unsigned i = 0; i < table.size(); ++i)
{
LineView line(table, i);
STRUCTURE_STATS *structureStats = line.stats(0, asStructureStats, numStructureStats);
FUNCTION *function = line.stats(1, asFunctions, numFunctions);
if (table.isError())
{
debug(LOG_ERROR, "%s", table.getError().toUtf8().constData());
return false;
}
++structureStats->defaultFunc;
if (structureStats->defaultFunc > (int)structureStats->asFuncList.size())
{
debug(LOG_ERROR, "Trying to allocate more functions than allowed for Structure");
return false;
}
structureStats->asFuncList[structureStats->defaultFunc] = function;
}
/**************************************************************************/
//Wall Function requires a structure stat so can allocate it now
for (unsigned i = 0; i < numFunctions; ++i)
{
if (asFunctions[i]->type != WALL_TYPE)
{
continue;
}
WALL_FUNCTION *wallFunction = (WALL_FUNCTION *)asFunctions[i];
wallFunction->pCornerStat = findStatsByName(wallFunction->pStructName, asStructureStats, numStructureStats);
//if haven't found the STRUCTURE STAT, then problem
if (wallFunction->pCornerStat == NULL)
{
debug(LOG_ERROR, "Unknown Corner Wall stat for function %s", wallFunction->pName);
return false;
}
}
return true;
}
/*Load the Structure Strength Modifiers from the file exported from Access*/
bool loadStructureStrengthModifiers(const char *pStrengthModData, UDWORD bufferSize)
{
//initialise to 100%
for (unsigned i = 0; i < WE_NUMEFFECTS; ++i)
{
for (unsigned j = 0; j < NUM_STRUCT_STRENGTH; ++j)
{
asStructStrengthModifier[i][j] = 100;
}
}
TableView table(pStrengthModData, bufferSize);
for (unsigned i = 0; i < table.size(); ++i)
{
LineView line(table, i);
asStructStrengthModifier[line.e(0, map_WEAPON_EFFECT)][line.e(1, map_STRUCT_STRENGTH)] = line.u16(2);
if (table.isError())
{
debug(LOG_ERROR, "%s", table.getError().toUtf8().constData());
return false;
}
}
return true;
}
bool structureStatsShutDown(void)
{
UDWORD inc;
delete[] asStructureStats;
asStructureStats = NULL;
numStructureStats = 0;
//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()
{
// TODO: do something here
}
/* 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
*/
int32_t structureDamage(STRUCTURE *psStructure, unsigned damage, WEAPON_CLASS weaponClass, WEAPON_SUBCLASS weaponSubClass, unsigned impactTime, bool isDamagePerSecond)
{
int32_t relativeDamage;
CHECK_STRUCTURE(psStructure);
debug(LOG_ATTACK, "structure id %d, body %d, armour %d, damage: %d",
psStructure->id, psStructure->body, psStructure->armour[weaponClass], damage);
relativeDamage = objDamage(psStructure, damage, structureBody(psStructure), weaponClass, weaponSubClass, isDamagePerSecond);
// If the shell did sufficient damage to destroy the structure
if (relativeDamage < 0)
{
debug(LOG_ATTACK, "Structure (id %d) DESTROYED", psStructure->id);
destroyStruct(psStructure, impactTime);
}
else
{
// Survived
CHECK_STRUCTURE(psStructure);
}
return relativeDamage;
}
int32_t getStructureDamage(const STRUCTURE *psStructure)
{
CHECK_STRUCTURE(psStructure);
unsigned maxBody = structureBodyBuilt(psStructure);
int64_t health = (int64_t)65536 * psStructure->body / maxBody;
CLIP(health, 0, 65536);
return 65536 - health;
}
/// Add buildPoints to the structures currentBuildPts, due to construction work by the droid
/// Also can deconstruct (demolish) a building if passed negative buildpoints
void structureBuild(STRUCTURE *psStruct, DROID *psDroid, int buildPoints, int buildRate)
{
bool checkResearchButton = psStruct->status == SS_BUILT; // We probably just started demolishing, if this is true.
int prevResearchState = 0;
if (checkResearchButton)
{
prevResearchState = intGetResearchState();
}
if (psDroid && !aiCheckAlliances(psStruct->player,psDroid->player))
{
// Enemy structure
return;
}
else if (psStruct->pStructureType->type != REF_FACTORY_MODULE)
{
for (unsigned player = 0; player < MAX_PLAYERS; player++)
{
for (DROID *psCurr = apsDroidLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
{
// An enemy droid is blocking it
if ((STRUCTURE *) orderStateObj(psCurr, DORDER_BUILD) == psStruct
&& !aiCheckAlliances(psStruct->player,psCurr->player))
{
return;
}
}
}
}
psStruct->buildRate += buildRate; // buildRate = buildPoints/GAME_UPDATES_PER_SEC, but might be rounded up or down each tick, so can't use buildPoints to get a stable number.
if (psStruct->currentBuildPts <= 0 && buildPoints > 0)
{
// Just starting to build structure, need power for it.
bool haveEnoughPower = requestPowerFor(psStruct, structPowerToBuild(psStruct));
if (!haveEnoughPower)
{
buildPoints = 0; // No power to build.
}
}
int newBuildPoints = psStruct->currentBuildPts + buildPoints;
ASSERT(newBuildPoints <= 1 + 3 * (int)psStruct->pStructureType->buildPoints, "unsigned int underflow?");
CLIP(newBuildPoints, 0, psStruct->pStructureType->buildPoints);
if (psStruct->currentBuildPts > 0 && newBuildPoints <= 0)
{
// Demolished structure, return some power.
addPower(psStruct->player, structureTotalReturn(psStruct));
}
ASSERT(newBuildPoints <= 1 + 3 * (int)psStruct->pStructureType->buildPoints, "unsigned int underflow?");
CLIP(newBuildPoints, 0, psStruct->pStructureType->buildPoints);
int deltaBody = quantiseFraction(9 * structureBody(psStruct), 10 * psStruct->pStructureType->buildPoints, newBuildPoints, psStruct->currentBuildPts);
psStruct->currentBuildPts = newBuildPoints;
psStruct->body = std::max<int>(psStruct->body + deltaBody, 1);
//check if structure is built
if (buildPoints > 0 && psStruct->currentBuildPts >= (SDWORD)psStruct->pStructureType->buildPoints)
{
buildingComplete(psStruct);
if (psDroid)
{
intBuildFinished(psDroid);
}
//only play the sound if selected player
if (psDroid &&
psStruct->player == selectedPlayer
&& (psDroid->order.type != DORDER_LINEBUILD
|| map_coord(psDroid->order.pos) == map_coord(psDroid->order.pos2)))
{
audio_QueueTrackPos( ID_SOUND_STRUCTURE_COMPLETED,
psStruct->pos.x, psStruct->pos.y, psStruct->pos.z );
intRefreshScreen(); // update any open interface bars.
}
/* must reset here before the callback, droid must have DACTION_NONE
in order to be able to start a new built task, doubled in actionUpdateDroid() */
if (psDroid)
{
DROID *psIter;
// Clear all orders for helping hands. Needed for AI script which runs next frame.
for (psIter = apsDroidLists[psDroid->player]; psIter; psIter = psIter->psNext)
{
if ((psIter->order.type == DORDER_BUILD || psIter->order.type == DORDER_HELPBUILD || psIter->order.type == DORDER_LINEBUILD)
&& psIter->order.psObj == psStruct
&& (psIter->order.type != DORDER_LINEBUILD || map_coord(psIter->order.pos) == map_coord(psIter->order.pos2)))
{
objTrace(psIter->id, "Construction order %s complete (%d, %d -> %d, %d)", getDroidOrderName(psDroid->order.type),
psIter->order.pos2.x, psIter->order.pos.y, psIter->order.pos2.x, psIter->order.pos2.y);
psIter->action = DACTION_NONE;
psIter->order = DroidOrder(DORDER_NONE);
setDroidActionTarget(psIter, NULL, 0);
}
}
/* Notify scripts we just finished building a structure, pass builder and what was built */
psScrCBNewStruct = psStruct;
psScrCBNewStructTruck = psDroid;
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_STRUCTBUILT);
audio_StopObjTrack( psDroid, ID_SOUND_CONSTRUCTION_LOOP );
}
triggerEventStructBuilt(psStruct, psDroid);
/* Not needed, but left for backward compatibility */
structureCompletedCallback(psStruct->pStructureType);
}
else
{
psStruct->status = SS_BEING_BUILT;
}
if (buildPoints < 0 && psStruct->currentBuildPts == 0)
{
removeStruct(psStruct, true);
}
if (checkResearchButton)
{
intNotifyResearchButton(prevResearchState);
}
}
static bool structureHasModules(STRUCTURE *psStruct)
{
if (psStruct->pStructureType->type == REF_POWER_GEN)
{
return psStruct->pFunctionality->powerGenerator.capacity != 0;
}
if (StructIsFactory(psStruct))
{
return psStruct->pFunctionality->factory.capacity != 0;
}
if (psStruct->pStructureType->type == REF_RESEARCH)
{
return psStruct->pFunctionality->researchFacility.capacity != 0;
}
return false;
}
static int structureTotalReturn(STRUCTURE *psStruct)
{
int result = structPowerToBuild(psStruct)/2;
if(psStruct->pStructureType->type == REF_POWER_GEN)
{
//if had module attached - the base must have been completely built
if (psStruct->pFunctionality->powerGenerator.capacity)
{
//so add the power required to build the base struct
result += psStruct->pStructureType->powerToBuild/2;
}
}
else
{
//if it had a module attached, need to add the power for the base struct as well
if (StructIsFactory(psStruct))
{
if (psStruct->pFunctionality->factory.capacity)
{
//if large factory - add half power for one upgrade
if (psStruct->pFunctionality->factory.capacity > SIZE_MEDIUM)
{
result += structPowerToBuild(psStruct) / 2;
}
}
}
else if (psStruct->pStructureType->type == REF_RESEARCH)
{
if (psStruct->pFunctionality->researchFacility.capacity)
{
//add half power for base struct
result += psStruct->pStructureType->powerToBuild/2;
}
}
}
return result;
}
void structureDemolish(STRUCTURE *psStruct, DROID *psDroid, int buildPoints)
{
structureBuild(psStruct, psDroid, -buildPoints);
}
bool structureRepair(STRUCTURE *psStruct, DROID *psDroid, int buildPoints)
{
int repairAmount = (buildPoints * structureBody(psStruct))/psStruct->pStructureType->buildPoints;
/* (droid construction power * current max hitpoints [incl. upgrades])
/ construction power that was necessary to build structure in the first place
=> to repair a building from 1HP to full health takes as much time as building it.
=> if buildPoints = 1 and structureBody < buildPoints, repairAmount might get truncated to zero.
This happens with expensive, but weak buildings like mortar pits. In this case, do nothing
and notify the caller (read: droid) of your idleness by returning false.
*/
if (repairAmount != 0) // didn't get truncated to zero
{
psStruct->body += repairAmount;
psStruct->body = MIN(psStruct->body, structureBody(psStruct));
if (psStruct->body == 0)
{
removeStruct(psStruct, true);
}
return true;
}
else
{
// got truncated to zero; wait until droid has accumulated enough buildpoints
return false;
}
}
/* Set the type of droid for a factory to build */
bool structSetManufacture(STRUCTURE *psStruct, DROID_TEMPLATE *psTempl, QUEUE_MODE mode)
{
FACTORY *psFact;
CHECK_STRUCTURE(psStruct);
ASSERT_OR_RETURN(false, psStruct != NULL && psStruct->type == OBJ_STRUCTURE, "Invalid factory pointer");
ASSERT_OR_RETURN(false, psStruct->pStructureType->type == REF_FACTORY || psStruct->pStructureType->type == REF_CYBORG_FACTORY
|| psStruct->pStructureType->type == REF_VTOL_FACTORY, "Invalid structure type %d for factory",
(int)psStruct->pStructureType->type);
/* psTempl might be NULL if the build is being cancelled in the middle */
ASSERT_OR_RETURN(false, (validTemplateForFactory(psTempl, psStruct) && researchedTemplate(psTempl, psStruct->player, true)) || psStruct->player == scavengerPlayer() || !bMultiPlayer, "Wrong template for player %d factory, type %d.", psStruct->player, psStruct->pStructureType->type);
psFact = &psStruct->pFunctionality->factory;
if (mode == ModeQueue)
{
sendStructureInfo(psStruct, STRUCTUREINFO_MANUFACTURE, psTempl);
setStatusPendingStart(*psFact, psTempl);
return true; // Wait for our message before doing anything.
}
//assign it to the Factory
psFact->psSubject = 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->productionLoops = 1;
}
psFact->timeStarted = ACTION_START_TIME;//gameTime;
psFact->timeStartHold = 0;
psFact->buildPointsRemaining = psTempl->buildPoints;
//check for zero build time - usually caused by 'silly' data! If so, set to 1 build point - ie very fast!
psFact->buildPointsRemaining = std::max(psFact->buildPointsRemaining, 1);
}
if (psStruct->player == productionPlayer)
{
intUpdateManufacture(psStruct);
}
return true;
}
/*****************************************************************************/
/*
* All this wall type code is horrible, but I really can't think of a better way to do it.
* John.
*/
enum WallOrientation
{
WallConnectNone = 0,
WallConnectLeft = 1,
WallConnectRight = 2,
WallConnectUp = 4,
WallConnectDown = 8,
};
// Orientations are:
//
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
// | | | | | | | |
// * -* *- -*- * -* *- -*- * -* *- -*- * -* *- -*-
// | | | | | | | |
// IMDs are:
//
// 0 1 2 3
// | | |
// -*- -*- -*- -*
// |
// Orientations are: IMDs are:
// 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3
// ╴ ╶ ─ ╵ ┘ └ ┴ ╷ ┐ ┌ ┬ │ ┤ ├ ┼ ─ ┼ ┴ ┘
static uint16_t wallDir(WallOrientation orient)
{
const uint16_t d0 = DEG(0), d1 = DEG(90), d2 = DEG(180), d3 = DEG(270); // d1 = rotate ccw, d3 = rotate cw
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
uint16_t dirs[16] = {d0, d0, d2, d0, d3, d0, d3, d0, d1, d1, d2, d2, d3, d1, d3, d0};
return dirs[orient];
}
static uint16_t wallType(WallOrientation orient)
{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
int types[16] = {0, 0, 0, 0, 0, 3, 3, 2, 0, 3, 3, 2, 0, 2, 2, 1};
return types[orient];
}
// look at where other walls are to decide what type of wall to build
static WallOrientation structWallScan(bool aWallPresent[5][5], int x, int y)
{
WallOrientation left = aWallPresent[x - 1][y]? WallConnectLeft : WallConnectNone;
WallOrientation right = aWallPresent[x + 1][y]? WallConnectRight : WallConnectNone;
WallOrientation up = aWallPresent[x][y - 1]? WallConnectUp : WallConnectNone;
WallOrientation down = aWallPresent[x][y + 1]? WallConnectDown : WallConnectNone;
return WallOrientation(left | right | up | down);
}
static bool isWallCombiningStructureType(STRUCTURE_STATS const *pStructureType)
{
STRUCTURE_TYPE type = pStructureType->type;
STRUCT_STRENGTH strength = pStructureType->strength;
return type == REF_WALL ||
type == REF_GATE ||
type == REF_WALLCORNER ||
(type == REF_DEFENSE && strength == STRENGTH_HARD) ||
(type == REF_BLASTDOOR && strength == STRENGTH_HARD); // fortresses
}
bool isWall(STRUCTURE_TYPE type)
{
return type == REF_WALL || type == REF_WALLCORNER;
}
bool isBuildableOnWalls(STRUCTURE_TYPE type)
{
return type == REF_DEFENSE || type == REF_GATE;
}
static void structFindWalls(unsigned player, Vector2i map, bool aWallPresent[5][5], STRUCTURE *apsStructs[5][5])
{
for (int y = -2; y <= 2; ++y)
for (int x = -2; x <= 2; ++x)
{
STRUCTURE *psStruct = castStructure(mapTile(map.x + x, map.y + y)->psObject);
if (psStruct != NULL && isWallCombiningStructureType(psStruct->pStructureType) && aiCheckAlliances(player, psStruct->player))
{
aWallPresent[x + 2][y + 2] = true;
apsStructs[x + 2][y + 2] = psStruct;
}
}
// add in the wall about to be built
aWallPresent[2][2] = true;
}
static void structFindWallBlueprints(Vector2i map, bool aWallPresent[5][5])
{
for (int y = -2; y <= 2; ++y)
for (int x = -2; x <= 2; ++x)
{
STRUCTURE_STATS const *stats = getTileBlueprintStats(map.x + x, map.y + y);
if (stats != NULL && isWallCombiningStructureType(stats))
{
aWallPresent[x + 2][y + 2] = true;
}
}
}
static bool wallBlockingTerrainJoin(Vector2i map)
{
MAPTILE *psTile = mapTile(map);
return terrainType(psTile) == TER_WATER || terrainType(psTile) == TER_CLIFFFACE || psTile->psObject != NULL;
}
static WallOrientation structWallScanTerrain(bool aWallPresent[5][5], Vector2i map)
{
WallOrientation orientation = structWallScan(aWallPresent, 2, 2);
if (orientation == WallConnectNone)
{
// If neutral, try choosing horizontal or vertical based on terrain, but don't change to corner type.
aWallPresent[2][1] = wallBlockingTerrainJoin(map + Vector2i( 0, -1));
aWallPresent[2][3] = wallBlockingTerrainJoin(map + Vector2i( 0, 1));
aWallPresent[1][2] = wallBlockingTerrainJoin(map + Vector2i(-1, 0));
aWallPresent[3][2] = wallBlockingTerrainJoin(map + Vector2i( 1, 0));
orientation = structWallScan(aWallPresent, 2, 2);
if ((orientation & (WallConnectLeft | WallConnectRight)) != 0 && (orientation & (WallConnectUp | WallConnectDown)) != 0)
{
orientation = WallConnectNone;
}
}
return orientation;
}
static WallOrientation structChooseWallTypeBlueprint(Vector2i map)
{
bool aWallPresent[5][5];
STRUCTURE * apsStructs[5][5];
// scan around the location looking for walls
memset(aWallPresent, 0, sizeof(aWallPresent));
structFindWalls(selectedPlayer, map, aWallPresent, apsStructs);
structFindWallBlueprints(map, aWallPresent);
// finally return the type for this wall
return structWallScanTerrain(aWallPresent, map);
}
// Choose a type of wall for a location - and update any neighbouring walls
static WallOrientation structChooseWallType(unsigned player, Vector2i map)
{
bool aWallPresent[5][5];
STRUCTURE *psStruct;
STRUCTURE *apsStructs[5][5];
// scan around the location looking for walls
memset(aWallPresent, 0, sizeof(aWallPresent));
structFindWalls(player, map, aWallPresent, apsStructs);
// now make sure that all the walls around this one are OK
for (int x = 1; x <= 3; ++x)
{
for (int y = 1; y <= 3; ++y)
{
// 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 && psStruct->pStructureType->type != REF_GATE)
{
// do not need to adjust anything apart from walls
continue;
}
// see what type the wall should be
WallOrientation scanType = structWallScan(aWallPresent, x,y);
// Got to change the wall
if (scanType != WallConnectNone)
{
psStruct->pFunctionality->wall.type = wallType(scanType);
psStruct->rot.direction = wallDir(scanType);
psStruct->sDisplay.imd = psStruct->pStructureType->pIMD[std::min<unsigned>(psStruct->pFunctionality->wall.type, psStruct->pStructureType->pIMD.size() - 1)];
}
}
}
}
// finally return the type for this wall
return structWallScanTerrain(aWallPresent, map);
}
/* 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*/
static int foundationHeight(STRUCTURE *psStruct)
{
StructureBounds b = getStructureBounds(psStruct);
//check the terrain is the correct type return -1 if not
//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!
//initialise the starting values so they get set in loop
int foundationMin = INT32_MAX;
int foundationMax = INT32_MIN;
for (int breadth = 0; breadth <= b.size.y; breadth++)
{
for (int width = 0; width <= b.size.x; width++)
{
int height = map_TileHeight(b.map.x + width, b.map.y + breadth);
foundationMin = std::min(foundationMin, height);
foundationMax = std::max(foundationMax, height);
}
}
//return the average of max/min height
return (foundationMin + foundationMax) / 2;
}
static void buildFlatten(STRUCTURE *pStructure, int h)
{
StructureBounds b = getStructureBounds(pStructure);
for (int breadth = 0; breadth <= b.size.y; ++breadth)
{
for (int width = 0; width <= b.size.x; ++width)
{
setTileHeight(b.map.x + width, b.map.y + breadth, h);
// We need to raise features on raised tiles to the new height
if (TileHasFeature(mapTile(b.map.x + width, b.map.y + breadth)))
{
getTileFeature(b.map.x + width, b.map.y + breadth)->pos.z = h;
}
}
}
}
static bool isPulledToTerrain(STRUCTURE const *psBuilding)
{
STRUCTURE_TYPE type = psBuilding->pStructureType->type;
return type == REF_DEFENSE || type == REF_GATE || type == REF_WALL || type == REF_WALLCORNER || type == REF_REARM_PAD;
}
void alignStructure(STRUCTURE *psBuilding)
{
/* DEFENSIVE structures are pulled to the terrain */
if (!isPulledToTerrain(psBuilding))
{
int mapH = foundationHeight(psBuilding);
buildFlatten(psBuilding, mapH);
psBuilding->pos.z = mapH;
psBuilding->foundationDepth = psBuilding->pos.z;
// Align surrounding structures.
StructureBounds b = getStructureBounds(psBuilding);
syncDebug("Flattened (%d+%d, %d+%d) to %d for %d(p%d)", b.map.x, b.size.x, b.map.y, b.size.y, mapH, psBuilding->id, psBuilding->player);
for (int breadth = -1; breadth <= b.size.y; ++breadth)
{
for (int width = -1; width <= b.size.x; ++width)
{
STRUCTURE *neighbourStructure = castStructure(mapTile(b.map.x + width, b.map.y + breadth)->psObject);
if (neighbourStructure != NULL && isPulledToTerrain(neighbourStructure))
{
alignStructure(neighbourStructure); // Recursive call, but will go to the else case, so will not re-recurse.
}
}
}
}
else
{
iIMDShape *strImd = psBuilding->sDisplay.imd;
psBuilding->pos.z = TILE_MIN_HEIGHT;
psBuilding->foundationDepth = TILE_MAX_HEIGHT;
// Now we got through the shape looking for vertices on the edge
for (int i = 0; i < strImd->npoints; i++)
{
int pointHeight = map_Height(psBuilding->pos.x + strImd->points[i].x, psBuilding->pos.y - strImd->points[i].z);
syncDebug("pointHeight=%d", pointHeight); // Eeek, strImd->points[i] is a Vector3f! If this causes desynchs, need to fix!
psBuilding->pos.z = std::max(psBuilding->pos.z, pointHeight);
psBuilding->foundationDepth = std::min<float>(psBuilding->foundationDepth, pointHeight);
}
}
}
/*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)
{
return buildStructureDir(pStructureType, x, y, 0, player, FromSave);
}
STRUCTURE* buildStructureDir(STRUCTURE_STATS *pStructureType, UDWORD x, UDWORD y, uint16_t direction, UDWORD player, bool FromSave)
{
STRUCTURE *psBuilding = NULL;
Vector2i size = getStructureStatsSize(pStructureType, direction);
ASSERT_OR_RETURN(NULL, pStructureType && pStructureType->type != REF_DEMOLISH, "You cannot build demolition!");
if (IsStatExpansionModule(pStructureType)==false)
{
SDWORD preScrollMinX = 0, preScrollMinY = 0, preScrollMaxX = 0, preScrollMaxY = 0;
UDWORD max = pStructureType - asStructureStats;
int i;
ASSERT_OR_RETURN(NULL, max <= numStructureStats, "Invalid structure type");
if (!strcmp(pStructureType->pName, "A0CyborgFactory") && player == 0 && !bMultiPlayer)
{
// HACK: correcting SP bug, needs fixing in script(!!) (only applies for player 0)
// should be OK for Skirmish/MP games, since that is set correctly.
// scrSetStructureLimits() is called by scripts to set this normally.
asStructLimits[player][max].limit = MAX_FACTORY;
asStructLimits[player][max].globalLimit = MAX_FACTORY;
}
// 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 = (x & ~TILE_MASK) + size.x%2 * TILE_UNITS/2;
y = (y & ~TILE_MASK) + size.y%2 * 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_WARNING, "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_WARNING, "attempting to build too closely to map-edge, "
"y coord (%u) too near edge (req. distance is %u)", y, TOO_NEAR_EDGE);
return NULL;
}
WallOrientation wallOrientation = WallConnectNone;
if (!FromSave && isWallCombiningStructureType(pStructureType))
{
wallOrientation = structChooseWallType(player, map_coord(Vector2i(x, y))); // This makes neighbouring walls match us, even if we're a hardpoint, not a wall.
}
// allocate memory for and initialize a structure object
psBuilding = new STRUCTURE(generateSynchronisedObjectId(), player);
if (psBuilding == NULL)
{
return NULL;
}
//fill in other details
psBuilding->pStructureType = pStructureType;
psBuilding->pos.x = x;
psBuilding->pos.y = y;
psBuilding->rot.direction = (direction + 0x2000)&0xC000;
psBuilding->rot.pitch = 0;
psBuilding->rot.roll = 0;
//This needs to be done before the functionality bit...
//load into the map data and structure list if not an upgrade
Vector2i map = map_coord(Vector2i(x, y)) - size/2;
//set up the imd to use for the display
psBuilding->sDisplay.imd = pStructureType->pIMD[0];
psBuilding->state = SAS_NORMAL;
psBuilding->lastStateTime = gameTime;
/* 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)
{
if (fireOnLocation(psFeature->pos.x,psFeature->pos.y))
{
// Can't build on burning oil resource
delete psBuilding;
return NULL;
}
// remove it from the map
turnOffMultiMsg(true); // dont send this one!
removeFeature(psFeature);
turnOffMultiMsg(false);
}
}
for (int width = 0; width < size.x; width++)
{
for (int breadth = 0; breadth < size.y; breadth++)
{
MAPTILE *psTile = mapTile(map.x + width, map.y + 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 (isBuildableOnWalls(pStructureType->type) && TileHasWall(psTile))
{
removeStruct((STRUCTURE *)psTile->psObject, true);
}
// don't really think this should be done here, but dont know otherwise.alexl
if (isWall(pStructureType->type))
{
if (TileHasStructure(mapTile(map.x + width, map.y + breadth)))
{
if (getTileStructure (map.x + width, map.y + breadth)->pStructureType->type == REF_WALLCORNER)
{
delete psBuilding;
return NULL; // dont build.
}
}
}
// end of dodgy stuff
else if (TileHasStructure(psTile))
{
debug(LOG_ERROR, "Player %u (%s): is building %s at (%d, %d) but found %s already at (%d, %d)",
player, isHumanPlayer(player) ? "Human" : "AI", pStructureType->pName, map.x, map.y,
getTileStructure(map.x + width, map.y + breadth)->pStructureType->pName,
map.x + width, map.y + breadth);
delete psBuilding;
return NULL;
}
psTile->psObject = psBuilding;
// if it's a tall structure then flag it in the map.
if (psBuilding->sDisplay.imd->max.y > TALLOBJECT_YMAX)
{
auxSetBlocking(map.x + width, map.y + breadth, AIR_BLOCKED);
}
}
}
switch (pStructureType->type)
{
case REF_REARM_PAD:
break; // Not blocking.
default:
auxStructureBlocking(psBuilding);
break;
}
//set up the rest of the data
for (i = 0;i < STRUCT_MAXWEAPS;i++)
{
psBuilding->asWeaps[i].rot.direction = 0;
psBuilding->asWeaps[i].rot.pitch = 0;
psBuilding->asWeaps[i].rot.roll = 0;
psBuilding->asWeaps[i].prevRot = psBuilding->asWeaps[i].rot;
psBuilding->psTarget[i] = NULL;
psBuilding->targetOrigin[i] = ORIGIN_UNKNOWN;
}
psBuilding->burnStart = 0;
psBuilding->burnDamage = 0;
psBuilding->status = SS_BEING_BUILT;
psBuilding->currentBuildPts = 0;
alignStructure(psBuilding);
//set up the sensor stats
objSensorCache(psBuilding, psBuilding->pStructureType->pSensor);
objEcmCache(psBuilding, psBuilding->pStructureType->pECM);
/* Store the weapons */
memset(psBuilding->asWeaps, 0, sizeof(WEAPON));
psBuilding->numWeaps = 0;
if (pStructureType->numWeaps > 0)
{
UDWORD weapon;
for(weapon=0; weapon < pStructureType->numWeaps; weapon++)
{
if (pStructureType->psWeapStat[weapon])
{
psBuilding->asWeaps[weapon].lastFired = 0;
psBuilding->asWeaps[weapon].shotsFired = 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->numWeaps++;
}
}
}
else
{
if (pStructureType->psWeapStat[0])
{
psBuilding->asWeaps[0].lastFired = 0;
psBuilding->asWeaps[0].shotsFired = 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;
}
}
for (int j = 0; j < WC_NUM_WEAPON_CLASSES; j++)
{
psBuilding->armour[j] = (UWORD)structureArmour(pStructureType, (UBYTE)player);
}
psBuilding->resistance = (UWORD)structureResistance(pStructureType, (UBYTE)player);
psBuilding->lastResistance = ACTION_START_TIME;
// Do the visibility stuff before setFunctionality - so placement of DP's can work
memset(psBuilding->seenThisTick, 0, sizeof(psBuilding->seenThisTick));
// Structure is visible to anyone with shared vision.
for (unsigned vPlayer = 0; vPlayer < MAX_PLAYERS; ++vPlayer)
{
psBuilding->visible[vPlayer] = hasSharedVision(vPlayer, player)? UINT8_MAX : 0;
}
// Reveal any tiles that can be seen by the structure
visTilesUpdate(psBuilding);
/*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;
// NOTE: resizeRadar() may be required here, since we change scroll limits?
}
//set the functionality dependant on the type of structure
if(!setFunctionality(psBuilding, pStructureType->type))
{
removeStructFromMap(psBuilding);
delete 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;
// NOTE: resizeRadar() may be required here, since we change scroll limits?
}
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;
// NOTE: resizeRadar() may be required here, since we change scroll limits?
}
// rotate a wall if necessary
if (!FromSave && (pStructureType->type == REF_WALL || pStructureType->type == REF_GATE))
{
psBuilding->pFunctionality->wall.type = wallType(wallOrientation);
if (wallOrientation != WallConnectNone)
{
psBuilding->rot.direction = wallDir(wallOrientation);
psBuilding->sDisplay.imd = psBuilding->pStructureType->pIMD[std::min<unsigned>(psBuilding->pFunctionality->wall.type, psBuilding->pStructureType->pIMD.size() - 1)];
}
}
psBuilding->body = (UWORD)structureBody(psBuilding);
psBuilding->expectedDamage = 0; // Begin life optimistically.
//add the structure to the list - this enables it to be drawn whilst being built
addStructure(psBuilding);
clustNewStruct(psBuilding);
asStructLimits[player][max].currentQuantity++;
if (isLasSat(psBuilding->pStructureType))
{
psBuilding->asWeaps[0].ammo = 1; // ready to trigger the fire button
}
}
else //its an upgrade
{
bool bUpgraded = false;
int32_t bodyDiff = 0;
//don't create the Structure use existing one
psBuilding = getTileStructure(map_coord(x), map_coord(y));
if (!psBuilding)
{
return NULL;
}
int prevResearchState = intGetResearchState();
int capacity = 0; // Dummy initialisation.
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 = 65536 - getStructureDamage(psBuilding);
++psBuilding->pFunctionality->factory.capacity;
bUpgraded = true;
//put any production on hold
holdProduction(psBuilding, ModeImmediate);
//quick check not trying to add too much
ASSERT_OR_RETURN(NULL, psBuilding->pFunctionality->factory.productionOutput +
((PRODUCTION_FUNCTION*)pStructureType->asFuncList[0])->productionOutput < UBYTE_MAX,
"building factory module - production Output is too big");
psBuilding->pFunctionality->factory.productionOutput += ((
PRODUCTION_FUNCTION*)pStructureType->asFuncList[0])->productionOutput;
capacity = psBuilding->pFunctionality->factory.capacity;
}
}
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 = 65536 - 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, ModeImmediate);
}
capacity = psBuilding->pFunctionality->researchFacility.capacity;
}
}
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 = 65536 - 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;
bUpgraded = true;
capacity = psBuilding->pFunctionality->powerGenerator.capacity;
//need to inform any res Extr associated that not digging until complete
releasePowerGen(psBuilding);
structurePowerUpgrade(psBuilding);
}
}
if (bUpgraded)
{
std::vector<iIMDShape *> &IMDs = psBuilding->pStructureType->pIMD;
int imdIndex = std::min<int>(capacity*2, IMDs.size() - 1) - 1; // *2-1 because even-numbered IMDs are structures, odd-numbered IMDs are just the modules, and we want just the module since we cache the fully-built part of the building in psBuilding->prebuiltImd.
psBuilding->prebuiltImd = psBuilding->sDisplay.imd;
psBuilding->sDisplay.imd = IMDs[imdIndex];
//calculate the new body points of the owning structure
psBuilding->body = (uint64_t)structureBody(psBuilding) * bodyDiff / 65536;
//initialise the build points
psBuilding->currentBuildPts = 0;
//start building again
psBuilding->status = SS_BEING_BUILT;
psBuilding->buildRate = 1; // Don't abandon the structure first tick, so set to nonzero.
if (psBuilding->player == selectedPlayer && !FromSave)
{
intRefreshScreen();
}
}
intNotifyResearchButton(prevResearchState);
}
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(psBuilding,player);
psBuilding->prevTime = gameTime - deltaGameTime; // Structure hasn't been updated this tick, yet.
psBuilding->time = psBuilding->prevTime - 1; // -1, so the times are different, even before updating.
return psBuilding;
}
STRUCTURE *buildBlueprint(STRUCTURE_STATS const *psStats, Vector2i xy, uint16_t direction, unsigned moduleIndex, STRUCT_STATES state)
{
STRUCTURE *blueprint;
ASSERT_OR_RETURN(NULL, psStats != NULL, "No blueprint stats");
ASSERT_OR_RETURN(NULL, psStats->pIMD[0] != NULL, "No blueprint model for %s", getStatName(psStats));
Vector3i pos(xy, INT32_MIN);
Rotation rot((direction + 0x2000)&0xC000, 0, 0); // Round direction to nearest 90°.
StructureBounds b = getStructureBounds(psStats, xy, direction);
for (int j = 0; j <= b.size.y; ++j)
for (int i = 0; i <= b.size.x; ++i)
{
pos.z = std::max(pos.z, map_TileHeight(b.map.x + i, b.map.y + j));
}
int moduleNumber = 0;
std::vector<iIMDShape *> const *pIMD = &psStats->pIMD;
if (IsStatExpansionModule(psStats))
{
STRUCTURE *baseStruct = castStructure(worldTile(xy)->psObject);
if (baseStruct != NULL)
{
if (moduleIndex == 0)
{
moduleIndex = nextModuleToBuild(baseStruct, 0);
}
int baseModuleNumber = moduleIndex*2 - 1; // *2-1 because even-numbered IMDs are structures, odd-numbered IMDs are just the modules.
std::vector<iIMDShape *> const *basepIMD = &baseStruct->pStructureType->pIMD;
if ((unsigned)baseModuleNumber < basepIMD->size())
{
// Draw the module.
moduleNumber = baseModuleNumber;
pIMD = basepIMD;
pos = baseStruct->pos;
rot = baseStruct->rot;
}
}
}
blueprint = new STRUCTURE(0, selectedPlayer);
// construct the fake structure
blueprint->pStructureType = const_cast<STRUCTURE_STATS *>(psStats); // Couldn't be bothered to fix const correctness everywhere.
blueprint->visible[selectedPlayer] = UBYTE_MAX;
blueprint->sDisplay.imd = (*pIMD)[std::min<int>(moduleNumber, pIMD->size() - 1)];
blueprint->pos = pos;
blueprint->rot = rot;
blueprint->selected = false;
blueprint->numWeaps = 0;
blueprint->asWeaps[0].nStat = 0;
// give defensive structures a weapon
if (psStats->psWeapStat[0])
{
blueprint->asWeaps[0].nStat = psStats->psWeapStat[0] - asWeaponStats;
}
// things with sensors or ecm (or repair facilities) need these set, even if they have no official weapon
blueprint->numWeaps = 0;
blueprint->asWeaps[0].lastFired = 0;
blueprint->asWeaps[0].rot.pitch = 0;
blueprint->asWeaps[0].rot.direction = 0;
blueprint->asWeaps[0].rot.roll = 0;
blueprint->asWeaps[0].prevRot = blueprint->asWeaps[0].rot;
blueprint->expectedDamage = 0;
// Times must be different, but don't otherwise matter.
blueprint->time = 23;
blueprint->prevTime = 42;
blueprint->status = state;
// Rotate wall if needed.
if (blueprint->pStructureType->type == REF_WALL || blueprint->pStructureType->type == REF_GATE)
{
WallOrientation scanType = structChooseWallTypeBlueprint(map_coord(removeZ(blueprint->pos)));
unsigned type = wallType(scanType);
if (scanType != WallConnectNone)
{
blueprint->rot.direction = wallDir(scanType);
blueprint->sDisplay.imd = blueprint->pStructureType->pIMD[std::min<unsigned>(type, blueprint->pStructureType->pIMD.size() - 1)];
}
}
return blueprint;
}
static 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:
case REF_WALL:
case REF_GATE:
// Allocate space for the buildings functionality
psBuilding->pFunctionality = (FUNCTIONALITY *)calloc(1, sizeof(*psBuilding->pFunctionality));
ASSERT_OR_RETURN(false, psBuilding != NULL, "Out of memory");
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 + (getStructureWidth(psBuilding) + 1) * TILE_UNITS/2);
y = map_coord(psBuilding->pos.y + (getStructureBreadth(psBuilding) + 1) * TILE_UNITS/2);
// 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_OR_RETURN(false, false, "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->capacity = 0;
// Take advantage of upgrades
structurePowerUpgrade(psBuilding);
break;
}
case REF_RESOURCE_EXTRACTOR:
{
RES_EXTRACTOR* psResExtracter = &psBuilding->pFunctionality->resourceExtractor;
// 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!
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;
psRepairFac->psGroup = grpCreate();
// Add NULL droid to the group
psRepairFac->psGroup->add(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 + (getStructureWidth(psBuilding) + 1) * TILE_UNITS/2);
y = map_coord(psBuilding->pos.y + (getStructureBreadth(psBuilding) + 1) * TILE_UNITS/2);
// 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_OR_RETURN( , StructIsFactory(psStruct),"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", "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;
syncDebug("Removed commander from factory %d", psStruct->id);
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_OR_RETURN( , !missionIsOffworld(), "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;
syncDebug("Assigned commander %d to factory %d", psCommander->id, psStruct->id);
}
}
// 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;
unsigned sWidth = getStructureWidth(psStructure);
unsigned sBreadth = getStructureBreadth(psStructure);
CHECK_STRUCTURE(psStructure);
/* Get the tile coords for the top left of the structure */
sx = (SWORD)(psStructure->pos.x - sWidth * TILE_UNITS/2);
sx = map_coord(sx);
sy = (SWORD)(psStructure->pos.y - sBreadth * TILE_UNITS/2);
sy = map_coord(sy);
/* Find the four corners of the square */
xmin = (SWORD)(sx - 1);
xmax = (SWORD)(sx + sWidth);
xmid = (SWORD)(sx + (sWidth-1)/2);
ymin = (SWORD)(sy - 1);
ymax = (SWORD)(sy + sBreadth);
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;
FLAG_POSITION *psFlag;
Vector3i iVecEffect;
UBYTE factoryType;
bool assignCommander;
CHECK_STRUCTURE(psStructure);
placed = placeDroid(psStructure, &x, &y);
if (placed)
{
INITIAL_DROID_ORDERS initialOrders = {psStructure->pFunctionality->factory.secondaryOrder, psStructure->pFunctionality->factory.psAssemblyPoint->coords.x, psStructure->pFunctionality->factory.psAssemblyPoint->coords.y, psStructure->id};
//create a droid near to the structure
syncDebug("Placing new droid at (%d,%d)", x, y);
turnOffMultiMsg(true);
psNewDroid = buildDroid(psTempl, world_coord(x), world_coord(y), psStructure->player, false, &initialOrders);
turnOffMultiMsg(false);
if (!psNewDroid)
{
*ppsDroid = NULL;
return false;
}
if (myResponsibility(psStructure->player))
{
uint32_t newState = psStructure->pFunctionality->factory.secondaryOrder;
uint32_t diff = newState ^ psNewDroid->secondaryOrder;
if ((diff & DSS_ARANGE_MASK) != 0)
{ // TODO Should synchronise factory.secondaryOrder and flag positions.
secondarySetState(psNewDroid, DSO_ATTACK_RANGE, (SECONDARY_STATE)(newState & DSS_ARANGE_MASK));
}
if ((diff & DSS_REPLEV_MASK) != 0)
{
secondarySetState(psNewDroid, DSO_REPAIR_LEVEL, (SECONDARY_STATE)(newState & DSS_REPLEV_MASK));
}
if ((diff & DSS_ALEV_MASK) != 0)
{
secondarySetState(psNewDroid, DSO_ATTACK_LEVEL, (SECONDARY_STATE)(newState & DSS_ALEV_MASK));
}
if ((diff & DSS_CIRCLE_MASK) != 0)
{
secondarySetState(psNewDroid, DSO_CIRCLE, (SECONDARY_STATE)(newState & DSS_CIRCLE_MASK));
}
}
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, gameTime - deltaGameTime + 1);
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, gameTime - deltaGameTime + 1);
iVecEffect.z = psNewDroid->pos.y + DROID_CONSTRUCTION_SMOKE_OFFSET;
addEffect(&iVecEffect, EFFECT_CONSTRUCTION, CONSTRUCTION_TYPE_DRIFTING, false, NULL, 0, gameTime - deltaGameTime + 1);
iVecEffect.x = psNewDroid->pos.x + DROID_CONSTRUCTION_SMOKE_OFFSET;
addEffect(&iVecEffect, EFFECT_CONSTRUCTION, CONSTRUCTION_TYPE_DRIFTING, false, NULL, 0, gameTime - deltaGameTime + 1);
iVecEffect.z = psNewDroid->pos.y - DROID_CONSTRUCTION_SMOKE_OFFSET;
addEffect(&iVecEffect, EFFECT_CONSTRUCTION, CONSTRUCTION_TYPE_DRIFTING, false, NULL, 0, gameTime - deltaGameTime + 1);
}
/* 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;
// 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 )
{
syncDebug("Has commander.");
if (idfDroid(psNewDroid) ||
isVtolDroid(psNewDroid))
{
orderDroidObj(psNewDroid, DORDER_FIRESUPPORT, psFact->psCommander, ModeImmediate);
moveToRearm(psNewDroid);
}
else
{
orderDroidObj(psNewDroid, DORDER_COMMANDERSUPPORT, psFact->psCommander, ModeImmediate);
}
}
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) && (psNewDroid->droidType != DROID_TRANSPORTER && psNewDroid->droidType != DROID_SUPERTRANSPORTER))
{
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 (isVtolDroid(psNewDroid))
{
Vector2i pos = removeZ(psFlag->coords);
//find a suitable location near the delivery point
actionVTOLLandingPos(psNewDroid, &pos);
orderDroidLoc(psNewDroid, DORDER_MOVE, pos.x, pos.y, ModeQueue);
}
else
{
orderDroidLoc(psNewDroid, DORDER_MOVE, psFlag->coords.x, psFlag->coords.y, ModeQueue);
}
}
}
if (assignCommander)
{
assignFactoryCommandDroid(psStructure, psNewDroid);
}
if ( psNewDroid->player == selectedPlayer )
{
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_DROIDBUILT);
}
return true;
}
else
{
*ppsDroid = NULL;
}
return false;
}
static bool IsFactoryCommanderGroupFull(const FACTORY* psFactory)
{
unsigned int 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 ? psFactory->psCommander->psGroup->getNumMembers() : 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,
// doesn't mean that these numbers can't be exceeded if units are
// put down in the editor or by the scripts.
bool IsPlayerStructureLimitReached(UDWORD PlayerNumber)
{
// PC currently doesn't limit number of structures a player can build.
return false;
}
UDWORD getMaxDroids(UDWORD PlayerNumber)
{
return bMultiPlayer? MAX_MP_DROIDS : PlayerNumber == 0? MAX_SP_DROIDS : MAX_SP_AI_DROIDS;
}
bool IsPlayerDroidLimitReached(UDWORD PlayerNumber)
{
unsigned int numDroids = getNumDroids(PlayerNumber) + getNumMissionDroids(PlayerNumber) + getNumTransporterDroids(PlayerNumber);
return numDroids >= getMaxDroids(PlayerNumber);
}
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(_("Can't build anymore units, Command Control Limit Reached - Production Halted"), DEFAULT_JUSTIFY, SYSTEM_MESSAGE);
lastMaxUnitMessage = gameTime;
}
return true;
}
return false;
}
static void aiUpdateStructure(STRUCTURE *psStructure, bool isMission)
{
BASE_STATS *pSubject = NULL;
UDWORD pointsToAdd;//, iPower;
RESEARCH *pResearch;
UDWORD structureMode = 0;
DROID *psDroid;
BASE_OBJECT *psChosenObjs[STRUCT_MAXWEAPS] = {NULL};
BASE_OBJECT *psChosenObj = NULL;
FACTORY *psFactory;
REPAIR_FACILITY *psRepairFac = NULL;
RESEARCH_FACILITY *psResFacility;
Vector3i iVecEffect;
bool bDroidPlaced = false;
WEAPON_STATS *psWStats;
SDWORD xdiff,ydiff, mindist, currdist;
UDWORD i;
UWORD tmpOrigin = ORIGIN_UNKNOWN;
CHECK_STRUCTURE(psStructure);
if (psStructure->time == gameTime)
{
// This isn't supposed to happen, and really shouldn't be possible - if this happens, maybe a structure is being updated twice?
int count1 = 0, count2 = 0;
STRUCTURE *s;
for (s = apsStructLists[psStructure->player]; s != NULL; s = s->psNext) count1 += s == psStructure;
for (s = mission.apsStructLists[psStructure->player]; s != NULL; s = s->psNext) count2 += s == psStructure;
debug(LOG_ERROR, "psStructure->prevTime = %u, psStructure->time = %u, gameTime = %u, count1 = %d, count2 = %d", psStructure->prevTime, psStructure->time, gameTime, count1, count2);
--psStructure->time;
}
psStructure->prevTime = psStructure->time;
psStructure->time = gameTime;
for (i = 0; i < MAX(1, psStructure->numWeaps); ++i)
{
psStructure->asWeaps[i].prevRot = psStructure->asWeaps[i].rot;
}
if (isMission)
{
switch (psStructure->pStructureType->type)
{
case REF_RESEARCH:
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
break;
default:
return; // nothing to do
}
}
// 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->asWeaps[0].rot.direction = (uint16_t)((uint64_t)gameTime * 65536 / 3000); // Cast wrapping intended.
psStructure->asWeaps[0].rot.pitch = 0;
}
}
/* Check lassat */
if (isLasSat(psStructure->pStructureType)
&& gameTime - psStructure->asWeaps[0].lastFired > weaponFirePause(&asWeaponStats[psStructure->asWeaps[0].nStat], psStructure->player)
&& psStructure->asWeaps[0].ammo > 0)
{
triggerEventStructureReady(psStructure);
psStructure->asWeaps[0].ammo = 0; // do not fire more than once
}
/* 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 (aiChooseTarget(psStructure, &psChosenObjs[i], i, true, &tmpOrigin) )
{
objTrace(psStructure->id, "Weapon %d is targeting %d at (%d, %d)", i, psChosenObjs[i]->id,
psChosenObjs[i]->pos.x, psChosenObjs[i]->pos.y);
setStructureTarget(psStructure, psChosenObjs[i], i, tmpOrigin);
}
else
{
if ( aiChooseTarget(psStructure, &psChosenObjs[0], 0, true, &tmpOrigin) )
{
if (psChosenObjs[0])
{
objTrace(psStructure->id, "Weapon %d is supporting main weapon: %d at (%d, %d)", i,
psChosenObjs[0]->id, psChosenObjs[0]->pos.x, psChosenObjs[0]->pos.y);
setStructureTarget(psStructure, psChosenObjs[0], i, tmpOrigin);
psChosenObjs[i] = psChosenObjs[0];
}
else
{
setStructureTarget(psStructure, NULL, i, ORIGIN_UNKNOWN);
psChosenObjs[i] = NULL;
}
}
else
{
setStructureTarget(psStructure, NULL, i, ORIGIN_UNKNOWN);
psChosenObjs[i] = NULL;
}
}
if (psChosenObjs[i] != NULL && !aiObjectIsProbablyDoomed(psChosenObjs[i]))
{
// 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->asWeaps[i].rot.direction = calcDirection(psStructure->pos.x, psStructure->pos.y, psChosenObjs[i]->pos.x, psChosenObjs[i]->pos.y);
combFire(&psStructure->asWeaps[i], psStructure, psChosenObjs[i], i);
}
else if (actionTargetTurret(psStructure, psChosenObjs[i], &psStructure->asWeaps[i]))
{
combFire(&psStructure->asWeaps[i], psStructure, psChosenObjs[i], i);
}
}
else
{
// realign the turret
if ((psStructure->asWeaps[i].rot.direction % DEG(90)) != 0 || psStructure->asWeaps[i].rot.pitch != 0)
{
actionAlignTurret(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) || objRadarDetector(psStructure))
{
if (aiChooseSensorTarget(psStructure, &psChosenObj))
{
objTrace(psStructure->id, "Sensing (%d)", psChosenObj->id);
if (objRadarDetector(psStructure))
{
setStructureTarget(psStructure, psChosenObj, 0, ORIGIN_RADAR_DETECTOR);
}
else
{
setStructureTarget(psStructure, psChosenObj, 0, ORIGIN_SENSOR);
}
}
else
{
setStructureTarget(psStructure, NULL, 0, ORIGIN_UNKNOWN);
}
psChosenObj = psStructure->psTarget[0];
}
else
{
psChosenObj = psStructure->psTarget[0];
}
}
//only interested if the Structure "does" something!
if (psStructure->pFunctionality == NULL)
{
return;
}
/* 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: // FIXME FIXME FIXME: Magic numbers in this section
{
psRepairFac = &psStructure->pFunctionality->repairFacility;
psChosenObj = psRepairFac->psObj;
structureMode = REF_REPAIR_FACILITY;
psDroid = (DROID *)psChosenObj;
// If the droid we're repairing just died, find a new one
if (psDroid && psDroid->died)
{
psDroid = NULL;
psChosenObj = NULL;
psRepairFac->psObj = NULL;
}
// skip droids that are trying to get to other repair factories
if (psDroid != NULL
&& (!orderState(psDroid, DORDER_RTR)
|| psDroid->order.psObj != psStructure))
{
psDroid = (DROID *)psChosenObj;
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psStructure->pos.x;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psStructure->pos.y;
// unless it has orders to repair here, forget about it when it gets out of range
if (xdiff * xdiff + ydiff * ydiff > (TILE_UNITS*5/2)*(TILE_UNITS*5/2))
{
psChosenObj = NULL;
psDroid = NULL;
psRepairFac->psObj = NULL;
}
}
// select next droid if none being repaired,
// or look for a better droid if not repairing one with repair orders
if (psChosenObj == NULL ||
(((DROID *)psChosenObj)->order.type != DORDER_RTR && ((DROID *)psChosenObj)->order.type != DORDER_RTR_SPECIFIED))
{
//FIX ME: (doesn't look like we need this?)
ASSERT(psRepairFac->psGroup != NULL, "invalid repair facility group pointer");
// Tries to find most important droid to repair
// Lower dist = more important
// mindist contains lowest dist found so far
mindist = (TILE_UNITS*8)*(TILE_UNITS*8)*3;
if (psChosenObj)
{
// We already have a valid droid to repair, no need to look at
// droids without a repair order.
mindist = (TILE_UNITS*8)*(TILE_UNITS*8)*2;
}
psRepairFac->droidQueue = 0;
for (psDroid = apsDroidLists[psStructure->player]; psDroid; psDroid = psDroid->psNext)
{
BASE_OBJECT * const psTarget = orderStateObj(psDroid, DORDER_RTR);
// Highest priority:
// Take any droid with orders to Return to Repair (DORDER_RTR),
// or that have been ordered to this repair facility (DORDER_RTR_SPECIFIED),
// or any "lost" unit with one of those two orders.
if (((psDroid->order.type == DORDER_RTR || (psDroid->order.type == DORDER_RTR_SPECIFIED
&& (!psTarget || psTarget == psStructure)))
&& psDroid->action != DACTION_WAITFORREPAIR && psDroid->action != DACTION_MOVETOREPAIRPOINT
&& psDroid->action != DACTION_WAITDURINGREPAIR)
|| (psTarget && psTarget == psStructure))
{
if (psDroid->body >= psDroid->originalBody)
{
objTrace(psStructure->id, "Repair not needed of droid %d", (int)psDroid->id);
/* set droid points to max */
psDroid->body = psDroid->originalBody;
// 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, psCommander, ModeImmediate);
}
else if (psRepairFac->psDeliveryPoint != NULL)
{
// move the droid out the way
objTrace(psDroid->id, "Repair not needed - move to delivery point");
orderDroidLoc(psDroid, DORDER_MOVE,
psRepairFac->psDeliveryPoint->coords.x,
psRepairFac->psDeliveryPoint->coords.y, ModeQueue); // ModeQueue because delivery points are not yet synchronised!
}
continue;
}
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 < (TILE_UNITS*8)*(TILE_UNITS*8))
{
mindist = currdist;
psChosenObj = psDroid;
}
if (psTarget && psTarget == psStructure)
{
psRepairFac->droidQueue++;
}
}
// Second highest priority:
// Help out another nearby repair facility
else if (psTarget && mindist > (TILE_UNITS*8)*(TILE_UNITS*8)
&& psTarget != 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 + (TILE_UNITS*8)*(TILE_UNITS*8); // lower priority
if (currdist < mindist && currdist - (TILE_UNITS*8)*(TILE_UNITS*8) < distLimit)
{
mindist = currdist;
psChosenObj = psDroid;
psRepairFac->droidQueue++; // shared queue
objTrace(psChosenObj->id, "Stolen by another repair facility, currdist=%d, mindist=%d, distLimit=%d", (int)currdist, (int)mindist, distLimit);
}
}
// Lowest priority:
// Just repair whatever's nearby and needs repairing.
else if (mindist > (TILE_UNITS*8)*(TILE_UNITS*8)*2 && psDroid->body < psDroid->originalBody)
{
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psStructure->pos.x;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psStructure->pos.y;
currdist = xdiff*xdiff + ydiff*ydiff + (TILE_UNITS*8)*(TILE_UNITS*8)*2; // even lower priority
if (currdist < mindist && currdist < (TILE_UNITS*5/2)*(TILE_UNITS*5/2) + (TILE_UNITS*8)*(TILE_UNITS*8)*2)
{
mindist = currdist;
psChosenObj = psDroid;
}
}
}
if (!psChosenObj) // Nothing to repair? Repair allied units!
{
mindist = (TILE_UNITS*5/2)*(TILE_UNITS*5/2);
for (i=0; i<MAX_PLAYERS; i++)
{
if (aiCheckAlliances(i, psStructure->player) && i != psStructure->player)
{
for (psDroid = apsDroidLists[i]; psDroid; psDroid = psDroid->psNext)
{
if (psDroid->body < psDroid->originalBody)
{
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 = psDroid;
}
}
}
}
}
}
psDroid = (DROID *)psChosenObj;
if (psDroid)
{
if (psDroid->order.type == DORDER_RTR || psDroid->order.type == DORDER_RTR_SPECIFIED)
{
// Hey, droid, it's your turn! Stop what you're doing and get ready to get repaired!
psDroid->action = DACTION_WAITFORREPAIR;
psDroid->order.psObj = psStructure;
}
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);
}
}
// send the droid to be repaired
if (psDroid)
{
/* set chosen object */
psChosenObj = psDroid;
/* move droid to repair point at rear of facility */
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psStructure->pos.x;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psStructure->pos.y;
if (psDroid->action == DACTION_WAITFORREPAIR ||
(psDroid->action == DACTION_WAITDURINGREPAIR
&& xdiff*xdiff + ydiff*ydiff > (TILE_UNITS*5/2)*(TILE_UNITS*5/2)))
{
objTrace(psStructure->id, "Requesting droid %d to come to us", (int)psDroid->id);
actionDroid(psDroid, DACTION_MOVETOREPAIRPOINT,
psStructure, psStructure->pos.x, psStructure->pos.y);
}
/* reset repair started if we were previously repairing something else */
if (psRepairFac->psObj != psDroid)
{
psRepairFac->psObj = psDroid;
}
}
// update repair arm position
if (psChosenObj)
{
actionTargetTurret(psStructure, psChosenObj, &psStructure->asWeaps[0]);
}
else if ((psStructure->asWeaps[0].rot.direction % DEG(90)) != 0 || psStructure->asWeaps[0].rot.pitch != 0)
{
// realign the turret
actionAlignTurret(psStructure, 0);
}
break;
}
case REF_REARM_PAD:
{
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 = psDroid;
}
}
if (!psChosenObj) // None available? Try allies.
{
for (i=0; i<MAX_PLAYERS; i++)
{
if (aiCheckAlliances(i, psStructure->player) && i != psStructure->player)
{
for (psDroid = apsDroidLists[i]; psDroid; psDroid = psDroid->psNext)
{
// move next droid waiting on ground to rearm pad
if (vtolReadyToRearm(psDroid, psStructure))
{
psChosenObj = psDroid;
break;
}
}
}
}
}
psDroid = (DROID *)psChosenObj;
if (psDroid != NULL)
{
actionDroid( psDroid, DACTION_MOVETOREARMPOINT, psStructure);
}
}
else
{
psDroid = (DROID *) psChosenObj;
if ( (psDroid->sMove.Status == MOVEINACTIVE ||
psDroid->sMove.Status == MOVEHOVER ) &&
psDroid->action == DACTION_WAITFORREARM )
{
actionDroid( psDroid, DACTION_MOVETOREARMPOINT, psStructure);
}
}
// if found a droid to rearm assign it to the rearm pad
if (psDroid != NULL)
{
/* set chosen object */
psChosenObj = psDroid;
psReArmPad->psObj = psChosenObj;
if ( psDroid->action == DACTION_MOVETOREARMPOINT )
{
/* reset rearm started */
psReArmPad->timeStarted = ACTION_START_TIME;
psReArmPad->timeLastUpdated = 0;
}
auxStructureBlocking(psStructure);
}
else
{
auxStructureNonblocking(psStructure);
}
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)
{
delPowerRequest(psStructure);
return;
}
//electronic warfare affects the functionality of some structures in multiPlayer
if (bMultiPlayer)
{
if (psStructure->resistance < (SWORD)structureResistance(psStructure->
pStructureType, psStructure->player))
{
return;
}
}
int researchIndex = pSubject->ref - REF_RESEARCH_START;
PLAYER_RESEARCH *pPlayerRes = &asPlayerResList[psStructure->player][researchIndex];
//check research has not already been completed by another structure
if (!IsResearchCompleted(pPlayerRes))
{
pResearch = (RESEARCH *)pSubject;
pointsToAdd = gameTimeAdjustedAverage(psResFacility->researchPoints);
pointsToAdd = MIN(pointsToAdd, pResearch->researchPoints - pPlayerRes->currentPoints);
if (pointsToAdd > 0 && pPlayerRes->currentPoints == 0)
{
bool haveEnoughPower = requestPowerFor(psStructure, pResearch->researchPower);
if (!haveEnoughPower)
{
pointsToAdd = 0;
}
}
if (pointsToAdd > 0 && pResearch->researchPoints > 0) // might be a "free" research
{
pPlayerRes->currentPoints += pointsToAdd;
}
syncDebug("Research at %u/%u.", pPlayerRes->currentPoints, pResearch->researchPoints);
//check if Research is complete
if (pPlayerRes->currentPoints >= pResearch->researchPoints)
{
int prevState = intGetResearchState();
//store the last topic researched - if its the best
if (psResFacility->psBestTopic == NULL)
{
psResFacility->psBestTopic = psResFacility->psSubject;
}
else
{
if (pResearch->researchPoints > psResFacility->psBestTopic->researchPoints)
{
psResFacility->psBestTopic = psResFacility->psSubject;
}
}
psResFacility->psSubject = NULL;
intResearchFinished(psStructure);
researchResult(researchIndex, psStructure->player, true, psStructure, true);
// Update allies research accordingly
if (game.type == SKIRMISH)
{
for (i = 0; i < MAX_PLAYERS; i++)
{
if (alliances[i][psStructure->player] == ALLIANCE_FORMED)
{
if (!IsResearchCompleted(&asPlayerResList[i][researchIndex]))
{
// Do the research for that player
researchResult(researchIndex, i, false, NULL, true);
}
}
}
}
//check if this result has enabled another topic
intNotifyResearchButton(prevState);
}
}
else
{
//cancel this Structure's research since now complete
psResFacility->psSubject = NULL;
intResearchFinished(psStructure);
syncDebug("Research completed elsewhere.");
}
}
//check for manufacture
else if (structureMode == REF_FACTORY)
{
psFactory = &psStructure->pFunctionality->factory;
//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))
{
return;
}
if(CheckHaltOnMaxUnitsReached(psStructure) == true) {
return;
}
}
/*must be enough power so subtract that required to build*/
if (psFactory->timeStarted == ACTION_START_TIME)
{
//set the time started
psFactory->timeStarted = gameTime;
}
if (psFactory->buildPointsRemaining > 0)
{
int progress = gameTimeAdjustedAverage(psFactory->productionOutput);
if (psFactory->buildPointsRemaining == psFactory->psSubject->buildPoints && progress > 0)
{
// We're just starting to build, check for power.
bool haveEnoughPower = requestPowerFor(psStructure, psFactory->psSubject->powerPoints);
if (!haveEnoughPower)
{
progress = 0;
}
}
psFactory->buildPointsRemaining -= progress;
}
//check for manufacture to be complete
if ((psFactory->buildPointsRemaining <= 0) && !IsFactoryCommanderGroupFull(psFactory) && !CheckHaltOnMaxUnitsReached(psStructure))
{
if (isMission)
{
// put it in the mission list
psDroid = buildMissionDroid((DROID_TEMPLATE *)pSubject,
psStructure->pos.x, psStructure->pos.y,
psStructure->player);
if (psDroid)
{
setDroidBase(psDroid, psStructure);
bDroidPlaced = true;
}
}
else
{
// place it on the map
bDroidPlaced = structPlaceDroid(psStructure, (DROID_TEMPLATE *)pSubject, &psDroid);
}
//reset the start time
psFactory->timeStarted = ACTION_START_TIME;
psFactory->psSubject = NULL;
doNextProduction(psStructure, (DROID_TEMPLATE *)pSubject, ModeImmediate);
//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 )
{
psDroid = (DROID *) psChosenObj;
ASSERT_OR_RETURN( , psDroid != NULL, "invalid droid pointer");
psRepairFac = &psStructure->pFunctionality->repairFacility;
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psStructure->pos.x;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psStructure->pos.y;
if (xdiff * xdiff + ydiff * ydiff <= (TILE_UNITS*5/2)*(TILE_UNITS*5/2))
{
//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 || psDroid->droidType == DROID_SUPERTRANSPORTER))
{
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;
}
}
// FIXME: duplicate code, make repairing cost power again
/* do repairing */
psDroid->body += gameTimeAdjustedAverage(psRepairFac->power);
}
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;
if ((psDroid->order.type == DORDER_RTR || psDroid->order.type == DORDER_RTR_SPECIFIED)
&& psDroid->order.psObj == psStructure)
{
// 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;
objTrace(psDroid->id, "Repair complete - move to commander");
orderDroidObj(psDroid, DORDER_GUARD, psCommander, ModeImmediate);
}
else if (psRepairFac->psDeliveryPoint != NULL)
{
// move the droid out the way
objTrace(psDroid->id, "Repair complete - move to delivery point");
orderDroidLoc( psDroid, DORDER_MOVE,
psRepairFac->psDeliveryPoint->coords.x,
psRepairFac->psDeliveryPoint->coords.y, ModeQueue); // ModeQueue because delivery points are not yet synchronised!
}
}
}
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, gameTime - deltaGameTime + 1);
}
}
}
//check for rearming
else if (structureMode == REF_REARM_PAD)
{
REARM_PAD *psReArmPad = &psStructure->pFunctionality->rearmPad;
UDWORD pointsAlreadyAdded;
psDroid = (DROID *)psChosenObj;
ASSERT_OR_RETURN( , psDroid != NULL, "invalid droid pointer");
ASSERT_OR_RETURN( , isVtolDroid(psDroid), "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 and last updated
psReArmPad->timeStarted = gameTime;
psReArmPad->timeLastUpdated = gameTime;
}
/* do rearming */
UDWORD pointsRequired;
//amount required is a factor of the droids' weight
pointsRequired = psDroid->weight / REARM_FACTOR;
//take numWeaps into consideration
pointsToAdd = psReArmPad->reArmPoints * (gameTime - psReArmPad->timeStarted) / GAME_TICKS_PER_SEC;
pointsAlreadyAdded = psReArmPad->reArmPoints * (psReArmPad->timeLastUpdated - psReArmPad->timeStarted) / GAME_TICKS_PER_SEC;
if (pointsToAdd >= pointsRequired)
{
// We should be fully loaded by now.
for (i = 0; i < psDroid->numWeaps; i++)
{
// set rearm value to no runs made
psDroid->asWeaps[i].usedAmmo = 0;
// reset ammo and lastFired
psDroid->asWeaps[i].ammo = asWeaponStats[psDroid->asWeaps[i].nStat].numRounds;
psDroid->asWeaps[i].lastFired = 0;
}
}
else
{
for (i = 0; i < psDroid->numWeaps; i++)
{
// Make sure it's a rearmable weapon (and so we don't divide by zero)
if (psDroid->asWeaps[i].usedAmmo > 0 && asWeaponStats[psDroid->asWeaps[i].nStat].numRounds > 0)
{
// Do not "simplify" this formula.
// It is written this way to prevent rounding errors.
int ammoToAddThisTime =
pointsToAdd*getNumAttackRuns(psDroid,i)/pointsRequired -
pointsAlreadyAdded*getNumAttackRuns(psDroid,i)/pointsRequired;
psDroid->asWeaps[i].usedAmmo -= std::min<unsigned>(ammoToAddThisTime, psDroid->asWeaps[i].usedAmmo);
if (ammoToAddThisTime)
{
// reset ammo and lastFired
psDroid->asWeaps[i].ammo = asWeaponStats[psDroid->asWeaps[i].nStat].numRounds;
psDroid->asWeaps[i].lastFired = 0;
break;
}
}
}
}
/* do repairing */
if (psDroid->body < psDroid->originalBody)
{
// Do not "simplify" this formula.
// It is written this way to prevent rounding errors.
pointsToAdd = VTOL_REPAIR_FACTOR * (100+asReArmUpgrade[psStructure->player].modifier) * (gameTime -
psReArmPad->timeStarted) / (GAME_TICKS_PER_SEC * 100);
pointsAlreadyAdded = VTOL_REPAIR_FACTOR * (100+asReArmUpgrade[psStructure->player].modifier) * (psReArmPad->timeLastUpdated -
psReArmPad->timeStarted) / (GAME_TICKS_PER_SEC * 100);
if ((pointsToAdd - pointsAlreadyAdded) > 0)
{
psDroid->body += (pointsToAdd - pointsAlreadyAdded);
}
if (psDroid->body >= psDroid->originalBody)
{
/* set droid points to max */
psDroid->body = psDroid->originalBody;
}
}
psReArmPad->timeLastUpdated = gameTime;
//check for fully armed and fully repaired
if (vtolHappy(psDroid))
{
//clear the rearm pad
psDroid->action = DACTION_NONE;
psReArmPad->psObj = NULL;
auxStructureNonblocking(psStructure);
triggerEventDroidIdle(psDroid);
}
}
}
}
}
/** 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
|| psStruct->status == SS_BEING_BUILT || psStruct->pStructureType->type == REF_GATE)
{
return(false);
}
else
{
return(true);
}
}
static float CalcStructureSmokeInterval(float damage)
{
return (((1. - damage) + 0.1) * 10) * STRUCTURE_DAMAGE_SCALING;
}
void _syncDebugStructure(const char *function, STRUCTURE const *psStruct, char ch)
{
int ref = 0;
int refChr = ' ';
// Print what the structure is producing, too.
switch (psStruct->pStructureType->type)
{
case REF_RESEARCH:
if (psStruct->pFunctionality->researchFacility.psSubject != NULL)
{
ref = psStruct->pFunctionality->researchFacility.psSubject->ref;
refChr = 'r';
}
break;
case REF_FACTORY:
case REF_CYBORG_FACTORY:
case REF_VTOL_FACTORY:
if (psStruct->pFunctionality->factory.psSubject != NULL)
{
ref = psStruct->pFunctionality->factory.psSubject->multiPlayerID;
refChr = 'p';
}
break;
default:
break;
}
int list[] =
{
ch,
(int)psStruct->id,
psStruct->player,
psStruct->pos.x, psStruct->pos.y, psStruct->pos.z,
(int)psStruct->status,
(int)psStruct->pStructureType->type, refChr, ref,
psStruct->currentBuildPts,
(int)psStruct->body,
};
_syncDebugIntList(function, "%c structure%d = p%d;pos(%d,%d,%d),status%d,type%d,%c%.0d,bld%d,body%d", list, ARRAY_SIZE(list));
}
int requestOpenGate(STRUCTURE *psStructure)
{
if (psStructure->status != SS_BUILT || psStructure->pStructureType->type != REF_GATE)
{
return 0; // Can't open.
}
switch (psStructure->state)
{
case SAS_NORMAL:
psStructure->lastStateTime = gameTime;
psStructure->state = SAS_OPENING;
break;
case SAS_OPEN:
psStructure->lastStateTime = gameTime;
return 0; // Already open.
case SAS_OPENING:
break;
case SAS_CLOSING:
psStructure->lastStateTime = 2*gameTime - psStructure->lastStateTime - SAS_OPEN_SPEED;
psStructure->state = SAS_OPENING;
default:
return 0; // Unknown state...
}
return psStructure->lastStateTime + SAS_OPEN_SPEED - gameTime;
}
int gateCurrentOpenHeight(STRUCTURE const *psStructure, uint32_t time, int minimumStub)
{
STRUCTURE_STATS const *psStructureStats = psStructure->pStructureType;
if (psStructureStats->type == REF_GATE)
{
int height = psStructure->sDisplay.imd->max.y;
int openHeight;
switch (psStructure->state)
{
case SAS_OPEN:
openHeight = height;
break;
case SAS_OPENING:
openHeight = (height * std::max<int>(time + GAME_TICKS_PER_UPDATE - psStructure->lastStateTime, 0)) / SAS_OPEN_SPEED;
break;
case SAS_CLOSING:
openHeight = height - (height * std::max<int>(time - psStructure->lastStateTime, 0)) / SAS_OPEN_SPEED;
break;
default:
return 0;
}
return std::max(std::min(openHeight, height - minimumStub), 0);
}
return 0;
}
/* The main update routine for all Structures */
void structureUpdate(STRUCTURE *psBuilding, bool mission)
{
UDWORD widthScatter,breadthScatter;
UDWORD emissionInterval, iPointsToAdd, iPointsRequired;
Vector3i dv;
int i;
syncDebugStructure(psBuilding, '<');
if (psBuilding->pStructureType->type == REF_GATE)
{
if (psBuilding->state == SAS_OPEN && psBuilding->lastStateTime + SAS_STAY_OPEN_TIME < gameTime)
{
bool found = false;
BASE_OBJECT *psObj;
gridStartIterate(psBuilding->pos.x, psBuilding->pos.y, TILE_UNITS);
while (!found && (psObj = gridIterate()))
{
found = (psObj->type == OBJ_DROID);
}
if (!found) // no droids on our tile, safe to close
{
psBuilding->state = SAS_CLOSING;
auxStructureClosedGate(psBuilding); // closed
psBuilding->lastStateTime = gameTime; // reset timer
}
}
else if (psBuilding->state == SAS_OPENING && psBuilding->lastStateTime + SAS_OPEN_SPEED < gameTime)
{
psBuilding->state = SAS_OPEN;
auxStructureOpenGate(psBuilding); // opened
psBuilding->lastStateTime = gameTime; // reset timer
}
else if (psBuilding->state == SAS_CLOSING && psBuilding->lastStateTime + SAS_OPEN_SPEED < gameTime)
{
psBuilding->state = SAS_NORMAL;
psBuilding->lastStateTime = gameTime; // reset timer
}
}
// Remove invalid targets. This must be done each frame.
for (i = 0; i < STRUCT_MAXWEAPS; i++)
{
if (psBuilding->psTarget[i] && psBuilding->psTarget[i]->died)
{
setStructureTarget(psBuilding, NULL, i, ORIGIN_UNKNOWN);
}
}
//update the manufacture/research of the building once complete
if (psBuilding->status == SS_BUILT)
{
aiUpdateStructure(psBuilding, mission);
}
if(psBuilding->status!=SS_BUILT)
{
if(psBuilding->selected)
{
psBuilding->selected = false;
}
}
if (psBuilding->status == SS_BEING_BUILT && psBuilding->buildRate == 0 && !structureHasModules(psBuilding))
{
if (psBuilding->pStructureType->powerToBuild == 0)
{
// Building is free, and not currently being built, so deconstruct slowly over 1 minute.
psBuilding->currentBuildPts -= std::min<int>(psBuilding->currentBuildPts, gameTimeAdjustedAverage(psBuilding->pStructureType->buildPoints, 60));
}
if (psBuilding->currentBuildPts == 0)
{
removeStruct(psBuilding, true); // If giving up on building something, remove the structure (and remove it from the power queue).
}
}
psBuilding->lastBuildRate = psBuilding->buildRate;
psBuilding->buildRate = 0; // Reset to 0, each truck building us will add to our buildRate.
/* Only add smoke if they're visible and they can 'burn' */
if (!mission && psBuilding->visible[selectedPlayer] && canSmoke(psBuilding))
{
const int32_t damage = getStructureDamage(psBuilding);
// Is there any damage?
if (damage > 0.)
{
emissionInterval = CalcStructureSmokeInterval(damage/65536.f);
unsigned effectTime = std::max(gameTime - deltaGameTime + 1, psBuilding->lastEmission + emissionInterval);
if (gameTime >= effectTime)
{
widthScatter = getStructureWidth(psBuilding) * TILE_UNITS/2/3;
breadthScatter = getStructureBreadth(psBuilding) * 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, effectTime);
psBuilding->lastEmission = effectTime;
}
}
}
if (!mission)
{
processVisibilityLevel(psBuilding);
}
/* Update the fire damage data */
if (psBuilding->burnStart != 0 && psBuilding->burnStart != gameTime - deltaGameTime) // -deltaGameTime, since projectiles are updated after structures.
{
// The burnStart has been set, but is not from the previous tick, so we must be out of the fire.
psBuilding->burnDamage = 0; // Reset burn damage done this tick.
// Finished burning.
psBuilding->burnStart = 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 && !mission)
{
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, gameTime - deltaGameTime + rand()%deltaGameTime);
}
if (iPointsToAdd)
{
psBuilding->body = (UWORD)(psBuilding->body + iPointsToAdd);
psBuilding->lastResistance = gameTime;
if ( psBuilding->body > iPointsRequired)
{
psBuilding->body = (UWORD)iPointsRequired;
psBuilding->lastResistance = ACTION_START_TIME;
}
}
}
}
syncDebugStructure(psBuilding, '>');
CHECK_STRUCTURE(psBuilding);
}
STRUCTURE::STRUCTURE(uint32_t id, unsigned player)
: BASE_OBJECT(OBJ_STRUCTURE, id, player)
, pFunctionality(NULL)
, buildRate(1) // Initialise to 1 instead of 0, to make sure we don't get destroyed first tick due to inactivity.
, lastBuildRate(0)
, psCurAnim(NULL)
, prebuiltImd(NULL)
{}
/* Release all resources associated with a structure */
STRUCTURE::~STRUCTURE()
{
STRUCTURE *psBuilding = this;
/* 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;
}
enum STRUCTURE_PACKABILITY
{
PACKABILITY_EMPTY = 0, PACKABILITY_DEFENSE = 1, PACKABILITY_NORMAL = 2, PACKABILITY_REPAIR = 3
};
static inline bool canPack(STRUCTURE_PACKABILITY a, STRUCTURE_PACKABILITY b)
{
return (int)a + (int)b <= 3; // Defense can be put next to anything except repair facilities, normal base structures can't be put next to each other, and anything goes next to empty tiles.
}
static STRUCTURE_PACKABILITY baseStructureTypePackability(STRUCTURE_TYPE type)
{
switch (type)
{
case REF_DEFENSE:
case REF_WALL:
case REF_WALLCORNER:
case REF_GATE:
case REF_REARM_PAD:
case REF_MISSILE_SILO:
return PACKABILITY_DEFENSE;
default:
return PACKABILITY_NORMAL;
case REF_REPAIR_FACILITY:
return PACKABILITY_REPAIR;
}
}
static STRUCTURE_PACKABILITY baseObjectPackability(BASE_OBJECT *psObject)
{
if (psObject == NULL)
{
return PACKABILITY_EMPTY;
}
switch (psObject->type)
{
case OBJ_STRUCTURE: return baseStructureTypePackability(((STRUCTURE *)psObject)->pStructureType->type);
case OBJ_FEATURE: return ((FEATURE *)psObject)->psStats->subType == FEAT_OIL_RESOURCE? PACKABILITY_NORMAL : PACKABILITY_EMPTY;
default: return PACKABILITY_EMPTY;
}
}
bool isBlueprintTooClose(STRUCTURE_STATS const *stats1, Vector2i pos1, uint16_t dir1, STRUCTURE_STATS const *stats2, Vector2i pos2, uint16_t dir2)
{
if (stats1 == stats2 && pos1 == pos2 && dir1 == dir2)
{
return false; // Same blueprint, so ignore it.
}
bool packable = canPack(baseStructureTypePackability(stats1->type), baseStructureTypePackability(stats2->type));
int minDist = packable? 0 : 1;
StructureBounds b1 = getStructureBounds(stats1, pos1, dir1);
StructureBounds b2 = getStructureBounds(stats2, pos2, dir2);
Vector2i delta12 = b2.map - (b1.map + b1.size);
Vector2i delta21 = b1.map - (b2.map + b2.size);
int dist = std::max(std::max(delta12.x, delta21.x), std::max(delta12.y, delta21.y));
return dist < minDist;
}
bool validLocation(BASE_STATS *psStats, Vector2i pos, uint16_t direction, unsigned player, bool bCheckBuildQueue)
{
STRUCTURE_STATS * psBuilding = NULL;
DROID_TEMPLATE * psTemplate = NULL;
StructureBounds b = getStructureBounds(psStats, pos, direction);
if (psStats->ref >= REF_STRUCTURE_START && psStats->ref < REF_STRUCTURE_START + REF_RANGE)
{
psBuilding = (STRUCTURE_STATS *)psStats; // Is a structure.
}
if (psStats->ref >= REF_TEMPLATE_START && psStats->ref < REF_TEMPLATE_START + REF_RANGE)
{
psTemplate = (DROID_TEMPLATE *)psStats; // Is a template.
}
if (psBuilding != NULL)
{
//if we're dragging the wall/defense we need to check along the current dragged size
if (wallDrag.status != DRAG_INACTIVE && bCheckBuildQueue
&& (psBuilding->type == REF_WALL || psBuilding->type == REF_DEFENSE || psBuilding->type == REF_REARM_PAD || psBuilding->type == REF_GATE)
&& !isLasSat(psBuilding))
{
wallDrag.x2 = mouseTileX; // Why must this be done here? If not doing it here, dragging works almost normally, except it suddenly stops working if the drag becomes invalid.
wallDrag.y2 = mouseTileY;
int dx = abs(wallDrag.x2 - wallDrag.x1);
int dy = abs(wallDrag.y2 - wallDrag.y1);
if (dx >= dy)
{
//build in x direction
wallDrag.y2 = wallDrag.y1;
}
else
{
//build in y direction
wallDrag.x2 = wallDrag.x1;
}
b.map.x = std::min(wallDrag.x1, wallDrag.x2);
b.map.y = std::min(wallDrag.y1, wallDrag.y2);
b.size.x = std::max(wallDrag.x1, wallDrag.x2) + 1 - b.map.x;
b.size.y = std::max(wallDrag.y1, wallDrag.y2) + 1 - b.map.y;
}
}
//make sure we are not too near map edge and not going to go over it
if (b.map.x < scrollMinX + TOO_NEAR_EDGE || b.map.x + b.size.x > scrollMaxX - TOO_NEAR_EDGE ||
b.map.y < scrollMinY + TOO_NEAR_EDGE || b.map.y + b.size.y > scrollMaxY - TOO_NEAR_EDGE)
{
return false;
}
if (bCheckBuildQueue)
{
// cant place on top of a delivery point...
for (FLAG_POSITION const *psCurrFlag = apsFlagPosLists[selectedPlayer]; psCurrFlag; psCurrFlag = psCurrFlag->psNext)
{
ASSERT_OR_RETURN(false, psCurrFlag->coords.x != ~0, "flag has invalid position");
Vector2i flagTile = map_coord(removeZ(psCurrFlag->coords));
if (flagTile.x >= b.map.x && flagTile.x < b.map.x + b.size.x && flagTile.y >= b.map.y && flagTile.y < b.map.y + b.size.y)
{
return false;
}
}
}
if (psBuilding != NULL)
{
for (int j = 0; j < b.size.y; ++j)
for (int i = 0; i < b.size.x; ++i)
{
// Don't allow building structures (allow delivery points, though) outside visible area in single-player with debug mode off. (Why..?)
if (!bMultiPlayer && !getDebugMappingStatus() && !TEST_TILE_VISIBLE(player, mapTile(b.map.x + i, b.map.y + j)))
{
return false;
}
}
switch(psBuilding->type)
{
case REF_DEMOLISH:
break;
case NUM_DIFF_BUILDINGS:
case REF_BRIDGE:
ASSERT(!"invalid structure type", "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_GATE:
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 (int j = 0; j < b.size.y; ++j)
for (int i = 0; i < b.size.x; ++i)
{
MAPTILE const *psTile = mapTile(b.map.x + i, b.map.y + j);
if ((terrainType(psTile) == TER_WATER) ||
(terrainType(psTile) == TER_CLIFFFACE) )
{
return false;
}
}
//check not within landing zone
for (int j = 0; j < b.size.y; ++j)
for (int i = 0; i < b.size.x; ++i)
{
if (withinLandingZone(b.map.x + i, b.map.y + j))
{
return false;
}
}
//walls/defensive structures can be built along any ground
if (!(psBuilding->type == REF_REPAIR_FACILITY ||
psBuilding->type == REF_DEFENSE ||
psBuilding->type == REF_GATE ||
psBuilding->type == REF_WALL))
{
/*cannot build on ground that is too steep*/
for (int j = 0; j < b.size.y; ++j)
for (int i = 0; i < b.size.x; ++i)
{
int max, min;
getTileMaxMin(b.map.x + i, b.map.y + j, &max, &min);
if (max - min > MAX_INCLINE)
{
return false;
}
}
}
//don't bother checking if already found a problem
STRUCTURE_PACKABILITY packThis = baseStructureTypePackability(psBuilding->type);
// skirmish AIs don't build nondefensives next to anything. (route hack)
if (packThis == PACKABILITY_NORMAL && bMultiPlayer && game.type == SKIRMISH && !isHumanPlayer(player))
{
packThis = PACKABILITY_REPAIR;
}
/* need to check there is one tile between buildings */
for (int j = -1; j < b.size.y + 1; ++j)
for (int i = -1; i < b.size.x + 1; ++i)
{
//skip the actual area the structure will cover
if (i < 0 || i >= b.size.x || j < 0 || j >= b.size.y)
{
STRUCTURE_PACKABILITY packObj = baseObjectPackability(mapTile(b.map.x + i, b.map.y + j)->psObject);
if (!canPack(packThis, packObj))
{
return false;
}
}
}
/*need to check each tile the structure will sit on*/
for (int j = 0; j < b.size.y; ++j)
for (int i = 0; i < b.size.x; ++i)
{
MAPTILE const *psTile = mapTile(b.map.x + i, b.map.y + j);
if (TileIsOccupied(psTile))
{
if (TileHasWall(psTile) && (psBuilding->type == REF_DEFENSE || psBuilding->type == REF_GATE || psBuilding->type == REF_WALL))
{
STRUCTURE const *psStruct = getTileStructure(b.map.x + i, b.map.y + j);
if (psStruct != NULL && psStruct->player != player)
{
return false;
}
}
else
{
return false;
}
}
}
break;
}
case REF_FACTORY_MODULE:
if (TileHasStructure(worldTile(pos)))
{
STRUCTURE const *psStruct = getTileStructure(map_coord(pos.x), map_coord(pos.y));
if (psStruct && (psStruct->pStructureType->type == REF_FACTORY ||
psStruct->pStructureType->type == REF_VTOL_FACTORY) &&
psStruct->status == SS_BUILT)
{
break;
}
}
return false;
case REF_RESEARCH_MODULE:
if (TileHasStructure(worldTile(pos)))
{
STRUCTURE const *psStruct = getTileStructure(map_coord(pos.x), map_coord(pos.y));
if (psStruct && psStruct->pStructureType->type == REF_RESEARCH &&
psStruct->status == SS_BUILT)
{
break;
}
}
return false;
case REF_POWER_MODULE:
if (TileHasStructure(worldTile(pos)))
{
STRUCTURE const *psStruct = getTileStructure(map_coord(pos.x), map_coord(pos.y));
if (psStruct && psStruct->pStructureType->type == REF_POWER_GEN &&
psStruct->status == SS_BUILT)
{
break;
}
}
return false;