1 /**
2  * @file
3  */
4 
5 /*
6 Copyright (C) 2002-2013 UFO: Alien Invasion.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 */
23 
24 #include "../../cl_shared.h"
25 #include "../../cl_inventory.h"
26 #include "../../ui/ui_dataids.h"
27 #include "cp_campaign.h"
28 #include "cp_market.h"
29 #include "cp_market_callbacks.h"
30 #include "cp_popup.h"
31 
32 /**
33  * @brief Prints general information about aircraft for Buy/Sell menu.
34  * @param[in] aircraftTemplate Aircraft type.
35  * @sa UP_AircraftDescription
36  * @sa UP_AircraftItemDescription
37  */
BS_MarketAircraftDescription(const aircraft_t * aircraftTemplate)38 static void BS_MarketAircraftDescription (const aircraft_t* aircraftTemplate)
39 {
40 	const technology_t* tech;
41 
42 	/* Break if no aircraft was given or if  it's no sample-aircraft (i.e. template). */
43 	if (!aircraftTemplate || aircraftTemplate != aircraftTemplate->tpl)
44 		return;
45 
46 	tech = aircraftTemplate->tech;
47 	assert(tech);
48 	UP_AircraftDescription(tech);
49 	cgi->Cvar_Set("mn_aircraftname", "%s", _(aircraftTemplate->name));
50 	cgi->Cvar_Set("mn_item", "%s", aircraftTemplate->id);
51 }
52 
53 /**
54  * @brief Opens the UFOpedia for the current selected item/aircraft/ugv.
55  * @note called by market_openpedia
56  */
BS_MarketInfoClick_f(void)57 static void BS_MarketInfoClick_f (void)
58 {
59 	const char* item = cgi->Cvar_GetString("mn_item");
60 	const technology_t* tech = RS_GetTechByProvided(item);
61 
62 	if (tech)
63 		UP_OpenWith(tech->id);
64 }
65 
66 /**
67  * @brief Sets/unsets or flips the autosell property of an item on the market
68  */
BS_SetAutosell_f(void)69 static void BS_SetAutosell_f (void)
70 {
71 	const objDef_t* od;
72 	const technology_t* tech;
73 
74 	if (cgi->Cmd_Argc() < 2) {
75 		Com_Printf("Usage: %s <item-id> [0|1]\nWhere second parameter is the state (off/on), if omitted the autosell property will be flipped.\n",
76 				cgi->Cmd_Argv(0));
77 		return;
78 	}
79 	/* aircraft check */
80 	if (AIR_GetAircraftSilent(cgi->Cmd_Argv(1)) != nullptr) {
81 		Com_Printf("Aircraft can't be autosold!\n");
82 		return;
83 	}
84 	/* items */
85 	od = INVSH_GetItemByID(cgi->Cmd_Argv(1));
86 	if (!od) {
87 		/* no printf, INVSH_GetItemByID gave warning already */
88 		return;
89 	}
90 	if (od->isVirtual) {
91 		Com_Printf("Item %s is virtual, can't be autosold!\n", od->id);
92 		return;
93 	}
94 	if (od->notOnMarket) {
95 		Com_Printf("Item %s is not on market, can't be autosold!\n", od->id);
96 		return;
97 	}
98 	tech = RS_GetTechForItem(od);
99 	/* Don't allow to enable autosell for items not researched. */
100 	if (!RS_IsResearched_ptr(tech)) {
101 		Com_Printf("Item %s is not researched, can't be autosold!\n", od->id);
102 		return;
103 	}
104 	if (cgi->Cmd_Argc() >= 3)
105 		ccs.eMarket.autosell[od->idx] = atoi(cgi->Cmd_Argv(2));
106 	else
107 		ccs.eMarket.autosell[od->idx] = ! ccs.eMarket.autosell[od->idx];
108 }
109 
110 /**
111  * @brief Buy/Sell item/aircraft/ugv on the market
112  */
BS_Buy_f(void)113 static void BS_Buy_f (void)
114 {
115 	const char* itemid;
116 	int count;
117 	base_t* base = B_GetCurrentSelectedBase();
118 	const aircraft_t* aircraft;
119 	const ugv_t* ugv;
120 	const objDef_t* od;
121 
122 	if (cgi->Cmd_Argc() < 2) {
123 		Com_Printf("Usage: %s <item-id> <count> [base-idx] \nNegative count means selling. If base index is omitted buys on the currently selected base.\n",
124 				cgi->Cmd_Argv(0));
125 		return;
126 	}
127 
128 	itemid = cgi->Cmd_Argv(1);
129 	count = atoi(cgi->Cmd_Argv(2));
130 
131 	if (cgi->Cmd_Argc() >= 4)
132 		base = B_GetFoundedBaseByIDX(atoi(cgi->Cmd_Argv(3)));
133 
134 	if (char const* const rest = Q_strstart(itemid, "aircraft-")) {
135 		/* aircraft sell - with aircraft golbal idx */
136 		int idx = atoi(rest);
137 		aircraft_t* aircraft = AIR_AircraftGetFromIDX(idx);
138 
139 		if (!aircraft) {
140 			Com_Printf("Invalid aircraft index!\n");
141 			return;
142 		}
143 		AIR_RemoveEmployees(*aircraft);
144 		BS_SellAircraft(aircraft);
145 		return;
146 	}
147 
148 	if (char const* const rest = Q_strstart(itemid, "ugv-")) {
149 		/* ugv sell - with unique character number index */
150 		int ucn = atoi(rest);
151 		Employee* robot = E_GetEmployeeByTypeFromChrUCN(EMPL_ROBOT, ucn);
152 
153 		if (!robot) {
154 			Com_Printf("Invalid UCN for UGV!\n");
155 			return;
156 		}
157 
158 		BS_SellUGV(robot);
159 		return;
160 	}
161 
162 	if (!base) {
163 		Com_Printf("No/invalid base selected.\n");
164 		return;
165 	}
166 
167 	aircraft = AIR_GetAircraftSilent(itemid);
168 	if (aircraft) {
169 		if (!B_GetBuildingStatus(base, B_COMMAND)) {
170 			CP_Popup(_("Note"), _("No Command Centre in this base.\nHangars are not functional.\n"));
171 			return;
172 		}
173 		/* We cannot buy aircraft if there is no power in our base. */
174 		if (!B_GetBuildingStatus(base, B_POWER)) {
175 			CP_Popup(_("Note"), _("No power supplies in this base.\nHangars are not functional."));
176 			return;
177 		}
178 		/* We cannot buy aircraft without any hangar. */
179 		if (!AIR_AircraftAllowed(base)) {
180 			CP_Popup(_("Note"), _("Build a hangar first."));
181 			return;
182 		}
183 		/* Check free space in hangars. */
184 		if (CAP_GetFreeCapacity(base, AIR_GetCapacityByAircraftWeight(aircraft)) <= 0) {
185 			CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough space in hangars.\n"));
186 			return;
187 		}
188 
189 		if (ccs.credits < BS_GetAircraftBuyingPrice(aircraft)) {
190 			CP_Popup(_("Notice"), _("You cannot buy this aircraft.\nNot enough credits.\n"));
191 			return;
192 		}
193 
194 		BS_BuyAircraft(aircraft, base);
195 		return;
196 	}
197 
198 	ugv = cgi->Com_GetUGVByIDSilent(itemid);
199 	if (ugv) {
200 		const objDef_t* ugvWeapon = INVSH_GetItemByID(ugv->weapon);
201 		if (!ugvWeapon)
202 			cgi->Com_Error(ERR_DROP, "BS_BuyItem_f: Could not get weapon '%s' for ugv/tank '%s'.", ugv->weapon, ugv->id);
203 
204 		if (E_CountUnhiredRobotsByType(ugv) < 1)
205 			return;
206 		if (ccs.eMarket.numItems[ugvWeapon->idx] < 1)
207 			return;
208 
209 		if (ccs.credits < ugv->price) {
210 			CP_Popup(_("Not enough money"), _("You cannot buy this item as you don't have enough credits."));
211 			return;
212 		}
213 
214 		if (CAP_GetFreeCapacity(base, CAP_ITEMS) < UGV_SIZE + ugvWeapon->size) {
215 			CP_Popup(_("Not enough storage space"), _("You cannot buy this item.\nNot enough space in storage.\nBuild more storage facilities."));
216 			return;
217 		}
218 
219 		BS_BuyUGV(ugv, base);
220 		return;
221 	}
222 
223 	if (count == 0) {
224 		Com_Printf("Invalid number of items to buy/sell: %s\n", cgi->Cmd_Argv(2));
225 		return;
226 	}
227 
228 	/* item */
229 	od = INVSH_GetItemByID(cgi->Cmd_Argv(1));
230 	if (od) {
231 		if (!BS_IsOnMarket(od))
232 			return;
233 
234 		if (count > 0) {
235 			/* buy */
236 			const int price = BS_GetItemBuyingPrice(od);
237 			count = std::min(count, BS_GetItemOnMarket(od));
238 
239 			/* no items available on market */
240 			if (count <= 0)
241 				return;
242 
243 			if (price <= 0) {
244 				Com_Printf("Item on market with invalid buying price: %s (%d)\n", od->id, BS_GetItemBuyingPrice(od));
245 				return;
246 			}
247 			/** @todo warn if player can buy less item due to available credits? */
248 			count = std::min(count, ccs.credits / price);
249 			/* not enough money for a single item */
250 			if (count <= 0) {
251 				CP_Popup(_("Not enough money"), _("You cannot buy this item as you don't have enough credits."));
252 				return;
253 			}
254 
255 			if (od->size <= 0) {
256 				Com_Printf("Item on market with invalid size: %s (%d)\n", od->id, od->size);
257 				return;
258 			}
259 			count = std::min(count, CAP_GetFreeCapacity(base, CAP_ITEMS) / od->size);
260 			if (count <= 0) {
261 				CP_Popup(_("Not enough storage space"), _("You cannot buy this item.\nNot enough space in storage.\nBuild more storage facilities."));
262 				return;
263 			}
264 
265 			BS_BuyItem(od, base, count);
266 		} else {
267 			/* sell */
268 			count = std::min(-1 * count, B_ItemInBase(od, base));
269 			/* no items in storage */
270 			if (count <= 0)
271 				return;
272 			BS_SellItem(od, base, count);
273 		}
274 		return;
275 	}
276 	Com_Printf("Invalid item ID\n");
277 }
278 
279 /**
280  * @brief Show informations about item/aircaft/ugv in the market
281  */
BS_ShowInfo_f(void)282 static void BS_ShowInfo_f (void)
283 {
284 	const char* itemid;
285 	const aircraft_t* aircraft;
286 	const ugv_t* ugv;
287 	const objDef_t* od;
288 
289 	if (cgi->Cmd_Argc() < 2) {
290 		Com_Printf("Usage: %s <item-id>\n", cgi->Cmd_Argv(0));
291 		return;
292 	}
293 
294 	itemid = cgi->Cmd_Argv(1);
295 
296 	if (char const* const rest = Q_strstart(itemid, "aircraft-")) {
297 		/* PHALANX aircraft - with aircraft golbal idx */
298 		int idx = atoi(rest);
299 		aircraft = AIR_AircraftGetFromIDX(idx);
300 
301 		if (!aircraft) {
302 			Com_Printf("Invalid aircraft index!\n");
303 			return;
304 		}
305 		/** @todo show specialized info about PHALANX aircraft */
306 		BS_MarketAircraftDescription(aircraft->tpl);
307 		return;
308 	}
309 
310 	if (char const* const rest = Q_strstart(itemid, "ugv-")) {
311 		/* PHALANX ugv - with unique character number index */
312 		int ucn = atoi(rest);
313 		Employee* robot = E_GetEmployeeByTypeFromChrUCN(EMPL_ROBOT, ucn);
314 
315 		if (!robot) {
316 			Com_Printf("Invalid UCN for UGV!\n");
317 			return;
318 		}
319 
320 		/** @todo show specialized info about PHLANX UGVs */
321 		UP_UGVDescription(robot->getUGV());
322 		return;
323 	}
324 
325 	aircraft = AIR_GetAircraftSilent(itemid);
326 	if (aircraft) {
327 		BS_MarketAircraftDescription(aircraft->tpl);
328 		return;
329 	}
330 
331 	ugv = cgi->Com_GetUGVByIDSilent(itemid);
332 	if (ugv) {
333 		UP_UGVDescription(ugv);
334 		return;
335 	}
336 
337 	/* item */
338 	od = INVSH_GetItemByID(cgi->Cmd_Argv(1));
339 	if (od) {
340 		if (!BS_IsOnMarket(od))
341 			return;
342 
343 		if (od->craftitem.type != MAX_ACITEMS)
344 			UP_AircraftItemDescription(od);
345 		else
346 			cgi->INV_ItemDescription(od);
347 		return;
348 	}
349 	Com_Printf("Invalid item ID\n");
350 }
351 
352 /**
353  * @brief Fill market item list
354  */
BS_FillMarket_f(void)355 static void BS_FillMarket_f (void)
356 {
357 	const base_t* base = B_GetCurrentSelectedBase();
358 	itemFilterTypes_t type;
359 
360 	if (cgi->Cmd_Argc() < 2) {
361 		Com_Printf("Usage: %s <category>\n", cgi->Cmd_Argv(0));
362 		return;
363 	}
364 	if (cgi->Cmd_Argc() >= 3)
365 		base = B_GetFoundedBaseByIDX(atoi(cgi->Cmd_Argv(2)));
366 	if (!base) {
367 		Com_Printf("No/invalid base selected.\n");
368 		return;
369 	}
370 
371 	type = INV_GetFilterTypeID(cgi->Cmd_Argv(1));
372 	cgi->UI_ExecuteConfunc("ui_market_clear");
373 	switch (type) {
374 	case FILTER_UGVITEM:
375 		/* show own UGV */
376 		E_Foreach(EMPL_ROBOT, robot) {
377 			const ugv_t* ugv = robot->getUGV();
378 			const technology_t* tech = RS_GetTechByProvided(ugv->id);
379 
380 			if (!robot->isHiredInBase(base))
381 				continue;
382 
383 			cgi->UI_ExecuteConfunc("ui_market_add \"ugv-%d\" \"%s\" 1 0 0 %d - \"%s\"", robot->chr.ucn, _(tech->name), ugv->price, robot->isAwayFromBase() ? _("UGV is away from home") : "-");
384 		}
385 		/* show buyable UGV */
386 		for (int i = 0; i < cgi->csi->numUGV; i++) {
387 			const ugv_t* ugv = &cgi->csi->ugvs[i];
388 			const technology_t* tech = RS_GetTechByProvided(ugv->id);
389 			const objDef_t* ugvWeapon = INVSH_GetItemByID(ugv->weapon);
390 			const int buyable = std::min(E_CountUnhiredRobotsByType(ugv), BS_GetItemOnMarket(ugvWeapon));
391 
392 			assert(tech);
393 			if (!RS_IsResearched_ptr(tech))
394 				continue;
395 			if (buyable <= 0)
396 				continue;
397 
398 			cgi->UI_ExecuteConfunc("ui_market_add %s \"%s\" 0 %d %d %d - -", ugv->id, _(tech->name), buyable, ugv->price, ugv->price);
399 		}
400 		/* show (UGV) items */
401 	case FILTER_S_PRIMARY:
402 	case FILTER_S_SECONDARY:
403 	case FILTER_S_HEAVY:
404 	case FILTER_S_IMPLANT:
405 	case FILTER_S_MISC:
406 	case FILTER_S_ARMOUR:
407 	case FILTER_DUMMY:
408 	case FILTER_CRAFTITEM:
409 	case MAX_FILTERTYPES: {
410 		for (int i = 0; i < cgi->csi->numODs; i++) {
411 			const objDef_t* od = &cgi->csi->ods[i];
412 			const technology_t* tech = RS_GetTechForItem(od);
413 
414 			if (!BS_IsOnMarket(od))
415 				continue;
416 			if (B_ItemInBase(od, base) + BS_GetItemOnMarket(od) <= 0)
417 				continue;
418 			if (type != MAX_FILTERTYPES && !INV_ItemMatchesFilter(od, type))
419 				continue;
420 			cgi->UI_ExecuteConfunc("ui_market_add %s \"%s\" %d %d %d %d %s -", od->id, _(od->name), B_ItemInBase(od, base), BS_GetItemOnMarket(od), BS_GetItemBuyingPrice(od), BS_GetItemSellingPrice(od), RS_IsResearched_ptr(tech) ? va("%d", ccs.eMarket.autosell[i]) : "-");
421 		}
422 		break;
423 	}
424 	case FILTER_AIRCRAFT: {
425 		AIR_ForeachFromBase(aircraft, base) {
426 			cgi->UI_ExecuteConfunc("ui_market_add \"aircraft-%d\" \"%s\" 1 0 0 %d - \"%s\"", aircraft->idx, aircraft->name, BS_GetAircraftSellingPrice(aircraft), AIR_IsAircraftInBase(aircraft) ? "-" : _("Aircraft is away from home"));
427 		}
428 		for (int i = 0; i < ccs.numAircraftTemplates; i++) {
429 			const aircraft_t* aircraft = &ccs.aircraftTemplates[i];
430 			if (!BS_AircraftIsOnMarket(aircraft))
431 				continue;
432 			if (!RS_IsResearched_ptr(aircraft->tech))
433 				continue;
434 			cgi->UI_ExecuteConfunc("ui_market_add \"%s\" \"%s\" 0 %d %d %d - -", aircraft->id, _(aircraft->tech->name), BS_GetAircraftOnMarket(aircraft), BS_GetAircraftBuyingPrice(aircraft), BS_GetAircraftSellingPrice(aircraft));
435 		}
436 		break;
437 	}
438 	default:
439 		break;
440 	}
441 	/* update capacity counters */
442 	cgi->UI_ExecuteConfunc("ui_market_update_caps %d %d %d %d %d %d", CAP_GetFreeCapacity(base, CAP_ITEMS), CAP_GetMax(base, CAP_ITEMS),
443 		CAP_GetFreeCapacity(base, CAP_AIRCRAFT_SMALL), CAP_GetMax(base, CAP_AIRCRAFT_SMALL),
444 		CAP_GetFreeCapacity(base, CAP_AIRCRAFT_BIG), CAP_GetMax(base, CAP_AIRCRAFT_BIG));
445 }
446 
447 /**
448  * @brief Function registers the callbacks of the maket UI and do initializations
449  */
BS_InitCallbacks(void)450 void BS_InitCallbacks(void)
451 {
452 	cgi->Cmd_AddCommand("market_openpedia", BS_MarketInfoClick_f, "Open UFOPedia entry for selected item");
453 
454 	cgi->Cmd_AddCommand("ui_market_setautosell", BS_SetAutosell_f, "Sets/unsets or flips the autosell property of an item on the market");
455 	cgi->Cmd_AddCommand("ui_market_buy", BS_Buy_f, "Buy/Sell item/aircraft/ugv on the market");
456 	cgi->Cmd_AddCommand("ui_market_showinfo", BS_ShowInfo_f, "Show informations about item/aircaft/ugv in the market");
457 	cgi->Cmd_AddCommand("ui_market_fill", BS_FillMarket_f, "Fill market item list");
458 }
459 
460 /**
461  * @brief Function unregisters the callbacks of the maket UI
462  */
BS_ShutdownCallbacks(void)463 void BS_ShutdownCallbacks(void)
464 {
465 	cgi->Cmd_RemoveCommand("ui_market_fill");
466 	cgi->Cmd_RemoveCommand("ui_market_showinfo");
467 	cgi->Cmd_RemoveCommand("ui_market_buy");
468 	cgi->Cmd_RemoveCommand("ui_market_setautosell");
469 
470 	cgi->Cmd_RemoveCommand("market_openpedia");
471 }
472