All pastes #1535645 Raw Edit

geever/ufoyards.patch

public diff v1 · immutable
#1535645 ·published 2009-08-19 22:05 UTC
rendered paste body
Index: src/client/campaign/cp_base.h===================================================================--- src/client/campaign/cp_base.h	(revision 25739)+++ src/client/campaign/cp_base.h	(working copy)@@ -305,7 +305,6 @@ void B_RemoveAircraftExceedingCapacity(base_t* base, buildingType_t buildingType); void B_DrawBuilding(base_t* base, building_t* building); void B_RemoveItemsExceedingCapacity(base_t *base);-void B_RemoveUFOsExceedingCapacity(base_t *base, const buildingType_t buildingType); void B_RemoveAntimatterExceedingCapacity(base_t *base); void B_ManageAntimatter(base_t *base, int amount, qboolean add); void B_UpdateStorageCap(base_t *base);Index: src/client/campaign/cp_campaign.h===================================================================--- src/client/campaign/cp_campaign.h	(revision 25739)+++ src/client/campaign/cp_campaign.h	(working copy)@@ -4,7 +4,7 @@  */  /*-Copyright (C) 2002-2007 UFO: Alien Invasion team.+Copyright (C) 2002-2009 UFO: Alien Invasion team.  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -32,6 +32,7 @@ struct installation_s; struct employee_s; struct menuNode_s; /**< @todo remove this once the menuNode_t usage is cleaned up */+struct storedUFO_s;  #define MAX_ASSEMBLIES	16 #define ANTIMATTER_SIZE 10@@ -467,6 +468,10 @@ 	int numComponents; 	components_t components[MAX_ASSEMBLIES]; +	/** stored UFOs */+	storedUFO_t storedUFOs[MAX_STOREDUFOS];+	int numStoredUFOs;+ 	/* Alien Team Definitions. */ 	int numAliensTD; Index: src/client/campaign/cp_installation.c===================================================================--- src/client/campaign/cp_installation.c	(revision 25739)+++ src/client/campaign/cp_installation.c	(working copy)@@ -156,7 +156,14 @@ 	if (!installation->founded) 		return; +	/* Disable radar */ 	RADAR_UpdateInstallationRadarCoverage(installation, 0, 0);+	/* Destroy stored UFOs */+	if (installation->ufoCapacity.max) {+		installation->ufoCapacity.max = 0;+		US_RemoveUFOsExceedingCapacity(installation);+	}+ 	CP_MissionNotifyInstallationDestroyed(installation);  	Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Installation %s was destroyed."), installation->name);@@ -306,7 +313,33 @@ } #endif +/**+ * @brief returns the first installation with (free) ufostoring capacity+ * @param[in] free On qtrue it gives the first UFO Yard with free space+ * @return installation_t Pointer to the UFO Yard+ **/+installation_t *INS_GetFirstUFOYard (qboolean free)+{+	int i; +	for (i = 0; i < ccs.numInstallations; i++) {+		installation_t *installation = INS_GetFoundedInstallationByIDX(i);++		if (!installation)+			continue;++		if (INS_GetType(installation) != INSTALLATION_UFOYARD)+			continue;++		if (free && installation->ufoCapacity.cur >= installation->ufoCapacity.max)+			continue;++		return installation;+	}++	return NULL;+}+ /**  * @brief Resets console commands.  * @sa MN_ResetMenusIndex: src/client/campaign/cp_installation.h===================================================================--- src/client/campaign/cp_installation.h	(revision 25739)+++ src/client/campaign/cp_installation.h	(working copy)@@ -103,13 +103,15 @@  /* Functions */ installation_t *INS_GetInstallationByIDX(int instIdx);-installation_t* INS_GetFoundedInstallationByIDX(int installationIdx);+installation_t *INS_GetFoundedInstallationByIDX(int installationIdx); installation_t *INS_GetFirstUnfoundedInstallation(void); -installationTemplate_t* INS_GetInstallationTemplateFromInstallationID(const char *id);+installationTemplate_t *INS_GetInstallationTemplateFromInstallationID(const char *id);  installationType_t INS_GetType(const installation_t *installation); +installation_t *INS_GetFirstUFOYard(qboolean free);+ void INS_SetUpInstallation(installation_t* installation, installationTemplate_t *installationTemplate, vec2_t pos);  installation_t *INS_GetCurrentSelectedInstallation(void);Index: src/client/campaign/cp_uforecovery.c===================================================================--- src/client/campaign/cp_uforecovery.c	(revision 25739)+++ src/client/campaign/cp_uforecovery.c	(working copy)@@ -1,11 +1,12 @@ /**  * @file cp_uforecovery.c- * @brief UFO recovery- * @note Functions with UR_*+ * @brief UFO recovery and storing+ * @note UFO recovery functions with UR_*+ * @note UFO storing functions with US_*  */  /*-Copyright (C) 2002-2007 UFO: Alien Invasion team.+Copyright (C) 2002-2009 UFO: Alien Invasion team.  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -28,6 +29,7 @@ #include "../menu/m_main.h" #include "cp_campaign.h" #include "cp_ufo.h"+#include "cp_map.h" #include "cp_uforecovery.h" #include "cp_uforecovery_callbacks.h" #include "cp_aircraft.h"@@ -41,7 +43,7 @@  * @sa CL_EventAddMail_f  * @sa CL_NewEventMail  */-static void UR_SendMail (const aircraft_t *ufocraft, const base_t *base)+static void UR_SendMail (const aircraft_t *ufocraft, const char *baseName) { 	int i; 	components_t *comp;@@ -49,7 +51,7 @@ 	char body[512] = "";  	assert(ufocraft);-	assert(base);+	assert(baseName);  	if (ccs.missionResults.crashsite) { 		/* take the source mail and create a copy of it */@@ -95,96 +97,20 @@ 	assert(mail);  	/* don't free the old mail body here - it's the string of the source mail */-	mail->body = Mem_PoolStrDup(va(_(mail->body), UFO_TypeToName(ccs.missionResults.ufotype), base->name, body), cp_campaignPool, 0);+	mail->body = Mem_PoolStrDup(va(_(mail->body), UFO_TypeToName(ccs.missionResults.ufotype), baseName, body), cp_campaignPool, 0);  	/* update subject */ 	/* Insert name of the mission in the template */-	mail->subject = Mem_PoolStrDup(va(_(mail->subject), base->name), cp_campaignPool, 0);+	mail->subject = Mem_PoolStrDup(va(_(mail->subject), baseName), cp_campaignPool, 0);  	/* Add the mail to unread mail */ 	Cmd_ExecuteString(va("addeventmail %s", mail->id)); }  /**- * @brief Prepares UFO recovery in global recoveries array.- * @param[in] base Pointer to the base, where the UFO recovery will be made.- * @sa UR_ProcessActive- * @sa UR_ConditionsForStoring- */-void UR_Prepare (base_t *base, ufoType_t ufoType)-{-	int i;-	ufoRecoveries_t *recovery = NULL;-	aircraft_t *ufocraft = NULL;-	date_t event;--	assert(base);--	/* Find ufo sample of given ufotype. */-	for (i = 0; i < ccs.numAircraftTemplates; i++) {-		ufocraft = &ccs.aircraftTemplates[i];-		if (ufocraft->type != AIRCRAFT_UFO)-			continue;-		if (ufocraft->ufotype == ufoType)-			break;-	}-	if (ufocraft == NULL) {-		Com_DPrintf(DEBUG_CLIENT, "UR_Prepare: no ufo with the type %i found.\n", ufoType);-		return;-	}--	/* Find free uforecovery slot. */-	for (i = 0; i < MAX_RECOVERIES; i++) {-		if (!ccs.recoveries[i].active) {-			/* Make sure it is empty here. */-			memset(&ccs.recoveries[i], 0, sizeof(ccs.recoveries[i]));-			recovery = &ccs.recoveries[i];-			break;-		}-	}-	if (!recovery) {-		Com_DPrintf(DEBUG_CLIENT, "UR_Prepare: free recovery slot not found.\n");-		return;-	}--	/* Prepare date of the recovery event - always two days after mission. */-	event = ccs.date;-	/* if you change these 2 days to something other you-	 * have to review all translations, too */-	event.day += 2;-	/* Prepare recovery. */-	recovery->active = qtrue;-	recovery->base = base;-	recovery->ufoTemplate = ufocraft->tpl;-	recovery->event = event;--	/* Update base capacity. */-	if (ufocraft->size == AIRCRAFT_LARGE) {-		/* Large UFOs can only fit in large UFO hangars */-		if (base->capacities[CAP_UFOHANGARS_LARGE].max > base->capacities[CAP_UFOHANGARS_LARGE].cur)-			base->capacities[CAP_UFOHANGARS_LARGE].cur++;-		else-			/* no more room -- this shouldn't happen as we've used UR_ConditionsForStoring */-			Com_Error(ERR_DROP, "UR_Prepare: No room in large UFO hangars to store %s", ufocraft->id);-	} else {-		/* Small UFOs can only fit in small UFO hangars */-		if (base->capacities[CAP_UFOHANGARS_SMALL].max > base->capacities[CAP_UFOHANGARS_SMALL].cur)-			base->capacities[CAP_UFOHANGARS_SMALL].cur++;-		else-			/* no more room -- this shouldn't happen as we've used UR_ConditionsForStoring */-			Com_Error(ERR_DROP, "UR_Prepare: No room in small UFO hangars to store %s", ufocraft->id);-	}--	Com_DPrintf(DEBUG_CLIENT, "UR_Prepare: the recovery entry in global array is done; base: %s, ufotype: %s, date: %i\n",-		base->name, recovery->ufoTemplate->id, recovery->event.day);--	/* Send an email */-	UR_SendMail(ufocraft, base);-}--/**  * @brief Function to process crashed UFO.  * @note Command to call this: cp_ufocrashed.+ * @todo is this still used? Remove or fix.  */ static void CP_UFOCrashed_f (void) {@@ -237,7 +163,7 @@ 	cargo = aircraft->itemcargo;  	/* Find components definition. */-	comp = CL_GetComponentsByItem(INVSH_GetItemByID(ufocraft->id));+	comp = CL_GetComponentsByID(ufocraft->id); 	assert(comp);  	/* Add components of crashed UFO to dropship. */@@ -256,7 +182,8 @@ 	ccs.missionResults.ufotype = ufocraft->ufotype;  	/* send mail */-	UR_SendMail(ufocraft, aircraft->homebase);+	assert(aircraft->homebase);+	UR_SendMail(ufocraft, aircraft->homebase->name); }  /*==================================@@ -264,125 +191,315 @@ ==================================*/  /**- * @brief Updates current capacities for UFO hangars in given base.- * @param[in] base The base where you want the update to be done+ * @brief Function to process active recoveries.+ * @sa CL_CampaignRun+ * @sa UR_Prepare  */-void UR_UpdateUFOHangarCapForAll (base_t *base)+void UR_ProcessActive (void) { 	int i;-	aircraft_t *ufocraft; -	if (!base) {-#ifdef DEBUG-		Com_Printf("UR_UpdateUFOHangarCapForAll: no base given!\n");-#endif-		return;+	for (i = 0; i < ccs.numStoredUFOs; i++) {+		storedUFO_t *ufo = US_GetStoredUFOByIDX(i);++		if (!ufo)+			continue;+		assert(ufo->ufoTemplate);+		assert(ufo->ufoTemplate->tech);+		if (ufo->ufoTemplate->tech->statusCollected)+			continue;+		if (ufo->arrive.day > ccs.date.day || (ufo->arrive.day == ccs.date.day && ufo->arrive.sec > ccs.date.sec))+			continue;++		RS_MarkCollected(ufo->ufoTemplate->tech); 	}+} -	/* Reset current capacities for UFO hangar. */-	base->capacities[CAP_UFOHANGARS_LARGE].cur = 0;-	base->capacities[CAP_UFOHANGARS_SMALL].cur = 0;+/* ==== UFO Storing stuff ==== */ -	for (i = 0; i < csi.numODs; i++) {-		/* we are looking for UFO */-		if (csi.ods[i].tech->type != RS_CRAFT)+/**+ * @brief Returns an ufostore place+ * @param[in] idx index of ufostore place+ * @return storedUFO_t Pointer+ */+storedUFO_t* US_GetStoredUFOPlaceByIDX (const int idx)+{+	if (idx < 0 || idx >= MAX_STOREDUFOS)+		return NULL;+	return &(ccs.storedUFOs[idx]);+}++/**+ * @brief Returns a stored ufo+ * @param[in] idx index of ufostore place+ * @return storedUFO_t Pointer+ */+storedUFO_t* US_GetStoredUFOByIDX (const int idx)+{+	if (idx < 0 || idx >= ccs.numStoredUFOs)+		return NULL;+	return &(ccs.storedUFOs[idx]);+}++/**+ * @brief Adds an UFO to the storage+ * @param[in] ufoTemplate Pointer to the aircraft(ufo)Template to add+ * @param[in] installation Pointer to the installation it should be added to+ * @param[in] date Date when UFO is arrives to the storage (recovery || transfer)+ * @return storedUFO_t pointer to the newly stored UFO (or NULL if failed)+ */+storedUFO_t *US_StoreUFO (aircraft_t *ufoTemplate, installation_t *installation, date_t date)+{+	storedUFO_t *ufo;++	if (!ufoTemplate) {+		Com_DPrintf(DEBUG_CLIENT, "US_StoreUFO: Invalid aircraft (UFO) Template.\n");+		return NULL;+	}++	if (!installation) {+		Com_DPrintf(DEBUG_CLIENT, "US_StoreUFO: Invalid Installation\n");+		return NULL;+	}++	if (ccs.numStoredUFOs >= MAX_STOREDUFOS) {+		Com_DPrintf(DEBUG_CLIENT, "US_StoreUFO: stored UFOs array is full.\n");+		return NULL;+	}++	if (installation->ufoCapacity.cur >= installation->ufoCapacity.max) {+		Com_DPrintf(DEBUG_CLIENT, "US_StoreUFO: Installation is full with UFOs.\n");+		return NULL;+	}++	/* we can store it there */+	ufo = US_GetStoredUFOPlaceByIDX(ccs.numStoredUFOs);		/* Select first empty place */+	ufo->idx = ccs.numStoredUFOs;+	Q_strncpyz(ufo->id, ufoTemplate->id, sizeof(ufo->id));+	ufo->comp = CL_GetComponentsByID(ufo->id);+	assert(ufo->comp);++	ufo->installation = installation;+	installation->ufoCapacity.cur++;++	assert(ufoTemplate->tech);++	ufo->ufoTemplate = ufoTemplate;+	ufo->disassembly = NULL;++	ufo->arrive = date;+	if (date.day < ccs.date.day || (date.day == ccs.date.day && date.sec <= ccs.date.sec))+		RS_MarkCollected(ufo->ufoTemplate->tech);++	ccs.numStoredUFOs++;++	return ufo;+}++/**+ * @brief Removes an UFO from the storage+ * @param[in] ufo stored UFO to remove+ */+void US_RemoveStoredUFO (storedUFO_t *ufo)+{+	int ufoCount;+	int i;++	assert(ufo);++	/* Stop disassembling */+	if (ufo->disassembly) {+		base_t *prodBase = PR_ProductionBase(ufo->disassembly);++		assert(prodBase);++		PR_QueueDelete(prodBase, &ccs.productions[prodBase->idx], ufo->disassembly->idx);+	}++	/* Stop running research if this is the only UFO from this type+	 * also clear collected status */+	assert(ufo->ufoTemplate);+	ufoCount = US_UFOsInStorage(ufo->ufoTemplate, NULL);+	if (ufoCount <= 1 && ufo->ufoTemplate->tech->statusResearch == RS_RUNNING) {+		RS_StopResearch(ufo->ufoTemplate->tech);+		ufo->ufoTemplate->tech->statusCollected = qfalse;+	}++	/* remove ufo */+	ufo->installation->ufoCapacity.cur--;+	REMOVE_ELEM_ADJUST_IDX(ccs.storedUFOs, ufo->idx, ccs.numStoredUFOs);++	/* Adjust UFO pointer of other disassemblies */+	for (i = 0; i < ccs.numBases; i++) {+		int j;+		for (j = 0; j < ccs.productions[i].numItems; j++) {+			production_t *prod = &(ccs.productions[i].items[j]);++			if (!prod->ufo)+				continue;+			if (prod->ufo > ufo)+				prod->ufo--;+		}+	}+}+++/**+ * @brief Returns the number of UFOs stored (on an installation or anywhere)+ * @param[in] ufoTemplate aircraftTemplate of the ufo+ * @param[in] installation Pointer to the installation to count at+ * @returns the number of ufos stored of the given ufotype at given installation or overall+ * @note installation == NULL means count on every ufoyards+ */+int US_UFOsInStorage (const aircraft_t *ufoTemplate, const installation_t *installation)+{+	int i;+	int count = 0;++	for (i = 0; i < ccs.numStoredUFOs; i++) {+		const storedUFO_t *ufo = US_GetStoredUFOByIDX(i);++		if (!ufo) 			continue;+		if (ufo->ufoTemplate != ufoTemplate)+			continue;+		if (installation && ufo->installation != installation)+			continue;+		if (ufo->arrive.day > ccs.date.day+		 || (ufo->arrive.day == ccs.date.day && ufo->arrive.sec > ccs.date.sec))+		 	continue; -		/* do we have UFO of this type ? */-		if (!base->storage.num[i])+	 	count++;+	}++	return count;+}++/**+ * @brief Removes ufos which are over the storing capacity+ * @param[in] installation pointer to the ufoyard the ufos are stored in+ */+void US_RemoveUFOsExceedingCapacity (installation_t *installation)+{+	int i;+	capacities_t *ufoCap;++	if (!installation)+		Com_Error(ERR_DROP, "US_RemoveUFOsExceedingCapacity: No installation given!\n");++	ufoCap = &(installation->ufoCapacity);++	/* loop ufos backwards */+	for (i = ccs.numStoredUFOs - 1; i >= 0; i--) {+		storedUFO_t *ufo = US_GetStoredUFOByIDX(i);++		assert(ufo);++		if (ufo->installation != installation) 			continue; -		/* look for corresponding aircraft in global array */-		ufocraft = AIR_GetAircraft(csi.ods[i].id);-		if (!ufocraft) {-			Com_Printf("UR_UpdateUFOHangarCapForAll: Did not find UFO %s\n", csi.ods[i].id);+		if (ufoCap->cur > ufoCap->max)+			US_RemoveStoredUFO(ufo);+	}+}++/**+ * @brief get the closest stored ufo (of a type) to a base+ * @param[in] ufoTemplate Pointer to the aircraft (ufo) template to look for (NULL for any type)+ * @param[in] base Pointer to the base. If it's NULL the function simply return the first stored UFO of type+ * @return storedUFO_t Pointer to the first stored UFO matches the conditions+ */+storedUFO_t *US_GetClosestStoredUFO (const aircraft_t *ufoTemplate, const base_t *base)+{+	int i;+	float minDistance = -1;+	storedUFO_t *closestUFO = NULL;++	for (i = 0; i < ccs.numStoredUFOs; i++) {+		storedUFO_t *ufo = US_GetStoredUFOByIDX(i);+		float distance = 0;++		if (!ufo) 			continue;-		} -		/* Update base capacity. */-		if (ufocraft->size == AIRCRAFT_LARGE)-			base->capacities[CAP_UFOHANGARS_LARGE].cur += base->storage.num[i];-		else-			base->capacities[CAP_UFOHANGARS_SMALL].cur += base->storage.num[i];+		if (ufoTemplate && ufo->ufoTemplate != ufoTemplate)+			continue;++		if (ufo->arrive.day > ccs.date.day+		 || (ufo->arrive.day == ccs.date.day && ufo->arrive.sec > ccs.date.sec))+		 	continue;++		assert(ufo->installation);+		if (base)+			distance = MAP_GetDistance(ufo->installation->pos, base->pos);++		if (minDistance < 0 || minDistance > distance) {+			minDistance = distance;+			closestUFO = ufo;+		}	 	}--	Com_DPrintf(DEBUG_CLIENT, "UR_UpdateUFOHangarCapForAll: base capacities.cur: small: %i big: %i\n", base->capacities[CAP_UFOHANGARS_SMALL].cur, base->capacities[CAP_UFOHANGARS_LARGE].cur);+	return closestUFO; } + /**- * @brief Function to process active recoveries.- * @sa CL_CampaignRun- * @sa UR_Prepare+ * @brief Save callback for savegames in XML Format+ * @sa US_LoadXML+ * @sa SAV_GameSaveXML  */-void UR_ProcessActive (void)+qboolean US_SaveXML (mxml_node_t *p) { 	int i;+	mxml_node_t *node = mxml_AddNode(p, "storedufos");+	for (i = 0; i < ccs.numStoredUFOs; i++) {+		const storedUFO_t *ufo = US_GetStoredUFOByIDX(i);+		mxml_node_t * snode = mxml_AddNode(node, "ufo"); -	for (i = 0; i < MAX_RECOVERIES; i++) {-		ufoRecoveries_t *recovery = &ccs.recoveries[i];-		if (recovery->active && recovery->event.day == ccs.date.day) {-			objDef_t *od;-			const aircraft_t *ufocraft;-			base_t *base = recovery->base;-			if (!base->founded) {-				/* Destination base was destroyed meanwhile. */-				/* UFO is lost, send proper message to the user.*/-				recovery->active = qfalse;-				/** @todo prepare MS_AddNewMessage() here */-				return;-			}-			/* Get ufocraft. */-			ufocraft = recovery->ufoTemplate;-			assert(ufocraft);-			/* Get item. */-			/* We can do this because aircraft id is the same as dummy item id. */-			od = INVSH_GetItemByID(ufocraft->id);-			if (!od)-				Com_Error(ERR_DROP, "Could not find ufo with id '%s'\n", ufocraft->id);+		mxml_AddString(snode, "ufoid", ufo->id);+		mxml_AddInt(snode, "day", ufo->arrive.day);+		mxml_AddInt(snode, "sec", ufo->arrive.sec); -			/* Process UFO recovery. */-			/* don't call B_UpdateStorageAndCapacity here - it's a dummy item */-			base->storage.num[od->idx]++;	/* Add dummy UFO item to enable research topic. */-			RS_MarkCollected(od->tech);	/* Enable research topic. */-			/* Reset this recovery. */-			memset(&ccs.recoveries[i], 0, sizeof(ccs.recoveries[i]));-		}+		if (ufo->installation)+			mxml_AddInt(snode, "installationidx", ufo->installation->idx); 	}+	return qtrue; }  /**- * @brief Checks conditions for storing given ufo in given base.- * @param[in] base Pointer to the base, where we are going to store UFO.- * @param[in] ufocraft Pointer to ufocraft which we are going to store.- * @return qtrue if given base can store given ufo.+ * @brief Load callback for xml savegames+ * @sa US_SaveXML+ * @sa SAV_GameLoadXML  */-qboolean UR_ConditionsForStoring (const base_t *base, const aircraft_t *ufocraft)+qboolean US_LoadXML (mxml_node_t *p) {-	assert(base && ufocraft);+	int i;+	mxml_node_t *node, *snode; -	if (ufocraft->size == AIRCRAFT_LARGE) {-		/* Large UFOs can only fit large UFO hangars */-		if (!B_GetBuildingStatus(base, B_UFO_HANGAR))-			return qfalse;+	node = mxml_GetNode(p, "storedufos"); -		/* Check there is still enough room for this UFO */-		if (base->capacities[CAP_UFOHANGARS_LARGE].max <= base->capacities[CAP_UFOHANGARS_LARGE].cur)+	for (i = 0, snode = mxml_GetNode(node, "ufo"); i < MAX_STOREDUFOS && snode;+			i++, snode = mxml_GetNextNode(snode, node, "ufo")) {+		aircraft_t *ufoTemplate = AIR_GetAircraft(mxml_GetString(snode, "ufoid"));+		int instIDX = mxml_GetInt(snode, "installationidx", MAX_INSTALLATIONS);+		installation_t *inst = (instIDX == MAX_INSTALLATIONS) ? NULL : INS_GetFoundedInstallationByIDX(instIDX);+		date_t arrive;++		arrive.day = mxml_GetInt(snode, "day", 0);+		arrive.sec = mxml_GetInt(snode, "sec", 0);++		if (!ufoTemplate) 			return qfalse;-	} else {-		/* This is a small UFO, can only fit in small hangar */ -		/* There's no small hangar functional */-		if (!B_GetBuildingStatus(base, B_UFO_SMALL_HANGAR))+		if (!inst) 			return qfalse; -		/* Check there is still enough room for this UFO */-		if (base->capacities[CAP_UFOHANGARS_SMALL].max <= base->capacities[CAP_UFOHANGARS_SMALL].cur)-			return qfalse;+		if (!US_StoreUFO(ufoTemplate, inst, arrive))+			Com_Printf("Cannot store ufo %s at installation idx=%i.\n", ufoTemplate->id, instIDX); 	}- 	return qtrue; } + void UR_InitStartup (void) { 	Cmd_AddCommand("cp_ufocrashed", CP_UFOCrashed_f, "Function to process crashed UFO after a mission.");Index: src/client/campaign/cp_parse.c===================================================================--- src/client/campaign/cp_parse.c	(revision 25739)+++ src/client/campaign/cp_parse.c	(working copy)@@ -605,6 +605,24 @@ }  /**+ * @brief Returns components definition by ID.+ * @param[in] id assemblyId of the component definition.+ * @return comp Pointer to components_t definition.+ */+components_t *CL_GetComponentsByID(const char *id)+{+	int i;++	for (i = 0; i < ccs.numComponents; i++) {+		components_t *comp = &ccs.components[i];+		if (!strcmp(comp->assemblyId, id)) {+			return comp;+		}+	}+	Com_Error(ERR_DROP, "CL_GetComponentsByItem: could not find components id for: %s", id);+}++/**  * @brief Parsing only for singleplayer  *  * parsed if we are no dedicated serverIndex: src/client/campaign/cp_uforecovery.h===================================================================--- src/client/campaign/cp_uforecovery.h	(revision 25739)+++ src/client/campaign/cp_uforecovery.h	(working copy)@@ -1,10 +1,10 @@ /**  * @file cp_uforecovery.h- * @brief UFO recovery+ * @brief UFO recovery and storing  */  /*-Copyright (C) 2002-2007 UFO: Alien Invasion team.+Copyright (C) 2002-2009 UFO: Alien Invasion team.  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -33,15 +33,42 @@  * @sa ufoRecovery_t */ typedef struct ufoRecoveries_s { 	qboolean active;			/**< True if the recovery is under processing. */-	base_t *base;				/**< Base where the recovery will be processed. */+	installation_t *installation;	/**< UFO Yard where the recovery will be processed. */ 	aircraft_t *ufoTemplate;	/**< Entry of UFO in aircraftTemplates array. */ 	date_t event;				/**< When the process will start (UFO got transported to base). */ } ufoRecoveries_t; -void UR_Prepare(base_t *base, ufoType_t ufoType);-void UR_UpdateUFOHangarCapForAll(base_t *base);+#define MAX_STOREDUFOS 128++/* time the recovery takes in days */+#define RECOVERY_DELAY 2.0f++/** @brief Structure for stored UFOs */+typedef struct storedUFO_s {+	int idx;+	char id[MAX_VAR];+	components_t *comp;+	aircraft_t *ufoTemplate;++	/* arrival date (recovery/transfer) */+	date_t arrive;++	/* installation UFO is stored */+	installation_t *installation;++	/* link to disassembly item */+	production_t *disassembly;+} storedUFO_t;+ void UR_ProcessActive(void);-qboolean UR_ConditionsForStoring(const base_t *base, const aircraft_t *ufocraft); void UR_InitStartup(void); +storedUFO_t *US_StoreUFO(aircraft_t *ufoTemplate, installation_t *installation, date_t date);+storedUFO_t *US_GetStoredUFOPlaceByIDX(const int idx);+storedUFO_t *US_GetStoredUFOByIDX(const int idx);+storedUFO_t *US_GetClosestStoredUFO(const aircraft_t *ufoTemplate, const base_t *base);+void US_RemoveStoredUFO(storedUFO_t *ufo);+int US_UFOsInStorage(const aircraft_t *ufoTemplate, const installation_t *installation);+void US_RemoveUFOsExceedingCapacity(installation_t *installation);+ #endifIndex: src/client/campaign/cp_parse.h===================================================================--- src/client/campaign/cp_parse.h	(revision 25739)+++ src/client/campaign/cp_parse.h	(working copy)@@ -47,5 +47,6 @@ void CL_ParseCampaign(const char *name, const char **text); void CL_ScriptSanityCheck(void); components_t *CL_GetComponentsByItem(const objDef_t *item);+components_t *CL_GetComponentsByID(const char *id);  #endifIndex: src/client/campaign/cp_aircraft.c===================================================================--- src/client/campaign/cp_aircraft.c	(revision 25739)+++ src/client/campaign/cp_aircraft.c	(working copy)@@ -802,20 +802,11 @@  int AIR_GetCapacityByAircraftWeight (const aircraft_t *aircraft) {-	if (aircraft->ufotype == UFO_MAX) {-		switch (aircraft->size) {-		case AIRCRAFT_SMALL:-			return CAP_AIRCRAFT_SMALL;-		case AIRCRAFT_LARGE:-			return CAP_AIRCRAFT_BIG;-		}-	} else {-		switch (aircraft->size) {-		case AIRCRAFT_SMALL:-			return CAP_UFOHANGARS_SMALL;-		case AIRCRAFT_LARGE:-			return CAP_UFOHANGARS_LARGE;-		}+	switch (aircraft->size) {+	case AIRCRAFT_SMALL:+		return CAP_AIRCRAFT_SMALL;+	case AIRCRAFT_LARGE:+		return CAP_AIRCRAFT_BIG; 	} 	Com_Error(ERR_DROP, "AIR_GetCapacityByAircraftWeight: Unkown weight of aircraft '%i'\n", aircraft->size); }@@ -2604,22 +2595,6 @@ 		mxml_AddBool(snode, "laser", ccs.projectiles[i].laser); 	} -	/* Save recoveries. */-	for (i = 0; i < MAX_RECOVERIES; i++) {-		/*check, if the transfer is active */-		/** @todo Check if we can really ignore them if active is false */-		if (!ccs.recoveries[i].active)-			continue;-		snode = mxml_AddNode(node, "recovery");-		/* mxml_AddBool(snode, "active", ccs.recoveries[i].active); */-		if (ccs.recoveries[i].base)-			mxml_AddInt(snode, "targetbase", ccs.recoveries[i].base->idx);-		/** @todo At some point we really need to save a unique string here. */-		if (ccs.recoveries[i].ufoTemplate)-			mxml_AddInt(snode, "ufotemplateidx", ccs.recoveries[i].ufoTemplate->idx);-		mxml_AddInt(snode, "day", ccs.recoveries[i].event.day);-		mxml_AddInt(snode, "sec", ccs.recoveries[i].event.sec);-	} 	return qtrue; } @@ -2931,17 +2906,28 @@  	ccs.numProjectiles = i; -	for (i = 0, snode = mxml_GetNode(node, "recovery"); snode && i < MAX_RECOVERIES;-			snode = mxml_GetNextNode(snode, node, "recovery"), i++) {-		byte base, ufotype;-		ccs.recoveries[i].active = qtrue;-		base = mxml_GetInt(snode, "targetbase", BYTES_NONE);-		ccs.recoveries[i].base = (base != BYTES_NONE) ? B_GetBaseByIDX((byte)base) : NULL;-		assert(ccs.numAircraftTemplates);-		ufotype = mxml_GetInt(snode, "ufotemplateidx", BYTES_NONE);-		ccs.recoveries[i].ufoTemplate = (ufotype != BYTES_NONE) ? &ccs.aircraftTemplates[ufotype] : NULL;-		ccs.recoveries[i].event.day = mxml_GetInt(snode, "day", 0);-		ccs.recoveries[i].event.sec = mxml_GetInt(snode, "sec", 0);+	/* fallback code to keep compatibility with UFO Hangars */+	/* @todo remove this on release or after a time */+	for (snode = mxml_GetNode(node, "recovery"); snode;+			snode = mxml_GetNextNode(snode, node, "recovery")) {+		installation_t *inst = INS_GetFirstUFOYard(qtrue);+		byte ufotype = mxml_GetInt(snode, "ufotemplateidx", BYTES_NONE);+		date_t event;++		if (!inst) {+			Com_Printf("AIR_LoadXML: No more free UFOYards for recovery\n");+			break;+		}++		event.day = mxml_GetInt(snode, "day", 0);+		event.sec = mxml_GetInt(snode, "sec", 0);++		if (ufotype >= ccs.numAircraftTemplates) {+			Com_Printf("AIR_LoadXML: Invalid template idx %i\n", ufotype);+			continue;+		}++		US_StoreUFO(&(ccs.aircraftTemplates[ufotype]), inst, event); 	}  	for (i = ccs.numUFOs - 1; i >= 0; i--) {Index: src/client/campaign/cp_research_callbacks.c===================================================================--- src/client/campaign/cp_research_callbacks.c	(revision 25739)+++ src/client/campaign/cp_research_callbacks.c	(working copy)@@ -608,10 +608,7 @@  	switch (tech->statusResearch) { 	case RS_RUNNING:-		/* Remove all scis from it and set the status to paused (i.e. it's differnet from a RS_NONE since it may have a little bit of progress already). */-		while (tech->scientists > 0)-			RS_RemoveScientist(tech, NULL);-		assert(tech->statusResearch == RS_PAUSED);+		RS_StopResearch(tech); 		break; 	case RS_PAUSED: 		/** @todo remove? Popup info how much is already researched? */Index: src/client/campaign/cp_base_callbacks.c===================================================================--- src/client/campaign/cp_base_callbacks.c	(revision 25739)+++ src/client/campaign/cp_base_callbacks.c	(working copy)@@ -33,6 +33,7 @@ #include "cp_base.h" #include "cp_map.h" #include "cp_popup.h"+#include "cp_ufo.h"  /** @brief Used from menu scripts as parameter for mn_select_base */ #define CREATE_NEW_BASE_ID -1@@ -377,10 +378,6 @@ 		case B_SMALL_HANGAR: 			B_RemoveAircraftExceedingCapacity(base, buildingType); 			break;-		case B_UFO_HANGAR:-		case B_UFO_SMALL_HANGAR:-			B_RemoveUFOsExceedingCapacity(base, buildingType);-			break; 		case B_QUARTERS: 			E_DeleteEmployeesExceedingCapacity(base); 			break;@@ -853,8 +850,17 @@ 			const production_t *production = &queue->items[i]; 			const objDef_t *objDef = production->item; 			const aircraft_t *aircraft = production->aircraft;-			const char *name = objDef ? _(objDef->name) : _(aircraft->name);-			assert(name);+			const storedUFO_t *ufo = production->ufo;+			const char *name;+			+			if (objDef)+				name = _(objDef->name);+			else if (aircraft)+				name = _(aircraft->name);+			else if (ufo)+				name = _(UFO_TypeToName(ufo->ufoTemplate->ufotype));+			else+				Com_Error(ERR_DROP, "BaseSummary_Init: Invalid production type (not item, not aircraft, not disassembly)\n");  			/** @todo use the same method as we do in PR_ProductionInfo */ 			Q_strcat(textStatsBuffer, va(_("%s\t\t\t\t\t\t%d\t\t\t\t%.2f%%\n"), name,Index: src/client/campaign/cp_transfer.c===================================================================--- src/client/campaign/cp_transfer.c	(revision 25739)+++ src/client/campaign/cp_transfer.c	(working copy)@@ -111,8 +111,9 @@  */ static int TR_CheckItem (const objDef_t *od, const base_t *destbase, int amount) {-	int i, intransfer = 0, amtransfer = 0;-	int smallufotransfer = 0, bigufotransfer = 0;+	int i;+	int intransfer = 0;+	int amtransfer = 0;  	assert(od); 	assert(destbase);@@ -122,14 +123,7 @@ 		if (td.trItemsTmp[i] > 0) { 			if (!strcmp(csi.ods[i].id, ANTIMATTER_TECH_ID)) 				amtransfer = ANTIMATTER_SIZE * td.trItemsTmp[i];-			if (od->tech->type == RS_CRAFT) {-				aircraft_t *ufocraft = AIR_GetAircraft(od->tech->provides);-				assert(ufocraft);-				if (ufocraft->size == AIRCRAFT_LARGE)-					bigufotransfer++;-				else-					smallufotransfer++;-			} else+			else 				intransfer += csi.ods[i].size * td.trItemsTmp[i]; 		} 	}@@ -151,33 +145,7 @@ 			/* amount to transfer can't be bigger than what we have */ 			amount = min(amount, (destbase->capacities[CAP_ANTIMATTER].max - destbase->capacities[CAP_ANTIMATTER].cur - amtransfer) / ANTIMATTER_SIZE); 		}-	} else if (od->tech->type == RS_CRAFT) { /* This is UFO craft */-		aircraft_t *ufocraft = AIR_GetAircraft(od->tech->provides);-		assert(ufocraft);-		if (ufocraft->size == AIRCRAFT_LARGE) {-			if (!B_GetBuildingStatus(destbase, B_UFO_HANGAR)) {-				MN_Popup(_("Missing Large UFO Hangar"), _("Destination base does not have functional Large UFO Hangar.\n"));-				return 0;-			} else {-				amount = min(amount, destbase->capacities[CAP_UFOHANGARS_LARGE].max - destbase->capacities[CAP_UFOHANGARS_LARGE].cur - bigufotransfer);-				if (amount <= 0) {-					MN_Popup(_("Missing Large UFO Hangar"), _("Destination base does not have enough Large UFO Hangar.\n"));-					return 0;-				}-			}-		} else if (ufocraft->size == AIRCRAFT_SMALL) {-			if (!B_GetBuildingStatus(destbase, B_UFO_SMALL_HANGAR)) {-				MN_Popup(_("Missing Small UFO Hangar"), _("Destination base does not have functional Small UFO Hangar.\n"));-				return qfalse;-			} else {-				amount = min(amount, destbase->capacities[CAP_UFOHANGARS_SMALL].max - destbase->capacities[CAP_UFOHANGARS_SMALL].cur - smallufotransfer);-				if (amount <= 0) {-					MN_Popup(_("Missing Small UFO Hangar"), _("Destination base does not have enough Small UFO Hangar.\n"));-					return 0;-				}-			}-		}-	} else {	/*This is not antimatter*/+	} else {	/*This is not antimatter */ 		if (!B_GetBuildingStatus(destbase, B_STORAGE))	/* Return if the target base doesn't have storage or power. */ 			return 0; @@ -689,16 +657,7 @@ 		if (td.trItemsTmp[i] > 0) { 			if (!strcmp(csi.ods[i].id, ANTIMATTER_TECH_ID)) 				B_ManageAntimatter(base, td.trItemsTmp[i], qtrue);-			else if (csi.ods[i].tech->type == RS_CRAFT) { /* This is UFO craft */-				const aircraft_t *ufocraft = AIR_GetAircraft(csi.ods[i].tech->provides);-				assert(ufocraft);-				/* don't use B_UpdateStorageAndCapacity: UFO are not stored in storage */-				base->storage.num[i] += td.trItemsTmp[i];-				if (ufocraft->size == AIRCRAFT_LARGE)-					base->capacities[CAP_UFOHANGARS_LARGE].cur++;-				else-					base->capacities[CAP_UFOHANGARS_SMALL].cur++;-			} else+			else 				B_UpdateStorageAndCapacity(base, &csi.ods[i], td.trItemsTmp[i], qfalse, qfalse); 		} 	}@@ -744,20 +703,7 @@ 				if (transfer->itemAmount[i] > 0) { 					if (!strcmp(csi.ods[i].id, ANTIMATTER_TECH_ID)) 						B_ManageAntimatter(destination, transfer->itemAmount[i], qtrue);-					else if (csi.ods[i].tech->type == RS_CRAFT) { /* This is UFO craft */-						const aircraft_t *ufocraft = AIR_GetAircraft(csi.ods[i].tech->provides);-						assert(ufocraft);-						for (j = 0; j < transfer->itemAmount[i]; j++) {-							if (UR_ConditionsForStoring(destination, ufocraft)) {-								/* don't use B_UpdateStorageAndCapacity: UFO are not stored in storage */-								destination->storage.num[i]++;-								if (ufocraft->size == AIRCRAFT_LARGE)-									destination->capacities[CAP_UFOHANGARS_LARGE].cur++;-								else-									destination->capacities[CAP_UFOHANGARS_SMALL].cur++;-							}-						}-					} else+					else 						B_UpdateStorageAndCapacity(destination, &csi.ods[i], transfer->itemAmount[i], qfalse, qtrue); 				} 			}@@ -1202,16 +1148,7 @@ 						td.trItemsTmp[i] += amount; 						if (!strcmp(od->id, ANTIMATTER_TECH_ID)) 							B_ManageAntimatter(base, amount, qfalse);-						else if (od->tech->type == RS_CRAFT) { /* This is UFO craft */-							const aircraft_t *ufocraft = AIR_GetAircraft(csi.ods[i].tech->provides);-							assert(ufocraft);-							/* don't use B_UpdateStorageAndCapacity: UFO are not stored in storage */-							base->storage.num[i] -= amount;-							if (ufocraft->size == AIRCRAFT_LARGE)-								base->capacities[CAP_UFOHANGARS_LARGE].cur -= amount;-							else-								base->capacities[CAP_UFOHANGARS_SMALL].cur -= amount;-						} else+						else 							B_UpdateStorageAndCapacity(base, od, -amount, qfalse, qfalse); 						break; 					} else@@ -1569,16 +1506,7 @@ 					td.trItemsTmp[i] -= amount; 					if (!strcmp(csi.ods[i].id, ANTIMATTER_TECH_ID)) 						B_ManageAntimatter(base, amount, qfalse);-					else if (csi.ods[i].tech->type == RS_CRAFT) { /* This is UFO craft */-						const aircraft_t *ufocraft = AIR_GetAircraft(csi.ods[i].tech->provides);-						assert(ufocraft);-						/* don't use B_UpdateStorageAndCapacity: UFO are not stored in storage */-						base->storage.num[i] += amount;-						if (ufocraft->size == AIRCRAFT_LARGE)-							base->capacities[CAP_UFOHANGARS_LARGE].cur += amount;-						else-							base->capacities[CAP_UFOHANGARS_SMALL].cur += amount;-					} else+					else 						B_UpdateStorageAndCapacity(base, &csi.ods[i], amount, qfalse, qfalse); 					break; 				}Index: src/client/campaign/cp_save.c===================================================================--- src/client/campaign/cp_save.c	(revision 25739)+++ src/client/campaign/cp_save.c	(working copy)@@ -650,6 +650,7 @@ 	static saveSubsystems_t xvi_subsystemXML = {"xvirate", XVI_SaveXML, XVI_LoadXML}; 	static saveSubsystems_t ins_subsystemXML = {"installation", INS_SaveXML, INS_LoadXML}; 	static saveSubsystems_t mso_subsystemXML = {"messageoptions", MSO_SaveXML, MSO_LoadXML};+	static saveSubsystems_t us_subsystemXML = {"ufostores", US_SaveXML, US_LoadXML};  	saveSubsystemsAmount = 0; 	memset(&saveSubsystems, 0, sizeof(saveSubsystems));@@ -664,15 +665,16 @@ 	SAV_AddSubsystem(&rs_subsystemXML); 	SAV_AddSubsystem(&e_subsystemXML); 	SAV_AddSubsystem(&ac_subsystemXML);+	SAV_AddSubsystem(&air_subsystemXML);+	SAV_AddSubsystem(&ins_subsystemXML);+	SAV_AddSubsystem(&us_subsystemXML); 	SAV_AddSubsystem(&pr_subsystemXML);-	SAV_AddSubsystem(&air_subsystemXML); 	SAV_AddSubsystem(&ms_subsystemXML); 	SAV_AddSubsystem(&stats_subsystemXML); 	SAV_AddSubsystem(&na_subsystemXML); 	SAV_AddSubsystem(&trans_subsystemXML); 	SAV_AddSubsystem(&ab_subsystemXML); 	SAV_AddSubsystem(&xvi_subsystemXML);-	SAV_AddSubsystem(&ins_subsystemXML); 	SAV_AddSubsystem(&mso_subsystemXML);  	Cmd_AddCommand("game_quickloadinit", SAV_GameQuickLoadInit_f, "Check whether there is a quicksave at all");Index: src/client/campaign/cp_produce_callbacks.c===================================================================--- src/client/campaign/cp_produce_callbacks.c	(revision 25739)+++ src/client/campaign/cp_produce_callbacks.c	(working copy)@@ -5,7 +5,7 @@  */  /*-Copyright (C) 1997-2008 UFO:AI Team+Copyright (C) 1997-2009 UFO:AI Team  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -29,6 +29,7 @@ #include "../menu/m_popup.h" #include "cp_campaign.h" #include "cp_market.h"+#include "cp_ufo.h" #include "cp_produce.h" #include "cp_produce_callbacks.h" @@ -50,7 +51,7 @@  /** Currently selected entry in the productionItemList (depends on content) */ static objDef_t *selectedItem = NULL;-static components_t *selectedDisassembly = NULL;+static storedUFO_t *selectedDisassembly = NULL; static aircraft_t *selectedAircraft = NULL;  /** @brief Number of blank lines between queued items and tech list. */@@ -68,28 +69,6 @@ }  /**- * @brief Conditions for disassembling.- * @param[in] base Pointer to base where the component is stored.- * @param[in] comp Pointer to components definition.- * @return qtrue if disassembling is ready, qfalse otherwise.- */-static qboolean PR_ConditionsDisassembly (const base_t *base, const components_t *comp)-{-	const objDef_t *od;--	assert(base);-	assert(comp);--	od = comp->assemblyItem;-	assert(od);--	if (RS_IsResearched_ptr(od->tech) && base->storage.num[od->idx] > 0)-		return qtrue;-	else-		return qfalse;-}--/**  * @brief Checks if the production requirements are met for a defined amount.  * @param[in] amount How many items are planned to be produced.  * @param[in] reqs The production requirements of the item that is to be produced.@@ -112,6 +91,12 @@ 					producible = qfalse; 				} 			}+			if (req->type == RS_LINK_UFO) {+				/* The same code is used in "RS_RequirementsMet" */+				Com_DPrintf(DEBUG_CLIENT, "PR_RequirementsMet: %s\n", req->id);+				if (US_UFOsInStorage(req->link, NULL) < req->amount)+					producible = qfalse;+			} 		} 		if (producible) 			producibleAmount++;@@ -130,13 +115,13 @@  * @param[in] amount Desired amount to produce.  * @param[in] disassembling True if this is disassembling, false if production.  */-static production_t *PR_QueueNew (base_t *base, production_queue_t *queue, objDef_t *item, aircraft_t *aircraftTemplate, signed int amount, qboolean disassembling)+static production_t *PR_QueueNew (base_t *base, production_queue_t *queue, objDef_t *item, aircraft_t *aircraftTemplate, storedUFO_t *ufo, signed int amount) { 	int numWorkshops = 0; 	production_t *prod; 	const technology_t *tech; -	assert((item && !aircraftTemplate) || (!item && aircraftTemplate));+	assert((item && !aircraftTemplate && !ufo) || (!item && aircraftTemplate && !ufo) || (!item && !aircraftTemplate && ufo)); 	assert(base);  	if (queue->numItems >= MAX_PRODUCTIONS)@@ -161,13 +146,15 @@ 	/* self-reference. */ 	prod->idx = queue->numItems; -	if (produceCategory != FILTER_AIRCRAFT)+	if (item)  		tech = item->tech;-	else+	else if (aircraftTemplate) 		tech = aircraftTemplate->tech;+	else+		tech = ufo->ufoTemplate->tech;  	/* We cannot queue new aircraft if no free hangar space. */-	if (produceCategory == FILTER_AIRCRAFT) {+	if (aircraftTemplate) { 		if (!B_GetBuildingStatus(base, B_COMMAND)) { 			MN_Popup(_("Hangars not ready"), _("You cannot queue aircraft.\nNo command centre in this base.\n")); 			return NULL;@@ -184,18 +171,20 @@  	prod->item = item; 	prod->aircraft = aircraftTemplate;-	prod->amount = amount;-	if (disassembling) {+	prod->ufo = ufo;+	+	if (ufo) { 		/* Disassembling. */ 		prod->production = qfalse;+		prod->amount = 1; -		/* We have to remove amount of items being disassembled from base storage. */-		base->storage.num[item->idx] -= amount;+		ufo->disassembly = prod; 		/* Now find related components definition. */ 		prod->percentDone = 0.0f; 	} else { 		/* Production. */ 		prod->production = qtrue;+		prod->amount = amount;  		/* Don't try to add to queue an item which is not producible. */ 		if (tech->produceTime < 0)@@ -227,12 +216,12 @@ 	/* First add all the queue items ... */ 	for (i = 0; i < queue->numItems; i++) { 		const production_t *prod = &queue->items[i];-		if (!prod->aircraft) {+		if (prod->item) { 			const objDef_t *od = prod->item; 			LIST_AddString(&productionList, va("%s", _(od->name))); 			LIST_AddString(&productionAmount, va("%i", base->storage.num[prod->item->idx])); 			LIST_AddString(&productionQueued, va("%i", prod->amount));-		} else {+		} else if (prod->aircraft) { 			const aircraft_t *aircraftTemplate = prod->aircraft; 			LIST_AddString(&productionList, va("%s", _(aircraftTemplate->name))); 			for (j = 0, counter = 0; j < ccs.numAircraft; j++) {@@ -243,6 +232,12 @@ 			} 			LIST_AddString(&productionAmount, va("%i", counter)); 			LIST_AddString(&productionQueued, va("%i", prod->amount));+		} else if (prod->ufo) {+			const storedUFO_t *ufo = prod->ufo;++			LIST_AddString(&productionList, va("%s (at %s)", _(UFO_TypeToName(ufo->ufoTemplate->ufotype)), ufo->installation->name));+			LIST_AddString(&productionAmount, va("%i", US_UFOsInStorage(ufo->ufoTemplate, ufo->installation)));+			LIST_AddString(&productionQueued, "1"); 		} 	} @@ -257,18 +252,26 @@  	/* Then go through all object definitions ... */ 	if (produceCategory == FILTER_DISASSEMBLY) {-		for (i = 0; i < ccs.numComponents; i++) {-			const objDef_t *asOd = ccs.components[i].assemblyItem;-			components_t *comp = &ccs.components[i];-			if (!asOd)+		/** UFOs at UFO stores */+		for (i = 0; i < ccs.numStoredUFOs; i++) {+			storedUFO_t *ufo = US_GetStoredUFOByIDX(i);++			if (!ufo) 				continue;-			if (PR_ConditionsDisassembly(base, comp)) {-				LIST_AddPointer(&productionItemList, comp);+			/* The UFO is being disassembled aready */+			if (ufo->disassembly)+				continue;+			/* UFO not researched */+			if (!RS_IsResearched_ptr(ufo->ufoTemplate->tech))+				continue;+			/* UFO is being transported */+			if (ufo->arrive.day > ccs.date.day || (ufo->arrive.day == ccs.date.day && ufo->arrive.sec > ccs.date.sec))+			 	continue; -				LIST_AddString(&productionList, va("%s", _(asOd->name)));-				LIST_AddString(&productionAmount, va("%i", base->storage.num[asOd->idx]));-				LIST_AddString(&productionQueued, "");-			}+			LIST_AddPointer(&productionItemList, ufo);+			LIST_AddString(&productionList, va("%s (at %s)", _(UFO_TypeToName(ufo->ufoTemplate->ufotype)), ufo->installation->name));+			LIST_AddString(&productionAmount, va("%i", US_UFOsInStorage(ufo->ufoTemplate, ufo->installation)));+			LIST_AddString(&productionQueued, ""); 		} 	} else if (produceCategory == FILTER_AIRCRAFT) { 		for (i = 0; i < ccs.numAircraftTemplates; i++) {@@ -343,7 +346,7 @@ 		Com_sprintf(productionInfo, sizeof(productionInfo), _("No item selected")); 		Cvar_Set("mn_item", ""); 	} else {-		prodPerHour = PR_CalculateProductionPercentDone(base, od->tech, NULL, qfalse);+		prodPerHour = PR_CalculateProductionPercentDone(base, od->tech, NULL); 		/* If you entered production menu, that means that prodPerHour > 0 (must not divide by 0) */ 		assert(prodPerHour > 0); 		time = ceil((1.0f - percentDone) / prodPerHour);@@ -363,35 +366,35 @@  * @param[in] base Pointer to the base where informations should be printed.  * @sa PR_ProductionInfo  */-static void PR_DisassemblyInfo (const base_t *base, const objDef_t *od, const components_t *comp, float percentDone)+static void PR_DisassemblyInfo (const base_t *base, const storedUFO_t *ufo, float percentDone) { 	static char productionInfo[512]; 	int time, i; 	float prodPerHour;  	assert(base);-	assert(od);-	assert(comp);-	assert(od->tech);+	assert(ufo);+	assert(ufo->ufoTemplate);+	assert(ufo->ufoTemplate->tech); -	prodPerHour = PR_CalculateProductionPercentDone(base, od->tech, comp, qtrue);+	prodPerHour = PR_CalculateProductionPercentDone(base, ufo->ufoTemplate->tech, ufo->comp); 	/* If you entered production menu, that means that prodPerHour > 0 (must not divide by 0) */ 	assert(prodPerHour > 0); 	time = ceil((1.0f - percentDone) / prodPerHour); -	Com_sprintf(productionInfo, sizeof(productionInfo), _("%s - disassembly\n"), _(od->name));-	Q_strcat(productionInfo, _("Components: "), sizeof(productionInfo));+	Com_sprintf(productionInfo, sizeof(productionInfo), _("%s - disassembly\n"), _(UFO_TypeToName(ufo->ufoTemplate->ufotype)));+	Q_strcat(productionInfo, _("Components:\n"), sizeof(productionInfo)); 	/* Print components. */-	for (i = 0; i < comp->numItemtypes; i++) {-		const objDef_t *compOd = comp->items[i];+	for (i = 0; i < ufo->comp->numItemtypes; i++) {+		const objDef_t *compOd = ufo->comp->items[i]; 		assert(compOd);-		Q_strcat(productionInfo, va(_("%s (%i) "), _(compOd->name), comp->itemAmount[i]),+		Q_strcat(productionInfo, va("  %s (%i)\n", _(compOd->name), ufo->comp->itemAmount[i]), 			sizeof(productionInfo)); 	} 	Q_strcat(productionInfo, "\n", sizeof(productionInfo)); 	Q_strcat(productionInfo, va(_("Disassembly time\t%ih\n"), time), sizeof(productionInfo));-	Cvar_Set("mn_item", od->id); 	MN_RegisterText(TEXT_PRODUCTION_INFO, productionInfo);+	Cvar_Set("mn_item", ufo->id); }  /**@@ -427,10 +430,15 @@ 		MN_ExecuteConfunc("prod_taskselected"); 		if (prod->aircraft) { 			PR_AircraftInfo(prod->aircraft);-		} else if (prod->production) {+			MN_ExecuteConfunc("amountsetter enable");+		} else if (prod->item) { 			PR_ItemProductionInfo(base, prod->item, prod->percentDone);+			MN_ExecuteConfunc("amountsetter enable");+		} else if (prod->ufo) {+			PR_DisassemblyInfo(base, prod->ufo, prod->percentDone);+			MN_ExecuteConfunc("amountsetter disable"); 		} else {-			PR_DisassemblyInfo(base, prod->item, CL_GetComponentsByItem(prod->item), prod->percentDone);+			Com_Error(ERR_DROP, "PR_ProductionInfo: Selected production is not item nor aircraft nor ufo.\n"); 		} 		Cvar_SetValue("mn_production_amount", selectedProduction->amount); 	} else {@@ -448,7 +456,7 @@ 			} else if (selectedItem) { 				PR_ItemProductionInfo(base, selectedItem, 0.0); 			} else if (selectedDisassembly) {-				PR_DisassemblyInfo(base, selectedDisassembly->assemblyItem, selectedDisassembly, 0.0);+				PR_DisassemblyInfo(base, selectedDisassembly, 0.0); 			} 		} 	}@@ -485,10 +493,12 @@ 		if (selectedProduction->aircraft) { 			assert(selectedProduction->aircraft->tech); 			UP_OpenWith(selectedProduction->aircraft->tech->id);-		} else {+		} else if (selectedProduction->item) { 			const objDef_t *od = selectedProduction->item; 			assert(od->tech); 			UP_OpenWith(od->tech->id);+		} else if (selectedProduction->ufo) {+			UP_OpenWith(selectedProduction->ufo->ufoTemplate->tech->id); 		} 	} else if (num >= queue->numItems + QUEUE_SPACERS) { 		/* Clicked in the item list. */@@ -502,9 +512,9 @@ 			if (aircraftTemplate && aircraftTemplate->tech) 				UP_OpenWith(aircraftTemplate->tech->id); 		} else if (produceCategory == FILTER_DISASSEMBLY) {-			components_t *comp = (components_t*)LIST_GetByIdx(productionItemList, idx);-			if (comp && comp->assemblyItem && comp->assemblyItem->tech) {-				UP_OpenWith(comp->assemblyItem->tech->id);+			storedUFO_t *ufo = (storedUFO_t*)LIST_GetByIdx(productionItemList, idx);+			if (ufo && ufo->ufoTemplate && ufo->ufoTemplate->tech) {+				UP_OpenWith(ufo->ufoTemplate->tech->id); 			} 		} else { 			objDef_t *od = (objDef_t*)LIST_GetByIdx(productionItemList, idx);@@ -568,10 +578,10 @@ 		const int idx = num - queue->numItems - QUEUE_SPACERS;  		if (produceCategory == FILTER_DISASSEMBLY) {-			components_t *comp = (components_t*)LIST_GetByIdx(productionItemList, idx);+			storedUFO_t *ufo = (storedUFO_t*)LIST_GetByIdx(productionItemList, idx);  			PR_ClearSelected();-			selectedDisassembly = comp;+			selectedDisassembly = ufo;  			PR_ProductionInfo(base); 		} else if (produceCategory == FILTER_AIRCRAFT) {@@ -650,7 +660,7 @@ 		if (produceCategory == FILTER_AIRCRAFT) 			selectedAircraft = (aircraft_t*)LIST_GetByIdx(productionItemList, 0); 		else if (produceCategory == FILTER_DISASSEMBLY)-			selectedDisassembly = (components_t*)LIST_GetByIdx(productionItemList, 0);+			selectedDisassembly = (storedUFO_t*)LIST_GetByIdx(productionItemList, 0); 		else 			selectedItem = (objDef_t*)LIST_GetByIdx(productionItemList, 0); 	}@@ -702,7 +712,7 @@  */ static void PR_ProductionIncrease_f (void) {-	int amount = 1, amountTemp = 0;+	int amount = 1; 	production_queue_t *queue; 	production_t *prod; 	base_t *base = B_GetCurrentSelectedBase();@@ -741,26 +751,8 @@ 				prod->amount += amount; 			} 		} else {	/* Disassembling. */-			/* We can disassembly only as many items as we have in base storage. */-			if (base->storage.num[prod->item->idx] > amount)-				amountTemp = amount;-			else-				amountTemp = base->storage.num[prod->item->idx];-			Com_DPrintf(DEBUG_CLIENT, "PR_ProductionIncrease_f: amounts: storage: %i, param: %i, temp: %i\n", base->storage.num[prod->item->idx], amount, amountTemp);--			/* Now check if we can add more items and-			 * remove the amount we just added to queue from base storage. */-			if (prod->amount + amountTemp > MAX_PRODUCTION_AMOUNT) {-				if (MAX_PRODUCTION_AMOUNT - prod->amount >= 0) {-					/* Calculate the maximum allowed amount to be added. */-					amountTemp = MAX_PRODUCTION_AMOUNT - prod->amount;-				} else {-					return;-				}-			}--			base->storage.num[prod->item->idx] -= amountTemp;-			prod->amount += amountTemp;+			/* We can disassembly UFOs only one-by-one. */+			return; 		} 	} else { 		if (!selectedDisassembly) {@@ -768,23 +760,10 @@ 				MN_Popup(_("Hangars not ready"), _("You cannot queue aircraft.\nNo free space in hangars.\n")); 				return; 			}-			prod = PR_QueueNew(base, queue, selectedItem, selectedAircraft, amount, qfalse);	/* Production. (only of of the "selected" pointer can be nonNULL) */+			prod = PR_QueueNew(base, queue, selectedItem, selectedAircraft, NULL, amount);	/* Production. (only of of the "selected" pointer can be nonNULL) */ 		} else {-			/* We can disassemble only as many items as we have in base storage. */-			assert(selectedDisassembly && selectedDisassembly->assemblyItem);--			if (base->storage.num[selectedDisassembly->assemblyItem->idx] > amount)-				amountTemp = amount;-			else-				amountTemp = base->storage.num[selectedDisassembly->assemblyItem->idx];--			/* Check if we can add more items. */-			if (amountTemp > MAX_PRODUCTION_AMOUNT) {-				/* Add as many items as possible. */-				amountTemp = MAX_PRODUCTION_AMOUNT;-			}--			prod = PR_QueueNew(base, queue, selectedDisassembly->assemblyItem, NULL, amountTemp, qtrue);	/* Disassembling. */+			/* We can disassemble UFOs only one-by-one. */+			prod = PR_QueueNew(base, queue, NULL, NULL, selectedDisassembly, 1);	/* Disassembling. */ 		}  		/** prod is NULL when queue limit is reached@@ -796,7 +775,8 @@ 		} else 			MN_ExecuteConfunc("prod_selectline %i", prod->idx); -		if (produceCategory != FILTER_AIRCRAFT) {+		+		if (prod->item) { 			/* Get technology of the item in the selected queue-entry. */ 			const objDef_t *od = prod->item; 			int producibleAmount = amount;@@ -816,13 +796,8 @@ 						amount, producibleAmount)); 				} -				if (!selectedDisassembly) {-					Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Production of %s started"), _(od->name));-					MSO_CheckAddNewMessage(NT_PRODUCTION_STARTED, _("Production started"), cp_messageBuffer, qfalse, MSG_PRODUCTION, od->tech);-				} else {-					Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Disassembling of %s started"), _(od->name));-					MSO_CheckAddNewMessage(NT_PRODUCTION_STARTED, _("Production started"), cp_messageBuffer, qfalse, MSG_PRODUCTION, od->tech);-				}+				Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Production of %s started"), _(od->name));+				MSO_CheckAddNewMessage(NT_PRODUCTION_STARTED, _("Production started"), cp_messageBuffer, qfalse, MSG_PRODUCTION, od->tech);  				/* Now we select the item we just created. */ 				PR_ClearSelected();@@ -835,7 +810,7 @@ 				 *     This info should also be displayed in the item-info. 				 *  -) can can (if possible) change the 'amount' to a vlalue that _can_ be produced (i.e. the maximum amount possible).*/ 			}-		} else {+		} else if (prod->aircraft) { 			const aircraft_t *aircraftTemplate = prod->aircraft; 			assert(aircraftTemplate); 			assert(aircraftTemplate == aircraftTemplate->tpl);@@ -846,6 +821,14 @@ 			/* Now we select the item we just created. */ 			PR_ClearSelected(); 			selectedProduction = &queue->items[queue->numItems - 1];+		} else { /* Disassembly */+			storedUFO_t *ufo = prod->ufo;++			Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Disassembling of %s started"), _(UFO_TypeToName(ufo->ufoTemplate->ufotype)));+			MSO_CheckAddNewMessage(NT_PRODUCTION_STARTED, _("Production started"), cp_messageBuffer, qfalse, MSG_PRODUCTION, ufo->ufoTemplate->tech);++			PR_ClearSelected();+			selectedProduction = &queue->items[queue->numItems - 1]; 		} 	} @@ -860,20 +843,22 @@ { 	production_queue_t *queue; 	base_t *base = B_GetCurrentSelectedBase();+	int prodIDX;  	if (!base || !selectedProduction) 		return; +	prodIDX = selectedProduction->idx; 	queue = &ccs.productions[base->idx]; -	PR_QueueDelete(base, queue, selectedProduction->idx);+	PR_QueueDelete(base, queue, prodIDX);  	if (queue->numItems == 0) { 		selectedProduction = NULL; 		MN_ExecuteConfunc("prod_selectline -1");-	} else if (selectedProduction->idx >= queue->numItems) {+	} else if (prodIDX >= queue->numItems) { 		selectedProduction = &queue->items[queue->numItems - 1];-		MN_ExecuteConfunc("prod_selectline %i", selectedProduction->idx);+		MN_ExecuteConfunc("prod_selectline %i", prodIDX); 	}  	PR_ProductionInfo(base);@@ -904,9 +889,6 @@ 		amountTemp = prod->amount;  	prod->amount -= amountTemp;-	/* We need to readd items being disassembled to base storage. */-	if (!prod->production)-		base->storage.num[prod->item->idx] += amountTemp;  	if (prod->amount <= 0) { 		PR_ProductionStop_f();Index: src/client/campaign/cp_produce.c===================================================================--- src/client/campaign/cp_produce.c	(revision 25739)+++ src/client/campaign/cp_produce.c	(working copy)@@ -5,7 +5,7 @@  */  /*-Copyright (C) 2002-2007 UFO: Alien Invasion team.+Copyright (C) 2002-2009 UFO: Alien Invasion team.  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -31,6 +31,7 @@ #include "../menu/m_nodes.h" #include "../mxml/mxml_ufoai.h" #include "cp_campaign.h"+#include "cp_ufo.h" #include "cp_produce_callbacks.h"  /** @brief Used in production costs (to allow reducing prices below 1x). */@@ -56,7 +57,7 @@  * @sa PR_DisassemblyInfo  * @return 0 if the production does not make any progress, 1 if the whole item is built in 1 hour  */-float PR_CalculateProductionPercentDone (const base_t *base, const technology_t *tech, const components_t *comp, qboolean disassembly)+float PR_CalculateProductionPercentDone (const base_t *base, const technology_t *tech, const components_t *comp) { 	signed int allworkers = 0, maxworkers = 0; 	signed int timeDefault = 0;@@ -72,11 +73,10 @@ 		maxworkers = allworkers; 	} -	if (!disassembly) {+	if (!comp) { 		assert(tech); 		timeDefault = tech->produceTime;	/* This is the default production time for 10 workers. */ 	} else {-		assert(comp); 		timeDefault = comp->time;		/* This is the default disassembly time for 10 workers. */ 	} @@ -148,7 +148,6 @@  */ void PR_QueueDelete (base_t *base, production_queue_t *queue, int index) {-	int i; 	const objDef_t *od; 	production_t *prod; @@ -156,7 +155,9 @@  	assert(base); -	if (prod->itemsCached && !prod->aircraft) {+	if (prod->ufo) {+		prod->ufo->disassembly = NULL;+	} else if (prod->itemsCached && !prod->aircraft) { 		/* Get technology of the item in the selected queue-entry. */ 		od = prod->item; 		if (od->tech) {@@ -168,20 +169,7 @@ 		prod->itemsCached = qfalse; 	} -	/* Read disassembly to base storage. */-	if (!prod->production)-		base->storage.num[prod->item->idx] += prod->amount;--	queue->numItems--;-	if (queue->numItems < 0)-		queue->numItems = 0;--	/* Copy up other items. */-	for (i = index; i < queue->numItems; i++) {-		queue->items[i] = queue->items[i + 1];-		/* Fix index in list */-		queue->items[i].idx = i;-	}+	REMOVE_ELEM_ADJUST_IDX(queue->items, index, queue->numItems); }  /**@@ -205,17 +193,23 @@ 	for (i = index; i < newIndex; i++) { 		queue->items[i] = queue->items[i + 1]; 		queue->items[i].idx = i;+		if (queue->items[i].ufo)+			queue->items[i].ufo->disassembly = &(queue->items[i]); 	}  	/* copy down */ 	for (i = index; i > newIndex; i--) { 		queue->items[i] = queue->items[i - 1]; 		queue->items[i].idx = i;+		if (queue->items[i].ufo)+			queue->items[i].ufo->disassembly = &(queue->items[i]); 	}  	/* insert item */ 	queue->items[newIndex] = saved; 	queue->items[newIndex].idx = newIndex;+	if (queue->items[newIndex].ufo)+		queue->items[newIndex].ufo->disassembly = &(queue->items[newIndex]); }  /**@@ -310,6 +304,7 @@ 	int i; 	const objDef_t *od; 	const aircraft_t *aircraft;+	storedUFO_t *ufo; 	production_t *prod;  	/* Loop through all founded bases. Then check productions@@ -333,14 +328,20 @@ 		if (prod->item) { 			od = prod->item; 			aircraft = NULL;-		} else {+			ufo = NULL;+		} else if (prod->aircraft) { 			assert(prod->aircraft); 			od = NULL; 			aircraft = prod->aircraft;+			ufo = NULL;+		} else {+			od = NULL;+			aircraft = NULL;+			ufo = prod->ufo; 		}  		if (prod->production) {	/* This is production, not disassembling. */-			if (!prod->aircraft) {+			if (od) { 				/* Not enough money to produce more items in this base. */ 				if (od->price * PRODUCE_FACTOR / PRODUCE_DIVISOR > ccs.credits) { 					if (!prod->creditMessage) {@@ -384,7 +385,7 @@ 				} 			} 		} else {		/* This is disassembling. */-			if (base->capacities[CAP_ITEMS].max - base->capacities[CAP_ITEMS].cur < PR_DisassembleItem(NULL, CL_GetComponentsByItem(prod->item), qtrue)) {+			if (base->capacities[CAP_ITEMS].max - base->capacities[CAP_ITEMS].cur < PR_DisassembleItem(NULL, ufo->comp, qtrue)) { 				if (!prod->spaceMessage) { 					Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Not enough free storage space in base %s. Disassembling postponed.\n"), base->name); 					MSO_CheckAddNewMessage(NT_PRODUCTION_FAILED, _("Notice"), cp_messageBuffer, qfalse, MSG_STANDARD, NULL);@@ -395,16 +396,13 @@ 			} 		} -		if (!prod->aircraft && !od->tech)-			Com_Error(ERR_FATAL, "PR_ProductionRun: No tech pointer for object %s ('%s')", prod->item->id, od->id);- 		if (prod->production) {	/* This is production, not disassembling. */ 			if (!prod->aircraft)-				prod->percentDone += (PR_CalculateProductionPercentDone(base, od->tech, NULL, qfalse) / MINUTES_PER_HOUR);+				prod->percentDone += (PR_CalculateProductionPercentDone(base, od->tech, NULL) / MINUTES_PER_HOUR); 			else-				prod->percentDone += (PR_CalculateProductionPercentDone(base, aircraft->tech, NULL, qfalse) / MINUTES_PER_HOUR);+				prod->percentDone += (PR_CalculateProductionPercentDone(base, aircraft->tech, NULL) / MINUTES_PER_HOUR); 		} else /* This is disassembling. */-			prod->percentDone += (PR_CalculateProductionPercentDone(base, od->tech, CL_GetComponentsByItem(prod->item), qtrue) / MINUTES_PER_HOUR);+			prod->percentDone += (PR_CalculateProductionPercentDone(base, ufo->ufoTemplate->tech, ufo->comp) / MINUTES_PER_HOUR);  		if (prod->percentDone >= 1.0f) { 			if (prod->production) {	/* This is production, not disassembling. */@@ -435,28 +433,14 @@ 					} 				} 			} else {	/* This is disassembling. */-				base->capacities[CAP_ITEMS].cur += PR_DisassembleItem(base, CL_GetComponentsByItem(prod->item), qfalse);+				base->capacities[CAP_ITEMS].cur += PR_DisassembleItem(base, ufo->comp, qfalse); 				prod->percentDone = 0.0f; 				prod->amount--;-				/* If this is aircraft dummy item, update UFO hangars capacity. */-				if (od->tech->type == RS_CRAFT) {-					const aircraft_t *ufocraft = AIR_GetAircraft(od->id);-					assert(ufocraft);-					if (ufocraft->size == AIRCRAFT_LARGE) {-						/* Large UFOs can only be stored in Large UFO Hangar */-						if (base->capacities[CAP_UFOHANGARS_LARGE].cur > 0)-							base->capacities[CAP_UFOHANGARS_LARGE].cur--;-					} else {-						/* Small UFOs can only be stored in Small UFO Hangar */-						if (base->capacities[CAP_UFOHANGARS_SMALL].cur > 0)-							base->capacities[CAP_UFOHANGARS_SMALL].cur--;-					}-				}-				if (prod->amount <= 0) {-					Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("The disassembling of %s has finished."), _(od->name));-					MSO_CheckAddNewMessage(NT_PRODUCTION_FINISHED, _("Production finished"), cp_messageBuffer, qfalse, MSG_PRODUCTION, od->tech);-					PR_QueueNext(base);-				}++				Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("The disassembling of %s has finished."), _(UFO_TypeToName(ufo->ufoTemplate->ufotype)));+				MSO_CheckAddNewMessage(NT_PRODUCTION_FINISHED, _("Production finished"), cp_messageBuffer, qfalse, MSG_PRODUCTION, ufo->ufoTemplate->tech);++				US_RemoveStoredUFO(ufo); 			} 		} 	}@@ -517,6 +501,23 @@ }  /**+ * @brief Returns the base pointer the production belongs to+ * @param[in] production pointer to the production entry+ * @returns base_t pointer to the base+ */+base_t *PR_ProductionBase (production_t *production) {+	int i;+	for (i = 0; i < ccs.numBases; i++) {+		base_t *base = B_GetBaseByIDX(i);+		const ptrdiff_t diff = ((ptrdiff_t)((production) - ccs.productions[i].items));++		if (diff >= 0 && diff < MAX_PRODUCTIONS)+			return base;+	}+	return NULL;+}++/**  * @brief Save callback for savegames in XML Format  * @sa PR_LoadXML  * @sa SAV_GameSaveXML@@ -528,21 +529,25 @@ 	for (i = 0; i < MAX_BASES; i++) { 		const production_queue_t *pq = &ccs.productions[i]; 		int j;-		mxml_node_t * snode = mxml_AddNode(node, "queue");+		mxml_node_t *snode = mxml_AddNode(node, "queue");+ 		mxml_AddInt(snode, "numitems", pq->numItems);+ 		for (j = 0; j < pq->numItems; j++) {-			/** @todo This will crash */ 			const objDef_t *item = pq->items[j].item; 			const aircraft_t *aircraft = pq->items[j].aircraft;+			const storedUFO_t *ufo = pq->items[j].ufo;+ 			mxml_node_t * ssnode = mxml_AddNode(snode, "item");-			assert(item || aircraft);+			assert(item || aircraft || ufo); 			if (item) 				mxml_AddString(ssnode, "itemid", item->id);+			else if (aircraft)+				mxml_AddString(ssnode, "aircraftid", aircraft->id);+			else if (ufo)+				mxml_AddInt(ssnode, "ufoidx", ufo->idx); 			mxml_AddInt(ssnode, "amount", pq->items[j].amount); 			mxml_AddFloat(ssnode, "percentdone", pq->items[j].percentDone);-			mxml_AddBool(ssnode, "prod", pq->items[j].production);-			if (aircraft)-				mxml_AddString(ssnode, "aircraftid", aircraft->id); 			mxml_AddBool(ssnode, "items_cached", pq->items[j].itemsCached); 		} 	}@@ -566,23 +571,84 @@ 		int j; 		mxml_node_t *ssnode; 		production_queue_t *pq = &ccs.productions[i];+ 		pq->numItems = mxml_GetInt(snode, "numitems", 0);++		if (pq->numItems > MAX_PRODUCTIONS) {+			Com_Printf("PR_Load: Too much productions (%i), last %i dropped (baseidx=%i).\n", pq->numItems, pq->numItems - MAX_PRODUCTIONS, i);+			pq->numItems = MAX_PRODUCTIONS;+		}+ 		for (j = 0, ssnode = mxml_GetNode(snode, "item"); j < pq->numItems && ssnode; 				j++, ssnode = mxml_GetNextNode(ssnode, snode, "item")) { 			const char *s1 = mxml_GetString(ssnode, "itemid"); 			const char *s2;+			int ufoIDX;++			pq->items[j].idx = j;+			pq->items[j].amount = mxml_GetInt(ssnode, "amount", 0);+			pq->items[j].percentDone = mxml_GetFloat(ssnode, "percentdone", 0.0);+ 			if (s1 && s1[0] != '\0') 				pq->items[j].item = INVSH_GetItemByID(s1);-			pq->items[j].idx = j;-			pq->items[j].amount = mxml_GetInt(ssnode, "amount", 0);++			/* This if block is for keeping compatibility with base-stored ufos */+			/* @todo remove this on release (or after time) */+			if (!mxml_GetBool(ssnode, "prod", qtrue)) {+				const int amount = pq->items[j].amount;+				int k;+				aircraft_t *ufoTemplate = (pq->items[j].item) ? AIR_GetAircraft(pq->items[j].item->id) : NULL;++				for (k = 0; k < amount; k++) {+					installation_t *installation = INS_GetFirstUFOYard(qtrue);+					storedUFO_t *ufo = NULL;++					if (ufoTemplate && installation)+						ufo = US_StoreUFO(ufoTemplate, installation, ccs.date);++					if (ufo) {+						pq->items[j + k].idx = j + k;+						pq->items[j + k].item = NULL;+						pq->items[j + k].ufo = ufo;+						pq->items[j + k].amount = 1;+						pq->items[j + k].percentDone = (k) ? 0.0 : pq->items[j + k].percentDone;+						pq->items[j + k].production = qfalse;+						ufo->disassembly = &(pq->items[j + k]);+					} else {+						Com_Printf("PR_Load: Could not add ufo to the UFO Yards, disassembly dropped (baseidx=%i, production idx=%i).\n", i, j);+						j--;+						pq->numItems--;+						continue;+					}+				}+				j += k - 1;+				pq->numItems += k - 1;+				continue;+			}+ 			if (pq->items[j].amount <= 0) { 				Com_Printf("PR_Load: Production with amount <= 0 dropped (baseidx=%i, production idx=%i).\n", i, j); 				j--; 				pq->numItems--; 				continue; 			}-			pq->items[j].percentDone = mxml_GetFloat(ssnode, "percentdone", 0.0);-			pq->items[j].production = mxml_GetBool(ssnode, "prod", qfalse);++			ufoIDX = mxml_GetInt(ssnode, "ufoidx", MAX_STOREDUFOS);+			if (ufoIDX != MAX_STOREDUFOS) {+				storedUFO_t *ufo = US_GetStoredUFOByIDX(ufoIDX);++				if (!ufo) {+					Com_Printf("PR_Load: Could not find ufo idx: %i\n", ufoIDX);+					return qfalse;+				}++				pq->items[j].ufo = ufo;+				pq->items[j].production = qfalse;+				pq->items[j].ufo->disassembly = &(pq->items[j]);+			} else {+				pq->items[j].production = qtrue;+			}+ 			s2 = mxml_GetString(ssnode, "aircraftid"); 			if (s2 && s2[0] != '\0') 				pq->items[j].aircraft = AIR_GetAircraft(s2);Index: src/client/campaign/cp_save.h===================================================================--- src/client/campaign/cp_save.h	(revision 25739)+++ src/client/campaign/cp_save.h	(working copy)@@ -4,7 +4,7 @@  */  /*-Copyright (C) 2002-2007 UFO: Alien Invasion team.+Copyright (C) 2002-2009 UFO: Alien Invasion team.  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -87,6 +87,8 @@ qboolean INS_LoadXML(mxml_node_t *parent); qboolean MSO_SaveXML(mxml_node_t *parent); qboolean MSO_LoadXML(mxml_node_t *parent);+qboolean US_SaveXML(mxml_node_t *parent);+qboolean US_LoadXML(mxml_node_t *parent);  void B_PostLoadInit(void); Index: src/client/campaign/cp_missions.c===================================================================--- src/client/campaign/cp_missions.c	(revision 25739)+++ src/client/campaign/cp_missions.c	(working copy)@@ -342,7 +342,7 @@ 			shortUFOType = Com_UFOTypeToShortName(ccs.selectedMission->ufo->ufotype); 		} -		Com_sprintf(mission->onwin, sizeof(mission->onwin), "cp_uforecovery_init %i;", mission->ufo->ufotype);+		Com_sprintf(mission->onwin, sizeof(mission->onwin), "cp_uforecovery_init %s", mission->ufo->id); 		/* Set random map UFO if this is a random map */ 		if (mission->mapDef->map[0] == '+') { 			/* set rm_ufo to the ufo type used */Index: src/client/campaign/cp_produce_callbacks.h===================================================================--- src/client/campaign/cp_produce_callbacks.h	(revision 25739)+++ src/client/campaign/cp_produce_callbacks.h	(working copy)@@ -4,7 +4,7 @@  */  /*- Copyright (C) 1997-2008 UFO:AI Team+ Copyright (C) 1997-2009 UFO:AI Team   This program is free software; you can redistribute it and/or  modify it under the terms of the GNU General Public License@@ -23,10 +23,10 @@   */ -#ifndef CLIENT_CL_PRODUCE_CALLBACKS_H-#define CLIENT_CL_PRODUCE_CALLBACKS_H+#ifndef CLIENT_CP_PRODUCE_CALLBACKS_H+#define CLIENT_CP_PRODUCE_CALLBACKS_H  void PR_InitCallbacks (void); void PR_ShutdownCallbacks (void); -#endif /* CLIENT_CL_PRODUCE_CALLBACKS_H */+#endif /* CLIENT_CP_PRODUCE_CALLBACKS_H */Index: src/client/campaign/cp_produce.h===================================================================--- src/client/campaign/cp_produce.h	(revision 25739)+++ src/client/campaign/cp_produce.h	(working copy)@@ -4,7 +4,7 @@  */  /*-Copyright (C) 2002-2007 UFO: Alien Invasion team.+Copyright (C) 2002-2009 UFO: Alien Invasion team.  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -23,8 +23,8 @@  */ -#ifndef CLIENT_CL_PROOUCE-#define CLIENT_CL_PROOUCE+#ifndef CLIENT_CP_PRODUCE+#define CLIENT_CP_PRODUCE  #include "../cl_inventory.h" @@ -50,7 +50,8 @@ 	 * @todo Make this a union 	 */ 	objDef_t *item;			/**< Item to be produced. */-	struct aircraft_s *aircraft;	/**< Aircraft (sample) to be produced. @todo Is there any way to make this const without cl_produce.c to break?*/+	struct aircraft_s *aircraft;	/**< Aircraft (sample) to be produced. @todo Is there any way to make this const without cp_produce.c to break?*/+	struct storedUFO_s *ufo;  	signed int amount;	/**< How much are we producing. */ 	float percentDone;		/**< Fraction of the item which is already produced.@@ -80,6 +81,7 @@ void PR_QueueMove (production_queue_t *queue, int index, int dir); void PR_QueueDelete (base_t *base, production_queue_t *queue, int index); void PR_UpdateRequiredItemsInBasestorage (base_t *base, int amount, requirements_t *reqs);-float PR_CalculateProductionPercentDone (const base_t *base, const technology_t *tech, const components_t *comp, qboolean disassembly);+float PR_CalculateProductionPercentDone (const base_t *base, const technology_t *tech, const components_t *comp);+base_t *PR_ProductionBase(production_t *production); -#endif /* CLIENT_CL_PROOUCE */+#endif /* CLIENT_CP_PRODUCE */Index: src/client/campaign/cp_uforecovery_callbacks.c===================================================================--- src/client/campaign/cp_uforecovery_callbacks.c	(revision 25739)+++ src/client/campaign/cp_uforecovery_callbacks.c	(working copy)@@ -1,11 +1,12 @@ /**  * @file cp_uforecovery_callbacks.c- * @brief UFO recovery callback functions- * @note Functions with UR_*+ * @brief UFO recovery and storing callback functions+ * @note UFO recovery functions with UR_*+ * @note UFO storing functions with US_*  */  /*-Copyright (C) 2002-2007 UFO: Alien Invasion team.+Copyright (C) 2002-2009 UFO: Alien Invasion team.  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -33,24 +34,18 @@ #include "../menu/m_main.h" #include "../menu/node/m_node_text.h" -typedef struct ufoRecoveryStorage_s {-	base_t *base; /**< base where actual ufo is located */-	aircraft_t *ufoTemplate; /**< template of ufo for storage id and name */-	objDef_t *ufoItem; /**< object definition of aircraft, used for capacity check */-} ufoRecoveryStorage_t;+#ifdef DEBUG+#include "cp_time.h"+#endif  /** @sa ufoRecoveries_t */ typedef struct ufoRecovery_s {-	base_t *base;			/**< selected base for current selected ufo recovery */-	ufoType_t ufoType;		/**< the ufo type of the current ufo recovery */+	installation_t *installation;			/**< selected ufoyard for current selected ufo recovery */+	aircraft_t *ufoTemplate;		/**< the ufo type of the current ufo recovery */ 	nation_t *nation;		/**< selected nation to sell to for current ufo recovery */ 	qboolean recoveryDone;	/**< recoveryDone? Then the buttons are disabled */-	base_t *UFObases[MAX_BASES];	/**< Array of base indexes where we can store UFO. */-	int baseHasUFOHangarCount;		/**< number of entries in the UFObases array */+ 	int UFOprices[MAX_NATIONS];		/**< Array of prices proposed by nation. */-	ufoRecoveryStorage_t replaceUFOs[MAX_BASES * UFO_MAX]; /**< Array of replaceable ufos in all bases */-	ufoRecoveryStorage_t *selectedStorage; /**< selected storage for replace */-	int replaceCount; 		/**< number of replaceable ufos in all bases */ } ufoRecovery_t;  static ufoRecovery_t ufoRecovery;@@ -64,365 +59,132 @@ }  /**- * @brief Checks conditions for storing given ufo in given base.- * @param[in] base Pointer to the base, where we are going to store UFO.- * @param[in] ufocraft Pointer to ufocraft which we are going to store.- * @return qtrue if given base can store given ufo.- */-static int UR_GetBaseCapacityForUfotype (const base_t *base, const aircraft_t *ufocraft)-{-	assert(base && ufocraft);--	if (ufocraft->size == AIRCRAFT_LARGE) {-		/* Large UFOs can only fit large UFO hangars */-		if (!B_GetBuildingStatus(base, B_UFO_HANGAR))-			return -1;--		/* Check there is still enough room for this UFO */-		return base->capacities[CAP_UFOHANGARS_LARGE].max;-	} else {-		/* This is a small UFO, can only fit in small hangar */--		/* There's no small hangar functional */-		if (!B_GetBuildingStatus(base, B_UFO_SMALL_HANGAR))-			return -1;--		/* Check there is still enough room for this UFO */-		return base->capacities[CAP_UFOHANGARS_SMALL].max;-	}-}---/**- * @brief Select appropriate the base or replace option from list- * @param num selected index must be within valid range- */-static void UR_DialogSelectStorageBase (const int num)-{-	qboolean useBase = qtrue;-	if (num < ufoRecovery.baseHasUFOHangarCount) {-		if (!ufoRecovery.UFObases[num])-			return;-	} else {-		useBase = qfalse;-		if (!ufoRecovery.replaceUFOs[num - ufoRecovery.baseHasUFOHangarCount].base)-			return;-	}--	if (useBase) {-		ufoRecovery.base = ufoRecovery.UFObases[num];-		Com_DPrintf(DEBUG_CLIENT, "CP_UFORecoveryBaseSelectPopup_f: picked base: %s\n", ufoRecovery.base->name);-		MN_ExecuteConfunc("btbasesel_enable");-	} else {-		ufoRecovery.selectedStorage = &ufoRecovery.replaceUFOs[num - ufoRecovery.baseHasUFOHangarCount];-		Com_DPrintf(DEBUG_CLIENT, "CP_UFORecoveryBaseSelectPopup_f: picked replace ufo %s from base %s\n",-				ufoRecovery.selectedStorage->ufoTemplate->id, ufoRecovery.selectedStorage->base->name);-		ufoRecovery.base = ufoRecovery.selectedStorage->base;-		MN_ExecuteConfunc("btbaserepl_enable");-	}-	Cvar_Set("mission_recoverybase", ufoRecovery.base->name);-}--/**  * @brief Function to trigger UFO Recovered event.  * @note This function prepares related cvars for the recovery dialog.  * @note Command to call this: cp_uforecovery_init.  */ static void UR_DialogInit_f (void) {-	int i;-	ufoType_t UFOtype;-	aircraft_t *ufocraft;-	qboolean ufofound = qfalse;+	char ufoID[MAX_VAR];+	aircraft_t *ufoCraft = NULL;  	if (Cmd_Argc() < 2) {-		Com_Printf("Usage: %s <UFOType>\n", Cmd_Argv(0));+		Com_Printf("Usage: %s <ufoID>\n", Cmd_Argv(0)); 		return; 	} -	if ((atoi(Cmd_Argv(1)) >= 0) && (atoi(Cmd_Argv(1)) < UFO_MAX)) {-		UFOtype = atoi(Cmd_Argv(1));-	} else {-		Com_Printf("CP_UFORecovered: UFOType: %i does not exist!\n", atoi(Cmd_Argv(1)));-		return;-	}--	ufocraft = NULL;-	/* Find ufo sample of given ufotype. */-	for (i = 0; i < ccs.numAircraftTemplates; i++) {-		ufocraft = &ccs.aircraftTemplates[i];-		if (ufocraft->type != AIRCRAFT_UFO)-			continue;-		if (ufocraft->ufotype == UFOtype) {-			ufofound = qtrue;-			break;-		}-	}+	Q_strncpyz(ufoID, Cmd_Argv(1), sizeof(ufoID));+	ufoCraft = AIR_GetAircraft(ufoID); 	/* Do nothing without UFO of this type. */-	if (!ufofound) {-		Com_Printf("CP_UFORecovered: UFOType: %i does not have valid craft definition!\n", atoi(Cmd_Argv(1)));+	if (!ufoCraft) {+		Com_Printf("CP_UFORecovered: UFO Type: %s is not a valid craft definition!\n", ufoID); 		return; 	}  	/* Put relevant info into missionResults array. */ 	ccs.missionResults.recovery = qtrue; 	ccs.missionResults.crashsite = qfalse;-	ccs.missionResults.ufotype = ufocraft->ufotype;+	ccs.missionResults.ufotype = ufoCraft->ufotype;  	/* Prepare related cvars. */ 	Cvar_SetValue("mission_uforecovered", 1);	/* This is used in menus to enable UFO Recovery nodes. */ 	memset(&ufoRecovery, 0, sizeof(ufoRecovery));-	ufoRecovery.ufoType = UFOtype;+	ufoRecovery.ufoTemplate = ufoCraft; +	Cvar_Set("mn_uforecovery_actualufo", UFO_MissionResultToString()); }  /**- * @brief Function calculates prices for actual ufo to sell and initializes menu text.- * @param ufocraft the ufo type to sell- * @note called by UR_DialogInitSell_f and UR_DialogStartReplace- */-static void UR_DialogInitSell (aircraft_t *ufocraft)-{-	int i, nations = 0;-	static char recoveryNationSelectPopup[MAX_SMALLMENUTEXTLEN];--	recoveryNationSelectPopup[0] = '\0';--	for (i = 0; i < ccs.numNations; i++) {-		const nation_t *nation = &ccs.nations[i];-		nations++;-		/* Calculate price offered by nation only if this is first popup opening. */-		if (!ufoRecovery.nation) {-			ufoRecovery.UFOprices[i] = (int) (ufocraft->price * (.85f + frand() * .3f));-			/* Nation will pay less if corrupted */-			ufoRecovery.UFOprices[i] = (int) (ufoRecovery.UFOprices[i] * exp(-nation->stats[0].xviInfection / 20.0f));-		}-		Com_sprintf(recoveryNationSelectPopup + strlen(recoveryNationSelectPopup), sizeof(recoveryNationSelectPopup),-				"%s\t\t\t%i\t\t%s\n", _(nation->name), ufoRecovery.UFOprices[i], NAT_GetHappinessString(nation));-	}--	/* Do nothing without at least one nation. */-	if (nations == 0)-		return;--	MN_RegisterText(TEXT_UFORECOVERY_NATIONS, recoveryNationSelectPopup);-}--/**  * @brief Function to initialize list of storage locations for recovered UFO.  * @note Command to call this: cp_uforecovery_store_init.  * @sa UR_ConditionsForStoring  */ static void UR_DialogInitStore_f (void) {-	int i,j, replaceTemplates = 0;-	aircraft_t *ufocraft;-	linkedList_t *recoveryBaseSelectPopup = NULL;-	qboolean ufofound = qfalse;-	linkedList_t *validReplaceTemplateList = NULL, *replaceObjDefList = NULL;+	int i;+	int count = 0;+	linkedList_t *recoveryYardSelectPopup = NULL; -	/* Do nothing without any base. */-	if (!ccs.numBases)-		return;- 	/* Do nothing if recovery process is finished. */ 	if (ufoRecovery.recoveryDone) 		return; -	/* Find ufo sample of given ufotype. */-	for (i = 0; i < ccs.numAircraftTemplates; i++) {-		ufocraft = &ccs.aircraftTemplates[i];-		if (ufocraft->type != AIRCRAFT_UFO)-			continue;-		if (ufocraft->ufotype == ufoRecovery.ufoType) {-			ufofound = qtrue;-			break;-		}-	}+	/* Check how many bases can store this UFO. */+	for (i = 0; i < ccs.numInstallations; i++) {+		installation_t *installation = INS_GetFoundedInstallationByIDX(i); -	/* Do nothing without UFO of this type. */-	if (!ufofound) {-		Com_Printf("CP_UFORecoveredStore_f: UFOType: %i does not have valid craft definition!\n", ufoRecovery.ufoType);-		return;-	}--	/* find valid replace templates */-	for (i = 0; i < ccs.numAircraftTemplates; i++) {-		aircraft_t *tempcraft = &ccs.aircraftTemplates[i];-		if (tempcraft == ufocraft)+		if (!installation) 			continue;-		if (tempcraft->type != AIRCRAFT_UFO)-			continue;-		if (tempcraft->size == ufocraft->size) {-			LIST_AddPointer(&validReplaceTemplateList,tempcraft);-			LIST_AddPointer(&replaceObjDefList,INVSH_GetItemByID(tempcraft->id));-			replaceTemplates++;-		}-	} -	/* Clear UFObases. */-	memset(ufoRecovery.UFObases, 0, sizeof(ufoRecovery.UFObases));--	/* Check how many bases can store this UFO. */-	for (i = 0; i < MAX_BASES; i++) {-		base_t *base = B_GetFoundedBaseByIDX(i);-		if (!base)-			continue;-		if (UR_ConditionsForStoring(base, ufocraft)) {-			LIST_AddString(&recoveryBaseSelectPopup, base->name);-			ufoRecovery.UFObases[ufoRecovery.baseHasUFOHangarCount++] = base;-		} else {-			int baseCapacity = UR_GetBaseCapacityForUfotype(base, ufocraft);-			if (baseCapacity > 0)-			{-				linkedList_t *curTemplate = validReplaceTemplateList;-				linkedList_t *curObjID = replaceObjDefList;-				for (j = 0; j < replaceTemplates; j++) {-					objDef_t *obj = (objDef_t*)curObjID->data;-					aircraft_t *craft = (aircraft_t *)curTemplate->data;-					if (base->storage.num[obj->idx] > 0) {-						/* found a valid replace, add it */-						ufoRecoveryStorage_t *store = &ufoRecovery.replaceUFOs[ufoRecovery.replaceCount];-						store->base = base;-						store->ufoItem = obj;-						store->ufoTemplate = craft;-						ufoRecovery.replaceCount++;-					}-					curObjID = curObjID->next;-					curTemplate = curTemplate->next;-				}-			}+		if (installation->ufoCapacity.max > 0+		 && installation->ufoCapacity.max > installation->ufoCapacity.cur) {+			LIST_AddString(&recoveryYardSelectPopup, installation->name);+			count++; 		} 	} -	for (i = 0; i < ufoRecovery.replaceCount; i++)-	{-		ufoRecoveryStorage_t store = ufoRecovery.replaceUFOs[i];-		LIST_AddString(&recoveryBaseSelectPopup, va(_("Replace %s at %s (%i stored available)\n"), _(store.ufoItem->name), store.base->name, store.base->storage.num[store.ufoItem->idx] ));-	}--	/* If only one base with UFO hangars, the recovery will be done in this base. */-	switch (ufoRecovery.baseHasUFOHangarCount + ufoRecovery.replaceCount) {-	case 0:+	Cvar_Set("mn_uforecovery_actualufo", UFO_MissionResultToString());+	if (count == 0) { 		/* No UFO base with proper conditions, show a hint and disable list. */-		LIST_AddString(&recoveryBaseSelectPopup, _("No ufo hangar or ufo yard available."));-		MN_ExecuteConfunc("cp_basesel_disable");-		break;-	case 1:-		/* there should only be one entry in UFObases - so use that one. */-		if (ufoRecovery.baseHasUFOHangarCount)-			ufoRecovery.base = ufoRecovery.UFObases[0];-		else-			ufoRecovery.selectedStorage = &ufoRecovery.replaceUFOs[0];-		UR_DialogSelectStorageBase(0);-		/** @todo some better way to do so without the need of a confunc? */-		MN_ExecuteConfunc("cp_basesel_select");-		break;-	default:-		/** @todo check whether this is needed */-		if (!ufoRecovery.base)-			ufoRecovery.base = ufoRecovery.UFObases[0];-		if (ufoRecovery.base)-			Cvar_Set("mission_recoverybase", ufoRecovery.base->name);-		break;+		LIST_AddString(&recoveryYardSelectPopup, _("No free ufo yard available."));+		MN_ExecuteConfunc("uforecovery_tabselect sell");+		MN_ExecuteConfunc("btbasesel disable");+	} else {+		MN_ExecuteConfunc("cp_basesel_select 0");+		MN_ExecuteConfunc("btbasesel enable"); 	}-	Cvar_Set("mn_uforecovery_actualufo",UFO_MissionResultToString());-	MN_RegisterLinkedListText(TEXT_UFORECOVERY_BASESTORAGE, recoveryBaseSelectPopup);+	MN_RegisterLinkedListText(TEXT_UFORECOVERY_BASESTORAGE, recoveryYardSelectPopup); }  /**- * @brief Finds the destination base for UFO recovery.- * @note The base selection is being done here.- * @note Callback command: cp_uforecovery_baselist_click.+ * @brief Function to start UFO recovery process.+ * @note Command to call this: cp_uforecovery_store_start.  */-static void UR_DialogSelectStorageBase_f (void)+static void UR_DialogStartStore_f (void) {-	int num;+	installation_t *UFOYard = NULL;+	int idx;+	int i;+	int count = 0;+	date_t date;  	if (Cmd_Argc() < 2) {-		Com_Printf("Usage: %s <baseid>\n", Cmd_Argv(0));+		Com_Printf("Usage: %s <installationIDX>\n", Cmd_Argv(0)); 		return; 	} -	num = atoi(Cmd_Argv(1));-	if (num < 0 || num >= ufoRecovery.replaceCount + ufoRecovery.baseHasUFOHangarCount)-		return;+	idx = atoi(Cmd_Argv(1)); -	UR_DialogSelectStorageBase(num);-}+	for (i = 0; i < ccs.numInstallations; i++) {+		installation_t *installation = INS_GetFoundedInstallationByIDX(i); -/**- * @brief Function starts the UFO recovery process.- * @note Called by UR_DialogStartStore_f and UR_DialogStartReplace_f- * @note uforecovery base value must be set- * @param finish flag indicating whether uforecovery should me marked as done- */-static void UR_DialogStartStore (qboolean finish)-{-	base_t *base = ufoRecovery.base;-	Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Recovered %s from the battlefield. UFO is being transported to base %s."), UFO_TypeToName(-			ufoRecovery.ufoType), base->name);-	MS_AddNewMessage(_("UFO Recovery"), cp_messageBuffer, qfalse, MSG_STANDARD, NULL);-	UR_Prepare(base, ufoRecovery.ufoType);+		if (!installation)+			continue; -	if (finish)-		/* UFO recovery process is done, disable buttons. */-		UR_DialogRecoveryDone();-}+		if (installation->ufoCapacity.max <= 0+		 || installation->ufoCapacity.max <= installation->ufoCapacity.cur)+		 	continue;+		+		if (count == idx) {+			UFOYard = installation;+			break;+		}+		count++;+	} -/**- * @brief Function to start UFO recovery process.- * @note Command to call this: cp_uforecovery_store_start.- * @note This needs the ufoRecovery base value set- */-static void UR_DialogStartStore_f (void)-{-	base_t *base = ufoRecovery.base;-	if (!base)+	if (!UFOYard) 		return; -	UR_DialogStartStore(qtrue);-}+	Com_sprintf(cp_messageBuffer, lengthof(cp_messageBuffer), _("Recovered %s from the battlefield. UFO is being transported to %s."), _(UFO_TypeToName(ufoRecovery.ufoTemplate->ufotype)), UFOYard->name);+	MS_AddNewMessage(_("UFO Recovery"), cp_messageBuffer, qfalse, MSG_STANDARD, NULL);+/*	UR_Prepare(UFOYard, ufoRecovery.ufoTemplate->ufotype); */+	date = ccs.date;+	date.day += (int) RECOVERY_DELAY;+/*	date.sec += (RECOVERY_DELAY - (int) RECOVERY_DELAY) % SECONDS_PER_DAY;*/ -/**- * @brief Function to start replace process- * @note Command to call this: cp_uforecovery_replace- * @note This needs the uforecovery selectedStorage value set- */-static void UR_DialogStartReplace_f (void)-{-	linkedList_t *replacetext = NULL;-	ufoRecoveryStorage_t *storage = ufoRecovery.selectedStorage;-	if (!storage)-		return;-	/** @todo can these asserts be merged into one? */-	assert(storage->base);-	assert(storage->ufoItem);-	assert(storage->ufoTemplate);+	US_StoreUFO(ufoRecovery.ufoTemplate, UFOYard, date); -	/* update hangar cap, remove old ufo*/-	storage->base->storage.num[storage->ufoItem->idx]--;-	if (storage->ufoTemplate->size == AIRCRAFT_LARGE) {-		if (storage->base->capacities[CAP_UFOHANGARS_LARGE].cur > 0)-			storage->base->capacities[CAP_UFOHANGARS_LARGE].cur--;-		else-			Com_Printf("UR_DialogStartReplace_f: Large UFO-hangar capacity is not greater than zero for replacing a UFO.");-	} else {-		if (storage->base->capacities[CAP_UFOHANGARS_SMALL].cur > 0)-			storage->base->capacities[CAP_UFOHANGARS_SMALL].cur--;-		else-			Com_Printf("UR_DialogStartReplace_f: Small UFO-hangar capacity is not greater than zero for replacing a UFO.");-	}-	/* reinit sell values for replaced ufo */-	memset(ufoRecovery.UFOprices,-1,sizeof(ufoRecovery.UFOprices));-	ufoRecovery.nation = NULL;-	UR_DialogInitSell(storage->ufoTemplate);-	/* update other menu texts */-	Cvar_Set("mn_uforecovery_actualufo",va(_("previously recovered %s from base %s"),UFO_TypeToName(storage->ufoTemplate->type), storage->base->name));-	LIST_AddString(&replacetext, va(_("Storage process for captured %s ongoing."), UFO_TypeToName(ufoRecovery.ufoType)));-	MN_RegisterLinkedListText(TEXT_UFORECOVERY_BASESTORAGE, replacetext);-	/* start storage process for recovered ufo */-	UR_DialogStartStore(qfalse);+	UR_DialogRecoveryDone(); }  /**@@ -432,27 +194,39 @@ static void UR_DialogInitSell_f (void) { 	int i;-	aircraft_t *ufocraft;+	int nations = 0;+	static char recoveryNationSelectPopup[MAX_SMALLMENUTEXTLEN];  	/* Do nothing if recovery process is finished. */ 	if (ufoRecovery.recoveryDone) 		return; -	ufocraft = NULL;-	/* Find ufo sample of given ufotype. */-	for (i = 0; i < ccs.numAircraftTemplates; i++) {-		ufocraft = &ccs.aircraftTemplates[i];-		if (ufocraft->type != AIRCRAFT_UFO)-			continue;-		if (ufocraft->ufotype == ufoRecovery.ufoType)-			break;-	}-	if (!ufocraft)+	if (!ufoRecovery.ufoTemplate) 		return;  	memset(ufoRecovery.UFOprices, -1, sizeof(ufoRecovery.UFOprices)); -	UR_DialogInitSell(ufocraft);+	recoveryNationSelectPopup[0] = '\0';++	for (i = 0; i < ccs.numNations; i++) {+		const nation_t *nation = &ccs.nations[i];+		nations++;+		/* Calculate price offered by nation only if this is first popup opening. */+		if (!ufoRecovery.nation) {+			ufoRecovery.UFOprices[i] = (int) (ufoRecovery.ufoTemplate->price * (.85f + frand() * .3f));+			/* Nation will pay less if corrupted */+			ufoRecovery.UFOprices[i] = (int) (ufoRecovery.UFOprices[i] * exp(-nation->stats[0].xviInfection / 20.0f));+		}+		Com_sprintf(recoveryNationSelectPopup + strlen(recoveryNationSelectPopup), lengthof(recoveryNationSelectPopup),+				"%s\t\t\t%i\t\t%s\n", _(nation->name), ufoRecovery.UFOprices[i], NAT_GetHappinessString(nation));+	}++	/* Do nothing without at least one nation. */+	if (nations == 0)+		return;++	MN_RegisterText(TEXT_UFORECOVERY_NATIONS, recoveryNationSelectPopup);+	MN_ExecuteConfunc("btnatsel disable"); }  /**@@ -481,7 +255,7 @@ 	Com_DPrintf(DEBUG_CLIENT, "CP_UFORecoveryNationSelectPopup_f: picked nation: %s\n", nation->name);  	Cvar_Set("mission_recoverynation", _(nation->name));-	MN_ExecuteConfunc("btnatsel_enable");+	MN_ExecuteConfunc("btnatsel enable"); }  /**@@ -502,13 +276,15 @@ 		Com_Printf("CP_UFOSellStart_f: Error: ufo price of -1 - nation: '%s'\n", nation->id); 		return; 	}+#if 0 	if (ufoRecovery.selectedStorage) { 		Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Sold previously recovered %s from %s to nation %s, gained %i credits."), UFO_TypeToName( 				ufoRecovery.selectedStorage->ufoTemplate->ufotype), ufoRecovery.selectedStorage->base->name, _(nation->name), ufoRecovery.UFOprices[nation->idx]); 	} else {+#endif 		Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Recovered %s from the battlefield. UFO sold to nation %s, gained %i credits."), UFO_TypeToName(-				ufoRecovery.ufoType), _(nation->name), ufoRecovery.UFOprices[nation->idx]);-	}+				ufoRecovery.ufoTemplate->ufotype), _(nation->name), ufoRecovery.UFOprices[nation->idx]);+/*	}*/ 	MS_AddNewMessage(_("UFO Recovery"), cp_messageBuffer, qfalse, MSG_STANDARD, NULL); 	CL_UpdateCredits(ccs.credits + ufoRecovery.UFOprices[nation->idx]); @@ -526,16 +302,121 @@ 	UR_DialogRecoveryDone(); } ++/* === UFO Storing === */++#ifdef DEBUG+/**+ * @brief Debug callback to list ufostores+ */+static void US_ListStoredUFOs_f (void)+{+	int i;++	Com_Printf("Number of stored UFOs: %i\n", ccs.numStoredUFOs);+	for (i = 0; i < ccs.numStoredUFOs; i++) {+		const storedUFO_t *ufo = US_GetStoredUFOByIDX(i);+		const base_t *prodBase = PR_ProductionBase(ufo->disassembly);+		dateLong_t date;++		Com_Printf("IDX: %i\n", ufo->idx);+		Com_Printf("id: %s\n", ufo->id);+		Com_Printf("stored at %s\n", (ufo->installation) ? ufo->installation->name : "NOWHERE");++		CL_DateConvertLong(&(ufo->arrive), &date);+		Com_Printf("arrived at: %i %s %02i, %02i:%02i\n", date.year,+		Date_GetMonthName(date.month - 1), date.day, date.hour, date.min);++		if (ufo->ufoTemplate->tech->base)+			Com_Printf("tech being researched at %s\n", ufo->ufoTemplate->tech->base->name);+		if (prodBase)+			Com_Printf("being disassembled at %s\n", prodBase->name);+	}+}++/**+ * @brief Adds an UFO to the stores+ */+static void US_StoreUFO_f (void)+{+	char ufoId[MAX_VAR];+	int installationIDX;+	installation_t *installation;+	aircraft_t *ufoType = NULL;+	int i;++	if (Cmd_Argc() <= 2) {+		Com_Printf("Usage: %s <ufoType> <installationIdx>\n", Cmd_Argv(0));+		return;+	}++	Q_strncpyz(ufoId, Cmd_Argv(1), sizeof(ufoId));+	installationIDX = atoi(Cmd_Argv(2));++	/* Get The UFO Yard */+	if (installationIDX < 0 || installationIDX >= MAX_INSTALLATIONS) {+		Com_Printf("US_StoreUFO_f: Invalid Installation index.\n");+		return;+	}		+	installation = INS_GetFoundedInstallationByIDX(installationIDX);+	if (!installation) {+		Com_Printf("US_StoreUFO_f: There is no Installation: idx=%i.\n", installationIDX);+		return;+	}++	/** Get UFO Type */+	for (i = 0; i < ccs.numAircraftTemplates; i++) {+		if (strstr(ccs.aircraftTemplates[i].id, ufoId)) {+			ufoType = &ccs.aircraftTemplates[i];+			break;+		}+	}+	if (ufoType == NULL) {+		Com_Printf("US_StoreUFO_f: In valid UFO Id.\n");+		return;+	}++	US_StoreUFO(ufoType, installation, ccs.date);+}++/**+ * @brief Removes an UFO from the stores+ */+static void US_RemoveStoredUFO_f (void)+{+	int idx;++	if (Cmd_Argc() < 2) {+		Com_Printf("Usage: %s <idx>\n", Cmd_Argv(0));+		return;+	}+	idx = atoi(Cmd_Argv(1));++	if (idx < 0 || idx >= ccs.numStoredUFOs) {+		Com_Printf("US_RemoveStoredUFO_f: No such ufo index.\n");+		return;+	}++	US_RemoveStoredUFO(US_GetStoredUFOByIDX(idx));+}+++#endif+ void UR_InitCallbacks (void) { 	Cmd_AddCommand("cp_uforecovery_init", UR_DialogInit_f, "Function to trigger UFO Recovered event"); 	Cmd_AddCommand("cp_uforecovery_sell_init", UR_DialogInitSell_f, "Function to initialize sell recovered UFO to desired nation."); 	Cmd_AddCommand("cp_uforecovery_store_init", UR_DialogInitStore_f, "Function to initialize store recovered UFO in desired base."); 	Cmd_AddCommand("cp_uforecovery_nationlist_click", UR_DialogSelectSellNation_f, "Callback for recovery sell to nation list.");-	Cmd_AddCommand("cp_uforecovery_baselist_click", UR_DialogSelectStorageBase_f, "Callback for recovery store in base list."); 	Cmd_AddCommand("cp_uforecovery_store_start", UR_DialogStartStore_f, "Function to start UFO recovery processing."); 	Cmd_AddCommand("cp_uforecovery_sell_start", UR_DialogStartSell_f, "Function to start UFO selling processing.");-	Cmd_AddCommand("cp_uforecovery_replace", UR_DialogStartReplace_f, "Function to start replace UFO in base processing. Sell is needed for replaced UFO.");++#ifdef DEBUG+	Cmd_AddCommand("debug_liststoredufos", US_ListStoredUFOs_f, "Debug function to list UFOs in Hangars.");+	Cmd_AddCommand("debug_storeufo", US_StoreUFO_f, "Debug function to Add UFO to Hangars.");+	Cmd_AddCommand("debug_removestoredufo", US_RemoveStoredUFO_f, "Debug function to Remove UFO from Hangars.");+#endif }  void UR_ShutdownCallbacks (void)@@ -544,8 +425,11 @@ 	Cmd_RemoveCommand("cp_uforecovery_sell_init"); 	Cmd_RemoveCommand("cp_uforecovery_store_init"); 	Cmd_RemoveCommand("cp_uforecovery_nationlist_click");-	Cmd_RemoveCommand("cp_uforecovery_baselist_click"); 	Cmd_RemoveCommand("cp_uforecovery_store_start"); 	Cmd_RemoveCommand("cp_uforecovery_sell_start");-	Cmd_RemoveCommand("cp_uforecovery_replace");+#ifdef DEBUG+	Cmd_RemoveCommand("debug_liststoredufos");+	Cmd_RemoveCommand("debug_storeufo");+	Cmd_RemoveCommand("debug_removestoredufo");+#endif }Index: src/client/campaign/cp_uforecovery_callbacks.h===================================================================--- src/client/campaign/cp_uforecovery_callbacks.h	(revision 25739)+++ src/client/campaign/cp_uforecovery_callbacks.h	(working copy)@@ -1,10 +1,10 @@ /**  * @file cp_uforecovery_callbacks.h- * @brief UFO recovery callback header file+ * @brief UFO recovery and storing callback header file  */  /*-Copyright (C) 2002-2007 UFO: Alien Invasion team.+Copyright (C) 2002-2009 UFO: Alien Invasion team.  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -23,8 +23,8 @@  */ -#ifndef CLIENT_CL_UFORECOVERY_CALLBACKS_H-#define CLIENT_CL_UFORECOVERY_CALLBACKS_H+#ifndef CLIENT_CP_UFORECOVERY_CALLBACKS_H+#define CLIENT_CP_UFORECOVERY_CALLBACKS_H  void UR_InitCallbacks(void); void UR_ShutdownCallbacks(void);Index: src/client/campaign/cp_alienbase.c===================================================================--- src/client/campaign/cp_alienbase.c	(revision 25739)+++ src/client/campaign/cp_alienbase.c	(working copy)@@ -80,6 +80,8 @@  		counter++; 	}+	if (counter == maxLoopPosition)+		Vector2Copy(randomPos, pos); }  /**Index: src/client/campaign/cp_research.c===================================================================--- src/client/campaign/cp_research.c	(revision 25739)+++ src/client/campaign/cp_research.c	(working copy)@@ -10,7 +10,7 @@  */  /*-Copyright (C) 2002-2007 UFO: Alien Invasion team.+Copyright (C) 2002-2009 UFO: Alien Invasion team.  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License@@ -88,6 +88,18 @@ }  /**+ * @brief Stops a research (Removes scientists from it)+ * @param[in] tech The technology that is being researched.+ */+void RS_StopResearch (technology_t* tech)+{+	assert(tech);+	while (tech->scientists > 0)+		RS_RemoveScientist(tech, NULL);+	assert(tech->statusResearch == RS_PAUSED);+}++/**  * @brief Marks one tech as researchable.  * @param tech The technology to be marked.  * @sa RS_MarkCollected@@ -197,6 +209,11 @@ 				if (AL_CountAll() < req->amount) 					met_AND = qfalse; 				break;+			case RS_LINK_UFO:+				assert(req->link);+				if (US_UFOsInStorage(req->link, NULL) < req->amount)+					met_AND = qfalse;+				break; 			default: 				break; 			}@@ -243,6 +260,11 @@ 				if (AL_CountAll() >= req->amount) 					met_OR = qtrue; 				break;+			case RS_LINK_UFO:+				assert(req->link);+				if (US_UFOsInStorage(req->link, NULL) >= req->amount)+					met_OR = qtrue;+				break; 			default: 				break; 			}@@ -410,6 +432,9 @@ 			if (!req->link) 				Com_Error(ERR_DROP, "RS_AssignTechLinks: Could not get alien type (alien or alien_dead) definition for '%s'", req->id); 			break;+		case RS_LINK_UFO:+			req->link = AIR_GetAircraft(req->id);+			break; 		default: 			break; 		}@@ -872,6 +897,8 @@ 		return ((teamDef_t*)req->link)->id; 	case RS_LINK_ALIEN_GLOBAL: 		return "global alien count";+	case RS_LINK_UFO:+		return ((aircraft_t*)req->link)->id; 	default: 		return "unknown"; 	}@@ -894,6 +921,8 @@ 		return "alien_dead"; 	case RS_LINK_ALIEN_GLOBAL: 		return "alienglobal";+	case RS_LINK_UFO:+		return "ufo"; 	default: 		return "unknown"; 	}@@ -1341,6 +1370,20 @@ 						} else { 							Com_Printf("RS_ParseTechnologies: \"%s\" Too many 'required' defined. Limit is %i - ignored.\n", name, MAX_TECHLINKS); 						}+					} else if (!strcmp(token, "ufo")) {+						/* Defines what ufos need to be collected for this item to be researchable. */+						if (requiredTemp->numLinks < MAX_TECHLINKS) {+							/* Set requirement-type. */+							requiredTemp->links[requiredTemp->numLinks].type = RS_LINK_UFO;+							/* Set requirement-name (id). */+							token = Com_Parse(text);+							requiredTemp->links[requiredTemp->numLinks].id = Mem_PoolStrDup(token, cp_campaignPool, 0);+							/* Set requirement-amount of item. */+							token = Com_Parse(text);+							requiredTemp->links[requiredTemp->numLinks].amount = atoi(token);+							Com_DPrintf(DEBUG_CLIENT, "RS_ParseTechnologies: require-ufo - %s - %i\n", requiredTemp->links[requiredTemp->numLinks].id, requiredTemp->links[requiredTemp->numLinks].amount);+							requiredTemp->numLinks++;+						} 					} else { 						Com_Printf("RS_ParseTechnologies: \"%s\" unknown requirement-type: \"%s\" - ignored.\n", name, token); 					}Index: src/client/campaign/cp_research.h===================================================================--- src/client/campaign/cp_research.h	(revision 25739)+++ src/client/campaign/cp_research.h	(working copy)@@ -70,6 +70,7 @@ 	RS_LINK_ALIEN, 	RS_LINK_ALIEN_DEAD, 	RS_LINK_ALIEN_GLOBAL,+	RS_LINK_UFO, 	MAX_RS_LINKTYPES } requirementType_t; @@ -84,6 +85,7 @@ 				 * RS_LINK_ALIEN		-> teamDef_t* 				 * RS_LINK_ALIEN_DEAD	-> teamDef_t* 				 * RS_LINK_ALIEN_GLOBAL -> NULL+				 * RS_LINK_UFO -> aircraft_t* 				 */ 	int amount;	/**< How many items are needed for research ... if any. (fix) */ 	int collected;	/**< How many items have been collected. (varies) */@@ -219,6 +221,7 @@ void RS_MarkCollected(technology_t *tech) __attribute__((nonnull)); void RS_MarkResearchable(qboolean init, const struct base_s *base); void RS_ResearchFinish(technology_t* tech);+void RS_StopResearch(technology_t* tech); void RS_MarkOneResearchable(technology_t *tech);  void RS_AssignScientist(technology_t* tech, struct base_s *base);Index: src/client/campaign/cp_base.c===================================================================--- src/client/campaign/cp_base.c	(revision 25739)+++ src/client/campaign/cp_base.c	(working copy)@@ -523,10 +523,6 @@ 	if (B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(CAP_WORKSPACE))) 		PR_UpdateProductionCap(base); -	if (B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(CAP_UFOHANGARS_SMALL)) ||-		B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(CAP_UFOHANGARS_LARGE)))-		UR_UpdateUFOHangarCapForAll(base);- 	if (B_GetBuildingStatus(base, B_GetBuildingTypeByCapacity(CAP_ANTIMATTER))) 		B_UpdateAntimatterCap(base); @@ -2575,8 +2571,8 @@ 	case CAP_ITEMS:			/**< Update items capacity in base. */ 	case CAP_AIRCRAFT_SMALL:	/**< Update aircraft capacity in base. */ 	case CAP_AIRCRAFT_BIG:		/**< Update aircraft capacity in base. */-	case CAP_UFOHANGARS_SMALL:	/**< Base capacities for UFO hangars. */-	case CAP_UFOHANGARS_LARGE:	/**< Base capacities for UFO hangars. */+	case CAP_UFOHANGARS_SMALL:      /**< Base capacities for UFO hangars. */+	case CAP_UFOHANGARS_LARGE:      /**< Base capacities for UFO hangars. */ 	case CAP_ANTIMATTER:		/**< Update antimatter capacity in base. */ 		/* Reset given capacity in current base. */ 		base->capacities[cap].max = 0;@@ -2789,12 +2785,52 @@ }  /**+ * @brief Moves the UFOs stored in the base's storage structure to UFO Yards' storedUFOs structure+ * @note this is a temporary function to keep savegame compatibility+ * @todo remove this before release (or after a time)+ */+static void B_PostLoadMoveUFOsToUFOYards (void)+{+	int i;++	/* loop throught bases */+	for (i = 0; i < ccs.numBases; i++) {+		int j;+		base_t * base = B_GetFoundedBaseByIDX(i);++		if (!base)+			continue;++		/* find ufos in storage */+		for (j = 0; j < csi.numODs; j++) {+			objDef_t *od = INVSH_GetItemByIDX(j);++			if (!od)+				continue;+			assert(od->tech);+			if (od->tech->type != RS_CRAFT)+				continue;++			/* add each to an ufoyard */+			for (;0 < base->storage.num[od->idx];) {+				installation_t *UFOYard = INS_GetFirstUFOYard(qtrue);++				if (UFOYard)+					US_StoreUFO(AIR_GetAircraft(od->id), UFOYard, ccs.date);+				base->storage.num[od->idx]--;+			}+		}+	}+}++/**  * @brief Set the capacity stuff for all the bases after loading a savegame  * @sa SAV_GameActionsAfterLoad  */ void B_PostLoadInit (void) { 	B_PostLoadInitCapacity();+	B_PostLoadMoveUFOsToUFOYards(); }  qboolean B_LoadStorageXML (mxml_node_t *parent, equipDef_t *equip)@@ -2874,6 +2910,8 @@ 			} else 				*building = *B_GetBuildingTemplate(buildingType); +			if (!building)+				continue; 			building->idx = B_GetBuildingIDX(b, building); 			if (building->idx != buildId) { 				Com_Printf("building ID doesn't match\n");@@ -2944,6 +2982,7 @@ 		/** @todo can't we use something like INVSH_DestroyInventory here? */ 		/* clear the mess of stray loaded pointers */ 		memset(&b->bEquipment, 0, sizeof(b->bEquipment));+		/* reset capacities */ 	}  	ccs.numBases = B_GetFoundedBaseCount();@@ -2963,6 +3002,7 @@ 	if (!strcmp(obj->id, ANTIMATTER_TECH_ID)) 		return qfalse; +	/* @todo: remove this condition - I keep it for compatibility for now. -geever */ 	/* aircraft are stored in hangars */ 	assert(obj->tech); 	if (obj->tech->type == RS_CRAFT)@@ -3118,74 +3158,6 @@ }  /**- * @brief Remove ufos until everything fits in ufo hangars.- * @param[in] base Pointer to the base- * @param[in] buildingType type- */-void B_RemoveUFOsExceedingCapacity (base_t *base, const buildingType_t buildingType)-{-	const baseCapacities_t capacityType = B_GetCapacityFromBuildingType(buildingType);-	int i;-	int objIdx[MAX_OBJDEFS];	/**< Will contain idx of items that can be removed */-	int num;--	if (capacityType != CAP_UFOHANGARS_SMALL && capacityType != CAP_UFOHANGARS_LARGE)-		return;--	if (base->capacities[capacityType].cur <= base->capacities[capacityType].max)-		return;--	for (i = 0, num = 0; i < csi.numODs; i++) {-		const objDef_t *obj = &csi.ods[i];-		aircraft_t *ufocraft;--		/* Don't count what isn't an aircraft */-		assert(obj->tech);-		if (obj->tech->type != RS_CRAFT)-			continue;--		/* look for corresponding aircraft in global array */-		ufocraft = AIR_GetAircraft(obj->id);-		if (!ufocraft) {-			Com_DPrintf(DEBUG_CLIENT, "B_RemoveUFOsExceedingCapacity: Did not find UFO %s\n", obj->id);-			continue;-		}--		if (ufocraft->size == AIRCRAFT_LARGE && capacityType != CAP_UFOHANGARS_LARGE)-			continue;-		if (ufocraft->size == AIRCRAFT_SMALL && capacityType != CAP_UFOHANGARS_SMALL)-			continue;--		/* Don't count item that we don't have in base */-		if (!base->storage.num[i])-			continue;--		objIdx[num++] = i;-	}--	while (num && base->capacities[capacityType].cur > base->capacities[capacityType].max) {-		/* Select the item to remove */-		const int randNumber = rand() % num;-		/* items are destroyed. We guess that all items of a given type are stored in the same location-		 *	=> destroy all items of this type */-		const int idx = objIdx[randNumber];--		assert(idx >= 0);-		assert(idx < MAX_OBJDEFS);-		B_UpdateStorageAndCapacity(base, &csi.ods[idx], -base->storage.num[idx], qfalse, qfalse);--		REMOVE_ELEM(objIdx, randNumber, num);-		UR_UpdateUFOHangarCapForAll(base);--		/* Make sure that we don't have an infinite loop */-		if (num <= 0)-			break;-	}-	Com_DPrintf(DEBUG_CLIENT, "B_RemoveUFOsExceedingCapacity: Remains %i in storage for a maximum of %i\n",-		base->capacities[capacityType].cur, base->capacities[capacityType].max);-}--/**  * @brief Update Storage Capacity.  * @param[in] base Pointer to the base  * @sa B_ResetAllStatusAndCapacities_fIndex: base/ufos/installation.ufo===================================================================--- base/ufos/installation.ufo	(revision 25739)+++ base/ufos/installation.ufo	(working copy)@@ -24,7 +24,7 @@ 	cost		30000 	radar_range	0 	max_batteries	0-	max_ufo_stored	10+	max_ufo_stored	6 	max_damage		100 	buildtime	11 }Index: base/ufos/research.ufo===================================================================--- base/ufos/research.ufo	(revision 25739)+++ base/ufos/research.ufo	(working copy)@@ -627,15 +627,15 @@  	require_OR 	{-		item craft_ufo_ripper 1-		item craft_ufo_gunboat 1-		item craft_ufo_scout 1-		item craft_ufo_fighter 1-		item craft_ufo_supply 1-		item craft_ufo_carrier 1-		item craft_ufo_bomber 1-		item craft_ufo_corrupter 1-		item craft_ufo_harvester 1+		ufo craft_ufo_ripper 1+		ufo craft_ufo_gunboat 1+		ufo craft_ufo_scout 1+		ufo craft_ufo_fighter 1+		ufo craft_ufo_supply 1+		ufo craft_ufo_carrier 1+		ufo craft_ufo_bomber 1+		ufo craft_ufo_corrupter 1+		ufo craft_ufo_harvester 1 	}  	time		3000@@ -3503,7 +3503,7 @@  	require_AND 	{-		item craft_ufo_scout 1+		ufo craft_ufo_scout 1 		tech rs_alien_ufo_theory 	} @@ -3546,7 +3546,7 @@ 	// FIXME 	require_AND 	{-		item craft_ufo_fighter 1+		ufo craft_ufo_fighter 1 		tech rs_alien_ufo_theory 	} @@ -3590,7 +3590,7 @@ 	require_AND 	{ 		tech rs_alien_ufo_theory-		item craft_ufo_supply 1+		ufo craft_ufo_supply 1 	}  	provides	"craft_ufo_supply"@@ -3633,7 +3633,7 @@ 	require_AND 	{ 		tech rs_alien_ufo_theory-		item craft_ufo_carrier 1+		ufo craft_ufo_carrier 1 	}  	provides	"craft_ufo_carrier"@@ -3676,7 +3676,7 @@ 	require_AND 	{ 		tech rs_alien_ufo_theory-		item craft_ufo_bomber 1+		ufo craft_ufo_bomber 1 	}  	provides	"craft_ufo_bomber"@@ -3719,7 +3719,7 @@ 	require_AND 	{ 		tech rs_alien_ufo_theory-		item craft_ufo_corrupter 1+		ufo craft_ufo_corrupter 1 	}  	provides	"craft_ufo_corrupter"@@ -3762,7 +3762,7 @@ 	require_AND 	{ 		tech rs_alien_ufo_theory-		item craft_ufo_gunboat 1+		ufo craft_ufo_gunboat 1 	}  	provides	"craft_ufo_gunboat"@@ -3805,7 +3805,7 @@ 	require_AND 	{ 		tech rs_alien_ufo_theory-		item craft_ufo_ripper 1+		ufo craft_ufo_ripper 1 	}  	provides	"craft_ufo_ripper"@@ -3848,7 +3848,7 @@ 	require_AND 	{ 		tech rs_alien_ufo_theory-		item craft_ufo_harvester 1+		ufo craft_ufo_harvester 1 	}  	provides	"craft_ufo_harvester"@@ -3891,7 +3891,7 @@ 	require_AND 	{ 		tech rs_alien_ufo_theory-		item craft_ufo_mothership 1+		ufo craft_ufo_mothership 1 	}  	provides	"craft_ufo_mothership"Index: base/ufos/basemanagement.ufo===================================================================--- base/ufos/basemanagement.ufo	(revision 25739)+++ base/ufos/basemanagement.ufo	(working copy)@@ -233,6 +233,7 @@ 	ondestroy		"building_ondestroy" 	capacity		1 	type			smallufohangar+	max_count	0 }  building building_large_ufo_hangar@@ -249,6 +250,7 @@ 	pedia			rs_building_large_ufo_hangar 	needs			building_large_ufo_hangar2 	type			ufohangar+	max_count	0 }  building building_large_ufo_hangar2Index: base/ufos/ui/lostwon.ufo===================================================================--- base/ufos/ui/lostwon.ufo	(revision 25739)+++ base/ufos/ui/lostwon.ufo	(working copy)@@ -166,17 +166,17 @@ 		pos		"17 125" 		size	"667 38" 		cvar	*cvar:mn_uforecovery_tab+		onChange {+			cmd "uforecovery_tabselect <cvar:mn_uforecovery_tab> ;"+		} 		option store { 			label	"_Store UFO"-			value	"0"+			value	"store" 		} 		option sell { 			label	"_Sell UFO"-			value	"1"+			value	"sell" 		}-		onChange {-			cmd "uforecovery_tabchange;"-		} 	}  	string actualufo@@ -214,7 +214,6 @@ 			lineheight	16 			tabwidth	100 			rows		"8"-			onClick		{ cmd "cp_uforecovery_baselist_click <lineselected>" } 			onViewChange	{ 				*(abstractscrollbar)store_panel.cp_ur_baselist_scroll@fullsize <fullsize> 				*(abstractscrollbar)store_panel.cp_ur_baselist_scroll@current <viewpos>@@ -232,18 +231,6 @@ 			onChange	{ *store_panel.cp_uforecovery_baselist@viewpos <current> } 		} -		string selected_base_str-		{-			string		"Selected base:"-			pos			"30 186"-		}--		string selected_base_val-		{-			string		*cvar:mission_recoverybase-			pos			"160 186"-		}- 		button bt_basesel 		{ 			string		"_Recover"@@ -253,26 +240,15 @@ 			size		"176 32" 			font		f_menu 			color		"0 0.5 0 1"-			onClick		{ cmd "cp_uforecovery_store_start;mn_pop;" }+			onClick		{ cmd "cp_uforecovery_store_start <node:root.store_panel.cp_uforecovery_baselist@lineselected>; mn_pop;" } 		}--		button bt_basereplace-		{-			string		"_Replace UFO"-			tooltip		"_Replace stored UFO with actual recovered one"-			image		ui/button_green_small-			pos			"375 210"-			size		"176 32"-			font		f_menu-			color		"0 0.5 0 1"-			onClick		{ cmd "start_replace;" }-		} 	}  	// 	// sell panel 	//-	panel sell_panel {+	panel sell_panel+	{ 		{ 			pos		"20 165" 			size	"660 255"@@ -360,66 +336,56 @@ 		} 	} -	//-	// function to manage tab-	//-	confunc uforecovery_tabchange {-		*store_panel@invis true-		*sell_panel@invis true-		if ( *cvar:mn_uforecovery_tab == 0 ) {+	/*+	 * function to manage tab+	 */+	confunc uforecovery_tabselect {+		*cvar:mn_uforecovery_tab = <1>+		if ( *cvar:mn_uforecovery_tab eq "store" ) { 			*store_panel@invis false+			*sell_panel@invis true 		}-		if ( *cvar:mn_uforecovery_tab == 1 ) {+		if ( *cvar:mn_uforecovery_tab eq "sell" ) { 			*sell_panel@invis false+			*store_panel@invis true 		} 	} -	confunc btnatsel_enable {-		*sell_panel.bt_natsel@disabled false+	confunc btnatsel {+		*cvar:tmp = <1>+		if ( *cvar:tmp eq "enable" ) {+			*sell_panel.bt_natsel@disabled false+		} else {+			*sell_panel.bt_natsel@disabled true+		}+		delete *cvar:tmp 	}--	confunc btbasesel_enable {-		*store_panel.bt_basesel@disabled false+	+	confunc btbasesel {+		*cvar:tmp = <1>+		if ( *cvar:tmp eq "enable" ) {+			*store_panel.bt_basesel@disabled false+		} else {+			*store_panel.bt_basesel@disabled true+		}+		delete *cvar:tmp 	} -	confunc btbaserepl_enable {-		*store_panel.bt_basereplace@disabled false-	} -	confunc cp_basesel_disable {-		*store_panel.cp_uforecovery_baselist@disabled true-	}--	// @todo perhaps a shorter way to do so? 	confunc cp_basesel_select {-		*store_panel.cp_uforecovery_baselist@lineselected 0+		*store_panel.cp_uforecovery_baselist@lineselected <1> 	} -	confunc start_replace {-		*cvar:mn_uforecovery_tab 1-		call uforecovery_tabchange-		cmd "cp_uforecovery_replace;"-		*store_panel.cp_uforecovery_baselist@disabled true-		*store_panel.cp_uforecovery_baselist@lineselected -1-		*store_panel.bt_basesel@disabled true-		*store_panel.bt_basereplace@disabled true-		*sell_panel.cp_uforecovery_nationlist@lineselected	-1-		*sell_panel.bt_natsel@disabled true-		*cvar:mission_recoverynation ""-	}- 	func onInit {-		*store_panel.cp_uforecovery_baselist@lineselected	-1-		*store_panel.cp_uforecovery_baselist@disabled false-		*store_panel.bt_basereplace@disabled true-		*store_panel.bt_basesel@disabled true-		*sell_panel.cp_uforecovery_nationlist@lineselected	-1-		*sell_panel.bt_natsel@disabled true-		*cvar:mn_uforecovery_tab 0-		cmd "del mission_recoverybase;del mission_recoverynation;uforecovery_tabchange;cp_uforecovery_sell_init;cp_uforecovery_store_init;"+		cmd "uforecovery_tabselect store"+		*store_panel.cp_uforecovery_baselist@lineselected = -1+		*sell_panel.cp_uforecovery_nationlist@lineselected = -1+		*cvar:mission_recoverynation = ""+		cmd "cp_uforecovery_sell_init;cp_uforecovery_store_init;" 	}  	func onClose {-		cmd "del mn_uforecovery_tab;del mn_uforecovery_actualufo;"+		delete *cvar:mn_uforecovery_tab+		delete *cvar:mn_uforecovery_actualufo 	} }Index: base/ufos/ui/production.ufo===================================================================--- base/ufos/ui/production.ufo	(revision 25739)+++ base/ufos/ui/production.ufo	(working copy)@@ -528,10 +528,10 @@ 	{ 		dataid		TEXT_PRODUCTION_INFO 		pos			"610 528"-		size		"346 100"+		size		"346 200" 		lineheight	20 		tabwidth	260-		rows		"5"+		rows		"10" 		onViewChange	{ 			*(abstractscrollbar)description_scroll@fullsize <fullsize> 			*(abstractscrollbar)description_scroll@current <viewpos>@@ -544,8 +544,8 @@ 		pos			"960 528" 		height		"100" 		current		0-		viewsize	5-		fullsize	5+		viewsize	10+		fullsize	10 		hidewhenunused true 		onChange	{ *description@viewpos <current> } 	}@@ -554,6 +554,18 @@ 	// init function 	// ================== +	confunc amountsetter {+		*cvar:tmp = <1>+		if ( *cvar:tmp eq "disable" ) {+			*prod_change@disabled = true+			*prod_change_10@disabled = true+		} else {+			*prod_change@disabled = false+			*prod_change_10@disabled = false+		}+		delete *cvar:tmp+	}+ 	func onInit 	{ 		cmd "prod_init; prod_type *mn_itemtype;"