/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2012  Warzone 2100 Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
 * Research.c
 *
 * Research tree and functions!
 *
 */
#include <string.h>

#include "lib/framework/frame.h"
#include "lib/framework/strres.h"
#include "lib/framework/frameresource.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"

//used to calc the research power
#define RESEARCH_FACTOR		32
#define RESEARCH_MAX_POWER  450

// 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, char *pName);
static COMPONENT_STATS * getComponentDetails(char *pName, char *pCompName);
static void replaceComponent(COMPONENT_STATS *pNewComponent, COMPONENT_STATS *pOldComponent,
					  UBYTE player);
static bool checkResearchName(RESEARCH *psRes, UDWORD numStats);

static const char *getResearchName(RESEARCH *pResearch)
{
	return(getName(pResearch->pName));
}

//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(const char *pResearchData, UDWORD bufferSize)
{
	unsigned int researchCount = numCR(pResearchData, bufferSize);
	COMPONENT_STATS *psComp;
	SDWORD structID;
	UDWORD i, keyTopic, techCode, resPoints;
	char ResearchName[MAX_STR_LENGTH];
	char msgName[MAX_STR_LENGTH], iconID[MAX_STR_LENGTH];
	char imdName[MAX_STR_LENGTH], imdName2[MAX_STR_LENGTH];
	char structName[MAX_STR_LENGTH], compName[MAX_STR_LENGTH],
		compType[MAX_STR_LENGTH];
	PLAYER_RESEARCH dummy;

	memset(&dummy, 0, sizeof(dummy));

	// Skip descriptive header
	if (strncmp(pResearchData,"Research ",9)==0)
	{
		pResearchData = strchr(pResearchData,'\n') + 1;
		researchCount--;
	}

	for (i = 0; i < researchCount; i++)
	{
		// 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);
		}

		RESEARCH research;

		research.index = i;
		//read the data into the storage - the data is delimeted using comma's
		ResearchName[0] = '\0';
		sscanf(pResearchData,"%255[^,'\r\n],", ResearchName);

		//allocate storage for the name
		research.pName = allocateName(ResearchName);
		ASSERT_OR_RETURN(false, research.pName != NULL, "Failed allocating research name");

		//check the name hasn't been used already
		ASSERT_OR_RETURN(false, checkResearchName(&research, i), "Research name %s used already", research.pName);

		pResearchData += (strlen(ResearchName)+1);
		research.ref = REF_RESEARCH_START + i;

		//determine the tech level (unused, so we don't use the resulting string)
		ResearchName[0] = '\0';
		sscanf(pResearchData,"%255[^,'\r\n],", ResearchName);
		pResearchData += (strlen(ResearchName)+1);

		ResearchName[0] = '\0';
		sscanf(pResearchData,"%255[^,'\r\n],", ResearchName);

		if (strcmp(ResearchName, "0"))
		{
			research.subGroup = setIconID(ResearchName, research.pName);
		}
		else
		{
			research.subGroup = NO_RESEARCH_ICON;
		}

		pResearchData += (strlen(ResearchName)+1);

		iconID[0] = '\0';
		imdName[0] = '\0';
		imdName2[0] = '\0';
		msgName[0] = '\0';
		structName[0] = '\0';
		compName[0] = '\0';
		compType[0] = '\0';

		{
			UDWORD numPRRequired;
			UDWORD numFunctions;
			UDWORD numStructures;
			UDWORD numRedStructs;
			UDWORD numStructResults;
			UDWORD numRedArtefacts;
			UDWORD numArteResults;

			sscanf(pResearchData,"%d,%255[^,'\r\n],%255[^,'\r\n],%255[^,'\r\n],%255[^,'\r\n],%255[^,'\r\n], \
			       %255[^,'\r\n],%255[^,'\r\n],%d,%d,%d,%d,%d,%d,%d,%d,%d",
				&techCode, iconID, imdName, imdName2, msgName,
			       structName, compName, compType,
				&resPoints, &keyTopic, &numPRRequired,
				&numFunctions, &numStructures,
				&numRedStructs, &numStructResults,
				&numRedArtefacts, &numArteResults);
		}

		//set keytopic flag
		if (keyTopic)
		{
			research.keyTopic = true;
		}
		else
		{
			research.keyTopic = false;
		}

		//check the tech code is valid
		ASSERT_OR_RETURN(false, techCode <= 1, "Invalid tech code for research topic - %s ", getResearchName(&research));
		if (techCode == 0)
		{
			research.techCode = TC_MAJOR;
		}
		else
		{
			research.techCode = TC_MINOR;
		}

		//set the iconID
		if (strcmp(iconID, "0"))
		{
			research.iconID = setIconID(iconID, research.pName);
		}
		else
		{
			research.iconID = NO_RESEARCH_ICON;
		}

		//get the IMDs used in the interface
		if (strcmp(structName, "0"))
		{
			//find the structure stat
			structID = getStructStatFromName(structName);
			ASSERT_OR_RETURN(false, structID >= 0, "Cannot find the structure Stat for Research %s", getResearchName(&research));
			research.psStat = (BASE_STATS *)(asStructureStats + structID);
		}
		else if (strcmp(compName, "0"))
		{
			//find the component stat
			psComp = getComponentDetails(compType, compName);
			ASSERT_OR_RETURN(false, psComp != NULL, "Cannot find the component Stat for Research %s", getResearchName(&research));
			research.psStat = (BASE_STATS *)psComp;
		}
		else
		{
			research.psStat = NULL;
		}
		if (strcmp(imdName, "0"))
		{
			research.pIMD = (iIMDShape *) resGetData("IMD", imdName);
			ASSERT_OR_RETURN(false, research.pIMD != NULL, "Cannot find the research PIE for record %s", getResearchName(&research));
		}
		else
		{
			research.pIMD = NULL;
		}

		if (strcmp(imdName2, "0"))
		{
			research.pIMD2 = (iIMDShape *) resGetData("IMD", imdName2);
			ASSERT_OR_RETURN(false, research.pIMD2 != NULL, "Cannot find the 2nd research PIE for record %s", getResearchName(&research));
		}
		else
		{
			research.pIMD2 = NULL;
		}

		//get the message viewdata - if any
		if (strcmp(msgName, "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!", getResearchName(&research));
			if (research.techCode == TC_MAJOR)
			{
				research.pViewData = getViewData(msgName);
			}
		}

		//set the researchPoints
		ASSERT_OR_RETURN(false, resPoints <= UWORD_MAX, "Research Points too high for research topic - %s ", getResearchName(&research));
		research.researchPoints = (UWORD)resPoints;

		//set the research power
		research.researchPower = research.researchPoints / RESEARCH_FACTOR;
		if (research.researchPower > RESEARCH_MAX_POWER)
		{
			research.researchPower = RESEARCH_MAX_POWER;
		}

		//increment the pointer to the start of the next record
		pResearchData = strchr(pResearchData,'\n') + 1;
		asResearch.push_back(research);
	}

	return true;
}


