2072 lines
57 KiB
C++
2072 lines
57 KiB
C++
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 1999-2004 Eidos Interactive
|
|
Copyright (C) 2005-2013 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 stats.c
|
|
*
|
|
* Store common stats for weapons, components, brains, etc.
|
|
*
|
|
*/
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
#include <QtCore/QHash>
|
|
|
|
#include "lib/framework/frame.h"
|
|
#include "lib/framework/strres.h"
|
|
#include "lib/framework/frameresource.h"
|
|
#include "lib/framework/wzconfig.h"
|
|
#include "lib/gamelib/gtime.h"
|
|
#include "objects.h"
|
|
#include "stats.h"
|
|
#include "map.h"
|
|
#include "main.h"
|
|
#include "lib/sound/audio_id.h"
|
|
#include "projectile.h"
|
|
#include "text.h"
|
|
|
|
#define WEAPON_TIME 100
|
|
|
|
/* The stores for the different stats */
|
|
BODY_STATS *asBodyStats;
|
|
BRAIN_STATS *asBrainStats;
|
|
PROPULSION_STATS *asPropulsionStats;
|
|
SENSOR_STATS *asSensorStats;
|
|
ECM_STATS *asECMStats;
|
|
REPAIR_STATS *asRepairStats;
|
|
WEAPON_STATS *asWeaponStats;
|
|
CONSTRUCT_STATS *asConstructStats;
|
|
PROPULSION_TYPES *asPropulsionTypes;
|
|
static int *asTerrainTable;
|
|
|
|
//used to hold the modifiers cross refd by weapon effect and propulsion type
|
|
WEAPON_MODIFIER asWeaponModifier[WE_NUMEFFECTS][PROPULSION_TYPE_NUM];
|
|
WEAPON_MODIFIER asWeaponModifierBody[WE_NUMEFFECTS][SIZE_NUM];
|
|
|
|
/* The number of different stats stored */
|
|
UDWORD numBodyStats;
|
|
UDWORD numBrainStats;
|
|
UDWORD numPropulsionStats;
|
|
UDWORD numSensorStats;
|
|
UDWORD numECMStats;
|
|
UDWORD numRepairStats;
|
|
UDWORD numWeaponStats;
|
|
UDWORD numConstructStats;
|
|
|
|
//the max values of the stats used in the design screen
|
|
static UDWORD maxComponentWeight;
|
|
static UDWORD maxBodyArmour;
|
|
static UDWORD maxBodyPower;
|
|
static UDWORD maxBodyPoints;
|
|
static UDWORD maxSensorRange;
|
|
static UDWORD maxECMRange;
|
|
static UDWORD maxConstPoints;
|
|
static UDWORD maxRepairPoints;
|
|
static UDWORD maxWeaponRange;
|
|
static UDWORD maxWeaponDamage;
|
|
static UDWORD maxWeaponROF;
|
|
static UDWORD maxPropulsionSpeed;
|
|
|
|
//stores for each players component states - can be either UNAVAILABLE, REDUNDANT, FOUND or AVAILABLE
|
|
UBYTE *apCompLists[MAX_PLAYERS][COMP_NUMCOMPONENTS];
|
|
|
|
//store for each players Structure states
|
|
UBYTE *apStructTypeLists[MAX_PLAYERS];
|
|
|
|
QHash<QString, COMPONENT_STATS *> lookupStatPtr;
|
|
|
|
static bool getMovementModel(const char *movementModel, MOVEMENT_MODEL *model);
|
|
static bool statsGetAudioIDFromString(const QString &szStatName, const QString &szWavName, int *piWavID);
|
|
|
|
//Access functions for the max values to be used in the Design Screen
|
|
static void setMaxComponentWeight(UDWORD weight);
|
|
static void setMaxBodyArmour(UDWORD armour);
|
|
static void setMaxBodyPower(UDWORD power);
|
|
static void setMaxBodyPoints(UDWORD points);
|
|
static void setMaxSensorRange(UDWORD range);
|
|
static void setMaxECMRange(UDWORD power);
|
|
static void setMaxConstPoints(UDWORD points);
|
|
static void setMaxRepairPoints(UDWORD repair);
|
|
static void setMaxWeaponRange(UDWORD range);
|
|
static void setMaxWeaponDamage(UDWORD damage);
|
|
static void setMaxWeaponROF(UDWORD rof);
|
|
static void setMaxPropulsionSpeed(UDWORD speed);
|
|
|
|
//determine the effect this upgrade would have on the max values
|
|
static void updateMaxWeaponStats(UWORD maxValue);
|
|
static void updateMaxSensorStats(UWORD maxRange);
|
|
static void updateMaxRepairStats(UWORD maxValue);
|
|
static void updateMaxECMStats(UWORD maxValue);
|
|
static void updateMaxBodyStats(UWORD maxBody, UWORD maxPower, UWORD maxArmour);
|
|
static void updateMaxConstStats(UWORD maxValue);
|
|
|
|
static inline bool stringToEnumFindFunction(std::pair<char const *, unsigned> const &a, char const *b)
|
|
{
|
|
return strcmp(a.first, b) < 0;
|
|
}
|
|
|
|
/***********************************************************************************
|
|
* Dealloc the extra storage tables
|
|
***********************************************************************************/
|
|
//Deallocate the storage assigned for the Propulsion Types table
|
|
static void deallocPropulsionTypes(void)
|
|
{
|
|
free(asPropulsionTypes);
|
|
asPropulsionTypes = NULL;
|
|
}
|
|
|
|
//dealloc the storage assigned for the terrain table
|
|
static void deallocTerrainTable(void)
|
|
{
|
|
free(asTerrainTable);
|
|
asTerrainTable = NULL;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Generic stats macros/functions
|
|
*******************************************************************************/
|
|
|
|
/* Macro to allocate memory for a set of stats */
|
|
#define ALLOC_STATS(numEntries, list, listSize, type) \
|
|
ASSERT((numEntries) < REF_RANGE, "Number of stats entries too large for " #type);\
|
|
if ((list)) delete [] (list); \
|
|
(list) = new type[numEntries]; \
|
|
(listSize) = (numEntries); \
|
|
return true
|
|
|
|
/*Macro to Deallocate stats*/
|
|
#define STATS_DEALLOC(list, listSize) \
|
|
delete [] (list); \
|
|
listSize = 0; \
|
|
(list) = NULL
|
|
|
|
void statsInitVars(void)
|
|
{
|
|
/* The number of different stats stored */
|
|
numBodyStats = 0;
|
|
numBrainStats = 0;
|
|
numPropulsionStats = 0;
|
|
numSensorStats = 0;
|
|
numECMStats = 0;
|
|
numRepairStats = 0;
|
|
numWeaponStats = 0;
|
|
numConstructStats = 0;
|
|
|
|
// init the max values
|
|
maxComponentWeight = maxBodyArmour = maxBodyPower =
|
|
maxBodyPoints = maxSensorRange = maxECMRange =
|
|
maxConstPoints = maxRepairPoints = maxWeaponRange = maxWeaponDamage =
|
|
maxPropulsionSpeed = 0;
|
|
}
|
|
|
|
/*Deallocate all the stats assigned from input data*/
|
|
bool statsShutDown(void)
|
|
{
|
|
lookupStatPtr.clear();
|
|
|
|
STATS_DEALLOC(asWeaponStats, numWeaponStats);
|
|
STATS_DEALLOC(asBrainStats, numBrainStats);
|
|
STATS_DEALLOC(asPropulsionStats, numPropulsionStats);
|
|
STATS_DEALLOC(asRepairStats, numRepairStats);
|
|
STATS_DEALLOC(asConstructStats, numConstructStats);
|
|
STATS_DEALLOC(asECMStats, numECMStats);
|
|
STATS_DEALLOC(asSensorStats, numSensorStats);
|
|
STATS_DEALLOC(asBodyStats, numBodyStats);
|
|
deallocPropulsionTypes();
|
|
deallocTerrainTable();
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Return the number of newlines in a file buffer */
|
|
UDWORD numCR(const char *pFileBuffer, UDWORD fileSize)
|
|
{
|
|
UDWORD lines = 0;
|
|
|
|
while (fileSize-- > 0)
|
|
{
|
|
if (*pFileBuffer++ == '\n')
|
|
{
|
|
lines++;
|
|
}
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Allocate stats functions
|
|
*******************************************************************************/
|
|
/* Allocate Weapon stats */
|
|
bool statsAllocWeapons(UDWORD numStats)
|
|
{
|
|
ALLOC_STATS(numStats, asWeaponStats, numWeaponStats, WEAPON_STATS);
|
|
}
|
|
/* Allocate Body Stats */
|
|
bool statsAllocBody(UDWORD numStats)
|
|
{
|
|
ALLOC_STATS(numStats, asBodyStats, numBodyStats, BODY_STATS);
|
|
}
|
|
/* Allocate Brain Stats */
|
|
bool statsAllocBrain(UDWORD numStats)
|
|
{
|
|
ALLOC_STATS(numStats, asBrainStats, numBrainStats, BRAIN_STATS);
|
|
}
|
|
/* Allocate Propulsion Stats */
|
|
bool statsAllocPropulsion(UDWORD numStats)
|
|
{
|
|
ALLOC_STATS(numStats, asPropulsionStats, numPropulsionStats, PROPULSION_STATS);
|
|
}
|
|
/* Allocate Sensor Stats */
|
|
bool statsAllocSensor(UDWORD numStats)
|
|
{
|
|
ALLOC_STATS(numStats, asSensorStats, numSensorStats, SENSOR_STATS);
|
|
}
|
|
/* Allocate Ecm Stats */
|
|
bool statsAllocECM(UDWORD numStats)
|
|
{
|
|
ALLOC_STATS(numStats, asECMStats, numECMStats, ECM_STATS);
|
|
}
|
|
|
|
/* Allocate Repair Stats */
|
|
bool statsAllocRepair(UDWORD numStats)
|
|
{
|
|
ALLOC_STATS(numStats, asRepairStats, numRepairStats, REPAIR_STATS);
|
|
}
|
|
|
|
/* Allocate Construct Stats */
|
|
bool statsAllocConstruct(UDWORD numStats)
|
|
{
|
|
ALLOC_STATS(numStats, asConstructStats, numConstructStats, CONSTRUCT_STATS);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Load stats functions
|
|
*******************************************************************************/
|
|
|
|
static iIMDShape *statsGetIMD(WzConfig &ini, BASE_STATS *psStats, QString key, int index = 0)
|
|
{
|
|
iIMDShape *retval = NULL;
|
|
if (ini.contains(key))
|
|
{
|
|
QStringList values = ini.value(key).toStringList();
|
|
if (values.size() > index && values[index].compare("0") != 0)
|
|
{
|
|
retval = modelGet(values[index]);
|
|
ASSERT(retval != NULL, "Cannot find the PIE model %s for stat %s in %s",
|
|
values[index].toUtf8().constData(), getName(psStats), ini.fileName().toUtf8().constData());
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
void loadCompStats(WzConfig &ini, COMPONENT_STATS *psStats, int index)
|
|
{
|
|
psStats->name = ini.value("name").toString();
|
|
psStats->id = ini.group();
|
|
psStats->buildPower = ini.value("buildPower", 0).toUInt();
|
|
psStats->buildPoints = ini.value("buildPoints", 0).toUInt();
|
|
psStats->index = index;
|
|
ASSERT(!lookupStatPtr.contains(psStats->id), "Duplicate ID found! (%s)", getID(psStats));
|
|
lookupStatPtr.insert(psStats->id, psStats);
|
|
}
|
|
|
|
/*Load the weapon stats from the file exported from Access*/
|
|
bool loadWeaponStats(const char *pFileName)
|
|
{
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
statsAllocWeapons(list.size());
|
|
// Hack to make sure ZNULLWEAPON is always first in list
|
|
int nullweapon = list.indexOf("ZNULLWEAPON");
|
|
ASSERT_OR_RETURN(false, nullweapon >= 0, "ZNULLWEAPON is mandatory");
|
|
list.swap(nullweapon, 0);
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
WEAPON_STATS *psStats = &asWeaponStats[i];
|
|
QStringList flags;
|
|
|
|
ini.beginGroup(list[i]);
|
|
loadCompStats(ini, psStats, i);
|
|
psStats->compType = COMP_WEAPON;
|
|
|
|
psStats->weight = ini.value("weight", 0).toUInt();
|
|
psStats->body = ini.value("hitpoints", 0).toUInt();
|
|
psStats->radiusLife = ini.value("radiusLife", 0).toUInt();
|
|
|
|
psStats->base.maxRange = ini.value("longRange").toUInt();
|
|
psStats->base.minRange = ini.value("minRange", 0).toUInt();
|
|
psStats->base.hitChance = ini.value("longHit", 100).toUInt();
|
|
psStats->base.firePause = ini.value("firePause").toUInt();
|
|
psStats->base.numRounds = ini.value("numRounds").toUInt();
|
|
psStats->base.reloadTime = ini.value("reloadTime").toUInt();
|
|
psStats->base.damage = ini.value("damage").toUInt();
|
|
psStats->base.minimumDamage = ini.value("minimumDamage", 0).toInt();
|
|
psStats->base.radius = ini.value("radius", 0).toUInt();
|
|
psStats->base.radiusDamage = ini.value("radiusDamage", 0).toUInt();
|
|
psStats->base.periodicalDamageTime = ini.value("periodicalDamageTime", 0).toUInt();
|
|
psStats->base.periodicalDamage = ini.value("periodicalDamage", 0).toUInt();
|
|
psStats->base.periodicalDamageRadius = ini.value("periodicalDamageRadius", 0).toUInt();
|
|
// multiply time stats
|
|
psStats->base.firePause *= WEAPON_TIME;
|
|
psStats->base.periodicalDamageTime *= WEAPON_TIME;
|
|
psStats->radiusLife *= WEAPON_TIME;
|
|
psStats->base.reloadTime *= WEAPON_TIME;
|
|
// copy for upgrades
|
|
for (int j = 0; j < MAX_PLAYERS; j++)
|
|
{
|
|
psStats->upgrade[j] = psStats->base;
|
|
}
|
|
|
|
psStats->numExplosions = ini.value("numExplosions").toUInt();
|
|
psStats->flightSpeed = ini.value("flightSpeed", 1).toUInt();
|
|
psStats->rotate = ini.value("rotate").toUInt();
|
|
psStats->minElevation = ini.value("minElevation").toInt();
|
|
psStats->maxElevation = ini.value("maxElevation").toInt();
|
|
psStats->recoilValue = ini.value("recoilValue").toUInt();
|
|
psStats->effectSize = ini.value("effectSize").toUInt();
|
|
flags = ini.value("flags", 0).toStringList();
|
|
psStats->vtolAttackRuns = ini.value("numAttackRuns", 0).toUInt();
|
|
psStats->designable = ini.value("designable").toBool();
|
|
psStats->penetrate = ini.value("penetrate", false).toBool();
|
|
// weapon size limitation
|
|
int weaponSize = ini.value("weaponSize", WEAPON_SIZE_ANY).toInt();
|
|
ASSERT(weaponSize <= WEAPON_SIZE_ANY, "Bad weapon size for %s", list[i].toUtf8().constData());
|
|
psStats->weaponSize = (WEAPON_SIZE)weaponSize;
|
|
|
|
ASSERT(psStats->flightSpeed > 0, "Invalid flight speed for %s", list[i].toUtf8().constData());
|
|
|
|
psStats->ref = REF_WEAPON_START + i;
|
|
|
|
//get the IMD for the component
|
|
psStats->pIMD = statsGetIMD(ini, psStats, "model");
|
|
psStats->pMountGraphic = statsGetIMD(ini, psStats, "mountModel");
|
|
if (GetGameMode() == GS_NORMAL)
|
|
{
|
|
psStats->pMuzzleGraphic = statsGetIMD(ini, psStats, "muzzleGfx");
|
|
psStats->pInFlightGraphic = statsGetIMD(ini, psStats, "flightGfx");
|
|
psStats->pTargetHitGraphic = statsGetIMD(ini, psStats, "hitGfx");
|
|
psStats->pTargetMissGraphic = statsGetIMD(ini, psStats, "missGfx");
|
|
psStats->pWaterHitGraphic = statsGetIMD(ini, psStats, "waterGfx");
|
|
psStats->pTrailGraphic = statsGetIMD(ini, psStats, "trailGfx");
|
|
}
|
|
psStats->fireOnMove = ini.value("fireOnMove", true).toBool();
|
|
|
|
//set the weapon class
|
|
if (!getWeaponClass(ini.value("weaponClass").toString(), &psStats->weaponClass))
|
|
{
|
|
debug(LOG_ERROR, "Invalid weapon class for weapon %s - assuming KINETIC", getName(psStats));
|
|
psStats->weaponClass = WC_KINETIC;
|
|
}
|
|
|
|
//set the subClass
|
|
if (!getWeaponSubClass(ini.value("weaponSubClass").toString().toUtf8().data(), &psStats->weaponSubClass))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// set max extra weapon range on misses, make this modifiable one day by mod makers
|
|
if (psStats->weaponSubClass == WSC_MGUN || psStats->weaponSubClass == WSC_COMMAND)
|
|
{
|
|
psStats->distanceExtensionFactor = 120;
|
|
}
|
|
else if (psStats->weaponSubClass == WSC_AAGUN)
|
|
{
|
|
psStats->distanceExtensionFactor = 100;
|
|
}
|
|
else // default
|
|
{
|
|
psStats->distanceExtensionFactor = 150;
|
|
}
|
|
|
|
//set the weapon effect
|
|
if (!getWeaponEffect(ini.value("weaponEffect").toString().toUtf8().constData(), &psStats->weaponEffect))
|
|
{
|
|
debug(LOG_FATAL, "loadWepaonStats: Invalid weapon effect for weapon %s", getName(psStats));
|
|
return false;
|
|
}
|
|
|
|
//set periodical damage weapon class
|
|
QString periodicalDamageWeaponClass = ini.value("periodicalDamageWeaponClass", "").toString();
|
|
if (periodicalDamageWeaponClass.compare("") == 0)
|
|
{
|
|
//was not setted in ini - use default value
|
|
psStats->periodicalDamageWeaponClass = psStats->weaponClass;
|
|
}
|
|
else if (!getWeaponClass(periodicalDamageWeaponClass, &psStats->periodicalDamageWeaponClass))
|
|
{
|
|
debug(LOG_ERROR, "Invalid periodicalDamageWeaponClass for weapon %s - assuming same class as weapon", getName(psStats));
|
|
psStats->periodicalDamageWeaponClass = psStats->weaponClass;
|
|
}
|
|
|
|
//set periodical damage weapon subclass
|
|
QString periodicalDamageWeaponSubClass = ini.value("periodicalDamageWeaponSubClass", "").toString();
|
|
if (periodicalDamageWeaponSubClass.compare("") == 0)
|
|
{
|
|
//was not setted in ini - use default value
|
|
psStats->periodicalDamageWeaponSubClass = psStats->weaponSubClass;
|
|
}
|
|
else if (!getWeaponSubClass(periodicalDamageWeaponSubClass.toUtf8().data(), &psStats->periodicalDamageWeaponSubClass))
|
|
{
|
|
debug(LOG_ERROR, "Invalid periodicalDamageWeaponSubClass for weapon %s - assuming same subclass as weapon", getName(psStats));
|
|
psStats->periodicalDamageWeaponSubClass = psStats->weaponSubClass;
|
|
}
|
|
|
|
//set periodical damage weapon effect
|
|
QString periodicalDamageWeaponEffect = ini.value("periodicalDamageWeaponEffect", "").toString();
|
|
if (periodicalDamageWeaponEffect.compare("") == 0)
|
|
{
|
|
//was not setted in ini - use default value
|
|
psStats->periodicalDamageWeaponEffect = psStats->weaponEffect;
|
|
}
|
|
else if (!getWeaponEffect(periodicalDamageWeaponEffect.toUtf8().data(), &psStats->periodicalDamageWeaponEffect))
|
|
{
|
|
debug(LOG_ERROR, "Invalid periodicalDamageWeaponEffect for weapon %s - assuming same effect as weapon", getName(psStats));
|
|
psStats->periodicalDamageWeaponEffect = psStats->weaponEffect;
|
|
}
|
|
|
|
//set the movement model
|
|
if (!getMovementModel(ini.value("movement").toString().toUtf8().constData(), &psStats->movementModel))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// set the face Player value
|
|
psStats->facePlayer = ini.value("facePlayer", false).toBool();
|
|
|
|
// set the In flight face Player value
|
|
psStats->faceInFlight = ini.value("faceInFlight", false).toBool();
|
|
|
|
//set the light world value
|
|
psStats->lightWorld = ini.value("lightWorld", false).toBool();
|
|
|
|
// interpret flags
|
|
psStats->surfaceToAir = SHOOT_ON_GROUND; // default
|
|
if (flags.contains("AirOnly", Qt::CaseInsensitive))
|
|
{
|
|
psStats->surfaceToAir = SHOOT_IN_AIR;
|
|
}
|
|
else if (flags.contains("ShootAir", Qt::CaseInsensitive))
|
|
{
|
|
psStats->surfaceToAir |= SHOOT_IN_AIR;
|
|
}
|
|
|
|
//set the weapon sounds to default value
|
|
psStats->iAudioFireID = NO_SOUND;
|
|
psStats->iAudioImpactID = NO_SOUND;
|
|
|
|
// load sounds
|
|
int weaponSoundID, explosionSoundID;
|
|
QString szWeaponWav = ini.value("weaponWav", "-1").toString();
|
|
QString szExplosionWav = ini.value("explosionWav", "-1").toString();
|
|
bool result = statsGetAudioIDFromString(list[i], szWeaponWav, &weaponSoundID);
|
|
ASSERT_OR_RETURN(false, result, "Weapon sound %s not found for %s", szWeaponWav.toUtf8().constData(), getName(psStats));
|
|
result = statsGetAudioIDFromString(list[i], szExplosionWav, &explosionSoundID);
|
|
ASSERT_OR_RETURN(false, result, "Explosion sound %s not found for %s", szExplosionWav.toUtf8().constData(), getName(psStats));
|
|
psStats->iAudioFireID = weaponSoundID;
|
|
psStats->iAudioImpactID = explosionSoundID;
|
|
|
|
// Set the max stat values for the design screen
|
|
if (psStats->designable)
|
|
{
|
|
setMaxWeaponRange(psStats->base.maxRange);
|
|
setMaxWeaponDamage(psStats->base.damage);
|
|
setMaxWeaponROF(weaponROF(psStats, 0));
|
|
setMaxComponentWeight(psStats->weight);
|
|
}
|
|
|
|
ini.endGroup();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*Load the Body stats from the file exported from Access*/
|
|
bool loadBodyStats(const char *pFileName)
|
|
{
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
statsAllocBody(list.size());
|
|
// Hack to make sure ZNULLBODY is always first in list
|
|
int nullbody = list.indexOf("ZNULLBODY");
|
|
ASSERT_OR_RETURN(false, nullbody >= 0, "ZNULLBODY is mandatory");
|
|
list.swap(nullbody, 0);
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
BODY_STATS *psStats = &asBodyStats[i];
|
|
|
|
ini.beginGroup(list[i]);
|
|
loadCompStats(ini, psStats, i);
|
|
psStats->compType = COMP_BODY;
|
|
|
|
psStats->weight = ini.value("weight", 0).toInt();
|
|
psStats->body = ini.value("hitpoints").toInt();
|
|
psStats->weaponSlots = ini.value("weaponSlots").toInt();
|
|
psStats->designable = ini.value("designable", false).toBool();
|
|
psStats->bodyClass = ini.value("class").toString();
|
|
psStats->base.thermal = ini.value("armourHeat").toInt();
|
|
psStats->base.armour = ini.value("armourKinetic").toInt();
|
|
psStats->base.power = ini.value("powerOutput").toInt();
|
|
psStats->base.body = psStats->body; // special exception hack
|
|
psStats->base.resistance = ini.value("resistance", 30).toInt();
|
|
for (int j = 0; j < MAX_PLAYERS; j++)
|
|
{
|
|
psStats->upgrade[j] = psStats->base;
|
|
}
|
|
QString dtype = ini.value("droidType", "DROID").toString();
|
|
psStats->droidTypeOverride = DROID_DEFAULT;
|
|
if (dtype.compare("PERSON") == 0)
|
|
{
|
|
psStats->droidTypeOverride = DROID_PERSON;
|
|
}
|
|
else if (dtype.compare("TRANSPORTER") == 0)
|
|
{
|
|
psStats->droidTypeOverride = DROID_TRANSPORTER;
|
|
}
|
|
else if (dtype.compare("CYBORG") == 0)
|
|
{
|
|
psStats->droidTypeOverride = DROID_CYBORG;
|
|
}
|
|
else if (dtype.compare("CYBORG_SUPER") == 0)
|
|
{
|
|
psStats->droidTypeOverride = DROID_CYBORG_SUPER;
|
|
}
|
|
else if (dtype.compare("CYBORG_CONSTRUCT") == 0)
|
|
{
|
|
psStats->droidTypeOverride = DROID_CYBORG_CONSTRUCT;
|
|
}
|
|
else if (dtype.compare("CYBORG_REPAIR") == 0)
|
|
{
|
|
psStats->droidTypeOverride = DROID_CYBORG_REPAIR;
|
|
}
|
|
psStats->ref = REF_BODY_START + i;
|
|
if (!getBodySize(ini.value("size").toString().toUtf8().constData(), &psStats->size))
|
|
{
|
|
ASSERT(false, "Unknown body size for %s", getName(psStats));
|
|
return false;
|
|
}
|
|
psStats->pIMD = statsGetIMD(ini, psStats, "model");
|
|
|
|
ini.endGroup();
|
|
|
|
//set the max stat values for the design screen
|
|
if (psStats->designable)
|
|
{
|
|
setMaxBodyArmour(psStats->base.armour);
|
|
setMaxBodyArmour(psStats->base.thermal);
|
|
setMaxBodyPower(psStats->base.power);
|
|
setMaxBodyPoints(psStats->body);
|
|
setMaxComponentWeight(psStats->weight);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*Load the Brain stats from the file exported from Access*/
|
|
bool loadBrainStats(const char *pFileName)
|
|
{
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
statsAllocBrain(list.size());
|
|
// Hack to make sure ZNULLBRAIN is always first in list
|
|
int nullbrain = list.indexOf("ZNULLBRAIN");
|
|
ASSERT_OR_RETURN(false, nullbrain >= 0, "ZNULLBRAIN is mandatory");
|
|
list.swap(nullbrain, 0);
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
BRAIN_STATS *psStats = &asBrainStats[i];
|
|
|
|
ini.beginGroup(list[i]);
|
|
loadCompStats(ini, psStats, i);
|
|
psStats->compType = COMP_BRAIN;
|
|
|
|
psStats->weight = ini.value("weight", 0).toInt();
|
|
psStats->maxDroids = ini.value("maxDroids").toInt();
|
|
psStats->maxDroidsMult = ini.value("maxDroidsMult").toInt();
|
|
psStats->ref = REF_BRAIN_START + i;
|
|
|
|
// check weapon attached
|
|
psStats->psWeaponStat = NULL;
|
|
if (ini.contains("turret"))
|
|
{
|
|
int weapon = getCompFromName(COMP_WEAPON, ini.value("turret").toString());
|
|
ASSERT_OR_RETURN(false, weapon >= 0, "Unable to find weapon for brain %s", getName(psStats));
|
|
psStats->psWeaponStat = asWeaponStats + weapon;
|
|
}
|
|
psStats->designable = ini.value("designable", false).toBool();
|
|
ini.endGroup();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*returns the propulsion type based on the string name passed in */
|
|
bool getPropulsionType(const char *typeName, PROPULSION_TYPE *type)
|
|
{
|
|
if (strcmp(typeName, "Wheeled") == 0)
|
|
{
|
|
*type = PROPULSION_TYPE_WHEELED;
|
|
}
|
|
else if (strcmp(typeName, "Tracked") == 0)
|
|
{
|
|
*type = PROPULSION_TYPE_TRACKED;
|
|
}
|
|
else if (strcmp(typeName, "Legged") == 0)
|
|
{
|
|
*type = PROPULSION_TYPE_LEGGED;
|
|
}
|
|
else if (strcmp(typeName, "Hover") == 0)
|
|
{
|
|
*type = PROPULSION_TYPE_HOVER;
|
|
}
|
|
else if (strcmp(typeName, "Lift") == 0)
|
|
{
|
|
*type = PROPULSION_TYPE_LIFT;
|
|
}
|
|
else if (strcmp(typeName, "Propellor") == 0)
|
|
{
|
|
*type = PROPULSION_TYPE_PROPELLOR;
|
|
}
|
|
else if (strcmp(typeName, "Half-Tracked") == 0)
|
|
{
|
|
*type = PROPULSION_TYPE_HALF_TRACKED;
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_ERROR, "getPropulsionType: Invalid Propulsion type %s - assuming Hover", typeName);
|
|
*type = PROPULSION_TYPE_HOVER;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*Load the Propulsion stats from the file exported from Access*/
|
|
bool loadPropulsionStats(const char *pFileName)
|
|
{
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
statsAllocPropulsion(list.size());
|
|
// Hack to make sure ZNULLPROP is always first in list
|
|
int nullprop = list.indexOf("ZNULLPROP");
|
|
ASSERT_OR_RETURN(false, nullprop >= 0, "ZNULLPROP is mandatory");
|
|
list.swap(nullprop, 0);
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
PROPULSION_STATS *psStats = &asPropulsionStats[i];
|
|
|
|
ini.beginGroup(list[i]);
|
|
loadCompStats(ini, psStats, i);
|
|
psStats->compType = COMP_PROPULSION;
|
|
|
|
psStats->weight = ini.value("weight").toInt();
|
|
psStats->body = ini.value("hitpoints").toInt();
|
|
psStats->maxSpeed = ini.value("speed").toInt();
|
|
psStats->designable = ini.value("designable", false).toBool();
|
|
psStats->ref = REF_PROPULSION_START + i;
|
|
psStats->turnSpeed = ini.value("turnSpeed", DEG(1) / 3).toInt();
|
|
psStats->spinSpeed = ini.value("spinSpeed", DEG(3) / 4).toInt();
|
|
psStats->spinAngle = ini.value("spinAngle", 180).toInt();
|
|
psStats->acceleration = ini.value("acceleration", 250).toInt();
|
|
psStats->deceleration = ini.value("deceleration", 800).toInt();
|
|
psStats->skidDeceleration = ini.value("skidDeceleration", 600).toInt();
|
|
psStats->pIMD = NULL;
|
|
psStats->pIMD = statsGetIMD(ini, psStats, "model");
|
|
if (!getPropulsionType(ini.value("type").toString().toUtf8().constData(), &psStats->propulsionType))
|
|
{
|
|
debug(LOG_FATAL, "loadPropulsionStats: Invalid Propulsion type for %s", getName(psStats));
|
|
return false;
|
|
}
|
|
ini.endGroup();
|
|
|
|
// set the max stat values for the design screen
|
|
if (psStats->designable)
|
|
{
|
|
setMaxPropulsionSpeed(psStats->maxSpeed);
|
|
}
|
|
}
|
|
|
|
/* since propulsion weight is a multiple of body weight we may need to adjust the max component weight value */
|
|
if (asBodyStats && asPropulsionStats) // check we've loaded them both in
|
|
{
|
|
//check against each body stat
|
|
for (int i = 0; i < numBodyStats; ++i)
|
|
{
|
|
//check stat is designable
|
|
if (asBodyStats[i].designable)
|
|
{
|
|
//check against each propulsion stat
|
|
for (int j = 0; j < numPropulsionStats; ++j)
|
|
{
|
|
//check stat is designable
|
|
if (asPropulsionStats[j].designable)
|
|
{
|
|
setMaxComponentWeight(asPropulsionStats[j].weight * asBodyStats[i].weight / 100);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*Load the Sensor stats from the file exported from Access*/
|
|
bool loadSensorStats(const char *pFileName)
|
|
{
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
statsAllocSensor(list.size());
|
|
// Hack to make sure ZNULLSENSOR is always first in list
|
|
int nullsensor = list.indexOf("ZNULLSENSOR");
|
|
ASSERT_OR_RETURN(false, nullsensor >= 0, "ZNULLSENSOR is mandatory");
|
|
list.swap(nullsensor, 0);
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
SENSOR_STATS *psStats = &asSensorStats[i];
|
|
|
|
ini.beginGroup(list[i]);
|
|
loadCompStats(ini, psStats, i);
|
|
psStats->compType = COMP_SENSOR;
|
|
|
|
psStats->weight = ini.value("weight", 0).toInt();
|
|
psStats->body = ini.value("hitpoints", 0).toInt();
|
|
psStats->base.range = ini.value("range").toInt();
|
|
for (int j = 0; j < MAX_PLAYERS; j++)
|
|
{
|
|
psStats->upgrade[j].range = psStats->base.range;
|
|
}
|
|
psStats->time = ini.value("time").toInt();
|
|
psStats->designable = ini.value("designable", false).toBool();
|
|
|
|
psStats->ref = REF_SENSOR_START + i;
|
|
|
|
QString location = ini.value("location").toString();
|
|
if (location.compare("DEFAULT") == 0)
|
|
{
|
|
psStats->location = LOC_DEFAULT;
|
|
}
|
|
else if (location.compare("TURRET") == 0)
|
|
{
|
|
psStats->location = LOC_TURRET;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(false, "Invalid Sensor location");
|
|
}
|
|
QString type = ini.value("type").toString();
|
|
if (type.compare("STANDARD") == 0)
|
|
{
|
|
psStats->type = STANDARD_SENSOR;
|
|
}
|
|
else if (type.compare("INDIRECT CB") == 0)
|
|
{
|
|
psStats->type = INDIRECT_CB_SENSOR;
|
|
}
|
|
else if (type.compare("VTOL CB") == 0)
|
|
{
|
|
psStats->type = VTOL_CB_SENSOR;
|
|
}
|
|
else if (type.compare("VTOL INTERCEPT") == 0)
|
|
{
|
|
psStats->type = VTOL_INTERCEPT_SENSOR;
|
|
}
|
|
else if (type.compare("SUPER") == 0)
|
|
{
|
|
psStats->type = SUPER_SENSOR;
|
|
}
|
|
else if (type.compare("RADAR DETECTOR") == 0)
|
|
{
|
|
psStats->type = RADAR_DETECTOR_SENSOR;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(false, "Invalid Sensor type");
|
|
}
|
|
//multiply time stats
|
|
psStats->time *= WEAPON_TIME;
|
|
|
|
//get the IMD for the component
|
|
psStats->pIMD = statsGetIMD(ini, psStats, "sensorModel");
|
|
psStats->pMountGraphic = statsGetIMD(ini, psStats, "mountModel");
|
|
|
|
ini.endGroup();
|
|
|
|
// set the max stat values for the design screen
|
|
if (psStats->designable)
|
|
{
|
|
setMaxSensorRange(psStats->base.range);
|
|
setMaxComponentWeight(psStats->weight);
|
|
}
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*Load the ECM stats from the file exported from Access*/
|
|
bool loadECMStats(const char *pFileName)
|
|
{
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
statsAllocECM(list.size());
|
|
// Hack to make sure ZNULLECM is always first in list
|
|
int nullecm = list.indexOf("ZNULLECM");
|
|
ASSERT_OR_RETURN(false, nullecm >= 0, "ZNULLECM is mandatory");
|
|
list.swap(nullecm, 0);
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
ECM_STATS *psStats = &asECMStats[i];
|
|
|
|
ini.beginGroup(list[i]);
|
|
loadCompStats(ini, psStats, i);
|
|
psStats->compType = COMP_ECM;
|
|
|
|
psStats->weight = ini.value("weight", 0).toInt();
|
|
psStats->body = ini.value("hitpoints", 0).toInt();
|
|
psStats->base.range = ini.value("range").toInt();
|
|
for (int j = 0; j < MAX_PLAYERS; j++)
|
|
{
|
|
psStats->upgrade[j].range = psStats->base.range;
|
|
}
|
|
psStats->designable = ini.value("designable", false).toBool();
|
|
|
|
psStats->ref = REF_ECM_START + i;
|
|
|
|
QString location = ini.value("location").toString();
|
|
if (location.compare("DEFAULT") == 0)
|
|
{
|
|
psStats->location = LOC_DEFAULT;
|
|
}
|
|
else if (location.compare("TURRET") == 0)
|
|
{
|
|
psStats->location = LOC_TURRET;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(false, "Invalid ECM location");
|
|
}
|
|
|
|
//get the IMD for the component
|
|
psStats->pIMD = statsGetIMD(ini, psStats, "sensorModel");
|
|
psStats->pMountGraphic = statsGetIMD(ini, psStats, "mountModel");
|
|
|
|
ini.endGroup();
|
|
|
|
// Set the max stat values for the design screen
|
|
if (psStats->designable)
|
|
{
|
|
setMaxECMRange(psStats->base.range);
|
|
setMaxComponentWeight(psStats->weight);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*Load the Repair stats from the file exported from Access*/
|
|
bool loadRepairStats(const char *pFileName)
|
|
{
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
statsAllocRepair(list.size());
|
|
// Hack to make sure ZNULLREPAIR is always first in list
|
|
int nullrepair = list.indexOf("ZNULLREPAIR");
|
|
ASSERT_OR_RETURN(false, nullrepair >= 0, "ZNULLREPAIR is mandatory");
|
|
list.swap(nullrepair, 0);
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
REPAIR_STATS *psStats = &asRepairStats[i];
|
|
|
|
ini.beginGroup(list[i]);
|
|
loadCompStats(ini, psStats, i);
|
|
psStats->compType = COMP_REPAIRUNIT;
|
|
|
|
psStats->weight = ini.value("weight", 0).toInt();
|
|
psStats->base.repairPoints = ini.value("repairPoints").toInt();
|
|
for (int j = 0; j < MAX_PLAYERS; j++)
|
|
{
|
|
psStats->upgrade[j].repairPoints = psStats->base.repairPoints;
|
|
}
|
|
psStats->time = ini.value("time", 0).toInt() * WEAPON_TIME;
|
|
psStats->designable = ini.value("designable", false).toBool();
|
|
|
|
psStats->ref = REF_REPAIR_START + i;
|
|
|
|
QString location = ini.value("location").toString();
|
|
if (location.compare("DEFAULT") == 0)
|
|
{
|
|
psStats->location = LOC_DEFAULT;
|
|
}
|
|
else if (location.compare("TURRET") == 0)
|
|
{
|
|
psStats->location = LOC_TURRET;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(false, "Invalid Repair location");
|
|
}
|
|
|
|
//check its not 0 since we will be dividing by it at a later stage
|
|
ASSERT_OR_RETURN(false, psStats->time > 0, "Repair delay cannot be zero for %s", getName(psStats));
|
|
|
|
//get the IMD for the component
|
|
psStats->pIMD = statsGetIMD(ini, psStats, "model");
|
|
psStats->pMountGraphic = statsGetIMD(ini, psStats, "mountModel");
|
|
|
|
ini.endGroup();
|
|
|
|
//set the max stat values for the design screen
|
|
if (psStats->designable)
|
|
{
|
|
setMaxRepairPoints(psStats->base.repairPoints);
|
|
setMaxComponentWeight(psStats->weight);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*Load the Construct stats from the file exported from Access*/
|
|
bool loadConstructStats(const char *pFileName)
|
|
{
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
statsAllocConstruct(list.size());
|
|
// Hack to make sure ZNULLCONSTRUCT is always first in list
|
|
int nullconstruct = list.indexOf("ZNULLCONSTRUCT");
|
|
ASSERT_OR_RETURN(false, nullconstruct >= 0, "ZNULLCONSTRUCT is mandatory");
|
|
list.swap(nullconstruct, 0);
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
CONSTRUCT_STATS *psStats = &asConstructStats[i];
|
|
|
|
ini.beginGroup(list[i]);
|
|
loadCompStats(ini, psStats, i);
|
|
psStats->compType = COMP_CONSTRUCT;
|
|
|
|
psStats->weight = ini.value("weight", 0).toInt();
|
|
psStats->body = ini.value("hitpoints", 0).toInt();
|
|
psStats->base.constructPoints = ini.value("constructPoints").toInt();
|
|
for (int j = 0; j < MAX_PLAYERS; j++)
|
|
{
|
|
psStats->upgrade[j].constructPoints = psStats->base.constructPoints;
|
|
}
|
|
psStats->designable = ini.value("designable", false).toBool();
|
|
psStats->ref = REF_CONSTRUCT_START + i;
|
|
|
|
//get the IMD for the component
|
|
psStats->pIMD = statsGetIMD(ini, psStats, "sensorModel");
|
|
psStats->pMountGraphic = statsGetIMD(ini, psStats, "mountModel");
|
|
|
|
ini.endGroup();
|
|
|
|
// Set the max stat values for the design screen
|
|
if (psStats->designable)
|
|
{
|
|
setMaxConstPoints(psStats->base.constructPoints);
|
|
setMaxComponentWeight(psStats->weight);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*Load the Propulsion Types from the file exported from Access*/
|
|
bool loadPropulsionTypes(const char *pFileName)
|
|
{
|
|
const unsigned int NumTypes = PROPULSION_TYPE_NUM;
|
|
PROPULSION_TYPES *pPropType;
|
|
unsigned int multiplier;
|
|
PROPULSION_TYPE type;
|
|
|
|
//allocate storage for the stats
|
|
asPropulsionTypes = (PROPULSION_TYPES *)malloc(sizeof(PROPULSION_TYPES) * NumTypes);
|
|
memset(asPropulsionTypes, 0, (sizeof(PROPULSION_TYPES)*NumTypes));
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
|
|
for (int i = 0; i < NumTypes; ++i)
|
|
{
|
|
ini.beginGroup(list[i]);
|
|
multiplier = ini.value("multiplier").toInt();
|
|
|
|
//set the pointer for this record based on the name
|
|
if (!getPropulsionType(list[i].toUtf8().constData(), &type))
|
|
{
|
|
debug(LOG_FATAL, "Invalid Propulsion type - %s", list[i].toUtf8().constData());
|
|
return false;
|
|
}
|
|
|
|
pPropType = asPropulsionTypes + type;
|
|
|
|
QString flightName = ini.value("flightName").toString();
|
|
if (flightName.compare("GROUND") == 0)
|
|
{
|
|
pPropType->travel = GROUND;
|
|
}
|
|
else if (flightName.compare("AIR") == 0)
|
|
{
|
|
pPropType->travel = AIR;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(false, "Invalid travel type for Propulsion");
|
|
}
|
|
|
|
//don't care about this anymore! AB FRIDAY 13/11/98
|
|
//want it back again! AB 27/11/98
|
|
if (multiplier > UWORD_MAX)
|
|
{
|
|
ASSERT(false, "loadPropulsionTypes: power Ratio multiplier too high");
|
|
//set to a default value since not life threatening!
|
|
multiplier = 100;
|
|
}
|
|
pPropType->powerRatioMult = (UWORD)multiplier;
|
|
|
|
//initialise all the sound variables
|
|
pPropType->startID = NO_SOUND;
|
|
pPropType->idleID = NO_SOUND;
|
|
pPropType->moveOffID = NO_SOUND;
|
|
pPropType->moveID = NO_SOUND;
|
|
pPropType->hissID = NO_SOUND;
|
|
pPropType->shutDownID = NO_SOUND;
|
|
|
|
ini.endGroup();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*Load the Terrain Table from the file exported from Access*/
|
|
bool loadTerrainTable(const char *pFileName)
|
|
{
|
|
asTerrainTable = (int *)malloc(sizeof(*asTerrainTable) * PROPULSION_TYPE_NUM * TER_MAX);
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
ini.beginGroup(list[i]);
|
|
int terrainType = list[i].toUInt();
|
|
QStringList speedFactors = ini.value("speedFactor").toStringList();
|
|
for (int j = 0; j < PROPULSION_TYPE_NUM; j++)
|
|
{
|
|
asTerrainTable[terrainType * PROPULSION_TYPE_NUM + j] = speedFactors[j].toUInt();
|
|
}
|
|
ini.endGroup();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* load the IMDs to use for each body-propulsion combination */
|
|
bool loadBodyPropulsionIMDs(const char *pFileName)
|
|
{
|
|
BODY_STATS *psBodyStat = asBodyStats;
|
|
unsigned int i, numStats;
|
|
QString propulsionName, leftIMD, rightIMD;
|
|
|
|
// check that the body and propulsion stats have already been read in
|
|
ASSERT(asBodyStats != NULL, "Body Stats have not been set up");
|
|
ASSERT(asPropulsionStats != NULL, "Propulsion Stats have not been set up");
|
|
|
|
// allocate space
|
|
for (numStats = 0; numStats < numBodyStats; ++numStats)
|
|
{
|
|
psBodyStat = &asBodyStats[numStats];
|
|
psBodyStat->ppIMDList.resize(numPropulsionStats * NUM_PROP_SIDES, NULL);
|
|
psBodyStat->ppMoveIMDList.resize(numPropulsionStats * NUM_PROP_SIDES, NULL);
|
|
psBodyStat->ppStillIMDList.resize(numPropulsionStats * NUM_PROP_SIDES, NULL);
|
|
}
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
for (i = 0; i < list.size(); ++i)
|
|
{
|
|
ini.beginGroup(list[i]);
|
|
//get the body stats
|
|
for (numStats = 0; numStats < numBodyStats; ++numStats)
|
|
{
|
|
psBodyStat = &asBodyStats[numStats];
|
|
if (list[i].compare(psBodyStat->id) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (numStats == numBodyStats) // not found
|
|
{
|
|
debug(LOG_FATAL, "Invalid body name %s", list[i].toUtf8().constData());
|
|
return false;
|
|
}
|
|
QStringList keys = ini.childKeys();
|
|
for (int j = 0; j < keys.size(); j++)
|
|
{
|
|
for (numStats = 0; numStats < numPropulsionStats; numStats++)
|
|
{
|
|
PROPULSION_STATS *psPropulsionStat = &asPropulsionStats[numStats];
|
|
if (keys[j].compare(psPropulsionStat->id) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (numStats == numPropulsionStats)
|
|
{
|
|
debug(LOG_FATAL, "Invalid propulsion name %s", keys[j].toUtf8().constData());
|
|
return false;
|
|
}
|
|
//allocate the left and right propulsion IMDs + movement and standing still animations
|
|
psBodyStat->ppIMDList[numStats * NUM_PROP_SIDES + LEFT_PROP] = statsGetIMD(ini, psBodyStat, keys[j], 0);
|
|
psBodyStat->ppIMDList[numStats * NUM_PROP_SIDES + RIGHT_PROP] = statsGetIMD(ini, psBodyStat, keys[j], 1);
|
|
psBodyStat->ppMoveIMDList[numStats] = statsGetIMD(ini, psBodyStat, keys[j], 2);
|
|
psBodyStat->ppStillIMDList[numStats] = statsGetIMD(ini, psBodyStat, keys[j], 3);
|
|
}
|
|
ini.endGroup();
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
static bool statsGetAudioIDFromString(const QString &szStatName, const QString &szWavName, int *piWavID)
|
|
{
|
|
if (szWavName.compare("-1") == 0)
|
|
{
|
|
*piWavID = NO_SOUND;
|
|
}
|
|
else if ((*piWavID = audio_GetIDFromStr(szWavName.toUtf8().constData())) == NO_SOUND)
|
|
{
|
|
debug(LOG_FATAL, "Could not get ID %d for sound %s", *piWavID, szWavName.toUtf8().constData());
|
|
return false;
|
|
}
|
|
if ((*piWavID < 0 || *piWavID > ID_MAX_SOUND) && *piWavID != NO_SOUND)
|
|
{
|
|
debug(LOG_FATAL, "Invalid ID - %d for sound %s", *piWavID, szStatName.toUtf8().constData());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool loadWeaponModifiers(const char *pFileName)
|
|
{
|
|
//initialise to 100%
|
|
for (int i = 0; i < WE_NUMEFFECTS; i++)
|
|
{
|
|
for (int j = 0; j < PROPULSION_TYPE_NUM; j++)
|
|
{
|
|
asWeaponModifier[i][j] = 100;
|
|
}
|
|
for (int j = 0; j < SIZE_NUM; j++)
|
|
{
|
|
asWeaponModifierBody[i][j] = 100;
|
|
}
|
|
}
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
for (int i = 0; i < list.size(); i++)
|
|
{
|
|
WEAPON_EFFECT effectInc;
|
|
PROPULSION_TYPE propInc;
|
|
|
|
ini.beginGroup(list[i]);
|
|
//get the weapon effect inc
|
|
if (!getWeaponEffect(list[i].toUtf8().constData(), &effectInc))
|
|
{
|
|
debug(LOG_FATAL, "Invalid Weapon Effect - %s", list[i].toUtf8().constData());
|
|
continue;
|
|
}
|
|
QStringList keys = ini.childKeys();
|
|
for (int j = 0; j < keys.size(); j++)
|
|
{
|
|
int modifier = ini.value(keys.at(j)).toInt();
|
|
if (!getPropulsionType(keys.at(j).toUtf8().data(), &propInc))
|
|
{
|
|
// If not propulsion, must be body
|
|
BODY_SIZE body = SIZE_NUM;
|
|
if (!getBodySize(keys.at(j).toUtf8().data(), &body))
|
|
{
|
|
debug(LOG_FATAL, "Invalid Propulsion or Body type - %s", keys.at(j).toUtf8().constData());
|
|
continue;
|
|
}
|
|
asWeaponModifierBody[effectInc][body] = modifier;
|
|
}
|
|
else // is propulsion
|
|
{
|
|
asWeaponModifier[effectInc][propInc] = modifier;
|
|
}
|
|
}
|
|
ini.endGroup();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*Load the propulsion type sounds from the file exported from Access*/
|
|
bool loadPropulsionSounds(const char *pFileName)
|
|
{
|
|
SDWORD i, startID, idleID, moveOffID, moveID, hissID, shutDownID;
|
|
PROPULSION_TYPE type;
|
|
PROPULSION_TYPES *pPropType;
|
|
|
|
ASSERT(asPropulsionTypes != NULL, "loadPropulsionSounds: Propulsion type stats not loaded");
|
|
|
|
WzConfig ini(pFileName, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
for (i = 0; i < list.size(); ++i)
|
|
{
|
|
ini.beginGroup(list[i]);
|
|
if (!statsGetAudioIDFromString(list[i], ini.value("szStart").toString(), &startID))
|
|
{
|
|
return false;
|
|
}
|
|
if (!statsGetAudioIDFromString(list[i], ini.value("szIdle").toString(), &idleID))
|
|
{
|
|
return false;
|
|
}
|
|
if (!statsGetAudioIDFromString(list[i], ini.value("szMoveOff").toString(), &moveOffID))
|
|
{
|
|
return false;
|
|
}
|
|
if (!statsGetAudioIDFromString(list[i], ini.value("szMove").toString(), &moveID))
|
|
{
|
|
return false;
|
|
}
|
|
if (!statsGetAudioIDFromString(list[i], ini.value("szHiss").toString(), &hissID))
|
|
{
|
|
return false;
|
|
}
|
|
if (!statsGetAudioIDFromString(list[i], ini.value("szShutDown").toString(), &shutDownID))
|
|
{
|
|
return false;
|
|
}
|
|
if (!getPropulsionType(list[i].toUtf8().constData(), &type))
|
|
{
|
|
debug(LOG_FATAL, "Invalid Propulsion type - %s", list[i].toUtf8().constData());
|
|
return false;
|
|
}
|
|
pPropType = asPropulsionTypes + type;
|
|
pPropType->startID = (SWORD)startID;
|
|
pPropType->idleID = (SWORD)idleID;
|
|
pPropType->moveOffID = (SWORD)moveOffID;
|
|
pPropType->moveID = (SWORD)moveID;
|
|
pPropType->hissID = (SWORD)hissID;
|
|
pPropType->shutDownID = (SWORD)shutDownID;
|
|
|
|
ini.endGroup();
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
//get the speed factor for a given terrain type and propulsion type
|
|
UDWORD getSpeedFactor(UDWORD type, UDWORD propulsionType)
|
|
{
|
|
ASSERT(propulsionType < PROPULSION_TYPE_NUM, "The propulsion type is too large");
|
|
return asTerrainTable[type * PROPULSION_TYPE_NUM + propulsionType];
|
|
}
|
|
|
|
//return the type of stat this stat is!
|
|
UDWORD statType(UDWORD ref)
|
|
{
|
|
if (ref >= REF_BODY_START && ref < REF_BODY_START +
|
|
REF_RANGE)
|
|
{
|
|
return COMP_BODY;
|
|
}
|
|
if (ref >= REF_BRAIN_START && ref < REF_BRAIN_START +
|
|
REF_RANGE)
|
|
{
|
|
return COMP_BRAIN;
|
|
}
|
|
if (ref >= REF_PROPULSION_START && ref <
|
|
REF_PROPULSION_START + REF_RANGE)
|
|
{
|
|
return COMP_PROPULSION;
|
|
}
|
|
if (ref >= REF_SENSOR_START && ref < REF_SENSOR_START +
|
|
REF_RANGE)
|
|
{
|
|
return COMP_SENSOR;
|
|
}
|
|
if (ref >= REF_ECM_START && ref < REF_ECM_START +
|
|
REF_RANGE)
|
|
{
|
|
return COMP_ECM;
|
|
}
|
|
if (ref >= REF_REPAIR_START && ref < REF_REPAIR_START +
|
|
REF_RANGE)
|
|
{
|
|
return COMP_REPAIRUNIT;
|
|
}
|
|
if (ref >= REF_WEAPON_START && ref < REF_WEAPON_START +
|
|
REF_RANGE)
|
|
{
|
|
return COMP_WEAPON;
|
|
}
|
|
if (ref >= REF_CONSTRUCT_START && ref < REF_CONSTRUCT_START +
|
|
REF_RANGE)
|
|
{
|
|
return COMP_CONSTRUCT;
|
|
}
|
|
//else
|
|
ASSERT(false, "Invalid stat pointer - cannot determine Stat Type");
|
|
return COMP_NUMCOMPONENTS;
|
|
}
|
|
|
|
//return the REF_START value of this type of stat
|
|
UDWORD statRefStart(UDWORD stat)
|
|
{
|
|
UDWORD start;
|
|
|
|
switch (stat)
|
|
{
|
|
case COMP_BODY:
|
|
{
|
|
start = REF_BODY_START;
|
|
break;
|
|
}
|
|
case COMP_BRAIN:
|
|
{
|
|
start = REF_BRAIN_START;
|
|
break;
|
|
}
|
|
case COMP_PROPULSION:
|
|
{
|
|
start = REF_PROPULSION_START;
|
|
break;
|
|
}
|
|
case COMP_SENSOR:
|
|
{
|
|
start = REF_SENSOR_START;
|
|
break;
|
|
}
|
|
case COMP_ECM:
|
|
{
|
|
start = REF_ECM_START;
|
|
break;
|
|
}
|
|
case COMP_REPAIRUNIT:
|
|
{
|
|
start = REF_REPAIR_START;
|
|
break;
|
|
}
|
|
case COMP_WEAPON:
|
|
{
|
|
start = REF_WEAPON_START;
|
|
break;
|
|
}
|
|
case COMP_CONSTRUCT:
|
|
{
|
|
start = REF_CONSTRUCT_START;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
debug(LOG_FATAL, "Invalid stat type");
|
|
start = 0;
|
|
}
|
|
}
|
|
return start;
|
|
}
|
|
|
|
/// Get the component index for a stat based on the name, and verify correct type
|
|
int getCompFromName(COMPONENT_TYPE compType, const QString &name)
|
|
{
|
|
COMPONENT_STATS *psComp = lookupStatPtr.value(name, NULL);
|
|
ASSERT_OR_RETURN(-1, psComp, "No such component [%s] found", name.toUtf8().constData());
|
|
ASSERT_OR_RETURN(-1, compType == psComp->compType, "Wrong component type for %s", name.toUtf8().constData());
|
|
return psComp->index;
|
|
}
|
|
|
|
/// Get the component for a stat based on the name alone.
|
|
/// Returns NULL if record not found
|
|
COMPONENT_STATS *getCompStatsFromName(const QString &name)
|
|
{
|
|
return lookupStatPtr.value(name, NULL);
|
|
}
|
|
|
|
/*sets the store to the body size based on the name passed in - returns false
|
|
if doesn't compare with any*/
|
|
bool getBodySize(const char *pSize, BODY_SIZE *pStore)
|
|
{
|
|
if (!strcmp(pSize, "LIGHT"))
|
|
{
|
|
*pStore = SIZE_LIGHT;
|
|
return true;
|
|
}
|
|
else if (!strcmp(pSize, "MEDIUM"))
|
|
{
|
|
*pStore = SIZE_MEDIUM;
|
|
return true;
|
|
}
|
|
else if (!strcmp(pSize, "HEAVY"))
|
|
{
|
|
*pStore = SIZE_HEAVY;
|
|
return true;
|
|
}
|
|
else if (!strcmp(pSize, "SUPER HEAVY"))
|
|
{
|
|
*pStore = SIZE_SUPER_HEAVY;
|
|
return true;
|
|
}
|
|
|
|
ASSERT(false, "Invalid size - %s", pSize);
|
|
return false;
|
|
}
|
|
|
|
/*returns the weapon sub class based on the string name passed in */
|
|
bool getWeaponSubClass(const char *subClass, WEAPON_SUBCLASS *wclass)
|
|
{
|
|
if (strcmp(subClass, "CANNON") == 0)
|
|
{
|
|
*wclass = WSC_CANNON;
|
|
}
|
|
else if (strcmp(subClass, "MORTARS") == 0)
|
|
{
|
|
*wclass = WSC_MORTARS;
|
|
}
|
|
else if (strcmp(subClass, "MISSILE") == 0)
|
|
{
|
|
*wclass = WSC_MISSILE;
|
|
}
|
|
else if (strcmp(subClass, "ROCKET") == 0)
|
|
{
|
|
*wclass = WSC_ROCKET;
|
|
}
|
|
else if (strcmp(subClass, "ENERGY") == 0)
|
|
{
|
|
*wclass = WSC_ENERGY;
|
|
}
|
|
else if (strcmp(subClass, "GAUSS") == 0)
|
|
{
|
|
*wclass = WSC_GAUSS;
|
|
}
|
|
else if (strcmp(subClass, "FLAME") == 0)
|
|
{
|
|
*wclass = WSC_FLAME;
|
|
}
|
|
else if (strcmp(subClass, "HOWITZERS") == 0)
|
|
{
|
|
*wclass = WSC_HOWITZERS;
|
|
}
|
|
else if (strcmp(subClass, "MACHINE GUN") == 0)
|
|
{
|
|
*wclass = WSC_MGUN;
|
|
}
|
|
else if (strcmp(subClass, "ELECTRONIC") == 0)
|
|
{
|
|
*wclass = WSC_ELECTRONIC;
|
|
}
|
|
else if (strcmp(subClass, "A-A GUN") == 0)
|
|
{
|
|
*wclass = WSC_AAGUN;
|
|
}
|
|
else if (strcmp(subClass, "SLOW MISSILE") == 0)
|
|
{
|
|
*wclass = WSC_SLOWMISSILE;
|
|
}
|
|
else if (strcmp(subClass, "SLOW ROCKET") == 0)
|
|
{
|
|
*wclass = WSC_SLOWROCKET;
|
|
}
|
|
else if (strcmp(subClass, "LAS_SAT") == 0)
|
|
{
|
|
*wclass = WSC_LAS_SAT;
|
|
}
|
|
else if (strcmp(subClass, "BOMB") == 0)
|
|
{
|
|
*wclass = WSC_BOMB;
|
|
}
|
|
else if (strcmp(subClass, "COMMAND") == 0)
|
|
{
|
|
*wclass = WSC_COMMAND;
|
|
}
|
|
else if (strcmp(subClass, "EMP") == 0)
|
|
{
|
|
*wclass = WSC_EMP;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(!"Invalid weapon sub class", "Invalid weapon sub class: %s", subClass);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*returns the weapon sub class based on the string name passed in */
|
|
const char *getWeaponSubClass(WEAPON_SUBCLASS wclass)
|
|
{
|
|
switch (wclass)
|
|
{
|
|
case WSC_CANNON: return "CANNON";
|
|
case WSC_MORTARS: return "MORTARS";
|
|
case WSC_MISSILE: return "MISSILE";
|
|
case WSC_ROCKET: return "ROCKET";
|
|
case WSC_ENERGY: return "ENERGY";
|
|
case WSC_GAUSS: return "GAUSS";
|
|
case WSC_FLAME: return "FLAME";
|
|
case WSC_HOWITZERS: return "HOWITZERS";
|
|
case WSC_MGUN: return "MACHINE GUN";
|
|
case WSC_ELECTRONIC: return "ELECTRONIC";
|
|
case WSC_AAGUN: return "A-A GUN";
|
|
case WSC_SLOWMISSILE: return "SLOW MISSILE";
|
|
case WSC_SLOWROCKET: return "SLOW ROCKET";
|
|
case WSC_LAS_SAT: return "LAS_SAT";
|
|
case WSC_BOMB: return "BOMB";
|
|
case WSC_COMMAND: return "COMMAND";
|
|
case WSC_EMP: return "EMP";
|
|
case WSC_NUM_WEAPON_SUBCLASSES: break;
|
|
}
|
|
ASSERT(false, "No such weapon subclass");
|
|
return "Bad weapon subclass";
|
|
}
|
|
|
|
/*returns the movement model based on the string name passed in */
|
|
bool getMovementModel(const char *movementModel, MOVEMENT_MODEL *model)
|
|
{
|
|
if (strcmp(movementModel, "DIRECT") == 0)
|
|
{
|
|
*model = MM_DIRECT;
|
|
}
|
|
else if (strcmp(movementModel, "INDIRECT") == 0)
|
|
{
|
|
*model = MM_INDIRECT;
|
|
}
|
|
else if (strcmp(movementModel, "HOMING-DIRECT") == 0)
|
|
{
|
|
*model = MM_HOMINGDIRECT;
|
|
}
|
|
else if (strcmp(movementModel, "HOMING-INDIRECT") == 0)
|
|
{
|
|
*model = MM_HOMINGINDIRECT;
|
|
}
|
|
else
|
|
{
|
|
// We've got problem if we got here
|
|
ASSERT(!"Invalid movement model", "Invalid movement model: %s", movementModel);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const StringToEnum<WEAPON_EFFECT> mapUnsorted_WEAPON_EFFECT[] =
|
|
{
|
|
{"ANTI PERSONNEL", WE_ANTI_PERSONNEL },
|
|
{"ANTI TANK", WE_ANTI_TANK },
|
|
{"BUNKER BUSTER", WE_BUNKER_BUSTER },
|
|
{"ARTILLERY ROUND", WE_ARTILLERY_ROUND },
|
|
{"FLAMER", WE_FLAMER },
|
|
{"ANTI AIRCRAFT", WE_ANTI_AIRCRAFT },
|
|
{"ALL ROUNDER", WE_ANTI_AIRCRAFT }, // Alternative name for WE_ANTI_AIRCRAFT.
|
|
};
|
|
const StringToEnumMap<WEAPON_EFFECT> map_WEAPON_EFFECT = mapUnsorted_WEAPON_EFFECT;
|
|
|
|
bool getWeaponEffect(const char *weaponEffect, WEAPON_EFFECT *effect)
|
|
{
|
|
if (strcmp(weaponEffect, "ANTI PERSONNEL") == 0)
|
|
{
|
|
*effect = WE_ANTI_PERSONNEL;
|
|
}
|
|
else if (strcmp(weaponEffect, "ANTI TANK") == 0)
|
|
{
|
|
*effect = WE_ANTI_TANK;
|
|
}
|
|
else if (strcmp(weaponEffect, "BUNKER BUSTER") == 0)
|
|
{
|
|
*effect = WE_BUNKER_BUSTER;
|
|
}
|
|
else if (strcmp(weaponEffect, "ARTILLERY ROUND") == 0)
|
|
{
|
|
*effect = WE_ARTILLERY_ROUND;
|
|
}
|
|
else if (strcmp(weaponEffect, "FLAMER") == 0)
|
|
{
|
|
*effect = WE_FLAMER;
|
|
}
|
|
else if (strcmp(weaponEffect, "ANTI AIRCRAFT") == 0 || strcmp(weaponEffect, "ALL ROUNDER") == 0)
|
|
{
|
|
*effect = WE_ANTI_AIRCRAFT;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(!"Invalid weapon effect", "Invalid weapon effect: %s", weaponEffect);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool getWeaponClass(QString weaponClassStr, WEAPON_CLASS *weaponClass)
|
|
{
|
|
if (weaponClassStr.compare("KINETIC") == 0)
|
|
{
|
|
*weaponClass = WC_KINETIC;
|
|
}
|
|
else if (weaponClassStr.compare("HEAT") == 0)
|
|
{
|
|
*weaponClass = WC_HEAT;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(false, "Bad weapon class %s", weaponClassStr.toUtf8().constData());
|
|
return false;
|
|
};
|
|
return true;
|
|
}
|
|
|
|
/*Access functions for the upgradeable stats of a weapon*/
|
|
int weaponFirePause(const WEAPON_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].firePause;
|
|
}
|
|
|
|
/* Reload time is reduced for weapons with salvo fire */
|
|
int weaponReloadTime(const WEAPON_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].reloadTime;
|
|
}
|
|
|
|
int weaponLongHit(const WEAPON_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].hitChance;
|
|
}
|
|
|
|
int weaponDamage(const WEAPON_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].damage;
|
|
}
|
|
|
|
int weaponRadDamage(const WEAPON_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].radiusDamage;
|
|
}
|
|
|
|
int weaponPeriodicalDamage(const WEAPON_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].periodicalDamage;
|
|
}
|
|
|
|
int sensorRange(const SENSOR_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].range;
|
|
}
|
|
|
|
int ecmRange(const ECM_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].range;
|
|
}
|
|
|
|
int repairPoints(const REPAIR_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].repairPoints;
|
|
}
|
|
|
|
int constructorPoints(const CONSTRUCT_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].constructPoints;
|
|
}
|
|
|
|
int bodyPower(const BODY_STATS *psStats, int player)
|
|
{
|
|
return psStats->upgrade[player].power;
|
|
}
|
|
|
|
int bodyArmour(const BODY_STATS *psStats, int player, WEAPON_CLASS weaponClass)
|
|
{
|
|
switch (weaponClass)
|
|
{
|
|
case WC_KINETIC:
|
|
return psStats->upgrade[player].armour;
|
|
case WC_HEAT:
|
|
return psStats->upgrade[player].thermal;
|
|
case WC_NUM_WEAPON_CLASSES:
|
|
break;
|
|
}
|
|
ASSERT(false, "Unknown weapon class");
|
|
return 0; // Should never get here.
|
|
}
|
|
|
|
//calculates the weapons ROF based on the fire pause and the salvos
|
|
int weaponROF(const WEAPON_STATS *psStat, int player)
|
|
{
|
|
int rof = 0;
|
|
// if there are salvos
|
|
if (player >= 0
|
|
&& psStat->upgrade[player].numRounds
|
|
&& psStat->upgrade[player].reloadTime != 0)
|
|
{
|
|
// Rounds per salvo multiplied with the number of salvos per minute
|
|
rof = psStat->upgrade[player].numRounds * 60 * GAME_TICKS_PER_SEC / weaponReloadTime(psStat, player);
|
|
}
|
|
|
|
if (rof == 0)
|
|
{
|
|
rof = weaponFirePause(psStat, selectedPlayer);
|
|
if (rof != 0)
|
|
{
|
|
rof = (UWORD)(60 * GAME_TICKS_PER_SEC / rof);
|
|
}
|
|
//else leave it at 0
|
|
}
|
|
return rof;
|
|
}
|
|
|
|
//Access functions for the max values to be used in the Design Screen
|
|
void setMaxComponentWeight(UDWORD weight)
|
|
{
|
|
if (weight > maxComponentWeight)
|
|
{
|
|
maxComponentWeight = weight;
|
|
}
|
|
}
|
|
UDWORD getMaxComponentWeight(void)
|
|
{
|
|
return maxComponentWeight;
|
|
}
|
|
|
|
void setMaxBodyArmour(UDWORD armour)
|
|
{
|
|
if (armour > maxBodyArmour)
|
|
{
|
|
maxBodyArmour = armour;
|
|
}
|
|
}
|
|
UDWORD getMaxBodyArmour(void)
|
|
{
|
|
return maxBodyArmour;
|
|
}
|
|
|
|
void setMaxBodyPower(UDWORD power)
|
|
{
|
|
if (power > maxBodyPower)
|
|
{
|
|
maxBodyPower = power;
|
|
}
|
|
}
|
|
UDWORD getMaxBodyPower(void)
|
|
{
|
|
return maxBodyPower;
|
|
}
|
|
|
|
void setMaxBodyPoints(UDWORD points)
|
|
{
|
|
if (points > maxBodyPoints)
|
|
{
|
|
maxBodyPoints = points;
|
|
}
|
|
}
|
|
UDWORD getMaxBodyPoints(void)
|
|
{
|
|
return maxBodyPoints;
|
|
}
|
|
|
|
void setMaxSensorRange(UDWORD range)
|
|
{
|
|
if (range > maxSensorRange)
|
|
{
|
|
maxSensorRange = range;
|
|
}
|
|
}
|
|
|
|
UDWORD getMaxSensorRange(void)
|
|
{
|
|
return maxSensorRange;
|
|
}
|
|
|
|
void setMaxECMRange(UDWORD range)
|
|
{
|
|
if (range > maxECMRange)
|
|
{
|
|
maxECMRange = range;
|
|
}
|
|
}
|
|
|
|
UDWORD getMaxECMRange(void)
|
|
{
|
|
return maxECMRange;
|
|
}
|
|
|
|
void setMaxConstPoints(UDWORD points)
|
|
{
|
|
if (points > maxConstPoints)
|
|
{
|
|
maxConstPoints = points;
|
|
}
|
|
}
|
|
UDWORD getMaxConstPoints(void)
|
|
{
|
|
return maxConstPoints;
|
|
}
|
|
|
|
void setMaxRepairPoints(UDWORD repair)
|
|
{
|
|
if (repair > maxRepairPoints)
|
|
{
|
|
maxRepairPoints = repair;
|
|
}
|
|
}
|
|
UDWORD getMaxRepairPoints(void)
|
|
{
|
|
return maxRepairPoints;
|
|
}
|
|
|
|
void setMaxWeaponRange(UDWORD range)
|
|
{
|
|
if (range > maxWeaponRange)
|
|
{
|
|
maxWeaponRange = range;
|
|
}
|
|
}
|
|
UDWORD getMaxWeaponRange(void)
|
|
{
|
|
return maxWeaponRange;
|
|
}
|
|
|
|
void setMaxWeaponDamage(UDWORD damage)
|
|
{
|
|
if (damage > maxWeaponDamage)
|
|
{
|
|
maxWeaponDamage = damage;
|
|
}
|
|
}
|
|
UDWORD getMaxWeaponDamage(void)
|
|
{
|
|
return maxWeaponDamage;
|
|
}
|
|
|
|
void setMaxWeaponROF(UDWORD rof)
|
|
{
|
|
if (rof > maxWeaponROF)
|
|
{
|
|
maxWeaponROF = rof;
|
|
}
|
|
}
|
|
UDWORD getMaxWeaponROF(void)
|
|
{
|
|
return maxWeaponROF;
|
|
}
|
|
|
|
void setMaxPropulsionSpeed(UDWORD speed)
|
|
{
|
|
if (speed > maxPropulsionSpeed)
|
|
{
|
|
maxPropulsionSpeed = speed;
|
|
}
|
|
}
|
|
UDWORD getMaxPropulsionSpeed(void)
|
|
{
|
|
return maxPropulsionSpeed;
|
|
}
|
|
|
|
//determine the effect this upgrade would have on the max values
|
|
void updateMaxWeaponStats(UWORD maxValue)
|
|
{
|
|
UDWORD currentMaxValue = getMaxWeaponDamage();
|
|
|
|
if (currentMaxValue < (currentMaxValue + maxValue / 100))
|
|
{
|
|
currentMaxValue += currentMaxValue * maxValue / 100;
|
|
setMaxWeaponDamage(currentMaxValue);
|
|
}
|
|
|
|
//the fire pause is dealt with differently
|
|
}
|
|
|
|
void updateMaxSensorStats(UWORD maxRange)
|
|
{
|
|
UDWORD currentMaxValue = getMaxSensorRange();
|
|
|
|
if (currentMaxValue < (currentMaxValue + currentMaxValue * maxRange / 100))
|
|
{
|
|
currentMaxValue += currentMaxValue * maxRange / 100;
|
|
setMaxSensorRange(currentMaxValue);
|
|
}
|
|
}
|
|
|
|
void updateMaxRepairStats(UWORD maxValue)
|
|
{
|
|
UDWORD currentMaxValue = getMaxRepairPoints();
|
|
|
|
if (currentMaxValue < (currentMaxValue + currentMaxValue * maxValue / 100))
|
|
{
|
|
currentMaxValue += currentMaxValue * maxValue / 100;
|
|
setMaxRepairPoints(currentMaxValue);
|
|
}
|
|
}
|
|
|
|
void updateMaxECMStats(UWORD maxValue)
|
|
{
|
|
int currentMaxValue = getMaxECMRange();
|
|
|
|
if (currentMaxValue < (currentMaxValue + currentMaxValue * maxValue / 100))
|
|
{
|
|
currentMaxValue += currentMaxValue * maxValue / 100;
|
|
setMaxECMRange(currentMaxValue);
|
|
}
|
|
}
|
|
|
|
void updateMaxBodyStats(UWORD maxBody, UWORD maxPower, UWORD maxArmour)
|
|
{
|
|
UDWORD currentMaxValue = getMaxBodyPoints();
|
|
|
|
if (currentMaxValue < (currentMaxValue + currentMaxValue * maxBody / 100))
|
|
{
|
|
currentMaxValue += currentMaxValue * maxBody / 100;
|
|
setMaxBodyPoints(currentMaxValue);
|
|
}
|
|
|
|
currentMaxValue = getMaxBodyPower();
|
|
if (currentMaxValue < (currentMaxValue + currentMaxValue * maxPower / 100))
|
|
{
|
|
currentMaxValue += currentMaxValue * maxPower / 100;
|
|
setMaxBodyPower(currentMaxValue);
|
|
}
|
|
|
|
currentMaxValue = getMaxBodyArmour();
|
|
if (currentMaxValue < (currentMaxValue + currentMaxValue * maxArmour / 100))
|
|
{
|
|
currentMaxValue += currentMaxValue * maxArmour / 100;
|
|
setMaxBodyArmour(currentMaxValue);
|
|
}
|
|
}
|
|
|
|
void updateMaxConstStats(UWORD maxValue)
|
|
{
|
|
UDWORD currentMaxValue = getMaxConstPoints();
|
|
|
|
if (currentMaxValue < (currentMaxValue + currentMaxValue * maxValue / 100))
|
|
{
|
|
currentMaxValue += currentMaxValue * maxValue / 100;
|
|
setMaxConstPoints(currentMaxValue);
|
|
}
|
|
}
|
|
|
|
void adjustMaxDesignStats(void)
|
|
{
|
|
UWORD weaponDamage, sensorRange, repairPoints,
|
|
ecmRange, constPoints, bodyPoints, bodyPower, bodyArmour;
|
|
|
|
// init all the values
|
|
weaponDamage = sensorRange = repairPoints = ecmRange = constPoints = bodyPoints = bodyPower = bodyArmour = 0;
|
|
|
|
//go thru' all the functions getting the max upgrade values for the stats
|
|
|
|
for (int j = 0; j < numBodyStats; j++)
|
|
{
|
|
BODY_STATS *psStats = asBodyStats + j;
|
|
bodyPoints = MAX(bodyPoints, psStats->upgrade[selectedPlayer].body);
|
|
bodyArmour = MAX(bodyArmour, psStats->upgrade[selectedPlayer].armour);
|
|
bodyPower = MAX(bodyPower, psStats->upgrade[selectedPlayer].power);
|
|
}
|
|
for (int j = 0; j < numSensorStats; j++)
|
|
{
|
|
SENSOR_STATS *psStats = asSensorStats + j;
|
|
sensorRange = MAX(sensorRange, psStats->upgrade[selectedPlayer].range);
|
|
}
|
|
for (int j = 0; j < numECMStats; j++)
|
|
{
|
|
ECM_STATS *psStats = asECMStats + j;
|
|
ecmRange = MAX(ecmRange, psStats->upgrade[selectedPlayer].range);
|
|
}
|
|
for (int j = 0; j < numRepairStats; j++)
|
|
{
|
|
REPAIR_STATS *psStats = asRepairStats + j;
|
|
repairPoints = MAX(repairPoints, psStats->upgrade[selectedPlayer].repairPoints);
|
|
}
|
|
for (int j = 0; j < numConstructStats; j++)
|
|
{
|
|
CONSTRUCT_STATS *psStats = asConstructStats + j;
|
|
constPoints = MAX(constPoints, psStats->upgrade[selectedPlayer].constructPoints);
|
|
}
|
|
for (int j = 0; j < numWeaponStats; j++)
|
|
{
|
|
WEAPON_STATS *psStats = asWeaponStats + j;
|
|
weaponDamage = MAX(weaponDamage, psStats->upgrade[selectedPlayer].damage);
|
|
}
|
|
|
|
//determine the effect on the max values for the stats
|
|
updateMaxWeaponStats(weaponDamage);
|
|
updateMaxSensorStats(sensorRange);
|
|
updateMaxRepairStats(repairPoints);
|
|
updateMaxECMStats(ecmRange);
|
|
updateMaxBodyStats(bodyPoints, bodyPower, bodyArmour);
|
|
updateMaxConstStats(constPoints);
|
|
}
|
|
|
|
/* Check if an object has a weapon */
|
|
bool objHasWeapon(const BASE_OBJECT *psObj)
|
|
{
|
|
//check if valid type
|
|
if (psObj->type == OBJ_DROID)
|
|
{
|
|
if (((DROID *)psObj)->numWeaps > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (psObj->type == OBJ_STRUCTURE)
|
|
{
|
|
if (((STRUCTURE *)psObj)->numWeaps > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
SENSOR_STATS *objActiveRadar(const BASE_OBJECT *psObj)
|
|
{
|
|
SENSOR_STATS *psStats = NULL;
|
|
int compIndex;
|
|
|
|
switch (psObj->type)
|
|
{
|
|
case OBJ_DROID:
|
|
if (((DROID *)psObj)->droidType != DROID_SENSOR && ((DROID *)psObj)->droidType != DROID_COMMAND)
|
|
{
|
|
return NULL;
|
|
}
|
|
compIndex = ((DROID *)psObj)->asBits[COMP_SENSOR];
|
|
ASSERT_OR_RETURN(NULL, compIndex < numSensorStats, "Invalid range referenced for numSensorStats, %d > %d", compIndex, numSensorStats);
|
|
psStats = asSensorStats + compIndex;
|
|
break;
|
|
case OBJ_STRUCTURE:
|
|
psStats = ((STRUCTURE *)psObj)->pStructureType->pSensor;
|
|
if (psStats == NULL || psStats->location != LOC_TURRET || ((STRUCTURE *)psObj)->status != SS_BUILT)
|
|
{
|
|
return NULL;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return psStats;
|
|
}
|
|
|
|
bool objRadarDetector(const BASE_OBJECT *psObj)
|
|
{
|
|
if (psObj->type == OBJ_STRUCTURE)
|
|
{
|
|
STRUCTURE *psStruct = (STRUCTURE *)psObj;
|
|
|
|
return (psStruct->status == SS_BUILT && psStruct->pStructureType->pSensor && psStruct->pStructureType->pSensor->type == RADAR_DETECTOR_SENSOR);
|
|
}
|
|
else if (psObj->type == OBJ_DROID)
|
|
{
|
|
DROID *psDroid = (DROID *)psObj;
|
|
SENSOR_STATS *psSensor = getSensorStats(psDroid);
|
|
|
|
return (psSensor && psSensor->type == RADAR_DETECTOR_SENSOR);
|
|
}
|
|
return false;
|
|
}
|