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 }