//Load the pre-requisites for a research list
bool loadResearchPR(const char *pPRData, UDWORD bufferSize)
{
	unsigned NumToAlloc = numCR(pPRData, bufferSize);
	char				ResearchName[MAX_STR_LENGTH], PRName[MAX_STR_LENGTH];

	for (int i = 0; i < NumToAlloc; i++)
	{
		bool recFound = false;

		//read the data into the storage - the data is delimited using commas
		ResearchName[0] = '\0';
		PRName[0] = '\0';
		sscanf(pPRData,"%255[^,'\r\n],%255[^,'\r\n],%*d", ResearchName, PRName);

		//loop through each Research to compare the name
		for (int incR = 0; incR < asResearch.size(); incR++)
		{
			if (!(strcmp(ResearchName, asResearch[incR].pName)))
			{
				//Research found
				for (int incPR = 0; incPR < asResearch.size(); incPR++)
				{
					if (!(strcmp(PRName, asResearch[incPR].pName)))
					{
						//PRresearch found alloc this to the current Research
						asResearch[incR].pPRList.push_back(incPR);
						recFound = true;
						break;
					}
				}
				ASSERT_OR_RETURN(false, recFound, "Unable to find Pre-requisite %s for research %s", PRName, ResearchName);
				break;
			}
		}
		ASSERT_OR_RETURN(false, recFound, "Unable to find Research %s", ResearchName);
		// increment the pointer to the start of the next record
		pPRData = strchr(pPRData,'\n') + 1;
	}
	return true;
}

