1 /**
2  * @file
3  * @brief Menu related callback functions used for production.
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "../../cl_shared.h"
27 #include "../../cl_inventory.h"
28 #include "../../ui/ui_dataids.h"
29 #include "cp_campaign.h"
30 #include "cp_market.h"
31 #include "cp_ufo.h"
32 #include "cp_popup.h"
33 #include "cp_produce.h"
34 #include "cp_produce_callbacks.h"
35 
36 /**
37  * Holds the current active production category/filter type.
38  * @sa itemFilterTypes_t
39  */
40 static itemFilterTypes_t produceCategory = FILTER_S_PRIMARY;
41 
42 /** Holds the current active selected queue entry. */
43 static production_t* selectedProduction = nullptr;
44 
45 /** A list if all producable items. */
46 static linkedList_t* productionItemList;
47 
48 /** Currently selected entry in the productionItemList (depends on content) */
49 static productionData_t selectedData;
50 
51 /** @brief Number of blank lines between queued items and tech list. */
52 static const int QUEUE_SPACERS = 2;
53 
54 /**
55  * @brief Resets the selected item data structure. Does not reset the selected production
56  * @sa PR_ClearSelected
57  */
PR_ClearSelectedItems(void)58 static void PR_ClearSelectedItems (void)
59 {
60 	OBJZERO(selectedData);
61 	selectedData.type = PRODUCTION_TYPE_MAX;
62 }
63 
64 /**
65  * @brief Resets all "selected" pointers to nullptr.
66  * @sa PR_ClearSelectedItems
67  */
PR_ClearSelected(void)68 static void PR_ClearSelected (void)
69 {
70 	PR_ClearSelectedItems();
71 	selectedProduction = nullptr;
72 }
73 
74 /**
75  * @brief update the list of queued and available items
76  * @param[in] base Pointer to the base.
77  */
PR_UpdateProductionList(const base_t * base)78 static void PR_UpdateProductionList (const base_t* base)
79 {
80 	int i;
81 	linkedList_t* productionList = nullptr;
82 	linkedList_t* productionQueued = nullptr;
83 	linkedList_t* productionAmount = nullptr;
84 	const production_queue_t* queue;
85 
86 	assert(base);
87 
88 	queue = PR_GetProductionForBase(base);
89 
90 	/* First add all the queue items ... */
91 	for (i = 0; i < queue->numItems; i++) {
92 		const production_t* prod = &queue->items[i];
93 		if (PR_IsItem(prod)) {
94 			const objDef_t* od = prod->data.data.item;
95 			cgi->LIST_AddString(&productionList, va("%s", _(od->name)));
96 			cgi->LIST_AddString(&productionAmount, va("%i", B_ItemInBase(prod->data.data.item, base)));
97 			cgi->LIST_AddString(&productionQueued, va("%i", prod->amount));
98 		} else if (PR_IsAircraft(prod)) {
99 			const aircraft_t* aircraftTemplate = prod->data.data.aircraft;
100 
101 			cgi->LIST_AddString(&productionList, va("%s", _(aircraftTemplate->name)));
102 			cgi->LIST_AddString(&productionAmount, va("%i", AIR_CountInBaseByTemplate(base, aircraftTemplate)));
103 			cgi->LIST_AddString(&productionQueued, va("%i", prod->amount));
104 		} else if (PR_IsDisassembly(prod)) {
105 			const storedUFO_t* ufo = prod->data.data.ufo;
106 
107 			cgi->LIST_AddString(&productionList, va("%s (%.0f%%)", UFO_TypeToName(ufo->ufoTemplate->ufotype), ufo->condition * 100));
108 			cgi->LIST_AddString(&productionAmount, va("%i", US_UFOsInStorage(ufo->ufoTemplate, ufo->installation)));
109 			cgi->LIST_AddString(&productionQueued, "1");
110 		}
111 	}
112 
113 	/* Then spacers ... */
114 	for (i = 0; i < QUEUE_SPACERS; i++) {
115 		cgi->LIST_AddString(&productionList, "");
116 		cgi->LIST_AddString(&productionAmount, "");
117 		cgi->LIST_AddString(&productionQueued, "");
118 	}
119 
120 	cgi->LIST_Delete(&productionItemList);
121 
122 	/* Then go through all object definitions ... */
123 	if (produceCategory == FILTER_DISASSEMBLY) {
124 		/** UFOs at UFO stores */
125 		US_Foreach(ufo) {
126 			/* UFO is being transported */
127 			if (ufo->status != SUFO_STORED)
128 				continue;
129 			/* UFO not researched */
130 			if (!RS_IsResearched_ptr(ufo->ufoTemplate->tech))
131 				continue;
132 			/* The UFO is being disassembled already */
133 			if (ufo->disassembly)
134 				continue;
135 
136 			cgi->LIST_AddPointer(&productionItemList, ufo);
137 			cgi->LIST_AddString(&productionList, va("%s (%.0f%%)", UFO_TypeToName(ufo->ufoTemplate->ufotype), ufo->condition * 100));
138 			cgi->LIST_AddString(&productionAmount, va("%i", US_UFOsInStorage(ufo->ufoTemplate, ufo->installation)));
139 			cgi->LIST_AddString(&productionQueued, "");
140 		}
141 	} else if (produceCategory == FILTER_AIRCRAFT) {
142 		for (i = 0; i < ccs.numAircraftTemplates; i++) {
143 			aircraft_t* aircraftTemplate = &ccs.aircraftTemplates[i];
144 			/* don't allow producing ufos */
145 			if (AIR_IsUFO(aircraftTemplate))
146 				continue;
147 			if (!aircraftTemplate->tech) {
148 				Com_Printf("PR_UpdateProductionList: no technology for craft %s!\n", aircraftTemplate->id);
149 				continue;
150 			}
151 
152 			Com_DPrintf(DEBUG_CLIENT, "air: %s ufotype: %i tech: %s time: %i\n", aircraftTemplate->id,
153 					aircraftTemplate->ufotype, aircraftTemplate->tech->id, aircraftTemplate->tech->produceTime);
154 
155 			if (aircraftTemplate->tech->produceTime > 0 && RS_IsResearched_ptr(aircraftTemplate->tech)) {
156 				cgi->LIST_AddPointer(&productionItemList, aircraftTemplate);
157 				cgi->LIST_AddString(&productionList, va("%s", _(aircraftTemplate->name)));
158 				cgi->LIST_AddString(&productionAmount, va("%i", AIR_CountInBaseByTemplate(base, aircraftTemplate)));
159 				cgi->LIST_AddString(&productionQueued, "");
160 			}
161 		}
162 	} else {
163 		objDef_t* od;
164 		for (i = 0, od = cgi->csi->ods; i < cgi->csi->numODs; i++, od++) {
165 			const technology_t* tech;
166 			if (od->isVirtual)
167 				continue;
168 			tech = RS_GetTechForItem(od);
169 			/* We will not show items that are not producible.
170 			 * We can only produce what was researched before. */
171 			if (RS_IsResearched_ptr(tech) && PR_ItemIsProduceable(od) && INV_ItemMatchesFilter(od, produceCategory)) {
172 				cgi->LIST_AddPointer(&productionItemList, od);
173 
174 				cgi->LIST_AddString(&productionList, va("%s", _(od->name)));
175 				cgi->LIST_AddString(&productionAmount, va("%i", B_ItemInBase(od, base)));
176 				cgi->LIST_AddString(&productionQueued, "");
177 			}
178 		}
179 	}
180 
181 	/* bind the menu text to our static char array */
182 	cgi->UI_RegisterLinkedListText(TEXT_PRODUCTION_LIST, productionList);
183 	/* bind the amount of available items */
184 	cgi->UI_RegisterLinkedListText(TEXT_PRODUCTION_AMOUNT, productionAmount);
185 	/* bind the amount of queued items */
186 	cgi->UI_RegisterLinkedListText(TEXT_PRODUCTION_QUEUED, productionQueued);
187 }
188 
PR_RequirementsInfo(const base_t * base,const requirements_t * reqs)189 static void PR_RequirementsInfo (const base_t* base, const requirements_t* reqs) {
190 	const vec4_t green = {0.0f, 1.0f, 0.0f, 0.8f};
191 	const vec4_t yellow = {1.0f, 0.874f, 0.294f, 1.0f};
192 	int i;
193 	uiNode_t* req_root = nullptr;
194 	uiNode_t* req_items = nullptr;
195 #if 0
196 	uiNode_t* req_techs = nullptr;
197 	uiNode_t* req_technots = nullptr;
198 #endif
199 
200 	cgi->UI_ResetData(OPTION_PRODUCTION_REQUIREMENTS);
201 
202 	if (!reqs->numLinks)
203 		return;
204 
205 	for (i = 0; i < reqs->numLinks; i++) {
206 		const requirement_t* req = &reqs->links[i];
207 
208 		switch (req->type) {
209 		case RS_LINK_ITEM: {
210 				uiNode_t* node = cgi->UI_AddOption(&req_items, req->link.od->id, va("%i %s", req->amount, _(req->link.od->name)), va("%i", i));
211 				if (B_ItemInBase(req->link.od, base) >= req->amount)
212 					Vector4Copy(green, node->color);
213 				else
214 					Vector4Copy(yellow, node->color);
215 				break;
216 			}
217 		case RS_LINK_ANTIMATTER: {
218 				technology_t* tech = RS_GetTechForItem(INVSH_GetItemByID(ANTIMATTER_TECH_ID));
219 				uiNode_t* node;
220 
221 				assert(tech);
222 				node = cgi->UI_AddOption(&req_items, ANTIMATTER_TECH_ID, va("%i %s", req->amount, _(tech->name)), va("%i", i));
223 				if (B_AntimatterInBase(base) >= req->amount)
224 					Vector4Copy(green, node->color);
225 				else
226 					Vector4Copy(yellow, node->color);
227 				break;
228 			}
229 #if 0
230 		case RS_LINK_TECH:
231 /*			if (node && RS_IsResearched_ptr(req->link)) ... */
232 			break;
233 		case RS_LINK_TECH_NOT:
234 /*			if (node && RS_IsResearched_ptr(req->link)) ... */
235 			break;
236 #endif
237 		default:
238 			break;
239 		}
240 	}
241 	if (req_items) {
242 		uiNode_t* node = cgi->UI_AddOption(&req_root, "items", "_Items", "item");
243 		node->firstChild = req_items;
244 	}
245 	cgi->UI_RegisterOption(OPTION_PRODUCTION_REQUIREMENTS, req_root);
246 }
247 
248 /**
249  * @brief Prints information about the selected item (no aircraft) in production.
250  * @param[in] base Pointer to the base where informations should be printed.
251  * @param[in] od The attributes of the item being produced.
252  * @param[in] remainingHours The remaining hours until this production is finished
253  * @sa PR_ProductionInfo
254  */
PR_ItemProductionInfo(const base_t * base,const objDef_t * od,int remainingHours)255 static void PR_ItemProductionInfo (const base_t* base, const objDef_t* od, int remainingHours)
256 {
257 	static char productionInfo[512];
258 
259 	assert(base);
260 	assert(od);
261 
262 	/* Don't try to display an item which is not producible. */
263 	if (!PR_ItemIsProduceable(od)) {
264 		Com_sprintf(productionInfo, sizeof(productionInfo), _("No item selected"));
265 		cgi->Cvar_Set("mn_item", "");
266 	} else {
267 		const technology_t* tech = RS_GetTechForItem(od);
268 
269 		Com_sprintf(productionInfo, sizeof(productionInfo), "%s\n", _(od->name));
270 		Q_strcat(productionInfo, sizeof(productionInfo), _("Cost per item\t%i c\n"), PR_GetPrice(od));
271 		Q_strcat(productionInfo, sizeof(productionInfo), _("Production time\t%ih\n"), remainingHours);
272 		Q_strcat(productionInfo, sizeof(productionInfo), _("Item size\t%i\n"), od->size);
273 		cgi->Cvar_Set("mn_item", "%s", od->id);
274 
275 		PR_RequirementsInfo(base, &tech->requireForProduction);
276 		cgi->Cmd_ExecuteString("show_requirements %i", tech->requireForProduction.numLinks);
277 	}
278 	cgi->UI_RegisterText(TEXT_PRODUCTION_INFO, productionInfo);
279 }
280 
281 /**
282  * @brief Prints information about the selected disassembly task
283  * @param[in] ufo The UFO being disassembled.
284  * @param[in] remainingHours The remaining hours until this production is finished
285  * @sa PR_ProductionInfo
286  */
PR_DisassemblyInfo(const storedUFO_t * ufo,int remainingHours)287 static void PR_DisassemblyInfo (const storedUFO_t* ufo, int remainingHours)
288 {
289 	static char productionInfo[512];
290 	int i;
291 
292 	assert(ufo);
293 	assert(ufo->ufoTemplate);
294 
295 	Com_sprintf(productionInfo, sizeof(productionInfo), "%s (%.0f%%) - %s\n", _(UFO_TypeToName(ufo->ufoTemplate->ufotype)), ufo->condition * 100, _("Disassembly"));
296 	Q_strcat(productionInfo, sizeof(productionInfo), _("Stored at: %s\n"), ufo->installation->name);
297 	Q_strcat(productionInfo, sizeof(productionInfo), _("Disassembly time: %ih\n"), remainingHours);
298 	Q_strcat(productionInfo, sizeof(productionInfo), _("Components:\n"));
299 	/* Print components. */
300 	for (i = 0; i < ufo->comp->numItemtypes; i++) {
301 		const objDef_t* compOd = ufo->comp->items[i];
302 		const int amount = (ufo->condition < 1 && ufo->comp->itemAmount2[i] != COMP_ITEMCOUNT_SCALED) ? ufo->comp->itemAmount2[i] : round(ufo->comp->itemAmount[i] * ufo->condition);
303 
304 		if (amount == 0)
305 			continue;
306 
307 		assert(compOd);
308 		Q_strcat(productionInfo, sizeof(productionInfo), "  %s (%i)\n", _(compOd->name), amount);
309 	}
310 	cgi->UI_RegisterText(TEXT_PRODUCTION_INFO, productionInfo);
311 	cgi->Cvar_Set("mn_item", "%s", ufo->id);
312 	cgi->Cmd_ExecuteString("show_requirements 0");
313 }
314 
315 /**
316  * @brief Prints information about the selected aircraft in production.
317  * @param[in] base Pointer to the base where informations should be printed.
318  * @param[in] aircraftTemplate The aircraft to print the information for
319  * @param[in] remainingHours The remaining hours until this production is finished
320  * @sa PR_ProductionInfo
321  */
PR_AircraftInfo(const base_t * base,const aircraft_t * aircraftTemplate,int remainingHours)322 static void PR_AircraftInfo (const base_t* base, const aircraft_t* aircraftTemplate, int remainingHours)
323 {
324 	static char productionInfo[512];
325 
326 	Com_sprintf(productionInfo, sizeof(productionInfo), "%s\n", _(aircraftTemplate->name));
327 	Q_strcat(productionInfo, sizeof(productionInfo), _("Production costs\t%i c\n"), PR_GetPrice(aircraftTemplate));
328 	Q_strcat(productionInfo, sizeof(productionInfo), _("Production time\t%ih\n"), remainingHours);
329 	cgi->UI_RegisterText(TEXT_PRODUCTION_INFO, productionInfo);
330 	cgi->Cvar_Set("mn_item", "%s", aircraftTemplate->id);
331 	PR_RequirementsInfo(base, &aircraftTemplate->tech->requireForProduction);
332 	cgi->Cmd_ExecuteString("show_requirements %i", aircraftTemplate->tech->requireForProduction.numLinks);
333 }
334 
335 /**
336  * @brief Prints information about the selected item in production.
337  * @param[in] base Pointer to the base where informations should be printed.
338  * @sa PR_AircraftInfo
339  * @sa PR_ItemProductionInfo
340  * @sa PR_DisassemblyInfo
341  */
PR_ProductionInfo(const base_t * base)342 static void PR_ProductionInfo (const base_t* base)
343 {
344 	if (selectedProduction) {
345 		const production_t* prod = selectedProduction;
346 		const int time = PR_GetRemainingHours(prod);
347 		cgi->UI_ExecuteConfunc("prod_taskselected");
348 
349 		if (PR_IsAircraft(prod)) {
350 			PR_AircraftInfo(base, prod->data.data.aircraft, time);
351 			cgi->UI_ExecuteConfunc("amountsetter enable");
352 		} else if (PR_IsItem(prod)) {
353 			PR_ItemProductionInfo(base, prod->data.data.item, time);
354 			cgi->UI_ExecuteConfunc("amountsetter enable");
355 		} else if (PR_IsDisassembly(prod)) {
356 			PR_DisassemblyInfo(prod->data.data.ufo, time);
357 			cgi->UI_ExecuteConfunc("amountsetter disable");
358 		} else {
359 			cgi->Com_Error(ERR_DROP, "PR_ProductionInfo: Selected production is not item nor aircraft nor ufo.\n");
360 		}
361 		cgi->Cvar_SetValue("mn_production_amount", selectedProduction->amount);
362 	} else {
363 		if (!PR_IsDataValid(&selectedData)) {
364 			cgi->UI_ExecuteConfunc("prod_nothingselected");
365 			if (produceCategory == FILTER_AIRCRAFT)
366 				cgi->UI_RegisterText(TEXT_PRODUCTION_INFO, _("No aircraft selected."));
367 			else
368 				cgi->UI_RegisterText(TEXT_PRODUCTION_INFO, _("No item selected"));
369 			cgi->Cvar_Set("mn_item", "");
370 		} else {
371 			cgi->UI_ExecuteConfunc("prod_availableselected");
372 			if (PR_IsAircraftData(&selectedData)) {
373 				PR_AircraftInfo(base, selectedData.data.aircraft, PR_GetProductionHours(base, &selectedData));
374 			} else if (PR_IsItemData(&selectedData)) {
375 				PR_ItemProductionInfo(base, selectedData.data.item, PR_GetProductionHours(base, &selectedData));
376 			} else if (PR_IsDisassemblyData(&selectedData)) {
377 				PR_DisassemblyInfo(selectedData.data.ufo, PR_GetProductionHours(base, &selectedData));
378 			}
379 		}
380 	}
381 	/* update capacity counters */
382 	cgi->UI_ExecuteConfunc("ui_prod_update_caps %d %d %d %d %d %d %d %d", CAP_GetFreeCapacity(base, CAP_ITEMS), CAP_GetMax(base, CAP_ITEMS),
383 		CAP_GetFreeCapacity(base, CAP_AIRCRAFT_SMALL), CAP_GetMax(base, CAP_AIRCRAFT_SMALL),
384 		CAP_GetFreeCapacity(base, CAP_AIRCRAFT_BIG), CAP_GetMax(base, CAP_AIRCRAFT_BIG),
385 		CAP_GetMax(base, CAP_WORKSPACE) - E_CountHired(base, EMPL_WORKER), CAP_GetMax(base, CAP_WORKSPACE)
386 	);
387 }
388 
389 /**
390  * @brief Click function for production list
391  * @note Opens the UFOpaedia - by right clicking an item
392  */
PR_ProductionListRightClick_f(void)393 static void PR_ProductionListRightClick_f (void)
394 {
395 	int num;
396 	production_queue_t* queue;
397 	base_t* base = B_GetCurrentSelectedBase();
398 
399 	/* can be called from everywhere without a base set */
400 	if (!base)
401 		return;
402 
403 	queue = PR_GetProductionForBase(base);
404 
405 	/* not enough parameters */
406 	if (cgi->Cmd_Argc() < 2) {
407 		Com_Printf("Usage: %s <arg>\n", cgi->Cmd_Argv(0));
408 		return;
409 	}
410 
411 	/* clicked which item? */
412 	num = atoi(cgi->Cmd_Argv(1));
413 
414 	/* Clicked the production queue or the item list? */
415 	if (num < queue->numItems && num >= 0) {
416 		production_t* prod = &queue->items[num];
417 		const technology_t* tech = PR_GetTech(&prod->data);
418 		selectedProduction = prod;
419 		UP_OpenWith(tech->id);
420 	} else if (num >= queue->numItems + QUEUE_SPACERS) {
421 		/* Clicked in the item list. */
422 		const int idx = num - queue->numItems - QUEUE_SPACERS;
423 
424 		if (produceCategory == FILTER_AIRCRAFT) {
425 			const aircraft_t* aircraftTemplate = (const aircraft_t*)cgi->LIST_GetByIdx(productionItemList, idx);
426 			/* aircraftTemplate may be empty if rclicked below real entry.
427 			 * UFO research definition must not have a tech assigned,
428 			 * only RS_CRAFT types have */
429 			if (aircraftTemplate && aircraftTemplate->tech)
430 				UP_OpenWith(aircraftTemplate->tech->id);
431 		} else if (produceCategory == FILTER_DISASSEMBLY) {
432 			const storedUFO_t* ufo = (const storedUFO_t*)cgi->LIST_GetByIdx(productionItemList, idx);
433 			if (ufo && ufo->ufoTemplate && ufo->ufoTemplate->tech) {
434 				UP_OpenWith(ufo->ufoTemplate->tech->id);
435 			}
436 		} else {
437 			objDef_t* od = (objDef_t*)cgi->LIST_GetByIdx(productionItemList, idx);
438 			const technology_t* tech = RS_GetTechForItem(od);
439 
440 			/* Open up UFOpaedia for this entry. */
441 			if (RS_IsResearched_ptr(tech) && INV_ItemMatchesFilter(od, produceCategory)) {
442 				PR_ClearSelected();
443 				PR_SetData(&selectedData, PRODUCTION_TYPE_ITEM, od);
444 				UP_OpenWith(tech->id);
445 				return;
446 			}
447 		}
448 	}
449 #ifdef DEBUG
450 	else
451 		Com_DPrintf(DEBUG_CLIENT, "PR_ProductionListRightClick_f: Click on spacer %i\n", num);
452 #endif
453 }
454 
455 /**
456  * @brief Click function for production list.
457  * @note "num" is the entry in the visible production list (includes queued entries and spaces).
458  * @todo left click on spacer should either delete current selection or do nothing, not update visible selection but show old info
459  */
PR_ProductionListClick_f(void)460 static void PR_ProductionListClick_f (void)
461 {
462 	int num;
463 	production_queue_t* queue;
464 	base_t* base = B_GetCurrentSelectedBase();
465 
466 	/* can be called from everywhere without a base set */
467 	if (!base)
468 		return;
469 
470 	queue = PR_GetProductionForBase(base);
471 
472 	/* Break if there are not enough parameters. */
473 	if (cgi->Cmd_Argc() < 2) {
474 		Com_Printf("Usage: %s <arg>\n", cgi->Cmd_Argv(0));
475 		return;
476 	}
477 
478 	/* Clicked which item? */
479 	num = atoi(cgi->Cmd_Argv(1));
480 
481 	/* Clicked the production queue or the item list? */
482 	if (num < queue->numItems && num >= 0) {
483 		selectedProduction = &queue->items[num];
484 		PR_ProductionInfo(base);
485 	} else if (num >= queue->numItems + QUEUE_SPACERS) {
486 		/* Clicked in the item list. */
487 		const int idx = num - queue->numItems - QUEUE_SPACERS;
488 
489 		if (produceCategory == FILTER_DISASSEMBLY) {
490 			storedUFO_t* ufo = (storedUFO_t*)cgi->LIST_GetByIdx(productionItemList, idx);
491 
492 			PR_ClearSelected();
493 			PR_SetData(&selectedData, PRODUCTION_TYPE_DISASSEMBLY, ufo);
494 
495 			PR_ProductionInfo(base);
496 		} else if (produceCategory == FILTER_AIRCRAFT) {
497 			aircraft_t* aircraftTemplate = (aircraft_t*)cgi->LIST_GetByIdx(productionItemList, idx);
498 			if (!aircraftTemplate) {
499 				Com_DPrintf(DEBUG_CLIENT, "PR_ProductionListClick_f: No item found at the list-position %i!\n", idx);
500 				return;
501 			}
502 			/* ufo research definition must not have a tech assigned
503 			 * only RS_CRAFT types have
504 			 * @sa RS_InitTree */
505 			if (aircraftTemplate->tech && aircraftTemplate->tech->produceTime >= 0
506 			 && RS_IsResearched_ptr(aircraftTemplate->tech)) {
507 				PR_ClearSelected();
508 				PR_SetData(&selectedData, PRODUCTION_TYPE_AIRCRAFT, aircraftTemplate);
509 				PR_ProductionInfo(base);
510 			}
511 		} else {
512 			objDef_t* od = (objDef_t*)cgi->LIST_GetByIdx(productionItemList, idx);
513 			const technology_t* tech = RS_GetTechForItem(od);
514 
515 			/* We can only produce items that fulfill the following conditions... */
516 			if (RS_IsResearched_ptr(tech) && PR_ItemIsProduceable(od) && INV_ItemMatchesFilter(od, produceCategory)) {
517 				PR_ClearSelected();
518 				PR_SetData(&selectedData, PRODUCTION_TYPE_ITEM, od);
519 				PR_ProductionInfo(base);
520 			}
521 		}
522 	}
523 }
524 
525 /**
526  * @brief Will select a new tab on the production list.
527  */
PR_ProductionType_f(void)528 static void PR_ProductionType_f (void)
529 {
530 	itemFilterTypes_t cat;
531 	base_t* base = B_GetCurrentSelectedBase();
532 
533 	if (cgi->Cmd_Argc() < 2) {
534 		Com_Printf("Usage: %s <category>\n", cgi->Cmd_Argv(0));
535 		return;
536 	}
537 
538 	cat = INV_GetFilterTypeID(cgi->Cmd_Argv(1));
539 
540 	/* Check if the given category index is valid. */
541 	if (cat == MAX_FILTERTYPES)
542 		cat = FILTER_S_PRIMARY;
543 
544 	/* Can be called from everywhere without a base set */
545 	if (!base)
546 		return;
547 
548 	produceCategory = cat;
549 	cgi->Cvar_Set("mn_itemtype", "%s", INV_GetFilterType(produceCategory));
550 
551 	/* Update list of entries for current production tab. */
552 	PR_UpdateProductionList(base);
553 
554 	/* Reset selected entry, if it was not from the queue */
555 	PR_ClearSelectedItems();
556 
557 	/* Select first entry in the list (if any). */
558 	if (cgi->LIST_Count(productionItemList) > 0) {
559 		if (produceCategory == FILTER_AIRCRAFT) {
560 			const aircraft_t* aircraft = (const aircraft_t*)cgi->LIST_GetByIdx(productionItemList, 0);
561 			PR_SetData(&selectedData, PRODUCTION_TYPE_AIRCRAFT, aircraft);
562 		} else if (produceCategory == FILTER_DISASSEMBLY) {
563 			const storedUFO_t* storedUFO = (const storedUFO_t*)cgi->LIST_GetByIdx(productionItemList, 0);
564 			PR_SetData(&selectedData, PRODUCTION_TYPE_DISASSEMBLY, storedUFO);
565 		} else {
566 			const objDef_t* item = (const objDef_t*)cgi->LIST_GetByIdx(productionItemList, 0);
567 			PR_SetData(&selectedData, PRODUCTION_TYPE_ITEM, item);
568 		}
569 	}
570 	/* update selection index if first entry of actual list was chosen */
571 	if (!selectedProduction) {
572 		const production_queue_t* prod = PR_GetProductionForBase(base);
573 		cgi->UI_ExecuteConfunc("prod_selectline %i", prod->numItems + QUEUE_SPACERS);
574 	}
575 
576 	/* Update displayed info about selected entry (if any). */
577 	PR_ProductionInfo(base);
578 }
579 
580 /**
581  * @brief Will fill the list of producible items.
582  * @note Some of Production Menu related cvars are being set here.
583  */
PR_ProductionList_f(void)584 static void PR_ProductionList_f (void)
585 {
586 	base_t* base = B_GetCurrentSelectedBase();
587 	/* can be called from everywhere without a started game */
588 	if (!base)
589 		return;
590 
591 	cgi->Cvar_SetValue("mn_production_basecap", CAP_GetMax(base, CAP_WORKSPACE));
592 
593 	/* Set amount of workers - all/ready to work (determined by base capacity. */
594 	PR_UpdateProductionCap(base);
595 
596 	cgi->Cvar_Set("mn_production_workers", "%i/%i",
597 			CAP_GetCurrent(base, CAP_WORKSPACE), E_CountHired(base, EMPL_WORKER));
598 
599 	cgi->Cvar_Set("mn_production_storage", "%i/%i",
600 			CAP_GetCurrent(base, CAP_ITEMS), CAP_GetMax(base, CAP_ITEMS));
601 
602 	PR_ClearSelected();
603 }
604 
605 /**
606  * @brief Increases the production amount by given parameter.
607  */
PR_ProductionIncrease_f(void)608 static void PR_ProductionIncrease_f (void)
609 {
610 	production_t* prod;
611 	base_t* base = B_GetCurrentSelectedBase();
612 	technology_t* tech = nullptr;
613 	int amount = 1;
614 	int producibleAmount;
615 
616 	if (!base)
617 		return;
618 
619 	if (!PR_IsDataValid(&selectedData))
620 		return;
621 
622 	if (cgi->Cmd_Argc() == 2)
623 		amount = atoi(cgi->Cmd_Argv(1));
624 
625 	if (selectedProduction) {
626 		prod = selectedProduction;
627 
628 		/* We can disassembly UFOs only one-by-one. */
629 		if (PR_IsDisassembly(prod))
630 			return;
631 
632 		if (PR_IsAircraft(prod)) {
633 			/* Don't allow to queue more aircraft if there is no free space. */
634 			if (CAP_GetFreeCapacity(base, AIR_GetCapacityByAircraftWeight(prod->data.data.aircraft)) <= 0) {
635 				CP_Popup(_("Hangars not ready"), _("You cannot queue aircraft.\nNo free space in hangars.\n"));
636 				cgi->Cvar_SetValue("mn_production_amount", prod->amount);
637 				return;
638 			}
639 		}
640 
641 		/* amount limit per one production */
642 		if (prod->amount + amount > MAX_PRODUCTION_AMOUNT) {
643 			amount = std::max(0, MAX_PRODUCTION_AMOUNT - prod->amount);
644 		}
645 		if (amount == 0) {
646 			cgi->Cvar_SetValue("mn_production_amount", prod->amount);
647 			return;
648 		}
649 
650 		tech = PR_GetTech(&prod->data);
651 		assert(tech);
652 
653 		producibleAmount = PR_RequirementsMet(amount, &tech->requireForProduction, base);
654 		if (producibleAmount == 0) {
655 			CP_Popup(_("Not enough materials"), _("You don't have the materials needed for producing more of this item.\n"));
656 			cgi->Cvar_SetValue("mn_production_amount", prod->amount);
657 			return;
658 		} else if (amount != producibleAmount) {
659 			CP_Popup(_("Not enough material!"), _("You don't have enough material to produce all (%i) additional items. Only %i could be added."), amount, producibleAmount);
660 		}
661 
662 		PR_IncreaseProduction(prod, producibleAmount);
663 		cgi->Cvar_SetValue("mn_production_amount", prod->amount);
664 	} else {
665 		const char* name = nullptr;
666 
667 		tech = PR_GetTech(&selectedData);
668 		name = PR_GetName(&selectedData);
669 
670 		producibleAmount = PR_RequirementsMet(amount, &tech->requireForProduction, base);
671 		if (producibleAmount == 0) {
672 			CP_Popup(_("Not enough materials"), _("You don't have the materials needed for producing this item.\n"));
673 			return;
674 		} else if (amount != producibleAmount) {
675 			CP_Popup(_("Not enough material!"), _("You don't have enough material to produce all (%i) items. Production will continue with a reduced (%i) number."), amount, producibleAmount);
676 		}
677 		/** @todo
678 		 *  -) need to popup something like: "You need the following items in order to produce more of ITEM:   x of ITEM, x of ITEM, etc..."
679 		 *     This info should also be displayed in the item-info.
680 		 *  -) can can (if possible) change the 'amount' to a vlalue that _can_ be produced (i.e. the maximum amount possible).*/
681 
682 		if (PR_IsAircraftData(&selectedData) && CAP_GetFreeCapacity(base, AIR_GetCapacityByAircraftWeight(selectedData.data.aircraft)) <= 0) {
683 			CP_Popup(_("Hangars not ready"), _("You cannot queue aircraft.\nNo free space in hangars.\n"));
684 			return;
685 		}
686 
687 		/* add production */
688 		prod = PR_QueueNew(base, &selectedData, producibleAmount);
689 
690 		/** @todo this popup hides any previous popup, like popup created in PR_QueueNew */
691 		if (!prod)
692 			return;
693 
694 		/* Now we select the item we just created. */
695 		selectedProduction = prod;
696 		cgi->UI_ExecuteConfunc("prod_selectline %i", selectedProduction->idx);
697 
698 		/* messages */
699 		Com_sprintf(cp_messageBuffer, sizeof(cp_messageBuffer), _("Work begun on %s"), _(name));
700 		MSO_CheckAddNewMessage(NT_PRODUCTION_STARTED, _("Production started"), cp_messageBuffer, MSG_PRODUCTION, tech);
701 	}
702 
703 	PR_ProductionInfo(base);
704 	PR_UpdateProductionList(base);
705 }
706 
707 /**
708  * @brief Stops the current running production
709  */
PR_ProductionStop_f(void)710 static void PR_ProductionStop_f (void)
711 {
712 	production_queue_t* queue;
713 	base_t* base = B_GetCurrentSelectedBase();
714 	int prodIDX;
715 
716 	if (!base || !selectedProduction)
717 		return;
718 
719 	prodIDX = selectedProduction->idx;
720 	queue = PR_GetProductionForBase(base);
721 
722 	PR_QueueDelete(base, queue, prodIDX);
723 
724 	if (queue->numItems == 0) {
725 		selectedProduction = nullptr;
726 		cgi->UI_ExecuteConfunc("prod_selectline -1");
727 	} else if (prodIDX >= queue->numItems) {
728 		selectedProduction = &queue->items[queue->numItems - 1];
729 		cgi->UI_ExecuteConfunc("prod_selectline %i", prodIDX);
730 	}
731 
732 	PR_ProductionInfo(base);
733 	PR_UpdateProductionList(base);
734 }
735 
736 /**
737  * @brief Decrease the production amount by given parameter
738  */
PR_ProductionDecrease_f(void)739 static void PR_ProductionDecrease_f (void)
740 {
741 	int amount = 1;
742 	const base_t* base = B_GetCurrentSelectedBase();
743 	production_t* prod = selectedProduction;
744 
745 	if (cgi->Cmd_Argc() == 2)
746 		amount = atoi(cgi->Cmd_Argv(1));
747 
748 	if (!prod)
749 		return;
750 
751 	if (prod->amount <= amount) {
752 		PR_ProductionStop_f();
753 		return;
754 	}
755 
756 	/** @todo add (confirmaton) popup in case storage cannot take all the items we add back to it */
757 	PR_DecreaseProduction(prod, amount);
758 
759 	if (base) {
760 		PR_ProductionInfo(base);
761 		PR_UpdateProductionList(base);
762 	}
763 }
764 
765 /**
766  * @brief Change the production amount by given diff.
767  */
PR_ProductionChange_f(void)768 static void PR_ProductionChange_f (void)
769 {
770 	int amount;
771 
772 	if (!selectedProduction)
773 		return;
774 
775 	if (!PR_IsDataValid(&selectedData))
776 		return;
777 
778 	if (cgi->Cmd_Argc() != 2) {
779 		Com_Printf("Usage: %s <diff> : change the production amount\n", cgi->Cmd_Argv(0));
780 		return;
781 	}
782 
783 	amount = atoi(cgi->Cmd_Argv(1));
784 	if (amount > 0) {
785 		cgi->Cbuf_AddText("prod_inc %i\n", amount);
786 	} else {
787 		cgi->Cbuf_AddText("prod_dec %i\n", -amount);
788 	}
789 }
790 
791 /**
792  * @brief shift the current production up the list
793  */
PR_ProductionUp_f(void)794 static void PR_ProductionUp_f (void)
795 {
796 	production_queue_t* queue;
797 	base_t* base = B_GetCurrentSelectedBase();
798 
799 	if (!base || !selectedProduction)
800 		return;
801 
802 	/* first position already */
803 	if (selectedProduction->idx == 0)
804 		return;
805 
806 	queue = PR_GetProductionForBase(base);
807 	PR_QueueMove(queue, selectedProduction->idx, -1);
808 
809 	selectedProduction = &queue->items[selectedProduction->idx - 1];
810 	cgi->UI_ExecuteConfunc("prod_selectline %i", selectedProduction->idx);
811 	PR_UpdateProductionList(base);
812 }
813 
814 /**
815  * @brief shift the current production down the list
816  */
PR_ProductionDown_f(void)817 static void PR_ProductionDown_f (void)
818 {
819 	production_queue_t* queue;
820 	base_t* base = B_GetCurrentSelectedBase();
821 
822 	if (!base || !selectedProduction)
823 		return;
824 
825 	queue = PR_GetProductionForBase(base);
826 
827 	if (selectedProduction->idx >= queue->numItems - 1)
828 		return;
829 
830 	PR_QueueMove(queue, selectedProduction->idx, 1);
831 
832 	selectedProduction = &queue->items[selectedProduction->idx + 1];
833 	cgi->UI_ExecuteConfunc("prod_selectline %i", selectedProduction->idx);
834 	PR_UpdateProductionList(base);
835 }
836 
PR_InitCallbacks(void)837 void PR_InitCallbacks (void)
838 {
839 	cgi->Cmd_AddCommand("prod_init", PR_ProductionList_f, nullptr);
840 	cgi->Cmd_AddCommand("prod_type", PR_ProductionType_f, nullptr);
841 	cgi->Cmd_AddCommand("prod_up", PR_ProductionUp_f, "Move production item up in the queue");
842 	cgi->Cmd_AddCommand("prod_down", PR_ProductionDown_f, "Move production item down in the queue");
843 	cgi->Cmd_AddCommand("prod_change", PR_ProductionChange_f, "Change production amount");
844 	cgi->Cmd_AddCommand("prod_inc", PR_ProductionIncrease_f, "Increase production amount");
845 	cgi->Cmd_AddCommand("prod_dec", PR_ProductionDecrease_f, "Decrease production amount");
846 	cgi->Cmd_AddCommand("prod_stop", PR_ProductionStop_f, "Stop production");
847 	cgi->Cmd_AddCommand("prodlist_rclick", PR_ProductionListRightClick_f, nullptr);
848 	cgi->Cmd_AddCommand("prodlist_click", PR_ProductionListClick_f, nullptr);
849 }
850 
PR_ShutdownCallbacks(void)851 void PR_ShutdownCallbacks (void)
852 {
853 	cgi->Cmd_RemoveCommand("prod_init");
854 	cgi->Cmd_RemoveCommand("prod_type");
855 	cgi->Cmd_RemoveCommand("prod_up");
856 	cgi->Cmd_RemoveCommand("prod_down");
857 	cgi->Cmd_RemoveCommand("prod_change");
858 	cgi->Cmd_RemoveCommand("prod_inc");
859 	cgi->Cmd_RemoveCommand("prod_dec");
860 	cgi->Cmd_RemoveCommand("prod_stop");
861 	cgi->Cmd_RemoveCommand("prodlist_rclick");
862 	cgi->Cmd_RemoveCommand("prodlist_click");
863 }
864