1 /*
2  *
3  *   Copyright (c) 2002, 2003 Johannes Prix
4  *   Copyright (c) 2004-2010 Arthur Huillet
5  *
6  *
7  *  This file is part of Freedroid
8  *
9  *  Freedroid is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  (at your option) any later version.
13  *
14  *  Freedroid 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.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with Freedroid; see the file COPYING. If not, write to the
21  *  Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
22  *  MA  02111-1307  USA
23  *
24  */
25 
26 /**
27  * This file contains all menu functions and their subfunctions
28  */
29 
30 #define _shop_c
31 
32 #include "system.h"
33 
34 #include "defs.h"
35 #include "struct.h"
36 #include "global.h"
37 #include "proto.h"
38 
39 #include "widgets/widgets.h"
40 
41 #define SHOP_ROW_LENGTH 8
42 
43 SDL_Rect ShopItemRowRect;
44 SDL_Rect TuxItemRowRect;
45 
46 /**
47  * At some points in the game, like when at the shop interface or at the
48  * items browser at the console, we wish to show a list of the items
49  * currently in inventory.  This function assembles this list.  It lets
50  * the caller decide on whether to include worn items in the list or not
51  * and it will return the number of items finally filled into that list.
52  */
AssemblePointerListForItemShow(item ** ItemPointerListPointer,int IncludeWornItems)53 int AssemblePointerListForItemShow(item ** ItemPointerListPointer, int IncludeWornItems)
54 {
55 	int i;
56 	item **CurrentItemPointer;
57 	int NumberOfItems = 0;
58 
59 	// First we clean out the new Show_Pointer_List
60 	//
61 	CurrentItemPointer = ItemPointerListPointer;
62 	for (i = 0; i < MAX_ITEMS_IN_INVENTORY; i++) {
63 		*CurrentItemPointer = NULL;
64 		CurrentItemPointer++;
65 	}
66 
67 	// Now we start to fill the Show_Pointer_List with the items
68 	// currently equipped, if that is what is desired by parameters...
69 	//
70 	CurrentItemPointer = ItemPointerListPointer;
71 	if (IncludeWornItems) {
72 		if (Me.weapon_item.type != (-1)) {
73 			*CurrentItemPointer = &(Me.weapon_item);
74 			CurrentItemPointer++;
75 			NumberOfItems++;
76 		}
77 		if (Me.drive_item.type != (-1)) {
78 			*CurrentItemPointer = &(Me.drive_item);
79 			CurrentItemPointer++;
80 			NumberOfItems++;
81 		}
82 		if (Me.armour_item.type != (-1)) {
83 			*CurrentItemPointer = &(Me.armour_item);
84 			CurrentItemPointer++;
85 			NumberOfItems++;
86 		}
87 		if (Me.shield_item.type != (-1)) {
88 			*CurrentItemPointer = &(Me.shield_item);
89 			CurrentItemPointer++;
90 			NumberOfItems++;
91 		}
92 		if (Me.special_item.type != (-1)) {
93 			*CurrentItemPointer = &(Me.special_item);
94 			CurrentItemPointer++;
95 			NumberOfItems++;
96 		}
97 	}
98 	// Now we start to fill the Show_Pointer_List with the items in the
99 	// pure unequipped inventory
100 	//
101 	for (i = 0; i < MAX_ITEMS_IN_INVENTORY; i++) {
102 		if (Me.Inventory[i].type < 0)
103 			continue;
104 		else {
105 			*CurrentItemPointer = &(Me.Inventory[i]);
106 			CurrentItemPointer++;
107 			NumberOfItems++;
108 		}
109 	}
110 
111 	return (NumberOfItems);
112 
113 }
114 
115 /**
116  * Maybe the user has clicked right onto the item overview row.  Then of
117  * course we must find out and return the index of the item clicked on.
118  * If no item was clicked on, then a -1 will be returned as index.
119  */
ClickWasOntoItemRowPosition(int x,int y,int TuxItemRow)120 static int ClickWasOntoItemRowPosition(int x, int y, int TuxItemRow)
121 {
122 	if (TuxItemRow) {
123 		if (y < TuxItemRowRect.y)
124 			return (-1);
125 		if (y > TuxItemRowRect.y + TuxItemRowRect.h)
126 			return (-1);
127 		if (x < TuxItemRowRect.x)
128 			return (-1);
129 		if (x > TuxItemRowRect.x + TuxItemRowRect.w)
130 			return (-1);
131 
132 		// Now at this point we know, that the click really was in the item
133 		// overview row.  Therefore we just need to find out the index and
134 		// can return;
135 		//
136 		return ((x - TuxItemRowRect.x) / (INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640));
137 	} else {
138 		if (y < ShopItemRowRect.y)
139 			return (-1);
140 		if (y > ShopItemRowRect.y + ShopItemRowRect.h)
141 			return (-1);
142 		if (x < ShopItemRowRect.x)
143 			return (-1);
144 		if (x > ShopItemRowRect.x + ShopItemRowRect.w)
145 			return (-1);
146 
147 		// Now at this point we know, that the click really was in the item
148 		// overview row.  Therefore we just need to find out the index and
149 		// can return;
150 		//
151 		return ((x - ShopItemRowRect.x) / (INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640));
152 	}
153 };				// int ClickWasOntoItemRowPosition ( int x , int y , int TuxItemRow )
154 
155 /**
156  * The item row in the shop interface (or wherever we're going to use it)
157  * should display not only the rotating item display but also a row or a
158  * column of the current equipment, so that some better overview is given
159  * as well and the item can be better associated with it's in-game inventory
160  * representation.  This function displays one such representation with
161  * the correct size to fit perfectly into the overview item row.
162  */
ShowRescaledItem(int position,int TuxItemRow,item * ShowItem)163 void ShowRescaledItem(int position, int TuxItemRow, item * ShowItem)
164 {
165 	SDL_Rect TargetRectangle =
166 	    { 0, 0, INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640, INITIAL_BLOCK_HEIGHT * GameConfig.screen_height / 480 };
167 	static struct image equipped_icon;
168 
169 	if (!image_loaded(&equipped_icon)) {
170 		load_image(&equipped_icon, "cursors/mouse_cursor_0003.png", NO_MOD);
171 	}
172 
173 	TuxItemRowRect.x = 55 * GameConfig.screen_width / 640;
174 	TuxItemRowRect.y = 410 * GameConfig.screen_height / 480;
175 	TuxItemRowRect.h = INITIAL_BLOCK_HEIGHT * GameConfig.screen_height / 480;
176 //    TuxItemRowRect . h = 64 ;
177 	TuxItemRowRect.w = INITIAL_BLOCK_WIDTH * SHOP_ROW_LENGTH * GameConfig.screen_width / 640;
178 //    TuxItemRowRect . w = 64 ;
179 
180 	ShopItemRowRect.x = 55 * GameConfig.screen_width / 640;
181 	ShopItemRowRect.y = 10 * GameConfig.screen_height / 480;
182 	ShopItemRowRect.h = INITIAL_BLOCK_HEIGHT * GameConfig.screen_height / 480;
183 	ShopItemRowRect.w = INITIAL_BLOCK_WIDTH * SHOP_ROW_LENGTH * GameConfig.screen_width / 640;
184 //    ShopItemRowRect . h = 64 ;
185 //    ShopItemRowRect . w = 64 ;
186 
187 	if (TuxItemRow == 1) {
188 		TargetRectangle.x = TuxItemRowRect.x + position * INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640;
189 		TargetRectangle.y = TuxItemRowRect.y;
190 	} else if (TuxItemRow == 0) {
191 		TargetRectangle.x = ShopItemRowRect.x + position * INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640;
192 		TargetRectangle.y = ShopItemRowRect.y;
193 	} else {
194 		TargetRectangle.x = ShopItemRowRect.x + position * INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640;
195 		TargetRectangle.y = TuxItemRow;
196 	}
197 
198 	struct image *img = get_item_shop_image(ShowItem->type);
199 	if (img) {
200 		display_image_on_screen(img, TargetRectangle.x, TargetRectangle.y, IMAGE_NO_TRANSFO);
201 	}
202 
203 	if (item_is_currently_equipped(ShowItem)) {
204 		display_image_on_screen(&equipped_icon, TargetRectangle.x + TargetRectangle.w - 24, TargetRectangle.y, IMAGE_NO_TRANSFO);
205 	}
206 };				// void ShowRescaledItem ( int position , item* ShowItem )
207 
208 /**
209  * This function displays an item picture.
210  */
ShowItemPicture(int PosX,int PosY,int Number)211 void ShowItemPicture(int PosX, int PosY, int Number)
212 {
213 	static char LastImageSeriesPrefix[1000] = "NONE_AT_ALL";
214 	static int NumberOfImagesInThisRotation = 1;
215 #define MAX_NUMBER_OF_IMAGES_IN_ITEM_ROTATION 64
216 	static struct image item_rotation_img[MAX_NUMBER_OF_IMAGES_IN_ITEM_ROTATION] = { EMPTY_IMAGE };
217 	int i;
218 	int RotationIndex;
219 
220 	if (!image_loaded(&item_rotation_img[0])) {
221 		// Initialize image structures
222 		struct image empty = EMPTY_IMAGE;
223 		for (i = 0; i < sizeof(item_rotation_img)/sizeof(item_rotation_img[0]); i++) {
224 			memcpy(&item_rotation_img[i], &empty, sizeof(struct image));
225 		}
226 	}
227 
228 	if (strcmp(LastImageSeriesPrefix, ItemMap[Number].item_rotation_series_prefix)) {
229 
230 		// Free previous images
231 		for (i = 0; i < MAX_NUMBER_OF_IMAGES_IN_ITEM_ROTATION; i++) {
232 			delete_image(&item_rotation_img[i]);
233 		}
234 
235 		// Load new images
236 		for (i = 0; i < MAX_NUMBER_OF_IMAGES_IN_ITEM_ROTATION; i++) {
237 			char ConstructedFileName[PATH_MAX];
238 			char fpath[PATH_MAX];
239 			sprintf(ConstructedFileName, "items/%s/portrait_%04d.jpg", ItemMap[Number].item_rotation_series_prefix, i + 1);
240 
241 			// Look for the next file
242 			if (find_file(ConstructedFileName, GRAPHICS_DIR, fpath, SILENT)) {
243 				load_image(&item_rotation_img[i], ConstructedFileName, NO_MOD);
244 			} else {
245 				NumberOfImagesInThisRotation = i;
246 
247 				if (!NumberOfImagesInThisRotation)
248 					error_message(__FUNCTION__, "Unable to load any item rotation image for item \"%s\". File \"%s\" was not found.", PLEASE_INFORM, ItemMap[Number].id, ConstructedFileName);
249 
250 				break;
251 			}
252 
253 		}
254 
255 		// Remember what series we have just loaded
256 		strcpy(LastImageSeriesPrefix, ItemMap[Number].item_rotation_series_prefix);
257 	}
258 
259 	RotationIndex = (SDL_GetTicks() / 45);
260 
261 	RotationIndex = RotationIndex - (RotationIndex / NumberOfImagesInThisRotation) * NumberOfImagesInThisRotation;
262 
263 	if (image_loaded(&item_rotation_img[RotationIndex]))
264 			display_image_on_screen(&item_rotation_img[RotationIndex], PosX, PosY, IMAGE_NO_TRANSFO);
265 }
266 
267 /**
268  * Assemble item description.
269  */
fill_item_description(struct widget_text * desc,item * show_item,int buy)270 static void fill_item_description(struct widget_text *desc, item *show_item, int buy)
271 {
272 	long int repair_price = 0;
273 	itemspec *info;
274 	int price = calculate_item_buy_price(show_item);
275 	const char *action = _("Buy");
276 	const char *buyone_highlight = font_switchto_neon;
277 	const char *buyall_highlight = font_switchto_neon;
278 
279 	if (show_item == NULL)
280 		return;
281 
282 	info = &ItemMap[show_item->type];
283 
284 	widget_text_init(desc, "");
285 
286 	append_item_description(desc->text, show_item);
287 
288 	// Now we give some pricing information, the base list price for the item,
289 	// the repair price and the sell value
290 	if (price) {
291 		if (!buy) {
292  			price = calculate_item_sell_price(show_item);
293 			action = _("Sell");
294 		} else if (price > Me.Gold) {
295 			buyone_highlight = font_switchto_red;
296 			buyall_highlight = font_switchto_red;
297 		} else if ((price * show_item->multiplicity) > Me.Gold) {
298 			buyall_highlight = font_switchto_red;
299 		}
300 
301 		if (ItemMap[show_item->type].item_group_together_in_inventory) {
302 			autostr_append(desc->text, _("Price per unit: %s%d%s\n%s all: %s%d%s\n"),
303 			               buyone_highlight, price, font_switchto_neon,
304 			               action, buyall_highlight, price * show_item->multiplicity, font_switchto_neon);
305 		} else {
306 			autostr_append(desc->text, _("%s Price: %s%d%s\n"), action, buyone_highlight, price, font_switchto_neon);
307 		}
308 
309 
310 		if (show_item->current_durability == show_item->max_durability || show_item->max_durability == (-1))
311 			repair_price = 0;
312 		else
313 			repair_price = calculate_item_repair_price(show_item);
314 
315 		if (ItemMap[show_item->type].base_item_durability != (-1)) {
316 			if (show_item->max_durability == (-1))
317 				autostr_append(desc->text, _("Indestructible\n"));
318 			else if (!buy)
319 				autostr_append(desc->text, _("Repair cost: %ld\n"), repair_price);
320 		}
321 	} else {
322 		autostr_append(desc->text, _("Unsellable\n"));
323 	}
324 
325 	/* If the item is a weapon, then we print out some weapon stats. */
326 	if (info->weapon_attack_time > 0)
327 		autostr_append(desc->text, _("Time between two attacks: %3.2fs\n"),
328 					   info->weapon_attack_time);
329 
330 	if (info->weapon_reloading_time > 0)
331 		autostr_append(desc->text, _("Time to reload ammo clip: %3.2fs\n"),
332 					   info->weapon_reloading_time);
333 
334 	autostr_append(desc->text, _("Notes: %s"), D_(info->item_description));
335 
336 	if (info->weapon_ammo_type) {
337 		const char *ammo = _(item_specs_get_name(get_item_type_by_id(info->weapon_ammo_type)));
338 		autostr_append(desc->text, _("\nThis weapon requires %s."), ammo);
339 	}
340 }
341 
342 /**
343  * This function does the item show when the user has selected item
344  * show from the console menu.
345  */
GreatShopInterface(int NumberOfItems,item * ShowPointerList[MAX_ITEMS_IN_INVENTORY],int NumberOfItemsInTuxRow,item * TuxItemsList[MAX_ITEMS_IN_INVENTORY],shop_decision * ShopOrder)346 int GreatShopInterface(int NumberOfItems, item * ShowPointerList[MAX_ITEMS_IN_INVENTORY],
347 		       int NumberOfItemsInTuxRow, item * TuxItemsList[MAX_ITEMS_IN_INVENTORY], shop_decision * ShopOrder)
348 {
349 	int i;
350 	int ClickTarget;
351 
352 	int RowLength = SHOP_ROW_LENGTH;
353 	static int RowStart = 0;
354 	static int ItemIndex = 0;
355 
356 	int TuxRowLength = SHOP_ROW_LENGTH;
357 	static int TuxRowStart = 0;
358 	static int TuxItemIndex = -1;
359 
360 	char GoldString[1000];
361 	SDL_Rect HighlightRect;
362 	int BuyButtonActive = FALSE;
363 	int SellButtonActive = FALSE;
364 	int ret = 0;
365 	int old_game_status = game_status;
366 	static struct widget_text item_description;
367 	const int scroll_to_top = -1000;
368 
369 	game_status = INSIDE_MENU;
370 
371 	// We add some security against indexing beyond the
372 	// range of items given in the list.
373 	if (NumberOfItems <= 0) {
374 		NumberOfItems = 0;
375 		RowLength = 0;
376 		RowStart = 0;
377 		ItemIndex = -1;
378 	} else {
379 		if (RowLength > NumberOfItems)
380 			RowLength = NumberOfItems;
381 		if (RowStart + RowLength > NumberOfItems)
382 			RowStart = NumberOfItems - RowLength;
383 		if (RowStart < 0)
384 			RowStart = 0;
385 		if (ItemIndex >= NumberOfItems)
386 			ItemIndex = NumberOfItems - 1;
387 	}
388 
389 	if (NumberOfItemsInTuxRow <= 0) {
390 		NumberOfItemsInTuxRow = 0;
391 		TuxRowLength = 0;
392 		TuxRowStart = 0;
393 		TuxItemIndex = -1;
394 	} else {
395 		if (TuxRowLength > NumberOfItemsInTuxRow)
396 			TuxRowLength = NumberOfItemsInTuxRow;
397 		if (TuxRowStart + TuxRowLength > NumberOfItemsInTuxRow)
398 			TuxRowStart = NumberOfItemsInTuxRow - TuxRowLength;
399 		if (TuxRowStart < 0)
400 			TuxRowStart = 0;
401 		if (TuxItemIndex >= NumberOfItemsInTuxRow)
402 			TuxItemIndex = NumberOfItemsInTuxRow - 1;
403 	}
404 
405 	/* Initialize the text widget. */
406 	widget_text_init(&item_description, "");
407 	widget_set_rect(WIDGET(&item_description), UNIVERSAL_COORD_W(258), UNIVERSAL_COORD_H(108), UNIVERSAL_COORD_W(346), UNIVERSAL_COORD_H(255));
408 	item_description.font = FPS_Display_Font;
409 	item_description.line_height_factor = 1.0;
410 	item_description.scroll_offset = scroll_to_top;
411 
412 	if (ItemIndex >= 0) {
413 		fill_item_description(&item_description, ShowPointerList[ItemIndex], 1);
414 		item_description.scroll_offset = scroll_to_top;
415 	} else if (TuxItemIndex >= 0) {
416 		fill_item_description(&item_description, TuxItemsList[TuxItemIndex], 0);
417 		item_description.scroll_offset = scroll_to_top;
418 	}
419 
420 	while (1) {
421 		save_mouse_state();
422 		input_handle();
423 
424 		SDL_Delay(1);
425 		ShopOrder->shop_command = DO_NOTHING;
426 
427 		// We show all the info and the buttons that should be in this
428 		// interface...
429 		AssembleCombatPicture(ONLY_SHOW_MAP);
430 		SDL_SetClipRect(Screen, NULL);
431 		blit_background("item_browser_shop.png");
432 
433 		/* This is a magic formula to place the item picture. */
434 		int x = 40 * GameConfig.screen_width / 1024 + ((250 * GameConfig.screen_width / 1024) - 132) / 2;
435 		int y = 185 * GameConfig.screen_height / 768 + ((322 * GameConfig.screen_height / 768) - 180) / 2;
436 
437 		if (ItemIndex >= 0) {
438 			ShowItemPicture(x, y, ShowPointerList[ItemIndex]->type);
439 		} else if (TuxItemIndex >= 0) {
440 			ShowItemPicture(x, y, TuxItemsList[TuxItemIndex]->type);
441 		}
442 
443 		widget_text_display(WIDGET(&item_description));
444 
445 		for (i = 0; i < RowLength; i++) {
446 			ShowRescaledItem(i, FALSE, ShowPointerList[i + RowStart]);
447 		}
448 
449 		for (i = 0; i < TuxRowLength; i++) {
450 			ShowRescaledItem(i, TRUE, TuxItemsList[i + TuxRowStart]);
451 		}
452 
453 		/* Highlight the currently selected item. */
454 		if (ItemIndex >= 0) {
455 			HighlightRect.x =
456 			    (ShopItemRowRect.x + (ItemIndex - RowStart) * INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640);
457 			HighlightRect.y = ShopItemRowRect.y;
458 			HighlightRect.w = INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640;
459 			HighlightRect.h = INITIAL_BLOCK_HEIGHT * GameConfig.screen_height / 480;
460 			HighlightRectangle(Screen, HighlightRect);
461 		}
462 		if (TuxItemIndex >= 0) {
463 			HighlightRect.x =
464 			    (TuxItemRowRect.x + (TuxItemIndex - TuxRowStart) * INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640);
465 			HighlightRect.y = TuxItemRowRect.y;
466 			HighlightRect.w = INITIAL_BLOCK_WIDTH * GameConfig.screen_width / 640;
467 			HighlightRect.h = INITIAL_BLOCK_HEIGHT * GameConfig.screen_height / 480;
468 			HighlightRectangle(Screen, HighlightRect);
469 		}
470 
471 		if (ItemIndex >= 0) {
472 			ShowGenericButtonFromList(BUY_BUTTON);
473 			BuyButtonActive = TRUE;
474 			SellButtonActive = FALSE;
475 		} else if (TuxItemIndex >= 0) {
476 			SellButtonActive = FALSE;
477 			if (calculate_item_sell_price(TuxItemsList[TuxItemIndex])) {
478 				ShowGenericButtonFromList(SELL_BUTTON);
479 				SellButtonActive = TRUE;
480 			}
481 			BuyButtonActive = FALSE;
482 
483 			if ((ItemMap[TuxItemsList[TuxItemIndex]->type].base_item_durability >= 0) &&
484 			    (TuxItemsList[TuxItemIndex]->max_durability > TuxItemsList[TuxItemIndex]->current_durability))
485 				ShowGenericButtonFromList(REPAIR_BUTTON);
486 		} else {
487 			BuyButtonActive = FALSE;
488 			SellButtonActive = FALSE;
489 		}
490 
491 		/* Show the amount of 'Valuable Circuits' Tux has. */
492 		sprintf(GoldString, "%6d", (int)Me.Gold);
493 		put_string(FPS_Display_Font, 40 * GameConfig.screen_width / 640 - 15,
494 			      370 * GameConfig.screen_height / 480, GoldString);
495 
496 		blit_mouse_cursor();
497 		our_SDL_flip_wrapper();
498 
499 
500 		if (MouseLeftClicked()) {
501 			if (MouseCursorIsOnButton(DESCRIPTION_WINDOW_UP_BUTTON, GetMousePos_x(), GetMousePos_y())) {
502 				MoveMenuPositionSound();
503 				item_description.scroll_offset--;
504 			} else if (MouseCursorIsOnButton(DESCRIPTION_WINDOW_DOWN_BUTTON, GetMousePos_x(), GetMousePos_y())) {
505 				MoveMenuPositionSound();
506 				item_description.scroll_offset++;
507 			} else if (MouseCursorIsOnButton(ITEM_BROWSER_EXIT_BUTTON, GetMousePos_x(), GetMousePos_y())) {
508 				while (MouseLeftPressed())
509 					SDL_Delay(1);
510 				ret = -1;
511 				goto out;
512 			} else if (MouseCursorIsOnButton(LEFT_TUX_SHOP_BUTTON, GetMousePos_x(), GetMousePos_y())) {
513 				if (0 < RowStart) {
514 					RowStart--;
515 					if (ItemIndex != -1) {
516 						if (ItemIndex >= RowStart + RowLength)
517 							ItemIndex--;
518 						fill_item_description(&item_description, ShowPointerList[ItemIndex], 1);
519 						item_description.scroll_offset = scroll_to_top;
520 					}
521 				}
522 				MoveMenuPositionSound();
523 			} else if (MouseCursorIsOnButton(RIGHT_TUX_SHOP_BUTTON, GetMousePos_x(), GetMousePos_y())) {
524 				if (RowStart + RowLength < NumberOfItems) {
525 					RowStart++;
526 					if (ItemIndex != -1) {
527 						if (ItemIndex < RowStart)
528 							ItemIndex++;
529 						fill_item_description(&item_description, ShowPointerList[ItemIndex], 1);
530 						item_description.scroll_offset = scroll_to_top;
531 					}
532 				}
533 				MoveMenuPositionSound();
534 			} else if (MouseCursorIsOnButton(LEFT_SHOP_BUTTON, GetMousePos_x(), GetMousePos_y())) {
535 				if (0 < TuxRowStart) {
536 					TuxRowStart--;
537 					if (TuxItemIndex != -1) {
538 						if (TuxItemIndex >= TuxRowStart + TuxRowLength)
539 							TuxItemIndex--;
540 						fill_item_description(&item_description, TuxItemsList[TuxItemIndex], 0);
541 						item_description.scroll_offset = scroll_to_top;
542 					}
543 				}
544 				MoveMenuPositionSound();
545 			} else if (MouseCursorIsOnButton(RIGHT_SHOP_BUTTON, GetMousePos_x(), GetMousePos_y())) {
546 				if (TuxRowStart + TuxRowLength < NumberOfItemsInTuxRow) {
547 					TuxRowStart++;
548 					if (TuxItemIndex != -1) {
549 						if (TuxItemIndex < TuxRowStart)
550 							TuxItemIndex++;
551 						fill_item_description(&item_description, TuxItemsList[TuxItemIndex], 0);
552 						item_description.scroll_offset = scroll_to_top;
553 					}
554 				}
555 				MoveMenuPositionSound();
556 			} else if (((ClickTarget = ClickWasOntoItemRowPosition(GetMousePos_x(), GetMousePos_y(), FALSE)) >= 0)) {
557 				if (ClickTarget < RowLength) {
558 					ItemIndex = RowStart + ClickTarget;
559 					TuxItemIndex = (-1);
560 					fill_item_description(&item_description, ShowPointerList[ItemIndex], 1);
561 					item_description.scroll_offset = scroll_to_top;
562 				}
563 			} else if (((ClickTarget = ClickWasOntoItemRowPosition(GetMousePos_x(), GetMousePos_y(), TRUE)) >= 0)) {
564 				if (ClickTarget < TuxRowLength) {
565 					TuxItemIndex = TuxRowStart + ClickTarget;
566 					ItemIndex = (-1);
567 					fill_item_description(&item_description, TuxItemsList[TuxItemIndex], 0);
568 					item_description.scroll_offset = scroll_to_top;
569 				}
570 			} else if (MouseCursorIsOnButton(BUY_BUTTON, GetMousePos_x(), GetMousePos_y())) {
571 				if (BuyButtonActive && ItemIndex != -1) {
572 					ShopOrder->item_selected = ItemIndex;
573 					ShopOrder->shop_command = BUY_1_ITEM;
574 					int afford = Me.Gold / ItemMap[ShowPointerList[ItemIndex]->type].base_list_price;
575 					if ((ItemMap[ShowPointerList[ItemIndex]->type].item_group_together_in_inventory) && (afford >= 1)) {
576 						ShopOrder->number_selected =
577 							do_graphical_number_selection_in_range(0,
578 									(ShowPointerList[ItemIndex]->multiplicity <= afford) ?
579 										ShowPointerList[ItemIndex]->multiplicity : afford,
580 									1, calculate_item_buy_price(ShowPointerList[ItemIndex]));
581 					} else {
582 						ShopOrder->number_selected = 1;
583 					}
584 					ret = 0;
585 					goto out;
586 				}
587 			} else if (MouseCursorIsOnButton(SELL_BUTTON, GetMousePos_x(), GetMousePos_y())) {
588 				if (SellButtonActive && TuxItemIndex != -1) {
589 					ShopOrder->item_selected = TuxItemIndex;
590 					ShopOrder->shop_command = SELL_1_ITEM;
591 
592 					if ((ItemMap[TuxItemsList[TuxItemIndex]->type].item_group_together_in_inventory) &&
593 					    (TuxItemsList[TuxItemIndex]->multiplicity > 1)) {
594 						ShopOrder->number_selected =
595 						    do_graphical_number_selection_in_range(0, TuxItemsList[TuxItemIndex]->multiplicity,
596 											   TuxItemsList[TuxItemIndex]->multiplicity,
597 											calculate_item_sell_price(TuxItemsList[TuxItemIndex]));
598 					} else
599 						ShopOrder->number_selected = 1;
600 					ret = 0;
601 					goto out;
602 				}
603 			} else if (MouseCursorIsOnButton(REPAIR_BUTTON, GetMousePos_x(), GetMousePos_y())) {
604 				// Reference to the Tux item list must only be made, when the 'highlight'
605 				// is really in the tux item row.  Otherwise we just get a segfault...
606 				//
607 				if (TuxItemIndex != -1) {
608 					// Of course the repair button should only have effect, if there is
609 					// really something to repair (and therefore the button is shown at
610 					// all further above.
611 					//
612 					if ((ItemMap[TuxItemsList[TuxItemIndex]->type].base_item_durability >= 0) &&
613 					    (TuxItemsList[TuxItemIndex]->max_durability > TuxItemsList[TuxItemIndex]->current_durability)) {
614 						ShopOrder->item_selected = TuxItemIndex;
615 						ShopOrder->shop_command = REPAIR_ITEM;
616 						ShopOrder->number_selected = 1;
617 						ret = 0;
618 						goto out;
619 					}
620 				}
621 			}
622 		}
623 
624 		if (UpPressed() || MouseWheelUpPressed()) {
625 			MoveMenuPositionSound();
626 			while (UpPressed()) ;
627 			item_description.scroll_offset--;
628 		}
629 		if (DownPressed() || MouseWheelDownPressed()) {
630 			MoveMenuPositionSound();
631 			while (DownPressed()) ;
632 			item_description.scroll_offset++;
633 		}
634 
635 		if (EscapePressed()) {
636 			while (EscapePressed()) ;
637 			ret = -1;
638 			goto out;
639 		}
640 
641 	}
642 
643  out:
644 	game_status = old_game_status;
645 	return ret;
646 }
647 
648 /**
649  * This function repairs the item given as parameter.
650  */
repair_item(item * RepairItem)651 static void repair_item(item * RepairItem)
652 {
653 	while (SpacePressed() || EnterPressed() || MouseLeftPressed())
654 		SDL_Delay(1);
655 
656 	if (calculate_item_repair_price(RepairItem) > Me.Gold) {
657 		alert_window("%s\n\n%s", D_(item_specs_get_name(RepairItem->type)), _("You can not afford to have this item repaired."));
658 		return;
659 	}
660 
661 	Me.Gold -= calculate_item_repair_price(RepairItem);
662 	RepairItem->current_durability = RepairItem->max_durability;
663 	play_sound("effects/Shop_ItemRepairedSound_0.ogg");
664 }
665 
666 /**
667  * This function tries to sell the item given as parameter.
668  */
TryToSellItem(item * SellItem,int AmountToSellAtMost)669 static void TryToSellItem(item * SellItem, int AmountToSellAtMost)
670 {
671 	// We catch the case, that not even one item was selected
672 	// for buying in the number selector...
673 	//
674 	if (AmountToSellAtMost <= 0) {
675 		DebugPrintf(0, "\nTried to sell 0 items of a kind... doing nothing... ");
676 		return;
677 	}
678 	// First some error-checking against illegal values.  This should not normally
679 	// occur, but some items on the map are from very old times and therefore the
680 	// engine might have made some mistakes back then or also changes that broke these
681 	// items, so some extra care will be taken here...
682 	//
683 	if (SellItem->multiplicity < 1) {
684 		error_message(__FUNCTION__, "\
685 		An item sold seemed to have multiplicity < 1.  This might be due to some\n\
686 		fatal errors in the engine OR it might be due to some items dropped on the\n\
687 		maps somewhere long ago still had multiplicity=0 setting, which should not\n\
688 		normally occur with 'freshly' generated items.  Well, that's some dust from\n\
689 		the past, but now it should be fixed and not occur in future releases (0.9.10\n\
690 		    or later) of the game.  If you encounter this message after release 0.9.10,\n\
691 		please inform the developers...", PLEASE_INFORM);
692 	}
693 
694 	if (AmountToSellAtMost > SellItem->multiplicity)
695 		AmountToSellAtMost = SellItem->multiplicity;
696 
697 	while (SpacePressed() || EnterPressed()) ;
698 
699 	// Ok.  Here we silently sell the item.
700 	//
701 	Me.Gold += calculate_item_sell_price(SellItem) * AmountToSellAtMost;
702 	if (AmountToSellAtMost < SellItem->multiplicity)
703 		SellItem->multiplicity -= AmountToSellAtMost;
704 	else
705 		DeleteItem(SellItem);
706 
707 	play_sound("effects/Shop_ItemSoldSound_0.ogg");
708 }
709 
710 /**
711  * This function tries to buy the item given as parameter.
712  * Returns -1 if buying failed, 0 if buying was possible, and 1 if all items were sold.
713  */
buy_item(item * BuyItem,int amount)714 static int buy_item(item *BuyItem, int amount)
715 {
716 	float item_price;
717 	item new_item;
718 
719 	if (amount <= 0) {
720 		return -1;
721 	}
722 
723 	CopyItem(BuyItem, &new_item);
724 
725 	if (BuyItem->multiplicity < amount)
726 		amount = BuyItem->multiplicity;
727 
728 	new_item.multiplicity = amount;
729 	item_price = calculate_item_buy_price(&new_item) * new_item.multiplicity;
730 
731 	// If the item is too expensive, bail out
732 	if (item_price > Me.Gold) {
733 		alert_window("%s\n\n%s", D_(item_specs_get_name(BuyItem->type)), _("You can not afford this item."));
734 		return -1;
735 	}
736 
737 	// Subtract money, give item, play sound.
738 	Me.Gold -= item_price;
739 	give_item(&new_item);
740 	play_sound("effects/Shop_ItemBoughtSound_0.ogg");
741 
742 	// Did player want all of the items?
743 	if(BuyItem->multiplicity == amount) {
744 		return 1;
745 	} else {
746 		BuyItem->multiplicity -= amount;
747 	}
748 
749 	return 0;
750 
751 }
752 
753 /**
754  * This is some preparation for the shop interface.  We assemble some
755  * pointer list with the stuff Tux has to sell and the stuff the shop
756  * has to offer.
757  *
758  *
759  */
InitTradeWithCharacter(struct npc * npc)760 void InitTradeWithCharacter(struct npc *npc)
761 {
762 #define NUMBER_OF_ITEMS_IN_SHOP 17
763 
764 	item *BuyPointerList[MAX_ITEMS_IN_INVENTORY];
765 	item *TuxItemsList[MAX_ITEMS_IN_INVENTORY];
766 	int i;
767 	int ItemSelected = 0;
768 	shop_decision ShopOrder;
769 	int NumberOfItemsInTuxRow = 0;
770 	struct dynarray *sold_items;
771 
772 	sold_items = npc_get_inventory(npc);
773 
774 	for (i = 0; i < sold_items->size && i < sizeof(BuyPointerList)/sizeof(BuyPointerList[0]); i++) {
775 			BuyPointerList[i] = &((item *)(sold_items->arr))[i];
776 	}
777 
778 	// Now here comes the new thing:  This will be a loop from now
779 	// on.  The buy and buy and buy until at one point we say 'BACK'
780 	//
781 	while (ItemSelected != (-1)) {
782 
783 		NumberOfItemsInTuxRow = AssemblePointerListForItemShow(&(TuxItemsList[0]), TRUE);
784 
785 		ItemSelected = GreatShopInterface(sold_items->size, BuyPointerList, NumberOfItemsInTuxRow, TuxItemsList, &(ShopOrder));
786 		switch (ShopOrder.shop_command) {
787 		case BUY_1_ITEM:
788 			if (buy_item(BuyPointerList[ShopOrder.item_selected], ShopOrder.number_selected) == 1) {
789 				// destroy our copy of the item
790 				npc_inventory_delete_item(npc, ShopOrder.item_selected);
791 			}
792 			break;
793 		case SELL_1_ITEM:
794 			TryToSellItem(TuxItemsList[ShopOrder.item_selected], ShopOrder.number_selected);
795 			break;
796 		case REPAIR_ITEM:
797 			repair_item(TuxItemsList[ShopOrder.item_selected]);
798 			break;
799 		default:
800 
801 			break;
802 		};
803 
804 		for (i = 0; i < sold_items->size && i < sizeof(BuyPointerList)/sizeof(BuyPointerList[0]); i++) {
805 			BuyPointerList[i] = &((item *)(sold_items->arr))[i];
806 		}
807 	}
808 }
809 
810 #undef _shop_c
811