1387 lines
39 KiB
C++
1387 lines
39 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
|
|
*/
|
|
/*
|
|
* Research.c
|
|
*
|
|
* Research tree and functions!
|
|
*
|
|
*/
|
|
#include <string.h>
|
|
#include <map>
|
|
|
|
#include "lib/framework/frame.h"
|
|
#include "lib/framework/strres.h"
|
|
#include "lib/framework/frameresource.h"
|
|
#include "lib/framework/wzconfig.h"
|
|
#include "objects.h"
|
|
#include "lib/gamelib/gtime.h"
|
|
#include "research.h"
|
|
#include "message.h"
|
|
#include "lib/sound/audio.h"
|
|
#include "lib/sound/audio_id.h"
|
|
#include "lib/script/script.h"
|
|
#include "scripttabs.h"
|
|
#include "hci.h"
|
|
#include "console.h"
|
|
#include "cmddroid.h"
|
|
#include "power.h"
|
|
#include "mission.h"
|
|
#include "frend.h" // frontend ids.
|
|
#include "intimage.h"
|
|
#include "multiplay.h"
|
|
#include "template.h"
|
|
#include "qtscript.h"
|
|
#include "stats.h"
|
|
|
|
// The stores for the research stats
|
|
std::vector<RESEARCH> asResearch;
|
|
|
|
//used for Callbacks to say which topic was last researched
|
|
RESEARCH *psCBLastResearch;
|
|
STRUCTURE *psCBLastResStructure;
|
|
SDWORD CBResFacilityOwner;
|
|
|
|
//List of pointers to arrays of PLAYER_RESEARCH[numResearch] for each player
|
|
std::vector<PLAYER_RESEARCH> asPlayerResList[MAX_PLAYERS];
|
|
|
|
/* Default level of sensor, Repair and ECM */
|
|
UDWORD aDefaultSensor[MAX_PLAYERS];
|
|
UDWORD aDefaultECM[MAX_PLAYERS];
|
|
UDWORD aDefaultRepair[MAX_PLAYERS];
|
|
|
|
//set the iconID based on the name read in in the stats
|
|
static UWORD setIconID(char *pIconName, const char *pName);
|
|
static void replaceComponent(COMPONENT_STATS *pNewComponent, COMPONENT_STATS *pOldComponent,
|
|
UBYTE player);
|
|
static bool checkResearchName(RESEARCH *psRes, UDWORD numStats);
|
|
|
|
//flag that indicates whether the player can self repair
|
|
static UBYTE bSelfRepair[MAX_PLAYERS];
|
|
static void replaceDroidComponent(DROID *pList, UDWORD oldType, UDWORD oldCompInc,
|
|
UDWORD newCompInc);
|
|
static void replaceStructureComponent(STRUCTURE *pList, UDWORD oldType, UDWORD oldCompInc,
|
|
UDWORD newCompInc, UBYTE player);
|
|
static void switchComponent(DROID *psDroid,UDWORD oldType, UDWORD oldCompInc,
|
|
UDWORD newCompInc);
|
|
static void replaceTransDroidComponents(DROID *psTransporter, UDWORD oldType,
|
|
UDWORD oldCompInc, UDWORD newCompInc);
|
|
|
|
|
|
bool researchInitVars(void)
|
|
{
|
|
psCBLastResearch = NULL;
|
|
psCBLastResStructure = NULL;
|
|
CBResFacilityOwner = -1;
|
|
asResearch.clear();
|
|
|
|
for (int i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
bSelfRepair[i] = false;
|
|
aDefaultSensor[i] = 0;
|
|
aDefaultECM[i] = 0;
|
|
aDefaultRepair[i] = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Load the research stats */
|
|
bool loadResearch(QString filename)
|
|
{
|
|
WzConfig ini(filename, WzConfig::ReadOnlyAndRequired);
|
|
QStringList list = ini.childGroups();
|
|
PLAYER_RESEARCH dummy;
|
|
memset(&dummy, 0, sizeof(dummy));
|
|
QVector<QStringList> preResearch;
|
|
preResearch.resize(list.size());
|
|
for (int inc = 0; inc < list.size(); ++inc)
|
|
{
|
|
// HACK FIXME: the code assumes we have empty PLAYER_RESEARCH entries to throw around
|
|
for (int j = 0; j < MAX_PLAYERS; j++)
|
|
{
|
|
asPlayerResList[j].push_back(dummy);
|
|
}
|
|
|
|
ini.beginGroup(list[inc]);
|
|
RESEARCH research;
|
|
research.index = inc;
|
|
research.name = ini.value("name").toString();
|
|
research.id = list[inc];
|
|
|
|
//check the name hasn't been used already
|
|
ASSERT_OR_RETURN(false, checkResearchName(&research, inc), "Research name '%s' used already", getName(&research));
|
|
|
|
research.ref = REF_RESEARCH_START + inc;
|
|
research.resultStrings = ini.value("results").toStringList();
|
|
|
|
//set subGroup icon
|
|
QString subGroup = ini.value("subgroupIconID", "").toString();
|
|
if (subGroup.compare("") != 0)
|
|
{
|
|
research.subGroup = setIconID(subGroup.toUtf8().data(), getName(&research));
|
|
}
|
|
else
|
|
{
|
|
research.subGroup = NO_RESEARCH_ICON;
|
|
}
|
|
|
|
//set key topic
|
|
unsigned int keyTopic = ini.value("keyTopic", 0).toUInt();
|
|
ASSERT(keyTopic <= 1, "Invalid keyTopic for research topic - '%s' ", getName(&research));
|
|
if(keyTopic <= 1)
|
|
research.keyTopic = ini.value("keyTopic", 0).toUInt();
|
|
else
|
|
research.keyTopic = 0;
|
|
|
|
//set tech code
|
|
UBYTE techCode = ini.value("techCode", 0).toUInt();
|
|
ASSERT(techCode <= 1, "Invalid tech code for research topic - '%s' ", getName(&research));
|
|
if (techCode == 0)
|
|
{
|
|
research.techCode = TC_MAJOR;
|
|
}
|
|
else
|
|
{
|
|
research.techCode = TC_MINOR;
|
|
}
|
|
|
|
//set the iconID
|
|
QString iconID = ini.value("iconID", "").toString();
|
|
if (iconID.compare("") != 0)
|
|
{
|
|
research.iconID = setIconID(iconID.toUtf8().data(), getName(&research));
|
|
}
|
|
else
|
|
{
|
|
research.iconID = NO_RESEARCH_ICON;
|
|
}
|
|
|
|
//get the IMDs used in the interface
|
|
QString statID = ini.value("statID", "").toString();
|
|
research.psStat = NULL;
|
|
if (statID.compare("") != 0)
|
|
{
|
|
//try find the structure stat with given name
|
|
int32_t structID = getStructStatFromName(statID.toUtf8().data());
|
|
if (structID >= 0)
|
|
{
|
|
research.psStat = (BASE_STATS *)(asStructureStats + structID);
|
|
}else
|
|
{
|
|
//try find the component stat with given name
|
|
research.psStat = getCompStatsFromName(statID);
|
|
}
|
|
ASSERT_OR_RETURN(false, research.psStat != NULL, "Cannot find the statID '%s' for Research '%s'", statID.toUtf8().data(), getName(&research));
|
|
}
|
|
|
|
QString imdName = ini.value("imdName", "").toString();
|
|
if (imdName.compare("") != 0)
|
|
{
|
|
research.pIMD = modelGet(imdName);
|
|
ASSERT(research.pIMD != NULL, "Cannot find the research PIE '%s' for record '%s'",imdName.toUtf8().data(), getName(&research));
|
|
}
|
|
|
|
QString imdName2 = ini.value("imdName2", "").toString();
|
|
if (imdName2.compare("") != 0)
|
|
{
|
|
research.pIMD2 = modelGet(imdName2);
|
|
ASSERT(research.pIMD2 != NULL, "Cannot find the 2nd research '%s' PIE for record '%s'",imdName2.toUtf8().data(), getName(&research));
|
|
}
|
|
|
|
QString msgName = ini.value("msgName", "").toString();
|
|
if (msgName.compare("") != 0)
|
|
{
|
|
//check its a major tech code
|
|
ASSERT(research.techCode == TC_MAJOR, "This research should not have a message associated with it, '%s' the message will be ignored!", getName(&research));
|
|
if (research.techCode == TC_MAJOR)
|
|
{
|
|
research.pViewData = getViewData(msgName.toUtf8().data());
|
|
}
|
|
}
|
|
|
|
//set the researchPoints
|
|
unsigned int resPoints = ini.value("researchPoints", 0).toUInt();
|
|
ASSERT_OR_RETURN(false, resPoints <= UWORD_MAX, "Research Points too high for research topic - '%s' ", getName(&research));
|
|
research.researchPoints = resPoints;
|
|
|
|
//set the research power
|
|
unsigned int resPower = ini.value("researchPower", 0).toUInt();
|
|
ASSERT_OR_RETURN(false, resPower <= UWORD_MAX, "Research Power too high for research topic - '%s' ", getName(&research));
|
|
research.researchPower = resPower;
|
|
|
|
//rememeber research pre-requisites for futher checking
|
|
preResearch[inc] = ini.value("requiredResearch").toStringList();
|
|
|
|
//set components results
|
|
QStringList compResults = ini.value("resultComponents").toStringList();
|
|
for (int j = 0; j < compResults.size(); j++)
|
|
{
|
|
QString compID = compResults[j].trimmed();
|
|
COMPONENT_STATS *pComp = getCompStatsFromName(compID);
|
|
if (pComp != NULL)
|
|
{
|
|
research.componentResults.push_back(pComp);
|
|
}else
|
|
{
|
|
ASSERT(false, "Invalid item '%s' in list of result components of research '%s' ", compID.toUtf8().constData(), getName(&research));
|
|
}
|
|
}
|
|
|
|
//set replaced components
|
|
QStringList replacedComp = ini.value("replacedComponents").toStringList();
|
|
for (int j = 0; j < replacedComp.size(); j++)
|
|
{
|
|
//read pair of components oldComponent:newComponent
|
|
QStringList pair = replacedComp[j].split(':');
|
|
ASSERT(pair.size() == 2, "Invalid item '%s' in list of replaced components of research '%s'. Required format: 'oldItem:newItem, item1:item2'", replacedComp[j].toUtf8().constData(), getName(&research));
|
|
if (pair.size() != 2)
|
|
{
|
|
continue; //skip invalid entries
|
|
}
|
|
QString oldCompID = pair[0].trimmed();
|
|
QString newCompID = pair[1].trimmed();
|
|
COMPONENT_STATS *oldComp = getCompStatsFromName(oldCompID);
|
|
if (oldComp == NULL)
|
|
{
|
|
ASSERT(false, "Invalid item '%s' in list of replaced components of research '%s'. Wrong component code.", oldCompID.toUtf8().constData(), getName(&research));
|
|
continue;
|
|
}
|
|
COMPONENT_STATS *newComp = getCompStatsFromName(newCompID);
|
|
if(newComp == NULL)
|
|
{
|
|
ASSERT(false, "Invalid item '%s' in list of replaced components of research '%s'. Wrong component code.", newCompID.toUtf8().constData(), getName(&research));
|
|
continue;
|
|
}
|
|
RES_COMP_REPLACEMENT replItem;
|
|
replItem.pOldComponent = oldComp;
|
|
replItem.pNewComponent = newComp;
|
|
research.componentReplacement.push_back(replItem);
|
|
}
|
|
|
|
//set redundant components
|
|
QStringList redComp = ini.value("redComponents").toStringList();
|
|
for (int j = 0; j < redComp.size(); j++)
|
|
{
|
|
QString compID = redComp[j].trimmed();
|
|
COMPONENT_STATS *pComp = getCompStatsFromName(compID);
|
|
if (pComp == NULL)
|
|
{
|
|
ASSERT(false, "Invalid item '%s' in list of redundant components of research '%s' ", compID.toUtf8().constData(), getName(&research));
|
|
}else
|
|
{
|
|
research.pRedArtefacts.push_back(pComp);
|
|
}
|
|
}
|
|
|
|
//set result structures
|
|
QStringList resStruct = ini.value("resultStructures").toStringList();
|
|
for (int j = 0; j < resStruct.size(); j++)
|
|
{
|
|
QString strucID = resStruct[j].trimmed();
|
|
int structIndex = getStructStatFromName(strucID.toUtf8().data());
|
|
ASSERT(structIndex >= 0, "Invalid item '%s' in list of result structures of research '%s' ", strucID.toUtf8().constData(), getName(&research));
|
|
if (structIndex >= 0)
|
|
{
|
|
research.pStructureResults.push_back(structIndex);
|
|
}
|
|
}
|
|
|
|
//set required structures
|
|
QStringList reqStruct = ini.value("requiredStructures").toStringList();
|
|
for (int j = 0; j < reqStruct.size(); j++)
|
|
{
|
|
QString strucID = reqStruct[j].trimmed();
|
|
int structIndex = getStructStatFromName(strucID.toUtf8().data());
|
|
ASSERT(structIndex >= 0, "Invalid item '%s' in list of required structures of research '%s' ", strucID.toUtf8().constData(), getName(&research));
|
|
if(structIndex >= 0)
|
|
{
|
|
research.pStructList.push_back(structIndex);
|
|
}
|
|
}
|
|
|
|
//set redundant structures
|
|
QStringList redStruct = ini.value("redStructures").toStringList();
|
|
for (int j = 0; j < redStruct.size(); j++)
|
|
{
|
|
QString strucID = redStruct[j].trimmed();
|
|
int structIndex = getStructStatFromName(strucID.toUtf8().data());
|
|
ASSERT(structIndex >= 0, "Invalid item '%s' in list of redundant structures of research '%s' ", strucID.toUtf8().constData(), getName(&research));
|
|
if (structIndex >= 0)
|
|
{
|
|
research.pRedStructs.push_back(structIndex);
|
|
}
|
|
}
|
|
|
|
asResearch.push_back(research);
|
|
ini.endGroup();
|
|
}
|
|
|
|
//Load and check research pre-requisites (need do it AFTER loading research items)
|
|
for (int inc = 0; inc < asResearch.size(); inc++)
|
|
{
|
|
QStringList preRes = preResearch[inc];
|
|
for (int j = 0; j < preRes.size(); j++)
|
|
{
|
|
QString resID = preRes[j].trimmed();
|
|
RESEARCH *preResItem = getResearch(resID.toUtf8().constData());
|
|
ASSERT(preResItem != NULL, "Invalid item '%s' in list of pre-requisites of research '%s' ", resID.toUtf8().constData(), getName(&asResearch[inc]));
|
|
if (preResItem != NULL)
|
|
asResearch[inc].pPRList.push_back(preResItem->index);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool researchAvailable(int inc, int playerID, QUEUE_MODE mode)
|
|
{
|
|
// Decide whether to use IsResearchCancelledPending/IsResearchStartedPending or IsResearchCancelled/IsResearchStarted.
|
|
bool (*IsResearchCancelledFunc)(PLAYER_RESEARCH const *) = IsResearchCancelledPending;
|
|
bool (*IsResearchStartedFunc)(PLAYER_RESEARCH const *) = IsResearchStartedPending;
|
|
if (mode == ModeImmediate)
|
|
{
|
|
IsResearchCancelledFunc = IsResearchCancelled;
|
|
IsResearchStartedFunc = IsResearchStarted;
|
|
}
|
|
|
|
UDWORD incPR, incS;
|
|
bool bPRFound, bStructFound;
|
|
|
|
// if its a cancelled topic - add to list
|
|
if (IsResearchCancelledFunc(&asPlayerResList[playerID][inc]))
|
|
{
|
|
return true;
|
|
}
|
|
// if the topic is possible and has not already been researched - add to list
|
|
if ((IsResearchPossible(&asPlayerResList[playerID][inc])))
|
|
{
|
|
if (!IsResearchCompleted(&asPlayerResList[playerID][inc])
|
|
&& !IsResearchStartedFunc(&asPlayerResList[playerID][inc]))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// if single player mode and key topic, then ignore cos can't do it!
|
|
if (!bMultiPlayer && asResearch[inc].keyTopic)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool researchStarted = IsResearchStartedFunc(&asPlayerResList[playerID][inc]);
|
|
if (researchStarted)
|
|
{
|
|
STRUCTURE *psBuilding = findResearchingFacilityByResearchIndex(playerID, inc); // May fail to find the structure here, if the research is merely pending, not actually started.
|
|
if (psBuilding != NULL && psBuilding->status == SS_BEING_BUILT)
|
|
{
|
|
researchStarted = false; // Although research is started, the facility is currently being upgraded or demolished, so we want to be able to research this elsewhere.
|
|
}
|
|
}
|
|
|
|
// make sure that the research is not completed or started by another researchfac
|
|
if (!IsResearchCompleted(&asPlayerResList[playerID][inc]) && !researchStarted)
|
|
{
|
|
// Research is not completed ... also it has not been started by another researchfac
|
|
|
|
// if there aren't any PR's - go to next topic
|
|
if (asResearch[inc].pPRList.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check for pre-requisites
|
|
bPRFound = true;
|
|
for (incPR = 0; incPR < asResearch[inc].pPRList.size(); incPR++)
|
|
{
|
|
if (IsResearchCompleted(&(asPlayerResList[playerID][asResearch[inc].pPRList[incPR]]))==0)
|
|
{
|
|
// if haven't pre-requisite - quit checking rest
|
|
bPRFound = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!bPRFound)
|
|
{
|
|
// if haven't pre-requisites, skip the rest of the checks
|
|
return false;
|
|
}
|
|
|
|
// check for structure effects
|
|
bStructFound = true;
|
|
for (incS = 0; incS < asResearch[inc].pStructList.size(); incS++)
|
|
{
|
|
if (!checkSpecificStructExists(asResearch[inc].pStructList[incS], playerID))
|
|
{
|
|
//if not built, quit checking
|
|
bStructFound = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!bStructFound)
|
|
{
|
|
// if haven't all structs built, skip to next topic
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Function to check what can be researched for a particular player at any one
|
|
instant.
|
|
|
|
A topic can be researched if the playerRes 'possible' flag has been set (by script)
|
|
or if the research pre-req topics have been researched. A check is made for any
|
|
structures that are required to have been built for topics that do not have
|
|
the 'possible' flag set.
|
|
|
|
**NB** A topic with zero PR's can ONLY be researched once the 'possible' flag
|
|
has been set.
|
|
|
|
There can only be 'limit' number of entries
|
|
'topic' is the currently researched topic
|
|
*/
|
|
// NOTE by AJL may 99 - skirmish now has it's own version of this, skTopicAvail.
|
|
UWORD fillResearchList(UWORD *plist, UDWORD playerID, UWORD topic, UWORD limit)
|
|
{
|
|
UWORD inc, count=0;
|
|
|
|
for (inc=0; inc < asResearch.size(); inc++)
|
|
{
|
|
// if the inc matches the 'topic' - automatically add to the list
|
|
if (inc == topic || researchAvailable(inc, playerID, ModeQueue))
|
|
{
|
|
*plist++ = inc;
|
|
count++;
|
|
if (count == limit)
|
|
{
|
|
return count;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* process the results of a completed research topic */
|
|
void researchResult(UDWORD researchIndex, UBYTE player, bool bDisplay, STRUCTURE *psResearchFacility, bool bTrigger)
|
|
{
|
|
RESEARCH * pResearch = &asResearch[researchIndex];
|
|
MESSAGE *pMessage;
|
|
//the message gets sent to console
|
|
char consoleMsg[MAX_RESEARCH_MSG_SIZE];
|
|
|
|
ASSERT_OR_RETURN( , researchIndex < asResearch.size(), "Invalid research index %u", researchIndex);
|
|
|
|
MakeResearchCompleted(&asPlayerResList[player][researchIndex]);
|
|
|
|
//check for structures to be made available
|
|
for (int inc = 0; inc < pResearch->pStructureResults.size(); inc++)
|
|
{
|
|
if (apStructTypeLists[player][pResearch->pStructureResults[inc]] != REDUNDANT)
|
|
{
|
|
apStructTypeLists[player][pResearch->pStructureResults[inc]] = AVAILABLE;
|
|
}
|
|
}
|
|
|
|
//check for structures to be made redundant
|
|
for (int inc = 0; inc < pResearch->pRedStructs.size(); inc++)
|
|
{
|
|
apStructTypeLists[player][pResearch->pRedStructs[inc]] = REDUNDANT;
|
|
}
|
|
|
|
//check for component replacement
|
|
if (pResearch->componentReplacement.size() != 0)
|
|
{
|
|
for (int ri = 0; ri < pResearch->componentReplacement.size(); ri++)
|
|
{
|
|
COMPONENT_STATS *pOldComp = pResearch->componentReplacement[ri].pOldComponent;
|
|
replaceComponent(pResearch->componentReplacement[ri].pNewComponent, pOldComp, player);
|
|
apCompLists[player][pOldComp->compType][pOldComp->index] = REDUNDANT;
|
|
}
|
|
}
|
|
|
|
//check for artefacts to be made available
|
|
for (int inc = 0; inc < pResearch->componentResults.size(); inc++)
|
|
{
|
|
//determine the type of artefact
|
|
COMPONENT_TYPE type = pResearch->componentResults[inc]->compType;
|
|
//set the component state to AVAILABLE
|
|
int compInc = pResearch->componentResults[inc]->index;
|
|
if (apCompLists[player][type][compInc] != REDUNDANT)
|
|
{
|
|
apCompLists[player][type][compInc] = AVAILABLE;
|
|
}
|
|
//check for default sensor
|
|
if (type == COMP_SENSOR && (asSensorStats + compInc)->location == LOC_DEFAULT)
|
|
{
|
|
aDefaultSensor[player] = compInc;
|
|
}
|
|
//check for default ECM
|
|
else if (type == COMP_ECM && (asECMStats + compInc)->location == LOC_DEFAULT)
|
|
{
|
|
aDefaultECM[player] = compInc;
|
|
}
|
|
//check for default Repair
|
|
else if (type == COMP_REPAIRUNIT && (asRepairStats + compInc)->location == LOC_DEFAULT)
|
|
{
|
|
aDefaultRepair[player] = compInc;
|
|
enableSelfRepair(player);
|
|
}
|
|
}
|
|
|
|
//check for artefacts to be made redundant
|
|
for (int inc = 0; inc < pResearch->pRedArtefacts.size(); inc++)
|
|
{
|
|
COMPONENT_TYPE type = pResearch->pRedArtefacts[inc]->compType;
|
|
apCompLists[player][type][pResearch->pRedArtefacts[inc]->index] = REDUNDANT;
|
|
}
|
|
|
|
//Add message to player's list if Major Topic
|
|
if ((pResearch->techCode == TC_MAJOR) && bDisplay)
|
|
{
|
|
//only play sound if major topic
|
|
if (player == selectedPlayer)
|
|
{
|
|
audio_QueueTrack(ID_SOUND_MAJOR_RESEARCH);
|
|
}
|
|
|
|
//check there is viewdata for the research topic - just don't add message if not!
|
|
if (pResearch->pViewData != NULL)
|
|
{
|
|
pMessage = addMessage(MSG_RESEARCH, false, player);
|
|
if (pMessage != NULL)
|
|
{
|
|
pMessage->pViewData = (MSG_VIEWDATA *)pResearch->pViewData;
|
|
}
|
|
}
|
|
}
|
|
else if (player == selectedPlayer && bDisplay)
|
|
{
|
|
audio_QueueTrack(ID_SOUND_RESEARCH_COMPLETED);
|
|
}
|
|
|
|
if (player == selectedPlayer && bDisplay)
|
|
{
|
|
//add console text message
|
|
if (pResearch->pViewData != NULL)
|
|
{
|
|
snprintf(consoleMsg, MAX_RESEARCH_MSG_SIZE, _("Research completed: %s"), _(pResearch->pViewData->textMsg[0].toUtf8().constData()));
|
|
addConsoleMessage(consoleMsg, LEFT_JUSTIFY, SYSTEM_MESSAGE);
|
|
}
|
|
else
|
|
{
|
|
addConsoleMessage(_("Research Completed"), LEFT_JUSTIFY, SYSTEM_MESSAGE);
|
|
}
|
|
}
|
|
|
|
if (psResearchFacility)
|
|
{
|
|
psResearchFacility->pFunctionality->researchFacility.psSubject = NULL; // Make sure topic is cleared
|
|
}
|
|
if ((bMultiPlayer || player == selectedPlayer) && bTrigger)
|
|
{
|
|
psCBLastResearch = pResearch; // Fun with pointers. Throw them into some random global variable, and get Nexus to absorb them.
|
|
CBResFacilityOwner = player;
|
|
psCBLastResStructure = psResearchFacility;
|
|
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_RESEARCHCOMPLETED);
|
|
psCBLastResStructure = NULL;
|
|
CBResFacilityOwner = -1;
|
|
psCBLastResearch = NULL;
|
|
}
|
|
triggerEventResearched(pResearch, psResearchFacility, player);
|
|
}
|
|
|
|
/*This function is called when the research files are reloaded*/
|
|
bool ResearchShutDown(void)
|
|
{
|
|
ResearchRelease();
|
|
return true;
|
|
}
|
|
|
|
/*This function is called when a game finishes*/
|
|
void ResearchRelease(void)
|
|
{
|
|
asResearch.clear();
|
|
for (int i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
asPlayerResList[i].clear();
|
|
}
|
|
}
|
|
|
|
/*puts research facility on hold*/
|
|
void holdResearch(STRUCTURE *psBuilding, QUEUE_MODE mode)
|
|
{
|
|
ASSERT( psBuilding->pStructureType->type == REF_RESEARCH,
|
|
"holdResearch: structure not a research facility" );
|
|
|
|
RESEARCH_FACILITY *psResFac = &psBuilding->pFunctionality->researchFacility;
|
|
|
|
if (mode == ModeQueue)
|
|
{
|
|
sendStructureInfo(psBuilding, STRUCTUREINFO_HOLDRESEARCH, NULL);
|
|
setStatusPendingHold(*psResFac);
|
|
return;
|
|
}
|
|
|
|
if (psResFac->psSubject)
|
|
{
|
|
//set the time the research facilty was put on hold
|
|
psResFac->timeStartHold = gameTime;
|
|
//play audio to indicate on hold
|
|
if (psBuilding->player == selectedPlayer)
|
|
{
|
|
audio_PlayTrack(ID_SOUND_WINDOWCLOSE);
|
|
}
|
|
}
|
|
|
|
delPowerRequest(psBuilding);
|
|
}
|
|
|
|
/*release a research facility from hold*/
|
|
void releaseResearch(STRUCTURE *psBuilding, QUEUE_MODE mode)
|
|
{
|
|
ASSERT( psBuilding->pStructureType->type == REF_RESEARCH,
|
|
"releaseResearch: structure not a research facility" );
|
|
|
|
RESEARCH_FACILITY *psResFac = &psBuilding->pFunctionality->researchFacility;
|
|
|
|
if (mode == ModeQueue)
|
|
{
|
|
sendStructureInfo(psBuilding, STRUCTUREINFO_RELEASERESEARCH, NULL);
|
|
setStatusPendingRelease(*psResFac);
|
|
return;
|
|
}
|
|
|
|
if (psResFac->psSubject && psResFac->timeStartHold)
|
|
{
|
|
//adjust the start time for the current subject
|
|
psResFac->timeStartHold = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
|
|
Cancel All Research for player 0
|
|
|
|
*/
|
|
void CancelAllResearch(UDWORD pl)
|
|
{
|
|
STRUCTURE *psCurr;
|
|
|
|
for (psCurr=apsStructLists[pl]; psCurr != NULL; psCurr = psCurr->psNext)
|
|
{
|
|
if (psCurr->pStructureType->type == REF_RESEARCH)
|
|
{
|
|
if (
|
|
( ((RESEARCH_FACILITY *)psCurr->pFunctionality)!=NULL )
|
|
&& ( ((RESEARCH_FACILITY *)psCurr->pFunctionality)->psSubject !=NULL )
|
|
)
|
|
{
|
|
debug( LOG_NEVER, "canceling research for %p\n", psCurr );
|
|
cancelResearch(psCurr, ModeQueue);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/** Sets the status of the topic to cancelled and stores the current research points accquired */
|
|
void cancelResearch(STRUCTURE *psBuilding, QUEUE_MODE mode)
|
|
{
|
|
UDWORD topicInc;
|
|
PLAYER_RESEARCH *pPlayerRes;
|
|
|
|
ASSERT(psBuilding->pStructureType->type == REF_RESEARCH, "Structure not a research facility");
|
|
|
|
RESEARCH_FACILITY *psResFac = &psBuilding->pFunctionality->researchFacility;
|
|
if( !(RESEARCH *)psResFac->psSubject)
|
|
{
|
|
debug(LOG_SYNC, "Invalid research topic");
|
|
return;
|
|
}
|
|
topicInc = ((RESEARCH *)psResFac->psSubject)->index;
|
|
ASSERT_OR_RETURN(, topicInc <= asResearch.size(), "Invalid research topic %u (max %d)", topicInc, (int)asResearch.size());
|
|
pPlayerRes = &asPlayerResList[psBuilding->player][topicInc];
|
|
if (psBuilding->pStructureType->type == REF_RESEARCH)
|
|
{
|
|
if (mode == ModeQueue)
|
|
{
|
|
// Tell others that we want to stop researching something.
|
|
sendResearchStatus(psBuilding, topicInc, psBuilding->player, false);
|
|
// Immediately tell the UI that we can research this now. (But don't change the game state.)
|
|
MakeResearchCancelledPending(pPlayerRes);
|
|
setStatusPendingCancel(*psResFac);
|
|
return; // Wait for our message before doing anything. (Whatever this function does...)
|
|
}
|
|
|
|
//check if waiting to accrue power
|
|
if (pPlayerRes->currentPoints == 0)
|
|
{
|
|
// Reset this topic as not having been researched
|
|
ResetResearchStatus(pPlayerRes);
|
|
}
|
|
else
|
|
{
|
|
// Set the researched flag
|
|
MakeResearchCancelled(pPlayerRes);
|
|
}
|
|
|
|
// Initialise the research facility's subject
|
|
psResFac->psSubject = NULL;
|
|
|
|
delPowerRequest(psBuilding);
|
|
}
|
|
}
|
|
|
|
/* For a given view data get the research this is related to */
|
|
RESEARCH *getResearchForMsg(VIEWDATA *pViewData)
|
|
{
|
|
for (int inc = 0; inc < asResearch.size(); inc++)
|
|
{
|
|
if (asResearch[inc].pViewData == pViewData) // compare the pointer
|
|
{
|
|
return &asResearch[inc];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//set the iconID based on the name read in in the stats
|
|
static UWORD setIconID(char *pIconName, const char *pName)
|
|
{
|
|
//compare the names with those created in 'Framer'
|
|
if (!strcmp(pIconName, "IMAGE_ROCKET"))
|
|
{
|
|
return IMAGE_ROCKET;
|
|
}
|
|
if (!strcmp(pIconName, "IMAGE_CANNON"))
|
|
{
|
|
return IMAGE_CANNON;
|
|
}
|
|
if (!strcmp(pIconName, "IMAGE_HOVERCRAFT"))
|
|
{
|
|
return IMAGE_HOVERCRAFT;
|
|
}
|
|
if (!strcmp(pIconName, "IMAGE_ECM"))
|
|
{
|
|
return IMAGE_ECM;
|
|
}
|
|
if (!strcmp(pIconName, "IMAGE_PLASCRETE"))
|
|
{
|
|
return IMAGE_PLASCRETE;
|
|
}
|
|
if (!strcmp(pIconName, "IMAGE_TRACKS"))
|
|
{
|
|
return IMAGE_TRACKS;
|
|
}
|
|
|
|
if (!strcmp(pIconName, "IMAGE_RES_DROIDTECH"))
|
|
{
|
|
return IMAGE_RES_DROIDTECH;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_WEAPONTECH"))
|
|
{
|
|
return IMAGE_RES_WEAPONTECH;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_COMPUTERTECH"))
|
|
{
|
|
return IMAGE_RES_COMPUTERTECH;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_POWERTECH"))
|
|
{
|
|
return IMAGE_RES_POWERTECH;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_SYSTEMTECH"))
|
|
{
|
|
return IMAGE_RES_SYSTEMTECH;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_STRUCTURETECH"))
|
|
{
|
|
return IMAGE_RES_STRUCTURETECH;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_CYBORGTECH"))
|
|
{
|
|
return IMAGE_RES_CYBORGTECH;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_DEFENCE"))
|
|
{
|
|
return IMAGE_RES_DEFENCE;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_QUESTIONMARK"))
|
|
{
|
|
return IMAGE_RES_QUESTIONMARK;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_GRPACC"))
|
|
{
|
|
return IMAGE_RES_GRPACC;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_GRPUPG"))
|
|
{
|
|
return IMAGE_RES_GRPUPG;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_GRPREP"))
|
|
{
|
|
return IMAGE_RES_GRPREP;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_GRPROF"))
|
|
{
|
|
return IMAGE_RES_GRPROF;
|
|
}
|
|
|
|
if (!strcmp(pIconName,"IMAGE_RES_GRPDAM"))
|
|
{
|
|
return IMAGE_RES_GRPDAM;
|
|
}
|
|
|
|
// Add more names as images are created
|
|
ASSERT( false, "Invalid icon graphic %s for topic %s", pIconName, pName );
|
|
|
|
return NO_RESEARCH_ICON; // Should never get here.
|
|
}
|
|
|
|
|
|
SDWORD mapRIDToIcon( UDWORD rid )
|
|
{
|
|
switch(rid)
|
|
{
|
|
case RID_ROCKET:
|
|
return(IMAGE_ROCKET);
|
|
break;
|
|
case RID_CANNON:
|
|
return(IMAGE_CANNON);
|
|
break;
|
|
case RID_HOVERCRAFT:
|
|
return(IMAGE_HOVERCRAFT);
|
|
break;
|
|
case RID_ECM:
|
|
return(IMAGE_ECM);
|
|
break;
|
|
case RID_PLASCRETE:
|
|
return(IMAGE_PLASCRETE);
|
|
break;
|
|
case RID_TRACKS:
|
|
return(IMAGE_TRACKS);
|
|
break;
|
|
case RID_DROIDTECH:
|
|
return(IMAGE_RES_DROIDTECH);
|
|
break;
|
|
case RID_WEAPONTECH:
|
|
return(IMAGE_RES_WEAPONTECH);
|
|
break;
|
|
case RID_COMPUTERTECH:
|
|
return(IMAGE_RES_COMPUTERTECH);
|
|
break;
|
|
case RID_POWERTECH:
|
|
return(IMAGE_RES_POWERTECH);
|
|
break;
|
|
case RID_SYSTEMTECH:
|
|
return(IMAGE_RES_SYSTEMTECH);
|
|
break;
|
|
case RID_STRUCTURETECH:
|
|
return(IMAGE_RES_STRUCTURETECH);
|
|
break;
|
|
case RID_CYBORGTECH:
|
|
return(IMAGE_RES_CYBORGTECH);
|
|
break;
|
|
case RID_DEFENCE:
|
|
return(IMAGE_RES_DEFENCE);
|
|
break;
|
|
case RID_QUESTIONMARK:
|
|
return(IMAGE_RES_QUESTIONMARK);
|
|
break;
|
|
case RID_GRPACC:
|
|
return(IMAGE_RES_GRPACC);
|
|
break;
|
|
case RID_GRPUPG:
|
|
return(IMAGE_RES_GRPUPG);
|
|
break;
|
|
case RID_GRPREP:
|
|
return(IMAGE_RES_GRPREP);
|
|
break;
|
|
case RID_GRPROF:
|
|
return(IMAGE_RES_GRPROF);
|
|
break;
|
|
case RID_GRPDAM:
|
|
return(IMAGE_RES_GRPDAM);
|
|
break;
|
|
|
|
default:
|
|
ASSERT( false,"Weirdy mapping request for RID to icon" );
|
|
return(-1); //pass back a value that can never have been set up
|
|
break;
|
|
}
|
|
}
|
|
|
|
SDWORD mapIconToRID(UDWORD iconID)
|
|
{
|
|
switch(iconID)
|
|
{
|
|
case IMAGE_ROCKET:
|
|
return(RID_ROCKET);
|
|
break;
|
|
case IMAGE_CANNON:
|
|
return(RID_CANNON);
|
|
break;
|
|
case IMAGE_HOVERCRAFT:
|
|
return(RID_HOVERCRAFT);
|
|
break;
|
|
case IMAGE_ECM:
|
|
return(RID_ECM);
|
|
break;
|
|
case IMAGE_PLASCRETE:
|
|
return(RID_PLASCRETE);
|
|
break;
|
|
case IMAGE_TRACKS:
|
|
return(RID_TRACKS);
|
|
break;
|
|
case IMAGE_RES_DROIDTECH:
|
|
return(RID_DROIDTECH);
|
|
break;
|
|
case IMAGE_RES_WEAPONTECH:
|
|
return(RID_WEAPONTECH);
|
|
break;
|
|
case IMAGE_RES_COMPUTERTECH:
|
|
return(RID_COMPUTERTECH);
|
|
break;
|
|
case IMAGE_RES_POWERTECH:
|
|
return(RID_POWERTECH);
|
|
break;
|
|
case IMAGE_RES_SYSTEMTECH:
|
|
return(RID_SYSTEMTECH);
|
|
break;
|
|
case IMAGE_RES_STRUCTURETECH:
|
|
return(RID_STRUCTURETECH);
|
|
break;
|
|
case IMAGE_RES_CYBORGTECH:
|
|
return(RID_CYBORGTECH);
|
|
break;
|
|
case IMAGE_RES_DEFENCE:
|
|
return(RID_DEFENCE);
|
|
break;
|
|
case IMAGE_RES_QUESTIONMARK:
|
|
return(RID_QUESTIONMARK);
|
|
break;
|
|
case IMAGE_RES_GRPACC:
|
|
return(RID_GRPACC);
|
|
break;
|
|
case IMAGE_RES_GRPUPG:
|
|
return(RID_GRPUPG);
|
|
break;
|
|
case IMAGE_RES_GRPREP:
|
|
return(RID_GRPREP);
|
|
break;
|
|
case IMAGE_RES_GRPROF:
|
|
return(RID_GRPROF);
|
|
break;
|
|
case IMAGE_RES_GRPDAM:
|
|
return(RID_GRPDAM);
|
|
break;
|
|
default:
|
|
return(-1); //pass back a value that can never have been set up
|
|
break;
|
|
}
|
|
}
|
|
|
|
//return a pointer to a research topic based on the name
|
|
RESEARCH *getResearch(const char *pName)
|
|
{
|
|
for (int inc = 0; inc < asResearch.size(); inc++)
|
|
{
|
|
if (asResearch[inc].id.compare(pName) == 0)
|
|
{
|
|
return &asResearch[inc];
|
|
}
|
|
}
|
|
debug(LOG_WARNING, "Unknown research - %s", pName);
|
|
return NULL;
|
|
}
|
|
|
|
/* looks through the players lists of structures and droids to see if any are using
|
|
the old component - if any then replaces them with the new component */
|
|
static void replaceComponent(COMPONENT_STATS *pNewComponent, COMPONENT_STATS *pOldComponent,
|
|
UBYTE player)
|
|
{
|
|
DROID_TEMPLATE *psTemplates;
|
|
COMPONENT_TYPE oldType = pOldComponent->compType;
|
|
int oldCompInc = pOldComponent->index;
|
|
COMPONENT_TYPE newType = pNewComponent->compType;
|
|
int newCompInc = pNewComponent->index;
|
|
|
|
//check old and new type are the same
|
|
if (oldType != newType)
|
|
{
|
|
return;
|
|
}
|
|
|
|
replaceDroidComponent(apsDroidLists[player], oldType, oldCompInc, newCompInc);
|
|
replaceDroidComponent(mission.apsDroidLists[player], oldType, oldCompInc, newCompInc);
|
|
replaceDroidComponent(apsLimboDroids[player], oldType, oldCompInc, newCompInc);
|
|
|
|
//check thru the templates
|
|
for (psTemplates = apsDroidTemplates[player]; psTemplates != NULL; psTemplates = psTemplates->psNext)
|
|
{
|
|
switch(oldType)
|
|
{
|
|
case COMP_BODY:
|
|
case COMP_BRAIN:
|
|
case COMP_PROPULSION:
|
|
case COMP_REPAIRUNIT:
|
|
case COMP_ECM:
|
|
case COMP_SENSOR:
|
|
case COMP_CONSTRUCT:
|
|
if (psTemplates->asParts[oldType] == (SDWORD)oldCompInc)
|
|
{
|
|
psTemplates->asParts[oldType] = newCompInc;
|
|
}
|
|
break;
|
|
case COMP_WEAPON:
|
|
for (int inc = 0; inc < psTemplates->numWeaps; inc++)
|
|
{
|
|
if (psTemplates->asWeaps[inc] == oldCompInc)
|
|
{
|
|
psTemplates->asWeaps[inc] = newCompInc;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
//unknown comp type
|
|
debug( LOG_ERROR, "Unknown component type - invalid Template" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
replaceStructureComponent(apsStructLists[player], oldType, oldCompInc, newCompInc, player);
|
|
replaceStructureComponent(mission.apsStructLists[player], oldType, oldCompInc, newCompInc, player);
|
|
}
|
|
|
|
/*Looks through all the currently allocated stats to check the name is not
|
|
a duplicate*/
|
|
static bool checkResearchName(RESEARCH *psResearch, UDWORD numStats)
|
|
{
|
|
for (int inc = 0; inc < numStats; inc++)
|
|
{
|
|
|
|
ASSERT_OR_RETURN(false, asResearch[inc].id.compare(psResearch->id) != 0,
|
|
"Research name has already been used - %s", getName(psResearch));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Sets the 'possible' flag for a player's research so the topic will appear in
|
|
the research list next time the Research Facilty is selected */
|
|
bool enableResearch(RESEARCH *psResearch, UDWORD player)
|
|
{
|
|
UDWORD inc;
|
|
|
|
ASSERT_OR_RETURN(false, psResearch, "No such research topic");
|
|
|
|
inc = psResearch->index;
|
|
if (inc > asResearch.size())
|
|
{
|
|
ASSERT( false, "enableResearch: Invalid research topic - %s", getName(psResearch) );
|
|
return false;
|
|
}
|
|
|
|
int prevState = intGetResearchState();
|
|
|
|
//found, so set the flag
|
|
MakeResearchPossible(&asPlayerResList[player][inc]);
|
|
|
|
if (player == selectedPlayer)
|
|
{
|
|
//set the research reticule button to flash if research facility is free
|
|
intNotifyResearchButton(prevState);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*find the last research topic of importance that the losing player did and
|
|
'give' the results to the reward player*/
|
|
void researchReward(UBYTE losingPlayer, UBYTE rewardPlayer)
|
|
{
|
|
UDWORD topicIndex = 0, researchPoints = 0, rewardID = 0;
|
|
STRUCTURE *psStruct;
|
|
RESEARCH_FACILITY *psFacility;
|
|
|
|
//look through the losing players structures to find a research facility
|
|
for (psStruct = apsStructLists[losingPlayer]; psStruct != NULL; psStruct =
|
|
psStruct->psNext)
|
|
{
|
|
if (psStruct->pStructureType->type == REF_RESEARCH)
|
|
{
|
|
psFacility = (RESEARCH_FACILITY *)psStruct->pFunctionality;
|
|
if (psFacility->psBestTopic)
|
|
{
|
|
topicIndex = ((RESEARCH *)psFacility->psBestTopic)->ref -
|
|
REF_RESEARCH_START;
|
|
if (topicIndex)
|
|
{
|
|
//if it cost more - it is better (or should be)
|
|
if (researchPoints < asResearch[topicIndex].researchPoints)
|
|
{
|
|
//store the 'best' topic
|
|
researchPoints = asResearch[topicIndex].researchPoints;
|
|
rewardID = topicIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//if a topic was found give the reward player the results of that research
|
|
if (rewardID)
|
|
{
|
|
researchResult(rewardID, rewardPlayer, true, NULL, true);
|
|
if (rewardPlayer == selectedPlayer)
|
|
{
|
|
//name the actual reward
|
|
CONPRINTF(ConsoleString,(ConsoleString,"%s :- %s",
|
|
_("Research Award"),
|
|
getName(&asResearch[rewardID])));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*flag self repair so droids can start when idle*/
|
|
void enableSelfRepair(UBYTE player)
|
|
{
|
|
bSelfRepair[player] = true;
|
|
}
|
|
|
|
/*check to see if any research has been completed that enables self repair*/
|
|
bool selfRepairEnabled(UBYTE player)
|
|
{
|
|
if (bSelfRepair[player])
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*checks the stat to see if its of type wall or defence*/
|
|
bool wallDefenceStruct(STRUCTURE_STATS *psStats)
|
|
{
|
|
if (psStats->type == REF_DEFENSE || psStats->type == REF_WALL || psStats->type == REF_GATE
|
|
|| psStats->type == REF_WALLCORNER || psStats->type == REF_GENERIC)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*for a given list of droids, replace the old component if exists*/
|
|
void replaceDroidComponent(DROID *pList, UDWORD oldType, UDWORD oldCompInc,
|
|
UDWORD newCompInc)
|
|
{
|
|
DROID *psDroid;
|
|
|
|
//check thru the droids
|
|
for (psDroid = pList; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
switchComponent(psDroid, oldType, oldCompInc, newCompInc);
|
|
// Need to replace the units inside the transporter
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
replaceTransDroidComponents(psDroid, oldType, oldCompInc, newCompInc);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*replaces any components necessary for units that are inside a transporter*/
|
|
void replaceTransDroidComponents(DROID *psTransporter, UDWORD oldType,
|
|
UDWORD oldCompInc, UDWORD newCompInc)
|
|
{
|
|
DROID *psCurr;
|
|
|
|
ASSERT ((psTransporter->droidType == DROID_TRANSPORTER || psTransporter->droidType == DROID_SUPERTRANSPORTER), "invalid unit type" );
|
|
|
|
for (psCurr = psTransporter->psGroup->psList; psCurr != NULL; psCurr =
|
|
psCurr->psGrpNext)
|
|
{
|
|
if (psCurr != psTransporter)
|
|
{
|
|
switchComponent(psCurr, oldType, oldCompInc, newCompInc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void replaceStructureComponent(STRUCTURE *pList, UDWORD oldType, UDWORD oldCompInc,
|
|
UDWORD newCompInc, UBYTE player)
|
|
{
|
|
STRUCTURE *psStructure;
|
|
int inc;
|
|
|
|
// If the type is not one we are interested in, then don't bother checking
|
|
if (!(oldType == COMP_ECM || oldType == COMP_SENSOR || oldType == COMP_WEAPON))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//check thru the structures
|
|
for (psStructure = pList; psStructure != NULL; psStructure = psStructure->psNext)
|
|
{
|
|
switch (oldType)
|
|
{
|
|
case COMP_WEAPON:
|
|
for (inc=0; inc < psStructure->numWeaps; inc++)
|
|
{
|
|
if (psStructure->asWeaps[inc].nStat > 0)
|
|
{
|
|
if (psStructure->asWeaps[inc].nStat == oldCompInc)
|
|
{
|
|
psStructure->asWeaps[inc].nStat = newCompInc;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
//ignore all other component types
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*swaps the old component for the new one for a specific droid*/
|
|
static void switchComponent(DROID *psDroid, UDWORD oldType, UDWORD oldCompInc,
|
|
UDWORD newCompInc)
|
|
{
|
|
ASSERT(psDroid != NULL, "switchComponent:invalid droid pointer");
|
|
|
|
switch(oldType)
|
|
{
|
|
case COMP_BODY:
|
|
case COMP_BRAIN:
|
|
case COMP_PROPULSION:
|
|
case COMP_REPAIRUNIT:
|
|
case COMP_ECM:
|
|
case COMP_SENSOR:
|
|
case COMP_CONSTRUCT:
|
|
if (psDroid->asBits[oldType] == oldCompInc)
|
|
{
|
|
psDroid->asBits[oldType] = (UBYTE)newCompInc;
|
|
}
|
|
break;
|
|
case COMP_WEAPON:
|
|
// Can only be one weapon now
|
|
if (psDroid->asWeaps[0].nStat > 0)
|
|
{
|
|
if (psDroid->asWeaps[0].nStat == oldCompInc)
|
|
{
|
|
psDroid->asWeaps[0].nStat = newCompInc;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
//unknown comp type
|
|
debug( LOG_ERROR, "Unknown component type - invalid droid" );
|
|
abort();
|
|
return;
|
|
}
|
|
}
|
|
|
|
static inline bool allyResearchSortFunction(AllyResearch const &a, AllyResearch const &b)
|
|
{
|
|
if (a.active != b.active) { return a.active; }
|
|
if (a.timeToResearch != b.timeToResearch) { return (unsigned)a.timeToResearch < (unsigned)b.timeToResearch; } // Unsigned cast = sort -1 as infinite.
|
|
if (a.powerNeeded != b.powerNeeded) { return (unsigned)a.powerNeeded < (unsigned)b.powerNeeded; }
|
|
if (a.completion != b.completion) { return a.completion > b.completion; }
|
|
return a.player < b.player;
|
|
}
|
|
|
|
std::vector<AllyResearch> const &listAllyResearch(unsigned ref)
|
|
{
|
|
static uint32_t lastGameTime = ~0;
|
|
static std::map<unsigned, std::vector<AllyResearch> > researches;
|
|
static const std::vector<AllyResearch> noAllyResearch;
|
|
|
|
if (gameTime != lastGameTime)
|
|
{
|
|
lastGameTime = gameTime;
|
|
researches.clear();
|
|
|
|
for (int player = 0; player < MAX_PLAYERS; ++player)
|
|
{
|
|
if (player == selectedPlayer || !aiCheckAlliances(selectedPlayer, player) || !alliancesSharedResearch(game.alliance))
|
|
{
|
|
continue; // Skip this player, not an ally.
|
|
}
|
|
|
|
// Check each research facility to see if they are doing this topic. (As opposed to having started the topic, but stopped researching it.)
|
|
for (STRUCTURE *psStruct = apsStructLists[player]; psStruct != NULL; psStruct = psStruct->psNext)
|
|
{
|
|
RESEARCH_FACILITY *res = (RESEARCH_FACILITY *)psStruct->pFunctionality;
|
|
if (psStruct->pStructureType->type != REF_RESEARCH || res->psSubject == NULL)
|
|
{
|
|
continue; // Not a researching research facility.
|
|
}
|
|
|
|
RESEARCH const &subject = *res->psSubject;
|
|
PLAYER_RESEARCH const &playerRes = asPlayerResList[player][subject.index];
|
|
unsigned cRef = subject.ref;
|
|
|
|
AllyResearch r;
|
|
r.player = player;
|
|
r.completion = playerRes.currentPoints;
|
|
r.powerNeeded = checkPowerRequest(psStruct);
|
|
r.timeToResearch = -1;
|
|
if (r.powerNeeded == -1)
|
|
{
|
|
r.timeToResearch = (subject.researchPoints - playerRes.currentPoints)
|
|
/ MAX(psStruct->pStructureType->base.research, 1u);
|
|
}
|
|
r.active = psStruct->status == SS_BUILT;
|
|
researches[cRef].push_back(r);
|
|
}
|
|
}
|
|
for (std::map<unsigned, std::vector<AllyResearch> >::iterator i = researches.begin(); i != researches.end(); ++i)
|
|
{
|
|
std::sort(i->second.begin(), i->second.end(), allyResearchSortFunction);
|
|
}
|
|
}
|
|
|
|
std::map<unsigned, std::vector<AllyResearch> >::const_iterator i = researches.find(ref);
|
|
if (i == researches.end())
|
|
{
|
|
return noAllyResearch;
|
|
}
|
|
return i->second;
|
|
}
|