warzone2100/src/template.cpp

530 lines
20 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 template.cpp
*
* Droid template functions.
*
*/
#include "template.h"
#include "lib/framework/frame.h"
#include "lib/framework/wzconfig.h"
#include "lib/framework/math_ext.h"
#include "lib/framework/strres.h"
#include "lib/netplay/netplay.h"
#include "cmddroiddef.h"
#include "mission.h"
#include "objects.h"
#include "droid.h"
#include "design.h"
#include "hci.h"
#include "multiplay.h"
#include "projectile.h"
#include "main.h"
// Template storage
DROID_TEMPLATE *apsDroidTemplates[MAX_PLAYERS];
bool allowDesign = true;
bool includeRedundantDesigns = false;
static bool researchedItem(DROID_TEMPLATE *psCurr, int player, COMPONENT_TYPE partIndex, int part, bool allowZero, bool allowRedundant)
{
if (allowZero && part <= 0)
{
return true;
}
int availability = apCompLists[player][partIndex][part];
return availability == AVAILABLE || (allowRedundant && availability == REDUNDANT);
}
static bool researchedPart(DROID_TEMPLATE *psCurr, int player, COMPONENT_TYPE partIndex, bool allowZero, bool allowRedundant)
{
return researchedItem(psCurr, player, partIndex, psCurr->asParts[partIndex], allowZero, allowRedundant);
}
static bool researchedWeap(DROID_TEMPLATE *psCurr, int player, int weapIndex, bool allowRedundant)
{
int availability = apCompLists[player][COMP_WEAPON][psCurr->asWeaps[weapIndex]];
return availability == AVAILABLE || (allowRedundant && availability == REDUNDANT);
}
bool researchedTemplate(DROID_TEMPLATE *psCurr, int player, bool allowRedundant, bool verbose)
{
ASSERT_OR_RETURN(false, psCurr, "Given a null template");
bool resBody = researchedPart(psCurr, player, COMP_BODY, false, allowRedundant);
bool resBrain = researchedPart(psCurr, player, COMP_BRAIN, true, allowRedundant);
bool resProp = researchedPart(psCurr, player, COMP_PROPULSION, false, allowRedundant);
bool resSensor = researchedPart(psCurr, player, COMP_SENSOR, true, allowRedundant);
bool resEcm = researchedPart(psCurr, player, COMP_ECM, true, allowRedundant);
bool resRepair = researchedPart(psCurr, player, COMP_REPAIRUNIT, true, allowRedundant);
bool resConstruct = researchedPart(psCurr, player, COMP_CONSTRUCT, true, allowRedundant);
bool researchedEverything = resBody && resBrain && resProp && resSensor && resEcm && resRepair && resConstruct;
if (verbose && !researchedEverything)
{
debug(LOG_ERROR, "%s : not researched : body=%d brai=%d prop=%d sensor=%d ecm=%d rep=%d con=%d", getName(psCurr),
(int)resBody, (int)resBrain, (int)resProp, (int)resSensor, (int)resEcm, (int)resRepair, (int)resConstruct);
}
for (unsigned weapIndex = 0; weapIndex < psCurr->numWeaps && researchedEverything; ++weapIndex)
{
researchedEverything = researchedWeap(psCurr, player, weapIndex, allowRedundant);
if (!researchedEverything && verbose)
{
debug(LOG_ERROR, "%s : not researched weapon %u", getName(psCurr), weapIndex);
}
}
return researchedEverything;
}
bool initTemplates()
{
WzConfig ini("userdata/" + QString(rulesettag) + "/templates.ini");
if (ini.status() != QSettings::NoError)
{
debug(LOG_FATAL, "Could not open templates.ini");
return false;
}
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
DROID_TEMPLATE design;
design.droidType = (DROID_TYPE)ini.value("droidType").toInt();
design.multiPlayerID = generateNewObjectId();
design.asParts[COMP_BODY] = getCompFromName(COMP_BODY, ini.value("body", QString("ZNULLBODY")).toString());
design.asParts[COMP_BRAIN] = getCompFromName(COMP_BRAIN, ini.value("brain", QString("ZNULLBRAIN")).toString());
design.asParts[COMP_PROPULSION] = getCompFromName(COMP_PROPULSION, ini.value("propulsion", QString("ZNULLPROP")).toString());
design.asParts[COMP_REPAIRUNIT] = getCompFromName(COMP_REPAIRUNIT, ini.value("repair", QString("ZNULLREPAIR")).toString());
design.asParts[COMP_ECM] = getCompFromName(COMP_ECM, ini.value("ecm", QString("ZNULLECM")).toString());
design.asParts[COMP_SENSOR] = getCompFromName(COMP_SENSOR, ini.value("sensor", QString("ZNULLSENSOR")).toString());
design.asParts[COMP_CONSTRUCT] = getCompFromName(COMP_CONSTRUCT, ini.value("construct", QString("ZNULLCONSTRUCT")).toString());
design.asWeaps[0] = getCompFromName(COMP_WEAPON, ini.value("weapon/1", QString("ZNULLWEAPON")).toString());
design.asWeaps[1] = getCompFromName(COMP_WEAPON, ini.value("weapon/2", QString("ZNULLWEAPON")).toString());
design.asWeaps[2] = getCompFromName(COMP_WEAPON, ini.value("weapon/3", QString("ZNULLWEAPON")).toString());
design.numWeaps = ini.value("weapons").toInt();
design.prefab = false; // not AI template
design.stored = true;
if (!(asBodyStats + design.asParts[COMP_BODY])->designable
|| !(asPropulsionStats + design.asParts[COMP_PROPULSION])->designable
|| (design.asParts[COMP_BRAIN] > 0 && !(asBrainStats + design.asParts[COMP_BRAIN])->designable)
|| (design.asParts[COMP_REPAIRUNIT] > 0 && !(asRepairStats + design.asParts[COMP_REPAIRUNIT])->designable)
|| (design.asParts[COMP_ECM] > 0 && !(asECMStats + design.asParts[COMP_ECM])->designable)
|| (design.asParts[COMP_SENSOR] > 0 && !(asSensorStats + design.asParts[COMP_SENSOR])->designable)
|| (design.asParts[COMP_CONSTRUCT] > 0 && !(asConstructStats + design.asParts[COMP_CONSTRUCT])->designable)
|| (design.numWeaps > 0 && !(asWeaponStats + design.asWeaps[0])->designable)
|| (design.numWeaps > 1 && !(asWeaponStats + design.asWeaps[1])->designable)
|| (design.numWeaps > 2 && !(asWeaponStats + design.asWeaps[2])->designable))
{
debug(LOG_ERROR, "Template %d / %s from stored templates cannot be designed", i, list[i].toUtf8().constData());
continue;
}
bool valid = intValidTemplate(&design, ini.value("name").toString().toUtf8().constData(), false, selectedPlayer);
if (!valid)
{
debug(LOG_ERROR, "Invalid template %d / %s from stored templates", i, list[i].toUtf8().constData());
continue;
}
DROID_TEMPLATE *psDestTemplate = apsDroidTemplates[selectedPlayer];
while (psDestTemplate != NULL)
{
// Check if template is identical to a loaded template
if (psDestTemplate->droidType == design.droidType
&& psDestTemplate->name.compare(design.name) == 0
&& psDestTemplate->numWeaps == design.numWeaps
&& psDestTemplate->asWeaps[0] == design.asWeaps[0]
&& psDestTemplate->asWeaps[1] == design.asWeaps[1]
&& psDestTemplate->asWeaps[2] == design.asWeaps[2]
&& psDestTemplate->asParts[COMP_BODY] == design.asParts[COMP_BODY]
&& psDestTemplate->asParts[COMP_PROPULSION] == design.asParts[COMP_PROPULSION]
&& psDestTemplate->asParts[COMP_REPAIRUNIT] == design.asParts[COMP_REPAIRUNIT]
&& psDestTemplate->asParts[COMP_ECM] == design.asParts[COMP_ECM]
&& psDestTemplate->asParts[COMP_SENSOR] == design.asParts[COMP_SENSOR]
&& psDestTemplate->asParts[COMP_CONSTRUCT] == design.asParts[COMP_CONSTRUCT]
&& psDestTemplate->asParts[COMP_BRAIN] == design.asParts[COMP_BRAIN])
{
break;
}
psDestTemplate = psDestTemplate->psNext;
}
if (psDestTemplate)
{
psDestTemplate->stored = true; // assimilate it
ini.endGroup();
continue; // next!
}
design.enabled = allowDesign;
addTemplateToList(&design, &apsDroidTemplates[selectedPlayer]);
sendTemplate(selectedPlayer, &design);
localTemplates.push_back(design);
ini.endGroup();
}
return true;
}
bool storeTemplates()
{
// Write stored templates (back) to file
WzConfig ini("userdata/" + QString(rulesettag) + "/templates.ini");
if (ini.status() != QSettings::NoError || !ini.isWritable())
{
debug(LOG_FATAL, "Could not open templates.ini");
return false;
}
for (DROID_TEMPLATE *psCurr = apsDroidTemplates[selectedPlayer]; psCurr != NULL; psCurr = psCurr->psNext)
{
if (!psCurr->stored) continue; // not stored
ini.beginGroup("template_" + QString::number(psCurr->multiPlayerID));
ini.setValue("name", psCurr->name);
ini.setValue("droidType", psCurr->droidType);
ini.setValue("body", (asBodyStats + psCurr->asParts[COMP_BODY])->id);
ini.setValue("propulsion", (asPropulsionStats + psCurr->asParts[COMP_PROPULSION])->id);
if (psCurr->asParts[COMP_BRAIN] != 0)
{
ini.setValue("brain", (asBrainStats + psCurr->asParts[COMP_BRAIN])->id);
}
if ((asRepairStats + psCurr->asParts[COMP_REPAIRUNIT])->location == LOC_TURRET) // avoid auto-repair...
{
ini.setValue("repair", (asRepairStats + psCurr->asParts[COMP_REPAIRUNIT])->id);
}
if ((asECMStats + psCurr->asParts[COMP_ECM])->location == LOC_TURRET)
{
ini.setValue("ecm", (asECMStats + psCurr->asParts[COMP_ECM])->id);
}
if ((asSensorStats + psCurr->asParts[COMP_SENSOR])->location == LOC_TURRET)
{
ini.setValue("sensor", (asSensorStats + psCurr->asParts[COMP_SENSOR])->id);
}
if (psCurr->asParts[COMP_CONSTRUCT] != 0)
{
ini.setValue("construct", (asConstructStats + psCurr->asParts[COMP_CONSTRUCT])->id);
}
ini.setValue("weapons", psCurr->numWeaps);
for (int j = 0; j < psCurr->numWeaps; j++)
{
ini.setValue("weapon/" + QString::number(j + 1), (asWeaponStats + psCurr->asWeaps[j])->id);
}
ini.endGroup();
}
return true;
}
bool shutdownTemplates()
{
return storeTemplates();
}
DROID_TEMPLATE::DROID_TEMPLATE() // This constructor replaces a memset in scrAssembleWeaponTemplate(), not needed elsewhere.
: BASE_STATS(REF_TEMPLATE_START)
//, asParts
, numWeaps(0)
//, asWeaps
, droidType(DROID_WEAPON)
, multiPlayerID(0)
, psNext(NULL)
, prefab(false)
, stored(false)
, enabled(false)
{
std::fill_n(asParts, DROID_MAXCOMP, 0);
std::fill_n(asWeaps, DROID_MAXWEAPS, 0);
}
/* load the Droid stats for the components from the Access database */
bool loadDroidTemplates(const char *filename)
{
WzConfig ini(filename, WzConfig::ReadOnlyAndRequired);
QStringList list = ini.childGroups();
for (int i = 0; i < list.size(); ++i)
{
ini.beginGroup(list[i]);
DROID_TEMPLATE design;
QString droidType = ini.value("type").toString();
design.id = list[i];
design.name = ini.value("name").toString();
if (droidType == "PERSON") design.droidType = DROID_PERSON;
else if (droidType == "CYBORG") design.droidType = DROID_CYBORG;
else if (droidType == "CYBORG_SUPER") design.droidType = DROID_CYBORG_SUPER;
else if (droidType == "CYBORG_CONSTRUCT") design.droidType = DROID_CYBORG_CONSTRUCT;
else if (droidType == "CYBORG_REPAIR") design.droidType = DROID_CYBORG_REPAIR;
else if (droidType == "TRANSPORTER") design.droidType = DROID_TRANSPORTER;
else if (droidType == "SUPERTRANSPORTER") design.droidType = DROID_SUPERTRANSPORTER;
else if (droidType == "DROID") design.droidType = DROID_DEFAULT;
else ASSERT(false, "No such droid type \"%s\" for %s", droidType.toUtf8().constData(), getID(&design));
design.multiPlayerID = generateNewObjectId();
design.asParts[COMP_BODY] = getCompFromName(COMP_BODY, ini.value("compBody", "ZNULLBODY").toString());
design.asParts[COMP_BRAIN] = getCompFromName(COMP_BRAIN, ini.value("compBrain", "ZNULLBRAIN").toString());
design.asParts[COMP_REPAIRUNIT] = getCompFromName(COMP_REPAIRUNIT, ini.value("compRepair", "ZNULLREPAIR").toString());
design.asParts[COMP_CONSTRUCT] = getCompFromName(COMP_CONSTRUCT, ini.value("compConstruct", "ZNULLCONSTRUCT").toString());
design.asParts[COMP_ECM] = getCompFromName(COMP_ECM, ini.value("compECM", "ZNULLECM").toString());
design.asParts[COMP_SENSOR] = getCompFromName(COMP_SENSOR, ini.value("compSensor", "ZNULLSENSOR").toString());
design.asParts[COMP_PROPULSION] = getCompFromName(COMP_PROPULSION, ini.value("compPropulsion", "ZNULLPROP").toString());
QStringList weapons = ini.value("weapons").toStringList();
for (int j = 0; j < weapons.size(); j++)
{
design.asWeaps[j] = getCompFromName(COMP_WEAPON, weapons[j]);
}
design.numWeaps = weapons.size();
design.prefab = true;
design.stored = false;
design.enabled = true;
bool available = ini.value("available", false).toBool();
char const *droidResourceName = getDroidResourceName(list[i].toUtf8().constData());
design.name = droidResourceName != NULL? droidResourceName : GetDefaultTemplateName(&design);
ini.endGroup();
for (int i = 0; i < MAX_PLAYERS; ++i)
{
// Give those meant for humans to all human players.
if (NetPlay.players[i].allocated && available)
{
design.prefab = false;
addTemplateToList(&design, &apsDroidTemplates[i]);
// This sets up the UI templates for display purposes ONLY--we still only use apsDroidTemplates for making them.
// FIXME: Why are we doing this here, and not on demand ?
// Only add unique designs to the UI list (Note, perhaps better to use std::map instead?)
std::list<DROID_TEMPLATE>::iterator it;
for (it = localTemplates.begin(); it != localTemplates.end(); ++it)
{
DROID_TEMPLATE *psCurr = &*it;
if (psCurr->multiPlayerID == design.multiPlayerID)
{
debug(LOG_ERROR, "Design id:%d (%s) *NOT* added to UI list (duplicate), player= %d", design.multiPlayerID, getName(&design), i);
break;
}
}
if (it == localTemplates.end())
{
debug(LOG_NEVER, "Design id:%d (%s) added to UI list, player =%d", design.multiPlayerID, getName(&design), i);
localTemplates.push_front(design);
}
}
else if (!NetPlay.players[i].allocated) // AI template
{
design.prefab = true; // prefabricated templates referenced from VLOs
addTemplateToList(&design, &apsDroidTemplates[i]);
}
}
debug(LOG_NEVER, "Droid template found, Name: %s, MP ID: %d, ref: %u, ID: %s, prefab: %s, type:%d (loading)",
getName(&design), design.multiPlayerID, design.ref, getID(&design), design.prefab ? "yes":"no", design.droidType);
}
return true;
}
//free the storage for the droid templates
bool droidTemplateShutDown(void)
{
unsigned int player;
DROID_TEMPLATE *pTemplate, *pNext;
for (player = 0; player < MAX_PLAYERS; player++)
{
for (pTemplate = apsDroidTemplates[player]; pTemplate != NULL; pTemplate = pNext)
{
pNext = pTemplate->psNext;
delete pTemplate;
}
apsDroidTemplates[player] = NULL;
}
localTemplates.clear();
return true;
}
/*!
* Get a static template from its name. This is used from scripts. These templates must
* never be changed or deleted.
* \param pName Template name
* \pre pName has to be the unique, untranslated name!
*/
DROID_TEMPLATE *getTemplateFromTranslatedNameNoPlayer(char const *pName)
{
for (int i=0; i < MAX_PLAYERS; i++)
{
for (DROID_TEMPLATE *psCurr = apsDroidTemplates[i]; psCurr != NULL; psCurr = psCurr->psNext)
{
if (psCurr->id.compare(pName) == 0)
{
return psCurr;
}
}
}
return NULL;
}
/*getTemplatefFromMultiPlayerID gets template for unique ID searching all lists */
DROID_TEMPLATE* getTemplateFromMultiPlayerID(UDWORD multiPlayerID)
{
UDWORD player;
DROID_TEMPLATE *pDroidDesign;
for (player = 0; player < MAX_PLAYERS; player++)
{
for(pDroidDesign = apsDroidTemplates[player]; pDroidDesign != NULL; pDroidDesign = pDroidDesign->psNext)
{
if (pDroidDesign->multiPlayerID == multiPlayerID)
{
return pDroidDesign;
}
}
}
return NULL;
}
/*called when a Template is deleted in the Design screen*/
void deleteTemplateFromProduction(DROID_TEMPLATE *psTemplate, unsigned player, QUEUE_MODE mode)
{
STRUCTURE *psStruct;
STRUCTURE *psList;
//see if any factory is currently using the template
for (unsigned i = 0; i < 2; ++i)
{
psList = NULL;
switch (i)
{
case 0:
psList = apsStructLists[player];
break;
case 1:
psList = mission.apsStructLists[player];
break;
}
for (psStruct = psList; psStruct != NULL; psStruct = psStruct->psNext)
{
if (StructIsFactory(psStruct))
{
FACTORY *psFactory = &psStruct->pFunctionality->factory;
if (psFactory->psAssemblyPoint->factoryInc < asProductionRun[psFactory->psAssemblyPoint->factoryType].size())
{
ProductionRun &productionRun = asProductionRun[psFactory->psAssemblyPoint->factoryType][psFactory->psAssemblyPoint->factoryInc];
for (unsigned inc = 0; inc < productionRun.size(); ++inc)
{
if (productionRun[inc].psTemplate->multiPlayerID == psTemplate->multiPlayerID && mode == ModeQueue)
{
//just need to erase this production run entry
productionRun.erase(productionRun.begin() + inc);
--inc;
}
}
}
if (psFactory->psSubject == NULL)
{
continue;
}
// check not being built in the factory for the template player
if (psTemplate->multiPlayerID == psFactory->psSubject->multiPlayerID && mode == ModeImmediate)
{
syncDebugStructure(psStruct, '<');
syncDebug("Clearing production");
// Clear the factory's subject, and returns power.
cancelProduction(psStruct, ModeImmediate, false);
// Check to see if anything left to produce. (Also calls cancelProduction again, if nothing left to produce, which is a no-op. But if other things are left to produce, doesn't call cancelProduction, so wouldn't return power without the explicit cancelProduction call above.)
doNextProduction(psStruct, NULL, ModeImmediate);
//tell the interface
intManufactureFinished(psStruct);
syncDebugStructure(psStruct, '>');
}
}
}
}
}
// return whether a template is for an IDF droid
bool templateIsIDF(DROID_TEMPLATE *psTemplate)
{
//add Cyborgs
if (!(psTemplate->droidType == DROID_WEAPON || psTemplate->droidType == DROID_CYBORG ||
psTemplate->droidType == DROID_CYBORG_SUPER))
{
return false;
}
if (proj_Direct(psTemplate->asWeaps[0] + asWeaponStats))
{
return false;
}
return true;
}
/*
fills the list with Templates that can be manufactured
in the Factory - based on size. There is a limit on how many can be manufactured
at any one time. Pass back the number available.
*/
void fillTemplateList(std::vector<DROID_TEMPLATE *> &pList, STRUCTURE *psFactory)
{
const int player = psFactory->player;
pList.clear();
DROID_TEMPLATE *psCurr;
BODY_SIZE iCapacity = (BODY_SIZE)psFactory->capacity;
/* Add the templates to the list*/
for (std::list<DROID_TEMPLATE>::iterator i = localTemplates.begin(); i != localTemplates.end(); ++i)
{
psCurr = &*i;
// Must add droids if currently in production.
if (!getProduction(psFactory, psCurr).quantity)
{
//can only have (MAX_CMDDROIDS) in the world at any one time
if (psCurr->droidType == DROID_COMMAND)
{
if (checkProductionForCommand(player) + checkCommandExist(player) >= (MAX_CMDDROIDS))
{
continue;
}
}
if (!psCurr->enabled || !validTemplateForFactory(psCurr, psFactory, false)
|| !researchedTemplate(psCurr, player, includeRedundantDesigns))
{
continue;
}
}
//check the factory can cope with this sized body
if (((asBodyStats + psCurr->asParts[COMP_BODY])->size <= iCapacity))
{
pList.push_back(psCurr);
}
else if (bMultiPlayer && (iCapacity == SIZE_HEAVY))
{
// Special case for Super heavy bodyies (Super Transporter)
if((asBodyStats + psCurr->asParts[COMP_BODY])->size == SIZE_SUPER_HEAVY)
{
pList.push_back(psCurr);
}
}
}
}