1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: playerinventory.cpp
5 Desc: contains player inventory related functions
6
7 Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 See LICENSE for details.
9
10 -------------------------------------------------------------------------------*/
11
12 #include "../main.hpp"
13 #include "../draw.hpp"
14 #include "../game.hpp"
15 #include "../stat.hpp"
16 #include "../items.hpp"
17 #include "../shops.hpp"
18 #include "../sound.hpp"
19 #include "../net.hpp"
20 #include "../magic/magic.hpp"
21 #include "../menu.hpp"
22 #include "../player.hpp"
23 #include "interface.hpp"
24 #ifdef STEAMWORKS
25 #include <steam/steam_api.h>
26 #include "../steam.hpp"
27 #endif
28
29 //Prototype helper functions for player inventory helper functions.
30 void itemContextMenu();
31
32
33 SDL_Surface* inventory_mode_item_img = NULL;
34 SDL_Surface* inventory_mode_item_highlighted_img = NULL;
35 SDL_Surface* inventory_mode_spell_img = NULL;
36 SDL_Surface* inventory_mode_spell_highlighted_img = NULL;
37 int inventory_mode = INVENTORY_MODE_ITEM;
38
39 selectBehavior_t itemSelectBehavior = BEHAVIOR_MOUSE;
40
warpMouseToSelectedInventorySlot()41 void warpMouseToSelectedInventorySlot()
42 {
43 SDL_WarpMouseInWindow(screen, INVENTORY_STARTX + (selected_inventory_slot_x * INVENTORY_SLOTSIZE) + (INVENTORY_SLOTSIZE / 2), INVENTORY_STARTY + (selected_inventory_slot_y * INVENTORY_SLOTSIZE) + (INVENTORY_SLOTSIZE / 2));
44 }
45
46 /*-------------------------------------------------------------------------------
47
48 itemUseString
49
50 Returns a string with the verb cooresponding to the item which is
51 to be used
52
53 -------------------------------------------------------------------------------*/
54
itemUseString(const Item * item)55 char* itemUseString(const Item* item)
56 {
57 if ( itemCategory(item) == WEAPON )
58 {
59 if ( itemIsEquipped(item, clientnum) )
60 {
61 return language[323];
62 }
63 else
64 {
65 return language[324];
66 }
67 }
68 else if ( itemCategory(item) == ARMOR )
69 {
70 switch ( item->type )
71 {
72 case WOODEN_SHIELD:
73 case BRONZE_SHIELD:
74 case IRON_SHIELD:
75 case STEEL_SHIELD:
76 case STEEL_SHIELD_RESISTANCE:
77 case CRYSTAL_SHIELD:
78 case MIRROR_SHIELD:
79 if ( itemIsEquipped(item, clientnum) )
80 {
81 return language[325];
82 }
83 else
84 {
85 return language[326];
86 }
87 default:
88 break;
89 }
90 if ( itemIsEquipped(item, clientnum) )
91 {
92 return language[327];
93 }
94 else
95 {
96 return language[328];
97 }
98 }
99 else if ( itemCategory(item) == AMULET )
100 {
101 if ( itemIsEquipped(item, clientnum) )
102 {
103 return language[327];
104 }
105 else
106 {
107 return language[328];
108 }
109 }
110 else if ( itemCategory(item) == POTION )
111 {
112 return language[329];
113 }
114 else if ( itemCategory(item) == SCROLL )
115 {
116 return language[330];
117 }
118 else if ( itemCategory(item) == MAGICSTAFF )
119 {
120 if ( itemIsEquipped(item, clientnum) )
121 {
122 return language[323];
123 }
124 else
125 {
126 return language[324];
127 }
128 }
129 else if ( itemCategory(item) == RING )
130 {
131 if ( itemIsEquipped(item, clientnum) )
132 {
133 return language[327];
134 }
135 else
136 {
137 return language[331];
138 }
139 }
140 else if ( itemCategory(item) == SPELLBOOK )
141 {
142 return language[330];
143 }
144 else if ( itemCategory(item) == GEM )
145 {
146 if ( itemIsEquipped(item, clientnum) )
147 {
148 return language[323];
149 }
150 else
151 {
152 return language[324];
153 }
154 }
155 else if ( itemCategory(item) == THROWN )
156 {
157 if ( itemIsEquipped(item, clientnum) )
158 {
159 return language[323];
160 }
161 else
162 {
163 return language[324];
164 }
165 }
166 else if ( itemCategory(item) == TOOL )
167 {
168 switch ( item->type )
169 {
170 case TOOL_PICKAXE:
171 if ( itemIsEquipped(item, clientnum) )
172 {
173 return language[323];
174 }
175 else
176 {
177 return language[324];
178 }
179 case TOOL_TINOPENER:
180 return language[1881];
181 case TOOL_MIRROR:
182 return language[332];
183 case TOOL_LOCKPICK:
184 case TOOL_SKELETONKEY:
185 if ( itemIsEquipped(item, clientnum) )
186 {
187 return language[333];
188 }
189 else
190 {
191 return language[334];
192 }
193 case TOOL_TORCH:
194 case TOOL_LANTERN:
195 case TOOL_CRYSTALSHARD:
196 if ( itemIsEquipped(item, clientnum) )
197 {
198 return language[335];
199 }
200 else
201 {
202 return language[336];
203 }
204 case TOOL_BLINDFOLD:
205 if ( itemIsEquipped(item, clientnum) )
206 {
207 return language[327];
208 }
209 else
210 {
211 return language[328];
212 }
213 case TOOL_TOWEL:
214 return language[332];
215 case TOOL_GLASSES:
216 if ( itemIsEquipped(item, clientnum) )
217 {
218 return language[327];
219 }
220 else
221 {
222 return language[331];
223 }
224 case TOOL_BEARTRAP:
225 return language[337];
226 case TOOL_ALEMBIC:
227 return language[3339];
228 case TOOL_METAL_SCRAP:
229 case TOOL_MAGIC_SCRAP:
230 return language[1881];
231 break;
232 default:
233 break;
234 }
235 }
236 else if ( itemCategory(item) == FOOD )
237 {
238 return language[338];
239 }
240 else if ( itemCategory(item) == BOOK )
241 {
242 return language[330];
243 }
244 else if ( itemCategory(item) == SPELL_CAT )
245 {
246 return language[339];
247 }
248 return language[332];
249 }
250
251 /*-------------------------------------------------------------------------------
252
253 updateAppraisalItemBox
254
255 draws the current item being appraised
256
257 -------------------------------------------------------------------------------*/
258
updateAppraisalItemBox()259 void updateAppraisalItemBox()
260 {
261 SDL_Rect pos;
262 Item* item;
263 int x, y;
264
265 x = INVENTORY_STARTX;
266 y = INVENTORY_STARTY;
267
268 // appraisal item box
269 if ( (item = uidToItem(appraisal_item)) != NULL && appraisal_timer > 0 )
270 {
271 if ( !shootmode )
272 {
273 pos.x = x + 16;
274 pos.y = y + INVENTORY_SIZEY * INVENTORY_SLOTSIZE + 16;
275 }
276 else
277 {
278 pos.x = 16;
279 pos.y = 16;
280 }
281 int w1, w2;
282 TTF_SizeUTF8(ttf12, language[340], &w1, NULL);
283 TTF_SizeUTF8(ttf12, item->getName(), &w2, NULL);
284 w2 += 48;
285 pos.w = std::max(w1, w2) + 8;
286 pos.h = 68;
287 drawTooltip(&pos);
288
289 char tempstr[64] = { 0 };
290 snprintf(tempstr, 63, language[341], (((double)(appraisal_timermax - appraisal_timer)) / ((double)appraisal_timermax)) * 100);
291 ttfPrintText( ttf12, pos.x + 8, pos.y + 8, tempstr );
292 if ( !shootmode )
293 {
294 pos.x = x + 24;
295 pos.y = y + INVENTORY_SIZEY * INVENTORY_SLOTSIZE + 16 + 24;
296 }
297 else
298 {
299 pos.x = 24;
300 pos.y = 16 + 24;
301 }
302 ttfPrintText( ttf12, pos.x + 40, pos.y + 8, item->getName() );
303 pos.w = 32;
304 pos.h = 32;
305 drawImageScaled(itemSprite(item), NULL, &pos);
306 }
307 }
308
select_inventory_slot(int x,int y)309 void select_inventory_slot(int x, int y)
310 {
311 if ( x < 0 ) //Wrap around left boundary.
312 {
313 x = INVENTORY_SIZEX - 1;
314 }
315 if ( x >= INVENTORY_SIZEX ) //Wrap around right boundary.
316 {
317 x = 0;
318 }
319
320 bool warpInv = true;
321
322 if ( y < 0 ) //Wrap around top to bottom.
323 {
324 y = INVENTORY_SIZEY - 1;
325 if ( hotbarGamepadControlEnabled() )
326 {
327 hotbarHasFocus = true; //Warp to hotbar.
328 float percentage = static_cast<float>(x + 1) / static_cast<float>(INVENTORY_SIZEX);
329 selectHotbarSlot((percentage + 0.09) * NUM_HOTBAR_SLOTS - 1);
330 warpMouseToSelectedHotbarSlot();
331 }
332 }
333 if ( y >= INVENTORY_SIZEY ) //Hit bottom. Wrap around or go to shop/chest?
334 {
335 if ( openedChest[clientnum] )
336 {
337 //Do not want to wrap around if opened chest or shop.
338 warpInv = false;
339 y = INVENTORY_SIZEY - 1; //Keeps the selected slot within the inventory, to warp back to later.
340
341 if ( numItemsInChest() > 0 ) //If chest even has an item...
342 {
343 //Then warp cursor to chest.
344 selectedChestSlot = 0; //Warp to first chest slot.
345 int warpX = CHEST_INVENTORY_X + (inventoryoptionChest_bmp->w / 2);
346 int warpY = CHEST_INVENTORY_Y + (inventoryoptionChest_bmp->h / 2) + 16;
347 SDL_WarpMouseInWindow(screen, warpX, warpY);
348 }
349 }
350 else if ( gui_mode == GUI_MODE_SHOP )
351 {
352 warpInv = false;
353 y = INVENTORY_SIZEY - 1; //Keeps the selected slot within the inventory, to warp back to later.
354
355 //Warp into shop inventory if shopkeep has any items.
356 if ( shopinvitems[0] )
357 {
358 selectedShopSlot = 0;
359 warpMouseToSelectedShopSlot();
360 }
361 }
362 else if ( identifygui_active )
363 {
364 warpInv = false;
365 y = INVENTORY_SIZEY - 1;
366
367 //Warp into identify GUI "inventory"...if there is anything there.
368 if ( identify_items[0] )
369 {
370 selectedIdentifySlot = 0;
371 warpMouseToSelectedIdentifySlot();
372 }
373 }
374 else if ( removecursegui_active )
375 {
376 warpInv = false;
377 y = INVENTORY_SIZEY - 1;
378
379 //Warp into Remove Curse GUI "inventory"...if there is anything there.
380 if ( removecurse_items[0] )
381 {
382 selectedRemoveCurseSlot = 0;
383 warpMouseToSelectedRemoveCurseSlot();
384 }
385 }
386 else if ( GenericGUI.isGUIOpen() )
387 {
388 warpInv = false;
389 y = INVENTORY_SIZEY - 1;
390
391 //Warp into GUI "inventory"...if there is anything there.
392 if ( GenericGUI.itemsDisplayed[0] )
393 {
394 GenericGUI.selectedSlot = 0;
395 GenericGUI.warpMouseToSelectedSlot();
396 }
397 }
398
399 if ( warpInv ) //Wrap around to top.
400 {
401 y = 0;
402
403 if ( hotbarGamepadControlEnabled() )
404 {
405 hotbarHasFocus = true;
406 float percentage = static_cast<float>(x + 1) / static_cast<float>(INVENTORY_SIZEX);
407 selectHotbarSlot((percentage + 0.09) * NUM_HOTBAR_SLOTS - 1);
408 warpMouseToSelectedHotbarSlot();
409 }
410 }
411 }
412
413 selected_inventory_slot_x = x;
414 selected_inventory_slot_y = y;
415 }
416
417 /*-------------------------------------------------------------------------------
418
419 updatePlayerInventory
420
421 Draws and processes everything related to the player's inventory window
422
423 -------------------------------------------------------------------------------*/
424
425 Item* selectedItem = nullptr;
426 int selectedItemFromHotbar = -1;
427 bool toggleclick = false;
428
429 bool itemMenuOpen = false;
430 int itemMenuX = 0;
431 int itemMenuY = 0;
432 int itemMenuSelected = 0;
433 Uint32 itemMenuItem = 0;
434
435 /*void releaseItem(int x, int y) {
436 node_t* node = nullptr;
437 node_t* nextnode = nullptr;
438
439 /*
440 * So, here is what must happen:
441 * * If mouse behavior mode, toggle drop!
442 * * If gamepad behavior mode, toggle release if x key pressed.
443 * * * However, keep in mind that you want a mouse click to trigger drop, just in case potato. You know, controller dying or summat. Don't wanna jam game.
444 */
445 /*
446
447 //Determine if should drop.
448 bool dropCondition = false;
449 //Check mouse behavior first.
450 if (itemSelectBehavior == BEHAVIOR_MOUSE) {
451 if ( (!mousestatus[SDL_BUTTON_LEFT] && !toggleclick) || (mousestatus[SDL_BUTTON_LEFT] && toggleclick)) {
452 //Releasing mouse button drops the item.
453 dropCondition = true;
454 }
455 } else if (itemSelectBehavior == BEHAVIOR_GAMEPAD) {
456 if (*inputPressed(joyimpulses[INJOY_MENU_LEFT_CLICK]))
457 {
458 //Pressing the item pick up button ("x" by default) again will drop the item.
459 dropCondition = true;
460 }
461 }
462
463 if (dropCondition)
464 {
465 }
466 }*/
467
releaseItem(int x,int y)468 void releaseItem(int x, int y) //TODO: This function uses toggleclick. Conflict with inventory context menu?
469 {
470 if ( !selectedItem )
471 {
472 return;
473 }
474
475 node_t* node = nullptr;
476 node_t* nextnode = nullptr;
477
478 if ( *inputPressed(joyimpulses[INJOY_MENU_CANCEL]))
479 {
480 if (selectedItemFromHotbar >= -1 && selectedItemFromHotbar < NUM_HOTBAR_SLOTS)
481 {
482 //Warp cursor back into hotbar, for gamepad convenience.
483 SDL_WarpMouseInWindow(screen, (HOTBAR_START_X) + (selectedItemFromHotbar * hotbar_img->w) + (hotbar_img->w / 2), (STATUS_Y) - (hotbar_img->h / 2));
484 hotbar[selectedItemFromHotbar].item = selectedItem->uid;
485 }
486 else
487 {
488 //Warp cursor back into inventory, for gamepad convenience.
489 SDL_WarpMouseInWindow(screen, INVENTORY_STARTX + (selectedItem->x * INVENTORY_SLOTSIZE) + (INVENTORY_SLOTSIZE / 2), INVENTORY_STARTY + (selectedItem->y * INVENTORY_SLOTSIZE) + (INVENTORY_SLOTSIZE / 2));
490 }
491
492 selectedItem = nullptr;
493 *inputPressed(joyimpulses[INJOY_MENU_CANCEL]) = 0;
494 return;
495 }
496
497 //TODO: Do proper refactoring.
498 if ( selectedItem && itemCategory(selectedItem) == SPELL_CAT && selectedItem->appearance >= 1000 )
499 {
500 if ( canUseShapeshiftSpellInCurrentForm(*selectedItem) == 0 )
501 {
502 selectedItem = nullptr;
503 return;
504 }
505 }
506
507 // releasing items
508 if ( (!mousestatus[SDL_BUTTON_LEFT] && !toggleclick) || (mousestatus[SDL_BUTTON_LEFT] && toggleclick) || ( (*inputPressed(joyimpulses[INJOY_MENU_LEFT_CLICK])) && toggleclick) )
509 {
510 *inputPressed(joyimpulses[INJOY_MENU_LEFT_CLICK]) = 0;
511 if (openedChest[clientnum] && itemCategory(selectedItem) != SPELL_CAT)
512 {
513 if (mousex >= CHEST_INVENTORY_X && mousey >= CHEST_INVENTORY_Y
514 && mousex < CHEST_INVENTORY_X + inventoryChest_bmp->w
515 && mousey < CHEST_INVENTORY_Y + inventoryChest_bmp->h)
516 {
517 if (selectedItem->count > 1)
518 {
519 openedChest[clientnum]->addItemToChestFromInventory(
520 clientnum, selectedItem, false);
521 toggleclick = true;
522 }
523 else
524 {
525 openedChest[clientnum]->addItemToChestFromInventory(
526 clientnum, selectedItem, false);
527 selectedItem = NULL;
528 toggleclick = false;
529 }
530 }
531 }
532
533 if (selectedItem)
534 {
535 if (mousex >= x && mousey >= y
536 && mousex < x + INVENTORY_SIZEX * INVENTORY_SLOTSIZE
537 && mousey < y + INVENTORY_SIZEY * INVENTORY_SLOTSIZE)
538 {
539 // within inventory
540 int oldx = selectedItem->x;
541 int oldy = selectedItem->y;
542 selectedItem->x = (mousex - x) / INVENTORY_SLOTSIZE;
543 selectedItem->y = (mousey - y) / INVENTORY_SLOTSIZE;
544 for (node = stats[clientnum]->inventory.first; node != NULL;
545 node = nextnode)
546 {
547 nextnode = node->next;
548 Item* tempItem = (Item*) (node->element);
549 if (tempItem == selectedItem)
550 {
551 continue;
552 }
553
554 toggleclick = false;
555 if (tempItem->x == selectedItem->x
556 && tempItem->y == selectedItem->y)
557 {
558 if (itemCategory(selectedItem) != SPELL_CAT
559 && itemCategory(tempItem) == SPELL_CAT)
560 {
561 //It's alright, the item can go here. The item sharing this x is just a spell, but the item being moved isn't a spell.
562 }
563 else if (itemCategory(selectedItem) == SPELL_CAT
564 && itemCategory(tempItem) != SPELL_CAT)
565 {
566 //It's alright, the item can go here. The item sharing this x isn't a spell, but the item being moved is a spell.
567 }
568 else
569 {
570 //The player just dropped an item onto another item.
571 tempItem->x = oldx;
572 tempItem->y = oldy;
573 selectedItem = tempItem;
574 toggleclick = true;
575 break;
576 }
577 }
578 }
579 if (!toggleclick)
580 {
581 selectedItem = NULL;
582 }
583
584 playSound(139, 64); // click sound
585 }
586 else if (itemCategory(selectedItem) == SPELL_CAT)
587 {
588 //Outside inventory. Spells can't be dropped.
589 hotbar_slot_t* slot = getHotbar(mousex, mousey);
590 if (slot)
591 {
592 //Add spell to hotbar.
593 Item* tempItem = uidToItem(slot->item);
594 if (tempItem)
595 {
596 slot->item = selectedItem->uid;
597 selectedItem = tempItem;
598 toggleclick = true;
599 }
600 else
601 {
602 slot->item = selectedItem->uid;
603 selectedItem = NULL;
604 toggleclick = false;
605 }
606 playSound(139, 64); // click sound
607 }
608 else
609 {
610 selectedItem = NULL;
611 }
612 }
613 else
614 {
615 // outside inventory
616 hotbar_slot_t* slot = getHotbar(mousex, mousey);
617 if (slot)
618 {
619 //Add item to hotbar.
620 Item* tempItem = uidToItem(slot->item);
621 if (tempItem)
622 {
623 slot->item = selectedItem->uid;
624 selectedItem = tempItem;
625 toggleclick = true;
626 }
627 else
628 {
629 slot->item = selectedItem->uid;
630 selectedItem = NULL;
631 toggleclick = false;
632 }
633 playSound(139, 64); // click sound
634 }
635 else
636 {
637 if (selectedItem->count > 1)
638 {
639 if ( dropItem(selectedItem, clientnum) )
640 {
641 selectedItem = NULL;
642 }
643 toggleclick = true;
644 }
645 else
646 {
647 dropItem(selectedItem, clientnum);
648 selectedItem = NULL;
649 toggleclick = false;
650 }
651 }
652 }
653 }
654 if (mousestatus[SDL_BUTTON_LEFT])
655 {
656 mousestatus[SDL_BUTTON_LEFT] = 0;
657 }
658 }
659 }
660
cycleInventoryTab()661 void cycleInventoryTab()
662 {
663 if ( inventory_mode == INVENTORY_MODE_ITEM)
664 {
665 inventory_mode = INVENTORY_MODE_SPELL;
666 }
667 else
668 {
669 //inventory_mode == INVENTORY_MODE_SPELL
670 inventory_mode = INVENTORY_MODE_ITEM;
671 }
672 }
673
674 /*
675 * Because the mouseInBounds() function looks at the omousex for whatever reason.
676 * And changing that function to use mousex has, through much empirical study, been proven to be a bad idea. Things will break.
677 * So, this function is used instead for one thing and only one thing: the gold borders that follow the mouse through the inventory.
678 *
679 * Places used so far:
680 * * Here. In updatePlayerInventory(), where it draws the gold borders around the inventory tile the mouse is hovering over.
681 * * drawstatus.cpp: drawing the gold borders around the hotbar slot the mouse is hovering over
682 */
mouseInBoundsRealtimeCoords(int x1,int x2,int y1,int y2)683 bool mouseInBoundsRealtimeCoords(int x1, int x2, int y1, int y2)
684 {
685 if (mousey >= y1 && mousey < y2)
686 if (mousex >= x1 && mousex < x2)
687 {
688 return true;
689 }
690
691 return false;
692 }
693
drawBlueInventoryBorder(const Item & item,int x,int y)694 void drawBlueInventoryBorder(const Item& item, int x, int y)
695 {
696 SDL_Rect pos;
697 pos.x = x + item.x * INVENTORY_SLOTSIZE + 2;
698 pos.y = y + item.y * INVENTORY_SLOTSIZE + 1;
699 pos.w = INVENTORY_SLOTSIZE;
700 pos.h = INVENTORY_SLOTSIZE;
701
702 Uint32 color = SDL_MapRGBA(mainsurface->format, 0, 0, 255, 127);
703 drawBox(&pos, color, 127);
704 }
705
updatePlayerInventory()706 void updatePlayerInventory()
707 {
708 bool disableMouseDisablingHotbarFocus = false;
709 SDL_Rect pos, mode_pos;
710 node_t* node, *nextnode;
711 int x, y;
712
713 x = INVENTORY_STARTX;
714 y = INVENTORY_STARTY;
715
716 // draw translucent box
717 pos.x = x;
718 pos.y = y;
719 pos.w = INVENTORY_SIZEX * INVENTORY_SLOTSIZE;
720 pos.h = INVENTORY_SIZEY * INVENTORY_SLOTSIZE;
721 drawRect(&pos, 0, 224);
722
723 if ( game_controller )
724 {
725 if ( gui_mode == GUI_MODE_SHOP )
726 {
727 if ( *inputPressed(joyimpulses[INJOY_MENU_CYCLE_SHOP_LEFT]) )
728 {
729 *inputPressed(joyimpulses[INJOY_MENU_CYCLE_SHOP_LEFT]) = 0;
730 cycleShopCategories(-1);
731 }
732 if ( *inputPressed(joyimpulses[INJOY_MENU_CYCLE_SHOP_RIGHT]) )
733 {
734 *inputPressed(joyimpulses[INJOY_MENU_CYCLE_SHOP_RIGHT]) = 0;
735 cycleShopCategories(1);
736 }
737 }
738
739 if ( selectedChestSlot < 0 && selectedShopSlot < 0
740 && selectedIdentifySlot < 0 && selectedRemoveCurseSlot < 0
741 && !itemMenuOpen && game_controller->handleInventoryMovement()
742 && GenericGUI.selectedSlot < 0 )
743 {
744 if ( selectedChestSlot < 0 && selectedShopSlot < 0
745 && selectedIdentifySlot < 0 && selectedRemoveCurseSlot < 0
746 && GenericGUI.selectedSlot < 0 ) //This second check prevents the extra mouse warp.
747 {
748 if ( !hotbarHasFocus )
749 {
750 warpMouseToSelectedInventorySlot();
751 }
752 else
753 {
754 disableMouseDisablingHotbarFocus = true;
755 }
756 }
757 }
758 else if ( selectedChestSlot >= 0 && !itemMenuOpen && game_controller->handleChestMovement() )
759 {
760 if ( selectedChestSlot < 0 )
761 {
762 //Move out of chest. Warp cursor back to selected inventory slot.
763 warpMouseToSelectedInventorySlot();
764 }
765 }
766 else if ( selectedShopSlot >= 0 && !itemMenuOpen && game_controller->handleShopMovement() )
767 {
768 if ( selectedShopSlot < 0 )
769 {
770 warpMouseToSelectedInventorySlot();
771 }
772 }
773 else if ( selectedIdentifySlot >= 0 && !itemMenuOpen && game_controller->handleIdentifyMovement() )
774 {
775 if ( selectedIdentifySlot < 0 )
776 {
777 warpMouseToSelectedInventorySlot();
778 }
779 }
780 else if ( selectedRemoveCurseSlot >= 0 && !itemMenuOpen && game_controller->handleRemoveCurseMovement() )
781 {
782 if ( selectedRemoveCurseSlot < 0 )
783 {
784 warpMouseToSelectedInventorySlot();
785 }
786 }
787 else if ( GenericGUI.selectedSlot >= 0 && !itemMenuOpen && game_controller->handleRepairGUIMovement() )
788 {
789 if ( GenericGUI.selectedSlot < 0 )
790 {
791 GenericGUI.warpMouseToSelectedSlot();
792 }
793 }
794
795 if ( *inputPressed(joyimpulses[INJOY_MENU_INVENTORY_TAB]) )
796 {
797 *inputPressed(joyimpulses[INJOY_MENU_INVENTORY_TAB]) = 0;
798 cycleInventoryTab();
799 }
800
801 if ( lastkeypressed == 300 )
802 {
803 lastkeypressed = 0;
804 }
805
806 if ( *inputPressed(joyimpulses[INJOY_MENU_MAGIC_TAB]) )
807 {
808 *inputPressed(joyimpulses[INJOY_MENU_MAGIC_TAB]) = 0;
809 cycleInventoryTab();
810 }
811 }
812
813 if ( !command && *inputPressed(impulses[IN_AUTOSORT]) )
814 {
815 autosortInventory();
816 //quickStackItems();
817 *inputPressed(impulses[IN_AUTOSORT]) = 0;
818 playSound(139, 64);
819 }
820
821 // draw grid
822 pos.x = x;
823 pos.y = y;
824 pos.w = INVENTORY_SIZEX * INVENTORY_SLOTSIZE;
825 pos.h = INVENTORY_SIZEY * INVENTORY_SLOTSIZE;
826 drawLine(pos.x, pos.y, pos.x, pos.y + pos.h, SDL_MapRGB(mainsurface->format, 150, 150, 150), 255);
827 drawLine(pos.x, pos.y, pos.x + pos.w, pos.y, SDL_MapRGB(mainsurface->format, 150, 150, 150), 255);
828 for ( x = 0; x <= INVENTORY_SIZEX; x++ )
829 {
830 drawLine(pos.x + x * INVENTORY_SLOTSIZE, pos.y, pos.x + x * INVENTORY_SLOTSIZE, pos.y + pos.h, SDL_MapRGB(mainsurface->format, 150, 150, 150), 255);
831 }
832 for ( y = 0; y <= INVENTORY_SIZEY; y++ )
833 {
834 drawLine(pos.x, pos.y + y * INVENTORY_SLOTSIZE, pos.x + pos.w, pos.y + y * INVENTORY_SLOTSIZE, SDL_MapRGB(mainsurface->format, 150, 150, 150), 255);
835 }
836
837 if ( !itemMenuOpen
838 && selectedChestSlot < 0 && selectedShopSlot < 0
839 && selectedIdentifySlot < 0 && selectedRemoveCurseSlot < 0
840 && GenericGUI.selectedSlot < 0 )
841 {
842 //Highlight (draw a gold border) currently selected inventory slot (for gamepad).
843 //Only if item menu is not open, no chest slot is selected, no shop slot is selected, no Identify GUI slot is selected, and no Remove Curse GUI slot is selected.
844 pos.w = INVENTORY_SLOTSIZE;
845 pos.h = INVENTORY_SLOTSIZE;
846 for (x = 0; x < INVENTORY_SIZEX; ++x)
847 {
848 for (y = 0; y < INVENTORY_SIZEY; ++y)
849 {
850 pos.x = INVENTORY_STARTX + x * INVENTORY_SLOTSIZE;
851 pos.y = INVENTORY_STARTY + y * INVENTORY_SLOTSIZE;
852
853 //Cursor moved over this slot, highlight it.
854 if (mouseInBoundsRealtimeCoords(pos.x, pos.x + pos.w, pos.y, pos.y + pos.h))
855 {
856 selected_inventory_slot_x = x;
857 selected_inventory_slot_y = y;
858 if ( hotbarHasFocus && !disableMouseDisablingHotbarFocus )
859 {
860 hotbarHasFocus = false; //Utter bodge to fix hotbar nav on OS X.
861 }
862 }
863
864 if ( x == selected_inventory_slot_x && y == selected_inventory_slot_y && !hotbarHasFocus )
865 {
866 Uint32 color = SDL_MapRGBA(mainsurface->format, 255, 255, 0, 127);
867 drawBox(&pos, color, 127);
868 }
869 }
870 }
871 }
872
873
874 // draw contents of each slot
875 x = INVENTORY_STARTX;
876 y = INVENTORY_STARTY;
877 for ( node = stats[clientnum]->inventory.first; node != NULL; node = nextnode )
878 {
879 nextnode = node->next;
880 Item* item = (Item*)node->element;
881
882 if ( item == selectedItem || (inventory_mode == INVENTORY_MODE_ITEM && itemCategory(item) == SPELL_CAT) || (inventory_mode == INVENTORY_MODE_SPELL && itemCategory(item) != SPELL_CAT) )
883 {
884 //Item is selected, or, item is a spell but it's item inventory mode, or, item is an item but it's spell inventory mode...(this filters out items)
885 if ( !(inventory_mode == INVENTORY_MODE_ITEM && itemCategory(item) == SPELL_CAT) || (inventory_mode == INVENTORY_MODE_SPELL && itemCategory(item) != SPELL_CAT) )
886 {
887 if ( item == selectedItem )
888 {
889 //Draw blue border around the slot if it's the currently grabbed item.
890 drawBlueInventoryBorder(*item, x, y);
891 }
892 }
893 continue;
894 }
895
896 pos.x = x + item->x * (INVENTORY_SLOTSIZE) + 2;
897 pos.y = y + item->y * (INVENTORY_SLOTSIZE) + 1;
898 pos.w = (INVENTORY_SLOTSIZE) - 2;
899 pos.h = (INVENTORY_SLOTSIZE) - 2;
900 if (!item->identified)
901 {
902 // give it a yellow background if it is unidentified
903 drawRect(&pos, SDL_MapRGB(mainsurface->format, 128, 128, 0), 125); //31875
904 }
905 else if (item->beatitude < 0)
906 {
907 // give it a red background if cursed
908 drawRect(&pos, SDL_MapRGB(mainsurface->format, 128, 0, 0), 125);
909 }
910 else if (item->beatitude > 0)
911 {
912 // give it a green background if blessed (light blue if colorblind mode)
913 if (colorblind)
914 {
915 drawRect(&pos, SDL_MapRGB(mainsurface->format, 100, 245, 255), 65);
916 }
917 else
918 {
919 drawRect(&pos, SDL_MapRGB(mainsurface->format, 0, 255, 0), 65);
920 }
921 }
922 if ( item->status == BROKEN )
923 {
924 drawRect(&pos, SDL_MapRGB(mainsurface->format, 160, 160, 160), 64);
925 }
926
927 if ( itemMenuOpen && item == uidToItem(itemMenuItem) )
928 {
929 //Draw blue border around the slot if it's the currently context menu'd item.
930 drawBlueInventoryBorder(*item, x, y);
931 }
932
933 // draw item
934 pos.x = x + item->x * INVENTORY_SLOTSIZE + 4 * uiscale_inventory;
935 pos.y = y + item->y * INVENTORY_SLOTSIZE + 4 * uiscale_inventory;
936 pos.w = 32 * uiscale_inventory;
937 pos.h = 32 * uiscale_inventory;
938 if ( itemSprite(item) )
939 {
940 drawImageScaled(itemSprite(item), NULL, &pos);
941 }
942
943 bool greyedOut = false;
944 if ( players[clientnum] && players[clientnum]->entity && players[clientnum]->entity->effectShapeshift != NOTHING )
945 {
946 // shape shifted, disable some items
947 if ( !item->usableWhileShapeshifted(stats[clientnum]) )
948 {
949 SDL_Rect greyBox;
950 greyBox.x = x + item->x * (INVENTORY_SLOTSIZE)+2;
951 greyBox.y = y + item->y * (INVENTORY_SLOTSIZE)+1;
952 greyBox.w = (INVENTORY_SLOTSIZE)-2;
953 greyBox.h = (INVENTORY_SLOTSIZE)-2;
954 drawRect(&greyBox, SDL_MapRGB(mainsurface->format, 64, 64, 64), 144);
955 greyedOut = true;
956 }
957 }
958 if ( !greyedOut && client_classes[clientnum] == CLASS_SHAMAN
959 && item->type == SPELL_ITEM && !(playerUnlockedShamanSpell(clientnum, item)) )
960 {
961 SDL_Rect greyBox;
962 greyBox.x = x + item->x * (INVENTORY_SLOTSIZE)+2;
963 greyBox.y = y + item->y * (INVENTORY_SLOTSIZE)+1;
964 greyBox.w = (INVENTORY_SLOTSIZE)-2;
965 greyBox.h = (INVENTORY_SLOTSIZE)-2;
966 drawRect(&greyBox, SDL_MapRGB(mainsurface->format, 64, 64, 64), 144);
967 greyedOut = true;
968 }
969
970 // item count
971 if ( item->count > 1 )
972 {
973 if ( uiscale_inventory < 1.5 )
974 {
975 printTextFormatted(font8x8_bmp, pos.x + pos.w - 8 * uiscale_inventory, pos.y + pos.h - 8 * uiscale_inventory, "%d", item->count);
976 }
977 else
978 {
979 printTextFormatted(font12x12_bmp, pos.x + pos.w - 12, pos.y + pos.h - 12, "%d", item->count);
980 }
981 }
982
983 // item equipped
984 if ( itemCategory(item) != SPELL_CAT )
985 {
986 if ( itemIsEquipped(item, clientnum) )
987 {
988 pos.x = x + item->x * INVENTORY_SLOTSIZE + 2;
989 pos.y = y + item->y * INVENTORY_SLOTSIZE + INVENTORY_SLOTSIZE - 18;
990 pos.w = 16;
991 pos.h = 16;
992 drawImage(equipped_bmp, NULL, &pos);
993 }
994 else if ( item->status == BROKEN )
995 {
996 pos.x = x + item->x * INVENTORY_SLOTSIZE + 2;
997 pos.y = y + item->y * INVENTORY_SLOTSIZE + INVENTORY_SLOTSIZE - 18;
998 pos.w = 16;
999 pos.h = 16;
1000 drawImage(itembroken_bmp, NULL, &pos);
1001 }
1002 }
1003 else
1004 {
1005 spell_t* spell = getSpellFromItem(item);
1006 if ( selected_spell == spell
1007 && (selected_spell_last_appearance == item->appearance || selected_spell_last_appearance == -1) )
1008 {
1009 pos.x = x + item->x * INVENTORY_SLOTSIZE + 2;
1010 pos.y = y + item->y * INVENTORY_SLOTSIZE + INVENTORY_SLOTSIZE - 18;
1011 pos.w = 16;
1012 pos.h = 16;
1013 drawImage(equipped_bmp, NULL, &pos);
1014 }
1015 }
1016 }
1017 // autosort button
1018 mode_pos.x = x + INVENTORY_SIZEX * INVENTORY_SLOTSIZE + inventory_mode_item_img->w * uiscale_inventory + 2;
1019 mode_pos.y = y;
1020 mode_pos.w = 24;
1021 mode_pos.h = 24;
1022 bool mouse_in_bounds = mouseInBounds(mode_pos.x, mode_pos.x + mode_pos.w, mode_pos.y, mode_pos.y + mode_pos.h);
1023 if ( !mouse_in_bounds )
1024 {
1025 drawWindow(mode_pos.x, mode_pos.y, mode_pos.x + mode_pos.w, mode_pos.y + mode_pos.h);
1026 }
1027 else
1028 {
1029 drawDepressed(mode_pos.x, mode_pos.y, mode_pos.x + mode_pos.w, mode_pos.y + mode_pos.h);
1030 }
1031 ttfPrintText(ttf12, mode_pos.x, mode_pos.y + 6, "||");
1032 if ( mouse_in_bounds )
1033 {
1034 mode_pos.x += 2;
1035 mode_pos.y += 2;
1036 mode_pos.w -= 4;
1037 mode_pos.h -= 4;
1038 drawRect(&mode_pos, SDL_MapRGB(mainsurface->format, 192, 192, 192), 64);
1039 // tooltip
1040 SDL_Rect src;
1041 src.x = mousex + 16;
1042 src.y = mousey + 8;
1043 src.h = TTF12_HEIGHT + 8;
1044 src.w = longestline(language[2960]) * TTF12_WIDTH + 8;
1045 drawTooltip(&src);
1046 ttfPrintTextFormatted(ttf12, src.x + 4, src.y + 4, language[2960], getInputName(impulses[IN_AUTOSORT]));
1047 if ( mousestatus[SDL_BUTTON_LEFT] )
1048 {
1049 mousestatus[SDL_BUTTON_LEFT] = 0;
1050 autosortInventory();
1051 playSound(139, 64);
1052 }
1053 }
1054 // do inventory mode buttons
1055 mode_pos.x = x + INVENTORY_SIZEX * INVENTORY_SLOTSIZE + 1;
1056 mode_pos.y = y + inventory_mode_spell_img->h * uiscale_inventory;
1057 mode_pos.w = inventory_mode_spell_img->w * uiscale_inventory;
1058 mode_pos.h = inventory_mode_spell_img->h * uiscale_inventory + 1;
1059 mouse_in_bounds = mouseInBounds(mode_pos.x, mode_pos.x + mode_pos.w,
1060 mode_pos.y, mode_pos.y + mode_pos.h);
1061 if (mouse_in_bounds)
1062 {
1063 drawImageScaled(inventory_mode_spell_highlighted_img, NULL, &mode_pos);
1064
1065 // tooltip
1066 SDL_Rect src;
1067 src.x = mousex + 16;
1068 src.y = mousey + 8;
1069 src.h = TTF12_HEIGHT + 8;
1070 src.w = longestline(language[342]) * TTF12_WIDTH + 8;
1071 drawTooltip(&src);
1072 ttfPrintText(ttf12, src.x + 4, src.y + 4, language[342]);
1073
1074 if (mousestatus[SDL_BUTTON_LEFT])
1075 {
1076 mousestatus[SDL_BUTTON_LEFT] = 0;
1077 inventory_mode = INVENTORY_MODE_SPELL;
1078 playSound(139, 64);
1079 }
1080 }
1081 else
1082 {
1083 drawImageScaled(inventory_mode_spell_img, NULL, &mode_pos);
1084 }
1085 mode_pos.x = x + INVENTORY_SIZEX * INVENTORY_SLOTSIZE + 1;
1086 mode_pos.y = y - 1;
1087 mode_pos.w = inventory_mode_item_img->w * uiscale_inventory;
1088 mode_pos.h = inventory_mode_item_img->h * uiscale_inventory + 2;
1089 mouse_in_bounds = mouseInBounds(mode_pos.x, mode_pos.x + mode_pos.w,
1090 mode_pos.y, mode_pos.y + mode_pos.h);
1091 if (mouse_in_bounds)
1092 {
1093 drawImageScaled(inventory_mode_item_highlighted_img, NULL, &mode_pos);
1094
1095 // tooltip
1096 SDL_Rect src;
1097 src.x = mousex + 16;
1098 src.y = mousey + 8;
1099 src.h = TTF12_HEIGHT + 8;
1100 src.w = longestline(language[343]) * TTF12_WIDTH + 8;
1101 drawTooltip(&src);
1102 ttfPrintText(ttf12, src.x + 4, src.y + 4, language[343]);
1103
1104 if (mousestatus[SDL_BUTTON_LEFT])
1105 {
1106 mousestatus[SDL_BUTTON_LEFT] = 0;
1107 inventory_mode = INVENTORY_MODE_ITEM;
1108 playSound(139, 64);
1109 }
1110 }
1111 else
1112 {
1113 drawImageScaled(inventory_mode_item_img, NULL, &mode_pos);
1114 }
1115
1116 // mouse interactions
1117 if ( !selectedItem )
1118 {
1119 for ( node = stats[clientnum]->inventory.first; node != NULL; node = nextnode )
1120 {
1121 nextnode = node->next;
1122 Item* item = (Item*)node->element; //I don't like that there's not a check that either are null.
1123
1124 if (item)
1125 {
1126 pos.x = x + item->x * INVENTORY_SLOTSIZE + 4;
1127 pos.y = y + item->y * INVENTORY_SLOTSIZE + 4;
1128 pos.w = INVENTORY_SLOTSIZE - 8;
1129 pos.h = INVENTORY_SLOTSIZE - 8;
1130
1131 if ( omousex >= pos.x && omousey >= pos.y && omousex < pos.x + pos.w && omousey < pos.y + pos.h )
1132 {
1133 // tooltip
1134 if ((inventory_mode == INVENTORY_MODE_ITEM && itemCategory(item) == SPELL_CAT) || (inventory_mode == INVENTORY_MODE_SPELL && itemCategory(item) != SPELL_CAT))
1135 {
1136 continue; //Skip over this items since the filter is blocking it (eg spell in normal inventory or vice versa).
1137 }
1138 if ( !itemMenuOpen )
1139 {
1140 SDL_Rect src;
1141 src.x = mousex + 16;
1142 src.y = mousey + 8;
1143 if (itemCategory(item) == SPELL_CAT)
1144 {
1145 spell_t* spell = getSpellFromItem(item);
1146 drawSpellTooltip(spell, item, nullptr);
1147 }
1148 else
1149 {
1150 src.w = std::max(13, longestline(item->description())) * TTF12_WIDTH + 8;
1151 src.h = TTF12_HEIGHT * 4 + 8;
1152 char spellEffectText[256] = "";
1153 if ( item->identified )
1154 {
1155 bool learnedSpellbook = false;
1156 if ( itemCategory(item) == SPELLBOOK )
1157 {
1158 learnedSpellbook = playerLearnedSpellbook(item);
1159 if ( !learnedSpellbook && stats[clientnum] && players[clientnum] && players[clientnum]->entity )
1160 {
1161 // spellbook tooltip shows if you have the magic requirement as well (for goblins)
1162 int skillLVL = stats[clientnum]->PROFICIENCIES[PRO_MAGIC] + statGetINT(stats[clientnum], players[clientnum]->entity);
1163 spell_t* spell = getSpellFromID(getSpellIDFromSpellbook(item->type));
1164 if ( spell && skillLVL >= spell->difficulty )
1165 {
1166 learnedSpellbook = true;
1167 }
1168 }
1169 }
1170
1171 if ( itemCategory(item) == WEAPON || itemCategory(item) == ARMOR || itemCategory(item) == THROWN
1172 || itemTypeIsQuiver(item->type) )
1173 {
1174 src.h += TTF12_HEIGHT;
1175 }
1176 else if ( itemCategory(item) == SCROLL && item->identified )
1177 {
1178 src.h += TTF12_HEIGHT;
1179 src.w = std::max((2 + longestline(language[3862]) + longestline(item->getScrollLabel())) * TTF12_WIDTH + 8, src.w);
1180 }
1181 else if ( itemCategory(item) == SPELLBOOK && learnedSpellbook )
1182 {
1183 int height = 1;
1184 char effectType[32] = "";
1185 int spellID = getSpellIDFromSpellbook(item->type);
1186 int damage = drawSpellTooltip(getSpellFromID(spellID), item, nullptr);
1187 real_t dummy = 0.f;
1188 getSpellEffectString(spellID, spellEffectText, effectType, damage, &height, &dummy);
1189 int width = longestline(spellEffectText) * TTF12_WIDTH + 8;
1190 if ( width > src.w )
1191 {
1192 src.w = width;
1193 }
1194 src.h += height * TTF12_HEIGHT;
1195 }
1196 else if ( item->type == TOOL_GYROBOT || item->type == TOOL_DUMMYBOT
1197 || item->type == TOOL_SENTRYBOT || item->type == TOOL_SPELLBOT
1198 || (item->type == ENCHANTED_FEATHER && item->identified) )
1199 {
1200 src.w += 7 * TTF12_WIDTH;
1201 }
1202 }
1203 int furthestX = xres;
1204 if ( proficienciesPage == 0 )
1205 {
1206 if ( src.y < interfaceSkillsSheet.y + interfaceSkillsSheet.h )
1207 {
1208 furthestX = xres - interfaceSkillsSheet.w;
1209 }
1210 }
1211 else
1212 {
1213 if ( src.y < interfacePartySheet.y + interfacePartySheet.h )
1214 {
1215 furthestX = xres - interfacePartySheet.w;
1216 }
1217 }
1218 if ( src.x + src.w + 16 > furthestX ) // overflow right side of screen
1219 {
1220 src.x -= (src.w + 32);
1221 }
1222
1223 drawTooltip(&src);
1224
1225 Uint32 color = 0xFFFFFFFF;
1226 if ( !item->identified )
1227 {
1228 color = SDL_MapRGB(mainsurface->format, 255, 255, 0);
1229 ttfPrintTextFormattedColor( ttf12, src.x + 4 + TTF12_WIDTH, src.y + 4 + TTF12_HEIGHT, color, language[309] );
1230 }
1231 else
1232 {
1233 if (item->beatitude < 0)
1234 {
1235 //Red if cursed
1236 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1237 ttfPrintTextFormattedColor(ttf12, src.x + 4 + TTF12_WIDTH, src.y + 4 + TTF12_HEIGHT, color, language[310]);
1238 }
1239 else if (item->beatitude == 0)
1240 {
1241 //White if normal item.
1242 color = 0xFFFFFFFF;
1243 ttfPrintTextFormattedColor(ttf12, src.x + 4 + TTF12_WIDTH, src.y + 4 + TTF12_HEIGHT, color, language[311]);
1244 }
1245 else
1246 {
1247 //Green if blessed.
1248 if (colorblind)
1249 {
1250 color = SDL_MapRGB(mainsurface->format, 100, 245, 255); //Light blue if colorblind
1251 }
1252 else
1253 {
1254 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1255 }
1256
1257 ttfPrintTextFormattedColor(ttf12, src.x + 4 + TTF12_WIDTH, src.y + 4 + TTF12_HEIGHT, color, language[312]);
1258 }
1259 }
1260 if ( item->beatitude == 0 || !item->identified )
1261 {
1262 color = 0xFFFFFFFF;
1263 }
1264
1265 if ( item->type == TOOL_GYROBOT || item->type == TOOL_DUMMYBOT
1266 || item->type == TOOL_SENTRYBOT || item->type == TOOL_SPELLBOT )
1267 {
1268 int health = 100;
1269 if ( !item->tinkeringBotIsMaxHealth() )
1270 {
1271 health = 25 * (item->appearance % 10);
1272 if ( health == 0 && item->status != BROKEN )
1273 {
1274 health = 5;
1275 }
1276 }
1277 ttfPrintTextFormattedColor(ttf12, src.x + 4, src.y + 4, color, "%s (%d%%)", item->description(), health);
1278 }
1279 else if ( item->type == ENCHANTED_FEATHER && item->identified )
1280 {
1281 ttfPrintTextFormattedColor(ttf12, src.x + 4, src.y + 4, color, "%s (%d%%)", item->description(), item->appearance % ENCHANTED_FEATHER_MAX_DURABILITY);
1282 }
1283 else
1284 {
1285 ttfPrintTextFormattedColor( ttf12, src.x + 4, src.y + 4, color, "%s", item->description());
1286 }
1287 int itemWeight = items[item->type].weight * item->count;
1288 if ( itemTypeIsQuiver(item->type) )
1289 {
1290 itemWeight = std::max(1, itemWeight / 5);
1291 }
1292 ttfPrintTextFormatted( ttf12, src.x + 4 + TTF12_WIDTH, src.y + 4 + TTF12_HEIGHT * 2, language[313], itemWeight);
1293 ttfPrintTextFormatted( ttf12, src.x + 4 + TTF12_WIDTH, src.y + 4 + TTF12_HEIGHT * 3, language[314], item->sellValue(clientnum));
1294 if ( strcmp(spellEffectText, "") )
1295 {
1296 ttfPrintTextFormattedColor(ttf12, src.x + 4, src.y + 4 + TTF12_HEIGHT * 4, SDL_MapRGB(mainsurface->format, 0, 255, 255), spellEffectText);
1297 }
1298
1299 if ( item->identified )
1300 {
1301 if ( itemCategory(item) == WEAPON || itemCategory(item) == THROWN
1302 || itemTypeIsQuiver(item->type) )
1303 {
1304 Monster tmpRace = stats[clientnum]->type;
1305 if ( stats[clientnum]->type == TROLL
1306 || stats[clientnum]->type == RAT
1307 || stats[clientnum]->type == SPIDER
1308 || stats[clientnum]->type == CREATURE_IMP )
1309 {
1310 // these monsters have 0 bonus from weapons, but want the tooltip to say the normal amount.
1311 stats[clientnum]->type = HUMAN;
1312 }
1313
1314 if ( item->weaponGetAttack(stats[clientnum]) >= 0 )
1315 {
1316 color = SDL_MapRGB(mainsurface->format, 0, 255, 255);
1317 }
1318 else
1319 {
1320 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1321 }
1322 if ( stats[clientnum]->type != tmpRace )
1323 {
1324 color = SDL_MapRGB(mainsurface->format, 127, 127, 127); // grey out the text if monster doesn't benefit.
1325 }
1326
1327 ttfPrintTextFormattedColor( ttf12, src.x + 4 + TTF12_WIDTH, src.y + 4 + TTF12_HEIGHT * 4, color, language[315], item->weaponGetAttack(stats[clientnum]));
1328 stats[clientnum]->type = tmpRace;
1329 }
1330 else if ( itemCategory(item) == ARMOR )
1331 {
1332 Monster tmpRace = stats[clientnum]->type;
1333 if ( stats[clientnum]->type == TROLL
1334 || stats[clientnum]->type == RAT
1335 || stats[clientnum]->type == SPIDER
1336 || stats[clientnum]->type == CREATURE_IMP )
1337 {
1338 // these monsters have 0 bonus from weapons, but want the tooltip to say the normal amount.
1339 stats[clientnum]->type = HUMAN;
1340 }
1341
1342 if ( item->armorGetAC(stats[clientnum]) >= 0 )
1343 {
1344 color = SDL_MapRGB(mainsurface->format, 0, 255, 255);
1345 }
1346 else
1347 {
1348 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1349 }
1350 if ( stats[clientnum]->type != tmpRace )
1351 {
1352 color = SDL_MapRGB(mainsurface->format, 127, 127, 127); // grey out the text if monster doesn't benefit.
1353 }
1354
1355 ttfPrintTextFormattedColor( ttf12, src.x + 4 + TTF12_WIDTH, src.y + 4 + TTF12_HEIGHT * 4, color, language[316], item->armorGetAC(stats[clientnum]));
1356 stats[clientnum]->type = tmpRace;
1357 }
1358 else if ( itemCategory(item) == SCROLL )
1359 {
1360 color = SDL_MapRGB(mainsurface->format, 0, 255, 255);
1361 ttfPrintTextFormattedColor(ttf12, src.x + 4 + TTF12_WIDTH, src.y + 4 + TTF12_HEIGHT * 4, color, "%s%s", language[3862], item->getScrollLabel());
1362 }
1363 }
1364 }
1365 }
1366
1367 if ( stats[clientnum]->HP <= 0 )
1368 {
1369 break;
1370 }
1371
1372 if ( *inputPressed(joyimpulses[INJOY_MENU_DROP_ITEM])
1373 && !itemMenuOpen && !selectedItem && selectedChestSlot < 0
1374 && selectedShopSlot < 0 && selectedIdentifySlot < 0 && selectedRemoveCurseSlot < 0
1375 && GenericGUI.selectedSlot < 0 )
1376 {
1377 *inputPressed(joyimpulses[INJOY_MENU_DROP_ITEM]) = 0;
1378 if ( dropItem(item, clientnum) )
1379 {
1380 item = nullptr;
1381 }
1382 }
1383
1384 bool disableItemUsage = false;
1385 if ( item )
1386 {
1387 if ( players[clientnum] && players[clientnum]->entity && players[clientnum]->entity->effectShapeshift != NOTHING )
1388 {
1389 // shape shifted, disable some items
1390 if ( !item->usableWhileShapeshifted(stats[clientnum]) )
1391 {
1392 disableItemUsage = true;
1393 }
1394 }
1395 if ( client_classes[clientnum] == CLASS_SHAMAN )
1396 {
1397 if ( item->type == SPELL_ITEM && !(playerUnlockedShamanSpell(clientnum, item)) )
1398 {
1399 disableItemUsage = true;
1400 }
1401 }
1402 }
1403
1404 // handle clicking
1405 if ( (mousestatus[SDL_BUTTON_LEFT]
1406 || (*inputPressed(joyimpulses[INJOY_MENU_LEFT_CLICK])
1407 && selectedChestSlot < 0 && selectedShopSlot < 0
1408 && selectedIdentifySlot < 0 && selectedRemoveCurseSlot < 0
1409 && GenericGUI.selectedSlot < 0))
1410 && !selectedItem && !itemMenuOpen )
1411 {
1412 if ( !(*inputPressed(joyimpulses[INJOY_MENU_LEFT_CLICK])) && (keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT]) )
1413 {
1414 if ( dropItem(item, clientnum) ) // Quick item drop
1415 {
1416 item = nullptr;
1417 }
1418 }
1419 else
1420 {
1421 selectedItem = item;
1422 //itemSelectBehavior = BEHAVIOR_MOUSE;
1423 playSound(139, 64); // click sound
1424
1425 toggleclick = false; //Default reset. Otherwise will break mouse support after using gamepad once to trigger a context menu.
1426
1427 if ( *inputPressed(joyimpulses[INJOY_MENU_LEFT_CLICK]) )
1428 {
1429 *inputPressed(joyimpulses[INJOY_MENU_LEFT_CLICK]) = 0;
1430 //itemSelectBehavior = BEHAVIOR_GAMEPAD;
1431 toggleclick = true;
1432 mousestatus[SDL_BUTTON_LEFT] = 0;
1433 //TODO: Change the mouse cursor to THE HAND.
1434 }
1435 }
1436 }
1437 else if ( (mousestatus[SDL_BUTTON_RIGHT]
1438 || (*inputPressed(joyimpulses[INJOY_MENU_USE])
1439 && selectedChestSlot < 0 && selectedShopSlot < 0
1440 && selectedIdentifySlot < 0 && selectedRemoveCurseSlot < 0
1441 && GenericGUI.selectedSlot < 0))
1442 && !itemMenuOpen && !selectedItem )
1443 {
1444 if ( (keystatus[SDL_SCANCODE_LSHIFT] || keystatus[SDL_SCANCODE_RSHIFT]) && !(*inputPressed(joyimpulses[INJOY_MENU_USE]) && selectedChestSlot < 0) ) //TODO: selected shop slot, identify, remove curse?
1445 {
1446 // auto-appraise the item
1447 identifygui_active = false;
1448 identifygui_appraising = true;
1449 identifyGUIIdentify(item);
1450 mousestatus[SDL_BUTTON_RIGHT] = 0;
1451
1452 //Cleanup identify GUI gamecontroller code here.
1453 selectedIdentifySlot = -1;
1454 }
1455 else if ( !disableItemUsage && (itemCategory(item) == POTION || itemCategory(item) == SPELLBOOK || item->type == FOOD_CREAMPIE) &&
1456 (keystatus[SDL_SCANCODE_LALT] || keystatus[SDL_SCANCODE_RALT])
1457 && !(*inputPressed(joyimpulses[INJOY_MENU_USE])) )
1458 {
1459 mousestatus[SDL_BUTTON_RIGHT] = 0;
1460 // force equip potion/spellbook
1461 playerTryEquipItemAndUpdateServer(item);
1462 }
1463 else
1464 {
1465 // open a drop-down menu of options for "using" the item
1466 itemMenuOpen = true;
1467 itemMenuX = mousex + 8;
1468 itemMenuY = mousey;
1469 itemMenuSelected = 0;
1470 itemMenuItem = item->uid;
1471
1472 toggleclick = false; //Default reset. Otherwise will break mouse support after using gamepad once to trigger a context menu.
1473
1474 if ( *inputPressed(joyimpulses[INJOY_MENU_USE]) )
1475 {
1476 *inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
1477 toggleclick = true;
1478 }
1479 }
1480 }
1481
1482 bool numkey_quick_add = hotbar_numkey_quick_add;
1483 if ( item && itemCategory(item) == SPELL_CAT && item->appearance >= 1000 &&
1484 players[clientnum] && players[clientnum]->entity && players[clientnum]->entity->effectShapeshift )
1485 {
1486 if ( canUseShapeshiftSpellInCurrentForm(*item) != 1 )
1487 {
1488 numkey_quick_add = false;
1489 }
1490 }
1491
1492 if ( numkey_quick_add && !command )
1493 {
1494 if ( keystatus[SDL_SCANCODE_1] )
1495 {
1496 keystatus[SDL_SCANCODE_1] = 0;
1497 hotbar[0].item = item->uid;
1498 }
1499 if ( keystatus[SDL_SCANCODE_2] )
1500 {
1501 keystatus[SDL_SCANCODE_2] = 0;
1502 hotbar[1].item = item->uid;
1503 }
1504 if ( keystatus[SDL_SCANCODE_3] )
1505 {
1506 keystatus[SDL_SCANCODE_3] = 0;
1507 hotbar[2].item = item->uid;
1508 }
1509 if ( keystatus[SDL_SCANCODE_4] )
1510 {
1511 keystatus[SDL_SCANCODE_4] = 0;
1512 hotbar[3].item = item->uid;
1513 }
1514 if ( keystatus[SDL_SCANCODE_5] )
1515 {
1516 keystatus[SDL_SCANCODE_5] = 0;
1517 hotbar[4].item = item->uid;
1518 }
1519 if ( keystatus[SDL_SCANCODE_6] )
1520 {
1521 keystatus[SDL_SCANCODE_6] = 0;
1522 hotbar[5].item = item->uid;
1523 }
1524 if ( keystatus[SDL_SCANCODE_7] )
1525 {
1526 keystatus[SDL_SCANCODE_7] = 0;
1527 hotbar[6].item = item->uid;
1528 }
1529 if ( keystatus[SDL_SCANCODE_8] )
1530 {
1531 keystatus[SDL_SCANCODE_8] = 0;
1532 hotbar[7].item = item->uid;
1533 }
1534 if ( keystatus[SDL_SCANCODE_9] )
1535 {
1536 keystatus[SDL_SCANCODE_9] = 0;
1537 hotbar[8].item = item->uid;
1538 }
1539 if ( keystatus[SDL_SCANCODE_0] )
1540 {
1541 keystatus[SDL_SCANCODE_0] = 0;
1542 hotbar[9].item = item->uid;
1543 }
1544 }
1545 break;
1546 }
1547 }
1548 }
1549 }
1550 else if ( stats[clientnum]->HP > 0 )
1551 {
1552 // releasing items
1553 releaseItem(x, y);
1554 }
1555
1556 itemContextMenu();
1557 }
1558
itemMenuSkipRow1ForShopsAndChests(const Item & item)1559 inline bool itemMenuSkipRow1ForShopsAndChests(const Item& item)
1560 {
1561 if ( (openedChest[clientnum] || gui_mode == GUI_MODE_SHOP)
1562 && (itemCategory(&item) == POTION || item.type == TOOL_ALEMBIC || item.type == TOOL_TINKERING_KIT || itemCategory(&item) == SPELLBOOK) )
1563 {
1564 return true;
1565 }
1566 return false;
1567 }
1568
1569 /*
1570 * Helper function to drawItemMenuSlots. Draws the empty window for an individual item context menu slot.
1571 */
drawItemMenuSlot(int x,int y,int width,int height,bool selected=false)1572 inline void drawItemMenuSlot(int x, int y, int width, int height, bool selected = false)
1573 {
1574 if (selected)
1575 {
1576 drawDepressed(x, y, x + width, y + height);
1577 }
1578 else
1579 {
1580 drawWindow(x, y, x + width, y + height);
1581 }
1582 }
1583
1584 /*
1585 * Helper function to itemContextMenu(). Draws the context menu slots.
1586 */
drawItemMenuSlots(const Item & item,int slot_width,int slot_height)1587 inline void drawItemMenuSlots(const Item& item, int slot_width, int slot_height)
1588 {
1589 //Draw the action select boxes. "Appraise", "Use, "Equip", etc.
1590 int current_x = itemMenuX;
1591 int current_y = itemMenuY;
1592 drawItemMenuSlot(current_x, current_y, slot_width, slot_height, itemMenuSelected == 0); //Option 0 => Store in chest, sell, use.
1593 if (itemCategory(&item) != SPELL_CAT)
1594 {
1595 if ( itemMenuSkipRow1ForShopsAndChests(item) )
1596 {
1597 current_y += slot_height;
1598 drawItemMenuSlot(current_x, current_y, slot_width, slot_height, itemMenuSelected == 2); //Option 1 => appraise
1599
1600 current_y += slot_height;
1601 drawItemMenuSlot(current_x, current_y, slot_width, slot_height, itemMenuSelected == 3); //Option 2 => drop
1602 return; // only draw 3 lines.
1603 }
1604
1605 current_y += slot_height;
1606 drawItemMenuSlot(current_x, current_y, slot_width, slot_height, itemMenuSelected == 1); //Option 1 => wield, unwield, use, appraise
1607
1608 current_y += slot_height;
1609 drawItemMenuSlot(current_x, current_y, slot_width, slot_height, itemMenuSelected == 2); //Option 2 => appraise, drop
1610
1611 if ( stats[clientnum] && stats[clientnum]->type == AUTOMATON && itemIsConsumableByAutomaton(item)
1612 && itemCategory(&item) != FOOD )
1613 {
1614 current_y += slot_height;
1615 drawItemMenuSlot(current_x, current_y, slot_width, slot_height, itemMenuSelected == 3); //Option 3 => drop
1616 }
1617 if (itemCategory(&item) == POTION || item.type == TOOL_ALEMBIC || item.type == TOOL_TINKERING_KIT )
1618 {
1619 current_y += slot_height;
1620 drawItemMenuSlot(current_x, current_y, slot_width, slot_height, itemMenuSelected == 3); //Option 3 => drop
1621 }
1622 else if ( itemCategory(&item) == SPELLBOOK )
1623 {
1624 current_y += slot_height;
1625 drawItemMenuSlot(current_x, current_y, slot_width, slot_height, itemMenuSelected == 3); //Option 3 => drop
1626 }
1627 else if ( item.type == FOOD_CREAMPIE )
1628 {
1629 current_y += slot_height;
1630 drawItemMenuSlot(current_x, current_y, slot_width, slot_height, itemMenuSelected == 3); //Option 3 => drop
1631 }
1632 }
1633 }
1634
1635 /*
1636 * drawOptionX() - renders the specified option in the given item option menu slot.
1637 */
drawOptionStoreInChest(int x,int y)1638 inline void drawOptionStoreInChest(int x, int y)
1639 {
1640 int width = 0;
1641 TTF_SizeUTF8(ttf12, language[344], &width, nullptr);
1642 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[344]);
1643 }
1644
drawOptionSell(int x,int y)1645 inline void drawOptionSell(int x, int y)
1646 {
1647 int width = 0;
1648 TTF_SizeUTF8(ttf12, language[345], &width, nullptr);
1649 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[345]);
1650 }
1651
drawOptionUse(const Item & item,int x,int y)1652 inline void drawOptionUse(const Item& item, int x, int y)
1653 {
1654 int width = 0;
1655 ttfPrintTextFormatted(ttf12, x + 50 - strlen(itemUseString(&item)) * TTF12_WIDTH / 2, y + 4, "%s", itemUseString(&item));
1656 }
1657
drawOptionUnwield(int x,int y)1658 inline void drawOptionUnwield(int x, int y)
1659 {
1660 int width = 0;
1661 TTF_SizeUTF8(ttf12, language[323], &width, nullptr);
1662 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[323]);
1663 }
1664
drawOptionWield(int x,int y)1665 inline void drawOptionWield(int x, int y)
1666 {
1667 int width = 0;
1668 TTF_SizeUTF8(ttf12, language[324], &width, nullptr);
1669 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[324]);
1670 }
1671
drawOptionAppraise(int x,int y)1672 inline void drawOptionAppraise(int x, int y)
1673 {
1674 int width = 0;
1675 TTF_SizeUTF8(ttf12, language[1161], &width, nullptr);
1676 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[1161]);
1677 }
1678
drawOptionDrop(int x,int y)1679 inline void drawOptionDrop(int x, int y)
1680 {
1681 int width = 0;
1682 TTF_SizeUTF8(ttf12, language[1162], &width, nullptr);
1683 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[1162]);
1684 }
1685
1686 /*
1687 * Helper function to itemContextMenu(). Draws a spell's options.
1688 */
drawItemMenuOptionSpell(const Item & item,int x,int y)1689 inline void drawItemMenuOptionSpell(const Item& item, int x, int y)
1690 {
1691 if (itemCategory(&item) != SPELL_CAT)
1692 {
1693 return;
1694 }
1695
1696 int width = 0;
1697
1698 //Option 0.
1699 drawOptionUse(item, x, y);
1700 }
1701
1702 /*
1703 * Helper function to itemContextMenu(). Draws a potion's options.
1704 */
drawItemMenuOptionPotion(const Item & item,int x,int y,int height,bool is_potion_bad=false)1705 inline void drawItemMenuOptionPotion(const Item& item, int x, int y, int height, bool is_potion_bad = false)
1706 {
1707 if (itemCategory(&item) != POTION && item.type != TOOL_ALEMBIC && item.type != TOOL_TINKERING_KIT )
1708 {
1709 return;
1710 }
1711
1712 int width = 0;
1713
1714 //Option 0.
1715 if (openedChest[clientnum])
1716 {
1717 drawOptionStoreInChest(x, y);
1718 }
1719 else if (gui_mode == GUI_MODE_SHOP)
1720 {
1721 drawOptionSell(x, y);
1722 }
1723 else
1724 {
1725 if ( item.type == TOOL_TINKERING_KIT )
1726 {
1727 if ( itemIsEquipped(&item, clientnum) )
1728 {
1729 drawOptionUnwield(x, y);
1730 }
1731 else
1732 {
1733 drawOptionWield(x, y);
1734 }
1735 }
1736 else if (!is_potion_bad)
1737 {
1738 drawOptionUse(item, x, y);
1739 }
1740 else
1741 {
1742 if (itemIsEquipped(&item, clientnum))
1743 {
1744 drawOptionUnwield(x, y);
1745 }
1746 else
1747 {
1748 drawOptionWield(x, y);
1749 }
1750 }
1751 }
1752 y += height;
1753
1754 //Option 1.
1755 if ( itemMenuSkipRow1ForShopsAndChests(item) )
1756 {
1757 // skip this row.
1758 }
1759 else
1760 {
1761 if (!is_potion_bad)
1762 {
1763 if ( item.type == TOOL_ALEMBIC )
1764 {
1765 TTF_SizeUTF8(ttf12, language[3341], &width, nullptr);
1766 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[3341]);
1767 }
1768 else if ( item.type == TOOL_TINKERING_KIT )
1769 {
1770 TTF_SizeUTF8(ttf12, language[3670], &width, nullptr);
1771 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[3670]);
1772 }
1773 else if (itemIsEquipped(&item, clientnum))
1774 {
1775 drawOptionUnwield(x, y);
1776 }
1777 else
1778 {
1779 drawOptionWield(x, y);
1780 }
1781 }
1782 else
1783 {
1784 drawOptionUse(item, x, y);
1785 }
1786 y += height;
1787 }
1788
1789 //Option 1.
1790 drawOptionAppraise(x, y);
1791 y += height;
1792
1793 //Option 2.
1794 drawOptionDrop(x, y);
1795 }
1796
drawItemMenuOptionAutomaton(const Item & item,int x,int y,int height,bool is_potion_bad)1797 inline void drawItemMenuOptionAutomaton(const Item& item, int x, int y, int height, bool is_potion_bad)
1798 {
1799 int width = 0;
1800
1801 //Option 0.
1802 if ( openedChest[clientnum] )
1803 {
1804 drawOptionStoreInChest(x, y);
1805 }
1806 else if ( gui_mode == GUI_MODE_SHOP )
1807 {
1808 drawOptionSell(x, y);
1809 }
1810 else
1811 {
1812 if ( !is_potion_bad )
1813 {
1814 if ( !itemIsConsumableByAutomaton(item) || (itemCategory(&item) != FOOD && item.type != TOOL_METAL_SCRAP && item.type != TOOL_MAGIC_SCRAP) )
1815 {
1816 drawOptionUse(item, x, y);
1817 }
1818 else
1819 {
1820 TTF_SizeUTF8(ttf12, language[3487], &width, nullptr);
1821 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[3487]);
1822 }
1823 }
1824 else
1825 {
1826 if ( itemIsEquipped(&item, clientnum) )
1827 {
1828 drawOptionUnwield(x, y);
1829 }
1830 else
1831 {
1832 drawOptionWield(x, y);
1833 }
1834 }
1835 }
1836 y += height;
1837
1838 //Option 1.
1839 if ( item.type == TOOL_METAL_SCRAP || item.type == TOOL_MAGIC_SCRAP )
1840 {
1841 TTF_SizeUTF8(ttf12, language[1881], &width, nullptr);
1842 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[1881]);
1843 y += height;
1844 }
1845 else if ( itemCategory(&item) != FOOD )
1846 {
1847 TTF_SizeUTF8(ttf12, language[3487], &width, nullptr);
1848 ttfPrintText(ttf12, x + 50 - width / 2, y + 4, language[3487]);
1849 y += height;
1850 }
1851
1852 //Option 1.
1853 drawOptionAppraise(x, y);
1854 y += height;
1855
1856 //Option 2.
1857 drawOptionDrop(x, y);
1858 }
1859
1860 /*
1861 * Helper function to itemContextMenu(). Draws all other items's options.
1862 */
drawItemMenuOptionGeneric(const Item & item,int x,int y,int height)1863 inline void drawItemMenuOptionGeneric(const Item& item, int x, int y, int height)
1864 {
1865 if (itemCategory(&item) == SPELL_CAT || itemCategory(&item) == POTION)
1866 {
1867 return;
1868 }
1869
1870 int width = 0;
1871
1872 //Option 0.
1873 if (openedChest[clientnum])
1874 {
1875 drawOptionStoreInChest(x, y);
1876 }
1877 else if (gui_mode == GUI_MODE_SHOP)
1878 {
1879 drawOptionSell(x, y);
1880 }
1881 else
1882 {
1883 drawOptionUse(item, x, y);
1884 }
1885 y += height;
1886
1887 //Option 1
1888 drawOptionAppraise(x, y);
1889 y += height;
1890
1891 //Option 2
1892 drawOptionDrop(x, y);
1893 }
1894
drawItemMenuOptionSpellbook(const Item & item,int x,int y,int height,bool learnedSpell)1895 inline void drawItemMenuOptionSpellbook(const Item& item, int x, int y, int height, bool learnedSpell)
1896 {
1897 int width = 0;
1898
1899 //Option 0.
1900 if ( openedChest[clientnum] )
1901 {
1902 drawOptionStoreInChest(x, y);
1903 }
1904 else if ( gui_mode == GUI_MODE_SHOP )
1905 {
1906 drawOptionSell(x, y);
1907 }
1908 else
1909 {
1910 if ( learnedSpell )
1911 {
1912 if ( itemIsEquipped(&item, clientnum) )
1913 {
1914 drawOptionUnwield(x, y);
1915 }
1916 else
1917 {
1918 drawOptionWield(x, y);
1919 }
1920 }
1921 else
1922 {
1923 drawOptionUse(item, x, y);
1924 }
1925 }
1926 y += height;
1927
1928 //Option 1
1929 if ( itemMenuSkipRow1ForShopsAndChests(item) )
1930 {
1931 // skip this row.
1932 }
1933 else
1934 {
1935 if ( learnedSpell )
1936 {
1937 drawOptionUse(item, x, y);
1938 }
1939 else
1940 {
1941 if ( itemIsEquipped(&item, clientnum) )
1942 {
1943 drawOptionUnwield(x, y);
1944 }
1945 else
1946 {
1947 drawOptionWield(x, y);
1948 }
1949 }
1950 y += height;
1951 }
1952
1953 //Option 2
1954 drawOptionAppraise(x, y);
1955 y += height;
1956
1957 //Option 3
1958 drawOptionDrop(x, y);
1959 }
1960
drawItemMenuOptionUsableAndWieldable(const Item & item,int x,int y,int height)1961 inline void drawItemMenuOptionUsableAndWieldable(const Item& item, int x, int y, int height)
1962 {
1963 int width = 0;
1964
1965 //Option 0.
1966 if ( openedChest[clientnum] )
1967 {
1968 drawOptionStoreInChest(x, y);
1969 }
1970 else if ( gui_mode == GUI_MODE_SHOP )
1971 {
1972 drawOptionSell(x, y);
1973 }
1974 else
1975 {
1976 drawOptionUse(item, x, y);
1977 }
1978 y += height;
1979
1980 //Option 1
1981 if ( itemIsEquipped(&item, clientnum) )
1982 {
1983 drawOptionUnwield(x, y);
1984 }
1985 else
1986 {
1987 drawOptionWield(x, y);
1988 }
1989 y += height;
1990
1991 //Option 2
1992 drawOptionAppraise(x, y);
1993 y += height;
1994
1995 //Option 3
1996 drawOptionDrop(x, y);
1997 }
1998
1999 /*
2000 * Helper function to itemContextMenu(). Changes the currently selected slot based on the mouse cursor's position.
2001 */
selectItemMenuSlot(const Item & item,int x,int y,int slot_width,int slot_height)2002 inline void selectItemMenuSlot(const Item& item, int x, int y, int slot_width, int slot_height)
2003 {
2004 int current_x = itemMenuX;
2005 int current_y = itemMenuY;
2006
2007 if (mousey < current_y - slot_height) //Check if out of bounds above.
2008 {
2009 itemMenuSelected = -1; //For canceling out.
2010 }
2011 if (mousey >= current_y - 2 && mousey < current_y + slot_height)
2012 {
2013 itemMenuSelected = 0;
2014 }
2015 if (itemCategory(&item) != SPELL_CAT)
2016 {
2017 if ( itemMenuSkipRow1ForShopsAndChests(item) )
2018 {
2019 current_y += slot_height;
2020 if ( mousey >= current_y && mousey < current_y + slot_height )
2021 {
2022 itemMenuSelected = 2;
2023 }
2024 current_y += slot_height;
2025 if ( mousey >= current_y && mousey < current_y + slot_height )
2026 {
2027 itemMenuSelected = 3;
2028 }
2029 }
2030 else
2031 {
2032 current_y += slot_height;
2033 if (mousey >= current_y && mousey < current_y + slot_height)
2034 {
2035 itemMenuSelected = 1;
2036 }
2037 current_y += slot_height;
2038 if (mousey >= current_y && mousey < current_y + slot_height)
2039 {
2040 itemMenuSelected = 2;
2041 }
2042 current_y += slot_height;
2043 if ( itemCategory(&item) == POTION || itemCategory(&item) == SPELLBOOK || item.type == FOOD_CREAMPIE
2044 || (stats[clientnum] && stats[clientnum]->type == AUTOMATON && itemIsConsumableByAutomaton(item)
2045 && itemCategory(&item) != FOOD) )
2046 {
2047 if (mousey >= current_y && mousey < current_y + slot_height)
2048 {
2049 itemMenuSelected = 3;
2050 }
2051 current_y += slot_height;
2052 }
2053 }
2054 }
2055
2056 if (mousey >= current_y + slot_height) //Check if out of bounds below.
2057 {
2058 itemMenuSelected = -1; //For canceling out.
2059 }
2060
2061 if (mousex >= current_x + slot_width) //Check if out of bounds to the right.
2062 {
2063 itemMenuSelected = -1; //For canceling out.
2064 }
2065 if ( mousex < itemMenuX - 10 || (mousex < itemMenuX && right_click_protect) ) //Check if out of bounds to the left.
2066 {
2067 itemMenuSelected = -1; //For canceling out.
2068 }
2069 }
2070
2071 /*
2072 * execteItemMenuOptionX() - Helper function to itemContextMenu(). Executes the specified menu option for the item.
2073 */
executeItemMenuOption0(Item * item,bool is_potion_bad,bool learnedSpell)2074 inline void executeItemMenuOption0(Item* item, bool is_potion_bad, bool learnedSpell)
2075 {
2076 if (!item)
2077 {
2078 return;
2079 }
2080
2081 bool disableItemUsage = false;
2082 if ( players[clientnum] && players[clientnum]->entity )
2083 {
2084 if ( players[clientnum]->entity->effectShapeshift != NOTHING )
2085 {
2086 // shape shifted, disable some items
2087 if ( !item->usableWhileShapeshifted(stats[clientnum]) )
2088 {
2089 disableItemUsage = true;
2090 }
2091 }
2092 else
2093 {
2094 if ( itemCategory(item) == SPELL_CAT && item->appearance >= 1000 )
2095 {
2096 if ( canUseShapeshiftSpellInCurrentForm(*item) != 1 )
2097 {
2098 disableItemUsage = true;
2099 }
2100 }
2101 }
2102 }
2103
2104 if ( client_classes[clientnum] == CLASS_SHAMAN )
2105 {
2106 if ( item->type == SPELL_ITEM && !(playerUnlockedShamanSpell(clientnum, item)) )
2107 {
2108 disableItemUsage = true;
2109 }
2110 }
2111
2112 if (openedChest[clientnum] && itemCategory(item) != SPELL_CAT)
2113 {
2114 //Option 0 = store in chest.
2115 openedChest[clientnum]->addItemToChestFromInventory(clientnum, item, false);
2116 }
2117 else if (gui_mode == GUI_MODE_SHOP && itemCategory(item) != SPELL_CAT)
2118 {
2119 //Option 0 = sell.
2120 sellItemToShop(item);
2121 }
2122 else
2123 {
2124 if (!is_potion_bad && !learnedSpell)
2125 {
2126 //Option 0 = use.
2127 if ( !(isItemEquippableInShieldSlot(item) && cast_animation.active_spellbook) )
2128 {
2129 if ( !disableItemUsage )
2130 {
2131 if ( stats[clientnum] && stats[clientnum]->type == AUTOMATON
2132 && (item->type == TOOL_METAL_SCRAP || item->type == TOOL_MAGIC_SCRAP) )
2133 {
2134 // consume item
2135 if ( multiplayer == CLIENT )
2136 {
2137 strcpy((char*)net_packet->data, "FODA");
2138 SDLNet_Write32((Uint32)item->type, &net_packet->data[4]);
2139 SDLNet_Write32((Uint32)item->status, &net_packet->data[8]);
2140 SDLNet_Write32((Uint32)item->beatitude, &net_packet->data[12]);
2141 SDLNet_Write32((Uint32)item->count, &net_packet->data[16]);
2142 SDLNet_Write32((Uint32)item->appearance, &net_packet->data[20]);
2143 net_packet->data[24] = item->identified;
2144 net_packet->data[25] = clientnum;
2145 net_packet->address.host = net_server.host;
2146 net_packet->address.port = net_server.port;
2147 net_packet->len = 26;
2148 sendPacketSafe(net_sock, -1, net_packet, 0);
2149 }
2150 item_FoodAutomaton(item, clientnum);
2151 }
2152 else
2153 {
2154 useItem(item, clientnum);
2155 }
2156 }
2157 else
2158 {
2159 if ( client_classes[clientnum] == CLASS_SHAMAN && item->type == SPELL_ITEM )
2160 {
2161 messagePlayer(clientnum, language[3488]); // unable to use with current level.
2162 }
2163 else
2164 {
2165 messagePlayer(clientnum, language[3432]); // unable to use in current form message.
2166 }
2167 }
2168 }
2169 }
2170 else
2171 {
2172 if ( !disableItemUsage )
2173 {
2174 //Option 0 = equip.
2175 playerTryEquipItemAndUpdateServer(item);
2176 }
2177 else
2178 {
2179 if ( client_classes[clientnum] == CLASS_SHAMAN && item->type == SPELL_ITEM )
2180 {
2181 messagePlayer(clientnum, language[3488]); // unable to use with current level.
2182 }
2183 else
2184 {
2185 messagePlayer(clientnum, language[3432]); // unable to use in current form message.
2186 }
2187 }
2188 }
2189 }
2190 }
2191
executeItemMenuOption1(Item * item,bool is_potion_bad,bool learnedSpell)2192 inline void executeItemMenuOption1(Item* item, bool is_potion_bad, bool learnedSpell)
2193 {
2194 if (!item || itemCategory(item) == SPELL_CAT)
2195 {
2196 return;
2197 }
2198
2199 bool disableItemUsage = false;
2200 if ( players[clientnum] && players[clientnum]->entity && players[clientnum]->entity->effectShapeshift != NOTHING )
2201 {
2202 // shape shifted, disable some items
2203 if ( !item->usableWhileShapeshifted(stats[clientnum]) )
2204 {
2205 disableItemUsage = true;
2206 }
2207 if ( item->type == FOOD_CREAMPIE )
2208 {
2209 disableItemUsage = true;
2210 }
2211 }
2212
2213 if ( client_classes[clientnum] == CLASS_SHAMAN )
2214 {
2215 if ( item->type == SPELL_ITEM && !(playerUnlockedShamanSpell(clientnum, item)) )
2216 {
2217 disableItemUsage = true;
2218 }
2219 }
2220
2221 if ( stats[clientnum] && stats[clientnum]->type == AUTOMATON && itemIsConsumableByAutomaton(*item)
2222 && itemCategory(item) != FOOD )
2223 {
2224 if ( item->type == TOOL_METAL_SCRAP || item->type == TOOL_MAGIC_SCRAP )
2225 {
2226 useItem(item, clientnum);
2227 }
2228 else
2229 {
2230 // consume item
2231 if ( multiplayer == CLIENT )
2232 {
2233 strcpy((char*)net_packet->data, "FODA");
2234 SDLNet_Write32((Uint32)item->type, &net_packet->data[4]);
2235 SDLNet_Write32((Uint32)item->status, &net_packet->data[8]);
2236 SDLNet_Write32((Uint32)item->beatitude, &net_packet->data[12]);
2237 SDLNet_Write32((Uint32)item->count, &net_packet->data[16]);
2238 SDLNet_Write32((Uint32)item->appearance, &net_packet->data[20]);
2239 net_packet->data[24] = item->identified;
2240 net_packet->data[25] = clientnum;
2241 net_packet->address.host = net_server.host;
2242 net_packet->address.port = net_server.port;
2243 net_packet->len = 26;
2244 sendPacketSafe(net_sock, -1, net_packet, 0);
2245 }
2246 item_FoodAutomaton(item, clientnum);
2247 }
2248 }
2249 else if ( item->type == TOOL_ALEMBIC )
2250 {
2251 // experimenting!
2252 if ( !disableItemUsage )
2253 {
2254 GenericGUI.openGUI(GUI_TYPE_ALCHEMY, true, item);
2255 }
2256 else
2257 {
2258 messagePlayer(clientnum, language[3432]); // unable to use in current form message.
2259 }
2260 }
2261 else if ( item->type == TOOL_TINKERING_KIT )
2262 {
2263 if ( !disableItemUsage )
2264 {
2265 GenericGUI.openGUI(GUI_TYPE_TINKERING, item);
2266 }
2267 else
2268 {
2269 messagePlayer(clientnum, language[3432]); // unable to use in current form message.
2270 }
2271 }
2272 else if (itemCategory(item) != POTION && itemCategory(item) != SPELLBOOK && item->type != FOOD_CREAMPIE)
2273 {
2274 //Option 1 = appraise.
2275 identifygui_active = false;
2276 identifygui_appraising = true;
2277
2278 //Cleanup identify GUI gamecontroller code here.
2279 selectedIdentifySlot = -1;
2280
2281 identifyGUIIdentify(item);
2282 }
2283 else
2284 {
2285 if ( !disableItemUsage )
2286 {
2287 if (!is_potion_bad && !learnedSpell)
2288 {
2289 //Option 1 = equip.
2290 playerTryEquipItemAndUpdateServer(item);
2291 }
2292 else
2293 {
2294 //Option 1 = drink/use/whatever.
2295 if ( !(isItemEquippableInShieldSlot(item) && cast_animation.active_spellbook) )
2296 {
2297 useItem(item, clientnum);
2298 }
2299 }
2300 }
2301 else
2302 {
2303 if ( client_classes[clientnum] == CLASS_SHAMAN && item->type == SPELL_ITEM )
2304 {
2305 messagePlayer(clientnum, language[3488]); // unable to use with current level.
2306 }
2307 else
2308 {
2309 messagePlayer(clientnum, language[3432]); // unable to use in current form message.
2310 }
2311 }
2312 }
2313 }
2314
executeItemMenuOption2(Item * item)2315 inline void executeItemMenuOption2(Item* item)
2316 {
2317 if (!item || itemCategory(item) == SPELL_CAT)
2318 {
2319 return;
2320 }
2321
2322 if ( stats[clientnum] && stats[clientnum]->type == AUTOMATON && itemIsConsumableByAutomaton(*item) && itemCategory(item) != FOOD )
2323 {
2324 //Option 2 = appraise.
2325 identifygui_active = false;
2326 identifygui_appraising = true;
2327
2328 //Cleanup identify GUI gamecontroller code here.
2329 selectedIdentifySlot = -1;
2330
2331 identifyGUIIdentify(item);
2332 }
2333 else if ( itemCategory(item) != POTION && item->type != TOOL_ALEMBIC
2334 && itemCategory(item) != SPELLBOOK && item->type != TOOL_TINKERING_KIT
2335 && item->type != FOOD_CREAMPIE )
2336 {
2337 //Option 2 = drop.
2338 dropItem(item, clientnum);
2339 }
2340 else
2341 {
2342 //Option 2 = appraise.
2343 identifygui_active = false;
2344 identifygui_appraising = true;
2345
2346 //Cleanup identify GUI gamecontroller code here.
2347 selectedIdentifySlot = -1;
2348
2349 identifyGUIIdentify(item);
2350 }
2351 }
2352
executeItemMenuOption3(Item * item)2353 inline void executeItemMenuOption3(Item* item)
2354 {
2355 if ( !item )
2356 {
2357 return;
2358 }
2359 if ( stats[clientnum] && stats[clientnum]->type == AUTOMATON && itemIsConsumableByAutomaton(*item) && itemCategory(item) != FOOD )
2360 {
2361 //Option 3 = drop (automaton has 4 options on consumable items).
2362 dropItem(item, clientnum);
2363 return;
2364 }
2365 if ( itemCategory(item) != POTION && item->type != TOOL_ALEMBIC
2366 && itemCategory(item) != SPELLBOOK && item->type != TOOL_TINKERING_KIT
2367 && item->type != FOOD_CREAMPIE )
2368 {
2369 return;
2370 }
2371
2372 //Option 3 = drop (only spellbooks/potions have option 3).
2373 dropItem(item, clientnum);
2374 }
2375
itemContextMenu()2376 void itemContextMenu()
2377 {
2378 if (!itemMenuOpen)
2379 {
2380 return;
2381 }
2382
2383 if ( *inputPressed(joyimpulses[INJOY_MENU_CANCEL]) )
2384 {
2385 *inputPressed(joyimpulses[INJOY_MENU_CANCEL]) = 0;
2386 itemMenuOpen = false;
2387 //Warp cursor back into inventory, for gamepad convenience.
2388 SDL_WarpMouseInWindow(screen, INVENTORY_STARTX + (uidToItem(itemMenuItem)->x * INVENTORY_SLOTSIZE) + (INVENTORY_SLOTSIZE / 2), INVENTORY_STARTY + (uidToItem(itemMenuItem)->y * INVENTORY_SLOTSIZE) + (INVENTORY_SLOTSIZE / 2));
2389 return;
2390 }
2391
2392 //Item *item = uidToItem(itemMenuItem);
2393
2394 Item* current_item = uidToItem(itemMenuItem);
2395 if (!current_item)
2396 {
2397 itemMenuOpen = false;
2398 return;
2399 }
2400
2401 bool is_potion_bad = false;
2402 if (current_item->identified)
2403 {
2404 is_potion_bad = isPotionBad(*current_item);
2405 }
2406 if ( current_item->type == POTION_EMPTY )
2407 {
2408 is_potion_bad = true; //So that you wield empty potions by default.
2409 }
2410
2411 const int slot_width = 100;
2412 const int slot_height = 20;
2413
2414 if ( game_controller->handleItemContextMenu(*current_item) )
2415 {
2416 SDL_WarpMouseInWindow(screen, itemMenuX + (slot_width / 2), itemMenuY + (itemMenuSelected * slot_height) + (slot_height / 2));
2417 }
2418
2419 drawItemMenuSlots(*current_item, slot_width, slot_height);
2420 bool learnedSpell = false;
2421
2422 if (itemCategory(current_item) == SPELL_CAT)
2423 {
2424 drawItemMenuOptionSpell(*current_item, itemMenuX, itemMenuY);
2425 }
2426 else
2427 {
2428 if ( stats[clientnum] && stats[clientnum]->type == AUTOMATON && itemIsConsumableByAutomaton(*current_item)
2429 && current_item->type != FOOD_CREAMPIE )
2430 {
2431 drawItemMenuOptionAutomaton(*current_item, itemMenuX, itemMenuY, slot_height, is_potion_bad);
2432 }
2433 else if ( current_item->type == TOOL_ALEMBIC || current_item->type == TOOL_TINKERING_KIT )
2434 {
2435 drawItemMenuOptionPotion(*current_item, itemMenuX, itemMenuY, slot_height, false);
2436 }
2437 else if (itemCategory(current_item) == POTION || current_item->type == TOOL_ALEMBIC)
2438 {
2439 drawItemMenuOptionPotion(*current_item, itemMenuX, itemMenuY, slot_height, is_potion_bad);
2440 }
2441 else if ( itemCategory(current_item) == SPELLBOOK )
2442 {
2443 learnedSpell = playerLearnedSpellbook(current_item);
2444 if ( itemIsEquipped(current_item, clientnum) )
2445 {
2446 learnedSpell = true; // equipped spellbook will unequip on use.
2447 }
2448 else if ( players[clientnum] && players[clientnum]->entity )
2449 {
2450 if ( players[clientnum]->entity->effectShapeshift == CREATURE_IMP )
2451 {
2452 learnedSpell = true; // imps can't learn spells but always equip books.
2453 }
2454 else if ( stats[clientnum] && stats[clientnum]->type == GOBLIN )
2455 {
2456 learnedSpell = true; // goblinos can't learn spells but always equip books.
2457 }
2458 }
2459
2460 drawItemMenuOptionSpellbook(*current_item, itemMenuX, itemMenuY, slot_height, learnedSpell);
2461 }
2462 else if ( current_item->type == FOOD_CREAMPIE )
2463 {
2464 drawItemMenuOptionUsableAndWieldable(*current_item, itemMenuX, itemMenuY, slot_height);
2465 }
2466 else
2467 {
2468 drawItemMenuOptionGeneric(*current_item, itemMenuX, itemMenuY, slot_height); //Every other item besides potions and spells.
2469 }
2470 }
2471
2472 selectItemMenuSlot(*current_item, itemMenuX, itemMenuY, slot_width, slot_height);
2473
2474 bool activateSelection = false;
2475 if (!mousestatus[SDL_BUTTON_RIGHT] && !toggleclick)
2476 {
2477 activateSelection = true;
2478 }
2479 else if ( *inputPressed(joyimpulses[INJOY_MENU_USE]) )
2480 {
2481 *inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
2482 activateSelection = true;
2483 //Warp cursor back into inventory, for gamepad convenience.
2484 SDL_WarpMouseInWindow(screen, INVENTORY_STARTX + (selected_inventory_slot_x * INVENTORY_SLOTSIZE) + (INVENTORY_SLOTSIZE / 2), INVENTORY_STARTY + (selected_inventory_slot_y * INVENTORY_SLOTSIZE) + (INVENTORY_SLOTSIZE / 2));
2485 }
2486
2487 if (activateSelection)
2488 {
2489 switch (itemMenuSelected)
2490 {
2491 case 0:
2492 executeItemMenuOption0(current_item, is_potion_bad, learnedSpell);
2493 break;
2494 case 1:
2495 executeItemMenuOption1(current_item, is_potion_bad, learnedSpell);
2496 break;
2497 case 2:
2498 executeItemMenuOption2(current_item);
2499 break;
2500 case 3:
2501 executeItemMenuOption3(current_item);
2502 break;
2503 default:
2504 break;
2505 }
2506
2507 //Close the menu.
2508 itemMenuOpen = false;
2509 itemMenuItem = 0;
2510 }
2511 }
2512
numItemMenuSlots(const Item & item)2513 int numItemMenuSlots(const Item& item)
2514 {
2515 int numSlots = 1; //Option 0 => store in chest, sell, use.
2516
2517 if (itemCategory(&item) != SPELL_CAT)
2518 {
2519 numSlots += 2; //Option 1 => wield, unwield, use, appraise. & Option 2 => appraise, drop
2520
2521 if (itemCategory(&item) == POTION)
2522 {
2523 numSlots += 1; //Option 3 => drop.
2524 }
2525 else if ( itemCategory(&item) == SPELLBOOK )
2526 {
2527 numSlots += 1; //Can read/equip spellbooks
2528 }
2529 else if ( item.type == FOOD_CREAMPIE )
2530 {
2531 numSlots += 1; //Can equip
2532 }
2533 }
2534
2535 return numSlots;
2536 }
2537
2538 //Used by the gamepad, primarily. Dpad buttons changes selection.
selectItemMenuSlot(const Item & item,int entry)2539 void selectItemMenuSlot(const Item& item, int entry)
2540 {
2541 if (entry > numItemMenuSlots(item))
2542 {
2543 entry = 0;
2544 }
2545 if (entry < 0)
2546 {
2547 entry = numItemMenuSlots(item);
2548 }
2549
2550 itemMenuSelected = entry;
2551 }
2552
2553 // filters out items excluded by auto_hotbar_categories
autoAddHotbarFilter(const Item & item)2554 bool autoAddHotbarFilter(const Item& item)
2555 {
2556 Category cat = itemCategory(&item);
2557 for ( int i = 0; i < NUM_HOTBAR_CATEGORIES; ++i )
2558 {
2559 if ( auto_hotbar_categories[i] )
2560 {
2561 switch ( i )
2562 {
2563 case 0: // weapons
2564 if ( cat == WEAPON )
2565 {
2566 return true;
2567 }
2568 break;
2569 case 1: // armor
2570 if ( cat == ARMOR )
2571 {
2572 return true;
2573 }
2574 break;
2575 case 2: // jewelry
2576 if ( cat == RING || cat == AMULET )
2577 {
2578 return true;
2579 }
2580 break;
2581 case 3: // books/spellbooks
2582 if ( cat == BOOK || cat == SPELLBOOK )
2583 {
2584 return true;
2585 }
2586 break;
2587 case 4: // tools
2588 if ( cat == TOOL )
2589 {
2590 return true;
2591 }
2592 break;
2593 case 5: // thrown
2594 if ( cat == THROWN || item.type == GEM_ROCK )
2595 {
2596 return true;
2597 }
2598 break;
2599 case 6: // gems
2600 if ( cat == GEM )
2601 {
2602 return true;
2603 }
2604 break;
2605 case 7: // potions
2606 if ( cat == POTION )
2607 {
2608 return true;
2609 }
2610 break;
2611 case 8: // scrolls
2612 if ( cat == SCROLL )
2613 {
2614 return true;
2615 }
2616 break;
2617 case 9: // magicstaves
2618 if ( cat == MAGICSTAFF )
2619 {
2620 return true;
2621 }
2622 break;
2623 case 10: // food
2624 if ( cat == FOOD )
2625 {
2626 return true;
2627 }
2628 break;
2629 case 11: // spells
2630 if ( cat == SPELL_CAT )
2631 {
2632 return true;
2633 }
2634 break;
2635 default:
2636 break;
2637 }
2638 }
2639 }
2640 return false;
2641 }
2642
quickStackItems()2643 void quickStackItems()
2644 {
2645 for ( node_t* node = stats[clientnum]->inventory.first; node != NULL; node = node->next )
2646 {
2647 Item* itemToStack = (Item*)node->element;
2648 if ( itemToStack && itemToStack->shouldItemStack(clientnum) )
2649 {
2650 for ( node_t* node = stats[clientnum]->inventory.first; node != NULL; node = node->next )
2651 {
2652 Item* item2 = (Item*)node->element;
2653 // if items are the same, check to see if they should stack
2654 if ( item2 && item2 != itemToStack && !itemCompare(itemToStack, item2, false) )
2655 {
2656 itemToStack->count += item2->count;
2657 if ( multiplayer == CLIENT && itemIsEquipped(itemToStack, clientnum) )
2658 {
2659 // if incrementing qty and holding item, then send "equip" for server to update their count of your held item.
2660 clientSendEquipUpdateToServer(EQUIP_ITEM_SLOT_WEAPON, EQUIP_ITEM_SUCCESS_UPDATE_QTY, clientnum,
2661 itemToStack->type, itemToStack->status, itemToStack->beatitude, itemToStack->count, itemToStack->appearance, itemToStack->identified);
2662 }
2663 if ( item2->node )
2664 {
2665 list_RemoveNode(item2->node);
2666 }
2667 else
2668 {
2669 free(item2);
2670 item2 = nullptr;
2671 }
2672 }
2673 }
2674 }
2675 }
2676 }
2677
autosortInventory()2678 void autosortInventory()
2679 {
2680 std::vector<std::pair<int, int>> autosortPairs;
2681 for ( int i = 0; i < NUM_AUTOSORT_CATEGORIES; ++i )
2682 {
2683 autosortPairs.push_back(std::make_pair(autosort_inventory_categories[i], i));
2684 }
2685
2686 for ( node_t* node = stats[clientnum]->inventory.first; node != NULL; node = node->next )
2687 {
2688 Item* item = (Item*)node->element;
2689 if ( item && (!itemIsEquipped(item, clientnum) || autosort_inventory_categories[11] != 0) && itemCategory(item) != SPELL_CAT )
2690 {
2691 item->x = -1;
2692 item->y = 0;
2693 // move all items away.
2694 }
2695 }
2696
2697 std::sort(autosortPairs.begin(), autosortPairs.end());
2698
2699 // iterate and sort from highest to lowest priority, 1 to 9
2700 for ( std::vector<std::pair<int, int>>::reverse_iterator it = autosortPairs.rbegin(); it != autosortPairs.rend(); ++it )
2701 {
2702 std::pair<int, int> tmpPair = *it;
2703 if ( tmpPair.first > 0 )
2704 {
2705 //messagePlayer(0, "priority %d, category: %d", tmpPair.first, tmpPair.second);
2706 bool invertSortDirection = false;
2707 switch ( tmpPair.second )
2708 {
2709 case 0: // weapons
2710 sortInventoryItemsOfType(WEAPON, invertSortDirection);
2711 break;
2712 case 1: // armor
2713 sortInventoryItemsOfType(ARMOR, invertSortDirection);
2714 break;
2715 case 2: // jewelry
2716 sortInventoryItemsOfType(RING, invertSortDirection);
2717 sortInventoryItemsOfType(AMULET, invertSortDirection);
2718 break;
2719 case 3: // books/spellbooks
2720 sortInventoryItemsOfType(SPELLBOOK, invertSortDirection);
2721 sortInventoryItemsOfType(BOOK, invertSortDirection);
2722 break;
2723 case 4: // tools
2724 sortInventoryItemsOfType(TOOL, invertSortDirection);
2725 break;
2726 case 5: // thrown
2727 sortInventoryItemsOfType(THROWN, invertSortDirection);
2728 break;
2729 case 6: // gems
2730 sortInventoryItemsOfType(GEM, invertSortDirection);
2731 break;
2732 case 7: // potions
2733 sortInventoryItemsOfType(POTION, invertSortDirection);
2734 break;
2735 case 8: // scrolls
2736 sortInventoryItemsOfType(SCROLL, invertSortDirection);
2737 break;
2738 case 9: // magicstaves
2739 sortInventoryItemsOfType(MAGICSTAFF, invertSortDirection);
2740 break;
2741 case 10: // food
2742 sortInventoryItemsOfType(FOOD, invertSortDirection);
2743 break;
2744 case 11: // equipped items
2745 sortInventoryItemsOfType(-2, invertSortDirection);
2746 break;
2747 default:
2748 break;
2749 }
2750 }
2751 }
2752
2753 // iterate and sort from lowest to highest priority, -9 to -1
2754 for ( std::vector<std::pair<int, int>>::iterator it = autosortPairs.begin(); it != autosortPairs.end(); ++it )
2755 {
2756 std::pair<int, int> tmpPair = *it;
2757 if ( tmpPair.first < 0 )
2758 {
2759 //messagePlayer(0, "priority %d, category: %d", tmpPair.first, tmpPair.second);
2760 bool invertSortDirection = true;
2761 switch ( tmpPair.second )
2762 {
2763 case 0: // weapons
2764 sortInventoryItemsOfType(WEAPON, invertSortDirection);
2765 break;
2766 case 1: // armor
2767 sortInventoryItemsOfType(ARMOR, invertSortDirection);
2768 break;
2769 case 2: // jewelry
2770 sortInventoryItemsOfType(RING, invertSortDirection);
2771 sortInventoryItemsOfType(AMULET, invertSortDirection);
2772 break;
2773 case 3: // books/spellbooks
2774 sortInventoryItemsOfType(SPELLBOOK, invertSortDirection);
2775 sortInventoryItemsOfType(BOOK, invertSortDirection);
2776 break;
2777 case 4: // tools
2778 sortInventoryItemsOfType(TOOL, invertSortDirection);
2779 break;
2780 case 5: // thrown
2781 sortInventoryItemsOfType(THROWN, invertSortDirection);
2782 break;
2783 case 6: // gems
2784 sortInventoryItemsOfType(GEM, invertSortDirection);
2785 break;
2786 case 7: // potions
2787 sortInventoryItemsOfType(POTION, invertSortDirection);
2788 break;
2789 case 8: // scrolls
2790 sortInventoryItemsOfType(SCROLL, invertSortDirection);
2791 break;
2792 case 9: // magicstaves
2793 sortInventoryItemsOfType(MAGICSTAFF, invertSortDirection);
2794 break;
2795 case 10: // food
2796 sortInventoryItemsOfType(FOOD, invertSortDirection);
2797 break;
2798 case 11: // equipped items
2799 sortInventoryItemsOfType(-2, invertSortDirection);
2800 break;
2801 default:
2802 break;
2803 }
2804 }
2805 }
2806
2807
2808 sortInventoryItemsOfType(-1, true); // clean up the rest of the items.
2809 }
2810
sortInventoryItemsOfType(int categoryInt,bool sortRightToLeft)2811 void sortInventoryItemsOfType(int categoryInt, bool sortRightToLeft)
2812 {
2813 node_t* node = nullptr;
2814 Item* itemBeingSorted = nullptr;
2815 Category cat = static_cast<Category>(categoryInt);
2816
2817 for ( node = stats[clientnum]->inventory.first; node != NULL; node = node->next )
2818 {
2819 itemBeingSorted = (Item*)node->element;
2820 if ( itemBeingSorted && itemBeingSorted->x == -1 )
2821 {
2822 if ( itemCategory(itemBeingSorted) == SPELL_CAT )
2823 {
2824 continue;
2825 }
2826 if ( categoryInt != -1 && categoryInt != -2 && itemCategory(itemBeingSorted) != cat )
2827 {
2828 if ( (itemBeingSorted->type == GEM_ROCK && categoryInt == THROWN) )
2829 {
2830 // exception for rocks as they are part of the thrown sort category...
2831 }
2832 else
2833 {
2834 // if item is not in the category specified, continue on.
2835 continue;
2836 }
2837 }
2838 if ( categoryInt == -2 && !itemIsEquipped(itemBeingSorted, clientnum) )
2839 {
2840 continue;
2841 }
2842 if ( categoryInt != -2 && itemIsEquipped(itemBeingSorted, clientnum) )
2843 {
2844 continue;
2845 }
2846
2847 // find a place...
2848 int x, y;
2849 bool notfree = false, foundaspot = false;
2850
2851 bool is_spell = false;
2852 int inventory_y = std::min(std::max(INVENTORY_SIZEY, 2), 3); // only sort y values of 2-3, if extra row don't auto sort into it.
2853 if ( itemCategory(itemBeingSorted) == SPELL_CAT )
2854 {
2855 is_spell = true;
2856 inventory_y = std::min(inventory_y, 3);
2857 }
2858
2859 if ( sortRightToLeft )
2860 {
2861 x = INVENTORY_SIZEX - 1; // fill rightmost first.
2862 }
2863 else
2864 {
2865 x = 0; // fill leftmost first.
2866 }
2867 while ( 1 )
2868 {
2869 for ( y = 0; y < inventory_y; y++ )
2870 {
2871 node_t* node2 = nullptr;
2872 for ( node2 = stats[clientnum]->inventory.first; node2 != nullptr; node2 = node2->next )
2873 {
2874 Item* tempItem = (Item*)node2->element;
2875 if ( tempItem == itemBeingSorted )
2876 {
2877 continue;
2878 }
2879 if ( tempItem )
2880 {
2881 if ( tempItem->x == x && tempItem->y == y )
2882 {
2883 if ( is_spell && itemCategory(tempItem) == SPELL_CAT )
2884 {
2885 notfree = true; //Both spells. Can't fit in the same slot.
2886 }
2887 else if ( !is_spell && itemCategory(tempItem) != SPELL_CAT )
2888 {
2889 notfree = true; //Both not spells. Can't fit in the same slot.
2890 }
2891 }
2892 }
2893 }
2894 if ( notfree )
2895 {
2896 notfree = false;
2897 continue;
2898 }
2899 itemBeingSorted->x = x;
2900 itemBeingSorted->y = y;
2901 foundaspot = true;
2902 break;
2903 }
2904 if ( foundaspot )
2905 {
2906 break;
2907 }
2908 if ( sortRightToLeft )
2909 {
2910 --x; // fill rightmost first.
2911 }
2912 else
2913 {
2914 ++x; // fill leftmost first.
2915 }
2916 }
2917
2918 // backpack sorting, sort into here as last priority.
2919 if ( (x < 0 || x > INVENTORY_SIZEX - 1) && INVENTORY_SIZEY > 3 )
2920 {
2921 foundaspot = false;
2922 notfree = false;
2923 if ( sortRightToLeft )
2924 {
2925 x = INVENTORY_SIZEX - 1; // fill rightmost first.
2926 }
2927 else
2928 {
2929 x = 0; // fill leftmost first.
2930 }
2931 while ( 1 )
2932 {
2933 for ( y = 3; y < INVENTORY_SIZEY; y++ )
2934 {
2935 node_t* node2 = nullptr;
2936 for ( node2 = stats[clientnum]->inventory.first; node2 != nullptr; node2 = node2->next )
2937 {
2938 Item* tempItem = (Item*)node2->element;
2939 if ( tempItem == itemBeingSorted )
2940 {
2941 continue;
2942 }
2943 if ( tempItem )
2944 {
2945 if ( tempItem->x == x && tempItem->y == y )
2946 {
2947 if ( is_spell && itemCategory(tempItem) == SPELL_CAT )
2948 {
2949 notfree = true; //Both spells. Can't fit in the same slot.
2950 }
2951 else if ( !is_spell && itemCategory(tempItem) != SPELL_CAT )
2952 {
2953 notfree = true; //Both not spells. Can't fit in the same slot.
2954 }
2955 }
2956 }
2957 }
2958 if ( notfree )
2959 {
2960 notfree = false;
2961 continue;
2962 }
2963 itemBeingSorted->x = x;
2964 itemBeingSorted->y = y;
2965 foundaspot = true;
2966 break;
2967 }
2968 if ( foundaspot )
2969 {
2970 break;
2971 }
2972 if ( sortRightToLeft )
2973 {
2974 --x; // fill rightmost first.
2975 }
2976 else
2977 {
2978 ++x; // fill leftmost first.
2979 }
2980 }
2981 }
2982 }
2983 }
2984 }
2985
mouseInsidePlayerInventory()2986 bool mouseInsidePlayerInventory()
2987 {
2988 SDL_Rect pos;
2989 pos.x = INVENTORY_STARTX;
2990 pos.y = INVENTORY_STARTY;
2991 pos.w = INVENTORY_SIZEX * INVENTORY_SLOTSIZE;
2992 pos.h = INVENTORY_SIZEY * INVENTORY_SLOTSIZE;
2993 return mouseInBounds(pos.x, pos.x + pos.w, pos.y, pos.y + pos.h);
2994 }
2995
mouseInsidePlayerHotbar()2996 bool mouseInsidePlayerHotbar()
2997 {
2998 SDL_Rect pos;
2999 pos.x = HOTBAR_START_X;
3000 pos.y = STATUS_Y - hotbar_img->h * uiscale_hotbar;
3001 pos.w = NUM_HOTBAR_SLOTS * hotbar_img->w * uiscale_hotbar;
3002 pos.h = hotbar_img->h * uiscale_hotbar;
3003 return mouseInBounds(pos.x, pos.x + pos.w, pos.y, pos.y + pos.h);
3004 }
3005
playerLearnedSpellbook(Item * current_item)3006 bool playerLearnedSpellbook(Item* current_item)
3007 {
3008 if ( !current_item )
3009 {
3010 return false;
3011 }
3012 if ( itemCategory(current_item) != SPELLBOOK )
3013 {
3014 return false;
3015 }
3016 for ( node_t* node = stats[clientnum]->inventory.first; node && current_item->identified; node = node->next )
3017 {
3018 Item* item = static_cast<Item*>(node->element);
3019 if ( !item )
3020 {
3021 continue;
3022 }
3023 //Search player's inventory for the special spell item.
3024 if ( itemCategory(item) != SPELL_CAT )
3025 {
3026 continue;
3027 }
3028 if ( item->appearance >= 1000 )
3029 {
3030 // special shaman racial spells, don't count this as being learnt
3031 continue;
3032 }
3033 spell_t *spell = getSpellFromItem(item); //Do not free or delete this.
3034 if ( !spell )
3035 {
3036 continue;
3037 }
3038 if ( current_item->type == getSpellbookFromSpellID(spell->ID) )
3039 {
3040 // learned spell, default option is now equip spellbook.
3041 return true;
3042 }
3043 }
3044 return false;
3045 }