//Load the artefacts for a research list
bool loadResearchArtefacts(const char *pArteData, UDWORD bufferSize, UDWORD listNumber)
{
	unsigned NumToAlloc = numCR(pArteData, bufferSize);
	char			ResearchName[MAX_STR_LENGTH], ArteName[MAX_STR_LENGTH], TypeName[MAX_STR_LENGTH];
	COMPONENT_STATS		*pArtefact;
	UDWORD				newType;

	// Skip descriptive header
	if (strncmp(pArteData, "Research ", 9) == 0)
	{
		pArteData = strchr(pArteData, '\n') + 1;
		NumToAlloc--;
	}

	for (int i = 0; i < NumToAlloc; i++)
	{
		//read the data into the storage - the data is delimited using commas
		ResearchName[0] = '\0';
		ArteName[0] = '\0';
		TypeName[0] = '\0';
		sscanf(pArteData,"%255[^,'\r\n],%255[^,'\r\n],%255[^,'\r\n]", ResearchName, ArteName, TypeName);

		//increment the data pointer
		pArteData += (strlen(ResearchName)+1+strlen(ArteName)+1+strlen(TypeName)+1);

		pArtefact = getComponentDetails(TypeName, ArteName);
		if (pArtefact == NULL)
		{
			return false;
		}
		//get the type for comparison later
		newType = statType(pArtefact->ref);

		RESEARCH *pResearch = getResearch(ResearchName);
		if (pResearch == NULL)
		{
			return false;
		}

		//ArtefactResearch found - alloc the artefact to the current Research topic
		switch (listNumber)
		{
			case RED_LIST:
				pResearch->pRedArtefacts.push_back(pArtefact);
				break;
			case RES_LIST:
				pResearch->pArtefactResults.push_back(pArtefact);
				break;
			default:
				debug( LOG_ERROR, "Unknown research list" );
				abort();
				return false;
		}
		//deal with extra data
		switch (listNumber)
		{
			case RED_LIST:
				//ignore the last character
				sscanf(pArteData,",%*d");
				break;
			case RES_LIST:
				ArteName[0] = '\0';
				TypeName[0] = '\0';
				sscanf(pArteData, "%255[^,'\r\n],%255[^,'\r\n],%*d", ArteName, TypeName);
				if (!strcmp(ArteName, "0"))
				{
					pResearch->pReplacedArtefacts.push_back(NULL);
				}
				else
				{
					pArtefact = getComponentDetails(TypeName, ArteName);
					if (pArtefact == NULL)
					{
						return false;
					}
					//check the old and new types are the same
					if (statType(pArtefact->ref) != newType)
					{
						debug( LOG_ERROR, "You are trying to replace one type of component with a different type for research %s in ResultComponents.txt", ResearchName );
						abort();
						return false;
					}
					// ArtefactResearch found - alloc the artefact to the current Research topic
					pResearch->pReplacedArtefacts.push_back(pArtefact);
				}
				break;
			default:
				debug( LOG_ERROR, "Unknown research list" );
				abort();
				return false;
		}
		//increment the pointer to the start of the next record
		pArteData = strchr(pArteData,'\n') + 1;
	}

	return true;
}

//Load the Structures for a research list
bool loadResearchStructures(const char *pStructData, UDWORD bufferSize,UDWORD listNumber)
{
	unsigned NumToAlloc = numCR(pStructData, bufferSize);
	unsigned int i = 0;
	char				ResearchName[MAX_STR_LENGTH], StructureName[MAX_STR_LENGTH];
	UWORD				incR, incS;
	STRUCTURE_STATS		*pStructure = asStructureStats;
	bool				recFound;

	// Skip descriptive header
	if (strncmp(pStructData, "Research ", 9) == 0)
	{
		pStructData = strchr(pStructData, '\n') + 1;
		NumToAlloc--;
	}

	for (i = 0; i < NumToAlloc; i++)
	{
		recFound = false;

		//read the data into the storage - the data is delimited using comma's
		ResearchName[0] = '\0';
		StructureName[0] = '\0';
		sscanf(pStructData,"%255[^,'\r\n],%255[^,'\r\n],%*d,%*d", ResearchName, StructureName);

		//loop through each Research to compare the name
		for (incR = 0; incR < asResearch.size(); incR++)
		{
			if (!(strcmp(ResearchName, asResearch[incR].pName)))
			{
				//Research found
				for (incS = 0; incS < numStructureStats; incS++)
				{
					if (!(strcmp(StructureName, pStructure[incS].pName)))
					{
						//Structure found - alloc this to the current Research
						switch (listNumber)
						{
							case REQ_LIST:
								asResearch[incR].pStructList.push_back(incS);
								break;
							case RED_LIST:
								asResearch[incR].pRedStructs.push_back(incS);
								break;
							case RES_LIST:
								asResearch[incR].pStructureResults.push_back(incS);
								break;
							default:
								/* NO DEFAULT CASE? Alex.... Here ya go - just for you...*/
								debug( LOG_FATAL, "Unknown research list" );
								abort();
								return false;
						}
						recFound = true;
						break;
					}
				}
				//if Structure not found - error
				if (!recFound)
				{
					debug(LOG_FATAL, "Unable to find Structure %s for research %s", StructureName, ResearchName);
					return false;
				}
				else
				{
					break;
				}
			}
		}
		//if Research not found - error
		if (!recFound)
		{
			debug(LOG_FATAL, "Unable to allocate all Research Structures for %s", ResearchName);
			return false;
		}
		//increment the pointer to the start of the next record
		pStructData = strchr(pStructData,'\n') + 1;
	}

	return true;
}

