1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: updatecharactersheet.cpp
5 Desc: contains updateCharacterSheet()
6
7 Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 See LICENSE for details.
9
10 -------------------------------------------------------------------------------*/
11
12 #include "../main.hpp"
13 #include "../draw.hpp"
14 #include "../game.hpp"
15 #include "../stat.hpp"
16 #include "../items.hpp"
17 #include "../player.hpp"
18 #include "../colors.hpp"
19 #include "interface.hpp"
20 #include "../sound.hpp"
21 #include "../magic/magic.hpp"
22 #include "../menu.hpp"
23 #include "../net.hpp"
24 #include "../scores.hpp"
25
26 void statsHoverText(Stat* tmpStat);
27
28 /*-------------------------------------------------------------------------------
29
30 updateCharacterSheet
31
32 Draws the character sheet and processes all interaction with it
33
34 -------------------------------------------------------------------------------*/
35
updateCharacterSheet()36 void updateCharacterSheet()
37 {
38 int i = 0;
39 int x = 0;
40 SDL_Rect pos;
41 bool b = false;
42 node_t* node = NULL;
43 Entity* entity = NULL;
44 Item* item = NULL;
45 int c;
46
47 // draw window
48 pos.x = 8;
49 pos.y = 8;
50 pos.w = 208;
51 pos.h = 180;
52 //drawImage(character_bmp, NULL, &pos);
53 //pos.x=0; pos.y=196;
54 //pos.w=222; pos.h=392-196;
55 //drawTooltip(&pos);
56 int statWindowY = 196;
57 int statWindowY2 = 404;
58 if ( uiscale_charactersheet )
59 {
60 pos.h = 236;
61 pos.w = 276;
62 statWindowY = pos.h + 16;
63 statWindowY2 = 554;
64 }
65
66
67 drawWindowFancy(0, 0, pos.w + 16, pos.h + 16);
68 drawRect(&pos, 0, 255);
69 drawWindowFancy(0, pos.h + 16, pos.w + 16, statWindowY2);
70
71 interfaceCharacterSheet.x = pos.x - 8;
72 interfaceCharacterSheet.y = pos.y - 8;
73 interfaceCharacterSheet.w = pos.w + 16;
74 interfaceCharacterSheet.h = statWindowY2;
75
76 // character sheet
77 double ofov = fov;
78 fov = 50;
79 if (players[clientnum] != nullptr && players[clientnum]->entity != nullptr)
80 {
81 if (!softwaremode)
82 {
83 glClear(GL_DEPTH_BUFFER_BIT);
84 }
85 //TODO: These two NOT PLAYERSWAP
86 //camera.x=players[clientnum]->x/16.0+.5*cos(players[clientnum]->yaw)-.4*sin(players[clientnum]->yaw);
87 //camera.y=players[clientnum]->y/16.0+.5*sin(players[clientnum]->yaw)+.4*cos(players[clientnum]->yaw);
88 camera_charsheet.x = players[clientnum]->entity->x / 16.0 + (.92 * cos(camera_charsheet_offsetyaw));
89 camera_charsheet.y = players[clientnum]->entity->y / 16.0 + (.92 * sin(camera_charsheet_offsetyaw));
90 camera_charsheet.z = players[clientnum]->entity->z * 2;
91 //camera.ang=atan2(players[clientnum]->y/16.0-camera.y,players[clientnum]->x/16.0-camera.x); //TODO: _NOT_ PLAYERSWAP
92 camera_charsheet.ang = (camera_charsheet_offsetyaw - PI); //5 * PI / 4;
93 camera_charsheet.vang = PI / 20;
94 camera_charsheet.winx = 8;
95 camera_charsheet.winy = 8;
96 camera_charsheet.winw = pos.w;
97 camera_charsheet.winh = pos.h;
98 b = players[clientnum]->entity->flags[BRIGHT];
99 players[clientnum]->entity->flags[BRIGHT] = true;
100 if ( !players[clientnum]->entity->flags[INVISIBLE] )
101 {
102 glDrawVoxel(&camera_charsheet, players[clientnum]->entity, REALCOLORS);
103 }
104 players[clientnum]->entity->flags[BRIGHT] = b;
105 c = 0;
106 if (multiplayer != CLIENT)
107 {
108 for (node = players[clientnum]->entity->children.first; node != nullptr; node = node->next)
109 {
110 if (c == 0)
111 {
112 c++;
113 continue;
114 }
115 entity = (Entity*) node->element;
116 if ( !entity->flags[INVISIBLE] )
117 {
118 b = entity->flags[BRIGHT];
119 entity->flags[BRIGHT] = true;
120 glDrawVoxel(&camera_charsheet, entity, REALCOLORS);
121 entity->flags[BRIGHT] = b;
122 }
123 c++;
124 }
125 for ( node = map.entities->first; node != NULL; node = node->next )
126 {
127 entity = (Entity*) node->element;
128 if ( (Sint32)entity->getUID() == -4 )
129 {
130 glDrawSprite(&camera_charsheet, entity, REALCOLORS);
131 }
132 }
133 }
134 else
135 {
136 for ( node = map.entities->first; node != NULL; node = node->next )
137 {
138 entity = (Entity*) node->element;
139 if ( (entity->behavior == &actPlayerLimb && entity->skill[2] == clientnum && !entity->flags[INVISIBLE]) || (Sint32)entity->getUID() == -4 )
140 {
141 b = entity->flags[BRIGHT];
142 entity->flags[BRIGHT] = true;
143 if ( (Sint32)entity->getUID() == -4 )
144 {
145 glDrawSprite(&camera_charsheet, entity, REALCOLORS);
146 }
147 else
148 {
149 glDrawVoxel(&camera_charsheet, entity, REALCOLORS);
150 }
151 entity->flags[BRIGHT] = b;
152 }
153 }
154 }
155
156 SDL_Rect rotateBtn;
157 rotateBtn.w = 16;
158 rotateBtn.h = 16;
159 rotateBtn.x = camera_charsheet.winx + camera_charsheet.winw - rotateBtn.w;
160 rotateBtn.y = camera_charsheet.winy + camera_charsheet.winh - rotateBtn.h;
161 drawWindow(rotateBtn.x, rotateBtn.y, rotateBtn.x + rotateBtn.w, rotateBtn.y + rotateBtn.h);
162 if ( mousestatus[SDL_BUTTON_LEFT] && !shootmode )
163 {
164 if ( mouseInBounds(rotateBtn.x, rotateBtn.x + rotateBtn.w, rotateBtn.y, rotateBtn.y + rotateBtn.h) )
165 {
166 camera_charsheet_offsetyaw += 0.05;
167 if ( camera_charsheet_offsetyaw > 2 * PI )
168 {
169 camera_charsheet_offsetyaw -= 2 * PI;
170 }
171 drawDepressed(rotateBtn.x, rotateBtn.y, rotateBtn.x + rotateBtn.w, rotateBtn.y + rotateBtn.h);
172 }
173 }
174 ttfPrintText(ttf12, rotateBtn.x + 2, rotateBtn.y + 2, ">");
175
176 rotateBtn.x = camera_charsheet.winx + camera_charsheet.winw - rotateBtn.w * 2 - 4;
177 rotateBtn.y = camera_charsheet.winy + camera_charsheet.winh - rotateBtn.h;
178 drawWindow(rotateBtn.x, rotateBtn.y, rotateBtn.x + rotateBtn.w, rotateBtn.y + rotateBtn.h);
179 if ( mousestatus[SDL_BUTTON_LEFT] && !shootmode )
180 {
181 if ( mouseInBounds(rotateBtn.x, rotateBtn.x + rotateBtn.w, rotateBtn.y, rotateBtn.y + rotateBtn.h) )
182 {
183 camera_charsheet_offsetyaw -= 0.05;
184 if ( camera_charsheet_offsetyaw < 0.f )
185 {
186 camera_charsheet_offsetyaw += 2 * PI;
187 }
188 drawDepressed(rotateBtn.x, rotateBtn.y, rotateBtn.x + rotateBtn.w, rotateBtn.y + rotateBtn.h);
189 }
190 }
191 ttfPrintText(ttf12, rotateBtn.x, rotateBtn.y + 2, "<");
192 }
193 fov = ofov;
194
195 TTF_Font* fontStat = ttf12;
196 int text_y = 0;
197 int pad_y = 12;
198 int fontWidth = TTF12_WIDTH;
199 if ( uiscale_charactersheet )
200 {
201 fontStat = ttf16;
202 pad_y = 18;
203 fontWidth = TTF16_WIDTH;
204 }
205 text_y = statWindowY + 6;
206 ttfPrintTextFormatted(fontStat, 8, text_y, "%s", stats[clientnum]->name);
207 text_y += pad_y;
208 ttfPrintTextFormatted(fontStat, 8, text_y, language[359], stats[clientnum]->LVL, playerClassLangEntry(client_classes[clientnum], clientnum));
209 text_y += pad_y;
210 ttfPrintTextFormatted(fontStat, 8, text_y, language[360], stats[clientnum]->EXP);
211 text_y += pad_y;
212 ttfPrintTextFormatted(fontStat, 8, text_y, language[361], currentlevel);
213
214 Entity* playerEntity = nullptr;
215 if ( players[clientnum] )
216 {
217 playerEntity = players[clientnum]->entity;
218 }
219
220 // attributes
221 Sint32 statModifier = 0;
222 char statText[64] = "";
223 //Uint32 statColor = uint32ColorWhite(*mainsurface);
224 text_y += pad_y * 2;
225 snprintf(statText, 64, language[1200], stats[clientnum]->STR);
226 ttfPrintTextFormatted(fontStat, 8, text_y, statText);
227 printStatBonus(fontStat, stats[clientnum]->STR, statGetSTR(stats[clientnum], playerEntity), 8 + longestline(statText) * fontWidth, text_y);
228
229 text_y += pad_y;
230 snprintf(statText, 64, language[1201], stats[clientnum]->DEX);
231 ttfPrintTextFormatted(fontStat, 8, text_y, statText);
232 printStatBonus(fontStat, stats[clientnum]->DEX, statGetDEX(stats[clientnum], playerEntity), 8 + longestline(statText) * fontWidth, text_y);
233
234 text_y += pad_y;
235 snprintf(statText, 64, language[1202], stats[clientnum]->CON);
236 ttfPrintTextFormatted(fontStat, 8, text_y, statText);
237 printStatBonus(fontStat, stats[clientnum]->CON, statGetCON(stats[clientnum], playerEntity), 8 + longestline(statText) * fontWidth, text_y);
238
239 text_y += pad_y;
240 snprintf(statText, 64, language[1203], stats[clientnum]->INT);
241 ttfPrintTextFormatted(fontStat, 8, text_y, statText);
242 printStatBonus(fontStat, stats[clientnum]->INT, statGetINT(stats[clientnum], playerEntity), 8 + longestline(statText) * fontWidth, text_y);
243
244 text_y += pad_y;
245 snprintf(statText, 64, language[1204], stats[clientnum]->PER);
246 ttfPrintTextFormatted(fontStat, 8, text_y, statText);
247 printStatBonus(fontStat, stats[clientnum]->PER, statGetPER(stats[clientnum], playerEntity), 8 + longestline(statText) * fontWidth, text_y);
248
249 text_y += pad_y;
250 snprintf(statText, 64, language[1205], stats[clientnum]->CHR);
251 ttfPrintTextFormatted(fontStat, 8, text_y, statText);
252 printStatBonus(fontStat, stats[clientnum]->CHR, statGetCHR(stats[clientnum], playerEntity), 8 + longestline(statText) * fontWidth, text_y);
253
254 // armor, gold, and weight
255 int attackInfo[6] = { 0 };
256 text_y += pad_y * 2;
257 ttfPrintTextFormatted(fontStat, 8, text_y, language[2542], displayAttackPower(attackInfo));
258
259 text_y += pad_y;
260 ttfPrintTextFormatted(fontStat, 8, text_y, language[371], AC(stats[clientnum]));
261
262 text_y += pad_y;
263 ttfPrintTextFormatted(fontStat, 8, text_y, language[370], stats[clientnum]->GOLD);
264 Uint32 weight = 0;
265 for ( node = stats[clientnum]->inventory.first; node != NULL; node = node->next )
266 {
267 item = (Item*)node->element;
268 int itemWeight = items[item->type].weight * item->count;
269 if ( itemTypeIsQuiver(item->type) )
270 {
271 itemWeight = std::max(1, itemWeight / 5);
272 }
273 weight += itemWeight;
274 }
275 weight += stats[clientnum]->GOLD / 100;
276 text_y += pad_y;
277 ttfPrintTextFormatted(fontStat, 8, text_y, language[372], weight);
278
279 statsHoverText(stats[clientnum]);
280 attackHoverText(attackInfo);
281
282 // gold hover text.
283 SDL_Rect src;
284 src.x = mousex + 16;
285 src.y = mousey + 16;
286 src.h = TTF12_HEIGHT + 8;
287 src.w = ( longestline(language[2968]) + strlen(getInputName(impulses[IN_USE])) ) * TTF12_WIDTH + 4;
288 if ( mouseInBounds(pos.x + 4, pos.x + pos.w, text_y - pad_y, text_y) )
289 {
290 drawTooltip(&src);
291 ttfPrintTextFormatted(ttf12, src.x + 4, src.y + 6, language[2968], getInputName(impulses[IN_USE]));
292 if ( *inputPressed(impulses[IN_USE]) )
293 {
294 consoleCommand("/dropgold");
295 *inputPressed(impulses[IN_USE]) = 0;
296 }
297 else if ( *inputPressed(joyimpulses[INJOY_GAME_USE]) )
298 {
299 consoleCommand("/dropgold");
300 *inputPressed(joyimpulses[INJOY_GAME_USE]) = 0;
301 }
302 }
303 }
304
drawSkillsSheet()305 void drawSkillsSheet()
306 {
307 SDL_Rect pos;
308 pos.w = 208;
309 pos.y = 32;
310
311 TTF_Font* fontSkill = ttf12;
312 int fontHeight = TTF12_HEIGHT;
313 int fontWidth = TTF12_WIDTH;
314 if ( uiscale_skillspage )
315 {
316 fontSkill = ttf16;
317 fontHeight = TTF16_HEIGHT;
318 fontWidth = TTF16_WIDTH;
319 pos.w = 276;
320 }
321 pos.x = xres - pos.w;
322
323
324 pos.h = (NUMPROFICIENCIES * fontHeight) + (fontHeight * 3);
325
326 drawWindowFancy(pos.x, pos.y, pos.x + pos.w, pos.y + pos.h);
327 interfaceSkillsSheet.x = pos.x;
328 interfaceSkillsSheet.y = pos.y;
329 interfaceSkillsSheet.w = pos.w;
330 interfaceSkillsSheet.h = pos.h;
331
332 ttfPrintTextFormatted(fontSkill, pos.x + 4, pos.y + 8, language[1883]);
333
334 SDL_Rect button;
335 button.x = xres - attributesright_bmp->w - 8;
336 button.w = attributesright_bmp->w;
337 button.y = pos.y;
338 button.h = attributesright_bmp->h;
339 if ( uiscale_skillspage )
340 {
341 button.w = attributesright_bmp->w * 1.3;
342 button.x = xres - button.w - 8;
343 button.y = pos.y;
344 button.h = attributesright_bmp->h * 1.3;
345 }
346
347 if ( mousestatus[SDL_BUTTON_LEFT] && !shootmode )
348 {
349 if ( omousex >= button.x && omousex <= button.x + button.w
350 && omousey >= button.y && omousey <= button.y + button.h )
351 {
352 buttonclick = 14;
353 playSound(139, 64);
354 if ( proficienciesPage == 0 )
355 {
356 proficienciesPage = 1;
357 }
358 else
359 {
360 proficienciesPage = 0;
361 }
362 mousestatus[SDL_BUTTON_LEFT] = 0;
363 }
364 }
365 if ( buttonclick == 14 )
366 {
367 drawImageScaled(attributesright_bmp, nullptr, &button);
368 }
369 else
370 {
371 drawImageScaled(attributesrightunclicked_bmp, nullptr, &button);
372 }
373
374 SDL_Rect lockbtn = button;
375 lockbtn.h = 24;
376 lockbtn.w = 24;
377 lockbtn.y += 2;
378 if ( uiscale_skillspage )
379 {
380 lockbtn.h = 24 * 1.3;
381 lockbtn.w = 24 * 1.3;
382 lockbtn.x -= 32 * 1.3;
383 }
384 else
385 {
386 lockbtn.x -= 32;
387 }
388 if ( lock_right_sidebar )
389 {
390 drawImageScaled(sidebar_lock_bmp, nullptr, &lockbtn);
391 }
392 else
393 {
394 drawImageScaled(sidebar_unlock_bmp, nullptr, &lockbtn);
395 }
396
397 if ( mousestatus[SDL_BUTTON_LEFT] && !shootmode )
398 {
399 if ( omousex >= lockbtn.x && omousex <= lockbtn.x + lockbtn.w
400 && omousey >= lockbtn.y && omousey <= lockbtn.y + lockbtn.h )
401 {
402 playSound(139, 64);
403 lock_right_sidebar = !lock_right_sidebar;
404 mousestatus[SDL_BUTTON_LEFT] = 0;
405 }
406 }
407
408 pos.y += fontHeight * 2 + 8;
409
410 SDL_Rect initialSkillPos = pos;
411 //Draw skill names.
412 for ( int c = 0; c < (NUMPROFICIENCIES); ++c, pos.y += (fontHeight /** 2*/) )
413 {
414 ttfPrintTextFormatted(fontSkill, pos.x + 4, pos.y, "%s:", getSkillLangEntry(c));
415 }
416
417 //Draw skill levels.
418 pos = initialSkillPos;
419 Uint32 color;
420 for ( int i = 0; i < (NUMPROFICIENCIES); ++i, pos.y += (fontHeight /** 2*/) )
421 {
422 if ( skillCapstoneUnlocked(clientnum, i) )
423 {
424 color = uint32ColorGreen(*mainsurface);
425 }
426 else
427 {
428 color = uint32ColorWhite(*mainsurface);
429 }
430
431
432 if ( show_skill_values )
433 {
434 ttfPrintTextFormattedColor(fontSkill, pos.x + 4, pos.y, color, "%15d / 100", stats[clientnum]->PROFICIENCIES[i]);
435 }
436 else if ( stats[clientnum]->PROFICIENCIES[i] == 0 )
437 {
438 ttfPrintTextFormattedColor(fontSkill, pos.x + 4, pos.y, color, language[363]);
439 }
440 else if ( stats[clientnum]->PROFICIENCIES[i] < SKILL_LEVEL_BASIC )
441 {
442 ttfPrintTextFormattedColor(fontSkill, pos.x + 4, pos.y, color, language[364]);
443 }
444 else if ( stats[clientnum]->PROFICIENCIES[i] >= SKILL_LEVEL_BASIC && stats[clientnum]->PROFICIENCIES[i] < SKILL_LEVEL_SKILLED )
445 {
446 ttfPrintTextFormattedColor(fontSkill, pos.x + 4, pos.y, color, language[365]);
447 }
448 else if ( stats[clientnum]->PROFICIENCIES[i] >= SKILL_LEVEL_SKILLED && stats[clientnum]->PROFICIENCIES[i] < SKILL_LEVEL_EXPERT )
449 {
450 ttfPrintTextFormattedColor(fontSkill, pos.x + 4, pos.y, color, language[366]);
451 }
452 else if ( stats[clientnum]->PROFICIENCIES[i] >= SKILL_LEVEL_EXPERT && stats[clientnum]->PROFICIENCIES[i] < SKILL_LEVEL_MASTER )
453 {
454 ttfPrintTextFormattedColor(fontSkill, pos.x + 4, pos.y, color, language[367]);
455 }
456 else if ( stats[clientnum]->PROFICIENCIES[i] >= SKILL_LEVEL_MASTER && stats[clientnum]->PROFICIENCIES[i] < SKILL_LEVEL_LEGENDARY )
457 {
458 ttfPrintTextFormattedColor(fontSkill, pos.x + 4, pos.y, color, language[368]);
459 }
460 else if ( stats[clientnum]->PROFICIENCIES[i] >= SKILL_LEVEL_LEGENDARY )
461 {
462 ttfPrintTextFormattedColor(fontSkill, pos.x + 4, pos.y, color, language[369]);
463 }
464 }
465 pos = initialSkillPos;
466 SDL_Rect skillTooltipRect;
467 std::string skillTooltip;
468 for ( int i = 0; !shootmode && i < (NUMPROFICIENCIES); ++i, pos.y += (fontHeight /** 2*/) )
469 {
470 if ( mouseInBounds(pos.x, pos.x + pos.w, pos.y, pos.y + fontHeight) && stats[clientnum] )
471 {
472 skillTooltipRect.w = (longestline(language[3255 + i]) * fontWidth) + 8;
473 skillTooltip = language[3255 + i];
474
475 size_t n = std::count(skillTooltip.begin(), skillTooltip.end(), '\n'); // count newlines
476 skillTooltipRect.h = fontHeight * (n + 2) + 8;
477 skillTooltipRect.x = mousex - 16 - skillTooltipRect.w;
478 skillTooltipRect.y = mousey + 16;
479
480 Uint32 capstoneTextColor = uint32ColorGray(*mainsurface);
481 if ( skillCapstoneUnlocked(clientnum, i) )
482 {
483 capstoneTextColor = uint32ColorGreen(*mainsurface);
484 }
485
486 switch ( i )
487 {
488 case PRO_LOCKPICKING:
489 skillTooltipRect.h += 4 * fontHeight;
490 drawTooltip(&skillTooltipRect);
491 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
492 capstoneTextColor, language[3270], CAPSTONE_LOCKPICKING_CHEST_GOLD_AMOUNT);
493 break;
494 case PRO_STEALTH:
495 skillTooltipRect.h += 4 * fontHeight + 4;
496 drawTooltip(&skillTooltipRect);
497 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
498 capstoneTextColor, language[3271]);
499 break;
500 case PRO_TRADING:
501 skillTooltipRect.h += 2 * fontHeight;
502 drawTooltip(&skillTooltipRect);
503 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
504 capstoneTextColor, language[3272]);
505 break;
506 case PRO_APPRAISAL:
507 skillTooltipRect.h += 2 * fontHeight;
508 drawTooltip(&skillTooltipRect);
509 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
510 capstoneTextColor, language[3273]);
511 break;
512 case PRO_SWIMMING:
513 skillTooltipRect.h += fontHeight;
514 drawTooltip(&skillTooltipRect);
515 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
516 capstoneTextColor, language[3274]);
517 break;
518 case PRO_LEADERSHIP:
519 skillTooltipRect.h += 2 * fontHeight + 4;
520 drawTooltip(&skillTooltipRect);
521 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
522 capstoneTextColor, language[3275]);
523 break;
524 case PRO_SPELLCASTING:
525 skillTooltipRect.h += 2 * fontHeight;
526 drawTooltip(&skillTooltipRect);
527 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
528 capstoneTextColor, language[3276]);
529 break;
530 case PRO_MAGIC:
531 break;
532 case PRO_RANGED:
533 skillTooltipRect.h += 2 * fontHeight + 4;
534 drawTooltip(&skillTooltipRect);
535 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
536 capstoneTextColor, language[3284]);
537 break;
538 case PRO_SWORD:
539 {
540 skillTooltipRect.h += 5 * fontHeight + 4;
541 drawTooltip(&skillTooltipRect);
542 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
543 capstoneTextColor, language[3278], language[3283]);
544 break;
545 }
546 case PRO_MACE:
547 {
548 skillTooltipRect.h += 3 * fontHeight + 4;
549 drawTooltip(&skillTooltipRect);
550 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
551 capstoneTextColor, language[3279]);
552 break;
553 }
554 case PRO_AXE:
555 {
556 skillTooltipRect.h += 3 * fontHeight + 4;
557 drawTooltip(&skillTooltipRect);
558 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
559 capstoneTextColor, language[3280]);
560 break;
561 }
562 case PRO_POLEARM:
563 skillTooltipRect.h += 3 * fontHeight + 4;
564 drawTooltip(&skillTooltipRect);
565 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
566 capstoneTextColor, language[3281]);
567 break;
568 case PRO_UNARMED:
569 skillTooltipRect.h += 3 * fontHeight + 4;
570 drawTooltip(&skillTooltipRect);
571 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
572 capstoneTextColor, language[3282]);
573 break;
574 case PRO_SHIELD:
575 skillTooltipRect.h += 2 * fontHeight;
576 drawTooltip(&skillTooltipRect);
577 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16,
578 capstoneTextColor, language[3283]);
579 break;
580 case PRO_ALCHEMY:
581 skillTooltipRect.w = (longestline(language[3348]) * fontWidth) + 8;
582 skillTooltipRect.x = mousex - 16 - skillTooltipRect.w;
583 break;
584 default:
585 drawTooltip(&skillTooltipRect);
586 break;
587 }
588
589 Uint32 headerColor = uint32ColorBaronyBlue(*mainsurface);
590 if ( skillCapstoneUnlocked(clientnum, i) )
591 {
592 headerColor = uint32ColorGreen(*mainsurface);
593 }
594
595 if ( i != PRO_MAGIC && i != PRO_ALCHEMY )
596 {
597 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 4, skillTooltipRect.y + 8,
598 headerColor, "%s: (%d / 100)", getSkillLangEntry(i), stats[clientnum]->PROFICIENCIES[i]);
599 }
600
601 real_t skillDetails[6] = { 0.f };
602
603 switch ( i )
604 {
605 case PRO_LOCKPICKING:
606 {
607 Sint32 PER = 0;
608 if ( players[clientnum] && players[clientnum]->entity )
609 {
610 PER = statGetPER(stats[clientnum], players[clientnum]->entity);
611 }
612 statGetPER(stats[clientnum], players[clientnum]->entity);
613 skillDetails[0] = stats[clientnum]->PROFICIENCIES[i] / 2.f; // lockpick chests/doors
614 if ( stats[clientnum]->PROFICIENCIES[i] == SKILL_LEVEL_LEGENDARY )
615 {
616 skillDetails[0] = 100.f;
617 }
618 skillDetails[1] = std::min(100.f, stats[clientnum]->PROFICIENCIES[i] + 50.f);
619 if ( stats[clientnum]->PROFICIENCIES[i] >= SKILL_LEVEL_EXPERT )
620 {
621 skillDetails[2] = 100.f; // lockpick automatons
622 }
623 else
624 {
625 skillDetails[2] = (100 - 100 / (static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20 + 1))); // lockpick automatons
626 }
627 skillDetails[3] = (100 - 100 / (std::max(1, static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 10)))); // disarm arrow traps
628 if ( stats[clientnum]->PROFICIENCIES[i] < SKILL_LEVEL_BASIC )
629 {
630 skillDetails[3] = 0.f;
631 }
632 std::string canRepairItems = " no";
633 if ( (stats[clientnum]->PROFICIENCIES[i] + PER + (stats[clientnum]->type == AUTOMATON ? 20 : 0)) >= SKILL_LEVEL_LEGENDARY )
634 {
635 canRepairItems = "all";
636 }
637 else if ( (stats[clientnum]->PROFICIENCIES[i] + PER + (stats[clientnum]->type == AUTOMATON ? 20 : 0)) >= SKILL_LEVEL_MASTER )
638 {
639 canRepairItems = "2/0";
640 }
641 else if ( (stats[clientnum]->PROFICIENCIES[i] + PER + (stats[clientnum]->type == AUTOMATON ? 20 : 0)) >= SKILL_LEVEL_EXPERT )
642 {
643 canRepairItems = "1/0";
644 }
645 skillDetails[4] = maximumTinkeringBotsCanBeDeployed(stats[clientnum]);
646
647 // bonus scrapping chances.
648 switch ( std::min(5, static_cast<int>((stats[clientnum]->PROFICIENCIES[i] + PER) / 20)) )
649 {
650 case 5:
651 skillDetails[5] = 150.f;
652 break;
653 case 4:
654 skillDetails[5] = 125.f;
655 break;
656 case 3:
657 skillDetails[5] = 50.f;
658 break;
659 case 2:
660 skillDetails[5] = 25.f;
661 break;
662 case 1:
663 skillDetails[5] = 12.5;
664 break;
665 default:
666 skillDetails[5] = 0.f;
667 break;
668 }
669 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
670 uint32ColorWhite(*mainsurface), language[3255 + i],
671 skillDetails[0], skillDetails[1], skillDetails[2], skillDetails[3], skillDetails[5], canRepairItems.c_str(), skillDetails[4], getInputName(impulses[IN_FOLLOWERMENU]));
672 break;
673 }
674 case PRO_STEALTH:
675 if ( players[clientnum] && players[clientnum]->entity )
676 {
677 skillDetails[0] = players[clientnum]->entity->entityLightAfterReductions(*stats[clientnum], nullptr);
678 skillDetails[0] = std::max(1, (static_cast<int>(skillDetails[0] / 32))); // general visibility
679 skillDetails[1] = stats[clientnum]->PROFICIENCIES[i] * 2 * 100 / 512.f; // % visibility reduction of above
680 skillDetails[2] = (2 + (stats[clientnum]->PROFICIENCIES[PRO_STEALTH] / 40)); // night vision when sneaking
681 skillDetails[3] = (stats[clientnum]->PROFICIENCIES[PRO_STEALTH] / 20 + 2) * 2; // backstab dmg
682 if ( skillCapstoneUnlocked(clientnum, i) )
683 {
684 skillDetails[3] *= 2;
685 }
686 }
687 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
688 uint32ColorWhite(*mainsurface), language[3255 + i],
689 skillDetails[0], skillDetails[1], skillDetails[2], skillDetails[3]);
690
691 break;
692 case PRO_TRADING:
693 skillDetails[0] = 1 / ((50 + stats[clientnum]->PROFICIENCIES[PRO_TRADING]) / 150.f); // buy value
694 skillDetails[1] = (50 + stats[clientnum]->PROFICIENCIES[PRO_TRADING]) / 150.f; // sell value
695 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
696 uint32ColorWhite(*mainsurface), language[3255 + i],
697 skillDetails[0], skillDetails[1]);
698 break;
699 case PRO_APPRAISAL:
700 skillDetails[0] = (60.f / (stats[clientnum]->PROFICIENCIES[PRO_APPRAISAL] + 1)) / (TICKS_PER_SECOND); // appraisal time per gold value
701 if ( players[clientnum] && players[clientnum]->entity )
702 {
703 skillDetails[1] = 10 * (stats[clientnum]->PROFICIENCIES[PRO_APPRAISAL] + players[clientnum]->entity->getPER() * 5); // max gold value can appraise
704 if ( skillDetails[1] < 0.1 )
705 {
706 skillDetails[1] = 9;
707 }
708 if ( (stats[clientnum]->PROFICIENCIES[PRO_APPRAISAL] + players[clientnum]->entity->getPER() * 5) >= 100 )
709 {
710 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
711 uint32ColorWhite(*mainsurface), language[3255 + i],
712 skillDetails[0], skillDetails[1], "yes");
713 }
714 else
715 {
716 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
717 uint32ColorWhite(*mainsurface), language[3255 + i],
718 skillDetails[0], skillDetails[1], "no");
719 }
720 }
721 else
722 {
723 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
724 uint32ColorWhite(*mainsurface), language[3255 + i],
725 skillDetails[0], skillDetails[1], "no");
726 }
727 break;
728 case PRO_SWIMMING:
729 skillDetails[0] = (((stats[clientnum]->PROFICIENCIES[PRO_SWIMMING] / 100.f) * 50.f) + 50); // water movement speed
730 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
731 uint32ColorWhite(*mainsurface), language[3255 + i],
732 skillDetails[0]);
733 break;
734 case PRO_LEADERSHIP:
735 skillDetails[0] = std::min(8, std::max(4, 2 * (stats[clientnum]->PROFICIENCIES[PRO_LEADERSHIP] / 20))); // max followers
736 if ( players[clientnum] && players[clientnum]->entity )
737 {
738 skillDetails[1] = 1 + (stats[clientnum]->PROFICIENCIES[PRO_LEADERSHIP] / 20);
739 skillDetails[2] = 80 + ((players[clientnum]->entity->getCHR() + stats[clientnum]->PROFICIENCIES[PRO_LEADERSHIP]) / 20) * 10;
740 }
741 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
742 uint32ColorWhite(*mainsurface), language[3255 + i],
743 getInputName(impulses[IN_USE]),skillDetails[0], skillDetails[1], skillDetails[2], getInputName(impulses[IN_FOLLOWERMENU]));
744 break;
745 case PRO_SPELLCASTING:
746 if ( players[clientnum] && players[clientnum]->entity )
747 {
748 skillDetails[0] = players[clientnum]->entity->getManaRegenInterval(*(stats[clientnum])) / (TICKS_PER_SECOND * 1.f);
749 if ( players[clientnum]->entity->isSpellcasterBeginner() )
750 {
751 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
752 uint32ColorWhite(*mainsurface), language[3255 + i],
753 skillDetails[0], "yes");
754 }
755 else
756 {
757 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
758 uint32ColorWhite(*mainsurface), language[3255 + i],
759 skillDetails[0], "no");
760 }
761 }
762 else
763 {
764 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
765 uint32ColorWhite(*mainsurface), language[3255 + i],
766 skillDetails[0], "");
767 }
768 break;
769 case PRO_MAGIC:
770 {
771 int skillLVL = 0;
772 std::string magics = "";
773 int lines = 0;
774 if ( players[clientnum] && players[clientnum]->entity )
775 {
776 skillLVL = (stats[clientnum]->PROFICIENCIES[PRO_MAGIC] + players[clientnum]->entity->getINT());
777 if ( stats[clientnum]->PROFICIENCIES[PRO_MAGIC] >= 100 )
778 {
779 skillLVL = 100;
780 }
781 }
782 if ( skillLVL < 0 )
783 {
784 skillTooltip = "none";
785 }
786 else
787 {
788 skillLVL = skillLVL / 20;
789 skillTooltip = "tier ";
790 if ( skillLVL == 0 )
791 {
792 skillTooltip += "I";
793 }
794 else if ( skillLVL == 1 )
795 {
796 skillTooltip += "II";
797 }
798 else if ( skillLVL == 2 )
799 {
800 skillTooltip += "III";
801 }
802 else if ( skillLVL == 3 )
803 {
804 skillTooltip += "IV";
805 }
806 else if ( skillLVL == 4 )
807 {
808 skillTooltip += "V";
809 }
810 else if ( skillLVL >= 5 )
811 {
812 skillTooltip += "VI";
813 }
814 for ( auto it = allGameSpells.begin(); it != allGameSpells.end(); ++it )
815 {
816 auto spellEntry = *it;
817 if ( spellEntry && spellEntry->difficulty == (skillLVL * 20) )
818 {
819 magics += " -[";
820 magics += spellEntry->name;
821 magics += "]\n";
822 ++lines;
823 }
824 }
825 }
826 skillTooltipRect.h += (1 + lines) * (fontHeight + lines / 6);
827 drawTooltip(&skillTooltipRect);
828 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16 + (lines * (fontHeight + lines / 6)),
829 capstoneTextColor, language[3277]);
830
831 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 4, skillTooltipRect.y + 8,
832 headerColor, "%s: (%d / 100)", getSkillLangEntry(i), stats[clientnum]->PROFICIENCIES[i]);
833
834 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
835 uint32ColorWhite(*mainsurface), language[3255 + i],
836 skillTooltip.c_str());
837 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16 + (fontHeight) * 3, // print magic list
838 uint32ColorBaronyBlue(*mainsurface), "%s",
839 magics.c_str());
840 break;
841 }
842 case PRO_RANGED:
843 {
844 skillDetails[0] = 100 - (100 - stats[clientnum]->PROFICIENCIES[PRO_RANGED]) / 2.f; // lowest damage roll
845 skillDetails[1] = 50 + static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20) * 10;
846 if ( stats[clientnum]->type == GOBLIN )
847 {
848 skillDetails[1] += 20;
849 if ( stats[clientnum]->PROFICIENCIES[PRO_RANGED] < SKILL_LEVEL_LEGENDARY )
850 {
851 skillDetails[1] = std::min(skillDetails[1], 90.0);
852 }
853 }
854 if ( players[clientnum] && players[clientnum]->entity )
855 {
856 skillDetails[2] = std::min(std::max(players[clientnum]->entity->getPER() / 2, 0), 50);
857 }
858 int skillLVL = stats[clientnum]->PROFICIENCIES[PRO_RANGED] / 20; // thrown dmg bonus
859 skillDetails[3] = 100 * thrownDamageSkillMultipliers[std::min(skillLVL, 5)];
860 if ( skillCapstoneUnlocked(clientnum, i) )
861 {
862 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
863 uint32ColorWhite(*mainsurface), language[3255 + i],
864 skillDetails[0], 0.f, skillDetails[2], skillDetails[3]);
865 }
866 else
867 {
868 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
869 uint32ColorWhite(*mainsurface), language[3255 + i],
870 skillDetails[0], 100 / skillDetails[1], skillDetails[2], skillDetails[3]);
871 }
872 break;
873 }
874 case PRO_SWORD:
875 case PRO_AXE:
876 case PRO_MACE:
877 case PRO_POLEARM:
878 if ( i == PRO_POLEARM )
879 {
880 skillDetails[0] = 100 - (100 - stats[clientnum]->PROFICIENCIES[i]) / 3.f; // lowest damage roll
881 }
882 else
883 {
884 skillDetails[0] = 100 - (100 - stats[clientnum]->PROFICIENCIES[i]) / 2.f; // lowest damage roll
885 }
886 if ( skillCapstoneUnlocked(clientnum, i) )
887 {
888 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
889 uint32ColorWhite(*mainsurface), language[3255 + i],
890 skillDetails[0], 0.f, 0.f);
891 }
892 else
893 {
894 skillDetails[1] = 50 + (stats[clientnum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg
895 skillDetails[2] = 4 + (stats[clientnum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg
896 skillDetails[1] += (static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20)) * 10;
897 skillDetails[2] += static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20);
898 if ( svFlags & SV_FLAG_HARDCORE )
899 {
900 skillDetails[1] *= 2;
901 skillDetails[2] *= 2;
902 }
903 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
904 uint32ColorWhite(*mainsurface), language[3255 + i],
905 skillDetails[0], 100 / skillDetails[1], 100 / skillDetails[2]);
906 }
907 break;
908 case PRO_UNARMED:
909 skillDetails[0] = 100 - (100 - stats[clientnum]->PROFICIENCIES[i]) / 2.f; // lowest damage roll
910 skillDetails[3] = static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20);
911 skillDetails[4] = static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20) * 20;
912 if ( skillCapstoneUnlocked(clientnum, i) )
913 {
914 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
915 uint32ColorWhite(*mainsurface), language[3255 + i],
916 skillDetails[0], 0.f, 0.f, skillDetails[3], skillDetails[4]);
917 }
918 else
919 {
920 skillDetails[1] = 100 + (stats[clientnum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg
921 skillDetails[2] = 8 + (stats[clientnum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg
922 skillDetails[1] += (static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20)) * 10;
923 skillDetails[2] += static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20);
924 if ( svFlags & SV_FLAG_HARDCORE )
925 {
926 skillDetails[1] *= 2;
927 skillDetails[2] *= 2;
928 }
929 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
930 uint32ColorWhite(*mainsurface), language[3255 + i],
931 skillDetails[0], 100 / skillDetails[1], 100 / skillDetails[2], skillDetails[3], skillDetails[4]);
932 }
933 break;
934 case PRO_SHIELD:
935 skillDetails[0] = 5 + static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 5);
936 if ( skillCapstoneUnlocked(clientnum, i) )
937 {
938 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
939 uint32ColorWhite(*mainsurface), language[3255 + i],
940 skillDetails[0], 0.f, 0.f);
941 }
942 else
943 {
944 skillDetails[1] = 25 + (stats[clientnum]->type == GOBLIN ? 10 : 0); // degrade > 0 dmg taken
945 skillDetails[2] = 10 + (stats[clientnum]->type == GOBLIN ? 10 : 0); // degrade on 0 dmg
946 skillDetails[1] += (static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 10));
947 skillDetails[2] += (static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 10));
948 if ( svFlags & SV_FLAG_HARDCORE )
949 {
950 skillDetails[2] = 40 + (stats[clientnum]->type == GOBLIN ? 10 : 0);
951 }
952 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
953 uint32ColorWhite(*mainsurface), language[3255 + i],
954 skillDetails[0], 100 / skillDetails[1], 100 / skillDetails[2]);
955 }
956 break;
957 case PRO_ALCHEMY:
958 {
959 std::string baseIngredients;
960 std::string secondaryIngredients;
961 int lines = 0;
962 int lines2 = 0;
963 for ( auto it = clientLearnedAlchemyIngredients.begin(); it != clientLearnedAlchemyIngredients.end(); ++it )
964 {
965 auto alchemyEntry = *it;
966 if ( GenericGUI.isItemBaseIngredient(alchemyEntry) )
967 {
968 baseIngredients += " -[";
969 std::string itemName = items[alchemyEntry].name_identified;
970 itemName = itemName.substr(10);
971 baseIngredients += itemName;
972 baseIngredients += "]\n";
973 ++lines;
974 }
975 if ( GenericGUI.isItemSecondaryIngredient(alchemyEntry) )
976 {
977 secondaryIngredients += " -[";
978 std::string itemName = items[alchemyEntry].name_identified;
979 itemName = itemName.substr(10);
980 secondaryIngredients += itemName;
981 secondaryIngredients += "]\n";
982 ++lines2;
983 }
984 }
985 lines = std::max(lines, lines2);
986 int skillLVL = stats[clientnum]->PROFICIENCIES[i] / 20;
987 skillDetails[0] = 100 * potionDamageSkillMultipliers[std::min(skillLVL, 5)];
988 skillDetails[1] = skillDetails[0];
989 skillDetails[2] = 50.f + static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20) * 10;
990 skillDetails[3] = std::min(80, (60 + static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20) * 10));
991 skillDetails[4] = 50.f + static_cast<int>(stats[clientnum]->PROFICIENCIES[i] / 20) * 5;
992
993 skillTooltipRect.h = fontHeight * (5 + 2) + 8;
994 skillTooltipRect.h += 4 + (6 + lines) * (fontHeight + lines / 6);
995 drawTooltip(&skillTooltipRect);
996 // legendary text
997 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 16 + (lines * (fontHeight + lines / 6)),
998 capstoneTextColor, language[3347]);
999 // header text
1000 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 4, skillTooltipRect.y + 8,
1001 headerColor, "%s: (%d / 100)", getSkillLangEntry(i), stats[clientnum]->PROFICIENCIES[i]);
1002 // effect text
1003 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 12,
1004 uint32ColorWhite(*mainsurface), language[3348],
1005 skillDetails[0], skillDetails[1], skillDetails[2], skillDetails[3], skillDetails[4]);
1006 // base potions
1007 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8, skillTooltipRect.y + 20 + (fontHeight) * 9, // print potion list
1008 uint32ColorBaronyBlue(*mainsurface), "%s",
1009 baseIngredients.c_str());
1010 // secondary potions
1011 ttfPrintTextFormattedColor(fontSkill, skillTooltipRect.x + 8 + 18 * fontWidth, skillTooltipRect.y + 20 + (fontHeight) * 9, // print potion list
1012 uint32ColorBaronyBlue(*mainsurface), "%s",
1013 secondaryIngredients.c_str());
1014 break;
1015 }
1016 default:
1017 break;
1018 }
1019 }
1020 }
1021 }
1022
drawPartySheet()1023 void drawPartySheet()
1024 {
1025 SDL_Rect pos;
1026 pos.w = 208;
1027
1028 TTF_Font* fontPlayer = ttf12;
1029 int fontHeight = TTF12_HEIGHT;
1030 int fontWidth = TTF12_WIDTH;
1031 if ( uiscale_skillspage )
1032 {
1033 fontPlayer = ttf16;
1034 fontHeight = TTF16_HEIGHT;
1035 fontWidth = TTF16_WIDTH;
1036 pos.w = 276;
1037 }
1038 int playerCnt = 0;
1039 for ( playerCnt = MAXPLAYERS - 1; playerCnt > 0; --playerCnt )
1040 {
1041 if ( !client_disconnected[playerCnt] )
1042 {
1043 break;
1044 }
1045 }
1046 pos.x = xres - pos.w;
1047 pos.y = 32;
1048 pos.h = (fontHeight * 2 + 12) + ((fontHeight * 4) + 6) * (std::max(playerCnt + 1, 1));
1049
1050 int numFollowers = 0;
1051 if ( stats[clientnum] )
1052 {
1053 numFollowers = list_Size(&stats[clientnum]->FOLLOWERS);
1054 }
1055
1056 if ( playerCnt == 0 ) // 1 player.
1057 {
1058 if ( numFollowers == 0 )
1059 {
1060 if ( shootmode )
1061 {
1062 return; // don't show menu if not in inventory, no point reminding the player they have no friends!
1063 }
1064 pos.h = (fontHeight * 4 + 12);
1065 }
1066 else
1067 {
1068 pos.h = (fontHeight + 12);
1069 }
1070 }
1071 drawWindowFancy(pos.x, pos.y, pos.x + pos.w, pos.y + pos.h);
1072 interfacePartySheet.x = pos.x;
1073 interfacePartySheet.y = pos.y;
1074 interfacePartySheet.w = pos.w;
1075 interfacePartySheet.h = pos.h;
1076
1077 ttfPrintTextFormatted(fontPlayer, pos.x + 4, pos.y + 8, "Party Stats");
1078
1079 SDL_Rect button;
1080 button.x = xres - attributesright_bmp->w - 8;
1081 button.w = attributesright_bmp->w;
1082 button.y = pos.y;
1083 button.h = attributesright_bmp->h;
1084 if ( uiscale_skillspage )
1085 {
1086 button.w = attributesright_bmp->w * 1.3;
1087 button.x = xres - button.w - 8;
1088 button.y = pos.y;
1089 button.h = attributesright_bmp->h * 1.3;
1090 }
1091
1092
1093 if ( mousestatus[SDL_BUTTON_LEFT] && !shootmode )
1094 {
1095 if ( omousex >= button.x && omousex <= button.x + button.w
1096 && omousey >= button.y && omousey <= button.y + button.h )
1097 {
1098 buttonclick = 14;
1099 playSound(139, 64);
1100 if ( proficienciesPage == 0 )
1101 {
1102 proficienciesPage = 1;
1103 }
1104 else
1105 {
1106 proficienciesPage = 0;
1107 }
1108 mousestatus[SDL_BUTTON_LEFT] = 0;
1109 }
1110 }
1111 if ( buttonclick == 14 )
1112 {
1113 drawImageScaled(attributesright_bmp, nullptr, &button);
1114 }
1115 else
1116 {
1117 drawImageScaled(attributesrightunclicked_bmp, nullptr, &button);
1118 }
1119
1120 SDL_Rect lockbtn = button;
1121 lockbtn.h = 24;
1122 lockbtn.w = 24;
1123 lockbtn.y += 2;
1124 if ( uiscale_skillspage )
1125 {
1126 lockbtn.h = 24 * 1.3;
1127 lockbtn.w = 24 * 1.3;
1128 lockbtn.x -= 32 * 1.3;
1129 }
1130 else
1131 {
1132 lockbtn.x -= 32;
1133 }
1134 if ( lock_right_sidebar )
1135 {
1136 drawImageScaled(sidebar_lock_bmp, nullptr, &lockbtn);
1137 }
1138 else
1139 {
1140 drawImageScaled(sidebar_unlock_bmp, nullptr, &lockbtn);
1141 }
1142
1143 if ( mousestatus[SDL_BUTTON_LEFT] && !shootmode )
1144 {
1145 if ( omousex >= lockbtn.x && omousex <= lockbtn.x + lockbtn.w
1146 && omousey >= lockbtn.y && omousey <= lockbtn.y + lockbtn.h )
1147 {
1148 playSound(139, 64);
1149 lock_right_sidebar = !lock_right_sidebar;
1150 mousestatus[SDL_BUTTON_LEFT] = 0;
1151 }
1152 }
1153
1154 pos.y += fontHeight * 2 + 4;
1155
1156 SDL_Rect initialSkillPos = pos;
1157 SDL_Rect playerBar;
1158
1159
1160 if ( playerCnt == 0 && numFollowers == 0 ) // 1 player.
1161 {
1162 ttfPrintTextFormatted(fontPlayer, pos.x + 32, pos.y + 8, "No party members");
1163 }
1164
1165 //Draw party stats
1166 Uint32 color = uint32ColorWhite(*mainsurface);
1167 if ( playerCnt > 0 )
1168 {
1169 for ( int i = 0; i < MAXPLAYERS; ++i, pos.y += (fontHeight * 4) + 6 )
1170 {
1171 if ( !client_disconnected[i] && stats[i] )
1172 {
1173
1174 if ( strlen(stats[i]->name) > 16 )
1175 {
1176 char shortname[32] = "";
1177 strncpy(shortname, stats[i]->name, 14);
1178 strcat(shortname, "..");
1179 ttfPrintTextFormattedColor(fontPlayer, pos.x + 12, pos.y, color, "[%d] %s", i, shortname);
1180 }
1181 else
1182 {
1183 ttfPrintTextFormattedColor(fontPlayer, pos.x + 12, pos.y, color, "[%d] %s", i, stats[i]->name);
1184 }
1185
1186 ttfPrintTextFormattedColor(fontPlayer, pos.x + 12, pos.y + fontHeight, color, "%s", playerClassLangEntry(client_classes[i], i));
1187 ttfPrintTextFormattedColor(fontPlayer, xres - 8 * 12, pos.y + fontHeight, color, "LVL %2d", stats[i]->LVL);
1188
1189 playerBar.x = pos.x + 64;
1190 playerBar.w = 10 * 11;
1191 if ( uiscale_skillspage )
1192 {
1193 playerBar.x += 10;
1194 playerBar.w += 48;
1195 }
1196 playerBar.y = pos.y + fontHeight * 2 + 1;
1197 playerBar.h = fontHeight;
1198 // draw tooltip with blue outline
1199 drawTooltip(&playerBar);
1200 // draw faint red bar underneath
1201 playerBar.x += 1;
1202 drawRect(&playerBar, SDL_MapRGB(mainsurface->format, 48, 0, 0), 255);
1203
1204 // draw main red bar for current HP
1205 playerBar.w = (playerBar.w) * (static_cast<double>(stats[i]->HP) / stats[i]->MAXHP);
1206 drawRect(&playerBar, SDL_MapRGB(mainsurface->format, 128, 0, 0), 255);
1207
1208 // draw HP values
1209 ttfPrintTextFormattedColor(fontPlayer, pos.x + 32, pos.y + fontHeight * 2 + 4, color, "HP: %3d / %3d", stats[i]->HP, stats[i]->MAXHP);
1210
1211 playerBar.x = pos.x + 64;
1212 playerBar.w = 10 * 11;
1213 if ( uiscale_skillspage )
1214 {
1215 playerBar.x += 10;
1216 playerBar.w += 48;
1217 }
1218 playerBar.y = pos.y + fontHeight * 3 + 1;
1219 // draw tooltip with blue outline
1220 drawTooltip(&playerBar);
1221 playerBar.x += 1;
1222 // draw faint blue bar underneath
1223 drawRect(&playerBar, SDL_MapRGB(mainsurface->format, 0, 0, 48), 255);
1224
1225 // draw blue red bar for current MP
1226 playerBar.w = (playerBar.w) * (static_cast<double>(stats[i]->MP) / stats[i]->MAXMP);
1227 drawRect(&playerBar, SDL_MapRGB(mainsurface->format, 0, 24, 128), 255);
1228
1229 // draw MP values
1230 ttfPrintTextFormattedColor(fontPlayer, pos.x + 32 , pos.y + fontHeight * 3 + 4, color, "MP: %3d / %3d", stats[i]->MP, stats[i]->MAXMP);
1231 }
1232 }
1233 }
1234
1235
1236
1237 // draw follower stats
1238 if ( numFollowers > 0 )
1239 {
1240 int monstersToDisplay = FollowerMenu.maxMonstersToDraw;
1241 if ( playerCnt != 0 )
1242 {
1243 pos.y -= (fontHeight * 4) * 2;
1244 pos.y += std::max(playerCnt - 1, 0) * (fontHeight * 4 + 8);
1245 monstersToDisplay = FollowerMenu.numMonstersToDrawInParty();
1246 }
1247 int i = 0;
1248 SDL_Rect monsterEntryWindow;
1249 monsterEntryWindow.x = pos.x + 8;
1250 monsterEntryWindow.w = pos.w - 8;
1251 if ( numFollowers > (monstersToDisplay + 1) )
1252 {
1253 monsterEntryWindow.w -= 16;
1254 }
1255 SDL_Rect slider = monsterEntryWindow;
1256 slider.y = pos.y;
1257
1258 for ( node_t* node = stats[clientnum]->FOLLOWERS.first; node != nullptr; node = node->next, ++i )
1259 {
1260 Entity* follower = nullptr;
1261 if ( (Uint32*)node->element )
1262 {
1263 follower = uidToEntity(*((Uint32*)node->element));
1264 }
1265 if ( follower )
1266 {
1267 Stat* followerStats = follower->getStats();
1268 if ( followerStats )
1269 {
1270 monsterEntryWindow.y = pos.y;
1271 monsterEntryWindow.h = fontHeight * 2 + 12;
1272
1273 bool hideDetail = false;
1274 if ( numFollowers > monstersToDisplay )
1275 {
1276 if ( i < FollowerMenu.sidebarScrollIndex )
1277 {
1278 hideDetail = true;
1279 }
1280 else if ( i > FollowerMenu.sidebarScrollIndex + monstersToDisplay )
1281 {
1282 hideDetail = true;
1283 }
1284 }
1285
1286 if ( !hideDetail )
1287 {
1288 drawWindowFancy(monsterEntryWindow.x, monsterEntryWindow.y,
1289 monsterEntryWindow.x + monsterEntryWindow.w, monsterEntryWindow.y + monsterEntryWindow.h);
1290
1291 if ( !FollowerMenu.recentEntity )
1292 {
1293 FollowerMenu.recentEntity = follower;
1294 }
1295 if ( FollowerMenu.recentEntity == follower )
1296 {
1297 // draw highlight on current selected monster.
1298 drawRect(&monsterEntryWindow, uint32ColorBaronyBlue(*mainsurface), 32);
1299 // ttfPrintText(ttf16, xres - 20, monsterEntryWindow.y + monsterEntryWindow.h / 2 - fontHeight / 2, "<");
1300 }
1301
1302 if ( stats[clientnum] && stats[clientnum]->HP > 0 && !shootmode
1303 && (mousestatus[SDL_BUTTON_LEFT] || (*inputPressed(impulses[IN_USE]) || *inputPressed(joyimpulses[INJOY_GAME_USE]))) )
1304 {
1305 bool inBounds = mouseInBounds(monsterEntryWindow.x, monsterEntryWindow.x + monsterEntryWindow.w,
1306 monsterEntryWindow.y, monsterEntryWindow.y + monsterEntryWindow.h);
1307 if ( inBounds )
1308 {
1309 if ( mousestatus[SDL_BUTTON_LEFT] )
1310 {
1311 FollowerMenu.recentEntity = follower;
1312 playSound(139, 64);
1313 FollowerMenu.accessedMenuFromPartySheet = true;
1314 FollowerMenu.partySheetMouseX = omousex;
1315 FollowerMenu.partySheetMouseY = omousey;
1316 mousestatus[SDL_BUTTON_LEFT] = 0;
1317 if ( FollowerMenu.recentEntity )
1318 {
1319 createParticleFollowerCommand(FollowerMenu.recentEntity->x, FollowerMenu.recentEntity->y, 0, 174);
1320 }
1321 }
1322 else if ( (*inputPressed(impulses[IN_USE]) || *inputPressed(joyimpulses[INJOY_GAME_USE])) )
1323 {
1324 FollowerMenu.followerToCommand = follower;
1325 FollowerMenu.recentEntity = follower;
1326 FollowerMenu.accessedMenuFromPartySheet = true;
1327 FollowerMenu.partySheetMouseX = omousex;
1328 FollowerMenu.partySheetMouseY = omousey;
1329 FollowerMenu.initFollowerMenuGUICursor();
1330 FollowerMenu.updateScrollPartySheet();
1331 if ( FollowerMenu.recentEntity )
1332 {
1333 createParticleFollowerCommand(FollowerMenu.recentEntity->x, FollowerMenu.recentEntity->y, 0, 174);
1334 }
1335 }
1336 }
1337 }
1338
1339 pos.y += 6;
1340 char name[16] = "";
1341 if ( strcmp(followerStats->name, "") && strcmp(followerStats->name, "nothing") )
1342 {
1343 if ( strlen(followerStats->name) > 10 )
1344 {
1345 if ( followerStats->type == SKELETON )
1346 {
1347 if ( !strcmp(followerStats->name, "skeleton sentinel") )
1348 {
1349 strncpy(name, "sentinel", 8);
1350 }
1351 else if ( !strcmp(followerStats->name, "skeleton knight") )
1352 {
1353 strncpy(name, "knight", 6);
1354 }
1355 else
1356 {
1357 strncpy(name, followerStats->name, 8);
1358 strcat(name, "..");
1359 }
1360 }
1361 else
1362 {
1363 strncpy(name, followerStats->name, 8);
1364 strcat(name, "..");
1365 }
1366 ttfPrintTextFormattedColor(fontPlayer, pos.x + 20, pos.y, color, "%s", name);
1367 }
1368 else
1369 {
1370 ttfPrintTextFormattedColor(fontPlayer, pos.x + 20, pos.y, color, "%s", followerStats->name);
1371 }
1372 }
1373 else
1374 {
1375 if ( strlen(monstertypename[followerStats->type]) > 10 )
1376 {
1377 strncpy(name, monstertypename[followerStats->type], 8);
1378 strcat(name, "..");
1379 ttfPrintTextFormattedColor(fontPlayer, pos.x + 20, pos.y, color, "%s", name);
1380 }
1381 else
1382 {
1383 ttfPrintTextFormattedColor(fontPlayer, pos.x + 20, pos.y, color, "%s", monstertypename[followerStats->type]);
1384 }
1385 }
1386 ttfPrintTextFormattedColor(fontPlayer, xres - 8 * 11, pos.y, color, "LVL %2d", followerStats->LVL);
1387
1388 playerBar.x = pos.x + 64;
1389 playerBar.w = 10 * 11;
1390 if ( uiscale_skillspage )
1391 {
1392 playerBar.x += 10;
1393 playerBar.w += 48;
1394 }
1395 playerBar.y = pos.y + fontHeight + 1;
1396 playerBar.h = fontHeight;
1397 // draw tooltip with blue outline
1398 drawTooltip(&playerBar);
1399 // draw faint red bar underneath
1400 playerBar.x += 1;
1401 drawRect(&playerBar, SDL_MapRGB(mainsurface->format, 48, 0, 0), 255);
1402
1403 // draw main red bar for current HP
1404 playerBar.w = (playerBar.w) * (static_cast<double>(followerStats->HP) / followerStats->MAXHP);
1405 drawRect(&playerBar, SDL_MapRGB(mainsurface->format, 128, 0, 0), 255);
1406
1407 // draw HP values
1408 ttfPrintTextFormattedColor(fontPlayer, pos.x + 32, pos.y + fontHeight + 4, color, "HP: %3d / %3d", followerStats->HP, followerStats->MAXHP);
1409 pos.y += (fontHeight * 2 + 6);
1410 }
1411 }
1412 }
1413 }
1414 slider.x = xres - 16;
1415 slider.w = 16;
1416 slider.h = (fontHeight * 2 + 12) * (std::min(monstersToDisplay + 1, numFollowers));
1417 interfacePartySheet.h += slider.h + 6;
1418
1419 if ( numFollowers > (monstersToDisplay + 1) )
1420 {
1421 drawDepressed(slider.x, slider.y, slider.x + slider.w,
1422 slider.y + slider.h);
1423
1424 bool mouseInScrollbarTotalHeight = mouseInBounds(xres - monsterEntryWindow.w, xres, slider.y,
1425 slider.y + slider.h);
1426
1427 if ( mousestatus[SDL_BUTTON_WHEELDOWN] && mouseInScrollbarTotalHeight )
1428 {
1429 mousestatus[SDL_BUTTON_WHEELDOWN] = 0;
1430 FollowerMenu.sidebarScrollIndex = std::min(FollowerMenu.sidebarScrollIndex + 1, numFollowers - monstersToDisplay - 1);
1431 }
1432 else if ( mousestatus[SDL_BUTTON_WHEELUP] && mouseInScrollbarTotalHeight )
1433 {
1434 mousestatus[SDL_BUTTON_WHEELUP] = 0;
1435 FollowerMenu.sidebarScrollIndex = std::max(FollowerMenu.sidebarScrollIndex - 1, 0);
1436 }
1437
1438 slider.h *= (1 / static_cast<real_t>(std::max(1, numFollowers - monstersToDisplay)));
1439 slider.y += slider.h * FollowerMenu.sidebarScrollIndex;
1440 drawWindowFancy(slider.x, slider.y, slider.x + slider.w, slider.y + slider.h);
1441 if ( mouseInScrollbarTotalHeight && mousestatus[SDL_BUTTON_LEFT] )
1442 {
1443 if ( !mouseInBounds(xres - monsterEntryWindow.w, xres, slider.y,
1444 slider.y + slider.h) )
1445 {
1446 if ( omousey < slider.y )
1447 {
1448 FollowerMenu.sidebarScrollIndex = std::max(FollowerMenu.sidebarScrollIndex - 1, 0);
1449 mousestatus[SDL_BUTTON_LEFT] = 0;
1450 }
1451 else if ( omousey > slider.y + slider.h )
1452 {
1453 FollowerMenu.sidebarScrollIndex = std::min(FollowerMenu.sidebarScrollIndex + 1, numFollowers - monstersToDisplay - 1);
1454 mousestatus[SDL_BUTTON_LEFT] = 0;
1455 }
1456 }
1457 }
1458 }
1459 }
1460 }
1461
statsHoverText(Stat * tmpStat)1462 void statsHoverText(Stat* tmpStat)
1463 {
1464 if ( tmpStat == nullptr )
1465 {
1466 return;
1467 }
1468
1469 int pad_y = 262; // 262 px.
1470 int pad_x = 8; // 8 px.
1471 int off_h = TTF12_HEIGHT - 4; // 12px. height of stat line.
1472 int off_w = 216; // 216px. width of stat line.
1473 int i = 0;
1474 int j = 0;
1475 SDL_Rect src;
1476 SDL_Rect pos;
1477
1478 if ( uiscale_charactersheet )
1479 {
1480 pad_y += 86;
1481 off_h = TTF16_HEIGHT - 4;
1482 off_w = 280;
1483 }
1484
1485 int tooltip_offset_x = 16; // 16px.
1486 int tooltip_offset_y = 16; // 16px.
1487 int tooltip_base_h = TTF12_HEIGHT;
1488 int tooltip_pad_h = 8;
1489 int tooltip_text_pad_x = 8;
1490
1491 char tooltipHeader[6][128] =
1492 {
1493 "strength: ",
1494 "dexterity: ",
1495 "constitution: ",
1496 "intelligence: ",
1497 "perception: ",
1498 "charisma: "
1499 };
1500
1501 char tooltipText[6][5][128] =
1502 {
1503 {
1504 "base: %2d ",
1505 "bonus: %2d ",
1506 ""
1507 },
1508 {
1509 "base: %2d ",
1510 "bonus: %2d ",
1511 ""
1512 },
1513 {
1514 "base: %2d ",
1515 "bonus: %2d ",
1516 "HP regen rate: 1 / %2.1fs",
1517 ""
1518 },
1519 {
1520 "base: %2d ",
1521 "bonus: %2d ",
1522 "MP regen rate: 1 / %2.1fs",
1523 "magic damage: %3d%%",
1524 "magic resistance: %2.1f%% "
1525 },
1526 {
1527 "base: %2d ",
1528 "bonus: %2d ",
1529 ""
1530 },
1531 {
1532 "base: %2d ",
1533 "bonus: %2d ",
1534 ""
1535 }
1536 };
1537
1538 char buf[128] = "";
1539 int numInfoLines = 0;
1540 Sint32 statBase = 0;
1541 Sint32 statBonus = 0;
1542
1543 SDL_Surface *tmp_bmp = NULL;
1544
1545 Entity* playerEntity = nullptr;
1546 if ( players[clientnum] )
1547 {
1548 playerEntity = players[clientnum]->entity;
1549 }
1550
1551 if ( attributespage == 0 )
1552 {
1553 for ( i = 0; i < 6; i++ ) // cycle through 6 stats.
1554 {
1555 switch ( i )
1556 {
1557 // prepare the stat image.
1558 case 0:
1559 numInfoLines = 2;
1560 tmp_bmp = str_bmp64;
1561 statBase = tmpStat->STR;
1562 statBonus = statGetSTR(tmpStat, playerEntity) - statBase;
1563 break;
1564 case 1:
1565 numInfoLines = 2;
1566 tmp_bmp = dex_bmp64;
1567 statBase = tmpStat->DEX;
1568 statBonus = statGetDEX(tmpStat, playerEntity) - statBase;
1569 break;
1570 case 2:
1571 numInfoLines = 3;
1572 tmp_bmp = con_bmp64;
1573 statBase = tmpStat->CON;
1574 statBonus = statGetCON(tmpStat, playerEntity) - statBase;
1575 break;
1576 case 3:
1577 numInfoLines = 5;
1578 tmp_bmp = int_bmp64;
1579 statBase = tmpStat->INT;
1580 statBonus = statGetINT(tmpStat, playerEntity) - statBase;
1581 break;
1582 case 4:
1583 numInfoLines = 2;
1584 tmp_bmp = per_bmp64;
1585 statBase = tmpStat->PER;
1586 statBonus = statGetPER(tmpStat, playerEntity) - statBase;
1587 break;
1588 case 5:
1589 numInfoLines = 2;
1590 tmp_bmp = chr_bmp64;
1591 statBase = tmpStat->CHR;
1592 statBonus = statGetCHR(tmpStat, playerEntity) - statBase;
1593 break;
1594 default:
1595 numInfoLines = 0;
1596 break;
1597 }
1598
1599 if ( mouseInBounds(pad_x, pad_x + off_w, pad_y, pad_y + off_h) )
1600 {
1601 src.x = mousex + tooltip_offset_x;
1602 src.y = mousey + tooltip_offset_y;
1603 src.h = std::max(tooltip_base_h * (numInfoLines + 1) + tooltip_pad_h, tooltip_base_h * (2) + tooltip_pad_h);
1604 src.w = 180;
1605 for ( j = 0; j < numInfoLines; j++ )
1606 {
1607 src.w = std::max(longestline(tooltipText[i][j]) * 12, src.w);
1608 }
1609 drawTooltip(&src);
1610
1611 pos.x = src.x + 6;
1612 pos.y = src.y + 4;
1613 pos.h = 32;
1614 pos.w = 32;
1615
1616 drawImageScaled(tmp_bmp, NULL, &pos);
1617
1618 src.x = pos.x + pos.w;
1619 src.y += 2;
1620
1621 ttfPrintText(ttf12, src.x + 4, src.y + 4, tooltipHeader[i]);
1622
1623 for ( j = 0; j < numInfoLines && numInfoLines > 0; j++ )
1624 {
1625 int infoText_x = src.x + 4 + tooltip_text_pad_x;
1626 int infoText_y = src.y + 4 + (tooltip_base_h * (j + 1));
1627 Uint32 color = uint32ColorWhite(*mainsurface);
1628
1629 if ( j == 0 )
1630 {
1631 snprintf(buf, longestline(tooltipText[i][j]), tooltipText[i][j], statBase);
1632 }
1633 else if ( j == 1 )
1634 {
1635 snprintf(buf, longestline(tooltipText[i][j]), tooltipText[i][j], statBonus);
1636 if ( statBonus > 0 )
1637 {
1638 color = uint32ColorGreen(*mainsurface);
1639 }
1640 else if ( statBonus < 0 )
1641 {
1642 color = uint32ColorRed(*mainsurface);
1643 }
1644 }
1645 else if ( j == 2 )
1646 {
1647 if ( i == 3 )
1648 {
1649 Entity* tmp = nullptr;
1650 if ( players[clientnum] && players[clientnum]->entity )
1651 {
1652 tmp = players[clientnum]->entity;
1653 real_t regen = (static_cast<real_t>(tmp->getManaRegenInterval(*tmpStat)) / TICKS_PER_SECOND);
1654 if ( stats[clientnum]->type == AUTOMATON )
1655 {
1656 if ( stats[clientnum]->HUNGER <= 300 )
1657 {
1658 regen /= 6; // degrade faster
1659 }
1660 else if ( stats[clientnum]->HUNGER > 1200 )
1661 {
1662 if ( stats[clientnum]->MP / static_cast<real_t>(std::max(1, stats[clientnum]->MAXMP)) <= 0.5 )
1663 {
1664 regen /= 4; // increase faster at < 50% mana
1665 }
1666 else
1667 {
1668 regen /= 2; // increase less faster at > 50% mana
1669 }
1670 }
1671 else if ( stats[clientnum]->HUNGER > 300 )
1672 {
1673 // normal manaRegenInterval 300-1200 hunger.
1674 }
1675 }
1676
1677 if ( regen < 0.f /*stats[clientnum]->playerRace == RACE_INSECTOID && stats[clientnum]->appearance == 0*/ )
1678 {
1679 regen = 0.f;
1680 snprintf(buf, longestline("MP regen rate: 0 / %2.1fs"), "MP regen rate: 0 / %2.1fs", (static_cast<real_t>(MAGIC_REGEN_TIME) / TICKS_PER_SECOND));
1681 }
1682 else
1683 {
1684 snprintf(buf, longestline(tooltipText[i][j]), tooltipText[i][j], regen);
1685 }
1686
1687 if ( stats[clientnum]->type == AUTOMATON )
1688 {
1689 if ( stats[clientnum]->HUNGER <= 300 )
1690 {
1691 color = uint32ColorRed(*mainsurface);
1692 }
1693 else if ( regen < static_cast<real_t>(tmp->getBaseManaRegen(*tmpStat)) / TICKS_PER_SECOND )
1694 {
1695 color = uint32ColorGreen(*mainsurface);
1696 }
1697 }
1698 else if ( stats[clientnum]->playerRace == RACE_INSECTOID && stats[clientnum]->appearance == 0 )
1699 {
1700 if ( !(svFlags & SV_FLAG_HUNGER) )
1701 {
1702 color = uint32ColorWhite(*mainsurface);
1703 }
1704 else
1705 {
1706 color = uint32ColorRed(*mainsurface);
1707 }
1708 }
1709 else if ( regen < static_cast<real_t>(tmp->getBaseManaRegen(*tmpStat)) / TICKS_PER_SECOND)
1710 {
1711 color = uint32ColorGreen(*mainsurface);
1712 }
1713
1714 }
1715 else
1716 {
1717 snprintf(buf, longestline(tooltipText[i][j]), tooltipText[i][j], 0.f);
1718 }
1719 }
1720 else if ( i == 2 )
1721 {
1722 Entity* tmp = nullptr;
1723 if ( players[clientnum] && players[clientnum]->entity )
1724 {
1725 tmp = players[clientnum]->entity;
1726 real_t regen = (static_cast<real_t>(tmp->getHealthRegenInterval(*tmpStat)) / TICKS_PER_SECOND);
1727 if ( tmpStat->type == SKELETON )
1728 {
1729 if ( !(svFlags & SV_FLAG_HUNGER) )
1730 {
1731 regen = HEAL_TIME * 4 / TICKS_PER_SECOND;
1732 }
1733 }
1734 if ( regen < 0 )
1735 {
1736 regen = 0.f;
1737 if ( !(svFlags & SV_FLAG_HUNGER) )
1738 {
1739 color = uint32ColorWhite(*mainsurface);
1740 }
1741 else
1742 {
1743 color = uint32ColorRed(*mainsurface);
1744 }
1745 snprintf(buf, longestline("HP regen rate: 0 / %2.1fs"), "HP regen rate: 0 / %2.1fs", (static_cast<real_t>(HEAL_TIME) / TICKS_PER_SECOND));
1746 }
1747 else if ( regen < HEAL_TIME / TICKS_PER_SECOND )
1748 {
1749 color = uint32ColorGreen(*mainsurface);
1750 }
1751 if ( regen > 0.f )
1752 {
1753 snprintf(buf, longestline(tooltipText[i][j]), tooltipText[i][j], regen);
1754 }
1755 }
1756 else
1757 {
1758 snprintf(buf, longestline(tooltipText[i][j]), tooltipText[i][j], 0.f);
1759 }
1760 }
1761 }
1762 else if ( j == 3 )
1763 {
1764 if ( i == 3 )
1765 {
1766 int bonusDamage = 100;
1767 if ( players[clientnum] && players[clientnum]->entity )
1768 {
1769 bonusDamage += 100 * (getBonusFromCasterOfSpellElement(players[clientnum]->entity, nullptr));
1770 }
1771 snprintf(buf, longestline(tooltipText[i][j]), tooltipText[i][j], bonusDamage);
1772 }
1773 }
1774 else if ( j == 4 )
1775 {
1776 if ( i == 3 )
1777 {
1778 Entity* tmp = nullptr;
1779 real_t resistance = 0.f;
1780 if ( players[clientnum] && players[clientnum]->entity )
1781 {
1782 tmp = players[clientnum]->entity;
1783 real_t resistance = 100 - 100 / (tmp->getMagicResistance() + 1);
1784 snprintf(buf, longestline(tooltipText[i][j]), tooltipText[i][j], resistance);
1785 if ( resistance > 0.f )
1786 {
1787 color = uint32ColorGreen(*mainsurface);
1788 }
1789 }
1790 else
1791 {
1792 snprintf(buf, longestline(tooltipText[i][j]), tooltipText[i][j], 0);
1793 }
1794 }
1795 }
1796 ttfPrintTextColor(ttf12, infoText_x, infoText_y, color, false, buf);
1797 }
1798 }
1799 numInfoLines = 0;
1800 pad_y += off_h;
1801 }
1802 }
1803 }
1804
displayAttackPower(Sint32 output[6])1805 Sint32 displayAttackPower(Sint32 output[6])
1806 {
1807 Sint32 attack = 0;
1808 Entity* entity = nullptr;
1809 if ( players[clientnum] && (entity = players[clientnum]->entity) )
1810 {
1811 if ( stats[clientnum] )
1812 {
1813 bool shapeshiftUseMeleeAttack = false;
1814 if ( entity->effectShapeshift != NOTHING )
1815 {
1816 shapeshiftUseMeleeAttack = true;
1817 if ( entity->effectShapeshift == CREATURE_IMP
1818 && stats[clientnum]->weapon && itemCategory(stats[clientnum]->weapon) == MAGICSTAFF )
1819 {
1820 shapeshiftUseMeleeAttack = false;
1821 }
1822 }
1823
1824 if ( !stats[clientnum]->weapon || shapeshiftUseMeleeAttack )
1825 {
1826 // fists
1827 attack += entity->getAttack();
1828 output[0] = 0; // melee
1829 output[1] = attack;
1830 output[2] = (stats[clientnum]->PROFICIENCIES[PRO_UNARMED] / 20); // bonus from proficiency
1831 output[3] = entity->getSTR(); // bonus from main attribute
1832 output[5] = attack - entity->getSTR() - BASE_PLAYER_UNARMED_DAMAGE - output[2]; // bonus from equipment
1833 // get damage variances.
1834 output[4] = (attack / 2) * (100 - stats[clientnum]->PROFICIENCIES[PRO_UNARMED]) / 100.f;
1835 attack -= (output[4] / 2); // attack is the midpoint between max and min damage.
1836 output[4] = ((output[4] / 2) / static_cast<real_t>(attack)) * 100.f;// return percent variance
1837 output[1] = attack;
1838 }
1839 else
1840 {
1841 int weaponskill = getWeaponSkill(stats[clientnum]->weapon);
1842 real_t variance = 0;
1843 if ( weaponskill == PRO_RANGED )
1844 {
1845 if ( isRangedWeapon(*stats[clientnum]->weapon) )
1846 {
1847 attack += entity->getRangedAttack();
1848 output[0] = 1; // ranged
1849 output[1] = attack;
1850 output[2] = stats[clientnum]->weapon->weaponGetAttack(stats[clientnum]); // bonus from weapon
1851 output[5] = 0;
1852 if ( stats[clientnum]->shield && rangedWeaponUseQuiverOnAttack(stats[clientnum]) )
1853 {
1854 int quiverATK = stats[clientnum]->shield->weaponGetAttack(stats[clientnum]);
1855 output[5] += quiverATK;
1856 attack += quiverATK;
1857 }
1858 output[3] = entity->getDEX(); // bonus from main attribute
1859 //output[4] = attack - output[2] - output[3] - BASE_RANGED_DAMAGE; // bonus from proficiency
1860
1861 output[4] = (attack / 2) * (100 - stats[clientnum]->PROFICIENCIES[weaponskill]) / 100.f;
1862 attack -= (output[4] / 2);
1863 output[4] = ((output[4] / 2) / static_cast<real_t>(attack)) * 100.f;// return percent variance
1864 output[1] = attack;
1865 }
1866 else if ( stats[clientnum]->weapon && stats[clientnum]->weapon->type == TOOL_WHIP )
1867 {
1868 attack += entity->getAttack();
1869 output[0] = 6; // ranged
1870 output[1] = attack;
1871 output[2] = stats[clientnum]->weapon->weaponGetAttack(stats[clientnum]); // bonus from weapon
1872 int atk = entity->getSTR() + entity->getDEX();
1873 atk = std::min(atk / 2, atk);
1874 output[3] = atk; // bonus from main attribute
1875 //output[4] = attack - output[2] - output[3] - BASE_RANGED_DAMAGE; // bonus from proficiency
1876
1877 output[4] = (attack / 2) * (100 - stats[clientnum]->PROFICIENCIES[weaponskill]) / 100.f;
1878 attack -= (output[4] / 2);
1879 output[4] = ((output[4] / 2) / static_cast<real_t>(attack)) * 100.f;// return percent variance
1880 output[1] = attack;
1881 }
1882 else
1883 {
1884 int skillLVL = stats[clientnum]->PROFICIENCIES[PRO_RANGED] / 20;
1885 attack += entity->getThrownAttack();
1886 output[0] = 2; // thrown
1887 output[1] = attack;
1888 // bonus from weapon
1889 output[2] = stats[clientnum]->weapon->weaponGetAttack(stats[clientnum]);
1890 // bonus from dex
1891 if ( itemCategory(stats[clientnum]->weapon) != POTION )
1892 {
1893 output[3] = entity->getDEX() / 4;
1894 }
1895 else
1896 {
1897 output[3] = 0.f;
1898 }
1899 // bonus from proficiency
1900 output[4] = attack - output[2] - output[3] - BASE_THROWN_DAMAGE;
1901 output[5] = 0; // bonus from equipment
1902 }
1903 }
1904 else if ( (weaponskill >= PRO_SWORD && weaponskill <= PRO_POLEARM) )
1905 {
1906 // melee weapon
1907 attack += entity->getAttack();
1908 output[0] = 3; // melee
1909 output[1] = attack;
1910 output[2] = stats[clientnum]->weapon->weaponGetAttack(stats[clientnum]); // bonus from weapon
1911 output[3] = entity->getSTR(); // bonus from main attribute
1912 if ( weaponskill == PRO_AXE )
1913 {
1914 output[5] = 1; // bonus from equipment
1915 attack += 1;
1916 }
1917 // get damage variances.
1918 if ( weaponskill == PRO_POLEARM )
1919 {
1920 output[4] = (attack / 3) * (100 - stats[clientnum]->PROFICIENCIES[weaponskill]) / 100.f;
1921 }
1922 else
1923 {
1924 output[4] = (attack / 2) * (100 - stats[clientnum]->PROFICIENCIES[weaponskill]) / 100.f;
1925 }
1926 attack -= (output[4] / 2); // attack is the midpoint between max and min damage.
1927 output[4] = ((output[4] / 2) / static_cast<real_t>(attack)) * 100.f;// return percent variance
1928 output[1] = attack;
1929 }
1930 else if ( itemCategory(stats[clientnum]->weapon) == MAGICSTAFF ) // staffs.
1931 {
1932 attack = 0;
1933 output[0] = 5; // staffs
1934 output[1] = attack;
1935 output[2] = 0; // bonus from weapon
1936 output[3] = 0; // bonus from main attribute
1937 output[4] = 0; // bonus from proficiency
1938 output[5] = 0; // bonus from equipment
1939 }
1940 else // tools etc.
1941 {
1942 attack += entity->getAttack();
1943 output[0] = 4; // tools
1944 output[1] = attack;
1945 output[2] = 0; // bonus from weapon
1946 output[3] = entity->getSTR(); // bonus from main attribute
1947 output[4] = 0; // bonus from proficiency
1948 output[5] = attack - entity->getSTR() - BASE_MELEE_DAMAGE; // bonus from equipment
1949 }
1950 }
1951 }
1952 }
1953 else
1954 {
1955 attack = 0;
1956 }
1957 return attack;
1958 }
1959
attackHoverText(Sint32 input[6])1960 void attackHoverText(Sint32 input[6])
1961 {
1962 int pad_y = 346; // 262 px.
1963 int pad_x = 8; // 8 px.
1964 int off_h = TTF12_HEIGHT - 4; // 12px. height of stat line.
1965 int off_w = 216; // 216px. width of stat line.
1966 int i = 0;
1967 int j = 0;
1968 SDL_Rect src;
1969 SDL_Rect pos;
1970 int tooltip_offset_x = 16; // 16px.
1971 int tooltip_offset_y = 16; // 16px.
1972 int tooltip_base_h = TTF12_HEIGHT;
1973 int tooltip_pad_h = 8;
1974 int tooltip_text_pad_x = 16;
1975 int numInfoLines = 3;
1976 char buf[128] = "";
1977
1978 if ( uiscale_charactersheet )
1979 {
1980 off_h = TTF16_HEIGHT - 4;
1981 pad_y += 126;
1982 off_w = 280;
1983 }
1984
1985 if ( attributespage == 0 )
1986 {
1987 if ( mouseInBounds(pad_x, pad_x + off_w, pad_y, pad_y + off_h) )
1988 {
1989 char tooltipHeader[32] = "";
1990 switch ( input[0] )
1991 {
1992 case 0: // fists
1993 snprintf(tooltipHeader, strlen(language[2529]), language[2529]);
1994 numInfoLines = 4;
1995 break;
1996 case 1: // ranged
1997 snprintf(tooltipHeader, strlen(language[2530]), language[2530]);
1998 numInfoLines = 4;
1999 break;
2000 case 2: // thrown
2001 snprintf(tooltipHeader, strlen(language[2531]), language[2531]);
2002 numInfoLines = 3;
2003 break;
2004 case 3: // melee
2005 snprintf(tooltipHeader, strlen(language[2532]), language[2532]);
2006 numInfoLines = 4;
2007 break;
2008 case 4: // tools
2009 snprintf(tooltipHeader, strlen(language[2540]), language[2540]);
2010 numInfoLines = 2;
2011 break;
2012 case 5: // staffs
2013 snprintf(tooltipHeader, strlen(language[2541]), language[2541]);
2014 numInfoLines = 0;
2015 break;
2016 case 6: // whip
2017 snprintf(tooltipHeader, strlen(language[2530]), language[2530]);
2018 numInfoLines = 3;
2019 break;
2020 default:
2021 break;
2022 }
2023
2024 // get tooltip draw location.
2025 src.x = mousex + tooltip_offset_x;
2026 src.y = mousey + tooltip_offset_y;
2027 src.h = std::max(tooltip_base_h * (numInfoLines + 1) + tooltip_pad_h, tooltip_base_h * (2) + tooltip_pad_h);
2028 src.w = 256;
2029 if ( input[0] == 6 ) // whip tooltip wider
2030 {
2031 src.w += 32;
2032 }
2033 drawTooltip(&src);
2034
2035 // draw header
2036 Uint32 color = uint32ColorWhite(*mainsurface);
2037 ttfPrintTextColor(ttf12, src.x + 4, src.y + 4, color, false, tooltipHeader);
2038 if ( input[1] >= 0 )
2039 {
2040 // attack >= 0
2041 color = SDL_MapRGB(mainsurface->format, 0, 255, 255);
2042 }
2043 else
2044 {
2045 // attack < 0
2046 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
2047 }
2048 snprintf(tooltipHeader, 32, language[2533], input[1]);
2049 ttfPrintTextColor(ttf12, src.x + 4, src.y + 4, color, false, tooltipHeader);
2050
2051 for ( j = 0; j < numInfoLines && numInfoLines > 0; j++ )
2052 {
2053 int infoText_x = src.x + 4 + tooltip_text_pad_x;
2054 int infoText_y = src.y + 4 + (tooltip_base_h * (j + 1));
2055 Uint32 color = uint32ColorWhite(*mainsurface);
2056
2057 if ( input[0] == 0 ) // fists
2058 {
2059 switch ( j )
2060 {
2061 case 0:
2062 snprintf(buf, longestline(language[3209]), language[3209], input[2]);
2063 break;
2064 case 1:
2065 snprintf(buf, longestline(language[2534]), language[2534], input[3]);
2066 break;
2067 case 2:
2068 snprintf(buf, longestline(language[2539]), language[2539], input[4]);
2069 break;
2070 case 3:
2071 snprintf(buf, longestline(language[2536]), language[2536], input[5]);
2072 default:
2073 break;
2074 }
2075 }
2076 else if ( input[0] == 1 ) // ranged
2077 {
2078 switch ( j )
2079 {
2080 case 0:
2081 snprintf(buf, longestline(language[2538]), language[2538], input[2]);
2082 break;
2083 case 1:
2084 snprintf(buf, longestline(language[2535]), language[2535], input[3]);
2085 break;
2086 case 2:
2087 snprintf(buf, longestline(language[2539]), language[2539], input[4]);
2088 break;
2089 case 3:
2090 snprintf(buf, longestline(language[2536]), language[2536], input[5]);
2091 break;
2092 default:
2093 break;
2094 }
2095 }
2096 else if ( input[0] == 2 ) // thrown
2097 {
2098 switch ( j )
2099 {
2100 case 0:
2101 snprintf(buf, longestline(language[2538]), language[2538], input[2]);
2102 break;
2103 case 1:
2104 snprintf(buf, longestline(language[2535]), language[2535], input[3]);
2105 break;
2106 case 2:
2107 snprintf(buf, longestline(language[2537]), language[2537], input[4]);
2108 break;
2109 default:
2110 break;
2111 }
2112 }
2113 else if ( input[0] == 3 ) // melee weapons
2114 {
2115 switch ( j )
2116 {
2117 case 0:
2118 snprintf(buf, longestline(language[2538]), language[2538], input[2]);
2119 break;
2120 case 1:
2121 snprintf(buf, longestline(language[2534]), language[2534], input[3]);
2122 break;
2123 case 2:
2124 snprintf(buf, longestline(language[2539]), language[2539], input[4]);
2125 break;
2126 case 3:
2127 snprintf(buf, longestline(language[2536]), language[2536], input[5]);
2128 default:
2129 break;
2130 }
2131 }
2132 else if ( input[0] == 4 ) // tools
2133 {
2134 switch ( j )
2135 {
2136 case 0:
2137 snprintf(buf, longestline(language[2534]), language[2534], input[3]);
2138 break;
2139 case 1:
2140 snprintf(buf, longestline(language[2536]), language[2536], input[5]);
2141 break;
2142 default:
2143 break;
2144 }
2145 }
2146 else if ( input[0] == 5 ) // staff
2147 {
2148
2149 }
2150 else if ( input[0] == 6 ) // whip
2151 {
2152 switch ( j )
2153 {
2154 case 0:
2155 snprintf(buf, 127, language[2538], input[2]);
2156 break;
2157 case 1:
2158 snprintf(buf, longestline(language[3458]), language[3458], input[3]);
2159 break;
2160 case 2:
2161 snprintf(buf, longestline(language[2539]), language[2539], input[4]);
2162 break;
2163 default:
2164 break;
2165 }
2166 }
2167 ttfPrintTextColor(ttf12, infoText_x, infoText_y, color, false, buf);
2168 }
2169 }
2170 }
2171 }
2172
printStatBonus(TTF_Font * outputFont,Sint32 stat,Sint32 statWithModifiers,int x,int y)2173 void printStatBonus(TTF_Font* outputFont, Sint32 stat, Sint32 statWithModifiers, int x, int y)
2174 {
2175 Uint32 color = 0;
2176 char bonusText[4] = "";
2177
2178 if ( statWithModifiers - stat == 0 )
2179 {
2180 color = uint32ColorWhite(*mainsurface);
2181 snprintf(bonusText, 4, "%2d", statWithModifiers);
2182 }
2183 else if ( statWithModifiers - stat < 0 )
2184 {
2185 color = uint32ColorRed(*mainsurface);
2186 snprintf(bonusText, 4, "%2d", statWithModifiers);
2187 }
2188 if ( statWithModifiers - stat > 0 )
2189 {
2190 color = uint32ColorGreen(*mainsurface);
2191 snprintf(bonusText, 4, "%2d", statWithModifiers);
2192 }
2193
2194 ttfPrintTextFormattedColor(outputFont, x, y, color, bonusText);
2195 }
2196