1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: interface.cpp
5 	Desc: contains code for game interface
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 "../files.hpp"
14 #include "../game.hpp"
15 #include "../stat.hpp"
16 #include "../messages.hpp"
17 #include "../entity.hpp"
18 #include "../magic/magic.hpp"
19 #include "interface.hpp"
20 #include "../monster.hpp"
21 #include "../items.hpp"
22 #include "../book.hpp"
23 #include "../sound.hpp"
24 #include "../shops.hpp"
25 #include "../menu.hpp"
26 #include "../player.hpp"
27 #include "../colors.hpp"
28 #include "../net.hpp"
29 #include "../draw.hpp"
30 #include "../scores.hpp"
31 #include "../scrolls.hpp"
32 #include "../lobbies.hpp"
33 
34 Uint32 svFlags = 30;
35 Uint32 settings_svFlags = svFlags;
36 SDL_Surface* backdrop_minotaur_bmp = nullptr;
37 SDL_Surface* backdrop_blessed_bmp = nullptr;
38 SDL_Surface* backdrop_cursed_bmp = nullptr;
39 SDL_Surface* status_bmp = nullptr;
40 SDL_Surface* character_bmp = nullptr;
41 SDL_Surface* hunger_bmp = nullptr;
42 SDL_Surface* hunger_blood_bmp = nullptr;
43 SDL_Surface* hunger_boiler_bmp = nullptr;
44 SDL_Surface* hunger_boiler_hotflame_bmp = nullptr;
45 SDL_Surface* hunger_boiler_flame_bmp = nullptr;
46 SDL_Surface* minotaur_bmp = nullptr;
47 int textscroll = 0;
48 int attributespage = 0;
49 int proficienciesPage = 0;
50 Item* invitemschest[kNumChestItemsToDisplay];
51 int inventorycategory = 7; // inventory window defaults to wildcard
52 int itemscroll = 0;
53 view_t camera_charsheet;
54 real_t camera_charsheet_offsetyaw = (330) * PI / 180;
55 
56 SDL_Surface* font12x12_small_bmp = NULL;
57 SDL_Surface* inventoryChest_bmp = NULL;
58 SDL_Surface* invclose_bmp = NULL;
59 SDL_Surface* invgraball_bmp = NULL;
60 SDL_Surface* button_bmp = NULL, *smallbutton_bmp = NULL, *invup_bmp = NULL, *invdown_bmp = NULL;
61 bool gui_clickdrag = false;
62 int dragoffset_x = 0;
63 int dragoffset_y = 0;
64 
65 int chestitemscroll = 0;
66 list_t chestInv;
67 int chestgui_offset_x = 0;
68 int chestgui_offset_y = 0;
69 bool dragging_chestGUI = false;
70 int selectedChestSlot = -1;
71 
72 int selected_inventory_slot_x = 0;
73 int selected_inventory_slot_y = 0;
74 
75 SDL_Surface* rightsidebar_titlebar_img = NULL;
76 SDL_Surface* rightsidebar_slot_img = NULL;
77 SDL_Surface* rightsidebar_slot_highlighted_img = NULL;
78 SDL_Surface* rightsidebar_slot_grayedout_img = NULL;
79 int rightsidebar_height = 0;
80 int appraisal_timer = 0;
81 int appraisal_timermax = 0;
82 Uint32 appraisal_item = 0;
83 
84 SDL_Surface* bookgui_img = NULL;
85 //SDL_Surface *nextpage_img = NULL;
86 //SDL_Surface *previouspage_img = NULL;
87 //SDL_Surface *bookclose_img = NULL;
88 node_t* book_page = NULL;
89 int bookgui_offset_x = 0;
90 int bookgui_offset_y = 0;
91 bool dragging_book_GUI = false;
92 bool book_open = false;
93 book_t* open_book = NULL;
94 Item* open_book_item = NULL;
95 //int book_characterspace_x = 0;
96 //int book_characterspace_y = 0;
97 
98 SDL_Surface* book_highlighted_left_img = NULL;
99 SDL_Surface* book_highlighted_right_img = NULL;
100 
101 int gui_mode = GUI_MODE_NONE;
102 
103 SDL_Surface* magicspellList_bmp = NULL;
104 SDL_Surface* spell_list_titlebar_bmp = NULL;
105 SDL_Surface* spell_list_gui_slot_bmp = NULL;
106 SDL_Surface* spell_list_gui_slot_highlighted_bmp = NULL;
107 SDL_Surface* textup_bmp = NULL;
108 SDL_Surface* textdown_bmp = NULL;
109 SDL_Surface* attributesleft_bmp = NULL;
110 SDL_Surface* attributesright_bmp = NULL;
111 SDL_Surface* attributesleftunclicked_bmp = NULL;
112 SDL_Surface* attributesrightunclicked_bmp = NULL;
113 SDL_Surface* inventory_bmp = NULL, *inventoryoption_bmp = NULL, *inventoryoptionChest_bmp = NULL, *equipped_bmp = NULL;
114 SDL_Surface* itembroken_bmp = nullptr;
115 //SDL_Surface *category_bmp[NUMCATEGORIES];
116 SDL_Surface* shopkeeper_bmp = NULL;
117 SDL_Surface* shopkeeper2_bmp = NULL;
118 SDL_Surface* damage_bmp = NULL;
119 SDL_Surface *str_bmp64u = NULL;
120 SDL_Surface *dex_bmp64u = NULL;
121 SDL_Surface *con_bmp64u = NULL;
122 SDL_Surface *int_bmp64u = NULL;
123 SDL_Surface *per_bmp64u = NULL;
124 SDL_Surface *chr_bmp64u = NULL;
125 SDL_Surface *str_bmp64 = NULL;
126 SDL_Surface *dex_bmp64 = NULL;
127 SDL_Surface *con_bmp64 = NULL;
128 SDL_Surface *int_bmp64 = NULL;
129 SDL_Surface *per_bmp64 = NULL;
130 SDL_Surface *chr_bmp64 = NULL;
131 SDL_Surface *sidebar_lock_bmp = nullptr;
132 SDL_Surface *sidebar_unlock_bmp = nullptr;
133 SDL_Surface *effect_drunk_bmp = nullptr;
134 SDL_Surface *effect_polymorph_bmp = nullptr;
135 SDL_Surface *effect_hungover_bmp = nullptr;
136 int spellscroll = 0;
137 int magicspell_list_offset_x = 0;
138 int magicspell_list_offset_y = 0;
139 bool dragging_magicspell_list_GUI = false;
140 int magic_GUI_state = 0;
141 SDL_Rect magic_gui_pos;
142 SDL_Surface* sustained_spell_generic_icon = NULL;
143 
144 int buttonclick = 0;
145 
146 bool draw_cursor = true;
147 
148 hotbar_slot_t hotbar[NUM_HOTBAR_SLOTS];
149 hotbar_slot_t hotbar_alternate[NUM_HOTBAR_ALTERNATES][NUM_HOTBAR_SLOTS];
150 int swapHotbarOnShapeshift = 0;
151 bool hotbarShapeshiftInit[NUM_HOTBAR_ALTERNATES] = { false, false, false, false, false };
152 int current_hotbar = 0;
153 SDL_Surface* hotbar_img = NULL;
154 SDL_Surface* hotbar_spell_img = NULL;
155 bool hotbarHasFocus = false;
156 
157 list_t damageIndicators;
158 
159 bool auto_hotbar_new_items = true;
160 bool auto_hotbar_categories[NUM_HOTBAR_CATEGORIES] = {	true, true, true, true,
161 														true, true, true, true,
162 														true, true, true, true };
163 int autosort_inventory_categories[NUM_AUTOSORT_CATEGORIES] = {	0, 0, 0, 0,
164 																0, 0, 0, 0,
165 																0, 0, 0, 0 };
166 bool hotbar_numkey_quick_add = false;
167 bool disable_messages = false;
168 bool right_click_protect = false;
169 bool auto_appraise_new_items = false;
170 bool lock_right_sidebar = false;
171 bool show_game_timer_always = false;
172 bool hide_statusbar = false;
173 bool hide_playertags = false;
174 bool show_skill_values = false;
175 real_t uiscale_chatlog = 1.f;
176 real_t uiscale_playerbars = 1.f;
177 real_t uiscale_hotbar = 1.f;
178 real_t uiscale_inventory = 1.f;
179 bool uiscale_charactersheet = false;
180 bool uiscale_skillspage = false;
181 
182 EnemyHPDamageBarHandler enemyHPDamageBarHandler;
183 FollowerRadialMenu FollowerMenu;
184 GenericGUIMenu GenericGUI;
185 SDL_Rect interfaceSkillsSheet;
186 SDL_Rect interfacePartySheet;
187 SDL_Rect interfaceCharacterSheet;
188 SDL_Rect interfaceMessageStatusBar;
189 
190 std::vector<std::pair<SDL_Surface**, std::string>> systemResourceImages =
191 {
192 	std::make_pair(&title_bmp, "images/system/title.png"),
193 	std::make_pair(&logo_bmp, "images/system/logo.png"),
194 	std::make_pair(&cursor_bmp, "images/system/cursor.png"),
195 	std::make_pair(&cross_bmp, "images/system/cross.png"),
196 
197 	std::make_pair(&fancyWindow_bmp, "images/system/fancyWindow.png"),
198 	std::make_pair(&font8x8_bmp, "images/system/font8x8.png"),
199 	std::make_pair(&font12x12_bmp, "images/system/font12x12.png"),
200 	std::make_pair(&font16x16_bmp, "images/system/font16x16.png"),
201 
202 	std::make_pair(&font12x12_small_bmp, "images/system/font12x12_small.png"),
203 	std::make_pair(&backdrop_minotaur_bmp, "images/system/backdrop.png"),
204 	std::make_pair(&backdrop_blessed_bmp, "images/system/backdrop_blessed.png"),
205 	std::make_pair(&backdrop_cursed_bmp, "images/system/backdrop_cursed.png"),
206 	std::make_pair(&button_bmp, "images/system/ButtonHighlighted.png"),
207 	std::make_pair(&smallbutton_bmp, "images/system/SmallButtonHighlighted.png"),
208 	std::make_pair(&invup_bmp, "images/system/InventoryUpHighlighted.png"),
209 	std::make_pair(&invdown_bmp, "images/system/InventoryDownHighlighted.png"),
210 	std::make_pair(&status_bmp, "images/system/StatusBar.png"),
211 	std::make_pair(&character_bmp, "images/system/CharacterSheet.png"),
212 	std::make_pair(&hunger_bmp, "images/system/Hunger.png"),
213 	std::make_pair(&hunger_blood_bmp, "images/system/Hunger_blood.png"),
214 	std::make_pair(&hunger_boiler_bmp, "images/system/Hunger_boiler.png"),
215 	std::make_pair(&hunger_boiler_hotflame_bmp, "images/system/Hunger_boiler_hotfire.png"),
216 	std::make_pair(&hunger_boiler_flame_bmp, "images/system/Hunger_boiler_fire.png"),
217 	std::make_pair(&minotaur_bmp, "images/system/minotaur.png"),
218 	std::make_pair(&attributesleft_bmp, "images/system/AttributesLeftHighlighted.png"),
219 	std::make_pair(&attributesright_bmp, "images/system/AttributesRightHighlighted.png"),
220 
221 	//General GUI images.
222 	std::make_pair(&attributesleftunclicked_bmp, "images/system/AttributesLeft.png"),
223 	std::make_pair(&attributesrightunclicked_bmp, "images/system/AttributesRight.png"),
224 	std::make_pair(&shopkeeper_bmp, "images/system/shopkeeper.png"),
225 	std::make_pair(&shopkeeper2_bmp, "images/system/shopkeeper2.png"),
226 	std::make_pair(&damage_bmp, "images/system/damage.png"),
227 
228 	//Magic GUI images.
229 	std::make_pair(&magicspellList_bmp, "images/system/spellList.png"),
230 	std::make_pair(&spell_list_titlebar_bmp, "images/system/spellListTitlebar.png"),
231 	std::make_pair(&spell_list_gui_slot_bmp, "images/system/spellListSlot.png"),
232 	std::make_pair(&spell_list_gui_slot_highlighted_bmp, "images/system/spellListSlotHighlighted.png"),
233 	std::make_pair(&sustained_spell_generic_icon, "images/system/magic/channeled_spell.png"),
234 
235 	// inventory GUI images.
236 	std::make_pair(&inventory_bmp, "images/system/Inventory.png"),
237 	std::make_pair(&inventoryoption_bmp, "images/system/InventoryOption.png"),
238 	std::make_pair(&inventory_mode_item_img, "images/system/inventory_mode_item.png"),
239 	std::make_pair(&inventory_mode_item_highlighted_img, "images/system/inventory_mode_item_highlighted.png"),
240 	std::make_pair(&inventory_mode_spell_img, "images/system/inventory_mode_spell.png"),
241 	std::make_pair(&inventory_mode_spell_highlighted_img, "images/system/inventory_mode_spell_highlighted.png"),
242 	std::make_pair(&equipped_bmp, "images/system/Equipped.png"),
243 	std::make_pair(&itembroken_bmp, "images/system/Broken.png"),
244 
245 	//Chest images..
246 	std::make_pair(&inventoryChest_bmp, "images/system/InventoryChest.png"),
247 	std::make_pair(&inventoryoptionChest_bmp, "images/system/InventoryOptionChest.png"),
248 	std::make_pair(&invclose_bmp, "images/system/InventoryCloseHighlighted.png"),
249 	std::make_pair(&invgraball_bmp, "images/system/InventoryChestGraballHighlighted.png"),
250 
251 	//Identify GUI images...
252 	std::make_pair(&identifyGUI_img, "images/system/identifyGUI.png"),
253 	std::make_pair(&rightsidebar_slot_grayedout_img, "images/system/rightSidebarSlotGrayedOut.png"),
254 	std::make_pair(&bookgui_img, "images/system/book.png"),
255 	std::make_pair(&book_highlighted_left_img, "images/system/bookpageleft-highlighted.png"),
256 	std::make_pair(&book_highlighted_right_img, "images/system/bookpageright-highlighted.png"),
257 
258 	//Levelup images.
259 	std::make_pair(&str_bmp64u, "images/system/str64u.png"),
260 	std::make_pair(&dex_bmp64u, "images/system/dex64u.png"),
261 	std::make_pair(&con_bmp64u, "images/system/con64u.png"),
262 	std::make_pair(&int_bmp64u, "images/system/int64u.png"),
263 	std::make_pair(&per_bmp64u, "images/system/per64u.png"),
264 	std::make_pair(&chr_bmp64u, "images/system/chr64u.png"),
265 	std::make_pair(&str_bmp64, "images/system/str64.png"),
266 	std::make_pair(&dex_bmp64, "images/system/dex64.png"),
267 	std::make_pair(&con_bmp64, "images/system/con64.png"),
268 	std::make_pair(&int_bmp64, "images/system/int64.png"),
269 	std::make_pair(&per_bmp64, "images/system/per64.png"),
270 	std::make_pair(&chr_bmp64, "images/system/chr64.png"),
271 
272 	//Misc GUI images.
273 	std::make_pair(&sidebar_lock_bmp, "images/system/locksidebar.png"),
274 	std::make_pair(&sidebar_unlock_bmp, "images/system/unlocksidebar.png"),
275 	std::make_pair(&hotbar_img, "images/system/hotbar_slot.png"),
276 	std::make_pair(&hotbar_spell_img, "images/system/magic/hotbar_spell.png"),
277 
278 	//Misc effect images.
279 	std::make_pair(&effect_drunk_bmp, "images/system/drunk.png"),
280 	std::make_pair(&effect_polymorph_bmp, "images/system/polymorph.png"),
281 	std::make_pair(&effect_hungover_bmp, "images/system/hungover.png")
282 };
283 
loadInterfaceResources()284 bool loadInterfaceResources()
285 {
286 	//General GUI images.
287 	font12x12_small_bmp = loadImage("images/system/font12x12_small.png");
288 	backdrop_minotaur_bmp = loadImage("images/system/backdrop.png");
289 	backdrop_blessed_bmp = loadImage("images/system/backdrop_blessed.png");
290 	backdrop_cursed_bmp = loadImage("images/system/backdrop_cursed.png");
291 	button_bmp = loadImage("images/system/ButtonHighlighted.png");
292 	smallbutton_bmp = loadImage("images/system/SmallButtonHighlighted.png");
293 	invup_bmp = loadImage("images/system/InventoryUpHighlighted.png");
294 	invdown_bmp = loadImage("images/system/InventoryDownHighlighted.png");
295 	status_bmp = loadImage("images/system/StatusBar.png");
296 	character_bmp = loadImage("images/system/CharacterSheet.png");
297 	hunger_bmp = loadImage("images/system/Hunger.png");
298 	hunger_blood_bmp = loadImage("images/system/Hunger_blood.png");
299 	hunger_boiler_bmp = loadImage("images/system/Hunger_boiler.png");
300 	hunger_boiler_hotflame_bmp = loadImage("images/system/Hunger_boiler_hotfire.png");
301 	hunger_boiler_flame_bmp = loadImage("images/system/Hunger_boiler_fire.png");
302 	minotaur_bmp = loadImage("images/system/minotaur.png"); // the file "images/system/minotaur.png" doesn't exist in current Data
303 	//textup_bmp = loadImage("images/system/TextBoxUpHighlighted.png");
304 	//textdown_bmp = loadImage("images/system/TextBoxDownHighlighted.png");
305 	attributesleft_bmp = loadImage("images/system/AttributesLeftHighlighted.png");
306 	attributesright_bmp = loadImage("images/system/AttributesRightHighlighted.png");
307 	attributesleftunclicked_bmp = loadImage("images/system/AttributesLeft.png");
308 	attributesrightunclicked_bmp = loadImage("images/system/AttributesRight.png");
309 	shopkeeper_bmp = loadImage("images/system/shopkeeper.png");
310 	shopkeeper2_bmp = loadImage("images/system/shopkeeper2.png");
311 	damage_bmp = loadImage("images/system/damage.png");
312 
313 	//Magic GUI images.
314 	magicspellList_bmp = loadImage("images/system/spellList.png");
315 	spell_list_titlebar_bmp = loadImage("images/system/spellListTitlebar.png");
316 	spell_list_gui_slot_bmp = loadImage("images/system/spellListSlot.png");
317 	spell_list_gui_slot_highlighted_bmp = loadImage("images/system/spellListSlotHighlighted.png");
318 	sustained_spell_generic_icon = loadImage("images/system/magic/channeled_spell.png");
319 	inventory_bmp = loadImage("images/system/Inventory.png");
320 	inventoryoption_bmp = loadImage("images/system/InventoryOption.png");
321 	inventory_mode_item_img = loadImage("images/system/inventory_mode_item.png");
322 	inventory_mode_item_highlighted_img = loadImage("images/system/inventory_mode_item_highlighted.png");
323 	inventory_mode_spell_img = loadImage("images/system/inventory_mode_spell.png");
324 	inventory_mode_spell_highlighted_img = loadImage("images/system/inventory_mode_spell_highlighted.png");
325 	equipped_bmp = loadImage("images/system/Equipped.png");
326 	itembroken_bmp = loadImage("images/system/Broken.png");
327 	//sky_bmp=scaleSurface(loadImage("images/system/sky.png"), 1280*(xres/320.0),468*(xres/320.0));
328 	/*category_bmp[0]=loadImage("images/system/Weapon.png");
329 	category_bmp[1]=loadImage("images/system/Armor.png");
330 	category_bmp[2]=loadImage("images/system/Amulet.png");
331 	category_bmp[3]=loadImage("images/system/Potion.png");
332 	category_bmp[4]=loadImage("images/system/Scroll.png");
333 	category_bmp[5]=loadImage("images/system/Magicstaff.png");
334 	category_bmp[6]=loadImage("images/system/Ring.png");
335 	category_bmp[7]=loadImage("images/system/Spellbook.png");
336 	category_bmp[8]=loadImage("images/system/Gem.png");
337 	category_bmp[9]=loadImage("images/system/Tool.png");
338 	category_bmp[10]=loadImage("images/system/Food.png");
339 	category_bmp[11]=loadImage("images/system/Spellbook.png");*/
340 
341 	//Chest images..
342 	inventoryChest_bmp = loadImage("images/system/InventoryChest.png");
343 	inventoryoptionChest_bmp = loadImage("images/system/InventoryOptionChest.png");
344 	invclose_bmp = loadImage("images/system/InventoryCloseHighlighted.png");
345 	invgraball_bmp = loadImage("images/system/InventoryChestGraballHighlighted.png");
346 
347 	//Identify GUI images...
348 	identifyGUI_img = loadImage("images/system/identifyGUI.png");
349 
350 	/*rightsidebar_titlebar_img = loadImage("images/system/rightSidebarTitlebar.png");
351 	if (!rightsidebar_titlebar_img) {
352 		printlog( "Failed to load \"images/system/rightSidebarTitlebar.png\".");
353 		return false;
354 	}
355 	rightsidebar_slot_img = loadImage("images/system/rightSidebarSlot.png");
356 	if (!rightsidebar_slot_img) {
357 		printlog( "Failed to load \"images/system/rightSidebarSlot.png\".");
358 		return false;
359 	}
360 	rightsidebar_slot_highlighted_img = loadImage("images/system/rightSidebarSlotHighlighted.png");
361 	if (!rightsidebar_slot_highlighted_img) {
362 		printlog( "Failed to load \"images/system/rightSidebarSlotHighlighted.png\".");
363 		return false;
364 	}*/
365 	rightsidebar_titlebar_img = spell_list_titlebar_bmp;
366 	rightsidebar_slot_img = spell_list_gui_slot_bmp;
367 	rightsidebar_slot_highlighted_img = spell_list_gui_slot_highlighted_bmp;
368 	rightsidebar_slot_grayedout_img = loadImage("images/system/rightSidebarSlotGrayedOut.png");
369 
370 	bookgui_img = loadImage("images/system/book.png");
371 	//nextpage_img = loadImage("images/system/nextpage.png");
372 	//previouspage_img = loadImage("images/system/previouspage.png");
373 	//bookclose_img = loadImage("images/system/bookclose.png");
374 
375 	book_highlighted_left_img = loadImage("images/system/bookpageleft-highlighted.png");
376 	book_highlighted_right_img = loadImage("images/system/bookpageright-highlighted.png");
377 
378 	str_bmp64u = loadImage("images/system/str64u.png");
379 	dex_bmp64u = loadImage("images/system/dex64u.png");
380 	con_bmp64u = loadImage("images/system/con64u.png");
381 	int_bmp64u = loadImage("images/system/int64u.png");
382 	per_bmp64u = loadImage("images/system/per64u.png");
383 	chr_bmp64u = loadImage("images/system/chr64u.png");
384 	str_bmp64 = loadImage("images/system/str64.png");
385 	dex_bmp64 = loadImage("images/system/dex64.png");
386 	con_bmp64 = loadImage("images/system/con64.png");
387 	int_bmp64 = loadImage("images/system/int64.png");
388 	per_bmp64 = loadImage("images/system/per64.png");
389 	chr_bmp64 = loadImage("images/system/chr64.png");
390 
391 	sidebar_lock_bmp = loadImage("images/system/locksidebar.png");
392 	sidebar_unlock_bmp = loadImage("images/system/unlocksidebar.png");
393 	hotbar_img = loadImage("images/system/hotbar_slot.png");
394 	hotbar_spell_img = loadImage("images/system/magic/hotbar_spell.png");
395 
396 	effect_drunk_bmp = loadImage("images/system/drunk.png");
397 	effect_polymorph_bmp = loadImage("images/system/polymorph.png");
398 	effect_hungover_bmp = loadImage("images/system/hungover.png");
399 
400 	int i = 0;
401 	for (i = 0; i < NUM_HOTBAR_SLOTS; ++i)
402 	{
403 		hotbar[i].item = 0;
404 		for ( int j = 0; j < NUM_HOTBAR_ALTERNATES; ++j )
405 		{
406 			hotbar_alternate[j][i].item = 0;
407 		}
408 	}
409 
410 	damageIndicators.first = nullptr;
411 	damageIndicators.last = nullptr;
412 
413 	return true;
414 }
415 
freeInterfaceResources()416 void freeInterfaceResources()
417 {
418 	//int c;
419 
420 	if (font12x12_small_bmp)
421 	{
422 		SDL_FreeSurface(font12x12_small_bmp);
423 	}
424 	if (backdrop_minotaur_bmp)
425 	{
426 		SDL_FreeSurface(backdrop_minotaur_bmp);
427 	}
428 	if ( backdrop_blessed_bmp )
429 	{
430 		SDL_FreeSurface(backdrop_blessed_bmp);
431 	}
432 	if ( backdrop_cursed_bmp )
433 	{
434 		SDL_FreeSurface(backdrop_cursed_bmp);
435 	}
436 	if (status_bmp)
437 	{
438 		SDL_FreeSurface(status_bmp);
439 	}
440 	if (character_bmp)
441 	{
442 		SDL_FreeSurface(character_bmp);
443 	}
444 	if (hunger_bmp)
445 	{
446 		SDL_FreeSurface(hunger_bmp);
447 	}
448 	if ( hunger_blood_bmp )
449 	{
450 		SDL_FreeSurface(hunger_blood_bmp);
451 	}
452 	if ( hunger_boiler_bmp )
453 	{
454 		SDL_FreeSurface(hunger_boiler_bmp);
455 	}
456 	if ( hunger_boiler_hotflame_bmp )
457 	{
458 		SDL_FreeSurface(hunger_boiler_hotflame_bmp);
459 	}
460 	if ( hunger_boiler_flame_bmp )
461 	{
462 		SDL_FreeSurface(hunger_boiler_flame_bmp);
463 	}
464 	if ( minotaur_bmp )
465 	{
466 		SDL_FreeSurface(minotaur_bmp);
467 	}
468 	//if(textup_bmp)
469 	//SDL_FreeSurface(textup_bmp);
470 	//if(textdown_bmp)
471 	//SDL_FreeSurface(textdown_bmp);
472 	if (attributesleft_bmp)
473 	{
474 		SDL_FreeSurface(attributesleft_bmp);
475 	}
476 	if (attributesright_bmp)
477 	{
478 		SDL_FreeSurface(attributesright_bmp);
479 	}
480 	if (attributesleftunclicked_bmp)
481 	{
482 		SDL_FreeSurface(attributesleftunclicked_bmp);
483 	}
484 	if (attributesrightunclicked_bmp)
485 	{
486 		SDL_FreeSurface(attributesrightunclicked_bmp);
487 	}
488 	if (magicspellList_bmp)
489 	{
490 		SDL_FreeSurface(magicspellList_bmp);
491 	}
492 	if (spell_list_titlebar_bmp)
493 	{
494 		SDL_FreeSurface(spell_list_titlebar_bmp);
495 	}
496 	if (spell_list_gui_slot_bmp)
497 	{
498 		SDL_FreeSurface(spell_list_gui_slot_bmp);
499 	}
500 	if (spell_list_gui_slot_highlighted_bmp)
501 	{
502 		SDL_FreeSurface(spell_list_gui_slot_highlighted_bmp);
503 	}
504 	if (sustained_spell_generic_icon)
505 	{
506 		SDL_FreeSurface(sustained_spell_generic_icon);
507 	}
508 	if (invup_bmp != NULL)
509 	{
510 		SDL_FreeSurface(invup_bmp);
511 	}
512 	if (invdown_bmp != NULL)
513 	{
514 		SDL_FreeSurface(invdown_bmp);
515 	}
516 	if (inventory_bmp != NULL)
517 	{
518 		SDL_FreeSurface(inventory_bmp);
519 	}
520 	if (inventoryoption_bmp != NULL)
521 	{
522 		SDL_FreeSurface(inventoryoption_bmp);
523 	}
524 	if (inventory_mode_item_img)
525 	{
526 		SDL_FreeSurface(inventory_mode_item_img);
527 	}
528 	if (inventory_mode_item_highlighted_img)
529 	{
530 		SDL_FreeSurface(inventory_mode_item_highlighted_img);
531 	}
532 	if (inventory_mode_spell_img)
533 	{
534 		SDL_FreeSurface(inventory_mode_spell_img);
535 	}
536 	if (inventory_mode_spell_highlighted_img)
537 	{
538 		SDL_FreeSurface(inventory_mode_spell_highlighted_img);
539 	}
540 	if (button_bmp != NULL)
541 	{
542 		SDL_FreeSurface(button_bmp);
543 	}
544 	if (smallbutton_bmp != NULL)
545 	{
546 		SDL_FreeSurface(smallbutton_bmp);
547 	}
548 	if (equipped_bmp != NULL)
549 	{
550 		SDL_FreeSurface(equipped_bmp);
551 	}
552 	if ( itembroken_bmp != nullptr )
553 	{
554 		SDL_FreeSurface(itembroken_bmp);
555 	}
556 	if (inventoryChest_bmp != NULL)
557 	{
558 		SDL_FreeSurface(inventoryChest_bmp);
559 	}
560 	if (invclose_bmp != NULL)
561 	{
562 		SDL_FreeSurface(invclose_bmp);
563 	}
564 	if (invgraball_bmp != NULL)
565 	{
566 		SDL_FreeSurface(invgraball_bmp);
567 	}
568 	if (inventoryoptionChest_bmp != NULL)
569 	{
570 		SDL_FreeSurface(inventoryoptionChest_bmp);
571 	}
572 	if (shopkeeper_bmp != NULL)
573 	{
574 		SDL_FreeSurface(shopkeeper_bmp);
575 	}
576 	if ( shopkeeper2_bmp != NULL )
577 	{
578 		SDL_FreeSurface(shopkeeper2_bmp);
579 	}
580 	if (damage_bmp != NULL)
581 	{
582 		SDL_FreeSurface(damage_bmp);
583 	}
584 	//for( c=0; c<NUMCATEGORIES; c++ )
585 	//if(category_bmp[c]!=NULL)
586 	//SDL_FreeSurface(category_bmp[c]);
587 	if (identifyGUI_img != NULL)
588 	{
589 		SDL_FreeSurface(identifyGUI_img);
590 	}
591 	/*if (rightsidebar_titlebar_img)
592 		SDL_FreeSurface(rightsidebar_titlebar_img);
593 	if (rightsidebar_slot_img)
594 		SDL_FreeSurface(rightsidebar_slot_img);
595 	if (rightsidebar_slot_highlighted_img)
596 		SDL_FreeSurface(rightsidebar_slot_highlighted_img);*/
597 	if (rightsidebar_slot_grayedout_img)
598 	{
599 		SDL_FreeSurface(rightsidebar_slot_grayedout_img);
600 	}
601 	if (bookgui_img)
602 	{
603 		SDL_FreeSurface(bookgui_img);
604 	}
605 	//if (nextpage_img)
606 	//SDL_FreeSurface(nextpage_img);
607 	//if (previouspage_img)
608 	//SDL_FreeSurface(previouspage_img);
609 	//if (bookclose_img)
610 	//SDL_FreeSurface(bookclose_img);
611 	if (book_highlighted_left_img)
612 	{
613 		SDL_FreeSurface(book_highlighted_left_img);
614 	}
615 	if (book_highlighted_right_img)
616 	{
617 		SDL_FreeSurface(book_highlighted_right_img);
618 	}
619 	if (hotbar_img)
620 	{
621 		SDL_FreeSurface(hotbar_img);
622 	}
623 	if (hotbar_spell_img)
624 	{
625 		SDL_FreeSurface(hotbar_spell_img);
626 	}
627 	if ( str_bmp64u )
628 	{
629 		SDL_FreeSurface(str_bmp64u);
630 	}
631 	if ( dex_bmp64u )
632 	{
633 		SDL_FreeSurface(dex_bmp64u);
634 	}
635 	if ( con_bmp64u )
636 	{
637 		SDL_FreeSurface(con_bmp64u);
638 	}
639 	if ( int_bmp64u )
640 	{
641 		SDL_FreeSurface(int_bmp64u);
642 	}
643 	if ( per_bmp64u )
644 	{
645 		SDL_FreeSurface(per_bmp64u);
646 	}
647 	if ( chr_bmp64u )
648 	{
649 		SDL_FreeSurface(chr_bmp64u);
650 	}
651 	if ( str_bmp64 )
652 	{
653 		SDL_FreeSurface(str_bmp64);
654 	}
655 	if ( dex_bmp64 )
656 	{
657 		SDL_FreeSurface(dex_bmp64);
658 	}
659 	if ( con_bmp64 )
660 	{
661 		SDL_FreeSurface(con_bmp64);
662 	}
663 	if ( int_bmp64 )
664 	{
665 		SDL_FreeSurface(int_bmp64);
666 	}
667 	if ( per_bmp64 )
668 	{
669 		SDL_FreeSurface(per_bmp64);
670 	}
671 	if ( chr_bmp64 )
672 	{
673 		SDL_FreeSurface(chr_bmp64);
674 	}
675 	if ( sidebar_lock_bmp )
676 	{
677 		SDL_FreeSurface(sidebar_lock_bmp);
678 	}
679 	if ( sidebar_unlock_bmp )
680 	{
681 		SDL_FreeSurface(sidebar_unlock_bmp);
682 	}
683 	if ( effect_drunk_bmp )
684 	{
685 		SDL_FreeSurface(effect_drunk_bmp);
686 	}
687 	if ( effect_polymorph_bmp )
688 	{
689 		SDL_FreeSurface(effect_polymorph_bmp);
690 	}
691 	if ( effect_hungover_bmp )
692 	{
693 		SDL_FreeSurface(effect_hungover_bmp);
694 	}
695 	list_FreeAll(&damageIndicators);
696 }
697 
defaultImpulses()698 void defaultImpulses()
699 {
700 #ifdef PANDORA
701 	impulses[IN_FORWARD] = 82;
702 	impulses[IN_LEFT] = 80;
703 	impulses[IN_BACK] = 81;
704 	impulses[IN_RIGHT] = 79;
705 #else
706 	impulses[IN_FORWARD] = 26;
707 	impulses[IN_LEFT] = 4;
708 	impulses[IN_BACK] = 22;
709 	impulses[IN_RIGHT] = 7;
710 #endif
711 	impulses[IN_TURNL] = 80;
712 	impulses[IN_TURNR] = 79;
713 	impulses[IN_UP] = 82;
714 	impulses[IN_DOWN] = 81;
715 	impulses[IN_CHAT] = 40;
716 	impulses[IN_COMMAND] = 56;
717 	impulses[IN_STATUS] = 43;
718 #ifdef PANDORA
719 	impulses[IN_SPELL_LIST] = 75;
720 	impulses[IN_CAST_SPELL] = 77;
721 	impulses[IN_DEFEND] = 78;
722 #else
723 	impulses[IN_SPELL_LIST] = 16;
724 	impulses[IN_CAST_SPELL] = 9;
725 	impulses[IN_DEFEND] = 44;
726 #endif
727 	impulses[IN_ATTACK] = 283;
728 	impulses[IN_USE] = 285;
729 	impulses[IN_AUTOSORT] = 21;
730 	impulses[IN_MINIMAPSCALE] = 27;
731 	impulses[IN_TOGGLECHATLOG] = 15;
732 	impulses[IN_FOLLOWERMENU] = 6;
733 	impulses[IN_FOLLOWERMENU_LASTCMD] = 20;
734 	impulses[IN_FOLLOWERMENU_CYCLENEXT] = 8;
735 	impulses[IN_HOTBAR_SCROLL_LEFT] = 286;
736 	impulses[IN_HOTBAR_SCROLL_RIGHT] = 287;
737 	impulses[IN_HOTBAR_SCROLL_SELECT] = 284;
738 
739 	joyimpulses[INJOY_STATUS] = 307;
740 	joyimpulses[INJOY_SPELL_LIST] = SCANCODE_UNASSIGNED_BINDING;
741 	joyimpulses[INJOY_GAME_CAST_SPELL] = 311;
742 	joyimpulses[INJOY_GAME_DEFEND] = 299;
743 	joyimpulses[INJOY_GAME_ATTACK] = 300;
744 	joyimpulses[INJOY_GAME_USE] = 301;
745 	joyimpulses[INJOY_PAUSE_MENU] = 305;
746 	joyimpulses[INJOY_MENU_LEFT_CLICK] = 303;
747 	joyimpulses[INJOY_DPAD_LEFT] = 314;
748 	joyimpulses[INJOY_DPAD_RIGHT] = 315;
749 	joyimpulses[INJOY_DPAD_UP] = 312;
750 	joyimpulses[INJOY_DPAD_DOWN] = 313;
751 	joyimpulses[INJOY_MENU_NEXT] = 301;
752 	joyimpulses[INJOY_GAME_HOTBAR_NEXT] = 315;
753 	joyimpulses[INJOY_GAME_HOTBAR_PREV] = 314;
754 	joyimpulses[INJOY_GAME_HOTBAR_ACTIVATE] = 310;
755 	joyimpulses[INJOY_MENU_CHEST_GRAB_ALL] = 304;
756 	joyimpulses[INJOY_MENU_CANCEL] = 302;
757 	joyimpulses[INJOY_MENU_USE] = 301;
758 	joyimpulses[INJOY_MENU_HOTBAR_CLEAR] = 304;
759 	joyimpulses[INJOY_MENU_REFRESH_LOBBY] = 304;
760 	joyimpulses[INJOY_MENU_DONT_LOAD_SAVE] = 304;
761 	joyimpulses[INJOY_MENU_RANDOM_CHAR] = 304;
762 	joyimpulses[INJOY_MENU_DROP_ITEM] = 302;
763 	joyimpulses[INJOY_MENU_CYCLE_SHOP_LEFT] = 310;
764 	joyimpulses[INJOY_MENU_CYCLE_SHOP_RIGHT] = 311;
765 	joyimpulses[INJOY_MENU_BOOK_NEXT] = 311;
766 	joyimpulses[INJOY_MENU_BOOK_PREV] = 310;
767 	joyimpulses[INJOY_MENU_SETTINGS_NEXT] = 311;
768 	joyimpulses[INJOY_MENU_SETTINGS_PREV] = 310;
769 	joyimpulses[INJOY_MENU_INVENTORY_TAB] = 299;
770 	joyimpulses[INJOY_MENU_MAGIC_TAB] = 300;
771 	joyimpulses[INJOY_MENU_RANDOM_NAME] = 304;
772 	joyimpulses[INJOY_GAME_TOGGLECHATLOG] = SCANCODE_UNASSIGNED_BINDING;
773 	joyimpulses[INJOY_GAME_MINIMAPSCALE] = SCANCODE_UNASSIGNED_BINDING;
774 	joyimpulses[INJOY_GAME_FOLLOWERMENU] = SCANCODE_UNASSIGNED_BINDING;
775 	joyimpulses[INJOY_GAME_FOLLOWERMENU_LASTCMD] = SCANCODE_UNASSIGNED_BINDING;
776 	joyimpulses[INJOY_GAME_FOLLOWERMENU_CYCLE] = SCANCODE_UNASSIGNED_BINDING;
777 }
778 
defaultConfig()779 void defaultConfig()
780 {
781 #ifdef PANDORA
782 	consoleCommand("/res 960x600");
783 	consoleCommand("/gamma 2.000");
784 	consoleCommand("/smoothlighting");
785 	consoleCommand("/fullscreen");
786 #else
787 	consoleCommand("/res 1280x720");
788 	consoleCommand("/gamma 1.000");
789 	consoleCommand("/smoothlighting");
790 #endif
791 	consoleCommand("/shaking");
792 	consoleCommand("/bobbing");
793 	consoleCommand("/sfxvolume 64");
794 	consoleCommand("/sfxambientvolume 64");
795 	consoleCommand("/sfxenvironmentvolume 64");
796 	consoleCommand("/musvolume 32");
797 #ifdef PANDORA
798 	consoleCommand("/mousespeed 105");
799 	consoleCommand("/svflags 30");
800 	consoleCommand("/bind 82 IN_FORWARD");
801 	consoleCommand("/bind 80 IN_LEFT");
802 	consoleCommand("/bind 81 IN_BACK");
803 	consoleCommand("/bind 79 IN_RIGHT");
804 #else
805 	consoleCommand("/mousespeed 16");
806 	consoleCommand("/svflags 30");
807 	consoleCommand("/bind 26 IN_FORWARD");
808 	consoleCommand("/bind 4 IN_LEFT");
809 	consoleCommand("/bind 22 IN_BACK");
810 	consoleCommand("/bind 7 IN_RIGHT");
811 #endif
812 	consoleCommand("/bind 80 IN_TURNL");
813 	consoleCommand("/bind 79 IN_TURNR");
814 	consoleCommand("/bind 82 IN_UP");
815 	consoleCommand("/bind 81 IN_DOWN");
816 	consoleCommand("/bind 40 IN_CHAT");
817 	consoleCommand("/bind 56 IN_COMMAND");
818 	consoleCommand("/bind 43 IN_STATUS");
819 #ifdef PANDORA
820 	consoleCommand("/bind 75 IN_SPELL_LIST");
821 	consoleCommand("/bind 77 IN_CAST_SPELL");
822 	consoleCommand("/bind 78 IN_DEFEND");
823 #else
824 	consoleCommand("/bind 16 IN_SPELL_LIST");
825 	consoleCommand("/bind 9 IN_CAST_SPELL");
826 	consoleCommand("/bind 44 IN_DEFEND");
827 #endif
828 	consoleCommand("/bind 283 IN_ATTACK");
829 	consoleCommand("/bind 285 IN_USE");
830 	consoleCommand("/bind 21 IN_AUTOSORT");
831 	consoleCommand("/bind 27 IN_MINIMAPSCALE");
832 	consoleCommand("/bind 15 IN_TOGGLECHATLOG");
833 	consoleCommand("/bind 6 IN_FOLLOWERMENU");
834 	consoleCommand("/bind 20 IN_FOLLOWERMENU_LASTCMD");
835 	consoleCommand("/bind 8 IN_FOLLOWERMENU_CYCLENEXT");
836 	consoleCommand("/bind 286 IN_HOTBAR_SCROLL_LEFT");
837 	consoleCommand("/bind 287 IN_HOTBAR_SCROLL_RIGHT");
838 	consoleCommand("/bind 284 IN_HOTBAR_SCROLL_SELECT");
839 
840 	consoleCommand("/joybind 307 INJOY_STATUS");
841 	consoleCommand("/joybind 399 INJOY_SPELL_LIST"); //SCANCODE_UNASSIGNED_BINDING
842 	consoleCommand("/joybind 311 INJOY_GAME_CAST_SPELL");
843 	consoleCommand("/joybind 299 INJOY_GAME_DEFEND");
844 	consoleCommand("/joybind 300 INJOY_GAME_ATTACK");
845 	consoleCommand("/joybind 301 INJOY_GAME_USE");
846 	consoleCommand("/joybind 301 INJOY_MENU_USE");
847 	consoleCommand("/joybind 305 INJOY_PAUSE_MENU");
848 	consoleCommand("/joybind 303 INJOY_MENU_LEFT_CLICK");
849 	consoleCommand("/joybind 314 INJOY_DPAD_LEFT");
850 	consoleCommand("/joybind 315 INJOY_DPAD_RIGHT");
851 	consoleCommand("/joybind 312 INJOY_DPAD_UP");
852 	consoleCommand("/joybind 313 INJOY_DPAD_DOWN");
853 	consoleCommand("/joybind 301 INJOY_MENU_NEXT");
854 	consoleCommand("/joybind 315 INJOY_GAME_HOTBAR_NEXT");
855 	consoleCommand("/joybind 314 INJOY_GAME_HOTBAR_PREV");
856 	consoleCommand("/joybind 310 INJOY_GAME_HOTBAR_ACTIVATE");
857 	consoleCommand("/joybind 304 INJOY_MENU_CHEST_GRAB_ALL");
858 	consoleCommand("/joybind 304 INJOY_MENU_HOTBAR_CLEAR");
859 	consoleCommand("/joybind 304 INJOY_MENU_REFRESH_LOBBY");
860 	consoleCommand("/joybind 304 INJOY_MENU_DONT_LOAD_SAVE");
861 	consoleCommand("/joybind 304 INJOY_MENU_RANDOM_CHAR");
862 	consoleCommand("/joybind 301 INJOY_MENU_NEXT");
863 	consoleCommand("/joybind 302 INJOY_MENU_CANCEL");
864 	consoleCommand("/joybind 302 INJOY_MENU_DROP_ITEM");
865 	consoleCommand("/joybind 310 INJOY_MENU_CYCLE_SHOP_LEFT");
866 	consoleCommand("/joybind 311 INJOY_MENU_CYCLE_SHOP_RIGHT");
867 	consoleCommand("/joybind 311 INJOY_MENU_BOOK_NEXT");
868 	consoleCommand("/joybind 310 INJOY_MENU_BOOK_PREV");
869 	consoleCommand("/joybind 311 INJOY_MENU_SETTINGS_NEXT");
870 	consoleCommand("/joybind 310 INJOY_MENU_SETTINGS_PREV");
871 	consoleCommand("/joybind 299 INJOY_MENU_INVENTORY_TAB");
872 	consoleCommand("/joybind 300 INJOY_MENU_MAGIC_TAB");
873 	consoleCommand("/joybind 304 INJOY_MENU_RANDOM_NAME");
874 	consoleCommand("/joybind 399 INJOY_GAME_MINIMAPSCALE"); //SCANCODE_UNASSIGNED_BINDING
875 	consoleCommand("/joybind 399 INJOY_GAME_TOGGLECHATLOG"); //SCANCODE_UNASSIGNED_BINDING
876 	consoleCommand("/joybind 399 INJOY_GAME_FOLLOWERMENU"); //SCANCODE_UNASSIGNED_BINDING
877 	consoleCommand("/joybind 399 INJOY_GAME_FOLLOWERMENU_LASTCMD"); //SCANCODE_UNASSIGNED_BINDING
878 	consoleCommand("/joybind 399 INJOY_GAME_FOLLOWERMENU_CYCLE"); //SCANCODE_UNASSIGNED_BINDING
879 	consoleCommand("/gamepad_deadzone 8000");
880 	consoleCommand("/gamepad_trigger_deadzone 18000");
881 	consoleCommand("/gamepad_leftx_sensitivity 1400");
882 	consoleCommand("/gamepad_lefty_sensitivity 1400");
883 	consoleCommand("/gamepad_rightx_sensitivity 500");
884 	consoleCommand("/gamepad_righty_sensitivity 600");
885 	consoleCommand("/gamepad_menux_sensitivity 1400");
886 	consoleCommand("/gamepad_menuy_sensitivity 1400");
887 	consoleCommand("/autoappraisenewitems");
888 	return;
889 }
890 
891 static char impulsenames[NUMIMPULSES][23] =
892 {
893 	"FORWARD",
894 	"LEFT",
895 	"BACK",
896 	"RIGHT",
897 	"TURNL",
898 	"TURNR",
899 	"UP",
900 	"DOWN",
901 	"CHAT",
902 	"COMMAND",
903 	"STATUS",
904 	"SPELL_LIST",
905 	"CAST_SPELL",
906 	"DEFEND",
907 	"ATTACK",
908 	"USE",
909 	"AUTOSORT",
910 	"MINIMAPSCALE",
911 	"TOGGLECHATLOG",
912 	"FOLLOWERMENU_OPEN",
913 	"FOLLOWERMENU_LASTCMD",
914 	"FOLLOWERMENU_CYCLENEXT",
915 	"HOTBAR_SCROLL_LEFT",
916 	"HOTBAR_SCROLL_RIGHT",
917 	"HOTBAR_SCROLL_SELECT"
918 };
919 
920 static char joyimpulsenames[NUM_JOY_IMPULSES][30] =
921 {
922 	//Bi-functional:
923 	"STATUS",
924 	"SPELL_LIST",
925 	"PAUSE_MENU",
926 	"DPAD_LEFT",
927 	"DPAD_RIGHT",
928 	"DPAD_UP",
929 	"DPAD_DOWN",
930 
931 	//Menu exclusive:
932 	"MENU_LEFT_CLICK",
933 	"MENU_NEXT",
934 	"MENU_CANCEL",
935 	"MENU_SETTINGS_NEXT",
936 	"MENU_SETTINGS_PREV",
937 	"MENU_REFRESH_LOBBY",
938 	"MENU_DONT_LOAD_SAVE",
939 	"MENU_RANDOM_NAME",
940 	"MENU_RANDOM_CHAR",
941 	"MENU_INVENTORY_TAB",
942 	"MENU_MAGIC_TAB",
943 	"MENU_USE",
944 	"MENU_HOTBAR_CLEAR",
945 	"MENU_DROP_ITEM",
946 	"MENU_CHEST_GRAB_ALL",
947 	"MENU_CYCLE_SHOP_LEFT",
948 	"MENU_CYCLE_SHOP_RIGHT",
949 	"MENU_BOOK_NEXT",
950 	"MENU_BOOK_PREV",
951 
952 	//Game Exclusive:
953 	"GAME_USE",
954 	"GAME_DEFEND",
955 	"GAME_ATTACK",
956 	"GAME_CAST_SPELL",
957 	"GAME_HOTBAR_ACTIVATE",
958 	"GAME_HOTBAR_PREV",
959 	"GAME_HOTBAR_NEXT",
960 	"GAME_MINIMAPSCALE",
961 	"GAME_TOGGLECHATLOG",
962 	"GAME_FOLLOWERMENU_OPEN",
963 	"GAME_FOLLOWERMENU_LASTCMD",
964 	"GAME_FOLLOWERMENU_CYCLENEXT"
965 };
966 
967 /*-------------------------------------------------------------------------------
968 
969 	saveCommand
970 
971 	saves a command to the command history
972 
973 -------------------------------------------------------------------------------*/
974 
saveCommand(char * content)975 void saveCommand(char* content)
976 {
977 	newString(&command_history, 0xFFFFFFFF, content);
978 }
979 
980 /*-------------------------------------------------------------------------------
981 
982 	loadConfig
983 
984 	Reads the provided config file and executes the commands therein. Return
985 	value represents number of errors in config file
986 
987 -------------------------------------------------------------------------------*/
988 
loadConfig(char * filename)989 int loadConfig(char* filename)
990 {
991 	defaultImpulses(); //So that a config file that's missing impulses can get all them.
992 
993 	char str[1024];
994 	FILE* fp;
995 	bool mallocd = false;
996 
997 	printlog("Loading config '%s'...\n", filename);
998 
999 	if ( strstr(filename, ".cfg") == NULL )
1000 	{
1001 		char* filename2 = filename;
1002 		filename = (char*) malloc(sizeof(char) * 256);
1003 		strcpy(filename, filename2);
1004 		mallocd = true;
1005 		strcat(filename, ".cfg");
1006 	}
1007 
1008 	// open the config file
1009 	if ( (fp = fopen(filename, "rb")) == NULL )
1010 	{
1011 		printlog("warning: config file '%s' does not exist!\n", filename);
1012 		defaultConfig(); //Set up the game with the default config.
1013 		return 0;
1014 	}
1015 
1016 	// read commands from it
1017 	while ( fgets(str, 1024, fp) != NULL )
1018 	{
1019 		if ( str[0] != '#' && str[0] != '\n' && str[0] != '\r' )   // if this line is not white space or a comment
1020 		{
1021 			// execute command
1022 			consoleCommand(str);
1023 		}
1024 	}
1025 	fclose(fp);
1026 	if ( mallocd )
1027 	{
1028 		free(filename);
1029 	}
1030 
1031 	if ( impulses[IN_FOLLOWERMENU_CYCLENEXT] == impulses[IN_TURNR]
1032 		&& impulses[IN_TURNR] == 8 )
1033 	{
1034 		// reset to default arrow key to avoid overlapping keybinds on first launch.
1035 		// due to legacy keybind, now we have useful things to assign to q,e,z,c
1036 		impulses[IN_TURNR] = 79;
1037 		printlog("Legacy keys detected, conflict with IN_FOLLOWERMENU_CYCLENEXT. Automatically rebound IN_TURNR: %d (Right arrow key)\n", impulses[IN_TURNR]);
1038 	}
1039 	if ( impulses[IN_FOLLOWERMENU] == impulses[IN_UP]
1040 		&& impulses[IN_UP] == 6 )
1041 	{
1042 		// reset to default arrow key to avoid overlapping keybinds on first launch.
1043 		// due to legacy keybind, now we have useful things to assign to q,e,z,c
1044 		impulses[IN_UP] = 82;
1045 		printlog("Legacy keys detected, conflict with IN_FOLLOWERMENU_CYCLENEXT. Automatically rebound IN_UP: %d (Right arrow key)\n", impulses[IN_UP]);
1046 	}
1047 	if ( impulses[IN_FOLLOWERMENU_LASTCMD] == impulses[IN_TURNL]
1048 		&& impulses[IN_TURNL] == 20 )
1049 	{
1050 		// reset to default arrow key to avoid overlapping keybinds on first launch.
1051 		// due to legacy keybind, now we have useful things to assign to q,e,z,c
1052 		impulses[IN_TURNL] = 80;
1053 		printlog("Legacy keys detected, conflict with IN_FOLLOWERMENU_CYCLENEXT. Automatically rebound IN_TURNL: %d (Right arrow key)\n", impulses[IN_TURNL]);
1054 	}
1055 
1056 	return 0;
1057 }
1058 
loadDefaultConfig()1059 int loadDefaultConfig()
1060 {
1061 	char path[PATH_MAX];
1062 	completePath(path, "default.cfg", outputdir);
1063 	return loadConfig(path);
1064 }
1065 
1066 /*-------------------------------------------------------------------------------
1067 
1068 	saveConfig
1069 
1070 	Opens the provided config file and saves the status of certain variables
1071 	therein
1072 
1073 -------------------------------------------------------------------------------*/
1074 
saveConfig(char const * const _filename)1075 int saveConfig(char const * const _filename)
1076 {
1077 	char path[PATH_MAX];
1078 	time_t t = time(NULL);
1079 	struct tm tm = *localtime(&t);
1080 	FILE* fp;
1081 	int c;
1082 	char *filename = strdup(_filename);
1083 
1084 	printlog("Saving config '%s'...\n", filename);
1085 
1086 	if ( strstr(filename, ".cfg") == NULL )
1087 	{
1088 		filename = (char*) realloc(filename, sizeof(char) * (strlen(filename) + 5));
1089 		strcat(filename, ".cfg");
1090 	}
1091 
1092 	completePath(path, filename, outputdir);
1093 
1094 	// open the config file
1095 	if ( (fp = fopen(path, "wb")) == NULL )
1096 	{
1097 		printlog("ERROR: failed to save config file '%s'!\n", filename);
1098 		free(filename);
1099 		return 1;
1100 	}
1101 
1102 	// write config header
1103 	fprintf(fp, "# %s\n", filename);
1104 	fprintf(fp, "# this file was auto-generated on %d-%02d-%02d at %02d:%02d:%02d\n\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
1105 
1106 	// write contents of config
1107 	fprintf(fp, "/lang %s\n", languageCode);
1108 	fprintf(fp, "/res %dx%d\n", xres, yres);
1109 	fprintf(fp, "/gamma %3.3f\n", vidgamma);
1110 	fprintf(fp, "/fov %d\n", fov);
1111 	fprintf(fp, "/fps %d\n", fpsLimit);
1112 	fprintf(fp, "/svflags %d\n", svFlags);
1113 	if ( lastname != "" )
1114 	{
1115 		fprintf(fp, "/lastname %s\n", lastname.c_str());
1116 	}
1117 	if ( smoothlighting )
1118 	{
1119 		fprintf(fp, "/smoothlighting\n");
1120 	}
1121 	if ( fullscreen )
1122 	{
1123 		fprintf(fp, "/fullscreen\n");
1124 	}
1125 	if ( borderless )
1126 	{
1127 		fprintf(fp, "/borderless\n");
1128 	}
1129 	if ( shaking )
1130 	{
1131 		fprintf(fp, "/shaking\n");
1132 	}
1133 	if ( bobbing )
1134 	{
1135 		fprintf(fp, "/bobbing\n");
1136 	}
1137 	fprintf(fp, "/sfxvolume %d\n", sfxvolume);
1138 	fprintf(fp, "/sfxambientvolume %d\n", sfxAmbientVolume);
1139 	fprintf(fp, "/sfxenvironmentvolume %d\n", sfxEnvironmentVolume);
1140 	fprintf(fp, "/musvolume %d\n", musvolume);
1141 	for (c = 0; c < NUMIMPULSES; c++)
1142 	{
1143 		fprintf(fp, "/bind %d IN_%s\n", impulses[c], impulsenames[c]);
1144 	}
1145 	for (c = 0; c < NUM_JOY_IMPULSES; c++)
1146 	{
1147 		fprintf(fp, "/joybind %d INJOY_%s\n", joyimpulses[c], joyimpulsenames[c]);
1148 	}
1149 	fprintf(fp, "/mousespeed %d\n", (int)(mousespeed));
1150 	if ( reversemouse )
1151 	{
1152 		fprintf(fp, "/reversemouse\n");
1153 	}
1154 	if ( smoothmouse )
1155 	{
1156 		fprintf(fp, "/smoothmouse\n");
1157 	}
1158 	if ( disablemouserotationlimit )
1159 	{
1160 		fprintf(fp, "/disablemouserotationlimit\n");
1161 	}
1162 	if (last_ip[0])
1163 	{
1164 		fprintf(fp, "/ip %s\n", last_ip);
1165 	}
1166 	if (last_port[0])
1167 	{
1168 		fprintf(fp, "/port %s\n", last_port);
1169 	}
1170 	if (!spawn_blood)
1171 	{
1172 		fprintf(fp, "/noblood\n");
1173 	}
1174 	if ( !flickerLights )
1175 	{
1176 		fprintf(fp, "/nolightflicker\n");
1177 	}
1178 	if ( verticalSync )
1179 	{
1180 		fprintf(fp, "/vsync\n");
1181 	}
1182 	if ( !showStatusEffectIcons )
1183 	{
1184 		fprintf(fp, "/hidestatusicons\n");
1185 	}
1186 	if ( minimapPingMute )
1187 	{
1188 		fprintf(fp, "/muteping\n");
1189 	}
1190 	if ( mute_audio_on_focus_lost )
1191 	{
1192 		fprintf(fp, "/muteaudiofocuslost\n");
1193 	}
1194 	if ( mute_player_monster_sounds )
1195 	{
1196 		fprintf(fp, "/muteplayermonstersounds\n");
1197 	}
1198 	if (colorblind)
1199 	{
1200 		fprintf(fp, "/colorblind\n");
1201 	}
1202 	if (!capture_mouse)
1203 	{
1204 		fprintf(fp, "/nocapturemouse\n");
1205 	}
1206 	if (broadcast)
1207 	{
1208 		fprintf(fp, "/broadcast\n");
1209 	}
1210 	if (nohud)
1211 	{
1212 		fprintf(fp, "/nohud\n");
1213 	}
1214 	if (!auto_hotbar_new_items)
1215 	{
1216 		fprintf(fp, "/disablehotbarnewitems\n");
1217 	}
1218 	for ( c = 0; c < NUM_HOTBAR_CATEGORIES; ++c )
1219 	{
1220 		fprintf(fp, "/hotbarenablecategory %d %d\n", c, auto_hotbar_categories[c]);
1221 	}
1222 	for ( c = 0; c < NUM_AUTOSORT_CATEGORIES; ++c )
1223 	{
1224 		fprintf(fp, "/autosortcategory %d %d\n", c, autosort_inventory_categories[c]);
1225 	}
1226 	if ( hotbar_numkey_quick_add )
1227 	{
1228 		fprintf(fp, "/quickaddtohotbar\n");
1229 	}
1230 	if ( lock_right_sidebar )
1231 	{
1232 		fprintf(fp, "/locksidebar\n");
1233 	}
1234 	if ( show_game_timer_always )
1235 	{
1236 		fprintf(fp, "/showgametimer\n");
1237 	}
1238 	if (disable_messages)
1239 	{
1240 		fprintf(fp, "/disablemessages\n");
1241 	}
1242 	if (right_click_protect)
1243 	{
1244 		fprintf(fp, "/right_click_protect\n");
1245 	}
1246 	if (auto_appraise_new_items)
1247 	{
1248 		fprintf(fp, "/autoappraisenewitems\n");
1249 	}
1250 	if (startfloor)
1251 	{
1252 		fprintf(fp, "/startfloor %d\n", startfloor);
1253 	}
1254 	if (splitscreen)
1255 	{
1256 		fprintf(fp, "/splitscreen\n");
1257 	}
1258 	if ( useModelCache )
1259 	{
1260 		fprintf(fp, "/usemodelcache\n");
1261 	}
1262 	fprintf(fp, "/lastcharacter %d %d %d %d\n", lastCreatedCharacterSex, lastCreatedCharacterClass, lastCreatedCharacterAppearance, lastCreatedCharacterRace);
1263 	fprintf(fp, "/gamepad_deadzone %d\n", gamepad_deadzone);
1264 	fprintf(fp, "/gamepad_trigger_deadzone %d\n", gamepad_trigger_deadzone);
1265 	fprintf(fp, "/gamepad_leftx_sensitivity %d\n", gamepad_leftx_sensitivity);
1266 	fprintf(fp, "/gamepad_lefty_sensitivity %d\n", gamepad_lefty_sensitivity);
1267 	fprintf(fp, "/gamepad_rightx_sensitivity %d\n", gamepad_rightx_sensitivity);
1268 	fprintf(fp, "/gamepad_righty_sensitivity %d\n", gamepad_righty_sensitivity);
1269 	fprintf(fp, "/gamepad_menux_sensitivity %d\n", gamepad_menux_sensitivity);
1270 	fprintf(fp, "/gamepad_menuy_sensitivity %d\n", gamepad_menuy_sensitivity);
1271 	if (gamepad_rightx_invert)
1272 	{
1273 		fprintf(fp, "/gamepad_rightx_invert\n");
1274 	}
1275 	if (gamepad_righty_invert)
1276 	{
1277 		fprintf(fp, "/gamepad_righty_invert\n");
1278 	}
1279 	if (gamepad_leftx_invert)
1280 	{
1281 		fprintf(fp, "/gamepad_leftx_invert\n");
1282 	}
1283 	if (gamepad_lefty_invert)
1284 	{
1285 		fprintf(fp, "/gamepad_lefty_invert\n");
1286 	}
1287 	if (gamepad_menux_invert)
1288 	{
1289 		fprintf(fp, "/gamepad_menux_invert\n");
1290 	}
1291 	if (gamepad_menuy_invert)
1292 	{
1293 		fprintf(fp, "/gamepad_menuy_invert\n");
1294 	}
1295 	fprintf(fp, "/skipintro\n");
1296 	fprintf(fp, "/minimaptransparencyfg %d\n", minimapTransparencyForeground);
1297 	fprintf(fp, "/minimaptransparencybg %d\n", minimapTransparencyBackground);
1298 	fprintf(fp, "/minimapscale %d\n", minimapScale);
1299 	fprintf(fp, "/minimapobjectzoom %d\n", minimapObjectZoom);
1300 	if ( uiscale_charactersheet )
1301 	{
1302 		fprintf(fp, "/uiscale_charsheet\n");
1303 	}
1304 	if ( uiscale_skillspage )
1305 	{
1306 		fprintf(fp, "/uiscale_skillsheet\n");
1307 	}
1308 	fprintf(fp, "/uiscale_inv %f\n", uiscale_inventory);
1309 	fprintf(fp, "/uiscale_hotbar %f\n", uiscale_hotbar);
1310 	fprintf(fp, "/uiscale_chatbox %f\n", uiscale_chatlog);
1311 	fprintf(fp, "/uiscale_playerbars %f\n", uiscale_playerbars);
1312 	if ( hide_playertags )
1313 	{
1314 		fprintf(fp, "/hideplayertags\n");
1315 	}
1316 	if ( hide_statusbar )
1317 	{
1318 		fprintf(fp, "/hidestatusbar\n");
1319 	}
1320 	if ( show_skill_values )
1321 	{
1322 		fprintf(fp, "/showskillvalues\n");
1323 	}
1324 	if ( disableMultithreadedSteamNetworking )
1325 	{
1326 		fprintf(fp, "/disablenetworkmultithreading\n");
1327 	}
1328 	if ( disableFPSLimitOnNetworkMessages )
1329 	{
1330 		fprintf(fp, "/disablenetcodefpslimit\n");
1331 	}
1332 #ifdef USE_EOS
1333 	if ( LobbyHandler.crossplayEnabled )
1334 	{
1335 		fprintf(fp, "/crossplay\n");
1336 	}
1337 #endif // USE_EOS
1338 
1339 	if ( !gamemods_mountedFilepaths.empty() )
1340 	{
1341 		std::vector<std::pair<std::string, std::string>>::iterator it;
1342 		for ( it = gamemods_mountedFilepaths.begin(); it != gamemods_mountedFilepaths.end(); ++it )
1343 		{
1344 			fprintf(fp, "/loadmod dir:%s name:%s", (*it).first.c_str(), (*it).second.c_str());
1345 #ifdef STEAMWORKS
1346 			for ( std::vector<std::pair<std::string, uint64>>::iterator itId = gamemods_workshopLoadedFileIDMap.begin();
1347 				itId != gamemods_workshopLoadedFileIDMap.end(); ++itId )
1348 			{
1349 				if ( itId->first.compare((*it).second) == 0 )
1350 				{
1351 					fprintf(fp, " fileid:%lld", (*itId).second);
1352 				}
1353 			}
1354 #endif // STEAMWORKS
1355 			fprintf(fp, "\n");
1356 		}
1357 	}
1358 
1359 	fclose(fp);
1360 	free(filename);
1361 	return 0;
1362 }
1363 
1364 /*-------------------------------------------------------------------------------
1365 
1366 	mouseInBounds
1367 
1368 	Returns true if the mouse is within the rectangle specified, otherwise
1369 	returns false
1370 
1371 -------------------------------------------------------------------------------*/
1372 
mouseInBounds(int x1,int x2,int y1,int y2)1373 bool mouseInBounds(int x1, int x2, int y1, int y2)
1374 {
1375 	if (omousey >= y1 && omousey < y2)
1376 		if (omousex >= x1 && omousex < x2)
1377 		{
1378 			return true;
1379 		}
1380 
1381 	return false;
1382 }
1383 
getHotbar(int x,int y)1384 hotbar_slot_t* getHotbar(int x, int y)
1385 {
1386 	if (x >= HOTBAR_START_X && x < HOTBAR_START_X + (10 * hotbar_img->w * uiscale_hotbar) && y >= STATUS_Y - hotbar_img->h * uiscale_hotbar && y < STATUS_Y)
1387 	{
1388 		int relx = x - HOTBAR_START_X; //X relative to the start of the hotbar.
1389 		return &hotbar[static_cast<int>(relx / (hotbar_img->w * uiscale_hotbar))]; //The slot will clearly be the x divided by the width of a slot
1390 	}
1391 
1392 	return NULL;
1393 }
1394 
1395 /*-------------------------------------------------------------------------------
1396 
1397 	getInputName
1398 
1399 	Returns the character string from the
1400 
1401 -------------------------------------------------------------------------------*/
1402 
getInputName(Uint32 scancode)1403 const char* getInputName(Uint32 scancode)
1404 {
1405 	if ( scancode >= 0 && scancode < 283 )
1406 	{
1407 		return SDL_GetKeyName(SDL_GetKeyFromScancode(static_cast<SDL_Scancode>(scancode)));
1408 	}
1409 	else if ( scancode < 299 )
1410 	{
1411 		switch ( scancode )
1412 		{
1413 			case 283:
1414 				return "Left Click";
1415 			case 284:
1416 				return "Middle Click";
1417 			case 285:
1418 				return "Right Click";
1419 			case 286:
1420 				return "Wheel up";
1421 			case 287:
1422 				return "Wheel down";
1423 			case 288:
1424 				return "Mouse 4";
1425 			case 289:
1426 				return "Mouse 5";
1427 			case 290:
1428 				return "Mouse 6";
1429 			case 291:
1430 				return "Mouse 7";
1431 			case 292:
1432 				return "Mouse 8";
1433 			case 293:
1434 				return "Mouse 11";
1435 			case 294:
1436 				return "Mouse 12";
1437 			case 295:
1438 				return "Mouse 13";
1439 			case 296:
1440 				return "Mouse 14";
1441 			case 297:
1442 				return "Mouse 15";
1443 			case 298:
1444 				return "Mouse 16";
1445 			default:
1446 				return "Unknown key";
1447 		}
1448 	}
1449 	else if ( scancode < 301 )     //Game Controller triggers.
1450 	{
1451 		switch ( scancode )
1452 		{
1453 			case 299:
1454 				return "Left Trigger";
1455 			case 300:
1456 				return "Right Trigger";
1457 			default:
1458 				return "Unknown trigger";
1459 		}
1460 	}
1461 	else if ( scancode < 317 )     //Game controller buttons.
1462 	{
1463 		return SDL_GameControllerGetStringForButton(static_cast<SDL_GameControllerButton>(scancode - 301));
1464 	}
1465 	else
1466 	{
1467 		if ( scancode == SCANCODE_UNASSIGNED_BINDING )
1468 		{
1469 			return "Unassigned key";
1470 		}
1471 		return "Unknown key";
1472 	}
1473 }
1474 
1475 /*-------------------------------------------------------------------------------
1476 
1477 	inputPressed
1478 
1479 	Returns non-zero number if the given key/mouse/joystick button is being
1480 	pressed, returns 0 otherwise
1481 
1482 -------------------------------------------------------------------------------*/
1483 
1484 Sint8 dummy_value = 0; //THIS LINE IS AN UTTER BODGE to stop this function from crashing.
1485 
inputPressed(Uint32 scancode)1486 Sint8* inputPressed(Uint32 scancode)
1487 {
1488 	if (scancode >= 0 && scancode < 283)
1489 	{
1490 		// usual (keyboard) scancode range
1491 		return &keystatus[scancode];
1492 	}
1493 	else if (scancode < 299)
1494 	{
1495 		// mouse scancodes
1496 		return &mousestatus[scancode - 282];
1497 	}
1498 	else if (scancode < 301)
1499 	{
1500 		//Analog joystick triggers are mapped to digital status (0 = not pressed, 1 = pressed).
1501 		return &joy_trigger_status[scancode - 299];
1502 	}
1503 	else if (scancode < 318)
1504 	{
1505 		return &joystatus[scancode - 301];
1506 	}
1507 	else
1508 	{
1509 		// bad scancode
1510 		//return nullptr; //This crashes.
1511 		dummy_value = 0;
1512 		return &dummy_value;
1513 		//Not an ideal solution, but...
1514 	}
1515 }
1516 
selectHotbarSlot(int slot)1517 void selectHotbarSlot(int slot)
1518 {
1519 	if (slot < 0)
1520 	{
1521 		slot = NUM_HOTBAR_SLOTS - 1;
1522 	}
1523 	if (slot >= NUM_HOTBAR_SLOTS)
1524 	{
1525 		slot = 0;
1526 	}
1527 
1528 	current_hotbar = slot;
1529 
1530 	hotbarHasFocus = true;
1531 }
1532 
openStatusScreen(int whichGUIMode,int whichInventoryMode)1533 void openStatusScreen(int whichGUIMode, int whichInventoryMode)
1534 {
1535 	shootmode = false;
1536 	gui_mode = whichGUIMode;
1537 	selectedItem = nullptr;
1538 	inventory_mode = whichInventoryMode;
1539 	SDL_SetRelativeMouseMode(SDL_FALSE);
1540 	SDL_WarpMouseInWindow(screen, xres / 2, yres / 2);
1541 	mousex = xres / 2;
1542 	mousey = yres / 2;
1543 	omousex = mousex;
1544 	omousey = mousey;
1545 	attributespage = 0;
1546 	//proficienciesPage = 0;
1547 }
1548 
closeAllGUIs(CloseGUIShootmode shootmodeAction,CloseGUIIgnore whatToClose)1549 void closeAllGUIs(CloseGUIShootmode shootmodeAction, CloseGUIIgnore whatToClose)
1550 {
1551 	CloseIdentifyGUI();
1552 	closeRemoveCurseGUI();
1553 	GenericGUI.closeGUI();
1554 	if ( whatToClose != CLOSEGUI_DONT_CLOSE_FOLLOWERGUI )
1555 	{
1556 		FollowerMenu.closeFollowerMenuGUI();
1557 	}
1558 	if ( whatToClose != CLOSEGUI_DONT_CLOSE_CHEST )
1559 	{
1560 		if ( openedChest[clientnum] )
1561 		{
1562 			openedChest[clientnum]->closeChest();
1563 		}
1564 	}
1565 
1566 	if ( whatToClose != CLOSEGUI_DONT_CLOSE_SHOP && shopkeeper != 0 )
1567 	{
1568 		if ( multiplayer != CLIENT )
1569 		{
1570 			Entity* entity = uidToEntity(shopkeeper);
1571 			if ( entity )
1572 			{
1573 				entity->skill[0] = 0;
1574 				if ( uidToEntity(entity->skill[1]) )
1575 				{
1576 					monsterMoveAside(entity, uidToEntity(entity->skill[1]));
1577 				}
1578 				entity->skill[1] = 0;
1579 			}
1580 		}
1581 		else
1582 		{
1583 			// inform server that we're done talking to shopkeeper
1584 			strcpy((char*)net_packet->data, "SHPC");
1585 			SDLNet_Write32((Uint32)shopkeeper, &net_packet->data[4]);
1586 			net_packet->address.host = net_server.host;
1587 			net_packet->address.port = net_server.port;
1588 			net_packet->len = 8;
1589 			sendPacketSafe(net_sock, -1, net_packet, 0);
1590 			list_FreeAll(shopInv);
1591 		}
1592 
1593 		shopkeeper = 0;
1594 
1595 		//Clean up shopkeeper gamepad code here.
1596 		selectedShopSlot = -1;
1597 	}
1598 	gui_mode = GUI_MODE_NONE;
1599 	if ( shootmodeAction == CLOSEGUI_ENABLE_SHOOTMODE )
1600 	{
1601 		shootmode = true;
1602 	}
1603 }
1604 
initFollowerMenuGUICursor(bool openInventory)1605 void FollowerRadialMenu::initFollowerMenuGUICursor(bool openInventory)
1606 {
1607 	if ( openInventory )
1608 	{
1609 		openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM);
1610 	}
1611 	omousex = mousex;
1612 	omousey = mousey;
1613 	if ( menuX == -1 )
1614 	{
1615 		menuX = mousex;
1616 	}
1617 	if ( menuY == -1 )
1618 	{
1619 		menuY = mousey;
1620 	}
1621 }
1622 
closeFollowerMenuGUI(bool clearRecentEntity)1623 void FollowerRadialMenu::closeFollowerMenuGUI(bool clearRecentEntity)
1624 {
1625 	followerToCommand = nullptr;
1626 	menuX = -1;
1627 	menuY = -1;
1628 	moveToX = -1;
1629 	moveToY = -1;
1630 	if ( clearRecentEntity )
1631 	{
1632 		recentEntity = nullptr;
1633 	}
1634 	menuToggleClick = false;
1635 	holdWheel = false;
1636 	if ( accessedMenuFromPartySheet )
1637 	{
1638 		if ( optionSelected == ALLY_CMD_MOVETO_CONFIRM || optionSelected == ALLY_CMD_ATTACK_CONFIRM )
1639 		{
1640 			initFollowerMenuGUICursor(true);
1641 		}
1642 		accessedMenuFromPartySheet = false;
1643 		if ( optionSelected != ALLY_CMD_CANCEL && optionSelected != -1 )
1644 		{
1645 			mousex = partySheetMouseX;
1646 			mousey = partySheetMouseY;
1647 			SDL_SetRelativeMouseMode(SDL_FALSE);
1648 			SDL_WarpMouseInWindow(screen, mousex, mousey);
1649 		}
1650 	}
1651 	optionSelected = -1;
1652 }
1653 
followerMenuIsOpen()1654 bool FollowerRadialMenu::followerMenuIsOpen()
1655 {
1656 	if ( selectMoveTo || followerToCommand != nullptr )
1657 	{
1658 		return true;
1659 	}
1660 	return false;
1661 }
1662 
drawFollowerMenu()1663 void FollowerRadialMenu::drawFollowerMenu()
1664 {
1665 	if ( selectMoveTo )
1666 	{
1667 		if ( !followerToCommand )
1668 		{
1669 			selectMoveTo = false;
1670 		}
1671 		return;
1672 	}
1673 
1674 	int disableOption = 0;
1675 	bool keepWheelOpen = false;
1676 
1677 	if ( followerToCommand )
1678 	{
1679 		if ( players[clientnum] && players[clientnum]->entity
1680 			&& followerToCommand->monsterTarget == players[clientnum]->entity->getUID() )
1681 		{
1682 			closeAllGUIs(CLOSEGUI_ENABLE_SHOOTMODE, CLOSEGUI_DONT_CLOSE_FOLLOWERGUI);
1683 			return;
1684 		}
1685 
1686 		Stat* followerStats = followerToCommand->getStats();
1687 		if ( !followerStats )
1688 		{
1689 			return;
1690 		}
1691 		bool tinkeringFollower = isTinkeringFollower(followerStats->type);
1692 		int skillLVL = 0;
1693 		if ( stats[clientnum] && players[clientnum] && players[clientnum]->entity )
1694 		{
1695 			if ( (*inputPressed(impulses[IN_FOLLOWERMENU_LASTCMD]) || *inputPressed(joyimpulses[INJOY_GAME_FOLLOWERMENU_LASTCMD])) && optionPrevious != -1 )
1696 			{
1697 				if ( optionPrevious == ALLY_CMD_ATTACK_CONFIRM )
1698 				{
1699 					optionPrevious = ALLY_CMD_ATTACK_SELECT;
1700 				}
1701 				else if ( optionPrevious == ALLY_CMD_MOVETO_CONFIRM )
1702 				{
1703 					optionPrevious = ALLY_CMD_MOVETO_SELECT;
1704 				}
1705 				else if ( optionPrevious == ALLY_CMD_FOLLOW || optionPrevious == ALLY_CMD_DEFEND )
1706 				{
1707 					if ( followerToCommand->monsterAllyState == ALLY_STATE_DEFEND || followerToCommand->monsterAllyState == ALLY_STATE_MOVETO )
1708 					{
1709 						optionPrevious = ALLY_CMD_FOLLOW;
1710 					}
1711 					else
1712 					{
1713 						optionPrevious = ALLY_CMD_DEFEND;
1714 					}
1715 				}
1716 				optionSelected = optionPrevious;
1717 			}
1718 			if ( optionSelected >= ALLY_CMD_DEFEND && optionSelected < ALLY_CMD_END && optionSelected != ALLY_CMD_ATTACK_CONFIRM )
1719 			{
1720 				skillLVL = stats[clientnum]->PROFICIENCIES[PRO_LEADERSHIP] + statGetCHR(stats[clientnum], players[clientnum]->entity);
1721 				if ( tinkeringFollower )
1722 				{
1723 					skillLVL = stats[clientnum]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[clientnum], players[clientnum]->entity);
1724 				}
1725 				if ( followerToCommand->monsterAllySummonRank != 0 )
1726 				{
1727 					skillLVL = SKILL_LEVEL_LEGENDARY;
1728 				}
1729 				if ( optionSelected == ALLY_CMD_ATTACK_SELECT )
1730 				{
1731 					if ( attackCommandOnly(followerStats->type) )
1732 					{
1733 						// attack only.
1734 						disableOption = FollowerMenu.optionDisabledForCreature(skillLVL, followerStats->type, ALLY_CMD_ATTACK_CONFIRM);
1735 					}
1736 					else
1737 					{
1738 						disableOption = FollowerMenu.optionDisabledForCreature(skillLVL, followerStats->type, optionSelected);
1739 					}
1740 				}
1741 				else
1742 				{
1743 					disableOption = FollowerMenu.optionDisabledForCreature(skillLVL, followerStats->type, optionSelected);
1744 				}
1745 			}
1746 		}
1747 		// process commands if option selected on the wheel.
1748 		if ( (!(*inputPressed(impulses[IN_USE])) && !(*inputPressed(joyimpulses[INJOY_GAME_USE])) && !menuToggleClick && !holdWheel)
1749 			|| ((*inputPressed(impulses[IN_USE]) || *inputPressed(joyimpulses[INJOY_GAME_USE])) && menuToggleClick)
1750 			|| (!(*inputPressed(impulses[IN_FOLLOWERMENU] || !(*inputPressed(joyimpulses[INJOY_GAME_FOLLOWERMENU])) )) && holdWheel && !menuToggleClick)
1751 			|| (*inputPressed(impulses[IN_FOLLOWERMENU_LASTCMD] || *inputPressed(joyimpulses[INJOY_GAME_FOLLOWERMENU_LASTCMD])) && optionPrevious != -1)
1752 			)
1753 		{
1754 			if ( menuToggleClick )
1755 			{
1756 				*inputPressed(impulses[IN_USE]) = 0;
1757 				*inputPressed(joyimpulses[INJOY_GAME_USE]) = 0;
1758 				menuToggleClick = false;
1759 				if ( optionSelected == -1 )
1760 				{
1761 					optionSelected = ALLY_CMD_CANCEL;
1762 				}
1763 			}
1764 
1765 			bool usingLastCmd = false;
1766 			if ( *inputPressed(impulses[IN_FOLLOWERMENU_LASTCMD]) || *inputPressed(joyimpulses[INJOY_GAME_FOLLOWERMENU_LASTCMD]) )
1767 			{
1768 				usingLastCmd = true;
1769 			}
1770 
1771 			if ( followerStats->type == GYROBOT )
1772 			{
1773 				monsterGyroBotConvertCommand(&optionSelected);
1774 			}
1775 			else if ( followerStats->type == DUMMYBOT || followerStats->type == SENTRYBOT || followerStats->type == SPELLBOT )
1776 			{
1777 				if ( optionSelected == ALLY_CMD_SPECIAL )
1778 				{
1779 					optionSelected = ALLY_CMD_DUMMYBOT_RETURN;
1780 				}
1781 			}
1782 			else if ( followerToCommand->monsterAllySummonRank != 0 && optionSelected == ALLY_CMD_CLASS_TOGGLE )
1783 			{
1784 				optionSelected = ALLY_CMD_RETURN_SOUL;
1785 			}
1786 
1787 			keepWheelOpen = (optionSelected == ALLY_CMD_CLASS_TOGGLE
1788 				|| optionSelected == ALLY_CMD_PICKUP_TOGGLE
1789 				|| optionSelected == ALLY_CMD_GYRO_LIGHT_TOGGLE
1790 				|| optionSelected == ALLY_CMD_GYRO_DETECT_TOGGLE);
1791 			if ( disableOption != 0 && !usingLastCmd )
1792 			{
1793 				keepWheelOpen = true;
1794 			}
1795 
1796 			if ( *inputPressed(impulses[IN_FOLLOWERMENU_LASTCMD]) || *inputPressed(joyimpulses[INJOY_GAME_FOLLOWERMENU_LASTCMD]) )
1797 			{
1798 				if ( keepWheelOpen )
1799 				{
1800 					// need to reset the coordinates of the mouse.
1801 					initFollowerMenuGUICursor();
1802 				}
1803 				*inputPressed(impulses[IN_FOLLOWERMENU_LASTCMD]) = 0;
1804 				*inputPressed(joyimpulses[INJOY_GAME_FOLLOWERMENU_LASTCMD]) = 0;
1805 			}
1806 
1807 			if ( optionSelected != -1 )
1808 			{
1809 				holdWheel = false;
1810 				if ( optionSelected != ALLY_CMD_ATTACK_CONFIRM && optionSelected != ALLY_CMD_MOVETO_CONFIRM )
1811 				{
1812 					playSound(139, 64); // click
1813 				}
1814 				else
1815 				{
1816 					playSound(399, 48); // ping
1817 				}
1818 				// return to shootmode and close guis etc. TODO: tidy up interface code into 1 spot?
1819 				if ( !keepWheelOpen )
1820 				{
1821 					if ( !accessedMenuFromPartySheet
1822 						|| optionSelected == ALLY_CMD_MOVETO_SELECT
1823 						|| optionSelected == ALLY_CMD_ATTACK_SELECT
1824 						|| optionSelected == ALLY_CMD_CANCEL )
1825 					{
1826 						closeAllGUIs(CLOSEGUI_ENABLE_SHOOTMODE, CLOSEGUI_DONT_CLOSE_FOLLOWERGUI);
1827 					}
1828 				}
1829 
1830 				if ( disableOption == 0
1831 					&& (optionSelected == ALLY_CMD_MOVETO_SELECT || optionSelected == ALLY_CMD_ATTACK_SELECT) )
1832 				{
1833 					// return early, let the player use select command point.
1834 					selectMoveTo = true;
1835 					return;
1836 				}
1837 				else
1838 				{
1839 					if ( disableOption == 0 )
1840 					{
1841 						if ( optionSelected == ALLY_CMD_DEFEND &&
1842 							(followerToCommand->monsterAllyState == ALLY_STATE_DEFEND || followerToCommand->monsterAllyState == ALLY_STATE_MOVETO) )
1843 						{
1844 							optionSelected = ALLY_CMD_FOLLOW;
1845 						}
1846 						if ( multiplayer == CLIENT )
1847 						{
1848 							if ( optionSelected == ALLY_CMD_ATTACK_CONFIRM )
1849 							{
1850 								sendAllyCommandClient(clientnum, followerToCommand->getUID(), optionSelected, 0, 0, followerToCommand->monsterAllyInteractTarget);
1851 							}
1852 							else if ( optionSelected == ALLY_CMD_MOVETO_CONFIRM )
1853 							{
1854 								sendAllyCommandClient(clientnum, followerToCommand->getUID(), optionSelected, moveToX, moveToY);
1855 							}
1856 							else
1857 							{
1858 								sendAllyCommandClient(clientnum, followerToCommand->getUID(), optionSelected, 0, 0);
1859 							}
1860 						}
1861 						else
1862 						{
1863 							followerToCommand->monsterAllySendCommand(optionSelected, moveToX, moveToY, followerToCommand->monsterAllyInteractTarget);
1864 						}
1865 					}
1866 					else if ( usingLastCmd )
1867 					{
1868 						// tell player current monster can't do what you asked (e.g using last command & swapping between monsters with different requirements)
1869 						if ( followerStats->type < KOBOLD ) // Original monster count
1870 						{
1871 							if ( disableOption < 0 )
1872 							{
1873 								messagePlayer(clientnum, language[3640], language[90 + followerStats->type]);
1874 							}
1875 							else if ( tinkeringFollower )
1876 							{
1877 								messagePlayer(clientnum, language[3639], language[90 + followerStats->type]);
1878 							}
1879 							else
1880 							{
1881 								messagePlayer(clientnum, language[3638], language[90 + followerStats->type]);
1882 							}
1883 						}
1884 						else if ( followerStats->type >= KOBOLD ) //New monsters
1885 						{
1886 							if ( disableOption < 0 )
1887 							{
1888 								messagePlayer(clientnum, language[3640], language[2000 + (followerStats->type - KOBOLD)]);
1889 							}
1890 							else if ( tinkeringFollower )
1891 							{
1892 								messagePlayer(clientnum, language[3639], language[2000 + (followerStats->type - KOBOLD)]);
1893 							}
1894 							else
1895 							{
1896 								messagePlayer(clientnum, language[3638], language[2000 + (followerStats->type - KOBOLD)]);
1897 							}
1898 						}
1899 					}
1900 
1901 					if ( optionSelected != ALLY_CMD_CANCEL && disableOption == 0 )
1902 					{
1903 						optionPrevious = optionSelected;
1904 					}
1905 
1906 					if ( !keepWheelOpen )
1907 					{
1908 						closeFollowerMenuGUI();
1909 					}
1910 					optionSelected = -1;
1911 				}
1912 			}
1913 			else
1914 			{
1915 				menuToggleClick = true;
1916 			}
1917 		}
1918 	}
1919 
1920 	if ( followerToCommand )
1921 	{
1922 		int skillLVL = 0;
1923 		Stat* followerStats = followerToCommand->getStats();
1924 		if ( !followerStats )
1925 		{
1926 			return;
1927 		}
1928 		bool tinkeringFollower = isTinkeringFollower(followerStats->type);
1929 		if ( stats[clientnum] && players[clientnum] && players[clientnum]->entity )
1930 		{
1931 			skillLVL = stats[clientnum]->PROFICIENCIES[PRO_LEADERSHIP] + statGetCHR(stats[clientnum], players[clientnum]->entity);
1932 			if ( tinkeringFollower )
1933 			{
1934 				skillLVL = stats[clientnum]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[clientnum], players[clientnum]->entity);
1935 			}
1936 			else if ( followerToCommand->monsterAllySummonRank != 0 )
1937 			{
1938 				skillLVL = SKILL_LEVEL_LEGENDARY;
1939 			}
1940 		}
1941 
1942 		SDL_Rect src;
1943 		src.x = xres / 2;
1944 		src.y = yres / 2;
1945 
1946 		int numoptions = 8;
1947 		real_t angleStart = PI / 2 - (PI / numoptions);
1948 		real_t angleMiddle = angleStart + PI / numoptions;
1949 		real_t angleEnd = angleMiddle + PI / numoptions;
1950 		int radius = 140;
1951 		int thickness = 70;
1952 		src.h = radius;
1953 		src.w = radius;
1954 		if ( yres <= 768 )
1955 		{
1956 			radius = 110;
1957 			thickness = 70;
1958 			src.h = 125;
1959 			src.w = 125;
1960 		}
1961 		int highlight = -1;
1962 		int i = 0;
1963 
1964 		int width = 0;
1965 		TTF_SizeUTF8(ttf12, language[3036], &width, nullptr);
1966 		if ( yres < 768 )
1967 		{
1968 			ttfPrintText(ttf12, src.x - width / 2, src.y - radius - thickness - 14, language[3036]);
1969 		}
1970 		else
1971 		{
1972 			ttfPrintText(ttf12, src.x - width / 2, src.y - radius - thickness - 24, language[3036]);
1973 		}
1974 
1975 		drawImageRing(fancyWindow_bmp, nullptr, radius, thickness, 40, 0, PI * 2, 156);
1976 
1977 		for ( i = 0; i < numoptions; ++i )
1978 		{
1979 			// draw borders around ring.
1980 			drawLine(xres / 2 + (radius - thickness) * cos(angleStart), yres / 2 - (radius - thickness) * sin(angleStart),
1981 				xres / 2 + (radius + thickness) * cos(angleStart), yres / 2 - (radius + thickness) * sin(angleStart), uint32ColorGray(*mainsurface), 192);
1982 			drawLine(xres / 2 + (radius - thickness) * cos(angleEnd), yres / 2 - (radius - thickness) * sin(angleEnd),
1983 				xres / 2 + (radius + thickness - 1) * cos(angleEnd), yres / 2 - (radius + thickness - 1) * sin(angleEnd), uint32ColorGray(*mainsurface), 192);
1984 
1985 			drawArcInvertedY(xres / 2, yres / 2, radius - thickness, std::round((angleStart * 180) / PI), ((angleEnd * 180) / PI), uint32ColorGray(*mainsurface), 192);
1986 			drawArcInvertedY(xres / 2, yres / 2, (radius + thickness), std::round((angleStart * 180) / PI), ((angleEnd * 180) / PI) + 1, uint32ColorGray(*mainsurface), 192);
1987 
1988 			angleStart += 2 * PI / numoptions;
1989 			angleMiddle = angleStart + PI / numoptions;
1990 			angleEnd = angleMiddle + PI / numoptions;
1991 		}
1992 
1993 		angleStart = PI / 2 - (PI / numoptions);
1994 		angleMiddle = angleStart + PI / numoptions;
1995 		angleEnd = angleMiddle + PI / numoptions;
1996 
1997 		bool mouseInCenterButton = sqrt(pow((omousex - menuX), 2) + pow((omousey - menuY), 2)) < (radius - thickness);
1998 
1999 		for ( i = 0; i < numoptions; ++i )
2000 		{
2001 			// see if mouse cursor is within an option.
2002 			if ( highlight == -1 )
2003 			{
2004 				if ( !mouseInCenterButton )
2005 				{
2006 					real_t x1 = menuX + (radius + thickness + 45) * cos(angleEnd);
2007 					real_t y1 = menuY - (radius + thickness + 45) * sin(angleEnd);
2008 					real_t x2 = menuX + 5 * cos(angleMiddle);
2009 					real_t y2 = menuY - 5 * sin(angleMiddle);
2010 					real_t x3 = menuX + (radius + thickness + 45) * cos(angleStart);
2011 					real_t y3 = menuY - (radius + thickness + 45) * sin(angleStart);
2012 					real_t a = ((y2 - y3)*(omousex - x3) + (x3 - x2)*(omousey - y3)) / ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3));
2013 					real_t b = ((y3 - y1)*(omousex - x3) + (x1 - x3)*(omousey - y3)) / ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3));
2014 					real_t c = 1 - a - b;
2015 					if ( (0 <= a && a <= 1) && (0 <= b && b <= 1) && (0 <= c && c <= 1) )
2016 					{
2017 						//barycentric calc for figuring if mouse point is within triangle.
2018 						highlight = i;
2019 						drawImageRing(fancyWindow_bmp, &src, radius, thickness, (numoptions) * 8, angleStart, angleEnd, 192);
2020 
2021 						// draw borders around highlighted item.
2022 						Uint32 borderColor = uint32ColorBaronyBlue(*mainsurface);
2023 						if ( optionDisabledForCreature(skillLVL, followerStats->type, i) != 0 )
2024 						{
2025 							borderColor = uint32ColorOrange(*mainsurface);
2026 						}
2027 						drawLine(xres / 2 + (radius - thickness) * cos(angleStart), yres / 2 - (radius - thickness) * sin(angleStart),
2028 							xres / 2 + (radius + thickness) * cos(angleStart), yres / 2 - (radius + thickness) * sin(angleStart), borderColor, 192);
2029 						drawLine(xres / 2 + (radius - thickness) * cos(angleEnd), yres / 2 - (radius - thickness) * sin(angleEnd),
2030 							xres / 2 + (radius + thickness - 1) * cos(angleEnd), yres / 2 - (radius + thickness - 1) * sin(angleEnd), borderColor, 192);
2031 
2032 						drawArcInvertedY(xres / 2, yres / 2, radius - thickness, std::round((angleStart * 180) / PI), ((angleEnd * 180) / PI), borderColor, 192);
2033 						drawArcInvertedY(xres / 2, yres / 2, (radius + thickness), std::round((angleStart * 180) / PI), ((angleEnd * 180) / PI) + 1, borderColor, 192);
2034 					}
2035 				}
2036 			}
2037 
2038 			SDL_Rect txt;
2039 			txt.x = src.x + src.w * cos(angleMiddle);
2040 			txt.y = src.y - src.h * sin(angleMiddle);
2041 			txt.w = 0;
2042 			txt.h = 0;
2043 			SDL_Rect img;
2044 			img.x = txt.x - sidebar_unlock_bmp->w / 2;
2045 			img.y = txt.y - sidebar_unlock_bmp->h / 2;
2046 			img.w = sidebar_unlock_bmp->w;
2047 			img.h = sidebar_unlock_bmp->h;
2048 
2049 			// draw the text for the menu wheel.
2050 
2051 			if ( optionDisabledForCreature(skillLVL, followerStats->type, i) != 0 )
2052 			{
2053 				drawImage(sidebar_unlock_bmp, nullptr, &img); // locked menu options
2054 			}
2055 			else if ( i == ALLY_CMD_DEFEND
2056 				&& (followerToCommand->monsterAllyState == ALLY_STATE_DEFEND || followerToCommand->monsterAllyState == ALLY_STATE_MOVETO) )
2057 			{
2058 				if ( followerStats->type == SENTRYBOT || followerStats->type == SPELLBOT )
2059 				{
2060 					TTF_SizeUTF8(ttf12, language[3675], &width, nullptr);
2061 					ttfPrintText(ttf12, txt.x - width / 2, txt.y - 4, language[3675]);
2062 				}
2063 				else
2064 				{
2065 					TTF_SizeUTF8(ttf12, language[3037 + i + 8], &width, nullptr);
2066 					ttfPrintText(ttf12, txt.x - width / 2, txt.y - 4, language[3037 + i + 8]);
2067 				}
2068 			}
2069 			else
2070 			{
2071 				TTF_SizeUTF8(ttf12, language[3037 + i], &width, nullptr);
2072 				if ( i == ALLY_CMD_DEFEND
2073 					&& followerToCommand->monsterAllyState == ALLY_STATE_DEFAULT
2074 					&& (followerStats->type == SENTRYBOT || followerStats->type == SPELLBOT) )
2075 				{
2076 					TTF_SizeUTF8(ttf12, language[3674], &width, nullptr);
2077 					ttfPrintText(ttf12, txt.x - width / 2, txt.y - 4, language[3674]);
2078 				}
2079 				else if ( i == ALLY_CMD_CLASS_TOGGLE )
2080 				{
2081 					if ( followerStats->type == GYROBOT )
2082 					{
2083 						// draw higher.
2084 						TTF_SizeUTF8(ttf12, language[3619], &width, nullptr);
2085 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 12, language[3619]);
2086 						TTF_SizeUTF8(ttf12, language[3620 + followerToCommand->monsterAllyClass], &width, nullptr);
2087 						ttfPrintText(ttf12, txt.x - width / 2, txt.y + 4, language[3620 + followerToCommand->monsterAllyClass]);
2088 					}
2089 					else if ( followerToCommand && followerToCommand->monsterAllySummonRank != 0 )
2090 					{
2091 						TTF_SizeUTF8(ttf12, "Relinquish ", &width, nullptr);
2092 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 12, language[3196]);
2093 					}
2094 					else
2095 					{
2096 						// draw higher.
2097 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 12, language[3037 + i]);
2098 						TTF_SizeUTF8(ttf12, language[3053 + followerToCommand->monsterAllyClass], &width, nullptr);
2099 						ttfPrintText(ttf12, txt.x - width / 2, txt.y + 4, language[3053 + followerToCommand->monsterAllyClass]);
2100 					}
2101 				}
2102 				else if ( i == ALLY_CMD_PICKUP_TOGGLE )
2103 				{
2104 					if ( followerStats->type == GYROBOT )
2105 					{
2106 						if ( followerToCommand->monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_METAL
2107 							|| followerToCommand->monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_MAGIC
2108 							|| followerToCommand->monsterAllyPickupItems == ALLY_GYRO_DETECT_ITEMS_VALUABLE )
2109 						{
2110 							TTF_SizeUTF8(ttf12, "Detect", &width, nullptr);
2111 							ttfPrintText(ttf12, txt.x - width / 2, txt.y - 24, language[3636]);
2112 							TTF_SizeUTF8(ttf12, language[3624 + followerToCommand->monsterAllyPickupItems], &width, nullptr);
2113 							ttfPrintText(ttf12, txt.x - width / 2, txt.y + 12, language[3624 + followerToCommand->monsterAllyPickupItems]);
2114 						}
2115 						else
2116 						{
2117 							TTF_SizeUTF8(ttf12, language[3623], &width, nullptr);
2118 							ttfPrintText(ttf12, txt.x - width / 2, txt.y - 12, language[3623]);
2119 							TTF_SizeUTF8(ttf12, language[3624 + followerToCommand->monsterAllyPickupItems], &width, nullptr);
2120 							ttfPrintText(ttf12, txt.x - width / 2, txt.y + 4, language[3624 + followerToCommand->monsterAllyPickupItems]);
2121 						}
2122 					}
2123 					else
2124 					{
2125 						// draw higher.
2126 						TTF_SizeUTF8(ttf12, "Pickup", &width, nullptr);
2127 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 24, language[3037 + i]);
2128 						TTF_SizeUTF8(ttf12, language[3056 + followerToCommand->monsterAllyPickupItems], &width, nullptr);
2129 						ttfPrintText(ttf12, txt.x - width / 2, txt.y + 12, language[3056 + followerToCommand->monsterAllyPickupItems]);
2130 					}
2131 				}
2132 				else if ( i == ALLY_CMD_DROP_EQUIP )
2133 				{
2134 					if ( followerStats->type == GYROBOT )
2135 					{
2136 						TTF_SizeUTF8(ttf12, language[3633], &width, nullptr);
2137 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 12, language[3633]);
2138 						TTF_SizeUTF8(ttf12, language[3634], &width, nullptr);
2139 						ttfPrintText(ttf12, txt.x - width / 2, txt.y + 4, language[3634]);
2140 					}
2141 					else
2142 					{
2143 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 12, language[3037 + i]);
2144 						if ( skillLVL >= SKILL_LEVEL_LEGENDARY )
2145 						{
2146 							TTF_SizeUTF8(ttf12, language[3061], &width, nullptr);
2147 							ttfPrintText(ttf12, txt.x - width / 2, txt.y + 4, language[3061]);
2148 						}
2149 						else if ( skillLVL >= SKILL_LEVEL_MASTER )
2150 						{
2151 							TTF_SizeUTF8(ttf12, language[3060], &width, nullptr);
2152 							ttfPrintText(ttf12, txt.x - width / 2, txt.y + 4, language[3060]);
2153 						}
2154 						else
2155 						{
2156 							TTF_SizeUTF8(ttf12, language[3059], &width, nullptr);
2157 							ttfPrintText(ttf12, txt.x - width / 2, txt.y + 4, language[3059]);
2158 						}
2159 					}
2160 				}
2161 				else if ( i == ALLY_CMD_SPECIAL )
2162 				{
2163 					if ( followerStats->type == GYROBOT )
2164 					{
2165 						TTF_SizeUTF8(ttf12, "Return &", &width, nullptr);
2166 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 12, language[3635]);
2167 					}
2168 					else if ( followerStats->type == DUMMYBOT )
2169 					{
2170 						TTF_SizeUTF8(ttf12, language[3641], &width, nullptr);
2171 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 12, language[3641]);
2172 						TTF_SizeUTF8(ttf12, language[3642], &width, nullptr);
2173 						ttfPrintText(ttf12, txt.x - width / 2, txt.y + 4, language[3642]);
2174 					}
2175 					else if ( followerStats->type == SENTRYBOT || followerStats->type == SPELLBOT )
2176 					{
2177 						TTF_SizeUTF8(ttf12, language[3649], &width, nullptr);
2178 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 4, language[3649]);
2179 					}
2180 					else
2181 					{
2182 						TTF_SizeUTF8(ttf12, language[3037 + i], &width, nullptr);
2183 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 4, language[3037 + i]);
2184 					}
2185 				}
2186 				else if ( i == ALLY_CMD_ATTACK_SELECT )
2187 				{
2188 					if ( !attackCommandOnly(followerStats->type) )
2189 					{
2190 						if ( optionDisabledForCreature(skillLVL, followerStats->type, ALLY_CMD_ATTACK_CONFIRM) == 0 )
2191 						{
2192 							TTF_SizeUTF8(ttf12, "Interact / ", &width, nullptr);
2193 							ttfPrintText(ttf12, txt.x - width / 2, txt.y - 12, language[3051]);
2194 						}
2195 						else
2196 						{
2197 							TTF_SizeUTF8(ttf12, language[3037 + i], &width, nullptr);
2198 							ttfPrintText(ttf12, txt.x - width / 2, txt.y + 4, language[3037 + i]);
2199 						}
2200 					}
2201 					else
2202 					{
2203 						TTF_SizeUTF8(ttf12, language[3104], &width, nullptr); // print just attack if no world interaction.
2204 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 4, language[3104]);
2205 					}
2206 				}
2207 				else if ( i == ALLY_CMD_MOVETO_SELECT )
2208 				{
2209 					if ( followerStats->type == SENTRYBOT || followerStats->type == SPELLBOT )
2210 					{
2211 						TTF_SizeUTF8(ttf12, language[3650], &width, nullptr);
2212 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 4, language[3650]);
2213 					}
2214 					else
2215 					{
2216 						TTF_SizeUTF8(ttf12, language[3037 + i], &width, nullptr);
2217 						ttfPrintText(ttf12, txt.x - width / 2, txt.y - 4, language[3037 + i]);
2218 					}
2219 				}
2220 				else
2221 				{
2222 					TTF_SizeUTF8(ttf12, language[3037 + i], &width, nullptr);
2223 					ttfPrintText(ttf12, txt.x - width / 2, txt.y - 4, language[3037 + i]);
2224 				}
2225 			}
2226 
2227 			angleStart += 2 * PI / numoptions;
2228 			angleMiddle = angleStart + PI / numoptions;
2229 			angleEnd = angleMiddle + PI / numoptions;
2230 		}
2231 		// draw center text.
2232 		if ( mouseInCenterButton )
2233 		{
2234 			highlight = -1;
2235 			//drawImageRing(fancyWindow_bmp, nullptr, 35, 35, 40, 0, 2 * PI, 192);
2236 			drawCircle(xres / 2, yres / 2, radius - thickness, uint32ColorBaronyBlue(*mainsurface), 192);
2237 			//TTF_SizeUTF8(ttf12, language[3063], &width, nullptr);
2238 			//ttfPrintText(ttf12, xres / 2 - width / 2, yres / 2 - 8, language[3063]);
2239 		}
2240 
2241 		if ( optionSelected == -1 && disableOption == 0 && highlight != -1 )
2242 		{
2243 			// in case optionSelected is cleared, but we're still highlighting text (happens on next frame when clicking on disabled option.)
2244 			if ( highlight == ALLY_CMD_ATTACK_SELECT )
2245 			{
2246 				if ( attackCommandOnly(followerStats->type) )
2247 				{
2248 					// attack only.
2249 					disableOption = FollowerMenu.optionDisabledForCreature(skillLVL, followerStats->type, ALLY_CMD_ATTACK_CONFIRM);
2250 				}
2251 				else
2252 				{
2253 					disableOption = FollowerMenu.optionDisabledForCreature(skillLVL, followerStats->type, highlight);
2254 				}
2255 			}
2256 			else
2257 			{
2258 				disableOption = FollowerMenu.optionDisabledForCreature(skillLVL, followerStats->type, highlight);
2259 			}
2260 		}
2261 
2262 		if ( disableOption != 0 )
2263 		{
2264 			SDL_Rect tooltip;
2265 			tooltip.x = omousex + 16;
2266 			tooltip.y = omousey + 16;
2267 			char* lowSkillLVLTooltip = language[3062];
2268 			if ( tinkeringFollower )
2269 			{
2270 				lowSkillLVLTooltip = language[3672];
2271 			}
2272 			tooltip.w = longestline(lowSkillLVLTooltip) * TTF12_WIDTH + 8;
2273 			tooltip.h = TTF12_HEIGHT * 2 + 8;
2274 
2275 			if ( disableOption == -2 ) // disabled due to cooldown
2276 			{
2277 				tooltip.h = TTF12_HEIGHT + 8;
2278 				tooltip.w = longestline(language[3092]) * TTF12_WIDTH + 8;
2279 				drawTooltip(&tooltip);
2280 				ttfPrintTextFormattedColor(ttf12, tooltip.x + 4, tooltip.y + 6, uint32ColorOrange(*mainsurface), language[3092]);
2281 			}
2282 			else if ( disableOption == -1 ) // disabled due to creature type
2283 			{
2284 				tooltip.h = TTF12_HEIGHT + 8;
2285 				tooltip.w = longestline(language[3103]) * TTF12_WIDTH + 8;
2286 				if ( followerStats->type < KOBOLD ) //Original monster count
2287 				{
2288 					tooltip.w += strlen(language[90 + followerStats->type]) * TTF12_WIDTH;
2289 					drawTooltip(&tooltip);
2290 					ttfPrintTextFormattedColor(ttf12, tooltip.x + 4, tooltip.y + 6,
2291 						uint32ColorOrange(*mainsurface), language[3103], language[90 + followerStats->type]);
2292 				}
2293 				else if ( followerStats->type >= KOBOLD ) //New monsters
2294 				{
2295 					tooltip.w += strlen(language[2000 + followerStats->type - KOBOLD]) * TTF12_WIDTH;
2296 					drawTooltip(&tooltip);
2297 					ttfPrintTextFormattedColor(ttf12, tooltip.x + 4, tooltip.y + 6,
2298 						uint32ColorOrange(*mainsurface), language[3103], language[2000 + followerStats->type - KOBOLD]);
2299 				}
2300 			}
2301 			else if ( disableOption == -3 ) // disabled due to tinkerbot quality
2302 			{
2303 				tooltip.h = TTF12_HEIGHT + 8;
2304 				tooltip.w = longestline(language[3673]) * TTF12_WIDTH + 8;
2305 				drawTooltip(&tooltip);
2306 				if ( followerStats->type >= KOBOLD ) //New monsters
2307 				{
2308 					tooltip.w += strlen(language[2000 + followerStats->type - KOBOLD]) * TTF12_WIDTH;
2309 					drawTooltip(&tooltip);
2310 					ttfPrintTextFormattedColor(ttf12, tooltip.x + 4, tooltip.y + 6,
2311 						uint32ColorOrange(*mainsurface), language[3673], language[2000 + followerStats->type - KOBOLD]);
2312 				}
2313 			}
2314 			else
2315 			{
2316 				drawTooltip(&tooltip);
2317 				std::string requirement = "";
2318 				std::string current = "";
2319 				if ( highlight >= ALLY_CMD_DEFEND && highlight <= ALLY_CMD_END && highlight != ALLY_CMD_CANCEL )
2320 				{
2321 					switch ( std::min(disableOption, SKILL_LEVEL_LEGENDARY) )
2322 					{
2323 						case 0:
2324 							requirement = language[363];
2325 							break;
2326 						case SKILL_LEVEL_NOVICE:
2327 							requirement = language[364];
2328 							break;
2329 						case SKILL_LEVEL_BASIC:
2330 							requirement = language[365];
2331 							break;
2332 						case SKILL_LEVEL_SKILLED:
2333 							requirement = language[366];
2334 							break;
2335 						case SKILL_LEVEL_EXPERT:
2336 							requirement = language[367];
2337 							break;
2338 						case SKILL_LEVEL_MASTER:
2339 							requirement = language[368];
2340 							break;
2341 						case SKILL_LEVEL_LEGENDARY:
2342 							requirement = language[369];
2343 							break;
2344 						default:
2345 							break;
2346 					}
2347 					requirement.erase(std::remove(requirement.begin(), requirement.end(), ' '), requirement.end()); // trim whitespace
2348 
2349 					if ( skillLVL >= SKILL_LEVEL_LEGENDARY )
2350 					{
2351 						current = language[369];
2352 					}
2353 					else if ( skillLVL >= SKILL_LEVEL_MASTER )
2354 					{
2355 						current = language[368];
2356 					}
2357 					else if ( skillLVL >= SKILL_LEVEL_EXPERT )
2358 					{
2359 						current = language[367];
2360 					}
2361 					else if ( skillLVL >= SKILL_LEVEL_SKILLED )
2362 					{
2363 						current = language[366];
2364 					}
2365 					else if ( skillLVL >= SKILL_LEVEL_BASIC )
2366 					{
2367 						current = language[365];
2368 					}
2369 					else if ( skillLVL >= SKILL_LEVEL_NOVICE )
2370 					{
2371 						current = language[364];
2372 					}
2373 					else
2374 					{
2375 						current = language[363];
2376 					}
2377 					current.erase(std::remove(current.begin(), current.end(), ' '), current.end()); // trim whitespace
2378 				}
2379 				ttfPrintTextFormattedColor(ttf12, tooltip.x + 4, tooltip.y + 6,
2380 					uint32ColorOrange(*mainsurface), lowSkillLVLTooltip, requirement.c_str(), current.c_str());
2381 			}
2382 		}
2383 
2384 		if ( !keepWheelOpen )
2385 		{
2386 			optionSelected = highlight; // don't reselect if we're keeping the wheel open by using a toggle option.
2387 		}
2388 	}
2389 }
2390 
numMonstersToDrawInParty()2391 int FollowerRadialMenu::numMonstersToDrawInParty()
2392 {
2393 	int players = 0;
2394 	for ( int c = 0; c < MAXPLAYERS; ++c )
2395 	{
2396 		if ( !client_disconnected[c] )
2397 		{
2398 			++players;
2399 		}
2400 	}
2401 	return std::max(2, (maxMonstersToDraw - std::max(0, players - 1) * 2));
2402 }
2403 
selectNextFollower()2404 void FollowerRadialMenu::selectNextFollower()
2405 {
2406 	if ( !stats[clientnum] )
2407 	{
2408 		return;
2409 	}
2410 
2411 	int numFollowers = list_Size(&stats[clientnum]->FOLLOWERS);
2412 
2413 	if ( numFollowers <= 0 )
2414 	{
2415 		return;
2416 	}
2417 
2418 	if ( !recentEntity ) // set first follower to be the selected one.
2419 	{
2420 		node_t* node = stats[clientnum]->FOLLOWERS.first;
2421 		if ( node )
2422 		{
2423 			Entity* follower = uidToEntity(*((Uint32*)node->element));
2424 			if ( follower )
2425 			{
2426 				recentEntity = follower;
2427 				FollowerMenu.sidebarScrollIndex = 0;
2428 				return;
2429 			}
2430 		}
2431 	}
2432 	else if ( numFollowers == 1 )
2433 	{
2434 		// only 1 follower, no work to do.
2435 		FollowerMenu.sidebarScrollIndex = 0;
2436 		return;
2437 	}
2438 
2439 	int monstersToDraw = numMonstersToDrawInParty();
2440 
2441 	node_t* node2 = nullptr;
2442 	int i = 0;
2443 	for ( node_t* node = stats[clientnum]->FOLLOWERS.first; node != nullptr; node = node->next, ++i)
2444 	{
2445 		Entity* follower = nullptr;
2446 		if ( (Uint32*)node->element )
2447 		{
2448 			follower = uidToEntity(*((Uint32*)node->element));
2449 		}
2450 		if ( follower && follower == recentEntity )
2451 		{
2452 			if ( node->next != nullptr )
2453 			{
2454 				follower = uidToEntity(*((Uint32*)(node->next)->element));
2455 				if ( follower )
2456 				{
2457 					recentEntity = follower;
2458 					if ( followerToCommand )
2459 					{
2460 						followerToCommand = follower; // if we had the menu open, we're now controlling the new selected follower.
2461 					}
2462 					if ( numFollowers > monstersToDraw )
2463 					{
2464 						if ( monstersToDraw > 1 )
2465 						{
2466 							if ( i < sidebarScrollIndex || i >= sidebarScrollIndex + monstersToDraw )
2467 							{
2468 								sidebarScrollIndex = std::min(i, numFollowers - monstersToDraw - 1);
2469 							}
2470 							if ( i != 0 )
2471 							{
2472 								sidebarScrollIndex = std::min(sidebarScrollIndex + 1, numFollowers - monstersToDraw - 1);
2473 							}
2474 						}
2475 					}
2476 				}
2477 			}
2478 			else
2479 			{
2480 				node2 = stats[clientnum]->FOLLOWERS.first; // loop around to first index.
2481 				follower = nullptr;
2482 				if ( (Uint32*)node2->element )
2483 				{
2484 					follower = uidToEntity(*((Uint32*)node2->element));
2485 				}
2486 				if ( follower )
2487 				{
2488 					recentEntity = follower;
2489 					if ( followerToCommand )
2490 					{
2491 						followerToCommand = follower; // if we had the menu open, we're now controlling the new selected follower.
2492 					}
2493 					sidebarScrollIndex = 0;
2494 				}
2495 			}
2496 			if ( recentEntity )
2497 			{
2498 				createParticleFollowerCommand(recentEntity->x, recentEntity->y, 0, 174);
2499 				playSound(139, 64);
2500 			}
2501 			return;
2502 		}
2503 	}
2504 }
2505 
updateScrollPartySheet()2506 void FollowerRadialMenu::updateScrollPartySheet()
2507 {
2508 	if ( !stats[clientnum] )
2509 	{
2510 		return;
2511 	}
2512 
2513 	int numFollowers = list_Size(&stats[clientnum]->FOLLOWERS);
2514 
2515 	if ( numFollowers <= 0 )
2516 	{
2517 		return;
2518 	}
2519 
2520 	int monstersToDraw = numMonstersToDrawInParty();
2521 
2522 	if ( !recentEntity ) // set first follower to be the selected one.
2523 	{
2524 		return;
2525 	}
2526 	else if ( numFollowers == 1 || numFollowers <= monstersToDraw )
2527 	{
2528 		// only 1 follower or limit not reached, no work to do.
2529 		FollowerMenu.sidebarScrollIndex = 0;
2530 		return;
2531 	}
2532 
2533 	int i = 0;
2534 
2535 	for ( node_t* node = stats[clientnum]->FOLLOWERS.first; node != nullptr; node = node->next, ++i )
2536 	{
2537 		Entity* follower = nullptr;
2538 		if ( (Uint32*)node->element )
2539 		{
2540 			follower = uidToEntity(*((Uint32*)node->element));
2541 		}
2542 		if ( follower && follower == recentEntity )
2543 		{
2544 			if ( monstersToDraw > 1 )
2545 			{
2546 				if ( i < sidebarScrollIndex || i >= sidebarScrollIndex + monstersToDraw )
2547 				{
2548 					sidebarScrollIndex = std::min(i, numFollowers - monstersToDraw - 1);
2549 				}
2550 			}
2551 			break;
2552 		}
2553 	}
2554 }
2555 
isTinkeringFollower(int type)2556 bool FollowerRadialMenu::isTinkeringFollower(int type)
2557 {
2558 	if ( type == GYROBOT || type == SENTRYBOT
2559 		|| type == SPELLBOT || type == DUMMYBOT )
2560 	{
2561 		return true;
2562 	}
2563 	return false;
2564 }
2565 
allowedInteractEntity(Entity & selectedEntity)2566 bool FollowerRadialMenu::allowedInteractEntity(Entity& selectedEntity)
2567 {
2568 	if ( optionSelected != ALLY_CMD_ATTACK_SELECT )
2569 	{
2570 		return false;
2571 	}
2572 
2573 	if ( !followerToCommand )
2574 	{
2575 		return false;
2576 	}
2577 
2578 	if ( followerToCommand == &selectedEntity )
2579 	{
2580 		return false;
2581 	}
2582 
2583 	Stat* followerStats = followerToCommand->getStats();
2584 	if ( !followerStats )
2585 	{
2586 		return false;
2587 	}
2588 	if ( !players[clientnum] || !players[clientnum]->entity )
2589 	{
2590 		return false;
2591 	}
2592 
2593 	bool interactItems = allowedInteractItems(followerStats->type) || allowedInteractFood(followerStats->type);
2594 	bool interactWorld = allowedInteractWorld(followerStats->type);
2595 	bool tinkeringFollower = isTinkeringFollower(followerStats->type);
2596 	int skillLVL = stats[clientnum]->PROFICIENCIES[PRO_LEADERSHIP] + statGetCHR(stats[clientnum], players[clientnum]->entity);
2597 	if ( tinkeringFollower )
2598 	{
2599 		skillLVL = stats[clientnum]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[clientnum], players[clientnum]->entity);
2600 	}
2601 	if ( followerToCommand->monsterAllySummonRank != 0 )
2602 	{
2603 		skillLVL = SKILL_LEVEL_LEGENDARY;
2604 	}
2605 
2606 	bool enableAttack = (optionDisabledForCreature(skillLVL, followerStats->type, ALLY_CMD_ATTACK_CONFIRM) == 0);
2607 
2608 	if ( !interactItems && !interactWorld && enableAttack )
2609 	{
2610 		strcpy(FollowerMenu.interactText, "Attack ");
2611 	}
2612 	else
2613 	{
2614 		strcpy(FollowerMenu.interactText, "Interact with ");
2615 	}
2616 	if ( selectedEntity.behavior == &actTorch && interactWorld )
2617 	{
2618 		strcat(FollowerMenu.interactText, items[TOOL_TORCH].name_identified);
2619 	}
2620 	else if ( (selectedEntity.behavior == &actSwitch || selectedEntity.sprite == 184) && interactWorld )
2621 	{
2622 		strcat(FollowerMenu.interactText, "switch");
2623 	}
2624 	else if ( selectedEntity.behavior == &actBomb && interactWorld && followerStats->type == GYROBOT )
2625 	{
2626 		strcpy(FollowerMenu.interactText, language[3093]);
2627 		strcat(FollowerMenu.interactText, "trap");
2628 	}
2629 	else if ( selectedEntity.behavior == &actItem && interactItems )
2630 	{
2631 		if ( multiplayer != CLIENT )
2632 		{
2633 			if ( selectedEntity.skill[15] == 0 )
2634 			{
2635 				strcat(FollowerMenu.interactText, items[selectedEntity.skill[10]].name_unidentified);
2636 			}
2637 			else
2638 			{
2639 				strcat(FollowerMenu.interactText, items[selectedEntity.skill[10]].name_identified);
2640 			}
2641 		}
2642 		else
2643 		{
2644 			strcat(FollowerMenu.interactText, "item");
2645 		}
2646 	}
2647 	else if ( selectedEntity.behavior == &actMonster && enableAttack )
2648 	{
2649 		strcpy(FollowerMenu.interactText, "Attack ");
2650 		int monsterType = selectedEntity.getMonsterTypeFromSprite();
2651 		if ( monsterType < KOBOLD ) //Original monster count
2652 		{
2653 			strcat(FollowerMenu.interactText, language[90 + monsterType]);
2654 		}
2655 		else if ( monsterType >= KOBOLD ) //New monsters
2656 		{
2657 			strcat(FollowerMenu.interactText, language[2000 + monsterType - KOBOLD]);
2658 		}
2659 	}
2660 	else
2661 	{
2662 		strcpy(FollowerMenu.interactText, "No interactions available");
2663 		return false;
2664 	}
2665 	return true;
2666 }
2667 
optionDisabledForCreature(int playerSkillLVL,int monsterType,int option)2668 int FollowerRadialMenu::optionDisabledForCreature(int playerSkillLVL, int monsterType, int option)
2669 {
2670 	int creatureTier = 0;
2671 
2672 	switch ( monsterType )
2673 	{
2674 		case HUMAN:
2675 		case RAT:
2676 		case SLIME:
2677 		case SPIDER:
2678 		case SKELETON:
2679 		case SCORPION:
2680 		case GYROBOT:
2681 		case SENTRYBOT:
2682 		case SPELLBOT:
2683 			creatureTier = 0;
2684 			break;
2685 		case GOBLIN:
2686 		case TROLL:
2687 		case GHOUL:
2688 		case GNOME:
2689 		case SCARAB:
2690 		case AUTOMATON:
2691 		case SUCCUBUS:
2692 			creatureTier = 1;
2693 			break;
2694 		case CREATURE_IMP:
2695 		case DEMON:
2696 		case KOBOLD:
2697 		case INCUBUS:
2698 		case INSECTOID:
2699 		case GOATMAN:
2700 			creatureTier = 2;
2701 			break;
2702 		case CRYSTALGOLEM:
2703 		case VAMPIRE:
2704 		case COCKATRICE:
2705 		case SHADOW:
2706 			creatureTier = 3;
2707 			break;
2708 		default:
2709 			break;
2710 	}
2711 
2712 	Stat* followerStats = nullptr;
2713 	if ( followerToCommand )
2714 	{
2715 		followerStats = followerToCommand->getStats();
2716 	}
2717 
2718 	int requirement = AllyNPCSkillRequirements[option];
2719 
2720 	if ( monsterType == GYROBOT )
2721 	{
2722 		monsterGyroBotConvertCommand(&option);
2723 		if ( followerStats )
2724 		{
2725 			if ( option == ALLY_CMD_GYRO_DETECT_TOGGLE )
2726 			{
2727 				if ( playerSkillLVL < SKILL_LEVEL_BASIC )
2728 				{
2729 					return SKILL_LEVEL_BASIC;
2730 				}
2731 				else if ( followerStats->LVL < 5 )
2732 				{
2733 					return -3;
2734 				}
2735 				else
2736 				{
2737 					return 0;
2738 				}
2739 			}
2740 			else if ( option == ALLY_CMD_GYRO_LIGHT_TOGGLE )
2741 			{
2742 				if ( playerSkillLVL < SKILL_LEVEL_BASIC )
2743 				{
2744 					return SKILL_LEVEL_BASIC;
2745 				}
2746 				if ( followerStats->LVL < 5 )
2747 				{
2748 					return -3;
2749 				}
2750 			}
2751 			else if ( option == ALLY_CMD_MOVETO_CONFIRM || option == ALLY_CMD_MOVETO_SELECT )
2752 			{
2753 				if ( playerSkillLVL < SKILL_LEVEL_BASIC )
2754 				{
2755 					return SKILL_LEVEL_BASIC;
2756 				}
2757 				if ( followerStats->LVL < 5 )
2758 				{
2759 					return -3;
2760 				}
2761 			}
2762 			else if ( option == ALLY_CMD_ATTACK_SELECT || option == ALLY_CMD_ATTACK_CONFIRM
2763 				|| option == ALLY_CMD_DROP_EQUIP )
2764 			{
2765 				if ( playerSkillLVL < SKILL_LEVEL_SKILLED )
2766 				{
2767 					return SKILL_LEVEL_SKILLED;
2768 				}
2769 				if ( followerStats->LVL < 10 )
2770 				{
2771 					return -3;
2772 				}
2773 			}
2774 		}
2775 
2776 	}
2777 	else
2778 	{
2779 		if ( monsterGyroBotOnlyCommand(option) )
2780 		{
2781 			return -1; // disabled due to monster.
2782 		}
2783 	}
2784 
2785 	if ( monsterType == DUMMYBOT )
2786 	{
2787 		if ( option != ALLY_CMD_SPECIAL && option != ALLY_CMD_DUMMYBOT_RETURN )
2788 		{
2789 			return -1; // disabled due to monster.
2790 		}
2791 		else
2792 		{
2793 			option = ALLY_CMD_DUMMYBOT_RETURN;
2794 		}
2795 	}
2796 	else if ( monsterType == SENTRYBOT || monsterType == SPELLBOT )
2797 	{
2798 		if ( option != ALLY_CMD_SPECIAL && option != ALLY_CMD_DUMMYBOT_RETURN )
2799 		{
2800 			if ( option != ALLY_CMD_MOVETO_CONFIRM && option != ALLY_CMD_MOVETO_SELECT
2801 				&&  option != ALLY_CMD_ATTACK_SELECT && option != ALLY_CMD_ATTACK_CONFIRM
2802 				&&  option != ALLY_CMD_CANCEL && option != ALLY_CMD_DEFEND && option != ALLY_CMD_FOLLOW )
2803 			{
2804 				return -1; // disabled due to monster.
2805 			}
2806 			else
2807 			{
2808 				if ( followerStats && (option != ALLY_CMD_CANCEL && option != ALLY_CMD_DEFEND && option != ALLY_CMD_FOLLOW ) )
2809 				{
2810 					if ( followerStats->LVL < 5 )
2811 					{
2812 						return -3; // disable all due to LVL
2813 					}
2814 					else if ( followerStats->LVL < 10 )
2815 					{
2816 						if ( option == ALLY_CMD_ATTACK_SELECT || option == ALLY_CMD_ATTACK_CONFIRM )
2817 						{
2818 							return -3; // disabled attack commands due to LVL
2819 						}
2820 					}
2821 				}
2822 			}
2823 		}
2824 		else
2825 		{
2826 			option = ALLY_CMD_DUMMYBOT_RETURN;
2827 		}
2828 	}
2829 
2830 	if ( option == ALLY_CMD_SPECIAL
2831 		&& followerToCommand->monsterAllySpecialCooldown != 0 )
2832 	{
2833 		return -2; // disabled due to cooldown.
2834 	}
2835 
2836 	switch ( option )
2837 	{
2838 		case ALLY_CMD_MOVEASIDE:
2839 			if ( monsterType == SENTRYBOT || monsterType == SPELLBOT )
2840 			{
2841 				return -1; // can't use.
2842 			}
2843 		case ALLY_CMD_CANCEL:
2844 			if ( playerSkillLVL < requirement )
2845 			{
2846 				return requirement; // disabled due to basic skill requirements.
2847 			}
2848 			return 0; // all permitted.
2849 			break;
2850 
2851 		case ALLY_CMD_FOLLOW:
2852 		case ALLY_CMD_DEFEND:
2853 			if ( monsterType == SENTRYBOT || monsterType == SPELLBOT )
2854 			{
2855 				return 0;
2856 			}
2857 			if ( creatureTier > 0 )
2858 			{
2859 				requirement = 20 * creatureTier; // 20, 40, 60.
2860 			}
2861 			if ( playerSkillLVL < requirement )
2862 			{
2863 				return requirement; // disabled due to advanced skill requirements.
2864 			}
2865 			return 0;
2866 			break;
2867 
2868 		case ALLY_CMD_MOVETO_SELECT:
2869 		case ALLY_CMD_MOVETO_CONFIRM:
2870 			if ( creatureTier > 0 )
2871 			{
2872 				requirement += 20 * creatureTier; // 40, 60, 80.
2873 			}
2874 			if ( playerSkillLVL < requirement )
2875 			{
2876 				return requirement; // disabled due to advanced skill requirements.
2877 			}
2878 			return 0;
2879 			break;
2880 
2881 		case ALLY_CMD_DROP_EQUIP:
2882 			if ( followerToCommand && followerToCommand->monsterAllySummonRank != 0 )
2883 			{
2884 				return -1;
2885 			}
2886 			if ( !allowedInteractItems(monsterType) )
2887 			{
2888 				return -1; // disabled due to creature.
2889 			}
2890 			else if ( monsterType == GYROBOT )
2891 			{
2892 				requirement = SKILL_LEVEL_SKILLED;
2893 			}
2894 			else if ( creatureTier > 0 )
2895 			{
2896 				requirement += 20 * creatureTier; // 60, 80, 100
2897 			}
2898 			if ( playerSkillLVL < requirement )
2899 			{
2900 				return requirement; // disabled due to advanced skill requirements.
2901 			}
2902 			return 0;
2903 			break;
2904 
2905 		case ALLY_CMD_ATTACK_SELECT:
2906 			if ( attackCommandOnly(monsterType) )
2907 			{
2908 				// attack only.
2909 				if ( creatureTier == 3 && playerSkillLVL < SKILL_LEVEL_LEGENDARY )
2910 				{
2911 					return SKILL_LEVEL_LEGENDARY; // disabled due to advanced skill requirements.
2912 				}
2913 				else if ( creatureTier == 2 && playerSkillLVL < SKILL_LEVEL_MASTER )
2914 				{
2915 					return SKILL_LEVEL_MASTER; // disabled due to advanced skill requirements.
2916 				}
2917 				else if ( playerSkillLVL < AllyNPCSkillRequirements[ALLY_CMD_ATTACK_CONFIRM] )
2918 				{
2919 					return AllyNPCSkillRequirements[ALLY_CMD_ATTACK_CONFIRM];
2920 				}
2921 				return 0;
2922 			}
2923 			else
2924 			{
2925 				if ( monsterType == GYROBOT )
2926 				{
2927 					if ( playerSkillLVL < SKILL_LEVEL_SKILLED )
2928 					{
2929 						return SKILL_LEVEL_SKILLED;
2930 					}
2931 				}
2932 				if ( playerSkillLVL < AllyNPCSkillRequirements[ALLY_CMD_ATTACK_SELECT] )
2933 				{
2934 					return AllyNPCSkillRequirements[ALLY_CMD_ATTACK_SELECT];
2935 				}
2936 				return 0;
2937 			}
2938 			break;
2939 
2940 		case ALLY_CMD_ATTACK_CONFIRM:
2941 			if ( monsterType == GYROBOT )
2942 			{
2943 				return -1; // disabled due to creature.
2944 				break;
2945 			}
2946 			else if ( creatureTier == 3 && playerSkillLVL < SKILL_LEVEL_LEGENDARY )
2947 			{
2948 				return SKILL_LEVEL_LEGENDARY; // disabled due to advanced skill requirements.
2949 			}
2950 			else if ( creatureTier == 2 && playerSkillLVL < SKILL_LEVEL_MASTER )
2951 			{
2952 				return SKILL_LEVEL_MASTER; // disabled due to advanced skill requirements.
2953 			}
2954 			else if ( playerSkillLVL < AllyNPCSkillRequirements[ALLY_CMD_ATTACK_CONFIRM] )
2955 			{
2956 				return AllyNPCSkillRequirements[ALLY_CMD_ATTACK_CONFIRM];
2957 			}
2958 			return 0;
2959 			break;
2960 
2961 		case ALLY_CMD_CLASS_TOGGLE:
2962 			if ( followerToCommand && followerToCommand->monsterAllySummonRank != 0 )
2963 			{
2964 				return 0;
2965 			}
2966 			if ( monsterType == GYROBOT )
2967 			{
2968 				return 0;
2969 			}
2970 			if ( !allowedClassToggle(monsterType) )
2971 			{
2972 				return -1; // disabled due to creature.
2973 			}
2974 			if ( playerSkillLVL < requirement )
2975 			{
2976 				return requirement; // disabled due to basic skill requirements.
2977 			}
2978 			return 0;
2979 			break;
2980 
2981 		case ALLY_CMD_PICKUP_TOGGLE:
2982 			if ( !allowedItemPickupToggle(monsterType) )
2983 			{
2984 				return -1; // disabled due to creature.
2985 			}
2986 			if ( monsterType == GYROBOT )
2987 			{
2988 				return 0;
2989 			}
2990 			if ( playerSkillLVL < requirement )
2991 			{
2992 				return requirement; // disabled due to basic skill requirements.
2993 			}
2994 			return 0;
2995 			break;
2996 
2997 		case ALLY_CMD_SPECIAL:
2998 			if ( creatureTier == 3 )
2999 			{
3000 				return -1; // disabled due to creature.
3001 			}
3002 			if ( playerSkillLVL < requirement )
3003 			{
3004 				return requirement; // disabled due to basic skill requirements.
3005 			}
3006 			break;
3007 		case ALLY_CMD_DUMMYBOT_RETURN:
3008 			if ( monsterType != DUMMYBOT && monsterType != SENTRYBOT && monsterType != SPELLBOT )
3009 			{
3010 				return -1;
3011 			}
3012 			else
3013 			{
3014 				return 0;
3015 			}
3016 			break;
3017 		case ALLY_CMD_GYRO_DETECT_TOGGLE:
3018 			if ( playerSkillLVL < requirement )
3019 			{
3020 				return requirement; // disabled due to basic skill requirements.
3021 			}
3022 			break;
3023 		case ALLY_CMD_GYRO_LIGHT_TOGGLE:
3024 			if ( playerSkillLVL < requirement )
3025 			{
3026 				return requirement; // disabled due to basic skill requirements.
3027 			}
3028 			break;
3029 		case ALLY_CMD_GYRO_RETURN:
3030 			if ( monsterType == GYROBOT )
3031 			{
3032 				return 0;
3033 			}
3034 			else
3035 			{
3036 				return -1;
3037 			}
3038 			break;
3039 		default:
3040 			if ( playerSkillLVL < requirement )
3041 			{
3042 				return requirement; // disabled due to basic skill requirements.
3043 			}
3044 			break;
3045 	}
3046 	return 0;
3047 }
3048 
allowedClassToggle(int monsterType)3049 bool FollowerRadialMenu::allowedClassToggle(int monsterType)
3050 {
3051 	switch ( monsterType )
3052 	{
3053 		case HUMAN:
3054 		case GOBLIN:
3055 		case AUTOMATON:
3056 		case GOATMAN:
3057 			return true;
3058 			break;
3059 		default:
3060 			break;
3061 	}
3062 	return false;
3063 }
3064 
allowedItemPickupToggle(int monsterType)3065 bool FollowerRadialMenu::allowedItemPickupToggle(int monsterType)
3066 {
3067 	switch ( monsterType )
3068 	{
3069 		case HUMAN:
3070 		case GOBLIN:
3071 		case AUTOMATON:
3072 		case GOATMAN:
3073 			return true;
3074 			break;
3075 		default:
3076 			break;
3077 	}
3078 	return false;
3079 }
3080 
allowedInteractFood(int monsterType)3081 bool FollowerRadialMenu::allowedInteractFood(int monsterType)
3082 {
3083 	switch ( monsterType )
3084 	{
3085 		case HUMAN:
3086 		case GOBLIN:
3087 		case GNOME:
3088 		case KOBOLD:
3089 		case GOATMAN:
3090 		case SLIME:
3091 		case INSECTOID:
3092 		case SPIDER:
3093 		case SCORPION:
3094 		case RAT:
3095 		case TROLL:
3096 		case COCKATRICE:
3097 		case SCARAB:
3098 			return true;
3099 			break;
3100 		default:
3101 			break;
3102 	}
3103 	return false;
3104 }
3105 
allowedInteractWorld(int monsterType)3106 bool FollowerRadialMenu::allowedInteractWorld(int monsterType)
3107 {
3108 	switch ( monsterType )
3109 	{
3110 		case HUMAN:
3111 		case GOBLIN:
3112 		case AUTOMATON:
3113 		case GNOME:
3114 		case KOBOLD:
3115 		case GOATMAN:
3116 		case SKELETON:
3117 		case GYROBOT:
3118 			return true;
3119 			break;
3120 		default:
3121 			break;
3122 	}
3123 	return false;
3124 }
3125 
allowedInteractItems(int monsterType)3126 bool FollowerRadialMenu::allowedInteractItems(int monsterType)
3127 {
3128 	switch ( monsterType )
3129 	{
3130 		case HUMAN:
3131 		case GOBLIN:
3132 		case AUTOMATON:
3133 		case GNOME:
3134 		case KOBOLD:
3135 		case GOATMAN:
3136 		case INCUBUS:
3137 		case INSECTOID:
3138 		case SKELETON:
3139 		case VAMPIRE:
3140 		case SLIME:
3141 		case GYROBOT:
3142 			if ( followerToCommand && followerToCommand->monsterAllySummonRank != 0 )
3143 			{
3144 				return false;
3145 			}
3146 			return true;
3147 			break;
3148 		default:
3149 			break;
3150 	}
3151 	return false;
3152 }
3153 
attackCommandOnly(int monsterType)3154 bool FollowerRadialMenu::attackCommandOnly(int monsterType)
3155 {
3156 	return !(allowedInteractItems(monsterType) || allowedInteractWorld(monsterType) || allowedInteractFood(monsterType));
3157 }
3158 
monsterGyroBotConvertCommand(int * option)3159 void FollowerRadialMenu::monsterGyroBotConvertCommand(int* option)
3160 {
3161 	if ( !option )
3162 	{
3163 		return;
3164 	}
3165 	switch ( *option )
3166 	{
3167 		case ALLY_CMD_PICKUP_TOGGLE:
3168 			*option = ALLY_CMD_GYRO_DETECT_TOGGLE;
3169 			break;
3170 		case ALLY_CMD_SPECIAL:
3171 		case ALLY_CMD_RETURN_SOUL:
3172 			*option = ALLY_CMD_GYRO_RETURN;
3173 			break;
3174 		case ALLY_CMD_CLASS_TOGGLE:
3175 			*option = ALLY_CMD_GYRO_LIGHT_TOGGLE;
3176 			break;
3177 		default:
3178 			break;
3179 	}
3180 }
3181 
3182 
monsterGyroBotOnlyCommand(int option)3183 bool FollowerRadialMenu::monsterGyroBotOnlyCommand(int option)
3184 {
3185 	switch ( option )
3186 	{
3187 		case ALLY_CMD_GYRO_DEPLOY:
3188 		case ALLY_CMD_GYRO_PATROL:
3189 		case ALLY_CMD_GYRO_LIGHT_TOGGLE:
3190 		case ALLY_CMD_GYRO_RETURN:
3191 		case ALLY_CMD_GYRO_DETECT_TOGGLE:
3192 			return true;
3193 			break;
3194 		default:
3195 			break;
3196 	}
3197 	return false;
3198 }
3199 
monsterGyroBotDisallowedCommands(int option)3200 bool FollowerRadialMenu::monsterGyroBotDisallowedCommands(int option)
3201 {
3202 	switch ( option )
3203 	{
3204 		case ALLY_CMD_CLASS_TOGGLE:
3205 		case ALLY_CMD_PICKUP_TOGGLE:
3206 			return true;
3207 			break;
3208 		default:
3209 			break;
3210 	}
3211 	return false;
3212 }
3213 
isItemRepairable(const Item * item,int repairScroll)3214 bool GenericGUIMenu::isItemRepairable(const Item* item, int repairScroll)
3215 {
3216 	if ( !item )
3217 	{
3218 		return false;
3219 	}
3220 	if ( !item->identified )
3221 	{
3222 		return false;
3223 	}
3224 	Category cat = itemCategory(item);
3225 	if ( repairScroll == SCROLL_CHARGING )
3226 	{
3227 		if ( item->type == ENCHANTED_FEATHER )
3228 		{
3229 			if ( item->appearance % ENCHANTED_FEATHER_MAX_DURABILITY < 100 )
3230 			{
3231 				return true;
3232 			}
3233 			return false;
3234 		}
3235 		else if ( cat == MAGICSTAFF )
3236 		{
3237 			if ( item->status == EXCELLENT )
3238 			{
3239 				return false;
3240 			}
3241 			return true;
3242 		}
3243 
3244 		return false;
3245 	}
3246 	else if ( repairScroll == SCROLL_REPAIR )
3247 	{
3248 		if ( item->status == EXCELLENT )
3249 		{
3250 			return false;
3251 		}
3252 	}
3253 	switch ( cat )
3254 	{
3255 		case WEAPON:
3256 			return true;
3257 		case ARMOR:
3258 			return true;
3259 		case MAGICSTAFF:
3260 			return false;
3261 		case THROWN:
3262 			if ( item->type == BOOMERANG )
3263 			{
3264 				return true;
3265 			}
3266 			return false;
3267 		case TOOL:
3268 			switch ( item->type )
3269 			{
3270 				case TOOL_TOWEL:
3271 				case TOOL_MIRROR:
3272 				case TOOL_SKELETONKEY:
3273 				case TOOL_TINOPENER:
3274 				case TOOL_METAL_SCRAP:
3275 				case TOOL_MAGIC_SCRAP:
3276 				case TOOL_TINKERING_KIT:
3277 				case TOOL_SENTRYBOT:
3278 				case TOOL_DETONATOR_CHARGE:
3279 				case TOOL_BOMB:
3280 				case TOOL_SLEEP_BOMB:
3281 				case TOOL_FREEZE_BOMB:
3282 				case TOOL_TELEPORT_BOMB:
3283 				case TOOL_GYROBOT:
3284 				case TOOL_SPELLBOT:
3285 				case TOOL_DECOY:
3286 				case TOOL_DUMMYBOT:
3287 				case ENCHANTED_FEATHER:
3288 					return false;
3289 					break;
3290 				default:
3291 					if ( itemTypeIsQuiver(item->type) )
3292 					{
3293 						return false;
3294 					}
3295 					return true;
3296 					break;
3297 			}
3298 			break;
3299 		default:
3300 			return false;
3301 	}
3302 }
3303 
3304 // Generic GUI Code
rebuildGUIInventory()3305 void GenericGUIMenu::rebuildGUIInventory()
3306 {
3307 	list_t* player_inventory = &stats[clientnum]->inventory;
3308 	node_t* node = nullptr;
3309 	Item* item = nullptr;
3310 	int c = 0;
3311 
3312 	if ( guiType == GUI_TYPE_TINKERING )
3313 	{
3314 		player_inventory = &tinkeringTotalItems;
3315 		tinkeringTotalLastCraftableNode = tinkeringTotalItems.last;
3316 		if ( tinkeringTotalLastCraftableNode )
3317 		{
3318 			tinkeringTotalLastCraftableNode->next = stats[clientnum]->inventory.first;
3319 		}
3320 	}
3321 	else if ( guiType == GUI_TYPE_SCRIBING )
3322 	{
3323 		player_inventory = &scribingTotalItems;
3324 		scribingTotalLastCraftableNode = scribingTotalItems.last;
3325 		if ( scribingTotalLastCraftableNode )
3326 		{
3327 			scribingTotalLastCraftableNode->next = stats[clientnum]->inventory.first;
3328 		}
3329 	}
3330 
3331 	if ( player_inventory )
3332 	{
3333 		//Count the number of items in the GUI "inventory".
3334 		for ( node = player_inventory->first; node != nullptr; node = node->next )
3335 		{
3336 			item = (Item*)node->element;
3337 			if ( item )
3338 			{
3339 				if ( shouldDisplayItemInGUI(item) )
3340 				{
3341 					++c;
3342 				}
3343 				if ( guiType == GUI_TYPE_TINKERING )
3344 				{
3345 					if ( item->node && item->node->list == &stats[clientnum]->inventory )
3346 					{
3347 						if ( item->type == TOOL_METAL_SCRAP )
3348 						{
3349 							tinkeringMetalScrap.insert(item->uid);
3350 						}
3351 						else if ( item->type == TOOL_MAGIC_SCRAP )
3352 						{
3353 							tinkeringMagicScrap.insert(item->uid);
3354 						}
3355 					}
3356 				}
3357 			}
3358 		}
3359 		if ( c == 0 && guiType == GUI_TYPE_ALCHEMY )
3360 		{
3361 			// did not find mixable item... close GUI
3362 			if ( !experimentingAlchemy )
3363 			{
3364 				messagePlayer(clientnum, language[3338]);
3365 			}
3366 			else
3367 			{
3368 				messagePlayer(clientnum, language[3343]);
3369 			}
3370 			closeGUI();
3371 			return;
3372 		}
3373 		scroll = std::max(0, std::min(scroll, c - kNumShownItems));
3374 		for ( c = 0; c < kNumShownItems; ++c )
3375 		{
3376 			itemsDisplayed[c] = nullptr;
3377 		}
3378 		c = 0;
3379 
3380 		//Assign the visible items to the GUI slots.
3381 		for ( node = player_inventory->first; node != nullptr; node = node->next )
3382 		{
3383 			if ( node->element )
3384 			{
3385 				item = (Item*)node->element;
3386 				if ( shouldDisplayItemInGUI(item) ) //Skip over all non-applicable items.
3387 				{
3388 					++c;
3389 					if ( c <= scroll )
3390 					{
3391 						continue;
3392 					}
3393 					itemsDisplayed[c - scroll - 1] = item;
3394 					if ( c > 3 + scroll )
3395 					{
3396 						break;
3397 					}
3398 				}
3399 			}
3400 		}
3401 	}
3402 }
3403 
3404 
updateGUI()3405 void GenericGUIMenu::updateGUI()
3406 {
3407 	SDL_Rect pos;
3408 	node_t* node;
3409 	int y, c;
3410 
3411 	//Generic GUI.
3412 	if ( guiActive )
3413 	{
3414 		if ( guiType == GUI_TYPE_ALCHEMY )
3415 		{
3416 			if ( !alembicItem )
3417 			{
3418 				closeGUI();
3419 				return;
3420 			}
3421 			if ( !alembicItem->node )
3422 			{
3423 				closeGUI();
3424 				return;
3425 			}
3426 			if ( alembicItem->node->list != &stats[clientnum]->inventory )
3427 			{
3428 				// dropped out of inventory or something.
3429 				closeGUI();
3430 				return;
3431 			}
3432 		}
3433 		else if ( guiType == GUI_TYPE_TINKERING )
3434 		{
3435 			if ( !tinkeringKitItem )
3436 			{
3437 				closeGUI();
3438 				return;
3439 			}
3440 			if ( !tinkeringKitItem->node )
3441 			{
3442 				closeGUI();
3443 				return;
3444 			}
3445 			if ( tinkeringKitItem->node->list != &stats[clientnum]->inventory )
3446 			{
3447 				// dropped out of inventory or something.
3448 				closeGUI();
3449 				return;
3450 			}
3451 		}
3452 		else if ( guiType == GUI_TYPE_SCRIBING )
3453 		{
3454 			if ( !scribingToolItem )
3455 			{
3456 				closeGUI();
3457 				return;
3458 			}
3459 			if ( !scribingToolItem->node )
3460 			{
3461 				closeGUI();
3462 				return;
3463 			}
3464 			if ( scribingToolItem->node->list != &stats[clientnum]->inventory )
3465 			{
3466 				// dropped out of inventory or something.
3467 				closeGUI();
3468 				return;
3469 			}
3470 			if ( scribingBlankScrollTarget && scribingFilter != SCRIBING_FILTER_CRAFTABLE )
3471 			{
3472 				scribingBlankScrollTarget = nullptr;
3473 			}
3474 			if ( scribingLastUsageDisplayTimer > 0 )
3475 			{
3476 				if ( ticks % 2 == 0 )
3477 				{
3478 					--scribingLastUsageDisplayTimer;
3479 				}
3480 			}
3481 			else
3482 			{
3483 				scribingLastUsageDisplayTimer = 0;
3484 				scribingLastUsageAmount = 0;
3485 			}
3486 		}
3487 
3488 		gui_starty = ((xres / 2) - (inventoryChest_bmp->w / 2)) + offsetx;
3489 		gui_startx = ((yres / 2) - (inventoryChest_bmp->h / 2)) + offsety;
3490 
3491 		//Center the GUI.
3492 		pos.x = gui_starty;
3493 		pos.y = gui_startx;
3494 		int windowX1 = pos.x - 20;
3495 		int windowX2 = pos.x + identifyGUI_img->w + 20;
3496 		int windowY1 = pos.y - 40;
3497 		int windowY2 = pos.y + identifyGUI_img->h + 40;
3498 		if ( guiType == GUI_TYPE_TINKERING )
3499 		{
3500 			drawWindowFancy(windowX1, windowY1, windowX2, windowY2);
3501 			int numMetalScrap = 0;
3502 			int numMagicScrap = 0;
3503 			if ( !tinkeringMetalScrap.empty() )
3504 			{
3505 				for ( auto uid : tinkeringMetalScrap )
3506 				{
3507 					if ( uidToItem(uid) )
3508 					{
3509 						numMetalScrap += (uidToItem(uid))->count;
3510 					}
3511 				}
3512 			}
3513 			if ( !tinkeringMagicScrap.empty() )
3514 			{
3515 				for ( auto uid : tinkeringMagicScrap )
3516 				{
3517 					if ( uidToItem(uid) )
3518 					{
3519 						numMagicScrap += (uidToItem(uid))->count;
3520 					}
3521 				}
3522 			}
3523 
3524 			// title
3525 			ttfPrintTextFormatted(ttf12, windowX1 + 16, windowY1 + 8,
3526 				language[3690]);
3527 			char kitStatusText[64] = "";
3528 			if ( tinkeringKitItem )
3529 			{
3530 				snprintf(kitStatusText, 63, language[3691], language[3691 + std::max(1, static_cast<int>(tinkeringKitItem->status))]);
3531 			}
3532 			ttfPrintTextFormatted(ttf12, windowX2 - 16 - (strlen(kitStatusText) + 1) * TTF12_WIDTH, windowY2 - TTF12_HEIGHT - 8,
3533 				kitStatusText);
3534 
3535 			ttfPrintTextFormatted(ttf12, windowX1 + 16, windowY2 - TTF12_HEIGHT - 8,
3536 				language[3647], numMetalScrap, numMagicScrap);
3537 			SDL_Rect smallIcon;
3538 			smallIcon.x = windowX1 + 16 + (strlen(language[3647]) - 5) * TTF12_WIDTH;
3539 			smallIcon.y = windowY2 - TTF12_HEIGHT - 12;
3540 			smallIcon.h = 16;
3541 			smallIcon.w = 16;
3542 			node_t* imageNode = items[TOOL_METAL_SCRAP].surfaces.first;
3543 			if ( imageNode )
3544 			{
3545 				drawImageScaled(*((SDL_Surface**)imageNode->element), NULL, &smallIcon);
3546 			}
3547 			smallIcon.x += TTF12_WIDTH * 6;
3548 			imageNode = items[TOOL_MAGIC_SCRAP].surfaces.first;
3549 			if ( imageNode )
3550 			{
3551 				drawImageScaled(*((SDL_Surface**)imageNode->element), NULL, &smallIcon);
3552 			}
3553 
3554 			// draw filter labels.
3555 			int txtWidth = 0;
3556 			int txtHeight = 0;
3557 			int charWidth = 0;
3558 			TTF_Font* font = ttf8;
3559 			TTF_SizeUTF8(font, "a", &charWidth, nullptr); // get 1 character width.
3560 			int textstartx = pos.x + 2 * charWidth + 4;
3561 
3562 			SDL_Rect highlightBtn;
3563 			// Craft
3564 			TTF_SizeUTF8(ttf8, language[3644], &txtWidth, &txtHeight);
3565 			highlightBtn.x = textstartx;
3566 			highlightBtn.y = pos.y + (12 - txtHeight);
3567 			highlightBtn.w = txtWidth + 2 * charWidth + 4;
3568 			highlightBtn.h = txtHeight + 4;
3569 			if ( (mousestatus[SDL_BUTTON_LEFT] || *inputPressed(joyimpulses[INJOY_MENU_USE]))
3570 				&& mouseInBounds(highlightBtn.x, highlightBtn.x + highlightBtn.w, highlightBtn.y, highlightBtn.y + highlightBtn.h) )
3571 			{
3572 				tinkeringFilter = TINKER_FILTER_CRAFTABLE;
3573 				mousestatus[SDL_BUTTON_LEFT] = 0;
3574 				*inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
3575 			}
3576 			if ( tinkeringFilter == TINKER_FILTER_CRAFTABLE )
3577 			{
3578 				drawImageScaled(button_bmp, NULL, &highlightBtn);
3579 			}
3580 			ttfPrintText(font, highlightBtn.x + 4 + charWidth, pos.y - (8 - txtHeight), language[3644]);
3581 
3582 			// Salvage
3583 			TTF_SizeUTF8(font, language[3645], &txtWidth, &txtHeight);
3584 			highlightBtn.x += highlightBtn.w;
3585 			highlightBtn.y = pos.y + (12 - txtHeight);
3586 			highlightBtn.w = txtWidth + 2 * charWidth + 4;
3587 			highlightBtn.h = txtHeight + 4;
3588 			if ( (mousestatus[SDL_BUTTON_LEFT] || *inputPressed(joyimpulses[INJOY_MENU_USE]))
3589 				&& mouseInBounds(highlightBtn.x, highlightBtn.x + highlightBtn.w, highlightBtn.y, highlightBtn.y + highlightBtn.h) )
3590 			{
3591 				tinkeringFilter = TINKER_FILTER_SALVAGEABLE;
3592 				mousestatus[SDL_BUTTON_LEFT] = 0;
3593 				*inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
3594 			}
3595 			if ( tinkeringFilter == TINKER_FILTER_SALVAGEABLE )
3596 			{
3597 				drawImageScaled(button_bmp, NULL, &highlightBtn);
3598 			}
3599 			ttfPrintText(font, highlightBtn.x + 4 + charWidth, pos.y - (8 - txtHeight), language[3645]);
3600 
3601 			// Repair
3602 			TTF_SizeUTF8(font, language[3646], &txtWidth, &txtHeight);
3603 			highlightBtn.x += highlightBtn.w;
3604 			highlightBtn.y = pos.y + (12 - txtHeight);
3605 			highlightBtn.w = txtWidth + 2 * charWidth + 4;
3606 			highlightBtn.h = txtHeight + 4;
3607 			if ( (mousestatus[SDL_BUTTON_LEFT] || *inputPressed(joyimpulses[INJOY_MENU_USE]))
3608 				&& mouseInBounds(highlightBtn.x, highlightBtn.x + highlightBtn.w, highlightBtn.y, highlightBtn.y + highlightBtn.h) )
3609 			{
3610 				tinkeringFilter = TINKER_FILTER_REPAIRABLE;
3611 				mousestatus[SDL_BUTTON_LEFT] = 0;
3612 				*inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
3613 			}
3614 			if ( tinkeringFilter == TINKER_FILTER_REPAIRABLE )
3615 			{
3616 				drawImageScaled(button_bmp, NULL, &highlightBtn);
3617 			}
3618 			ttfPrintText(font, highlightBtn.x + 4 + charWidth, pos.y - (8 - txtHeight), language[3646]);
3619 
3620 			// Filter include all (*)
3621 			TTF_SizeUTF8(font, language[356], &txtWidth, &txtHeight);
3622 			highlightBtn.x += highlightBtn.w;
3623 			highlightBtn.y = pos.y + (12 - txtHeight);
3624 			highlightBtn.w = 2 * charWidth + 4;
3625 			highlightBtn.h = txtHeight + 4;
3626 			if ( (mousestatus[SDL_BUTTON_LEFT] || *inputPressed(joyimpulses[INJOY_MENU_USE]))
3627 				&& mouseInBounds(highlightBtn.x, highlightBtn.x + highlightBtn.w, highlightBtn.y, highlightBtn.y + highlightBtn.h) )
3628 			{
3629 				tinkeringFilter = TINKER_FILTER_ALL;
3630 				mousestatus[SDL_BUTTON_LEFT] = 0;
3631 				*inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
3632 			}
3633 			if ( tinkeringFilter == TINKER_FILTER_ALL )
3634 			{
3635 				drawImageScaled(smallbutton_bmp, NULL, &highlightBtn);
3636 			}
3637 			ttfPrintText(font, highlightBtn.x + (highlightBtn.w - txtWidth) / 2, pos.y - (8 - txtHeight), language[356]);
3638 		}
3639 		else if ( guiType == GUI_TYPE_SCRIBING )
3640 		{
3641 			drawWindowFancy(windowX1, windowY1, windowX2, windowY2);
3642 
3643 			// title
3644 			ttfPrintTextFormatted(ttf12, windowX1 + 16, windowY1 + 8,
3645 				language[3716]);
3646 			char toolStatusText[64] = "";
3647 			if ( scribingToolItem && scribingToolItem->identified )
3648 			{
3649 				snprintf(toolStatusText, 63, language[3717], scribingToolItem->appearance % ENCHANTED_FEATHER_MAX_DURABILITY);
3650 			}
3651 			ttfPrintTextFormatted(ttf12, windowX2 - 16 - (strlen(toolStatusText) + 1) * TTF12_WIDTH, windowY2 - TTF12_HEIGHT - 8,
3652 				toolStatusText);
3653 			/*if ( scribingLastUsageDisplayTimer > 0 )
3654 			{
3655 				ttfPrintTextFormattedColor(ttf12, windowX2 - 16 - 11 * TTF12_WIDTH, windowY2 - TTF12_HEIGHT - 8, uint32ColorRed(*mainsurface),
3656 						"(%3d)", -scribingLastUsageAmount);
3657 			}*/
3658 
3659 			if ( scribingFilter == SCRIBING_FILTER_CRAFTABLE )
3660 			{
3661 				if ( scribingBlankScrollTarget )
3662 				{
3663 					snprintf(tempstr, 1024, language[3722], scribingBlankScrollTarget->beatitude, items[SCROLL_BLANK].name_identified);
3664 					ttfPrintTextFormatted(ttf12, windowX1 + 16, windowY2 - 2 * TTF12_HEIGHT - 8, tempstr);
3665 
3666 					SDL_Rect smallIcon;
3667 					smallIcon.x = windowX1 + 16 + (longestline(tempstr) - 5) * TTF12_WIDTH;
3668 					smallIcon.y = windowY2 - TTF12_HEIGHT - 12 - 4;
3669 					smallIcon.h = 16;
3670 					smallIcon.w = 16;
3671 					node_t* imageNode = items[SCROLL_BLANK].surfaces.first;
3672 					if ( imageNode )
3673 					{
3674 						drawImageScaled(*((SDL_Surface**)imageNode->element), NULL, &smallIcon);
3675 					}
3676 					smallIcon.x += smallIcon.w + 4;
3677 					smallIcon.w = longestline(language[3723]) * TTF12_WIDTH + 8;
3678 					smallIcon.y -= 2;
3679 					smallIcon.h += 2;
3680 					if ( mouseInBounds(smallIcon.x, smallIcon.x + smallIcon.w, smallIcon.y, smallIcon.y + smallIcon.h) )
3681 					{
3682 						drawDepressed(smallIcon.x, smallIcon.y, smallIcon.x + smallIcon.w, smallIcon.y + smallIcon.h);
3683 						if ( mousestatus[SDL_BUTTON_LEFT] || *inputPressed(joyimpulses[INJOY_MENU_USE]) )
3684 						{
3685 							*inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
3686 							mousestatus[SDL_BUTTON_LEFT] = 0;
3687 							scribingBlankScrollTarget = nullptr;
3688 						}
3689 					}
3690 					else
3691 					{
3692 						drawWindow(smallIcon.x, smallIcon.y, smallIcon.x + smallIcon.w, smallIcon.y + smallIcon.h);
3693 					}
3694 					ttfPrintTextFormatted(ttf12, smallIcon.x + 6, windowY2 - 2 * TTF12_HEIGHT + 2, language[3723]);
3695 				}
3696 				else
3697 				{
3698 					ttfPrintTextFormatted(ttf12, windowX1 + 16, windowY2 - 2 * TTF12_HEIGHT - 8,
3699 						language[3720]);
3700 				}
3701 			}
3702 			else if ( scribingFilter == SCRIBING_FILTER_REPAIRABLE )
3703 			{
3704 				ttfPrintTextFormatted(ttf12, windowX1 + 16, windowY2 - 2 * TTF12_HEIGHT - 8,
3705 					language[3726]);
3706 			}
3707 
3708 			// draw filter labels.
3709 			int txtWidth = 0;
3710 			int txtHeight = 0;
3711 			int charWidth = 0;
3712 			TTF_Font* font = ttf8;
3713 			TTF_SizeUTF8(font, "a", &charWidth, nullptr); // get 1 character width.
3714 			int textstartx = pos.x + 2 * charWidth + 4;
3715 
3716 			SDL_Rect highlightBtn;
3717 			// Inscribe
3718 			TTF_SizeUTF8(ttf8, language[3718], &txtWidth, &txtHeight);
3719 			highlightBtn.x = textstartx;
3720 			highlightBtn.y = pos.y + (12 - txtHeight);
3721 			highlightBtn.w = txtWidth + 2 * charWidth + 4;
3722 			highlightBtn.h = txtHeight + 4;
3723 			if ( (mousestatus[SDL_BUTTON_LEFT] || *inputPressed(joyimpulses[INJOY_MENU_USE]))
3724 				&& mouseInBounds(highlightBtn.x, highlightBtn.x + highlightBtn.w, highlightBtn.y, highlightBtn.y + highlightBtn.h) )
3725 			{
3726 				scribingFilter = SCRIBING_FILTER_CRAFTABLE;
3727 				mousestatus[SDL_BUTTON_LEFT] = 0;
3728 				*inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
3729 			}
3730 			if ( scribingFilter == SCRIBING_FILTER_CRAFTABLE )
3731 			{
3732 				drawImageScaled(button_bmp, NULL, &highlightBtn);
3733 			}
3734 			ttfPrintText(font, highlightBtn.x + 4 + charWidth, pos.y - (8 - txtHeight), language[3718]);
3735 
3736 			// Repair
3737 			TTF_SizeUTF8(font, language[3719], &txtWidth, &txtHeight);
3738 			highlightBtn.x += highlightBtn.w;
3739 			highlightBtn.y = pos.y + (12 - txtHeight);
3740 			highlightBtn.w = txtWidth + 2 * charWidth + 4;
3741 			highlightBtn.h = txtHeight + 4;
3742 			if ( (mousestatus[SDL_BUTTON_LEFT] || *inputPressed(joyimpulses[INJOY_MENU_USE]))
3743 				&& mouseInBounds(highlightBtn.x, highlightBtn.x + highlightBtn.w, highlightBtn.y, highlightBtn.y + highlightBtn.h) )
3744 			{
3745 				scribingFilter = SCRIBING_FILTER_REPAIRABLE;
3746 				mousestatus[SDL_BUTTON_LEFT] = 0;
3747 				*inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
3748 			}
3749 			if ( scribingFilter == SCRIBING_FILTER_REPAIRABLE )
3750 			{
3751 				drawImageScaled(button_bmp, NULL, &highlightBtn);
3752 			}
3753 			ttfPrintText(font, highlightBtn.x + 4 + charWidth, pos.y - (8 - txtHeight), language[3719]);
3754 		}
3755 		drawImage(identifyGUI_img, NULL, &pos);
3756 
3757 		//Buttons
3758 		if ( mousestatus[SDL_BUTTON_LEFT] )
3759 		{
3760 			//GUI scroll up button.
3761 			if ( omousey >= gui_startx + 16 && omousey < gui_startx + 52 )
3762 			{
3763 				if ( omousex >= gui_starty + (identifyGUI_img->w - 28) && omousex < gui_starty + (identifyGUI_img->w - 12) )
3764 				{
3765 					buttonclick = 7;
3766 					scroll--;
3767 					mousestatus[SDL_BUTTON_LEFT] = 0;
3768 				}
3769 			}
3770 			//GUI scroll down button.
3771 			else if ( omousey >= gui_startx + 52 && omousey < gui_startx + 88 )
3772 			{
3773 				if ( omousex >= gui_starty + (identifyGUI_img->w - 28) && omousex < gui_starty + (identifyGUI_img->w - 12) )
3774 				{
3775 					buttonclick = 8;
3776 					scroll++;
3777 					mousestatus[SDL_BUTTON_LEFT] = 0;
3778 				}
3779 			}
3780 			else if ( omousey >= gui_startx && omousey < gui_startx + 15 )
3781 			{
3782 				//GUI close button.
3783 				if ( omousex >= gui_starty + 393 && omousex < gui_starty + 407 )
3784 				{
3785 					buttonclick = 9;
3786 					mousestatus[SDL_BUTTON_LEFT] = 0;
3787 				}
3788 				if ( omousex >= gui_starty && omousex < gui_starty + 377 && omousey >= gui_startx && omousey < gui_startx + 15 )
3789 				{
3790 					gui_clickdrag = true;
3791 					draggingGUI = true;
3792 					dragoffset_x = omousex - gui_starty;
3793 					dragoffset_y = omousey - gui_startx;
3794 					mousestatus[SDL_BUTTON_LEFT] = 0;
3795 				}
3796 			}
3797 		}
3798 
3799 		// mousewheel
3800 		if ( omousex >= gui_starty + 12 && omousex < gui_starty + (identifyGUI_img->w - 28) )
3801 		{
3802 			if ( omousey >= gui_startx + 16 && omousey < gui_startx + (identifyGUI_img->h - 8) )
3803 			{
3804 				if ( mousestatus[SDL_BUTTON_WHEELDOWN] )
3805 				{
3806 					mousestatus[SDL_BUTTON_WHEELDOWN] = 0;
3807 					scroll++;
3808 				}
3809 				else if ( mousestatus[SDL_BUTTON_WHEELUP] )
3810 				{
3811 					mousestatus[SDL_BUTTON_WHEELUP] = 0;
3812 					scroll--;
3813 				}
3814 			}
3815 		}
3816 
3817 		if ( draggingGUI )
3818 		{
3819 			if ( gui_clickdrag )
3820 			{
3821 				offsetx = (omousex - dragoffset_x) - (gui_starty - offsetx);
3822 				offsety = (omousey - dragoffset_y) - (gui_startx - offsety);
3823 				if ( gui_starty <= 0 )
3824 				{
3825 					offsetx = 0 - (gui_starty - offsetx);
3826 				}
3827 				if ( gui_starty > 0 + xres - identifyGUI_img->w )
3828 				{
3829 					offsetx = (0 + xres - identifyGUI_img->w) - (gui_starty - offsetx);
3830 				}
3831 				if ( gui_startx <= 0 )
3832 				{
3833 					offsety = 0 - (gui_startx - offsety);
3834 				}
3835 				if ( gui_startx > 0 + yres - identifyGUI_img->h )
3836 				{
3837 					offsety = (0 + yres - identifyGUI_img->h) - (gui_startx - offsety);
3838 				}
3839 			}
3840 			else
3841 			{
3842 				draggingGUI = false;
3843 			}
3844 		}
3845 
3846 		list_t* player_inventory = &stats[clientnum]->inventory;
3847 		if ( guiType == GUI_TYPE_TINKERING )
3848 		{
3849 			player_inventory = &tinkeringTotalItems;
3850 			tinkeringTotalLastCraftableNode = tinkeringTotalItems.last;
3851 			if ( tinkeringTotalLastCraftableNode )
3852 			{
3853 				tinkeringTotalLastCraftableNode->next = stats[clientnum]->inventory.first;
3854 			}
3855 		}
3856 		else if ( guiType == GUI_TYPE_SCRIBING )
3857 		{
3858 			player_inventory = &scribingTotalItems;
3859 			scribingTotalLastCraftableNode = scribingTotalItems.last;
3860 			if ( scribingTotalLastCraftableNode )
3861 			{
3862 				scribingTotalLastCraftableNode->next = stats[clientnum]->inventory.first;
3863 			}
3864 		}
3865 
3866 		if ( !player_inventory )
3867 		{
3868 			messagePlayer(0, "Warning: stats[%d].inventory is not a valid list. This should not happen.", clientnum);
3869 		}
3870 		else
3871 		{
3872 			//Print the window label signifying this GUI.
3873 			char* window_name;
3874 			if ( guiType == GUI_TYPE_REPAIR )
3875 			{
3876 				if ( repairItemType == SCROLL_REPAIR )
3877 				{
3878 					window_name = language[3286];
3879 				}
3880 				else if ( repairItemType == SCROLL_CHARGING )
3881 				{
3882 					window_name = language[3732];
3883 				}
3884 				ttfPrintText(ttf8, (gui_starty + 2 + ((identifyGUI_img->w / 2) - ((TTF8_WIDTH * longestline(window_name)) / 2))), gui_startx + 4, window_name);
3885 			}
3886 			else if ( guiType == GUI_TYPE_ALCHEMY )
3887 			{
3888 				if ( !basePotion )
3889 				{
3890 					if ( !experimentingAlchemy )
3891 					{
3892 						window_name = language[3328];
3893 					}
3894 					else
3895 					{
3896 						window_name = language[3344];
3897 					}
3898 					ttfPrintText(ttf8, (gui_starty + 2 + ((identifyGUI_img->w / 2) - ((TTF8_WIDTH * longestline(window_name)) / 2))), gui_startx + 4, window_name);
3899 				}
3900 				else
3901 				{
3902 					if ( !experimentingAlchemy )
3903 					{
3904 						window_name = language[3329];
3905 					}
3906 					else
3907 					{
3908 						window_name = language[3345];
3909 					}
3910 					ttfPrintText(ttf8, (gui_starty + 2 + ((identifyGUI_img->w / 2) - ((TTF8_WIDTH * longestline(window_name)) / 2))),
3911 						gui_startx + 4 - TTF8_HEIGHT - 4, window_name);
3912 					int count = basePotion->count;
3913 					basePotion->count = 1;
3914 					char *description = basePotion->description();
3915 					basePotion->count = count;
3916 					ttfPrintText(ttf8, (gui_starty + 2 + ((identifyGUI_img->w / 2) - ((TTF8_WIDTH * longestline(description)) / 2))),
3917 						gui_startx + 4, description);
3918 				}
3919 			}
3920 
3921 			//GUI up button.
3922 			if ( buttonclick == 7 )
3923 			{
3924 				pos.x = gui_starty + (identifyGUI_img->w - 28);
3925 				pos.y = gui_startx + 16;
3926 				pos.w = 0;
3927 				pos.h = 0;
3928 				drawImage(invup_bmp, NULL, &pos);
3929 			}
3930 			//GUI down button.
3931 			if ( buttonclick == 8 )
3932 			{
3933 				pos.x = gui_starty + (identifyGUI_img->w - 28);
3934 				pos.y = gui_startx + 52;
3935 				pos.w = 0;
3936 				pos.h = 0;
3937 				drawImage(invdown_bmp, NULL, &pos);
3938 			}
3939 			//GUI close button.
3940 			if ( buttonclick == 9 )
3941 			{
3942 				pos.x = gui_starty + 393;
3943 				pos.y = gui_startx;
3944 				pos.w = 0;
3945 				pos.h = 0;
3946 				drawImage(invclose_bmp, NULL, &pos);
3947 				closeGUI();
3948 			}
3949 
3950 			Item *item = nullptr;
3951 
3952 			bool selectingSlot = false;
3953 			SDL_Rect slotPos;
3954 			slotPos.x = gui_starty + 12;
3955 			slotPos.w = inventoryoptionChest_bmp->w;
3956 			slotPos.y = gui_startx + 16;
3957 			slotPos.h = inventoryoptionChest_bmp->h;
3958 			bool mouseWithinBoundaryX = (mousex >= slotPos.x && mousex < slotPos.x + slotPos.w);
3959 
3960 			for ( int i = 0; i < kNumShownItems; ++i, slotPos.y += slotPos.h )
3961 			{
3962 				pos.x = slotPos.x;
3963 				pos.w = 0;
3964 				pos.h = 0;
3965 
3966 
3967 				if ( mouseWithinBoundaryX && omousey >= slotPos.y && omousey < slotPos.y + slotPos.h && itemsDisplayed[i] )
3968 				{
3969 					pos.y = slotPos.y;
3970 					drawImage(inventoryoptionChest_bmp, nullptr, &pos);
3971 					selectedSlot = i;
3972 					selectingSlot = true;
3973 					if ( mousestatus[SDL_BUTTON_LEFT] || *inputPressed(joyimpulses[INJOY_MENU_USE]) )
3974 					{
3975 						*inputPressed(joyimpulses[INJOY_MENU_USE]) = 0;
3976 						mousestatus[SDL_BUTTON_LEFT] = 0;
3977 
3978 						bool result = executeOnItemClick(itemsDisplayed[i]);
3979 						GUICurrentType oldType = guiType;
3980 						rebuildGUIInventory();
3981 
3982 						if ( oldType == GUI_TYPE_ALCHEMY && !guiActive )
3983 						{
3984 							// do nothing
3985 						}
3986 						else if ( itemsDisplayed[i] == nullptr )
3987 						{
3988 							if ( itemsDisplayed[0] == nullptr )
3989 							{
3990 								//Go back to inventory.
3991 								selectedSlot = -1;
3992 								warpMouseToSelectedInventorySlot();
3993 							}
3994 							else
3995 							{
3996 								//Move up one slot.
3997 								--selectedSlot;
3998 								warpMouseToSelectedSlot();
3999 							}
4000 						}
4001 					}
4002 				}
4003 			}
4004 
4005 			if ( !selectingSlot )
4006 			{
4007 				selectedSlot = -1;
4008 			}
4009 
4010 			//Okay, now prepare to render all the items.
4011 			y = gui_startx + 22;
4012 			c = 0;
4013 			if ( player_inventory )
4014 			{
4015 				rebuildGUIInventory();
4016 
4017 				//Actually render the items.
4018 				c = 0;
4019 				for ( node = player_inventory->first; node != NULL; node = node->next )
4020 				{
4021 					if ( node->element )
4022 					{
4023 						item = (Item*)node->element;
4024 						bool displayItem = shouldDisplayItemInGUI(item);
4025 						if ( displayItem )   //Skip over all non-used items
4026 						{
4027 							c++;
4028 							if ( c <= scroll )
4029 							{
4030 								continue;
4031 							}
4032 							char tempstr[256] = { 0 };
4033 							int showTinkeringBotHealthPercentage = false;
4034 							Uint32 color = uint32ColorWhite(*mainsurface);
4035 							if ( guiType == GUI_TYPE_TINKERING )
4036 							{
4037 								if ( isNodeTinkeringCraftableItem(item->node) )
4038 								{
4039 									// if anything, these should be doing
4040 									// strncpy(tempstr, language[N], TEMPSTR_LEN - <extra space needed>)
4041 									// not strlen(language[N]). there is zero safety conferred from this
4042 									// anti-pattern. different story with memcpy(), but strcpy() is not
4043 									// memcpy().
4044 									strcpy(tempstr, language[3644]); // craft
4045 									strncat(tempstr, item->description(), 46 - strlen(language[3644]));
4046 									if ( !tinkeringPlayerCanAffordCraft(item) || (tinkeringPlayerHasSkillLVLToCraft(item) == -1) )
4047 									{
4048 										color = uint32ColorGray(*mainsurface);
4049 									}
4050 								}
4051 								else if ( isItemSalvageable(item, clientnum) && tinkeringFilter != TINKER_FILTER_REPAIRABLE )
4052 								{
4053 									strcpy(tempstr, language[3645]); // salvage
4054 									strncat(tempstr, item->description(), 46 - strlen(language[3645]));
4055 								}
4056 								else if ( tinkeringIsItemRepairable(item, clientnum) )
4057 								{
4058 									if ( tinkeringIsItemUpgradeable(item) )
4059 									{
4060 										if ( tinkeringUpgradeMaxStatus(item) <= item->status )
4061 										{
4062 											color = uint32ColorGray(*mainsurface); // can't upgrade since it's higher status than we can craft.
4063 										}
4064 										else if ( !tinkeringPlayerCanAffordRepair(item) )
4065 										{
4066 											color = uint32ColorGray(*mainsurface); // can't upgrade since no materials
4067 										}
4068 										strcpy(tempstr, language[3684]); // upgrade
4069 										strncat(tempstr, item->description(), 46 - strlen(language[3684]));
4070 									}
4071 									else
4072 									{
4073 										if ( tinkeringPlayerHasSkillLVLToCraft(item) == -1 && itemCategory(item) == TOOL )
4074 										{
4075 											color = uint32ColorGray(*mainsurface); // can't repair since no we can't craft it.
4076 										}
4077 										else if ( !tinkeringPlayerCanAffordRepair(item) )
4078 										{
4079 											color = uint32ColorGray(*mainsurface); // can't repair since no materials
4080 										}
4081 										strcpy(tempstr, language[3646]); // repair
4082 										strncat(tempstr, item->description(), 46 - strlen(language[3646]));
4083 									}
4084 									if ( item->type == TOOL_SENTRYBOT || item->type == TOOL_DUMMYBOT || item->type == TOOL_SPELLBOT )
4085 									{
4086 										showTinkeringBotHealthPercentage = true;
4087 									}
4088 								}
4089 								else
4090 								{
4091 									messagePlayer(0, "%d", item->type);
4092 									strncat(tempstr, "invalid item", 13);
4093 								}
4094 							}
4095 							else if ( guiType == GUI_TYPE_SCRIBING )
4096 							{
4097 								if ( isNodeScribingCraftableItem(item->node) )
4098 								{
4099 									snprintf(tempstr, sizeof(tempstr), language[3721], item->getScrollLabel());
4100 								}
4101 								else
4102 								{
4103 									if ( scribingFilter == SCRIBING_FILTER_REPAIRABLE )
4104 									{
4105 										strcpy(tempstr, language[3719]); // repair
4106 										strncat(tempstr, item->description(), 46 - strlen(language[3718]));
4107 									}
4108 									else
4109 									{
4110 										strcpy(tempstr, language[3718]); // inscribe
4111 										int oldcount = item->count;
4112 										item->count = 1;
4113 										strncat(tempstr, item->description(), 46 - strlen(language[3718]));
4114 										item->count = oldcount;
4115 									}
4116 								}
4117 							}
4118 							else
4119 							{
4120 								strncpy(tempstr, item->description(), 46);
4121 							}
4122 
4123 							if ( showTinkeringBotHealthPercentage )
4124 							{
4125 								int health = 100;
4126 								if ( item->appearance >= 0 && item->appearance <= 4 )
4127 								{
4128 									health = 25 * item->appearance;
4129 									if ( health == 0 && item->status != BROKEN )
4130 									{
4131 										health = 5;
4132 									}
4133 								}
4134 								char healthstr[32] = "";
4135 								snprintf(healthstr, 16, " (%d%%)", health);
4136 								strncat(tempstr, healthstr, 46 - strlen(tempstr) - strlen(healthstr));
4137 							}
4138 							else if ( item->type == ENCHANTED_FEATHER && item->identified )
4139 							{
4140 								char healthstr[32] = "";
4141 								snprintf(healthstr, 16, " (%d%%)", item->appearance % ENCHANTED_FEATHER_MAX_DURABILITY);
4142 								strncat(tempstr, healthstr, 46 - strlen(tempstr) - strlen(healthstr));
4143 							}
4144 
4145 
4146 							if ( strlen(tempstr) >= 46 )
4147 							{
4148 								strcat(tempstr, " ...");
4149 							}
4150 							ttfPrintTextColor(ttf8, gui_starty + 36, y, color, true, tempstr);
4151 							pos.x = gui_starty + 16;
4152 							pos.y = gui_startx + 17 + 18 * (c - scroll - 1);
4153 							pos.w = 16;
4154 							pos.h = 16;
4155 							drawImageScaled(itemSprite(item), NULL, &pos);
4156 							if ( guiType == GUI_TYPE_TINKERING )
4157 							{
4158 								int metal = 0;
4159 								int magic = 0;
4160 								if ( isNodeTinkeringCraftableItem(item->node) )
4161 								{
4162 									tinkeringGetCraftingCost(item, &metal, &magic);
4163 								}
4164 								else if ( isItemSalvageable(item, clientnum) && tinkeringFilter != TINKER_FILTER_REPAIRABLE )
4165 								{
4166 									tinkeringGetItemValue(item, &metal, &magic);
4167 								}
4168 								else if ( tinkeringIsItemRepairable(item, clientnum) )
4169 								{
4170 									tinkeringGetRepairCost(item, &metal, &magic);
4171 								}
4172 								pos.x = windowX2 - 20 - TTF8_WIDTH * 12;
4173 								if ( !item->identified )
4174 								{
4175 									ttfPrintTextFormattedColor(ttf8, windowX2 - 24 - TTF8_WIDTH * 15, y, color, "  ?    ?");
4176 								}
4177 								else
4178 								{
4179 									ttfPrintTextFormattedColor(ttf8, windowX2 - 24 - TTF8_WIDTH * 15, y, color, "%3d  %3d", metal, magic);
4180 								}
4181 								node_t* imageNode = items[TOOL_METAL_SCRAP].surfaces.first;
4182 								if ( imageNode )
4183 								{
4184 									drawImageScaled(*((SDL_Surface**)imageNode->element), NULL, &pos);
4185 								}
4186 								pos.x += TTF12_WIDTH * 4;
4187 								imageNode = items[TOOL_MAGIC_SCRAP].surfaces.first;
4188 								if ( imageNode )
4189 								{
4190 									drawImageScaled(*((SDL_Surface**)imageNode->element), NULL, &pos);
4191 								}
4192 							}
4193 							y += 18;
4194 							if ( c > 3 + scroll )
4195 							{
4196 								break;
4197 							}
4198 						}
4199 					}
4200 				}
4201 			}
4202 		}
4203 	}
4204 }
4205 
shouldDisplayItemInGUI(Item * item)4206 bool GenericGUIMenu::shouldDisplayItemInGUI(Item* item)
4207 {
4208 	if ( !item )
4209 	{
4210 		return false;
4211 	}
4212 	if ( guiType == GUI_TYPE_REPAIR )
4213 	{
4214 		return isItemRepairable(item, repairItemType);
4215 	}
4216 	else if ( guiType == GUI_TYPE_ALCHEMY )
4217 	{
4218 		return isItemMixable(item);
4219 	}
4220 	else if ( guiType == GUI_TYPE_TINKERING )
4221 	{
4222 		if ( isNodeTinkeringCraftableItem(item->node) )
4223 		{
4224 			if ( tinkeringFilter == TINKER_FILTER_ALL || tinkeringFilter == TINKER_FILTER_CRAFTABLE )
4225 			{
4226 				return true;
4227 			}
4228 		}
4229 		else if ( tinkeringIsItemRepairable(item, clientnum) && tinkeringFilter == TINKER_FILTER_REPAIRABLE )
4230 		{
4231 			return true;
4232 		}
4233 		else if ( isItemSalvageable(item, clientnum) )
4234 		{
4235 			if ( tinkeringFilter == TINKER_FILTER_ALL || tinkeringFilter == TINKER_FILTER_SALVAGEABLE )
4236 			{
4237 				return true;
4238 			}
4239 		}
4240 	}
4241 	else if ( guiType == GUI_TYPE_SCRIBING )
4242 	{
4243 		if ( scribingFilter != SCRIBING_FILTER_REPAIRABLE )
4244 		{
4245 			if ( isNodeScribingCraftableItem(item->node) )
4246 			{
4247 				if ( scribingBlankScrollTarget )
4248 				{
4249 					return true;
4250 				}
4251 				else
4252 				{
4253 					return false;
4254 				}
4255 			}
4256 			else if ( !scribingBlankScrollTarget && item->identified && item->type == SCROLL_BLANK )
4257 			{
4258 				return true;
4259 			}
4260 		}
4261 		else if ( scribingFilter != SCRIBING_FILTER_CRAFTABLE )
4262 		{
4263 			if ( itemCategory(item) == SPELLBOOK && item->identified && item->status < EXCELLENT )
4264 			{
4265 				return true;
4266 			}
4267 		}
4268 	}
4269 	return false;
4270 }
4271 
repairItem(Item * item)4272 void GenericGUIMenu::repairItem(Item* item)
4273 {
4274 	if ( !item )
4275 	{
4276 		return;
4277 	}
4278 	if ( !shouldDisplayItemInGUI(item) )
4279 	{
4280 		messagePlayer(clientnum, language[3287], item->getName());
4281 		return;
4282 	}
4283 
4284 	bool isEquipped = itemIsEquipped(item, clientnum);
4285 
4286 	if ( repairItemType == SCROLL_CHARGING )
4287 	{
4288 		if ( itemCategory(item) == MAGICSTAFF )
4289 		{
4290 			if ( item->status == BROKEN )
4291 			{
4292 				if ( usingScrollBeatitude > 0 )
4293 				{
4294 					item->status = EXCELLENT;
4295 				}
4296 				else
4297 				{
4298 					item->status = WORN;
4299 				}
4300 			}
4301 			else
4302 			{
4303 				item->status = EXCELLENT;
4304 			}
4305 		}
4306 		else if ( item->type == ENCHANTED_FEATHER )
4307 		{
4308 			int durability = item->appearance % ENCHANTED_FEATHER_MAX_DURABILITY;
4309 			int repairAmount = 100 - durability;
4310 			if ( repairAmount > (ENCHANTED_FEATHER_MAX_DURABILITY / 2) )
4311 			{
4312 				if ( usingScrollBeatitude == 0 )
4313 				{
4314 					repairAmount = ENCHANTED_FEATHER_MAX_DURABILITY / 2;
4315 				}
4316 			}
4317 			item->appearance += repairAmount;
4318 			item->status = EXCELLENT;
4319 		}
4320 		messagePlayer(clientnum, language[3730], item->getName());
4321 	}
4322 	else
4323 	{
4324 		if ( item->status == BROKEN )
4325 		{
4326 			item->status = DECREPIT;
4327 		}
4328 		else
4329 		{
4330 			if ( (item->type >= ARTIFACT_SWORD && item->type <= ARTIFACT_GLOVES) || item->type == BOOMERANG )
4331 			{
4332 				item->status = static_cast<Status>(std::min(item->status + 1, static_cast<int>(EXCELLENT)));
4333 			}
4334 			else
4335 			{
4336 				item->status = static_cast<Status>(std::min(item->status + 2 + usingScrollBeatitude, static_cast<int>(EXCELLENT)));
4337 			}
4338 		}
4339 		messagePlayer(clientnum, language[872], item->getName());
4340 	}
4341 	closeGUI();
4342 	if ( multiplayer == CLIENT && isEquipped )
4343 	{
4344 		// the client needs to inform the server that their equipment was repaired.
4345 		int armornum = 0;
4346 		if ( item == stats[clientnum]->weapon )
4347 		{
4348 			armornum = 0;
4349 		}
4350 		else if ( item == stats[clientnum]->helmet )
4351 		{
4352 			armornum = 1;
4353 		}
4354 		else if ( item == stats[clientnum]->breastplate )
4355 		{
4356 			armornum = 2;
4357 		}
4358 		else if ( item == stats[clientnum]->gloves )
4359 		{
4360 			armornum = 3;
4361 		}
4362 		else if ( item == stats[clientnum]->shoes )
4363 		{
4364 			armornum = 4;
4365 		}
4366 		else if ( item == stats[clientnum]->shield )
4367 		{
4368 			armornum = 5;
4369 		}
4370 		else if ( item == stats[clientnum]->cloak )
4371 		{
4372 			armornum = 6;
4373 		}
4374 		else if ( item == stats[clientnum]->mask )
4375 		{
4376 			armornum = 7;
4377 		}
4378 		strcpy((char*)net_packet->data, "REPA");
4379 		net_packet->data[4] = clientnum;
4380 		net_packet->data[5] = armornum;
4381 		net_packet->data[6] = item->status;
4382 		net_packet->address.host = net_server.host;
4383 		net_packet->address.port = net_server.port;
4384 		net_packet->len = 7;
4385 		sendPacketSafe(net_sock, -1, net_packet, 0);
4386 	}
4387 }
4388 
closeGUI()4389 void GenericGUIMenu::closeGUI()
4390 {
4391 	tinkeringFreeLists();
4392 	scribingFreeLists();
4393 	guiActive = false;
4394 	selectedSlot = -1;
4395 	guiType = GUI_TYPE_NONE;
4396 	basePotion = nullptr;
4397 	secondaryPotion = nullptr;
4398 	alembicItem = nullptr;
4399 	repairItemType = 0;
4400 }
4401 
getItemInfo(int slot)4402 inline Item* GenericGUIMenu::getItemInfo(int slot)
4403 {
4404 	if ( slot >= kNumShownItems )
4405 	{
4406 		return nullptr; //Out of bounds,
4407 	}
4408 
4409 	return itemsDisplayed[slot];
4410 }
4411 
selectSlot(int slot)4412 void GenericGUIMenu::selectSlot(int slot)
4413 {
4414 	if ( slot < selectedSlot )
4415 	{
4416 		//Moving up.
4417 
4418 		/*
4419 		* Possible cases:
4420 		* * 1) Move cursor up the GUI through different selectedSlot.
4421 		* * 2) Page up through scroll--
4422 		* * 3) Scrolling up past top of GUI, no scroll (move back to inventory)
4423 		*/
4424 
4425 		if ( selectedSlot <= 0 )
4426 		{
4427 			//Covers cases 2 & 3.
4428 
4429 			/*
4430 			* Possible cases:
4431 			* * A) Hit very top of "inventory", can't go any further. Return to inventory.
4432 			* * B) Page up, scrolling through scroll.
4433 			*/
4434 
4435 			if ( scroll <= 0 )
4436 			{
4437 				//Case 3/A: Return to inventory.
4438 				selectedSlot = -1;
4439 			}
4440 			else
4441 			{
4442 				//Case 2/B: Page up through "inventory".
4443 				--scroll;
4444 			}
4445 		}
4446 		else
4447 		{
4448 			//Covers case 1.
4449 
4450 			//Move cursor up the GUI through different selectedSlot (--selectedSlot).
4451 			--selectedSlot;
4452 			warpMouseToSelectedSlot();
4453 		}
4454 	}
4455 	else if ( slot > selectedSlot )
4456 	{
4457 		//Moving down.
4458 
4459 		/*
4460 		* Possible cases:
4461 		* * 1) Moving cursor down through GUI through different selectedSlot.
4462 		* * 2) Scrolling down past bottom of GUI through scroll++
4463 		* * 3) Scrolling down past bottom of GUI, max scroll (revoke move -- can't go beyond limit of GUI).
4464 		*/
4465 
4466 		if ( selectedSlot >= kNumShownItems - 1 )
4467 		{
4468 			//Covers cases 2 & 3.
4469 			++scroll; //scroll is automatically sanitized in updateGUI().
4470 		}
4471 		else
4472 		{
4473 			//Covers case 1.
4474 			//Move cursor down through the GUI through different selectedSlot (++selectedSlot).
4475 			//This is a little bit trickier since must revoke movement if there is no item in the next slot!
4476 
4477 			/*
4478 			* Two possible cases:
4479 			* * A) Items below this. Advance selectedSlot to them.
4480 			* * B) On last item already. Do nothing (revoke movement).
4481 			*/
4482 
4483 			Item* item = getItemInfo(selectedSlot + 1);
4484 
4485 			if ( item )
4486 			{
4487 				++selectedSlot;
4488 				warpMouseToSelectedSlot();
4489 			}
4490 			else
4491 			{
4492 				//No more items. Stop.
4493 			}
4494 		}
4495 	}
4496 }
4497 
warpMouseToSelectedSlot()4498 void GenericGUIMenu::warpMouseToSelectedSlot()
4499 {
4500 	SDL_Rect slotPos;
4501 	slotPos.x = gui_starty;
4502 	slotPos.w = inventoryoptionChest_bmp->w;
4503 	slotPos.h = inventoryoptionChest_bmp->h;
4504 	slotPos.y = gui_startx + 16 + (slotPos.h * selectedSlot);
4505 
4506 	SDL_WarpMouseInWindow(screen, slotPos.x + (slotPos.w / 2), slotPos.y + (slotPos.h / 2));
4507 }
4508 
initGUIControllerCode()4509 void GenericGUIMenu::initGUIControllerCode()
4510 {
4511 	if ( itemsDisplayed[0] )
4512 	{
4513 		selectedSlot = 0;
4514 		this->warpMouseToSelectedSlot();
4515 	}
4516 	else
4517 	{
4518 		selectedSlot = -1;
4519 	}
4520 }
4521 
openGUI(int type,int scrollBeatitude,int scrollType)4522 void GenericGUIMenu::openGUI(int type, int scrollBeatitude, int scrollType)
4523 {
4524 	this->closeGUI();
4525 	shootmode = false;
4526 	openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM); // Reset the GUI to the inventory.
4527 	guiActive = true;
4528 	usingScrollBeatitude = scrollBeatitude;
4529 	repairItemType = scrollType;
4530 	guiType = static_cast<GUICurrentType>(type);
4531 	gui_starty = ((xres / 2) - (inventoryChest_bmp->w / 2)) + offsetx;
4532 	gui_startx = ((yres / 2) - (inventoryChest_bmp->h / 2)) + offsety;
4533 
4534 	if ( removecursegui_active )
4535 	{
4536 		closeRemoveCurseGUI();
4537 	}
4538 	if ( identifygui_active )
4539 	{
4540 		CloseIdentifyGUI();
4541 	}
4542 	FollowerMenu.closeFollowerMenuGUI();
4543 
4544 	if ( openedChest[clientnum] )
4545 	{
4546 		openedChest[clientnum]->closeChest();
4547 	}
4548 	rebuildGUIInventory();
4549 	this->initGUIControllerCode();
4550 }
4551 
openGUI(int type,bool experimenting,Item * itemOpenedWith)4552 void GenericGUIMenu::openGUI(int type, bool experimenting, Item* itemOpenedWith)
4553 {
4554 	this->closeGUI();
4555 	shootmode = false;
4556 	openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM); // Reset the GUI to the inventory.
4557 	guiActive = true;
4558 	alembicItem = itemOpenedWith;
4559 	experimentingAlchemy = experimenting;
4560 	guiType = static_cast<GUICurrentType>(type);
4561 
4562 	gui_starty = ((xres / 2) - (inventoryChest_bmp->w / 2)) + offsetx;
4563 	gui_startx = ((yres / 2) - (inventoryChest_bmp->h / 2)) + offsety;
4564 
4565 	if ( removecursegui_active )
4566 	{
4567 		closeRemoveCurseGUI();
4568 	}
4569 	if ( identifygui_active )
4570 	{
4571 		CloseIdentifyGUI();
4572 	}
4573 	FollowerMenu.closeFollowerMenuGUI();
4574 
4575 	if ( openedChest[clientnum] )
4576 	{
4577 		openedChest[clientnum]->closeChest();
4578 	}
4579 	rebuildGUIInventory();
4580 	this->initGUIControllerCode();
4581 }
4582 
openGUI(int type,Item * itemOpenedWith)4583 void GenericGUIMenu::openGUI(int type, Item* itemOpenedWith)
4584 {
4585 	this->closeGUI();
4586 	shootmode = false;
4587 	openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM); // Reset the GUI to the inventory.
4588 	guiActive = true;
4589 	guiType = static_cast<GUICurrentType>(type);
4590 
4591 	gui_starty = ((xres / 2) - (inventoryChest_bmp->w / 2)) + offsetx;
4592 	gui_startx = ((yres / 2) - (inventoryChest_bmp->h / 2)) + offsety;
4593 
4594 	// build the craftables list.
4595 	if ( guiType == GUI_TYPE_TINKERING )
4596 	{
4597 		tinkeringKitItem = itemOpenedWith;
4598 		tinkeringCreateCraftableItemList();
4599 	}
4600 	else if ( guiType == GUI_TYPE_SCRIBING )
4601 	{
4602 		scribingToolItem = itemOpenedWith;
4603 		scribingCreateCraftableItemList();
4604 	}
4605 
4606 	if ( removecursegui_active )
4607 	{
4608 		closeRemoveCurseGUI();
4609 	}
4610 	if ( identifygui_active )
4611 	{
4612 		CloseIdentifyGUI();
4613 	}
4614 	FollowerMenu.closeFollowerMenuGUI();
4615 
4616 	if ( openedChest[clientnum] )
4617 	{
4618 		openedChest[clientnum]->closeChest();
4619 	}
4620 	rebuildGUIInventory();
4621 	this->initGUIControllerCode();
4622 }
4623 
executeOnItemClick(Item * item)4624 bool GenericGUIMenu::executeOnItemClick(Item* item)
4625 {
4626 	if ( !item )
4627 	{
4628 		return false;
4629 	}
4630 
4631 	if ( guiType == GUI_TYPE_REPAIR )
4632 	{
4633 		repairItem(item);
4634 		return true;
4635 	}
4636 	else if ( guiType == GUI_TYPE_ALCHEMY )
4637 	{
4638 		if ( !basePotion )
4639 		{
4640 			if ( isItemMixable(item) )
4641 			{
4642 				basePotion = item;
4643 				// check if secondary potion available.
4644 				list_t* player_inventory = &stats[clientnum]->inventory;
4645 				for ( node_t* node = player_inventory->first; node != nullptr; node = node->next )
4646 				{
4647 					if ( node->element )
4648 					{
4649 						Item* checkItem = (Item*)node->element;
4650 						if ( checkItem && isItemMixable(checkItem) )
4651 						{
4652 							return true;
4653 						}
4654 					}
4655 				}
4656 				// did not find mixable item... close GUI
4657 				if ( !experimentingAlchemy )
4658 				{
4659 					messagePlayer(clientnum, language[3337]);
4660 				}
4661 				else
4662 				{
4663 					messagePlayer(clientnum, language[3342]);
4664 				}
4665 				closeGUI();
4666 				return false;
4667 			}
4668 		}
4669 		else if ( !secondaryPotion )
4670 		{
4671 			if ( isItemMixable(item) )
4672 			{
4673 				secondaryPotion = item;
4674 				if ( secondaryPotion && basePotion )
4675 				{
4676 					alchemyCombinePotions();
4677 					basePotion = nullptr;
4678 					secondaryPotion = nullptr;
4679 				}
4680 			}
4681 		}
4682 		return true;
4683 	}
4684 	else if ( guiType == GUI_TYPE_TINKERING )
4685 	{
4686 		if ( isNodeTinkeringCraftableItem(item->node) )
4687 		{
4688 			tinkeringCraftItem(item);
4689 		}
4690 		else if ( isNodeFromPlayerInventory(item->node) )
4691 		{
4692 			if ( tinkeringIsItemRepairable(item, clientnum) && tinkeringFilter == TINKER_FILTER_REPAIRABLE )
4693 			{
4694 				tinkeringRepairItem(item);
4695 			}
4696 			else
4697 			{
4698 				tinkeringSalvageItem(item, false, clientnum);
4699 			}
4700 		}
4701 		return true;
4702 	}
4703 	else if ( guiType == GUI_TYPE_SCRIBING )
4704 	{
4705 		if ( isNodeScribingCraftableItem(item->node) )
4706 		{
4707 			scribingWriteItem(item);
4708 		}
4709 		else if ( isNodeFromPlayerInventory(item->node) )
4710 		{
4711 			if ( item->identified && item->type == SCROLL_BLANK )
4712 			{
4713 				scribingBlankScrollTarget = item;
4714 			}
4715 			else if ( item->identified && itemCategory(item) == SPELLBOOK )
4716 			{
4717 				scribingWriteItem(item);
4718 			}
4719 		}
4720 		return true;
4721 	}
4722 
4723 	return false;
4724 }
4725 
isItemMixable(const Item * item)4726 bool GenericGUIMenu::isItemMixable(const Item* item)
4727 {
4728 	if ( !item )
4729 	{
4730 		return false;
4731 	}
4732 
4733 	if ( itemCategory(item) != POTION )
4734 	{
4735 		return false;
4736 	}
4737 	if ( item->type == POTION_EMPTY )
4738 	{
4739 		return false;
4740 	}
4741 
4742 	if ( players[clientnum] && players[clientnum]->entity )
4743 	{
4744 		if ( players[clientnum]->entity->isBlind() )
4745 		{
4746 			messagePlayer(clientnum, language[892]);
4747 			closeGUI();
4748 			return false; // I can't see!
4749 		}
4750 	}
4751 
4752 
4753 	if ( !experimentingAlchemy && !item->identified )
4754 	{
4755 		if ( !basePotion )
4756 		{
4757 			return false;
4758 		}
4759 		else if ( item != basePotion )
4760 		{
4761 			return true;
4762 		}
4763 		return false;
4764 	}
4765 
4766 	if ( itemIsEquipped(item, clientnum) )
4767 	{
4768 		return false; // don't want to deal with client/server desync problems here.
4769 	}
4770 
4771 	//int skillLVL = 0;
4772 	//if ( stats[clientnum] )
4773 	//{
4774 	//	skillLVL = stats[clientnum]->PROFICIENCIES[PRO_ALCHEMY] / 20; // 0 to 5;
4775 	//}
4776 
4777 	if ( experimentingAlchemy )
4778 	{
4779 		if ( !basePotion )
4780 		{
4781 			return true;
4782 		}
4783 		else if ( !secondaryPotion )
4784 		{
4785 			if ( item != basePotion )
4786 			{
4787 				return true;
4788 			}
4789 		}
4790 	}
4791 
4792 	if ( !basePotion )
4793 	{
4794 		// we're selecting the first potion.
4795 		switch ( item->type )
4796 		{
4797 			case POTION_WATER:
4798 			case POTION_POLYMORPH:
4799 			case POTION_BOOZE:
4800 			case POTION_JUICE:
4801 			case POTION_ACID:
4802 			case POTION_INVISIBILITY:
4803 				if ( clientLearnedAlchemyIngredients.find(item->type) != clientLearnedAlchemyIngredients.end() )
4804 				{
4805 					return true;
4806 				}
4807 				else
4808 				{
4809 					return false;
4810 				}
4811 				break;
4812 			default:
4813 				return false;
4814 				break;
4815 		}
4816 	}
4817 
4818 	if ( !secondaryPotion )
4819 	{
4820 		if ( item == basePotion )
4821 		{
4822 			return false;
4823 		}
4824 
4825 		// we're selecting the second potion.
4826 		switch ( item->type )
4827 		{
4828 			case POTION_WATER:
4829 			case POTION_SICKNESS:
4830 			case POTION_CONFUSION:
4831 			case POTION_CUREAILMENT:
4832 			case POTION_BLINDNESS:
4833 			case POTION_RESTOREMAGIC:
4834 			case POTION_SPEED:
4835 			case POTION_POLYMORPH:
4836 				if ( clientLearnedAlchemyIngredients.find(item->type) != clientLearnedAlchemyIngredients.end() )
4837 				{
4838 					return true;
4839 				}
4840 				else
4841 				{
4842 					return false;
4843 				}
4844 				break;
4845 			default:
4846 				return false;
4847 				break;
4848 		}
4849 	}
4850 
4851 	return false;
4852 }
4853 
alchemyCombinePotions()4854 void GenericGUIMenu::alchemyCombinePotions()
4855 {
4856 	if ( !basePotion || !secondaryPotion )
4857 	{
4858 		return;
4859 	}
4860 
4861 	bool tryDuplicatePotion = false;
4862 	ItemType result = POTION_SICKNESS;
4863 	bool randomResult = false;
4864 	bool explodeSelf = false;
4865 
4866 	switch ( basePotion->type )
4867 	{
4868 		case POTION_WATER:
4869 			if ( secondaryPotion->type == POTION_ACID )
4870 			{
4871 				explodeSelf = true;
4872 			}
4873 			else
4874 			{
4875 				tryDuplicatePotion = true;
4876 			}
4877 			break;
4878 		case POTION_BOOZE:
4879 			switch ( secondaryPotion->type )
4880 			{
4881 				case POTION_SICKNESS:
4882 					result = POTION_CONFUSION;
4883 					break;
4884 				case POTION_CONFUSION:
4885 					result = POTION_ACID;
4886 					break;
4887 				case POTION_CUREAILMENT:
4888 					result = POTION_SPEED;
4889 					break;
4890 				case POTION_BLINDNESS:
4891 					result = POTION_STRENGTH;
4892 					break;
4893 				case POTION_RESTOREMAGIC:
4894 					result = POTION_BLINDNESS;
4895 					break;
4896 				case POTION_SPEED:
4897 					result = POTION_PARALYSIS;
4898 					break;
4899 				case POTION_POLYMORPH:
4900 					randomResult = true;
4901 					break;
4902 				default:
4903 					break;
4904 			}
4905 			break;
4906 		case POTION_JUICE:
4907 			switch ( secondaryPotion->type )
4908 			{
4909 				case POTION_SICKNESS:
4910 					result = POTION_BOOZE;
4911 					break;
4912 				case POTION_CONFUSION:
4913 					result = POTION_BOOZE;
4914 					break;
4915 				case POTION_CUREAILMENT:
4916 					result = POTION_RESTOREMAGIC;
4917 					break;
4918 				case POTION_BLINDNESS:
4919 					result = POTION_CUREAILMENT;
4920 					break;
4921 				case POTION_RESTOREMAGIC:
4922 					result = POTION_HEALING;
4923 					break;
4924 				case POTION_SPEED:
4925 					result = POTION_INVISIBILITY;
4926 					break;
4927 				case POTION_POLYMORPH:
4928 					randomResult = true;
4929 					break;
4930 				default:
4931 					break;
4932 			}
4933 			break;
4934 		case POTION_ACID:
4935 			switch ( secondaryPotion->type )
4936 			{
4937 				case POTION_WATER:
4938 					explodeSelf = true; // oh no. don't do that.
4939 					break;
4940 				case POTION_SICKNESS:
4941 					result = POTION_FIRESTORM;
4942 					break;
4943 				case POTION_CONFUSION:
4944 					result = POTION_JUICE;
4945 					break;
4946 				case POTION_CUREAILMENT:
4947 					result = POTION_FIRESTORM;
4948 					break;
4949 				case POTION_BLINDNESS:
4950 					result = POTION_ICESTORM;
4951 					break;
4952 				case POTION_RESTOREMAGIC:
4953 					result = POTION_ICESTORM;
4954 					break;
4955 				case POTION_SPEED:
4956 					result = POTION_THUNDERSTORM;
4957 					break;
4958 				case POTION_POLYMORPH:
4959 					randomResult = true;
4960 					break;
4961 				default:
4962 					explodeSelf = true;
4963 					break;
4964 			}
4965 			break;
4966 		case POTION_INVISIBILITY:
4967 			switch ( secondaryPotion->type )
4968 			{
4969 				case POTION_SICKNESS:
4970 					result = POTION_BLINDNESS;
4971 					break;
4972 				case POTION_CONFUSION:
4973 					result = POTION_PARALYSIS;
4974 					break;
4975 				case POTION_CUREAILMENT:
4976 					result = POTION_LEVITATION;
4977 					break;
4978 				case POTION_BLINDNESS:
4979 					result = POTION_POLYMORPH;
4980 					break;
4981 				case POTION_RESTOREMAGIC:
4982 					result = POTION_EXTRAHEALING;
4983 					break;
4984 				case POTION_SPEED:
4985 					result = POTION_RESTOREMAGIC;
4986 					break;
4987 				case POTION_POLYMORPH:
4988 					randomResult = true;
4989 					break;
4990 				default:
4991 					break;
4992 			}
4993 			break;
4994 		default:
4995 			break;
4996 	}
4997 
4998 	if ( result == POTION_SICKNESS ) // didn't get a result, try flip the potion order
4999 	{
5000 		switch ( secondaryPotion->type )
5001 		{
5002 			case POTION_WATER:
5003 				if ( basePotion->type == POTION_ACID )
5004 				{
5005 					explodeSelf = true;
5006 				}
5007 				else
5008 				{
5009 					tryDuplicatePotion = true;
5010 				}
5011 				break;
5012 			case POTION_BOOZE:
5013 				switch ( basePotion->type )
5014 				{
5015 					case POTION_SICKNESS:
5016 						result = POTION_CONFUSION;
5017 						break;
5018 					case POTION_CONFUSION:
5019 						result = POTION_ACID;
5020 						break;
5021 					case POTION_CUREAILMENT:
5022 						result = POTION_SPEED;
5023 						break;
5024 					case POTION_BLINDNESS:
5025 						result = POTION_STRENGTH;
5026 						break;
5027 					case POTION_RESTOREMAGIC:
5028 						result = POTION_BLINDNESS;
5029 						break;
5030 					case POTION_SPEED:
5031 						result = POTION_PARALYSIS;
5032 						break;
5033 					case POTION_POLYMORPH:
5034 						randomResult = true;
5035 						break;
5036 					default:
5037 						break;
5038 				}
5039 				break;
5040 			case POTION_JUICE:
5041 				switch ( basePotion->type )
5042 				{
5043 					case POTION_SICKNESS:
5044 						result = POTION_BOOZE;
5045 						break;
5046 					case POTION_CONFUSION:
5047 						result = POTION_BOOZE;
5048 						break;
5049 					case POTION_CUREAILMENT:
5050 						result = POTION_RESTOREMAGIC;
5051 						break;
5052 					case POTION_BLINDNESS:
5053 						result = POTION_CUREAILMENT;
5054 						break;
5055 					case POTION_RESTOREMAGIC:
5056 						result = POTION_HEALING;
5057 						break;
5058 					case POTION_SPEED:
5059 						result = POTION_INVISIBILITY;
5060 						break;
5061 					case POTION_POLYMORPH:
5062 						randomResult = true;
5063 						break;
5064 					default:
5065 						break;
5066 				}
5067 				break;
5068 			case POTION_ACID:
5069 				switch ( basePotion->type )
5070 				{
5071 					case POTION_WATER:
5072 						explodeSelf = true; // oh no. don't do that.
5073 						break;
5074 					case POTION_SICKNESS:
5075 						result = POTION_FIRESTORM;
5076 						break;
5077 					case POTION_CONFUSION:
5078 						result = POTION_JUICE;
5079 						break;
5080 					case POTION_CUREAILMENT:
5081 						result = POTION_FIRESTORM;
5082 						break;
5083 					case POTION_BLINDNESS:
5084 						result = POTION_ICESTORM;
5085 						break;
5086 					case POTION_RESTOREMAGIC:
5087 						result = POTION_ICESTORM;
5088 						break;
5089 					case POTION_SPEED:
5090 						result = POTION_THUNDERSTORM;
5091 						break;
5092 					case POTION_POLYMORPH:
5093 						randomResult = true;
5094 						break;
5095 					default:
5096 						explodeSelf = true;
5097 						break;
5098 				}
5099 				break;
5100 			case POTION_INVISIBILITY:
5101 				switch ( basePotion->type )
5102 				{
5103 					case POTION_SICKNESS:
5104 						result = POTION_BLINDNESS;
5105 						break;
5106 					case POTION_CONFUSION:
5107 						result = POTION_PARALYSIS;
5108 						break;
5109 					case POTION_CUREAILMENT:
5110 						result = POTION_LEVITATION;
5111 						break;
5112 					case POTION_BLINDNESS:
5113 						result = POTION_POLYMORPH;
5114 						break;
5115 					case POTION_RESTOREMAGIC:
5116 						result = POTION_EXTRAHEALING;
5117 						break;
5118 					case POTION_SPEED:
5119 						result = POTION_RESTOREMAGIC;
5120 						break;
5121 					case POTION_POLYMORPH:
5122 						randomResult = true;
5123 						break;
5124 					default:
5125 						break;
5126 				}
5127 				break;
5128 			default:
5129 				break;
5130 		}
5131 	}
5132 
5133 	if ( basePotion->type == POTION_POLYMORPH || secondaryPotion->type == POTION_POLYMORPH )
5134 	{
5135 		randomResult = true;
5136 	}
5137 
5138 	int skillLVL = 0;
5139 	if ( stats[clientnum] )
5140 	{
5141 		skillLVL = stats[clientnum]->PROFICIENCIES[PRO_ALCHEMY] / 20; // 0 to 5;
5142 	}
5143 
5144 	Status status = SERVICABLE;
5145 	bool duplicateSucceed = false;
5146 	if ( tryDuplicatePotion && !explodeSelf && !randomResult )
5147 	{
5148 		// do duplicate.
5149 		if ( rand() % 100 < (50 + skillLVL * 10) ) // 50 - 100% chance
5150 		{
5151 			duplicateSucceed = true;
5152 			if ( basePotion->type == POTION_WATER )
5153 			{
5154 				result = secondaryPotion->type;
5155 				status = secondaryPotion->status;
5156 			}
5157 			else if ( secondaryPotion->type == POTION_WATER )
5158 			{
5159 				result = basePotion->type;
5160 				status = basePotion->status;
5161 			}
5162 			else
5163 			{
5164 				result = POTION_WATER;
5165 			}
5166 		}
5167 		else
5168 		{
5169 			result = POTION_WATER;
5170 		}
5171 	}
5172 
5173 	if ( randomResult )
5174 	{
5175 		std::vector<int> potionChances =
5176 		{
5177 			0,	//POTION_WATER,
5178 			1,	//POTION_BOOZE,
5179 			1,	//POTION_JUICE,
5180 			1,	//POTION_SICKNESS,
5181 			1,	//POTION_CONFUSION,
5182 			1,	//POTION_EXTRAHEALING,
5183 			1,	//POTION_HEALING,
5184 			1,	//POTION_CUREAILMENT,
5185 			1,	//POTION_BLINDNESS,
5186 			1,	//POTION_RESTOREMAGIC,
5187 			1,	//POTION_INVISIBILITY,
5188 			1,	//POTION_LEVITATION,
5189 			1,	//POTION_SPEED,
5190 			1,	//POTION_ACID,
5191 			1,	//POTION_PARALYSIS,
5192 			0,	//POTION_POLYMORPH
5193 			0,	//POTION_FIRESTORM
5194 			0,	//POTION_ICESTORM
5195 			0	//POTION_THUNDERSTORM
5196 		};
5197 		std::discrete_distribution<> potionDistribution(potionChances.begin(), potionChances.end());
5198 		auto generatedPotion = potionStandardAppearanceMap.at(potionDistribution(fountainSeed));
5199 		result = static_cast<ItemType>(generatedPotion.first);
5200 	}
5201 
5202 	if ( basePotion->identified && secondaryPotion->identified )
5203 	{
5204 		messagePlayerColor(clientnum, uint32ColorWhite(*mainsurface), language[3332],
5205 			items[basePotion->type].name_identified, items[secondaryPotion->type].name_identified);
5206 	}
5207 	else if ( basePotion->identified )
5208 	{
5209 		messagePlayerColor(clientnum, uint32ColorWhite(*mainsurface), language[3334],
5210 			items[basePotion->type].name_identified);
5211 	}
5212 	else if ( secondaryPotion->identified )
5213 	{
5214 		messagePlayerColor(clientnum, uint32ColorWhite(*mainsurface), language[3333],
5215 			items[secondaryPotion->type].name_identified);
5216 	}
5217 	else
5218 	{
5219 		messagePlayerColor(clientnum, uint32ColorWhite(*mainsurface), language[3335]);
5220 	}
5221 
5222 	if ( !explodeSelf && result != POTION_SICKNESS && !tryDuplicatePotion )
5223 	{
5224 		if ( !(alchemyLearnRecipe(basePotion->type, true)) )
5225 		{
5226 			if ( secondaryPotion->identified )
5227 			{
5228 				alchemyLearnRecipe(secondaryPotion->type, true);
5229 			}
5230 		}
5231 	}
5232 
5233 	bool degradeAlembic = false;
5234 	if ( explodeSelf )
5235 	{
5236 		degradeAlembic = true;
5237 	}
5238 	else
5239 	{
5240 		if ( basePotion->type == POTION_ACID || secondaryPotion->type == POTION_ACID )
5241 		{
5242 			if ( rand() % 5 == 0 )
5243 			{
5244 				degradeAlembic = true;
5245 			}
5246 		}
5247 		else
5248 		{
5249 			if ( rand() % 20 == 0 )
5250 			{
5251 				degradeAlembic = true;
5252 			}
5253 		}
5254 	}
5255 
5256 	int appearance = 0;
5257 	int blessing = std::min(static_cast<int>(std::min(basePotion->beatitude, secondaryPotion->beatitude)), 0);
5258 	if ( basePotion->type == secondaryPotion->type )
5259 	{
5260 		// same potion, keep the second potion only.
5261 		result = secondaryPotion->type;
5262 		blessing = secondaryPotion->beatitude;
5263 		appearance = secondaryPotion->appearance;
5264 	}
5265 
5266 	bool knewBothBaseIngredients = false;
5267 	if ( clientLearnedAlchemyIngredients.find(basePotion->type) != clientLearnedAlchemyIngredients.end() )
5268 	{
5269 		if ( clientLearnedAlchemyIngredients.find(secondaryPotion->type) != clientLearnedAlchemyIngredients.end() )
5270 		{
5271 			// knew about both combinations.
5272 			if ( !tryDuplicatePotion && !explodeSelf && result != POTION_SICKNESS )
5273 			{
5274 				if ( skillLVL >= 3 )
5275 				{
5276 					knewBothBaseIngredients = true; // auto ID the new potion.
5277 				}
5278 			}
5279 		}
5280 	}
5281 
5282 	Item* duplicatedPotion = nullptr;
5283 
5284 	if ( duplicateSucceed )
5285 	{
5286 		if ( basePotion->type == POTION_WATER )
5287 		{
5288 			consumeItem(basePotion, clientnum);
5289 			duplicatedPotion = secondaryPotion;
5290 		}
5291 		else if ( secondaryPotion->type == POTION_WATER )
5292 		{
5293 			consumeItem(secondaryPotion, clientnum);
5294 			duplicatedPotion = basePotion;
5295 		}
5296 	}
5297 	else
5298 	{
5299 		consumeItem(basePotion, clientnum);
5300 		consumeItem(secondaryPotion, clientnum);
5301 		if ( rand() % 100 < (50 + skillLVL * 5) ) // 50 - 75% chance
5302 		{
5303 			Item* emptyBottle = newItem(POTION_EMPTY, SERVICABLE, 0, 1, 0, true, nullptr);
5304 			itemPickup(clientnum, emptyBottle);
5305 			messagePlayer(clientnum, language[3351], items[POTION_EMPTY].name_identified);
5306 			free(emptyBottle);
5307 		}
5308 	}
5309 
5310 	if ( alembicItem )
5311 	{
5312 		if ( alembicItem->beatitude >= 1 )
5313 		{
5314 			blessing = 1;
5315 		}
5316 		else if ( alembicItem->beatitude <= -1 )
5317 		{
5318 			blessing = alembicItem->beatitude;
5319 		}
5320 	}
5321 
5322 	if ( skillCapstoneUnlocked(clientnum, PRO_ALCHEMY) )
5323 	{
5324 		blessing = 2;
5325 		degradeAlembic = false;
5326 	}
5327 
5328 	if ( degradeAlembic && alembicItem )
5329 	{
5330 		alembicItem->status = static_cast<Status>(alembicItem->status - 1);
5331 		if ( alembicItem->status > BROKEN )
5332 		{
5333 			messagePlayer(clientnum, language[681], alembicItem->getName());
5334 		}
5335 		else
5336 		{
5337 			messagePlayer(clientnum, language[2351], alembicItem->getName());
5338 			playSoundPlayer(clientnum, 162, 64);
5339 			consumeItem(alembicItem, clientnum);
5340 			alembicItem = nullptr;
5341 		}
5342 	}
5343 
5344 	if ( explodeSelf && players[clientnum] && players[clientnum]->entity )
5345 	{
5346 		// hurt.
5347 		if ( multiplayer == CLIENT )
5348 		{
5349 			strcpy((char*)net_packet->data, "BOOM");
5350 			net_packet->data[4] = clientnum;
5351 			net_packet->address.host = net_server.host;
5352 			net_packet->address.port = net_server.port;
5353 			net_packet->len = 5;
5354 			sendPacketSafe(net_sock, -1, net_packet, 0);
5355 		}
5356 		else
5357 		{
5358 			spawnMagicTower(nullptr, players[clientnum]->entity->x, players[clientnum]->entity->y, SPELL_FIREBALL, nullptr);
5359 			players[clientnum]->entity->setObituary(language[3350]);
5360 		}
5361 		closeGUI();
5362 		return;
5363 	}
5364 
5365 	for ( auto it = potionStandardAppearanceMap.begin(); it != potionStandardAppearanceMap.end(); ++it )
5366 	{
5367 		if ( (*it).first == result )
5368 		{
5369 			if ( appearance == 0 )
5370 			{
5371 				appearance = (*it).second;
5372 			}
5373 			bool raiseSkill = true;
5374 			if ( result == POTION_SICKNESS )
5375 			{
5376 				appearance = 0 + rand() % 3;
5377 				if ( rand() % 10 > 0 )
5378 				{
5379 					raiseSkill = false;
5380 				}
5381 			}
5382 			else if ( result == POTION_WATER )
5383 			{
5384 				raiseSkill = false;
5385 			}
5386 			else if ( duplicateSucceed )
5387 			{
5388 				if ( rand() % 10 > 0 )
5389 				{
5390 					raiseSkill = false;
5391 				}
5392 			}
5393 
5394 			Item* newPotion = newItem(result, status, blessing, 1, appearance, knewBothBaseIngredients, nullptr);
5395 			if ( tryDuplicatePotion )
5396 			{
5397 				if ( result == POTION_WATER && !duplicateSucceed )
5398 				{
5399 					messagePlayer(clientnum, language[3356]);
5400 					newPotion->identified = true;
5401 				}
5402 				else
5403 				{
5404 					if ( duplicatedPotion )
5405 					{
5406 						newPotion->appearance = duplicatedPotion->appearance;
5407 						newPotion->beatitude = duplicatedPotion->beatitude;
5408 						newPotion->identified = duplicatedPotion->identified;
5409 						newPotion->status = duplicatedPotion->status;
5410 					}
5411 					messagePlayer(clientnum, language[3352], newPotion->description());
5412 				}
5413 			}
5414 			else
5415 			{
5416 				messagePlayer(clientnum, language[3352], newPotion->description());
5417 				steamStatisticUpdate(STEAM_STAT_IN_THE_MIX, STEAM_STAT_INT, 1);
5418 			}
5419 			itemPickup(clientnum, newPotion);
5420 			free(newPotion);
5421 			if ( players[clientnum] && players[clientnum]->entity )
5422 			{
5423 				playSoundEntityLocal(players[clientnum]->entity, 401, 64);
5424 			}
5425 			if ( raiseSkill && rand() % 2 == 0 )
5426 			{
5427 				if ( multiplayer == CLIENT )
5428 				{
5429 					// request level up
5430 					strcpy((char*)net_packet->data, "CSKL");
5431 					net_packet->data[4] = clientnum;
5432 					net_packet->data[5] = PRO_ALCHEMY;
5433 					net_packet->address.host = net_server.host;
5434 					net_packet->address.port = net_server.port;
5435 					net_packet->len = 6;
5436 					sendPacketSafe(net_sock, -1, net_packet, 0);
5437 				}
5438 				else
5439 				{
5440 					if ( players[clientnum] && players[clientnum]->entity )
5441 					{
5442 						players[clientnum]->entity->increaseSkill(PRO_ALCHEMY);
5443 					}
5444 				}
5445 			}
5446 			break;
5447 		}
5448 	}
5449 	closeGUI();
5450 }
5451 
alchemyLearnRecipe(int type,bool increaseskill,bool notify)5452 bool GenericGUIMenu::alchemyLearnRecipe(int type, bool increaseskill, bool notify)
5453 {
5454 	int index = 0;
5455 	for ( auto it = potionStandardAppearanceMap.begin(); it != potionStandardAppearanceMap.end(); ++it )
5456 	{
5457 		// loop through to get the index number to insert into gameStatistics[STATISTICS_ALCHEMY_RECIPES]
5458 		if ( (*it).first == type )
5459 		{
5460 			if ( clientLearnedAlchemyIngredients.find(type) == clientLearnedAlchemyIngredients.end() )
5461 			{
5462 				// new recipe!
5463 				clientLearnedAlchemyIngredients.insert(type);
5464 				Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
5465 				if ( notify )
5466 				{
5467 					if ( isItemBaseIngredient(type) )
5468 					{
5469 						messagePlayerColor(clientnum, color, language[3346], items[type].name_identified);
5470 					}
5471 					else if ( isItemSecondaryIngredient(type) )
5472 					{
5473 						messagePlayerColor(clientnum, color, language[3349], items[type].name_identified);
5474 					}
5475 				}
5476 				if ( increaseskill )
5477 				{
5478 					if ( multiplayer == CLIENT )
5479 					{
5480 						// request level up
5481 						strcpy((char*)net_packet->data, "CSKL");
5482 						net_packet->data[4] = clientnum;
5483 						net_packet->data[5] = PRO_ALCHEMY;
5484 						net_packet->address.host = net_server.host;
5485 						net_packet->address.port = net_server.port;
5486 						net_packet->len = 6;
5487 						sendPacketSafe(net_sock, -1, net_packet, 0);
5488 					}
5489 					else
5490 					{
5491 						if ( players[clientnum] && players[clientnum]->entity )
5492 						{
5493 							players[clientnum]->entity->increaseSkill(PRO_ALCHEMY);
5494 						}
5495 					}
5496 				}
5497 				gameStatistics[STATISTICS_ALCHEMY_RECIPES] |= (1 << index);
5498 				return true;
5499 			}
5500 			else
5501 			{
5502 				// store the potion index into here for game saves, just in case we don't have it set the element in anyway.
5503 				gameStatistics[STATISTICS_ALCHEMY_RECIPES] |= (1 << index);
5504 				if ( increaseskill && rand() % 6 == 0 )
5505 				{
5506 					if ( multiplayer == CLIENT )
5507 					{
5508 						// request level up
5509 						strcpy((char*)net_packet->data, "CSKL");
5510 						net_packet->data[4] = clientnum;
5511 						net_packet->data[5] = PRO_ALCHEMY;
5512 						net_packet->address.host = net_server.host;
5513 						net_packet->address.port = net_server.port;
5514 						net_packet->len = 6;
5515 						sendPacketSafe(net_sock, -1, net_packet, 0);
5516 					}
5517 					else
5518 					{
5519 						if ( players[clientnum] && players[clientnum]->entity )
5520 						{
5521 							players[clientnum]->entity->increaseSkill(PRO_ALCHEMY);
5522 						}
5523 					}
5524 				}
5525 			}
5526 			break;
5527 		}
5528 		++index;
5529 	}
5530 	return false;
5531 }
5532 
isItemBaseIngredient(int type)5533 bool GenericGUIMenu::isItemBaseIngredient(int type)
5534 {
5535 	switch ( type )
5536 	{
5537 		case POTION_WATER:
5538 		case POTION_POLYMORPH:
5539 		case POTION_BOOZE:
5540 		case POTION_JUICE:
5541 		case POTION_ACID:
5542 		case POTION_INVISIBILITY:
5543 			return true;
5544 		default:
5545 			return false;
5546 			break;
5547 	}
5548 	return false;
5549 }
5550 
isItemSecondaryIngredient(int type)5551 bool GenericGUIMenu::isItemSecondaryIngredient(int type)
5552 {
5553 	switch ( type )
5554 	{
5555 		case POTION_WATER:
5556 		case POTION_SICKNESS:
5557 		case POTION_CONFUSION:
5558 		case POTION_CUREAILMENT:
5559 		case POTION_BLINDNESS:
5560 		case POTION_RESTOREMAGIC:
5561 		case POTION_SPEED:
5562 		case POTION_POLYMORPH:
5563 			return true;
5564 		default:
5565 			return false;
5566 			break;
5567 	}
5568 	return false;
5569 }
5570 
alchemyLearnRecipeOnLevelUp(int skill)5571 void GenericGUIMenu::alchemyLearnRecipeOnLevelUp(int skill)
5572 {
5573 	bool learned = false;
5574 	if ( skill > 0 )
5575 	{
5576 		ItemType potion = POTION_WATER;
5577 		learned = GenericGUI.alchemyLearnRecipe(potion, false);
5578 	}
5579 
5580 	if ( skill == 20 )
5581 	{
5582 		ItemType potion = POTION_JUICE;
5583 		learned = GenericGUI.alchemyLearnRecipe(potion, false);
5584 		potion = POTION_BOOZE;
5585 		learned = GenericGUI.alchemyLearnRecipe(potion, false);
5586 	}
5587 	else if ( skill == 40 )
5588 	{
5589 		ItemType potion = POTION_ACID;
5590 		learned = GenericGUI.alchemyLearnRecipe(potion, false);
5591 	}
5592 	else if ( skill == 60 )
5593 	{
5594 		ItemType potion = POTION_INVISIBILITY;
5595 		learned = GenericGUI.alchemyLearnRecipe(potion, false);
5596 		potion = POTION_POLYMORPH;
5597 		learned = GenericGUI.alchemyLearnRecipe(potion, false);
5598 	}
5599 
5600 	if ( !learned && skill % 5 == 0 )
5601 	{
5602 		ItemType potion = itemLevelCurve(POTION, 0, currentlevel);
5603 		GenericGUI.alchemyLearnRecipe(potion, false);
5604 	}
5605 }
5606 
tinkeringCreateCraftableItemList()5607 void GenericGUIMenu::tinkeringCreateCraftableItemList()
5608 {
5609 	tinkeringFreeLists();
5610 	/*Item* tempItem = newItem(TOOL_BOMB, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, nullptr);
5611 	if ( tinkeringPlayerCanAffordCraft(tempItem) )
5612 	{
5613 	}*/
5614 	std::vector<Item*> items;
5615 	items.push_back(newItem(TOOL_BOMB, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5616 	items.push_back(newItem(TOOL_FREEZE_BOMB, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5617 	items.push_back(newItem(TOOL_SLEEP_BOMB, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5618 	items.push_back(newItem(TOOL_TELEPORT_BOMB, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5619 	items.push_back(newItem(TOOL_DUMMYBOT, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5620 	items.push_back(newItem(TOOL_DECOY, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5621 	items.push_back(newItem(TOOL_GYROBOT, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5622 	items.push_back(newItem(TOOL_SENTRYBOT, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5623 	items.push_back(newItem(TOOL_SPELLBOT, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5624 	items.push_back(newItem(TOOL_BEARTRAP, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5625 	items.push_back(newItem(CLOAK_BACKPACK, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5626 	items.push_back(newItem(TOOL_ALEMBIC, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5627 	items.push_back(newItem(TOOL_LOCKPICK, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5628 	items.push_back(newItem(TOOL_GLASSES, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5629 	items.push_back(newItem(TOOL_LANTERN, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5630 	items.push_back(newItem(POTION_EMPTY, SERVICABLE, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems));
5631 	for ( auto it = items.begin(); it != items.end(); ++it )
5632 	{
5633 		Item* item = *it;
5634 		if ( item )
5635 		{
5636 			int skillLVL = 0;
5637 			int requiredSkill = tinkeringPlayerHasSkillLVLToCraft(item);
5638 			if ( stats[clientnum] && players[clientnum] )
5639 			{
5640 				skillLVL = (stats[clientnum]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[clientnum], players[clientnum]->entity)) / 20; // 0 to 5
5641 				skillLVL = std::min(skillLVL, 5);
5642 			}
5643 			if ( item->type == TOOL_DUMMYBOT || item->type == TOOL_SENTRYBOT
5644 				|| item->type == TOOL_SPELLBOT || item->type == TOOL_GYROBOT )
5645 			{
5646 				if ( skillLVL >= 5 ) // maximum
5647 				{
5648 					item->status = EXCELLENT;
5649 				}
5650 				else if ( requiredSkill >= skillLVL || requiredSkill == -1 )
5651 				{
5652 					item->status = DECREPIT;
5653 				}
5654 				else
5655 				{
5656 					if ( skillLVL - requiredSkill == 1 )
5657 					{
5658 						item->status = WORN;
5659 					}
5660 					else if ( skillLVL - requiredSkill == 2 )
5661 					{
5662 						item->status = SERVICABLE;
5663 					}
5664 					else if ( skillLVL - requiredSkill >= 3 )
5665 					{
5666 						item->status = EXCELLENT;
5667 					}
5668 				}
5669 			}
5670 		}
5671 	}
5672 	//newItem(TOOL_BEARTRAP, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems);
5673 	//newItem(TOOL_DETONATOR_CHARGE, EXCELLENT, 0, 1, ITEM_TINKERING_APPEARANCE, true, &tinkeringTotalItems);
5674 
5675 	//messagePlayer(clientnum, "asserting craftable num items: %d", list_Size(&tinkeringTotalItems));
5676 	if ( stats[clientnum] )
5677 	{
5678 		// make the last node jump to the player's actual items,
5679 		// so consuming the items in this list will actually update the player's inventory.
5680 		node_t* tinkeringTotalLastCraftableNode = tinkeringTotalItems.last;
5681 		if ( tinkeringTotalLastCraftableNode )
5682 		{
5683 			tinkeringTotalLastCraftableNode->next = stats[clientnum]->inventory.first;
5684 		}
5685 		//messagePlayer(clientnum, "asserting total list size: %d", list_Size(&tinkeringTotalItems));
5686 	}
5687 }
5688 
tinkeringFreeLists()5689 void GenericGUIMenu::tinkeringFreeLists()
5690 {
5691 	node_t* nextnode = nullptr;
5692 	int itemcnt = 0;
5693 
5694 	// totalItems is a unique list, contains unique craftable data,
5695 	// as well as a pointer to continue to the player's inventory
5696 	for ( node_t* node = tinkeringTotalItems.first; node; node = nextnode )
5697 	{
5698 		nextnode = node->next;
5699 		if ( node->list == &tinkeringTotalItems )
5700 		{
5701 			list_RemoveNode(node);
5702 			++itemcnt;
5703 		}
5704 		else if ( node->list == &stats[clientnum]->inventory )
5705 		{
5706 			//messagePlayer(clientnum, "reached inventory after clearing %d items", itemcnt);
5707 			break;
5708 		}
5709 	}
5710 	tinkeringMetalScrap.clear();
5711 	tinkeringMagicScrap.clear();
5712 	tinkeringTotalItems.first = nullptr;
5713 	tinkeringTotalItems.last = nullptr;
5714 	tinkeringTotalLastCraftableNode = nullptr;
5715 }
5716 
tinkeringCraftItem(Item * item)5717 bool GenericGUIMenu::tinkeringCraftItem(Item* item)
5718 {
5719 	if ( !item )
5720 	{
5721 		return false;
5722 	}
5723 
5724 	// add checks/consuming of items here.
5725 	if ( tinkeringPlayerHasSkillLVLToCraft(item) == -1 )
5726 	{
5727 		playSound(90, 64);
5728 		messagePlayer(clientnum, language[3652], items[item->type].name_identified);
5729 		return false;
5730 	}
5731 	if ( !tinkeringPlayerCanAffordCraft(item) )
5732 	{
5733 		playSound(90, 64);
5734 		messagePlayer(clientnum, language[3648], items[item->type].name_identified);
5735 		return false;
5736 	}
5737 
5738 	Item* crafted = tinkeringCraftItemAndConsumeMaterials(item);
5739 	if ( crafted )
5740 	{
5741 		Item* pickedUp = itemPickup(clientnum, crafted);
5742 		messagePlayer(clientnum, language[3668], crafted->description());
5743 		free(crafted);
5744 		return true;
5745 	}
5746 	return false;
5747 }
5748 
tinkeringSalvageItem(Item * item,bool outsideInventory,int player)5749 bool GenericGUIMenu::tinkeringSalvageItem(Item* item, bool outsideInventory, int player)
5750 {
5751 	if ( !item )
5752 	{
5753 		return false;
5754 	}
5755 
5756 	if ( !outsideInventory && itemIsEquipped(item, player) )
5757 	{
5758 		messagePlayer(player, language[3669]);
5759 		return false; // don't want to deal with client/server desync problems here.
5760 	}
5761 
5762 	// add checks/consuming of items here.
5763 	int metal = 0;
5764 	int magic = 0;
5765 	tinkeringGetItemValue(item, &metal, &magic);
5766 	bool didCraft = false;
5767 	int skillLVL = 0;
5768 	int bonusMetalScrap = 0;
5769 	int bonusMagicScrap = 0;
5770 	if ( stats[player] && players[player] && item->status > BROKEN )
5771 	{
5772    		skillLVL = (stats[player]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[player], players[player]->entity)) / 20;
5773 		skillLVL = std::min(skillLVL, 5);
5774 		switch ( skillLVL )
5775 		{
5776 			case 5:
5777 				bonusMetalScrap = (1 + (rand() % 2 == 0) ? 1 : 0) * metal; // 2x or 50% 3x extra scrap
5778 				bonusMagicScrap = (1 + (rand() % 2 == 0) ? 1 : 0) * magic; // 2x or 50% 3x extra scrap
5779 				break;
5780 			case 4:
5781 				bonusMetalScrap = (1 + (rand() % 4 == 0) ? 1 : 0) * metal; // 2x or 25% 3x extra scrap
5782 				bonusMagicScrap = (1 + (rand() % 4 == 0) ? 1 : 0) * magic; // 2x or 25% 3x extra scrap
5783 				break;
5784 			case 3:
5785 				bonusMetalScrap = ((rand() % 2 == 0) ? 1 : 0) * metal; // 50% 2x scrap value
5786 				bonusMagicScrap = ((rand() % 2 == 0) ? 1 : 0) * magic; // 50% 2x scrap value
5787 				break;
5788 			case 2:
5789 				bonusMetalScrap = ((rand() % 4 == 0) ? 1 : 0) * metal; // 25% 2x scrap value
5790 				bonusMagicScrap = ((rand() % 4 == 0) ? 1 : 0) * magic; // 25% 2x scrap value
5791 				break;
5792 			case 1:
5793 				bonusMetalScrap = ((rand() % 8 == 0) ? 1 : 0) * metal; // 12.5% 2x scrap value
5794 				bonusMagicScrap = ((rand() % 8 == 0) ? 1 : 0) * magic; // 12.5% 2x scrap value
5795 				break;
5796 			default:
5797 				break;
5798 		}
5799 	}
5800 	if ( metal > 0 )
5801 	{
5802 		metal += bonusMetalScrap;
5803 	}
5804 	if ( magic > 0 )
5805 	{
5806 		magic += bonusMagicScrap;
5807 	}
5808 	if ( metal > 0 )
5809 	{
5810 		Item* crafted = newItem(TOOL_METAL_SCRAP, DECREPIT, 0, metal, 0, true, nullptr);
5811 		if ( crafted )
5812 		{
5813 			Item* pickedUp = itemPickup(player, crafted);
5814 			if ( bonusMetalScrap > 0 )
5815 			{
5816 				Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
5817 				messagePlayerColor(player, color, language[3665], metal, items[pickedUp->type].name_identified);
5818 			}
5819 			else
5820 			{
5821 				messagePlayer(player, language[3665], metal, items[pickedUp->type].name_identified);
5822 			}
5823 			free(crafted); // if player != clientnum, then crafted == pickedUp
5824 			didCraft = true;
5825 		}
5826 	}
5827 	if ( magic > 0 )
5828 	{
5829 		Item* crafted = newItem(TOOL_MAGIC_SCRAP, DECREPIT, 0, magic, 0, true, nullptr);
5830 		if ( crafted )
5831 		{
5832 			Item* pickedUp = itemPickup(player, crafted);
5833 			if ( bonusMagicScrap > 0 )
5834 			{
5835 				Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
5836 				messagePlayerColor(player, color, language[3665], magic, items[pickedUp->type].name_identified);
5837 			}
5838 			else
5839 			{
5840 				messagePlayer(player, language[3665], magic, items[pickedUp->type].name_identified);
5841 			}
5842 			free(crafted); // if player != clientnum, then crafted == pickedUp
5843 			didCraft = true;
5844 		}
5845 	}
5846 
5847 	bool increaseSkill = false;
5848 	if ( stats[player] && didCraft )
5849 	{
5850 		if ( metal >= 4 || magic >= 4 )
5851 		{
5852 			if ( rand() % 2 == 0 ) // 50%
5853 			{
5854 				if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] < SKILL_LEVEL_EXPERT )
5855 				{
5856 					increaseSkill = true;
5857 				}
5858 				else if ( rand() % 20 == 0 )
5859 				{
5860 					messagePlayer(player, language[3666]); // nothing left to learn from salvaging.
5861 				}
5862 			}
5863 		}
5864 		else if ( metal >= 2 || magic >= 2 )
5865 		{
5866 			if ( rand() % 5 == 0 ) // 20%
5867 			{
5868 				if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] < SKILL_LEVEL_EXPERT )
5869 				{
5870 					increaseSkill = true;
5871 				}
5872 				else if ( rand() % 20 == 0 )
5873 				{
5874 					messagePlayer(player, language[3666]); // nothing left to learn from salvaging.
5875 				}
5876 			}
5877 		}
5878 
5879 		if ( item->type == TOOL_TORCH || item->type == TOOL_CRYSTALSHARD )
5880 		{
5881 			achievementObserver.playerAchievements[player].torchererScrap += (magic + metal);
5882 		}
5883 		else if ( item->type == TOOL_SENTRYBOT || item->type == TOOL_SPELLBOT
5884 			|| item->type == TOOL_GYROBOT || item->type == TOOL_DUMMYBOT
5885 			|| item->type == TOOL_DETONATOR_CHARGE )
5886 		{
5887 			if ( item->status == BROKEN )
5888 			{
5889 				achievementObserver.playerAchievements[player].fixerUpper += 1;
5890 			}
5891 		}
5892 
5893 		achievementObserver.playerAchievements[player].superShredder += (magic + metal);
5894 
5895 		if ( players[player] && players[player]->entity )
5896 		{
5897 			if ( (ticks - tinkeringSfxLastTicks) > 200 && ((metal >= 4 || magic >= 4) || rand() % 5 == 0) )
5898 			{
5899 				tinkeringSfxLastTicks = ticks;
5900 				playSoundEntity(players[player]->entity, 421 + (rand() % 2) * 3, 64);
5901 			}
5902 			else
5903 			{
5904 				if ( rand() % 4 == 0 )
5905 				{
5906 					playSoundEntity(players[player]->entity, 35 + rand() % 3, 64);
5907 				}
5908 				else
5909 				{
5910 					playSoundEntity(players[player]->entity, 462 + rand() % 2, 64);
5911 				}
5912 			}
5913 		}
5914 	}
5915 
5916 	if ( increaseSkill )
5917 	{
5918 		if ( player != clientnum ) // server initiated craft for client.
5919 		{
5920 			if ( players[player] && players[player]->entity )
5921 			{
5922 				players[player]->entity->increaseSkill(PRO_LOCKPICKING);
5923 			}
5924 		}
5925 		else if ( player == clientnum ) // client/server initiated craft for self.
5926 		{
5927 			if ( multiplayer == CLIENT )
5928 			{
5929 				// request level up
5930 				strcpy((char*)net_packet->data, "CSKL");
5931 				net_packet->data[4] = clientnum;
5932 				net_packet->data[5] = PRO_LOCKPICKING;
5933 				net_packet->address.host = net_server.host;
5934 				net_packet->address.port = net_server.port;
5935 				net_packet->len = 6;
5936 				sendPacketSafe(net_sock, -1, net_packet, 0);
5937 			}
5938 			else
5939 			{
5940 				if ( players[clientnum] && players[clientnum]->entity )
5941 				{
5942 					players[clientnum]->entity->increaseSkill(PRO_LOCKPICKING);
5943 				}
5944 			}
5945 		}
5946 	}
5947 
5948 	if ( !outsideInventory && didCraft )
5949 	{
5950 		consumeItem(item, player);
5951 	}
5952 	return true;
5953 }
5954 
isNodeFromPlayerInventory(node_t * node)5955 bool GenericGUIMenu::isNodeFromPlayerInventory(node_t* node)
5956 {
5957 	if ( stats[clientnum] && node )
5958 	{
5959 		return (node->list == &stats[clientnum]->inventory);
5960 	}
5961 	return false;
5962 }
5963 
isItemSalvageable(const Item * item,int player)5964 bool GenericGUIMenu::isItemSalvageable(const Item* item, int player)
5965 {
5966 	if ( !item )
5967 	{
5968 		return false;
5969 	}
5970 	/*if ( itemIsConsumableByAutomaton(*item) )
5971 	{
5972 		return false;
5973 	}*/
5974 	if ( player == clientnum && isNodeFromPlayerInventory(item->node) && itemIsEquipped(item, clientnum) )
5975 	{
5976 		return false;
5977 	}
5978 	if ( item == tinkeringKitItem )
5979 	{
5980 		return false;
5981 	}
5982 	if ( item->type == TOOL_METAL_SCRAP || item->type == TOOL_MAGIC_SCRAP )
5983 	{
5984 		return false;
5985 	}
5986 
5987 	int metal = 0;
5988 	int magic = 0;
5989 	if ( tinkeringGetItemValue(item, &metal, &magic) )
5990 	{
5991 		return true;
5992 	}
5993 	return false;
5994 }
5995 
tinkeringIsItemRepairable(Item * item,int player)5996 bool GenericGUIMenu::tinkeringIsItemRepairable(Item* item, int player)
5997 {
5998 	if ( !item )
5999 	{
6000 		return false;
6001 	}
6002 	/*if ( player == clientnum && itemIsEquipped(item, clientnum) )
6003 	{
6004 		return false;
6005 	}*/
6006 	/*if ( item == tinkeringKitItem )
6007 	{
6008 		return false;
6009 	}*/
6010 
6011 	int metal = 0;
6012 	int magic = 0;
6013 	if ( tinkeringGetRepairCost(item, &metal, &magic) )
6014 	{
6015 		return true;
6016 	}
6017 	return false;
6018 }
6019 
tinkeringPlayerCanAffordRepair(Item * item)6020 bool GenericGUIMenu::tinkeringPlayerCanAffordRepair(Item* item)
6021 {
6022 	if ( !item )
6023 	{
6024 		return false;
6025 	}
6026 	int metal = 0;
6027 	int magic = 0;
6028 	tinkeringGetRepairCost(item, &metal, &magic);
6029 	if ( metal == 0 && magic == 0 )
6030 	{
6031 		return false;
6032 	}
6033 
6034 	if ( tinkeringPlayerHasMaterialsInventory(metal, magic) )
6035 	{
6036 		return true;
6037 	}
6038 
6039 	return false;
6040 }
6041 
tinkeringPlayerCanAffordCraft(const Item * item)6042 bool GenericGUIMenu::tinkeringPlayerCanAffordCraft(const Item* item)
6043 {
6044 	if ( !item )
6045 	{
6046 		return false;
6047 	}
6048 	int metal = 0;
6049 	int magic = 0;
6050 	tinkeringGetCraftingCost(item, &metal, &magic);
6051 	if ( metal == 0 && magic == 0 )
6052 	{
6053 		return false;
6054 	}
6055 
6056 	if ( tinkeringPlayerHasMaterialsInventory(metal, magic) )
6057 	{
6058 		return true;
6059 	}
6060 
6061 	return false;
6062 }
6063 
tinkeringCraftItemAndConsumeMaterials(const Item * item)6064 Item* GenericGUIMenu::tinkeringCraftItemAndConsumeMaterials(const Item* item)
6065 {
6066 	if ( !item )
6067 	{
6068 		return nullptr;
6069 	}
6070 	int metal = 0;
6071 	int magic = 0;
6072 	tinkeringGetCraftingCost(item, &metal, &magic);
6073 	if ( metal == 0 && magic == 0 )
6074 	{
6075 		return nullptr;
6076 	}
6077 	if ( tinkeringPlayerHasMaterialsInventory(metal, magic) )
6078 	{
6079 		bool increaseSkill = false;
6080 		if ( stats[clientnum] )
6081 		{
6082 			if ( metal > 4 || magic > 4 )
6083 			{
6084 				if ( rand() % 10 == 0 )
6085 				{
6086 					increaseSkill = true;
6087 				}
6088 			}
6089 			else
6090 			{
6091 				if ( metal > 2 || magic > 2 )
6092 				{
6093 					if ( rand() % 20 == 0 )
6094 					{
6095 						increaseSkill = true;
6096 					}
6097 				}
6098 				else
6099 				{
6100 					if ( stats[clientnum]->PROFICIENCIES[PRO_LOCKPICKING] < SKILL_LEVEL_BASIC )
6101 					{
6102 						if ( rand() % 10 == 0 )
6103 						{
6104 							increaseSkill = true;
6105 						}
6106 					}
6107 					else if ( rand() % 20 == 0 )
6108 					{
6109 						messagePlayer(clientnum, language[3667], items[item->type].name_identified);
6110 					}
6111 				}
6112 			}
6113 		}
6114 
6115 		if ( increaseSkill )
6116 		{
6117 			if ( multiplayer == CLIENT )
6118 			{
6119 				// request level up
6120 				strcpy((char*)net_packet->data, "CSKL");
6121 				net_packet->data[4] = clientnum;
6122 				net_packet->data[5] = PRO_LOCKPICKING;
6123 				net_packet->address.host = net_server.host;
6124 				net_packet->address.port = net_server.port;
6125 				net_packet->len = 6;
6126 				sendPacketSafe(net_sock, -1, net_packet, 0);
6127 			}
6128 			else
6129 			{
6130 				if ( players[clientnum] && players[clientnum]->entity )
6131 				{
6132 					players[clientnum]->entity->increaseSkill(PRO_LOCKPICKING);
6133 				}
6134 			}
6135 		}
6136 
6137 		if ( (ticks - tinkeringSfxLastTicks) > 100 )
6138 		{
6139 			tinkeringSfxLastTicks = ticks;
6140 			if ( itemIsThrowableTinkerTool(item) )
6141 			{
6142 				playSoundEntity(players[clientnum]->entity, 459 + (rand() % 3), 92);
6143 			}
6144 			else
6145 			{
6146 				if ( rand() % 3 == 0 )
6147 				{
6148 					playSoundEntity(players[clientnum]->entity, 422 + (rand() % 2), 92);
6149 				}
6150 				else
6151 				{
6152 					playSoundEntity(players[clientnum]->entity, 35 + rand() % 3, 64);
6153 				}
6154 			}
6155 		}
6156 		else
6157 		{
6158 			playSoundEntity(players[clientnum]->entity, 35 + rand() % 3, 64);
6159 		}
6160 
6161 		for ( int c = 0; c < metal; ++c )
6162 		{
6163 			Item* item = uidToItem(tinkeringRetrieveLeastScrapStack(TOOL_METAL_SCRAP));
6164 			if ( item )
6165 			{
6166 				consumeItem(item, clientnum);
6167 			}
6168 		}
6169 		for ( int c = 0; c < magic; ++c )
6170 		{
6171 			Item* item = uidToItem(tinkeringRetrieveLeastScrapStack(TOOL_MAGIC_SCRAP));
6172 			if ( item )
6173 			{
6174 				consumeItem(item, clientnum);
6175 			}
6176 		}
6177 		if ( tinkeringKitRollIfShouldBreak() )
6178 		{
6179 			tinkeringKitDegradeOnUse(clientnum);
6180 		}
6181 		return newItem(item->type, item->status, item->beatitude, 1, ITEM_TINKERING_APPEARANCE, true, nullptr);
6182 	}
6183 	return nullptr;
6184 }
6185 
tinkeringRetrieveLeastScrapStack(int type)6186 Uint32 GenericGUIMenu::tinkeringRetrieveLeastScrapStack(int type)
6187 {
6188 	if ( type == TOOL_METAL_SCRAP )
6189 	{
6190 		if ( !tinkeringMetalScrap.empty() )
6191 		{
6192 			int lowestCount = 9999;
6193 			Uint32 lowestUid = 0;
6194 			for ( auto it : tinkeringMetalScrap )
6195 			{
6196 				Item* item = uidToItem(it);
6197 				if ( item && item->count > 0 && item->count < lowestCount )
6198 				{
6199 					lowestCount = item->count;
6200 					lowestUid = it;
6201 				}
6202 			}
6203 			return lowestUid;
6204 		}
6205 	}
6206 	else if ( type == TOOL_MAGIC_SCRAP )
6207 	{
6208 		if ( !tinkeringMagicScrap.empty() )
6209 		{
6210 			int lowestCount = 9999;
6211 			Uint32 lowestUid = 0;
6212 			for ( auto it : tinkeringMagicScrap )
6213 			{
6214 				Item* item = uidToItem(it);
6215 				if ( item && item->count > 0 && item->count < lowestCount )
6216 				{
6217 					lowestCount = item->count;
6218 					lowestUid = it;
6219 				}
6220 			}
6221 			return lowestUid;
6222 		}
6223 	}
6224 	return 0;
6225 }
6226 
tinkeringCountScrapTotal(int type)6227 int GenericGUIMenu::tinkeringCountScrapTotal(int type)
6228 {
6229 	int count = 0;
6230 	if ( type == TOOL_METAL_SCRAP )
6231 	{
6232 		if ( !tinkeringMetalScrap.empty() )
6233 		{
6234 			for ( auto it : tinkeringMetalScrap )
6235 			{
6236 				Item* item = uidToItem(it);
6237 				if ( item )
6238 				{
6239 					count += item->count;
6240 				}
6241 			}
6242 		}
6243 	}
6244 	else if ( type == TOOL_MAGIC_SCRAP )
6245 	{
6246 		if ( !tinkeringMagicScrap.empty() )
6247 		{
6248 			for ( auto it : tinkeringMagicScrap )
6249 			{
6250 				Item* item = uidToItem(it);
6251 				if ( item )
6252 				{
6253 					count += item->count;
6254 				}
6255 			}
6256 		}
6257 	}
6258 	return count;
6259 }
6260 
tinkeringPlayerHasMaterialsInventory(int metal,int magic)6261 bool GenericGUIMenu::tinkeringPlayerHasMaterialsInventory(int metal, int magic)
6262 {
6263 	bool hasMaterials = false;
6264 	if ( metal > 0 && magic > 0 )
6265 	{
6266 		if ( tinkeringCountScrapTotal(TOOL_METAL_SCRAP) >= metal && tinkeringCountScrapTotal(TOOL_MAGIC_SCRAP) >= magic )
6267 		{
6268 			hasMaterials = true;
6269 		}
6270 		else
6271 		{
6272 			hasMaterials = false;
6273 		}
6274 	}
6275 	else if ( metal > 0 )
6276 	{
6277 		if ( tinkeringCountScrapTotal(TOOL_METAL_SCRAP) >= metal )
6278 		{
6279 			hasMaterials = true;
6280 		}
6281 		else
6282 		{
6283 			hasMaterials = false;
6284 		}
6285 	}
6286 	else if ( magic > 0 )
6287 	{
6288 		if ( tinkeringCountScrapTotal(TOOL_MAGIC_SCRAP) >= magic )
6289 		{
6290 			hasMaterials = true;
6291 		}
6292 		else
6293 		{
6294 			hasMaterials = false;
6295 		}
6296 	}
6297 	return hasMaterials;
6298 }
6299 
tinkeringGetCraftingCost(const Item * item,int * metal,int * magic)6300 bool GenericGUIMenu::tinkeringGetCraftingCost(const Item* item, int* metal, int* magic)
6301 {
6302 	if ( !item || !metal || !magic )
6303 	{
6304 		return false;
6305 	}
6306 
6307 	switch ( item->type )
6308 	{
6309 		case TOOL_BOMB:
6310 		case TOOL_FREEZE_BOMB:
6311 			*metal = 8;
6312 			*magic = 12;
6313 			break;
6314 		case TOOL_SLEEP_BOMB:
6315 		case TOOL_TELEPORT_BOMB:
6316 			*metal = 4;
6317 			*magic = 8;
6318 			break;
6319 		case CLOAK_BACKPACK:
6320 			*metal = 20;
6321 			*magic = 4;
6322 			break;
6323 		case TOOL_DUMMYBOT:
6324 			*metal = 8;
6325 			*magic = 4;
6326 			break;
6327 		case TOOL_GYROBOT:
6328 			*metal = 16;
6329 			*magic = 12;
6330 			break;
6331 		case TOOL_SENTRYBOT:
6332 			*metal = 16;
6333 			*magic = 8;
6334 			break;
6335 		case TOOL_SPELLBOT:
6336 			*metal = 8;
6337 			*magic = 16;
6338 			break;
6339 		case TOOL_ALEMBIC:
6340 			*metal = 16;
6341 			*magic = 16;
6342 			break;
6343 		case TOOL_DECOY:
6344 			*metal = 8;
6345 			*magic = 1;
6346 			break;
6347 		case TOOL_BEARTRAP:
6348 			*metal = 12;
6349 			*magic = 0;
6350 			break;
6351 		case TOOL_LOCKPICK:
6352 			*metal = 2;
6353 			*magic = 0;
6354 			break;
6355 		case TOOL_GLASSES:
6356 		case TOOL_LANTERN:
6357 			*metal = 8;
6358 			*magic = 4;
6359 			break;
6360 		case POTION_EMPTY:
6361 			*metal = 2;
6362 			*magic = 2;
6363 			break;
6364 		default:
6365 			*metal = 0;
6366 			*magic = 0;
6367 			return false;
6368 			break;
6369 	}
6370 
6371 	return true;
6372 }
6373 
tinkeringGetItemValue(const Item * item,int * metal,int * magic)6374 bool GenericGUIMenu::tinkeringGetItemValue(const Item* item, int* metal, int* magic)
6375 {
6376 	if ( !item || !metal || !magic )
6377 	{
6378 		return false;
6379 	}
6380 
6381 	switch ( item->type )
6382 	{
6383 		case WOODEN_SHIELD:
6384 		case QUARTERSTAFF:
6385 		case BRONZE_SWORD:
6386 		case BRONZE_MACE:
6387 		case BRONZE_AXE:
6388 		case BRONZE_SHIELD:
6389 		case SLING:
6390 		case GLOVES:
6391 		case CLOAK:
6392 		case LEATHER_BOOTS:
6393 		case HAT_PHRYGIAN:
6394 		case HAT_HOOD:
6395 		case LEATHER_HELM:
6396 		case TOOL_TINOPENER:
6397 		case TOOL_MIRROR:
6398 		case TOOL_TORCH:
6399 		case TOOL_BLINDFOLD:
6400 		case TOOL_TOWEL:
6401 		case FOOD_TIN:
6402 		case WIZARD_DOUBLET:
6403 		case HEALER_DOUBLET:
6404 		case BRASS_KNUCKLES:
6405 		case BRONZE_TOMAHAWK:
6406 		case CLOAK_BLACK:
6407 		case POTION_EMPTY:
6408 		case TUNIC:
6409 		case SUEDE_BOOTS:
6410 		case SUEDE_GLOVES:
6411 		case HAT_HOOD_RED:
6412 		case HAT_HOOD_SILVER:
6413 		case SILVER_DOUBLET:
6414 		case CLOAK_SILVER:
6415 		case TOOL_LOCKPICK:
6416 			*metal = 1;
6417 			*magic = 0;
6418 			break;
6419 
6420 		case CLOAK_MAGICREFLECTION:
6421 		case CLOAK_PROTECTION:
6422 		case HAT_WIZARD:
6423 		case HAT_JESTER:
6424 		case AMULET_WATERBREATHING:
6425 		case AMULET_STRANGULATION:
6426 		case AMULET_POISONRESISTANCE:
6427 		case MAGICSTAFF_LIGHT:
6428 		case MAGICSTAFF_LOCKING:
6429 		case MAGICSTAFF_SLOW:
6430 		case RING_ADORNMENT:
6431 		case RING_PROTECTION:
6432 		case RING_TELEPORTATION:
6433 		case GEM_GARNET:
6434 		case GEM_JADE:
6435 		case GEM_JETSTONE:
6436 		case GEM_OBSIDIAN:
6437 		case GEM_GLASS:
6438 		case TOOL_CRYSTALSHARD:
6439 		case HAT_FEZ:
6440 			*metal = 1;
6441 			*magic = 1;
6442 			break;
6443 
6444 		case GLOVES_DEXTERITY:
6445 		case LEATHER_BOOTS_SPEED:
6446 		case AMULET_SEXCHANGE:
6447 		case MAGICSTAFF_OPENING:
6448 		case MAGICSTAFF_COLD:
6449 		case MAGICSTAFF_FIRE:
6450 		case MAGICSTAFF_LIGHTNING:
6451 		case MAGICSTAFF_SLEEP:
6452 		case MAGICSTAFF_POISON:
6453 		case RING_STRENGTH:
6454 		case RING_CONSTITUTION:
6455 		case RING_CONFLICT:
6456 		case GEM_AMBER:
6457 		case GEM_EMERALD:
6458 		case GEM_AMETHYST:
6459 		case GEM_FLUORITE:
6460 			*metal = 1;
6461 			*magic = 2;
6462 			break;
6463 
6464 		case AMULET_MAGICREFLECTION:
6465 		case MAGICSTAFF_DIGGING:
6466 		case MAGICSTAFF_MAGICMISSILE:
6467 		case RING_WARNING:
6468 		case RING_MAGICRESISTANCE:
6469 		case GEM_RUBY:
6470 		case GEM_JACINTH:
6471 		case GEM_CITRINE:
6472 		case GEM_SAPPHIRE:
6473 		case GEM_AQUAMARINE:
6474 		case GEM_OPAL:
6475 		case TOOL_BLINDFOLD_FOCUS:
6476 			*metal = 1;
6477 			*magic = 3;
6478 			break;
6479 
6480 		case CLOAK_INVISIBILITY:
6481 		case AMULET_LIFESAVING:
6482 		case RING_SLOWDIGESTION:
6483 		case RING_INVISIBILITY:
6484 		case RING_LEVITATION:
6485 		case RING_REGENERATION:
6486 		case GEM_DIAMOND:
6487 		case TOOL_SKELETONKEY:
6488 		case VAMPIRE_DOUBLET:
6489 		case MAGICSTAFF_CHARM:
6490 		case MAGICSTAFF_BLEED:
6491 		case MAGICSTAFF_STONEBLOOD:
6492 		case MAGICSTAFF_SUMMON:
6493 		case MASK_SHAMAN:
6494 			*metal = 1;
6495 			*magic = 4;
6496 			break;
6497 
6498 		case SCROLL_LIGHT:
6499 		case SCROLL_FIRE:
6500 		case SCROLL_MAGICMAPPING:
6501 		case SCROLL_REPAIR:
6502 		case SCROLL_DESTROYARMOR:
6503 		case SCROLL_TELEPORTATION:
6504 			*metal = 0;
6505 			*magic = 2;
6506 			break;
6507 
6508 		case SCROLL_IDENTIFY:
6509 		case SCROLL_REMOVECURSE:
6510 		case SCROLL_FOOD:
6511 		case SCROLL_SUMMON:
6512 		case SPELLBOOK_FORCEBOLT:
6513 		case SPELLBOOK_LIGHT:
6514 		case SPELLBOOK_SLOW:
6515 		case SPELLBOOK_LOCKING:
6516 		case SPELLBOOK_TELEPORTATION:
6517 		case SPELLBOOK_REVERT_FORM:
6518 		case SPELLBOOK_RAT_FORM:
6519 		case SPELLBOOK_SPRAY_WEB:
6520 		case SPELLBOOK_POISON:
6521 		case SPELLBOOK_SPEED:
6522 		case SPELLBOOK_DETECT_FOOD:
6523 		case SPELLBOOK_SHADOW_TAG:
6524 		case SPELLBOOK_SALVAGE:
6525 		case SPELLBOOK_DASH:
6526 		case SPELLBOOK_9:
6527 		case SPELLBOOK_10:
6528 			*metal = 0;
6529 			*magic = 4;
6530 			break;
6531 
6532 		case TOOL_BLINDFOLD_TELEPATHY:
6533 			*metal = 1;
6534 			*magic = 6;
6535 			break;
6536 
6537 		case SCROLL_ENCHANTWEAPON:
6538 		case SCROLL_ENCHANTARMOR:
6539 		case SPELLBOOK_COLD:
6540 		case SPELLBOOK_FIREBALL:
6541 		case SPELLBOOK_REMOVECURSE:
6542 		case SPELLBOOK_LIGHTNING:
6543 		case SPELLBOOK_IDENTIFY:
6544 		case SPELLBOOK_MAGICMAPPING:
6545 		case SPELLBOOK_SLEEP:
6546 		case SPELLBOOK_CONFUSE:
6547 		case SPELLBOOK_OPENING:
6548 		case SPELLBOOK_HEALING:
6549 		case SPELLBOOK_CUREAILMENT:
6550 		case SPELLBOOK_ACID_SPRAY:
6551 		case SPELLBOOK_CHARM_MONSTER:
6552 		case SPELLBOOK_SPIDER_FORM:
6553 		case SPELLBOOK_TROLL_FORM:
6554 		case SPELLBOOK_FEAR:
6555 		case SPELLBOOK_STRIKE:
6556 		case SPELLBOOK_TELEPULL:
6557 		case SPELLBOOK_FLUTTER:
6558 		case SCROLL_CHARGING:
6559 		case SCROLL_CONJUREARROW:
6560 			*metal = 0;
6561 			*magic = 6;
6562 			break;
6563 
6564 		case SPELLBOOK_MAGICMISSILE:
6565 		case SPELLBOOK_LEVITATION:
6566 		case SPELLBOOK_INVISIBILITY:
6567 		case SPELLBOOK_EXTRAHEALING:
6568 		case SPELLBOOK_DIG:
6569 		case SPELLBOOK_SUMMON:
6570 		case SPELLBOOK_BLEED:
6571 		case SPELLBOOK_REFLECT_MAGIC:
6572 		case SPELLBOOK_STONEBLOOD:
6573 		case SPELLBOOK_STEAL_WEAPON:
6574 		case SPELLBOOK_DRAIN_SOUL:
6575 		case SPELLBOOK_VAMPIRIC_AURA:
6576 		case SPELLBOOK_IMP_FORM:
6577 		case SPELLBOOK_TROLLS_BLOOD:
6578 		case SPELLBOOK_WEAKNESS:
6579 		case SPELLBOOK_AMPLIFY_MAGIC:
6580 		case SPELLBOOK_DEMON_ILLU:
6581 		case SPELLBOOK_SELF_POLYMORPH:
6582 		case GEM_LUCK:
6583 		case ENCHANTED_FEATHER:
6584 			*metal = 0;
6585 			*magic = 8;
6586 
6587 		case IRON_SPEAR:
6588 		case IRON_SWORD:
6589 		case IRON_MACE:
6590 		case IRON_AXE:
6591 		case IRON_SHIELD:
6592 		case SHORTBOW:
6593 		case BRACERS:
6594 		case IRON_BOOTS:
6595 		case LEATHER_BREASTPIECE:
6596 		case IRON_HELM:
6597 		case TOOL_PICKAXE:
6598 		case TOOL_LANTERN:
6599 		case TOOL_GLASSES:
6600 		case IRON_KNUCKLES:
6601 		case TOOL_BEARTRAP:
6602 		case IRON_DAGGER:
6603 			*metal = 2;
6604 			*magic = 0;
6605 			break;
6606 
6607 		case BRACERS_CONSTITUTION:
6608 		case TOOL_ALEMBIC:
6609 		case PUNISHER_HOOD:
6610 			*metal = 2;
6611 			*magic = 2;
6612 			break;
6613 
6614 		case IRON_BOOTS_WATERWALKING:
6615 			*metal = 2;
6616 			*magic = 3;
6617 			break;
6618 
6619 		case MIRROR_SHIELD:
6620 			*metal = 2;
6621 			*magic = 4;
6622 			break;
6623 
6624 		case STEEL_HALBERD:
6625 		case STEEL_SWORD:
6626 		case STEEL_MACE:
6627 		case STEEL_AXE:
6628 		case STEEL_SHIELD:
6629 		case CROSSBOW:
6630 		case GAUNTLETS:
6631 		case STEEL_BOOTS:
6632 		case IRON_BREASTPIECE:
6633 		case STEEL_HELM:
6634 		case SPIKED_GAUNTLETS:
6635 		case STEEL_CHAKRAM:
6636 		case TOOL_WHIP:
6637 		case MACHINIST_APRON:
6638 		case LONGBOW:
6639 			*metal = 3;
6640 			*magic = 0;
6641 			break;
6642 
6643 		case GAUNTLETS_STRENGTH:
6644 			*metal = 3;
6645 			*magic = 2;
6646 			break;
6647 
6648 		case STEEL_SHIELD_RESISTANCE:
6649 			*metal = 3;
6650 			*magic = 4;
6651 			break;
6652 
6653 		case STEEL_BREASTPIECE:
6654 		case CRYSTAL_SHURIKEN:
6655 		case HEAVY_CROSSBOW:
6656 			*metal = 4;
6657 			*magic = 0;
6658 			break;
6659 
6660 		case CRYSTAL_HELM:
6661 		case CRYSTAL_BOOTS:
6662 		case CRYSTAL_SHIELD:
6663 		case CRYSTAL_GLOVES:
6664 		case CRYSTAL_SWORD:
6665 		case CRYSTAL_SPEAR:
6666 		case CRYSTAL_BATTLEAXE:
6667 		case CRYSTAL_MACE:
6668 		case CLOAK_BACKPACK:
6669 		case COMPOUND_BOW:
6670 			*metal = 4;
6671 			*magic = 2;
6672 			break;
6673 
6674 		case STEEL_BOOTS_FEATHER:
6675 			*metal = 4;
6676 			*magic = 3;
6677 			break;
6678 
6679 		case STEEL_BOOTS_LEVITATION:
6680 			*metal = 4;
6681 			*magic = 4;
6682 			break;
6683 
6684 		case ARTIFACT_BOW:
6685 		case BOOMERANG:
6686 			*metal = 4;
6687 			*magic = 16;
6688 			break;
6689 		case ARTIFACT_CLOAK:
6690 			*metal = 4;
6691 			*magic = 24;
6692 			break;
6693 
6694 		case CRYSTAL_BREASTPIECE:
6695 			*metal = 8;
6696 			*magic = 2;
6697 			break;
6698 
6699 		case ARTIFACT_SWORD:
6700 		case ARTIFACT_MACE:
6701 		case ARTIFACT_SPEAR:
6702 		case ARTIFACT_AXE:
6703 		case ARTIFACT_HELM:
6704 		case ARTIFACT_BOOTS:
6705 		case ARTIFACT_GLOVES:
6706 			*metal = 8;
6707 			*magic = 16;
6708 			break;
6709 
6710 		case ARTIFACT_BREASTPIECE:
6711 			*metal = 16;
6712 			*magic = 16;
6713 			break;
6714 
6715 		case TOOL_SENTRYBOT:
6716 		case TOOL_SPELLBOT:
6717 		case TOOL_DUMMYBOT:
6718 		case TOOL_GYROBOT:
6719 			if ( item->status == BROKEN )
6720 			{
6721 				tinkeringGetCraftingCost(item, &(*metal), &(*magic));
6722 				*metal /= 2;
6723 				*magic /= 2;
6724 			}
6725 			else
6726 			{
6727 				*metal = 0;
6728 				*magic = 0;
6729 			}
6730 			break;
6731 		case TOOL_DECOY:
6732 			*metal = 2;
6733 			*magic = 0;
6734 			break;
6735 		case TOOL_DETONATOR_CHARGE:
6736 			*metal = 2;
6737 			*magic = 4;
6738 			break;
6739 		default:
6740 			*metal = 0;
6741 			*magic = 0;
6742 			break;
6743 	}
6744 
6745 
6746 	if ( *metal > 0 || *magic > 0 )
6747 	{
6748 		if ( item->beatitude != 0 )
6749 		{
6750 			*magic += (abs(item->beatitude) * 1);
6751 		}
6752 		return true;
6753 	}
6754 	return false;
6755 }
6756 
tinkeringGetRepairCost(Item * item,int * metal,int * magic)6757 bool GenericGUIMenu::tinkeringGetRepairCost(Item* item, int* metal, int* magic)
6758 {
6759 	if ( !item || !metal || !magic )
6760 	{
6761 		return false;
6762 	}
6763 
6764 	switch ( item->type )
6765 	{
6766 		case TOOL_SENTRYBOT:
6767 		case TOOL_SPELLBOT:
6768 		case TOOL_DUMMYBOT:
6769 		case TOOL_GYROBOT:
6770 			if ( item->status != BROKEN )
6771 			{
6772 				tinkeringGetCraftingCost(item, &(*metal), &(*magic));
6773 				if ( *metal > 0 )
6774 				{
6775 					*metal = std::max(1, (*metal) / 4);
6776 				}
6777 				if ( *magic > 0 )
6778 				{
6779 					*magic = std::max(0, (*magic) / 4);
6780 				}
6781 
6782 				if ( item->tinkeringBotIsMaxHealth() && item->status == EXCELLENT )
6783 				{
6784 					// can't repair/upgrade.
6785 					*metal = 0;
6786 					*magic = 0;
6787 				}
6788 			}
6789 			else
6790 			{
6791 				*metal = 0;
6792 				*magic = 0;
6793 			}
6794 			break;
6795 		case TOOL_TINKERING_KIT:
6796 			if ( item->status < EXCELLENT )
6797 			{
6798 				*metal = 16;
6799 				*magic = 0;
6800 			}
6801 			break;
6802 		default:
6803 			*metal = 0;
6804 			*magic = 0;
6805 			if ( item->status < EXCELLENT )
6806 			{
6807 				int requirement = tinkeringRepairGeneralItemSkillRequirement(item);
6808 				if ( requirement >= 0 && stats[clientnum]
6809 					&& ((stats[clientnum]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[clientnum], players[clientnum]->entity)) >= requirement) )
6810 				{
6811 					int metalSalvage = 0;
6812 					int magicSalvage = 0;
6813 					tinkeringGetItemValue(item, &metalSalvage, &magicSalvage);
6814 					*metal = metalSalvage * 8;
6815 					*magic = magicSalvage * 8;
6816 					int blessingOrCurse = abs(item->beatitude);
6817 					*magic += blessingOrCurse * 4;
6818 				}
6819 			}
6820 			break;
6821 	}
6822 	// clamp repair cost limits to 99 since GUI overlaps 3 digits...
6823 	*metal = std::min(99, *metal);
6824 	*magic = std::min(99, *magic);
6825 
6826 	if ( *metal > 0 || *magic > 0 )
6827 	{
6828 		return true;
6829 	}
6830 
6831 	return false;
6832 }
6833 
tinkeringRepairGeneralItemSkillRequirement(Item * item)6834 int GenericGUIMenu::tinkeringRepairGeneralItemSkillRequirement(Item* item)
6835 {
6836 	if ( !item )
6837 	{
6838 		return -1;
6839 	}
6840 	if ( itemCategory(item) != WEAPON && itemCategory(item) != ARMOR )
6841 	{
6842 		if ( item->type == BOOMERANG )
6843 		{
6844 			// exception, allowed to repair.
6845 		}
6846 		else
6847 		{
6848 			return -1;
6849 		}
6850 	}
6851 	int metal = 0;
6852 	int magic = 0;
6853 	int requirement = 0;
6854 	int blessing = item->beatitude;
6855 	item->beatitude = 0;
6856 	tinkeringGetItemValue(item, &metal, &magic);
6857 	item->beatitude = blessing;
6858 
6859 	if ( metal == 0 && magic == 0 )
6860 	{
6861 		return -1;
6862 	}
6863 	if ( magic > 0 || metal >= 3 )
6864 	{
6865 		requirement = SKILL_LEVEL_LEGENDARY;
6866 	}
6867 	else if ( metal >= 2 )
6868 	{
6869 		requirement = SKILL_LEVEL_MASTER;
6870 	}
6871 	else if ( metal >= 1 )
6872 	{
6873 		requirement = SKILL_LEVEL_EXPERT;
6874 	}
6875 
6876 	if ( requirement > 0 )
6877 	{
6878 		if ( stats[clientnum] && stats[clientnum]->type == AUTOMATON )
6879 		{
6880 			requirement -= 20;
6881 		}
6882 		return requirement;
6883 	}
6884 	return -1;
6885 }
6886 
tinkeringIsItemUpgradeable(const Item * item)6887 bool GenericGUIMenu::tinkeringIsItemUpgradeable(const Item* item)
6888 {
6889 	if ( !item )
6890 	{
6891 		return false;
6892 	}
6893 	switch ( item->type )
6894 	{
6895 		case TOOL_SENTRYBOT:
6896 		case TOOL_SPELLBOT:
6897 		case TOOL_DUMMYBOT:
6898 		case TOOL_GYROBOT:
6899 			if ( item->tinkeringBotIsMaxHealth() && (tinkeringPlayerHasSkillLVLToCraft(item) != -1) )
6900 			{
6901 				return true;
6902 			}
6903 			break;
6904 		default:
6905 			break;
6906 	}
6907 	return false;
6908 }
6909 
6910 
tinkeringPlayerHasSkillLVLToCraft(const Item * item)6911 int GenericGUIMenu::tinkeringPlayerHasSkillLVLToCraft(const Item* item)
6912 {
6913 	if ( !item )
6914 	{
6915 		return -1;
6916 	}
6917 	int skillLVL = 0;
6918 	if ( stats[clientnum] && players[clientnum] )
6919 	{
6920 		skillLVL = (stats[clientnum]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[clientnum], players[clientnum]->entity)) / 20; // 0 to 5
6921 	}
6922 
6923 	switch ( item->type )
6924 	{
6925 		case TOOL_LOCKPICK:
6926 		case TOOL_GLASSES:
6927 		case POTION_EMPTY:
6928 		case TOOL_DECOY:
6929 			if ( stats[clientnum]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[clientnum], players[clientnum]->entity) >= 10 ) // 10 requirement
6930 			{
6931 				return 0;
6932 			}
6933 			break;
6934 		case TOOL_BEARTRAP:
6935 		case TOOL_GYROBOT:
6936 		case TOOL_SLEEP_BOMB:
6937 		case TOOL_FREEZE_BOMB:
6938 		case TOOL_DUMMYBOT:
6939 		case TOOL_LANTERN:
6940 			if ( skillLVL >= 1 ) // 20 requirement
6941 			{
6942 				return 1;
6943 			}
6944 			break;
6945 		case TOOL_BOMB:
6946 		case TOOL_TELEPORT_BOMB:
6947 		case TOOL_SENTRYBOT:
6948 			if ( skillLVL >= 2 ) // 40 requirement
6949 			{
6950 				return 2;
6951 			}
6952 			break;
6953 		case TOOL_SPELLBOT:
6954 		case TOOL_ALEMBIC:
6955 			if ( skillLVL >= 3 ) // 60 requirement
6956 			{
6957 				return 3;
6958 			}
6959 			break;
6960 		case CLOAK_BACKPACK:
6961 			if ( skillLVL >= 4 ) // 80 requirement
6962 			{
6963 				return 4;
6964 			}
6965 			break;
6966 		case TOOL_TINKERING_KIT:
6967 			return 0;
6968 			break;
6969 		default:
6970 			break;
6971 	}
6972 	return -1;
6973 }
6974 
tinkeringKitDegradeOnUse(int player)6975 bool GenericGUIMenu::tinkeringKitDegradeOnUse(int player)
6976 {
6977 	if ( player == clientnum )
6978 	{
6979 		Item* toDegrade = tinkeringKitItem;
6980 		if ( !toDegrade && player == clientnum )
6981 		{
6982 			// look for tinkeringKit in inventory.
6983 			toDegrade = tinkeringKitFindInInventory();
6984 			if ( !toDegrade )
6985 			{
6986 				return false;
6987 			}
6988 		}
6989 
6990 		bool isEquipped = itemIsEquipped(toDegrade, clientnum);
6991 
6992 		toDegrade->status = std::max(BROKEN, static_cast<Status>(toDegrade->status - 1));
6993 		if ( toDegrade->status > BROKEN )
6994 		{
6995 			messagePlayer(clientnum, language[681], toDegrade->getName());
6996 		}
6997 		else
6998 		{
6999 			messagePlayer(clientnum, language[662], toDegrade->getName());
7000 			if ( players[clientnum] && players[clientnum]->entity )
7001 			{
7002 				playSoundEntityLocal(players[clientnum]->entity, 76, 64);
7003 			}
7004 			tinkeringKitItem = nullptr;
7005 		}
7006 		if ( multiplayer == CLIENT && isEquipped )
7007 		{
7008 			// the client needs to inform the server that their equipment was damaged.
7009 			int armornum = 5;
7010 			strcpy((char*)net_packet->data, "REPA");
7011 			net_packet->data[4] = clientnum;
7012 			net_packet->data[5] = armornum;
7013 			net_packet->data[6] = toDegrade->status;
7014 			net_packet->address.host = net_server.host;
7015 			net_packet->address.port = net_server.port;
7016 			net_packet->len = 7;
7017 			sendPacketSafe(net_sock, -1, net_packet, 0);
7018 		}
7019 		return true;
7020 	}
7021 	return false;
7022 }
7023 
tinkeringKitFindInInventory()7024 Item* GenericGUIMenu::tinkeringKitFindInInventory()
7025 {
7026 	if ( tinkeringKitItem )
7027 	{
7028 		return tinkeringKitItem;
7029 	}
7030 	else
7031 	{
7032 		for ( node_t* invnode = stats[clientnum]->inventory.first; invnode != NULL; invnode = invnode->next )
7033 		{
7034 			Item* tinkerItem = (Item*)invnode->element;
7035 			if ( tinkerItem && tinkerItem->type == TOOL_TINKERING_KIT && tinkerItem->status > BROKEN )
7036 			{
7037 				return tinkerItem;
7038 			}
7039 		}
7040 	}
7041 	return nullptr;
7042 }
7043 
tinkeringKitRollIfShouldBreak()7044 bool GenericGUIMenu::tinkeringKitRollIfShouldBreak()
7045 {
7046 	if ( rand() % 20 == 10 )
7047 	{
7048 		return true;
7049 	}
7050 	return false;
7051 }
7052 
tinkeringRepairItem(Item * item)7053 bool GenericGUIMenu::tinkeringRepairItem(Item* item)
7054 {
7055 	if ( !item )
7056 	{
7057 		return false;
7058 	}
7059 	//if ( itemIsEquipped(item, clientnum) && item->type != TOOL_TINKERING_KIT )
7060 	//{
7061 	//	messagePlayer(clientnum, language[3681]);
7062 	//	return false; // don't want to deal with client/server desync problems here.
7063 	//}
7064 
7065 	if ( stats[clientnum] && players[clientnum] )
7066 	{
7067 		if ( item->type == TOOL_SENTRYBOT || item->type == TOOL_SPELLBOT || item->type == TOOL_DUMMYBOT || item->type == TOOL_GYROBOT )
7068 		{
7069 			if ( item->tinkeringBotIsMaxHealth() )
7070 			{
7071 				// try upgrade item?
7072 				int craftRequirement = tinkeringPlayerHasSkillLVLToCraft(item);
7073 				if ( craftRequirement == -1 ) // can't craft, can't upgrade!
7074 				{
7075 					playSound(90, 64);
7076 					messagePlayer(clientnum, language[3685], items[item->type].name_identified);
7077 					return false;
7078 				}
7079 				else if ( !tinkeringPlayerCanAffordRepair(item) )
7080 				{
7081 					playSound(90, 64);
7082 					messagePlayer(clientnum, language[3687], items[item->type].name_identified);
7083 					return false;
7084 				}
7085 
7086 				Status newStatus = DECREPIT;
7087 				Status maxStatus = static_cast<Status>(tinkeringUpgradeMaxStatus(item));
7088 
7089 				if ( maxStatus <= item->status )
7090 				{
7091 					playSound(90, 64);
7092 					messagePlayer(clientnum, language[3685], items[item->type].name_identified);
7093 					return false;
7094 				}
7095 
7096 				if ( tinkeringConsumeMaterialsForRepair(item, true) )
7097 				{
7098 					newStatus = std::min(static_cast<Status>(item->status + 1), maxStatus);
7099 					Item* upgradedItem = newItem(item->type, newStatus, item->beatitude, 1, ITEM_TINKERING_APPEARANCE, true, nullptr);
7100 					if ( upgradedItem )
7101 					{
7102 						achievementObserver.playerAchievements[clientnum].fixerUpper += 1;
7103 						Item* pickedUp = itemPickup(clientnum, upgradedItem);
7104 						if ( pickedUp && item->count == 1 )
7105 						{
7106 							// item* will be consumed, so pickedUp can take the inventory slot of it.
7107 							pickedUp->x = item->x;
7108 							pickedUp->y = item->y;
7109 							for ( int c = 0; c < NUM_HOTBAR_SLOTS; ++c )
7110 							{
7111 								if ( hotbar[c].item == item->uid )
7112 								{
7113 									hotbar[c].item = pickedUp->uid;
7114 								}
7115 								else if ( hotbar[c].item == pickedUp->uid )
7116 								{
7117 									// this was auto placed by itemPickup just above, undo it.
7118 									hotbar[c].item = 0;
7119 								}
7120 							}
7121 						}
7122 						free(upgradedItem);
7123 					}
7124 					messagePlayer(clientnum, language[3683], items[item->type].name_identified);
7125 					consumeItem(item, clientnum);
7126 					return true;
7127 				}
7128 			}
7129 			else
7130 			{
7131 				int craftRequirement = tinkeringPlayerHasSkillLVLToCraft(item);
7132 				if ( craftRequirement == -1 ) // can't craft, can't repair!
7133 				{
7134 					playSound(90, 64);
7135 					messagePlayer(clientnum, language[3688], items[item->type].name_identified);
7136 					return false;
7137 				}
7138 				else if ( !tinkeringPlayerCanAffordRepair(item) )
7139 				{
7140 					playSound(90, 64);
7141 					messagePlayer(clientnum, language[3686], items[item->type].name_identified);
7142 					return false;
7143 				}
7144 
7145 				if ( tinkeringConsumeMaterialsForRepair(item, false) )
7146 				{
7147 					Uint32 repairedAppearance = std::min((item->appearance % 10) + 1, static_cast<Uint32>(4));
7148 					if ( repairedAppearance == 4 )
7149 					{
7150 						repairedAppearance = ITEM_TINKERING_APPEARANCE;
7151 					}
7152 					Item* repairedItem = newItem(item->type, item->status, item->beatitude, 1, repairedAppearance, true, nullptr);
7153 					if ( repairedItem )
7154 					{
7155 						achievementObserver.playerAchievements[clientnum].fixerUpper += 1;
7156 						Item* pickedUp = itemPickup(clientnum, repairedItem);
7157 						if ( pickedUp && item->count == 1 )
7158 						{
7159 							// item* will be consumed, so pickedUp can take the inventory slot of it.
7160 							pickedUp->x = item->x;
7161 							pickedUp->y = item->y;
7162 							for ( int c = 0; c < NUM_HOTBAR_SLOTS; ++c )
7163 							{
7164 								if ( hotbar[c].item == item->uid )
7165 								{
7166 									hotbar[c].item = pickedUp->uid;
7167 								}
7168 								else if ( hotbar[c].item == pickedUp->uid )
7169 								{
7170 									// this was auto placed by itemPickup just above, undo it.
7171 									hotbar[c].item = 0;
7172 								}
7173 							}
7174 						}
7175 						free(repairedItem);
7176 					}
7177 					messagePlayer(clientnum, language[3682], items[item->type].name_identified);
7178 					consumeItem(item, clientnum);
7179 					return true;
7180 				}
7181 			}
7182 		}
7183 		else
7184 		{
7185 			// normal items.
7186 			int craftRequirement = tinkeringPlayerHasSkillLVLToCraft(item);
7187 			if ( craftRequirement == -1 && itemCategory(item) == TOOL ) // can't craft, can't repair!
7188 			{
7189 				playSound(90, 64);
7190 				messagePlayer(clientnum, language[3688], items[item->type].name_identified);
7191 				return false;
7192 			}
7193 			if ( !tinkeringPlayerCanAffordRepair(item) )
7194 			{
7195 				playSound(90, 64);
7196 				messagePlayer(clientnum, language[3686], items[item->type].name_identified);
7197 				return false;
7198 			}
7199 
7200 			if ( tinkeringConsumeMaterialsForRepair(item, false) )
7201 			{
7202 				int repairedStatus = std::min(static_cast<Status>(item->status + 1), EXCELLENT);
7203 				bool isEquipped = itemIsEquipped(item, clientnum);
7204 				item->status = static_cast<Status>(repairedStatus);
7205 				messagePlayer(clientnum, language[872], item->getName());
7206 				bool replaceTinkeringKit = false;
7207 				if ( item == tinkeringKitItem )
7208 				{
7209 					replaceTinkeringKit = true;
7210 				}
7211 				if ( !isEquipped )
7212 				{
7213 					Item* repairedItem = newItem(item->type, item->status, item->beatitude, 1, item->appearance, true, nullptr);
7214 					if ( repairedItem )
7215 					{
7216 						Item* pickedUp = itemPickup(clientnum, repairedItem);
7217 						if ( pickedUp && item->count == 1 )
7218 						{
7219 							// item* will be consumed, so pickedUp can take the inventory slot of it.
7220 							pickedUp->x = item->x;
7221 							pickedUp->y = item->y;
7222 							for ( int c = 0; c < NUM_HOTBAR_SLOTS; ++c )
7223 							{
7224 								if ( hotbar[c].item == item->uid )
7225 								{
7226 									hotbar[c].item = pickedUp->uid;
7227 								}
7228 								else if ( hotbar[c].item == pickedUp->uid )
7229 								{
7230 									// this was auto placed by itemPickup just above, undo it.
7231 									hotbar[c].item = 0;
7232 								}
7233 							}
7234 							if ( replaceTinkeringKit )
7235 							{
7236 								tinkeringKitItem = pickedUp;
7237 							}
7238 						}
7239 						free(repairedItem);
7240 					}
7241 					consumeItem(item, clientnum);
7242 				}
7243 				else if ( multiplayer == CLIENT && isEquipped )
7244 				{
7245 					// the client needs to inform the server that their equipment was repaired.
7246 					int armornum = 0;
7247 					if ( item == stats[clientnum]->weapon )
7248 					{
7249 						armornum = 0;
7250 					}
7251 					else if ( item == stats[clientnum]->helmet )
7252 					{
7253 						armornum = 1;
7254 					}
7255 					else if ( item == stats[clientnum]->breastplate )
7256 					{
7257 						armornum = 2;
7258 					}
7259 					else if ( item == stats[clientnum]->gloves )
7260 					{
7261 						armornum = 3;
7262 					}
7263 					else if ( item == stats[clientnum]->shoes )
7264 					{
7265 						armornum = 4;
7266 					}
7267 					else if ( item == stats[clientnum]->shield )
7268 					{
7269 						armornum = 5;
7270 					}
7271 					else if ( item == stats[clientnum]->cloak )
7272 					{
7273 						armornum = 6;
7274 					}
7275 					else if ( item == stats[clientnum]->mask )
7276 					{
7277 						armornum = 7;
7278 					}
7279 
7280 					strcpy((char*)net_packet->data, "REPA");
7281 					net_packet->data[4] = clientnum;
7282 					net_packet->data[5] = armornum;
7283 					net_packet->data[6] = item->status;
7284 					net_packet->address.host = net_server.host;
7285 					net_packet->address.port = net_server.port;
7286 					net_packet->len = 7;
7287 					sendPacketSafe(net_sock, -1, net_packet, 0);
7288 				}
7289 				return true;
7290 			}
7291 		}
7292 	}
7293 	return false;
7294 }
7295 
tinkeringUpgradeMaxStatus(Item * item)7296 int GenericGUIMenu::tinkeringUpgradeMaxStatus(Item* item)
7297 {
7298 	if ( !item )
7299 	{
7300 		return BROKEN;
7301 	}
7302 	int skillLVL = 0;
7303 	if ( stats[clientnum] && players[clientnum] )
7304 	{
7305 		skillLVL = (stats[clientnum]->PROFICIENCIES[PRO_LOCKPICKING] + statGetPER(stats[clientnum], players[clientnum]->entity)) / 20; // 0 to 5
7306 		int craftRequirement = tinkeringPlayerHasSkillLVLToCraft(item);
7307 		if ( skillLVL >= 5 )
7308 		{
7309 			return EXCELLENT;
7310 		}
7311 		else
7312 		{
7313 			if ( skillLVL - craftRequirement == 1 )
7314 			{
7315 				return WORN;
7316 			}
7317 			else if ( skillLVL - craftRequirement == 2 )
7318 			{
7319 				return SERVICABLE;
7320 			}
7321 			else if ( skillLVL - craftRequirement >= 3 )
7322 			{
7323 				return EXCELLENT;
7324 			}
7325 		}
7326 	}
7327 	return BROKEN;
7328 }
7329 
tinkeringConsumeMaterialsForRepair(Item * item,bool upgradingItem)7330 bool GenericGUIMenu::tinkeringConsumeMaterialsForRepair(Item* item, bool upgradingItem)
7331 {
7332 	if ( !item )
7333 	{
7334 		return false;
7335 	}
7336 	int metal = 0;
7337 	int magic = 0;
7338 	tinkeringGetRepairCost(item, &metal, &magic);
7339 	if ( metal == 0 && magic == 0 )
7340 	{
7341 		return false;
7342 	}
7343 	if ( tinkeringPlayerHasMaterialsInventory(metal, magic) )
7344 	{
7345 		bool increaseSkill = false;
7346 		if ( stats[clientnum] )
7347 		{
7348 			if ( !upgradingItem )
7349 			{
7350 				if ( rand() % 40 == 0 )
7351 				{
7352 					increaseSkill = true;
7353 				}
7354 			}
7355 			else
7356 			{
7357 				if ( rand() % 10 == 0 )
7358 				{
7359 					increaseSkill = true;
7360 				}
7361 			}
7362 		}
7363 
7364 		if ( increaseSkill )
7365 		{
7366 			if ( multiplayer == CLIENT )
7367 			{
7368 				// request level up
7369 				strcpy((char*)net_packet->data, "CSKL");
7370 				net_packet->data[4] = clientnum;
7371 				net_packet->data[5] = PRO_LOCKPICKING;
7372 				net_packet->address.host = net_server.host;
7373 				net_packet->address.port = net_server.port;
7374 				net_packet->len = 6;
7375 				sendPacketSafe(net_sock, -1, net_packet, 0);
7376 			}
7377 			else
7378 			{
7379 				if ( players[clientnum] && players[clientnum]->entity )
7380 				{
7381 					players[clientnum]->entity->increaseSkill(PRO_LOCKPICKING);
7382 				}
7383 			}
7384 		}
7385 
7386 		for ( int c = 0; c < metal; ++c )
7387 		{
7388 			Item* item = uidToItem(tinkeringRetrieveLeastScrapStack(TOOL_METAL_SCRAP));
7389 			if ( item )
7390 			{
7391 				consumeItem(item, clientnum);
7392 			}
7393 		}
7394 		for ( int c = 0; c < magic; ++c )
7395 		{
7396 			Item* item = uidToItem(tinkeringRetrieveLeastScrapStack(TOOL_MAGIC_SCRAP));
7397 			if ( item )
7398 			{
7399 				consumeItem(item, clientnum);
7400 			}
7401 		}
7402 		if ( tinkeringKitRollIfShouldBreak() && item != tinkeringKitItem )
7403 		{
7404 			tinkeringKitDegradeOnUse(clientnum);
7405 		}
7406 		return true;
7407 	}
7408 	return false;
7409 }
7410 
scribingCreateCraftableItemList()7411 void GenericGUIMenu::scribingCreateCraftableItemList()
7412 {
7413 	scribingFreeLists();
7414 	std::vector<Item*> items;
7415 	std::unordered_map<int, int> scrollAppearanceMap;
7416 	for ( int i = 0; i < (NUMLABELS) && i < enchantedFeatherScrollsShuffled.size(); ++i )
7417 	{
7418 		int itemType = enchantedFeatherScrollsShuffled.at(i);
7419 		if ( itemType != SCROLL_BLANK )
7420 		{
7421 			auto find = scrollAppearanceMap.find(itemType);
7422 			if ( find != scrollAppearanceMap.end() )
7423 			{
7424 				// found ItemType in map.
7425 				scrollAppearanceMap[itemType] = (*find).second + 1;
7426 			}
7427 			else
7428 			{
7429 				// new element.
7430 				scrollAppearanceMap.insert({ itemType, 0 });
7431 			}
7432 			items.push_back(newItem(static_cast<ItemType>(itemType),
7433 				EXCELLENT, 0, 1, scrollAppearanceMap[itemType], false, &scribingTotalItems));
7434 		}
7435 	}
7436 
7437 	if ( stats[clientnum] )
7438 	{
7439 		// make the last node jump to the player's actual items,
7440 		// so consuming the items in this list will actually update the player's inventory.
7441 		node_t* scribingTotalLastCraftableNode = scribingTotalItems.last;
7442 		if ( scribingTotalLastCraftableNode )
7443 		{
7444 			scribingTotalLastCraftableNode->next = stats[clientnum]->inventory.first;
7445 		}
7446 	}
7447 }
7448 
scribingFreeLists()7449 void GenericGUIMenu::scribingFreeLists()
7450 {
7451 	node_t* nextnode = nullptr;
7452 	int itemcnt = 0;
7453 
7454 	// totalItems is a unique list, contains unique craftable data,
7455 	// as well as a pointer to continue to the player's inventory
7456 	for ( node_t* node = scribingTotalItems.first; node; node = nextnode )
7457 	{
7458 		nextnode = node->next;
7459 		if ( node->list == &scribingTotalItems )
7460 		{
7461 			list_RemoveNode(node);
7462 			++itemcnt;
7463 		}
7464 		else if ( node->list == &stats[clientnum]->inventory )
7465 		{
7466 			//messagePlayer(clientnum, "reached inventory after clearing %d items", itemcnt);
7467 			break;
7468 		}
7469 	}
7470 	scribingTotalItems.first = nullptr;
7471 	scribingTotalItems.last = nullptr;
7472 	scribingTotalLastCraftableNode = nullptr;
7473 	scribingBlankScrollTarget = nullptr;
7474 	scribingLastUsageDisplayTimer = 0;
7475 	scribingLastUsageAmount = 0;
7476 }
7477 
scribingToolDegradeOnUse(Item * itemUsedWith)7478 int GenericGUIMenu::scribingToolDegradeOnUse(Item* itemUsedWith)
7479 {
7480 	if ( !itemUsedWith )
7481 	{
7482 		return -1;
7483 	}
7484 	Item* toDegrade = scribingToolItem;
7485 	if ( !toDegrade )
7486 	{
7487 		// look for scribing tool in inventory.
7488 		toDegrade = scribingToolFindInInventory();
7489 		if ( !toDegrade )
7490 		{
7491 			return -1;
7492 		}
7493 	}
7494 
7495 	int durability = toDegrade->appearance % ENCHANTED_FEATHER_MAX_DURABILITY;
7496 	int usageCost = 0;
7497 	if ( itemCategory(itemUsedWith) == SCROLL )
7498 	{
7499 		switch ( itemUsedWith->type )
7500 		{
7501 			case SCROLL_MAIL:
7502 				usageCost = 2;
7503 				break;
7504 			case SCROLL_DESTROYARMOR:
7505 			case SCROLL_FIRE:
7506 			case SCROLL_LIGHT:
7507 				usageCost = 4;
7508 				break;
7509 				break;
7510 			case SCROLL_SUMMON:
7511 			case SCROLL_IDENTIFY:
7512 			case SCROLL_REMOVECURSE:
7513 				usageCost = 6;
7514 				break;
7515 			case SCROLL_FOOD:
7516 			case SCROLL_TELEPORTATION:
7517 				usageCost = 8;
7518 				break;
7519 			case SCROLL_REPAIR:
7520 			case SCROLL_MAGICMAPPING:
7521 				usageCost = 12;
7522 				break;
7523 			case SCROLL_ENCHANTWEAPON:
7524 			case SCROLL_ENCHANTARMOR:
7525 				usageCost = 16;
7526 				break;
7527 			default:
7528 				usageCost = 8;
7529 				break;
7530 		}
7531 	}
7532 	else if ( itemCategory(itemUsedWith) == SPELLBOOK )
7533 	{
7534 		usageCost = 16;
7535 	}
7536 	int randomValue = 0;
7537 	if ( stats[clientnum] )
7538 	{
7539 		int skillLVL = 0;
7540 		if ( stats[clientnum] && players[clientnum] )
7541 		{
7542 			skillLVL = (stats[clientnum]->PROFICIENCIES[PRO_MAGIC] + statGetINT(stats[clientnum], players[clientnum]->entity)) / 20; // 0 to 5
7543 		}
7544 		if ( toDegrade->beatitude > 0 )
7545 		{
7546 			skillLVL = 5; // blessed feather.
7547 		}
7548 		else if ( toDegrade->beatitude < 0 )
7549 		{
7550 			skillLVL = 0; // cursed feather.
7551 		}
7552 
7553 		if ( skillLVL >= 4 )
7554 		{
7555 			randomValue = rand() % 5;
7556 			usageCost = std::max(2, usageCost - randomValue);
7557 		}
7558 		else if ( skillLVL < 2 )
7559 		{
7560 			randomValue = rand() % 7;
7561 			usageCost += randomValue;
7562 		}
7563 		else if ( skillLVL == 2 )
7564 		{
7565 			randomValue = rand() % 5;
7566 			usageCost += randomValue;
7567 		}
7568 		else if ( skillLVL == 3 )
7569 		{
7570 			randomValue = rand() % 3;
7571 			usageCost += randomValue;
7572 		}
7573 	}
7574 
7575 	if ( durability - usageCost < 0 )
7576 	{
7577 		toDegrade->status = BROKEN;
7578 		toDegrade->appearance = 0;
7579 	}
7580 	else
7581 	{
7582 		scribingLastUsageDisplayTimer = 200;
7583 		scribingLastUsageAmount = usageCost;
7584 		toDegrade->appearance -= usageCost;
7585 		if ( toDegrade->appearance % ENCHANTED_FEATHER_MAX_DURABILITY == 0 )
7586 		{
7587 			toDegrade->status = BROKEN;
7588 			messagePlayer(clientnum, language[3727], toDegrade->getName());
7589 			scribingToolItem = nullptr;
7590 			return usageCost;
7591 		}
7592 		else
7593 		{
7594 			if ( durability > 25 && (toDegrade->appearance % ENCHANTED_FEATHER_MAX_DURABILITY) <= 25 )
7595 			{
7596 				// notify we're at less than 25%.
7597 				messagePlayer(clientnum, language[3729], toDegrade->getName());
7598 			}
7599 		}
7600 	}
7601 	if ( toDegrade->status > BROKEN )
7602 	{
7603 		//messagePlayer(clientnum, language[681], toDegrade->getName());
7604 		return usageCost;
7605 	}
7606 	else
7607 	{
7608 		if ( (usageCost / 2) < durability && itemCategory(itemUsedWith) == SCROLL )
7609 		{
7610 			// if scroll cost is a little more than the durability, then let it succeed.
7611 			messagePlayer(clientnum, language[3727], toDegrade->getName());
7612 			scribingToolItem = nullptr;
7613 			return usageCost;
7614 		}
7615 		else
7616 		{
7617 			messagePlayer(clientnum, language[3728], toDegrade->getName());
7618 			scribingToolItem = nullptr;
7619 			return 0;
7620 		}
7621 	}
7622 	return -1;
7623 }
7624 
scribingToolFindInInventory()7625 Item* GenericGUIMenu::scribingToolFindInInventory()
7626 {
7627 	if ( scribingToolItem )
7628 	{
7629 		return scribingToolItem;
7630 	}
7631 	else
7632 	{
7633 		for ( node_t* invnode = stats[clientnum]->inventory.first; invnode != NULL; invnode = invnode->next )
7634 		{
7635 			Item* scribeItem = (Item*)invnode->element;
7636 			if ( scribeItem && scribeItem->type == ENCHANTED_FEATHER && scribeItem->status > BROKEN )
7637 			{
7638 				return scribeItem;
7639 			}
7640 		}
7641 	}
7642 	return nullptr;
7643 }
7644 
scribingWriteItem(Item * item)7645 bool GenericGUIMenu::scribingWriteItem(Item* item)
7646 {
7647 	if ( !item )
7648 	{
7649 		return false;
7650 	}
7651 
7652 	if ( itemCategory(item) == SCROLL )
7653 	{
7654 		if ( !scribingBlankScrollTarget )
7655 		{
7656 			return false;
7657 		}
7658 
7659 		int result = scribingToolDegradeOnUse(item);
7660 		if ( result == 0 )
7661 		{
7662 			// item broken before completion.
7663 			return false;
7664 		}
7665 		else if ( result < 0 )
7666 		{
7667 			// failure - invalid variables.
7668 			return false;
7669 		}
7670 
7671 		bool increaseSkill = false;
7672 		if ( stats[clientnum] )
7673 		{
7674 			if ( rand() % 5 == 0 )
7675 			{
7676 				increaseSkill = true;
7677 			}
7678 		}
7679 
7680 		if ( increaseSkill )
7681 		{
7682 			if ( multiplayer == CLIENT )
7683 			{
7684 				// request level up
7685 				strcpy((char*)net_packet->data, "CSKL");
7686 				net_packet->data[4] = clientnum;
7687 				net_packet->data[5] = PRO_MAGIC;
7688 				net_packet->address.host = net_server.host;
7689 				net_packet->address.port = net_server.port;
7690 				net_packet->len = 6;
7691 				sendPacketSafe(net_sock, -1, net_packet, 0);
7692 			}
7693 			else
7694 			{
7695 				if ( players[clientnum] && players[clientnum]->entity )
7696 				{
7697 					players[clientnum]->entity->increaseSkill(PRO_MAGIC);
7698 				}
7699 			}
7700 		}
7701 
7702 		Item* crafted = newItem(item->type, scribingBlankScrollTarget->status,
7703 			scribingBlankScrollTarget->beatitude, 1, item->appearance, false, nullptr);
7704 		if ( crafted )
7705 		{
7706 			if ( crafted->type == SCROLL_MAIL )
7707 			{
7708 				// mail uses the appearance to generate the text, so randomise it here.
7709 				crafted->appearance = rand();
7710 			}
7711 			Item* pickedUp = itemPickup(clientnum, crafted);
7712 			//messagePlayerColor(clientnum, uint32ColorGreen(*mainsurface), language[3724]);
7713 			int oldcount = pickedUp->count;
7714 			pickedUp->count = 1;
7715 			messagePlayerColor(clientnum, uint32ColorGreen(*mainsurface), language[3724], pickedUp->description());
7716 			pickedUp->count = oldcount;
7717 			consumeItem(scribingBlankScrollTarget, clientnum);
7718 			//scribingBlankScrollTarget = nullptr;
7719 			if ( client_classes[clientnum] == CLASS_SHAMAN )
7720 			{
7721 				steamStatisticUpdate(STEAM_STAT_ROLL_THE_BONES, STEAM_STAT_INT, 1);
7722 			}
7723 			free(crafted);
7724 			return true;
7725 		}
7726 	}
7727 	else if ( itemCategory(item) == SPELLBOOK )
7728 	{
7729 		if ( item->status == EXCELLENT )
7730 		{
7731 			return false;
7732 		}
7733 		int result = scribingToolDegradeOnUse(item);
7734 		if ( result == 0 )
7735 		{
7736 			// item broken before completion.
7737 			return false;
7738 		}
7739 		else if ( result < 0 )
7740 		{
7741 			// failure - invalid variables.
7742 			return false;
7743 		}
7744 
7745 		bool increaseSkill = false;
7746 		if ( stats[clientnum] )
7747 		{
7748 			if ( rand() % 10 == 0 )
7749 			{
7750 				increaseSkill = true;
7751 			}
7752 		}
7753 
7754 		if ( increaseSkill )
7755 		{
7756 			if ( multiplayer == CLIENT )
7757 			{
7758 				// request level up
7759 				strcpy((char*)net_packet->data, "CSKL");
7760 				net_packet->data[4] = clientnum;
7761 				net_packet->data[5] = PRO_MAGIC;
7762 				net_packet->address.host = net_server.host;
7763 				net_packet->address.port = net_server.port;
7764 				net_packet->len = 6;
7765 				sendPacketSafe(net_sock, -1, net_packet, 0);
7766 			}
7767 			else
7768 			{
7769 				if ( players[clientnum] && players[clientnum]->entity )
7770 				{
7771 					players[clientnum]->entity->increaseSkill(PRO_MAGIC);
7772 				}
7773 			}
7774 		}
7775 		int repairedStatus = std::min(static_cast<Status>(item->status + 1), EXCELLENT);
7776 		bool isEquipped = itemIsEquipped(item, clientnum);
7777 		item->status = static_cast<Status>(repairedStatus);
7778 		messagePlayer(clientnum, language[3725]);
7779 		messagePlayer(clientnum, language[872], item->getName());
7780 		if ( !isEquipped )
7781 		{
7782 			Item* repairedItem = newItem(item->type, item->status, item->beatitude, 1, item->appearance, true, nullptr);
7783 			if ( repairedItem )
7784 			{
7785 				itemPickup(clientnum, repairedItem);
7786 				free(repairedItem);
7787 			}
7788 			consumeItem(item, clientnum);
7789 		}
7790 		else if ( multiplayer == CLIENT && isEquipped )
7791 		{
7792 			// the client needs to inform the server that their equipment was repaired.
7793 			int armornum = 0;
7794 			if ( item == stats[clientnum]->shield )
7795 			{
7796 				armornum = 5;
7797 			}
7798 
7799 			strcpy((char*)net_packet->data, "REPA");
7800 			net_packet->data[4] = clientnum;
7801 			net_packet->data[5] = armornum;
7802 			net_packet->data[6] = item->status;
7803 			net_packet->address.host = net_server.host;
7804 			net_packet->address.port = net_server.port;
7805 			net_packet->len = 7;
7806 			sendPacketSafe(net_sock, -1, net_packet, 0);
7807 		}
7808 		return true;
7809 	}
7810 	return false;
7811 }
7812 
displayCurrentHPBar()7813 void EnemyHPDamageBarHandler::displayCurrentHPBar()
7814 {
7815 	if ( HPBars.empty() )
7816 	{
7817 		return;
7818 	}
7819 	Uint32 mostRecentTicks = 0;
7820 	auto mostRecentEntry = HPBars.end();
7821 	auto highPriorityMostRecentEntry = HPBars.end();
7822 	bool foundHighPriorityEntry = false;
7823 	for ( auto it = HPBars.begin(); it != HPBars.end(); )
7824 	{
7825 		if ( ticks - (*it).second.enemy_timer >= k_maxTickLifetime )
7826 		{
7827 			it = HPBars.erase(it); // no need to show this bar, delete it
7828 		}
7829 		else
7830 		{
7831 			if ( (*it).second.enemy_timer > mostRecentTicks && (*it).second.shouldDisplay )
7832 			{
7833 				if ( mostRecentEntry != HPBars.end() )
7834 				{
7835 					// previous most recent tick should not display until updated by high priority.
7836 					// since we've found a new one to display.
7837 					(*mostRecentEntry).second.shouldDisplay = false;
7838 				}
7839 				if ( !(*it).second.lowPriorityTick )
7840 				{
7841 					// this is a normal priority damage update (not burn/poison etc)
7842 					// if a newer tick is low priority, then defer to this one.
7843 					highPriorityMostRecentEntry = it;
7844 					foundHighPriorityEntry = true;
7845 				}
7846 				mostRecentEntry = it;
7847 				mostRecentTicks = (*it).second.enemy_timer;
7848 			}
7849 			else
7850 			{
7851 				(*it).second.shouldDisplay = false;
7852 			}
7853 			++it;
7854 		}
7855 	}
7856 	if ( mostRecentTicks > 0 )
7857 	{
7858 		if ( !foundHighPriorityEntry )
7859 		{
7860 			// all low priority, just display the last added.
7861 		}
7862 		else
7863 		{
7864 			if ( (mostRecentEntry != highPriorityMostRecentEntry) && foundHighPriorityEntry )
7865 			{
7866 				// the most recent was low priority, so defer to the most recent high priority.
7867 				mostRecentEntry = highPriorityMostRecentEntry;
7868 			}
7869 		}
7870 		(*mostRecentEntry).second.enemy_hp = std::max(0, (*mostRecentEntry).second.enemy_hp);
7871 
7872 		// bar
7873 		SDL_Rect pos;
7874 		pos.x = xres / 2 - 256;
7875 		pos.y = yres - 224;
7876 		pos.w = 512;
7877 		pos.h = 38;
7878 		drawTooltip(&pos);
7879 		pos.x = xres / 2 - 253;
7880 		pos.y = yres - 221;
7881 		pos.w = 506;
7882 		pos.h = 32;
7883 		drawRect(&pos, SDL_MapRGB(mainsurface->format, 16, 0, 0), 255);
7884 		if ( (*mostRecentEntry).second.enemy_oldhp > (*mostRecentEntry).second.enemy_hp )
7885 		{
7886 			int timeDiff = ticks - (*mostRecentEntry).second.enemy_timer;
7887 			if ( timeDiff > 30 || (*mostRecentEntry).second.enemy_hp == 0 )
7888 			{
7889 				// delay 30 ticks before background hp drop animation, or if health 0 start immediately.
7890 				// we want to complete animation with x ticks to go
7891 				int depletionTicks = (80 - timeDiff) / 2;
7892 				int healthDiff = (*mostRecentEntry).second.enemy_oldhp - (*mostRecentEntry).second.enemy_hp;
7893 				if ( ticks % 2 == 0 )
7894 				{
7895 					(*mostRecentEntry).second.enemy_oldhp -= std::max((healthDiff) / std::max(depletionTicks, 1), 1);
7896 				}
7897 			}
7898 			pos.w = 506 * ((double)(*mostRecentEntry).second.enemy_oldhp / (*mostRecentEntry).second.enemy_maxhp);
7899 			if ( (*mostRecentEntry).second.enemy_bar_color > 0 )
7900 			{
7901 				drawRect(&pos, (*mostRecentEntry).second.enemy_bar_color, 128);
7902 			}
7903 			else
7904 			{
7905 				drawRect(&pos, SDL_MapRGB(mainsurface->format, 128, 0, 0), 128);
7906 			}
7907 		}
7908 		if ( (*mostRecentEntry).second.enemy_hp > 0 )
7909 		{
7910 			pos.w = 506 * ((double)(*mostRecentEntry).second.enemy_hp / (*mostRecentEntry).second.enemy_maxhp);
7911 			drawRect(&pos, SDL_MapRGB(mainsurface->format, 128, 0, 0), 255);
7912 			if ( (*mostRecentEntry).second.enemy_bar_color > 0 )
7913 			{
7914 				drawRect(&pos, (*mostRecentEntry).second.enemy_bar_color, 224);
7915 			}
7916 		}
7917 
7918 		// name
7919 		int x = xres / 2 - longestline((*mostRecentEntry).second.enemy_name) * TTF12_WIDTH / 2 + 2;
7920 		int y = yres - 221 + 16 - TTF12_HEIGHT / 2 + 2;
7921 		ttfPrintText(ttf12, x, y, (*mostRecentEntry).second.enemy_name);
7922 	}
7923 }
7924 
addEnemyToList(Sint32 HP,Sint32 maxHP,Sint32 oldHP,Uint32 color,Uint32 uid,char * name,bool isLowPriority)7925 void EnemyHPDamageBarHandler::addEnemyToList(Sint32 HP, Sint32 maxHP, Sint32 oldHP, Uint32 color, Uint32 uid, char* name, bool isLowPriority)
7926 {
7927 	auto find = HPBars.find(uid);
7928 	if ( find != HPBars.end() )
7929 	{
7930 		// uid exists in list.
7931 		(*find).second.enemy_hp = HP;
7932 		(*find).second.enemy_maxhp = maxHP;
7933 		(*find).second.enemy_bar_color = color;
7934 		(*find).second.lowPriorityTick = isLowPriority;
7935 		if ( !isLowPriority )
7936 		{
7937 			(*find).second.shouldDisplay = true;
7938 		}
7939 		(*find).second.enemy_timer = ticks;
7940 	}
7941 	else
7942 	{
7943 		HPBars.insert(std::make_pair(uid, EnemyHPDetails(HP, maxHP, oldHP, color, name, isLowPriority)));
7944 	}
7945 }