//Load the pre-requisites for a research list
bool loadResearchFunctions(const char *pFunctionData, UDWORD bufferSize)
{
	unsigned NumToAlloc = numCR(pFunctionData, bufferSize);
	unsigned int i = 0;
	char				ResearchName[MAX_STR_LENGTH], FunctionName[MAX_STR_LENGTH];
	UDWORD				incR, incF;
	FUNCTION			**pFunction = asFunctions;
	bool				recFound;

	// Skip descriptive header
	if (strncmp(pFunctionData, "Research ", 9) == 0)
	{
		pFunctionData = strchr(pFunctionData, '\n') + 1;
		NumToAlloc--;
	}

	for (i=0; i < NumToAlloc; i++)
	{
		recFound = false;
		//read the data into the storage - the data is delimited using comma's
		ResearchName[0] = '\0';
		FunctionName[0] = '\0';
		sscanf(pFunctionData,"%255[^,'\r\n],%255[^,'\r\n],%*d", ResearchName, FunctionName);

		//loop through each Research to compare the name
		for (incR=0; incR < asResearch.size(); incR++)
		{
			if (!(strcmp(ResearchName, asResearch[incR].pName)))
			{
				//Research found
				for (incF=0; incF < numFunctions; incF++)
				{
					if (!(strcmp(FunctionName, (*pFunction[incF]).pName)))
					{
						// Function found alloc this to the current Research
						asResearch[incR].pFunctionList.push_back(pFunction[incF]);
						recFound = true;
						break;
					}
				}
				//if Function not found - error
				if (!recFound)
				{
					debug( LOG_ERROR, "Unable to find Function %s for research %s", FunctionName, ResearchName );
					abort();
					return false;
				}
				else
				{
					break;
				}
			}
		}
		//if Research not found - error
		if (!recFound)
		{
			debug( LOG_ERROR, "Unable to allocate all research Functions for %s", ResearchName );
			abort();
			return false;
		}

		//increment the pointer to the start of the next record
		pFunctionData = strchr(pFunctionData,'\n') + 1;
	}

	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];
	UDWORD						type, inc;
	STRUCTURE					*psCurr;
	DROID						*psDroid;
	FUNCTION					*pFunction;
	UDWORD						compInc;
	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 (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 (inc = 0; inc < pResearch->pRedStructs.size(); inc++)
	{
		apStructTypeLists[player][pResearch->pRedStructs[inc]] = REDUNDANT;
	}

	//check for artefacts to be made available
	for (inc = 0; inc < pResearch->pArtefactResults.size(); inc++)
	{
		//determine the type of artefact
		type = statType(pResearch->pArtefactResults[inc]->ref);
		//set the component state to AVAILABLE
		compInc = pResearch->pArtefactResults[inc]->ref - statRefStart(type);
		if (apCompLists[player][type][compInc] != REDUNDANT)
		{
			apCompLists[player][type][compInc] = AVAILABLE;
		}
		//check for default sensor
		if (type == COMP_SENSOR)
		{
			if ((asSensorStats + compInc)->location == LOC_DEFAULT)
			{
				aDefaultSensor[player] = compInc;
			}
		}
		//check for default ECM
		if (type == COMP_ECM)
		{
			if ((asECMStats + compInc)->location == LOC_DEFAULT)
			{
				aDefaultECM[player] = compInc;
			}
		}
		//check for default Repair
		if (type == COMP_REPAIRUNIT)
		{
			if ((asRepairStats + compInc)->location == LOC_DEFAULT)
			{
				aDefaultRepair[player] = compInc;
			}
		}
		//check if this component replaces an 'older' component
		if (pResearch->pReplacedArtefacts[inc] != NULL)
		{
			replaceComponent(pResearch->pArtefactResults[inc], pResearch->pReplacedArtefacts[inc], player);
			//set the 'old' component to unavailable
			type = statType(pResearch->pReplacedArtefacts[inc]->ref);
			//set the component state to REDUNDANT
			compInc = pResearch->pReplacedArtefacts[inc]->ref - statRefStart(type);
			apCompLists[player][type][compInc] = REDUNDANT;
		}
		//check if self repair has come on line
		if (type == COMP_REPAIRUNIT)
		{
			if (asRepairStats[compInc].location == LOC_DEFAULT)
			{
				enableSelfRepair(player);
			}
		}
	}

	//check for artefacts to be made redundant
	for (inc = 0; inc < pResearch->pRedArtefacts.size(); inc++)
	{
		// determine the type of artefact
		type = statType(pResearch->pRedArtefacts[inc]->ref);
		// set the component state to REDUNDANT
		apCompLists[player][type][pResearch->pRedArtefacts[inc]->ref - statRefStart(type)] = REDUNDANT;
	}

	//check for technology effects
	for (inc = 0; inc < pResearch->pFunctionList.size(); inc++)
	{
		pFunction = pResearch->pFunctionList[inc];

		switch (pFunction->type)
		{
			case(PRODUCTION_UPGRADE_TYPE):
				productionUpgrade(pFunction, player);
				// search the list of players structures for a Factory
				for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if ((psCurr->pStructureType->type == REF_FACTORY &&
						((PRODUCTION_UPGRADE_FUNCTION *)pFunction)->factory) ||
						(psCurr->pStructureType->type == REF_CYBORG_FACTORY &&
						((PRODUCTION_UPGRADE_FUNCTION *)pFunction)->cyborgFactory) ||
						(psCurr->pStructureType->type == REF_VTOL_FACTORY &&
						((PRODUCTION_UPGRADE_FUNCTION *)pFunction)->vtolFactory))
					{
						// upgrade the Output for the structure
						structureProductionUpgrade(psCurr);
					}
				}
				// and the mission structures
				for (psCurr = mission.apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if ((psCurr->pStructureType->type == REF_FACTORY &&
						((PRODUCTION_UPGRADE_FUNCTION *)pFunction)->factory) ||
						(psCurr->pStructureType->type == REF_CYBORG_FACTORY &&
						((PRODUCTION_UPGRADE_FUNCTION *)pFunction)->cyborgFactory) ||
						(psCurr->pStructureType->type == REF_VTOL_FACTORY &&
						((PRODUCTION_UPGRADE_FUNCTION *)pFunction)->vtolFactory))
					{
						// upgrade the Output for the structure
						structureProductionUpgrade(psCurr);
					}
				}
			   	// message/sound in here for production boost
				break;
			case(RESEARCH_UPGRADE_TYPE):
				researchUpgrade(pFunction, player);
				//search the list of players structures for a Research Facility
				for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if (psCurr->pStructureType->type == REF_RESEARCH)
					{
						// upgrade the research points
						structureResearchUpgrade(psCurr);
					}
				}
				// and the mission structures
				for (psCurr = mission.apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if (psCurr->pStructureType->type == REF_RESEARCH)
					{
						// upgrade the research points
						structureResearchUpgrade(psCurr);
					}
				}
				// Stuff a message in here/sound whatever for research boost.
				break;
			case(POWER_UPGRADE_TYPE):
				powerUpgrade(pFunction, player);
				// search the list of players structures for a Power Gens
				for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if (psCurr->pStructureType->type == REF_POWER_GEN)
					{
						// upgrade the power points
						structurePowerUpgrade(psCurr);
					}
				}
				// and the mission structure
				for (psCurr = mission.apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if (psCurr->pStructureType->type == REF_POWER_GEN)
					{
						// upgrade the power points
						structurePowerUpgrade(psCurr);
					}
				}
				break;
			case(REARM_UPGRADE_TYPE):
				reArmUpgrade(pFunction, player);
				// search the list of players structures for a ReArm pad
				for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if (psCurr->pStructureType->type == REF_REARM_PAD)
					{
						// upgrade the rearm points
						structureReArmUpgrade(psCurr);
					}
				}
				// and the mission structure
				for (psCurr = mission.apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if (psCurr->pStructureType->type == REF_REARM_PAD)
					{
						// upgrade the rearm points
						structureReArmUpgrade(psCurr);
					}
				}
				break;
			case(REPAIR_UPGRADE_TYPE):
				repairFacUpgrade(pFunction, player);
				// search the list of players structures for a Power Gens
				for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if (psCurr->pStructureType->type == REF_REPAIR_FACILITY)
					{
						// upgrade the repair points
						structureRepairUpgrade(psCurr);
					}
				}
				// and the mission structure
				for (psCurr = mission.apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					if (psCurr->pStructureType->type == REF_REPAIR_FACILITY)
					{
						// upgrade the repair points
						structureRepairUpgrade(psCurr);
					}
				}
				break;
			case(WEAPON_UPGRADE_TYPE):
				// for the current player, upgrade the weapon stats
				weaponUpgrade(pFunction, player);
				// message/sound for weapon upgrade
				break;
			case(DROIDSENSOR_UPGRADE_TYPE):
				// for the current player, upgrade the sensor stats
				sensorUpgrade(pFunction, player);
				// for each structure in the player's list, upgrade the sensor stat
				for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					structureSensorUpgrade(psCurr);
					visTilesUpdate(psCurr);
				}
				for (psCurr = mission.apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					structureSensorUpgrade(psCurr);
				}
				// for each droid in the player's list, upgrade the sensor stat
				for (psDroid = apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
				{
					droidSensorUpgrade(psDroid);
					visTilesUpdate(psDroid);
					if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
					{
						upgradeTransporterDroids(psDroid, droidSensorUpgrade);
					}
				}
				for (psDroid = mission.apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
				{
					droidSensorUpgrade(psDroid);
					if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
					{
						upgradeTransporterDroids(psDroid, droidSensorUpgrade);
					}
				}
				for (psDroid = apsLimboDroids[player]; psDroid != NULL; psDroid = psDroid->psNext)
				{
					droidSensorUpgrade(psDroid);
					if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
					{
						upgradeTransporterDroids(psDroid, droidSensorUpgrade);
					}
				}
				// message/sound for sensor upgrade
				break;
			case(DROIDECM_UPGRADE_TYPE):
				// for the current player, upgrade the ecm stats
				ecmUpgrade(pFunction, player);
				// for each structure in the player's list, upgrade the ecm stat
				for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					structureECMUpgrade(psCurr);
				}
				for (psCurr = mission.apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					structureECMUpgrade(psCurr);
				}
				// for each droid in the player's list, upgrade the ecm stat
				for (psDroid = apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
				{
					droidECMUpgrade(psDroid);
					if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
					{
						upgradeTransporterDroids(psDroid, droidECMUpgrade);
					}
				}
				for (psDroid = mission.apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
				{
					droidECMUpgrade(psDroid);
					if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
					{
						upgradeTransporterDroids(psDroid, droidECMUpgrade);
					}
				}
				for (psDroid = apsLimboDroids[player]; psDroid != NULL; psDroid = psDroid->psNext)
				{
					droidECMUpgrade(psDroid);
					if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
					{
						upgradeTransporterDroids(psDroid, droidECMUpgrade);
					}
				}
				// message/sound for ecm upgrade
				break;
			case(DROIDREPAIR_UPGRADE_TYPE):
				// for the current player, upgrade the repair stats
				repairUpgrade(pFunction, player);
				// message/sound for repair upgrade
				break;
			case(DROIDCONST_UPGRADE_TYPE):
				// for the current player, upgrade the constructor stats
				constructorUpgrade(pFunction, player);
				// message/sound for constructor upgrade
				break;
			case(DROIDBODY_UPGRADE_TYPE):
				// for each droid in the player's list, upgrade the body points
				for (psDroid = apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
				{
					droidBodyUpgrade(pFunction, psDroid);
				}
				for (psDroid = mission.apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
				{
					droidBodyUpgrade(pFunction, psDroid);
					if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
					{
						upgradeTransporterDroids(psDroid, droidSensorUpgrade);
					}
				}
				for (psDroid = apsLimboDroids[player]; psDroid != NULL; psDroid = psDroid->psNext)
				{
					droidBodyUpgrade(pFunction, psDroid);
					if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
					{
						upgradeTransporterDroids(psDroid, droidSensorUpgrade);
					}
				}
				// DO THIS AFTER so above calculations can use the previous upgrade values for
				// the current player, upgrade the body stats
				bodyUpgrade(pFunction, player);
				// message/sound for body upgrade
				break;
			case(STRUCTURE_UPGRADE_TYPE):
				// for each structure in the player's list, upgrade the stats
				for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					//do this for none wallDefense structs
					if (!wallDefenceStruct(psCurr->pStructureType))
					{
						structureBodyUpgrade(pFunction, psCurr);
						structureArmourUpgrade(pFunction, psCurr);
						structureResistanceUpgrade(pFunction, psCurr);
					}
					// Defense type can have resistance upgrade now - AB 19/02/99
					if (psCurr->pStructureType->type == REF_DEFENSE)
					{
						structureResistanceUpgrade(pFunction, psCurr);
					}
				}
				for (psCurr = mission.apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					//do this for none wallDefense structs
					if (!wallDefenceStruct(psCurr->pStructureType))
					{
						structureBodyUpgrade(pFunction, psCurr);
						structureArmourUpgrade(pFunction, psCurr);
						structureResistanceUpgrade(pFunction, psCurr);
					}
					// Defense type can have resistance upgrade now - AB 19/02/99
					if (psCurr->pStructureType->type == REF_DEFENSE)
					{
						structureResistanceUpgrade(pFunction, psCurr);
					}
				}
				// DO THIS AFTER so above calculations can use the previous upgrade values
				// for the current player, upgrade the structure stats
				structureUpgrade(pFunction, player);
				// message/sound for structure upgrade
				break;
			case(WALLDEFENCE_UPGRADE_TYPE):
				//for each structure in the player's list, upgrade the stats
				for (psCurr = apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					// do this for wallDefense structs
					if (wallDefenceStruct(psCurr->pStructureType))
					{
						structureBodyUpgrade(pFunction, psCurr);
						structureArmourUpgrade(pFunction, psCurr);
					}
				}
				for (psCurr = mission.apsStructLists[player]; psCurr != NULL; psCurr = psCurr->psNext)
				{
					// do this for wallDefense structs
					if (wallDefenceStruct(psCurr->pStructureType))
					{
						structureBodyUpgrade(pFunction, psCurr);
						structureArmourUpgrade(pFunction, psCurr);
					}
				}
				// DO THIS AFTER so above calculations can use the previous upgrade values
				// for the current player, upgrade the wall/defence structure stats
				wallDefenceUpgrade(pFunction, player);
				// message/sound for wall/defence structure upgrade
				break;
		//	default:
		//		ASSERT(false,"Invalid function type");
		//		break;
		}//end of switch
	}//end of function loop

	//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);
	}

#ifdef DEBUG
    /*this just checks that there are not more than 32 weapons now available for
    the design screen - it'll give us grief in the design screen (which we may HAVE TO fix)!*/
    //only check if selectedPlayer has done the research
    if (player == selectedPlayer)
    {
        UDWORD    vtolCompInc;

        compInc = vtolCompInc =0;
	    for (inc = 0; inc < numWeaponStats; inc++)
	    {
		    if (apCompLists[selectedPlayer][COMP_WEAPON][inc] == AVAILABLE &&
                asWeaponStats[inc].designable)
            {
                if (asWeaponStats[inc].vtolAttackRuns)
                {
                    vtolCompInc++;
                }
                else
                {
                    compInc++;
                }
                if (compInc >= 128)
                {
					debug(LOG_ERROR, "researchResult - more than 128 weapons now available");
                   
					//don't bother checking any more
                    break;
                }
                if (vtolCompInc >= 128)
                {
					debug(LOG_ERROR, "researchResult - more than 128 vtol weapons now available");
                    
					//don't bother checking any more
                    break;
                }
            }
	    }
    }
#endif
}

