1 /**
2  * @file
3  * @brief Single player market stuff.
4  * @note Buy/Sell functions prefix: BS_
5  */
6 
7 /*
8 Copyright (C) 2002-2013 UFO: Alien Invasion.
9 
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 
19 See the GNU General Public License for more details.
20 
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24 */
25 
26 #include "../../cl_shared.h"
27 #include "cp_campaign.h"
28 #include "cp_market.h"
29 #include "cp_popup.h"
30 #include "save/save_market.h"
31 
32 #define BS_GetMarket() (&ccs.eMarket)
33 
34 /**
35  * @brief Check if an item is on market
36  * @param[in] item Pointer to the item to check
37  * @note this function doesn't check if the item is available on market (buyable > 0)
38  */
BS_IsOnMarket(const objDef_t * item)39 bool BS_IsOnMarket (const objDef_t* item)
40 {
41 	assert(item);
42 	return !(item->isVirtual || item->notOnMarket);
43 }
44 
45 /**
46  * @brief Get the number of items of the given type on the market
47  * @param[in] od the item (objDef) to search the market for
48  * @return The amount of items for the given type
49  */
BS_GetItemOnMarket(const objDef_t * od)50 int BS_GetItemOnMarket (const objDef_t* od)
51 {
52 	const market_t* market = BS_GetMarket();
53 	return BS_IsOnMarket(od) ? market->numItems[od->idx] : 0;
54 }
55 
56 /**
57  * @brief Internal function to add items to the market
58  * @param[in] od Object definition (the item itself)
59  * @param[in] amount Non-negative number of items to add
60  */
BS_AddItemToMarket(const objDef_t * od,int amount)61 static void BS_AddItemToMarket (const objDef_t* od, int amount)
62 {
63 	market_t* market = BS_GetMarket();
64 	assert(amount >= 0);
65 	market->numItems[od->idx] += amount;
66 }
67 
68 /**
69  * @brief Internal function to remove items from the market
70  * @param[in] od Object definition (the item itself)
71  * @param[in] amount Non-negative number of items to remove
72  */
BS_RemoveItemFromMarket(const objDef_t * od,int amount)73 static void BS_RemoveItemFromMarket (const objDef_t* od, int amount)
74 {
75 	market_t* market = BS_GetMarket();
76 
77 	assert(amount >= 0);
78 
79 	market->numItems[od->idx] = std::max(market->numItems[od->idx] - amount, 0);
80 }
81 
82 /**
83  * @brief Get the price for an item that you want to sell on the market
84  * @param[in] od The item to sell
85  * @return The price of the item
86  */
BS_GetItemSellingPrice(const objDef_t * od)87 int BS_GetItemSellingPrice (const objDef_t* od)
88 {
89 	const market_t* market = BS_GetMarket();
90 	return market->bidItems[od->idx];
91 }
92 
93 /**
94  * @brief Get the price for an item that you want to buy on the market
95  * @param[in] od The item to buy
96  * @return The price of the item
97  */
BS_GetItemBuyingPrice(const objDef_t * od)98 int BS_GetItemBuyingPrice (const objDef_t* od)
99 {
100 	const market_t* market = BS_GetMarket();
101 	return market->askItems[od->idx];
102 }
103 
104 /**
105  * @brief Checks whether a given aircraft should appear on the market
106  * @param aircraft The aircraft to check
107  * @return @c true if the aircraft should appear on the market
108  */
BS_AircraftIsOnMarket(const aircraft_t * aircraft)109 bool BS_AircraftIsOnMarket (const aircraft_t* aircraft)
110 {
111 	if (aircraft->type == AIRCRAFT_UFO)
112 		return false;
113 	if (aircraft->price == -1)
114 		return false;
115 
116 	return  true;
117 }
118 
119 /**
120  * @brief Get the number of aircraft of the given type on the market
121  * @param[in] aircraft The aircraft to search the market for
122  * @return The amount of aircraft for the given type
123  */
BS_GetAircraftOnMarket(const aircraft_t * aircraft)124 int BS_GetAircraftOnMarket (const aircraft_t* aircraft)
125 {
126 	const humanAircraftType_t type = cgi->Com_DropShipShortNameToID(aircraft->id);
127 	const market_t* market = BS_GetMarket();
128 
129 	return BS_AircraftIsOnMarket(aircraft) ? market->numAircraft[type] : 0;
130 }
131 
132 /**
133  * @brief Internal function to add aircraft to the market
134  * @param[in] aircraft Aircraft template definition
135  * @param[in] amount Non-negative number of aircraft to add
136  */
BS_AddAircraftToMarket(const aircraft_t * aircraft,int amount)137 static void BS_AddAircraftToMarket (const aircraft_t* aircraft, int amount)
138 {
139 	const humanAircraftType_t type = cgi->Com_DropShipShortNameToID(aircraft->id);
140 	market_t* market = BS_GetMarket();
141 	assert(amount >= 0);
142 	assert(aircraft->type != AIRCRAFT_UFO);
143 	market->numAircraft[type] += amount;
144 }
145 
146 /**
147  * @brief Internal function to remove aircraft from the market
148  * @param[in] aircraft Aircraft template definition
149  * @param[in] amount Non-negative number of aircraft to remove
150  */
BS_RemoveAircraftFromMarket(const aircraft_t * aircraft,int amount)151 static void BS_RemoveAircraftFromMarket (const aircraft_t* aircraft, int amount)
152 {
153 	const humanAircraftType_t type = cgi->Com_DropShipShortNameToID(aircraft->id);
154 	market_t* market = BS_GetMarket();
155 
156 	assert(amount >= 0);
157 	assert(aircraft->type != AIRCRAFT_UFO);
158 
159 	market->numAircraft[type] = std::max(market->numAircraft[type] - amount, 0);
160 }
161 
162 /**
163  * @brief Get the price for an aircraft that you want to sell on the market
164  * @param[in] aircraft The aircraft to sell
165  * @return The price of the aircraft
166  */
BS_GetAircraftSellingPrice(const aircraft_t * aircraft)167 int BS_GetAircraftSellingPrice (const aircraft_t* aircraft)
168 {
169 	const humanAircraftType_t type = cgi->Com_DropShipShortNameToID(aircraft->id);
170 	const market_t* market = BS_GetMarket();
171 	int sellPrice = market->bidAircraft[type];
172 
173 	assert(aircraft->type != AIRCRAFT_UFO);
174 
175 	if (aircraft->tpl != aircraft) {
176 		int i;
177 
178 		if (aircraft->stats[AIR_STATS_DAMAGE] > 0)
179 			sellPrice *= (float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE];
180 
181 		if (aircraft->shield.item)
182 			sellPrice += BS_GetItemSellingPrice(aircraft->shield.item);
183 		if (aircraft->shield.ammo)
184 			sellPrice += BS_GetItemSellingPrice(aircraft->shield.ammo);
185 
186 		for (i = 0; i < aircraft->maxWeapons; i++) {
187 			if (aircraft->weapons[i].item)
188 				sellPrice += BS_GetItemSellingPrice(aircraft->weapons[i].item);
189 			if (aircraft->weapons[i].ammo)
190 				sellPrice += BS_GetItemSellingPrice(aircraft->weapons[i].ammo);
191 		}
192 
193 		for (i = 0; i < aircraft->maxElectronics; i++) {
194 			if (aircraft->electronics[i].item)
195 				sellPrice += BS_GetItemSellingPrice(aircraft->electronics[i].item);
196 			if (aircraft->electronics[i].ammo)
197 				sellPrice += BS_GetItemSellingPrice(aircraft->electronics[i].ammo);
198 		}
199 	}
200 	return sellPrice;
201 }
202 
203 /**
204  * @brief Get the price for an aircraft that you want to buy on the market
205  * @param[in] aircraft The aircraft to buy
206  * @return The price of the aircraft
207  */
BS_GetAircraftBuyingPrice(const aircraft_t * aircraft)208 int BS_GetAircraftBuyingPrice (const aircraft_t* aircraft)
209 {
210 	const humanAircraftType_t type = cgi->Com_DropShipShortNameToID(aircraft->id);
211 	const market_t* market = BS_GetMarket();
212 	assert(aircraft->type != AIRCRAFT_UFO);
213 	return market->askAircraft[type];
214 }
215 
216 /**
217  * @brief Update storage, the market, and the player's credits
218  * @note Don't update capacity here because we can sell items directly from aircraft (already removed from storage).
219  */
BS_ProcessCraftItemSale(const objDef_t * craftitem,const int numItems)220 static void BS_ProcessCraftItemSale (const objDef_t* craftitem, const int numItems)
221 {
222 	if (craftitem) {
223 		BS_AddItemToMarket(craftitem, numItems);
224 		CP_UpdateCredits(ccs.credits + BS_GetItemSellingPrice(craftitem) * numItems);
225 	}
226 }
227 
228 /**
229  * @brief Buys an aircraft
230  * @param[in] aircraftTemplate The aircraft template to buy
231  * @param[out] base Base to buy at
232  * @return @c true if the aircraft could get bought, @c false otherwise
233  */
BS_BuyAircraft(const aircraft_t * aircraftTemplate,base_t * base)234 bool BS_BuyAircraft (const aircraft_t* aircraftTemplate, base_t* base)
235 {
236 	if (!base)
237 		cgi->Com_Error(ERR_DROP, "BS_BuyAircraft: No base given.");
238 	if (!aircraftTemplate)
239 		cgi->Com_Error(ERR_DROP, "BS_BuyAircraft: No aircraft template given.");
240 
241 	int price = BS_GetAircraftBuyingPrice(aircraftTemplate);
242 	if (ccs.credits < price)
243 		return false;
244 
245 	/* Hangar capacities are being updated in AIR_NewAircraft().*/
246 	BS_RemoveAircraftFromMarket(aircraftTemplate, 1);
247 	CP_UpdateCredits(ccs.credits - price);
248 	AIR_NewAircraft(base, aircraftTemplate);
249 
250 	return true;
251 }
252 
253 /**
254  * @brief Sells the given aircraft with all the equipment.
255  * @param aircraft The aircraft to sell
256  * @return @c true if the aircraft could get sold, @c false otherwise
257  */
BS_SellAircraft(aircraft_t * aircraft)258 bool BS_SellAircraft (aircraft_t* aircraft)
259 {
260 	int j;
261 
262 	if (AIR_GetTeamSize(aircraft) > 0)
263 		return false;
264 
265 	if (!AIR_IsAircraftInBase(aircraft))
266 		return false;
267 
268 	/* sell off any items which are mounted on it */
269 	for (j = 0; j < aircraft->maxWeapons; j++) {
270 		const aircraftSlot_t* slot = &aircraft->weapons[j];
271 		BS_ProcessCraftItemSale(slot->item, 1);
272 		BS_ProcessCraftItemSale(slot->ammo, 1);
273 	}
274 
275 	BS_ProcessCraftItemSale(aircraft->shield.item, 1);
276 	/* there should be no ammo here, but checking can't hurt */
277 	BS_ProcessCraftItemSale(aircraft->shield.ammo, 1);
278 
279 	for (j = 0; j < aircraft->maxElectronics; j++) {
280 		const aircraftSlot_t* slot = &aircraft->electronics[j];
281 		BS_ProcessCraftItemSale(slot->item, 1);
282 		/* there should be no ammo here, but checking can't hurt */
283 		BS_ProcessCraftItemSale(slot->ammo, 1);
284 	}
285 
286 	/* the capacities are also updated here */
287 	BS_AddAircraftToMarket(aircraft, 1);
288 	CP_UpdateCredits(ccs.credits + BS_GetAircraftSellingPrice(aircraft));
289 	AIR_DeleteAircraft(aircraft);
290 
291 	return true;
292 }
293 
294 /**
295  * @brief Buys the given UGV
296  * @param[in] ugv The ugv template of the UGV to buy
297  * @param[out] base Base to buy at
298  * @return @c true if the ugv could get bought, @c false otherwise
299  * @todo Implement this correctly once we have UGV
300  */
BS_BuyUGV(const ugv_t * ugv,base_t * base)301 bool BS_BuyUGV (const ugv_t* ugv, base_t* base)
302 {
303 	const objDef_t* ugvWeapon;
304 
305 	if (!ugv)
306 		cgi->Com_Error(ERR_DROP, "BS_BuyUGV: Called on nullptr UGV!");
307 	if (!base)
308 		cgi->Com_Error(ERR_DROP, "BS_BuyUGV: Called on nullptr base!");
309 	ugvWeapon = INVSH_GetItemByID(ugv->weapon);
310 	if (!ugvWeapon)
311 		cgi->Com_Error(ERR_DROP, "BS_BuyItem_f: Could not get weapon '%s' for ugv/tank '%s'.", ugv->weapon, ugv->id);
312 
313 	if (ccs.credits < ugv->price)
314 		return false;
315 	if (E_CountUnhiredRobotsByType(ugv) <= 0)
316 		return false;
317 	if (BS_GetItemOnMarket(ugvWeapon) <= 0)
318 		return false;
319 	if (CAP_GetFreeCapacity(base, CAP_ITEMS) < UGV_SIZE + ugvWeapon->size)
320 		return false;
321 	if (!E_HireRobot(base, ugv))
322 		return false;
323 
324 	BS_RemoveItemFromMarket(ugvWeapon, 1);
325 	CP_UpdateCredits(ccs.credits - ugv->price);
326 	B_AddToStorage(base, ugvWeapon, 1);
327 
328 	return true;
329 }
330 
331 /**
332  * @brief Sells the given UGV with all the equipment.
333  * @param robot The employee record of the UGV to sell
334  * @return @c true if the ugv could get sold, @c false otherwise
335  * @todo Implement this correctly once we have UGV
336  */
BS_SellUGV(Employee * robot)337 bool BS_SellUGV (Employee* robot)
338 {
339 	const objDef_t* ugvWeapon;
340 	const ugv_t* ugv;
341 	base_t* base;
342 
343 	if (!robot)
344 		cgi->Com_Error(ERR_DROP, "Selling nullptr UGV!");
345 	if (!robot->getUGV())
346 		cgi->Com_Error(ERR_DROP, "Selling invalid UGV with UCN: %i", robot->chr.ucn);
347 	ugv = robot->getUGV();
348 	base = robot->baseHired;
349 
350 	/* Check if we have a weapon for this ugv in the market to sell it. */
351 	ugvWeapon = INVSH_GetItemByID(ugv->weapon);
352 	if (!ugvWeapon)
353 		cgi->Com_Error(ERR_DROP, "BS_BuyItem_f: Could not get wepaon '%s' for ugv/tank '%s'.", ugv->weapon, ugv->id);
354 
355 	if (!robot->unhire()) {
356 		/** @todo message - Couldn't fire employee. */
357 		Com_DPrintf(DEBUG_CLIENT, "Couldn't sell/fire robot/ugv.\n");
358 		return false;
359 	}
360 
361 	BS_AddItemToMarket(ugvWeapon, 1);
362 	CP_UpdateCredits(ccs.credits + ugv->price);
363 	B_AddToStorage(base, ugvWeapon, -1);
364 
365 	return true;
366 }
367 
368 /**
369  * @brief Buys items from the market
370  * @param[in] od pointer to the item (Object Definition record)
371  * @param[out] base Base to buy at
372  * @param[in ] count Number of items to buy
373  * @return @c true if the ugv could get bought, @c false otherwise
374  */
BS_BuyItem(const objDef_t * od,base_t * base,int count)375 bool BS_BuyItem (const objDef_t* od, base_t* base, int count)
376 {
377 	if (!od)
378 		cgi->Com_Error(ERR_DROP, "BS_BuyItem: Called on nullptr objDef!");
379 	if (!base)
380 		cgi->Com_Error(ERR_DROP, "BS_BuyItem: Called on nullptr base!");
381 
382 	if (count <= 0)
383 		return false;
384 	if (!BS_IsOnMarket(od))
385 		return false;
386 	if (ccs.credits < BS_GetItemBuyingPrice(od) * count)
387 		return false;
388 	if (BS_GetItemOnMarket(od) < count)
389 		return false;
390 	if (CAP_GetFreeCapacity(base, CAP_ITEMS) < od->size * count)
391 		return false;
392 
393 	B_AddToStorage(base, od, count);
394 	BS_RemoveItemFromMarket(od, count);
395 	CP_UpdateCredits(ccs.credits - BS_GetItemBuyingPrice(od) * count);
396 
397 	return true;
398 }
399 
400 /**
401  * @brief Sells items from the market
402  * @param[in] od pointer to the item (Object Definition record)
403  * @param[out] base Base to sell at
404  * @param[in ] count Number of items to sell
405  * @return @c true if the ugv could get sold, @c false otherwise
406  */
BS_SellItem(const objDef_t * od,base_t * base,int count)407 bool BS_SellItem (const objDef_t* od, base_t* base, int count)
408 {
409 	if (!od)
410 		cgi->Com_Error(ERR_DROP, "BS_SellItem: Called on nullptr objDef!");
411 
412 	if (count <= 0)
413 		return false;
414 	if (!BS_IsOnMarket(od))
415 		return false;
416 
417 	if (base) {
418 		if (B_ItemInBase(od, base) < count)
419 			return false;
420 		B_AddToStorage(base, od, -1 * count);
421 	}
422 
423 	BS_AddItemToMarket(od, count);
424 	CP_UpdateCredits(ccs.credits + BS_GetItemSellingPrice(od) * count);
425 
426 	return true;
427 }
428 
429 /**
430  * @brief Save callback for savegames
431  * @param[out] parent XML Node structure, where we write the information to
432  * @sa BS_LoadXML
433  * @sa SAV_GameSaveXML
434  */
BS_SaveXML(xmlNode_t * parent)435 bool BS_SaveXML (xmlNode_t* parent)
436 {
437 	int i;
438 	xmlNode_t* node;
439 	const market_t* market = BS_GetMarket();
440 
441 	/* store market */
442 	node = cgi->XML_AddNode(parent, SAVE_MARKET_MARKET);
443 	for (i = 0; i < cgi->csi->numODs; i++) {
444 		const objDef_t* od = INVSH_GetItemByIDX(i);
445 		if (BS_IsOnMarket(od)) {
446 			xmlNode_t* snode = cgi->XML_AddNode(node, SAVE_MARKET_ITEM);
447 			cgi->XML_AddString(snode, SAVE_MARKET_ID, od->id);
448 			cgi->XML_AddIntValue(snode, SAVE_MARKET_NUM, market->numItems[i]);
449 			cgi->XML_AddIntValue(snode, SAVE_MARKET_BID, market->bidItems[i]);
450 			cgi->XML_AddIntValue(snode, SAVE_MARKET_ASK, market->askItems[i]);
451 			cgi->XML_AddDoubleValue(snode, SAVE_MARKET_EVO, market->currentEvolutionItems[i]);
452 			cgi->XML_AddBoolValue(snode, SAVE_MARKET_AUTOSELL, market->autosell[i]);
453 		}
454 	}
455 	for (i = 0; i < AIRCRAFTTYPE_MAX; i++) {
456 		if (market->bidAircraft[i] > 0 || market->askAircraft[i] > 0) {
457 			xmlNode_t* snode = cgi->XML_AddNode(node, SAVE_MARKET_AIRCRAFT);
458 			const char* shortName = cgi->Com_DropShipTypeToShortName((humanAircraftType_t)i);
459 			cgi->XML_AddString(snode, SAVE_MARKET_ID, shortName);
460 			cgi->XML_AddIntValue(snode, SAVE_MARKET_NUM, market->numAircraft[i]);
461 			cgi->XML_AddIntValue(snode, SAVE_MARKET_BID, market->bidAircraft[i]);
462 			cgi->XML_AddIntValue(snode, SAVE_MARKET_ASK, market->askAircraft[i]);
463 			cgi->XML_AddDoubleValue(snode, SAVE_MARKET_EVO, market->currentEvolutionAircraft[i]);
464 		}
465 	}
466 	return true;
467 }
468 
469 /**
470  * @brief Load callback for savegames
471  * @param[in] parent XML Node structure, where we get the information from
472  * @sa BS_Save
473  * @sa SAV_GameLoad
474  */
BS_LoadXML(xmlNode_t * parent)475 bool BS_LoadXML (xmlNode_t* parent)
476 {
477 	xmlNode_t* node, *snode;
478 	market_t* market = BS_GetMarket();
479 
480 	node = cgi->XML_GetNode(parent, SAVE_MARKET_MARKET);
481 	if (!node)
482 		return false;
483 
484 	for (snode = cgi->XML_GetNode(node, SAVE_MARKET_ITEM); snode; snode = cgi->XML_GetNextNode(snode, node, SAVE_MARKET_ITEM)) {
485 		const char* s = cgi->XML_GetString(snode, SAVE_MARKET_ID);
486 		const objDef_t* od = INVSH_GetItemByID(s);
487 
488 		if (!od) {
489 			Com_Printf("BS_LoadXML: Could not find item '%s'\n", s);
490 			continue;
491 		}
492 
493 		market->numItems[od->idx] = cgi->XML_GetInt(snode, SAVE_MARKET_NUM, 0);
494 		market->bidItems[od->idx] = cgi->XML_GetInt(snode, SAVE_MARKET_BID, 0);
495 		market->askItems[od->idx] = cgi->XML_GetInt(snode, SAVE_MARKET_ASK, 0);
496 		market->currentEvolutionItems[od->idx] = cgi->XML_GetDouble(snode, SAVE_MARKET_EVO, 0.0);
497 		market->autosell[od->idx] = cgi->XML_GetBool(snode, SAVE_MARKET_AUTOSELL, false);
498 	}
499 	for (snode = cgi->XML_GetNode(node, SAVE_MARKET_AIRCRAFT); snode; snode = cgi->XML_GetNextNode(snode, node, SAVE_MARKET_AIRCRAFT)) {
500 		const char* s = cgi->XML_GetString(snode, SAVE_MARKET_ID);
501 		const humanAircraftType_t type = cgi->Com_DropShipShortNameToID(s);
502 
503 		market->numAircraft[type] = cgi->XML_GetInt(snode, SAVE_MARKET_NUM, 0);
504 		market->bidAircraft[type] = cgi->XML_GetInt(snode, SAVE_MARKET_BID, 0);
505 		market->askAircraft[type] = cgi->XML_GetInt(snode, SAVE_MARKET_ASK, 0);
506 		market->currentEvolutionAircraft[type] = cgi->XML_GetDouble(snode, SAVE_MARKET_EVO, 0.0);
507 	}
508 
509 	return true;
510 }
511 
512 /**
513  * @brief sets market prices at start of the game
514  * @sa CP_CampaignInit
515  * @sa B_SetUpFirstBase
516  * @sa BS_Load (Market load function)
517  */
BS_InitMarket(const campaign_t * campaign)518 void BS_InitMarket (const campaign_t* campaign)
519 {
520 	int i;
521 	market_t* market = BS_GetMarket();
522 
523 	for (i = 0; i < cgi->csi->numODs; i++) {
524 		const objDef_t* od = INVSH_GetItemByIDX(i);
525 		if (market->askItems[i] == 0) {
526 			market->askItems[i] = od->price;
527 			market->bidItems[i] = floor(market->askItems[i] * BID_FACTOR);
528 		}
529 
530 		if (campaign->marketDef->numItems[i] <= 0)
531 			continue;
532 
533 		if (RS_IsResearched_ptr(RS_GetTechForItem(od))) {
534 			/* the other relevant values were already set above */
535 			market->numItems[i] = campaign->marketDef->numItems[i];
536 		} else {
537 			Com_Printf("BS_InitMarket: Could not add item %s to the market - not marked as researched in campaign %s\n",
538 					od->id, campaign->id);
539 		}
540 	}
541 
542 	for (i = 0; i < AIRCRAFTTYPE_MAX; i++) {
543 		const char* name = cgi->Com_DropShipTypeToShortName((humanAircraftType_t)i);
544 		const aircraft_t* aircraft = AIR_GetAircraft(name);
545 		if (market->askAircraft[i] == 0) {
546 			market->askAircraft[i] = aircraft->price;
547 			market->bidAircraft[i] = floor(market->askAircraft[i] * BID_FACTOR);
548 		}
549 
550 		if (campaign->marketDef->numAircraft[i] <= 0)
551 			continue;
552 
553 		if (RS_IsResearched_ptr(aircraft->tech)) {
554 			/* the other relevant values were already set above */
555 			market->numAircraft[i] = campaign->marketDef->numAircraft[i];
556 		} else {
557 			Com_Printf("BS_InitMarket: Could not add aircraft %s to the market - not marked as researched in campaign %s\n",
558 					aircraft->id, campaign->id);
559 		}
560 	}
561 }
562 
563 /**
564  * @brief make number of items change every day.
565  * @sa CP_CampaignRun
566  * @sa daily called
567  * @note This function makes items number on market slowly reach the asymptotic number of items defined in equipment.ufo
568  * If an item has just been researched, it's not available on market until RESEARCH_LIMIT_DELAY days is reached.
569  */
CP_CampaignRunMarket(campaign_t * campaign)570 void CP_CampaignRunMarket (campaign_t* campaign)
571 {
572 	int i;
573 	const float TYPICAL_TIME = 10.f;			/**< Number of days to reach the asymptotic number of items */
574 	const int RESEARCH_LIMIT_DELAY = 30;		/**< Numbers of days after end of research to wait in order to have
575 												 * items added on market */
576 	market_t* market = BS_GetMarket();
577 
578 	assert(campaign->marketDef);
579 	assert(campaign->asymptoticMarketDef);
580 
581 	for (i = 0; i < cgi->csi->numODs; i++) {
582 		const objDef_t* od = INVSH_GetItemByIDX(i);
583 		const technology_t* tech = RS_GetTechForItem(od);
584 		int asymptoticNumber;
585 
586 		if (RS_IsResearched_ptr(tech) && (campaign->marketDef->numItems[i] != 0 || ccs.date.day > tech->researchedDate.day + RESEARCH_LIMIT_DELAY)) {
587 			/* if items are researched for more than RESEARCH_LIMIT_DELAY or was on the initial market,
588 			 * there number tend to the value defined in equipment.ufo.
589 			 * This value is the asymptotic value if it is not 0, or initial value else */
590 			asymptoticNumber = campaign->asymptoticMarketDef->numItems[i] ? campaign->asymptoticMarketDef->numItems[i] : campaign->marketDef->numItems[i];
591 		} else {
592 			/* items that have just been researched don't appear on market, but they can disappear */
593 			asymptoticNumber = 0;
594 		}
595 
596 		/* Store the evolution of the market in currentEvolution */
597 		market->currentEvolutionItems[i] += (asymptoticNumber - market->numItems[i]) / TYPICAL_TIME;
598 
599 		/* Check if new items appeared or disappeared on market */
600 		if (fabs(market->currentEvolutionItems[i]) >= 1.0f) {
601 			const int num = (int)(market->currentEvolutionItems[i]);
602 			if (num >= 0)
603 				BS_AddItemToMarket(od, num);
604 			else
605 				BS_RemoveItemFromMarket(od, -num);
606 			market->currentEvolutionItems[i] -= num;
607 		}
608 	}
609 
610 	for (i = 0; i < AIRCRAFTTYPE_MAX; i++) {
611 		const humanAircraftType_t type = (humanAircraftType_t)i;
612 		const char* aircraftID = cgi->Com_DropShipTypeToShortName(type);
613 		const aircraft_t* aircraft = AIR_GetAircraft(aircraftID);
614 		const technology_t* tech = aircraft->tech;
615 		int asymptoticNumber;
616 
617 		if (RS_IsResearched_ptr(tech) && (campaign->marketDef->numAircraft[i] != 0 || ccs.date.day > tech->researchedDate.day + RESEARCH_LIMIT_DELAY)) {
618 			/* if aircraft is researched for more than RESEARCH_LIMIT_DELAY or was on the initial market,
619 			 * there number tend to the value defined in equipment.ufo.
620 			 * This value is the asymptotic value if it is not 0, or initial value else */
621 			asymptoticNumber = campaign->asymptoticMarketDef->numAircraft[i] ? campaign->asymptoticMarketDef->numAircraft[i] : campaign->marketDef->numAircraft[i];
622 		} else {
623 			/* items that have just been researched don't appear on market, but they can disappear */
624 			asymptoticNumber = 0;
625 		}
626 		/* Store the evolution of the market in currentEvolution */
627 		market->currentEvolutionAircraft[i] += (asymptoticNumber - market->numAircraft[i]) / TYPICAL_TIME;
628 
629 		/* Check if new items appeared or disappeared on market */
630 		if (fabs(market->currentEvolutionAircraft[i]) >= 1.0f) {
631 			const int num = (int)(market->currentEvolutionAircraft[i]);
632 			if (num >= 0)
633 				BS_AddAircraftToMarket(aircraft, num);
634 			else
635 				BS_RemoveAircraftFromMarket(aircraft, -num);
636 			market->currentEvolutionAircraft[i] -= num;
637 		}
638 	}
639 }
640 
641 /**
642  * @brief Returns true if you can buy or sell equipment
643  * @param[in] base Pointer to base to check on
644  * @sa B_BaseInit_f
645  */
BS_BuySellAllowed(const base_t * base)646 bool BS_BuySellAllowed (const base_t* base)
647 {
648 	return !B_IsUnderAttack(base) && B_GetBuildingStatus(base, B_STORAGE);
649 }
650