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 }