/*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, 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 0;	// 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;
	}
}

/* returns a pointer to a component based on the name - used to load in the research */
COMPONENT_STATS * getComponentDetails(char *pName, char *pCompName)
{
	UDWORD stat, size, quantity, inc;
	COMPONENT_STATS		*pArtefact;

	stat = componentType(pName);
	//get the stat list
	switch (stat)
	{
		case COMP_BODY:
		{
			pArtefact = (COMPONENT_STATS*)asBodyStats;
			size = sizeof(BODY_STATS);
			quantity = numBodyStats;
			break;
		}
		case COMP_BRAIN:
		{
			pArtefact = (COMPONENT_STATS*)asBrainStats;
			size = sizeof(BRAIN_STATS);
			quantity = numBrainStats;
			break;
		}
		case COMP_PROPULSION:
		{
			pArtefact = (COMPONENT_STATS*)asPropulsionStats;
			size = sizeof(PROPULSION_STATS);
			quantity = numPropulsionStats;
			break;
		}
		case COMP_REPAIRUNIT:
		{
			pArtefact = (COMPONENT_STATS*)asRepairStats;
			size = sizeof(REPAIR_STATS);
			quantity = numRepairStats;
			break;
		}
		case COMP_ECM:
		{
			pArtefact = (COMPONENT_STATS*)asECMStats;
			size = sizeof(ECM_STATS);
			quantity = numECMStats;
			break;
		}
		case COMP_SENSOR:
		{
			pArtefact = (COMPONENT_STATS*)asSensorStats;
			size = sizeof(SENSOR_STATS);
			quantity = numSensorStats;
			break;
		}
		case COMP_WEAPON:
		{
			pArtefact = (COMPONENT_STATS*)asWeaponStats;
			size = sizeof(WEAPON_STATS);
			quantity = numWeaponStats;
			break;
		}
		case COMP_CONSTRUCT:
		{
			pArtefact = (COMPONENT_STATS*)asConstructStats;
			size = sizeof(CONSTRUCT_STATS);
			quantity = numConstructStats;
			break;
		}
		default:
		{
			//COMP_UNKNOWN should be an error
			debug( LOG_ERROR, "Unknown artefact type  - %s", pName );
			abort();
			return NULL;
		}
	}

	for (inc = 0; inc < quantity; inc++)
	{
		if (!strcmp(pArtefact->pName, pCompName))
		{
			return pArtefact;
		}
		pArtefact = (COMPONENT_STATS*)((char*)pArtefact + size);
	}

	debug( LOG_ERROR, "Cannot find component %s", pCompName );
	abort();
	return NULL;
}

