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