//return a pointer to a research topic based on the name
RESEARCH *getResearch(const char *pName)
{
	unsigned int inc = 0;

	for (inc = 0; inc < asResearch.size(); inc++)
	{
		if (!strcasecmp(asResearch[inc].pName, pName))
		{
			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;
	UDWORD			inc, oldType, newType, oldCompInc, newCompInc;

	//get the type and index of the old component
	oldType = statType(pOldComponent->ref);
	oldCompInc = pOldComponent->ref - statRefStart(oldType);
	//get the type and index of the new component
	newType = statType(pNewComponent->ref);
	newCompInc = pNewComponent->ref - statRefStart(newType);

	//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 (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" );
				abort();
				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)
{
	UDWORD inc;
	char *pName=psResearch->pName;

	for (inc = 0; inc < numStats; inc++)
	{

		if (!strcmp(asResearch[inc].pName, pName))
		{
			//oops! found the name
			ASSERT( false, "Research name has already been used - %s", pName );
			return false;
		}
	}
	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;

	inc = psResearch->index;
	if (inc > asResearch.size())
	{
		ASSERT( false, "enableResearch: Invalid research topic - %s", getResearchName(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].pName)));
		}
	}
}


/*checks that the research has loaded up as expected - must be done after
all research parts have been loaded*/
bool checkResearchStats(void)
{
	UDWORD resInc, inc;
	for (resInc = 0; resInc < asResearch.size(); resInc++)
	{
		for (inc = 0; inc < asResearch[resInc].pPRList.size(); inc++)
		{
			ASSERT(asResearch[resInc].pPRList[inc] <= asResearch.size(), "Invalid PreReq for topic %s", asResearch[resInc].pName);
		}
		for (inc = 0; inc < asResearch[resInc].pStructList.size(); inc++)
		{
			ASSERT(asResearch[resInc].pStructList[inc] <= numStructureStats, "Invalid Structure for topic %s", asResearch[resInc].pName);
		}
		for (inc = 0; inc < asResearch[resInc].pFunctionList.size(); inc++)
		{
			ASSERT(asResearch[resInc].pFunctionList[inc]->ref - REF_FUNCTION_START <= numFunctions,
			       "Invalid function for %s", asResearch[resInc].pName);
		}
		for (inc = 0; inc < asResearch[resInc].pRedStructs.size(); inc++)
		{
			ASSERT(asResearch[resInc].pRedStructs[inc] <= numStructureStats,
			       "Invalid Redundant Structure for topic %s", asResearch[resInc].pName);
		}
		for (inc = 0; inc < asResearch[resInc].pStructureResults.size(); inc++)
		{
			ASSERT(asResearch[resInc].pStructureResults[inc] <= numStructureStats,
			       "Invalid Result Structure for topic %s", asResearch[resInc].pName);
		}
		for (inc = 0; inc < asResearch[resInc].pArtefactResults.size(); inc++)
		{
			ASSERT(asResearch[resInc].pArtefactResults[inc] != NULL,
			       "Invalid Comp Result for topic %s", asResearch[resInc].pName);
		}
		for (inc = 0; inc < asResearch[resInc].pRedArtefacts.size(); inc++)
		{
			ASSERT(asResearch[resInc].pRedArtefacts[inc] != NULL,
			       "Invalid Redundant Comp for topic %s", asResearch[resInc].pName);
		}
	}

	return true;
}

/*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_BLASTDOOR)
	{
		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_ECM:
				objEcmCache((BASE_OBJECT *)psStructure, asECMStats + newCompInc);
				break;
			case COMP_SENSOR:
				objSensorCache((BASE_OBJECT *)psStructure, asSensorStats + newCompInc);
				break;
			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].nStat == oldCompInc)
			{
				psDroid->asBits[oldType].nStat = (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;
	}
}
