1 /*
2 * PROPRIETARY INFORMATION. This software is proprietary to POWDER
3 * Development, and is not to be reproduced, transmitted, or disclosed
4 * in any way without written permission.
5 *
6 * Produced by: Jeff Lait
7 *
8 * POWDER Development
9 *
10 * NAME: victory.cpp ( POWDER Library, C++ )
11 *
12 * COMMENTS:
13 * This handles all the code triggered when you win. Or when
14 * you die. The latter is better tested :>
15 */
16
17 #include "glbdef.h"
18 #include "gfxengine.h"
19 #include "creature.h"
20 #include <ctype.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include "victory.h"
24 #include "msg.h"
25 #include "item.h"
26 #include "control.h"
27 #include "speed.h"
28 #include "hiscore.h"
29 #include "encyc_support.h"
30 #include "stylus.h"
31 #include "buf.h"
32
33 // Avatar name...
34 char glbAvatarName[100];
35 int glbGameStartFrame;
36 bool glbWizard;
37 bool glbHasAlreadyWon;
38 bool glbTutorial;
39 bool glbStressTest = false;
40 bool glbMapStats = false;
41 bool glbOpaqueTiles = false;
42 s8 glbGender = -1;
43
44 BUF
victory_formattime(int endtime,int * scale)45 victory_formattime(int endtime, int *scale)
46 {
47 int sec;
48 int min;
49 int hour;
50 int day;
51 int year;
52 BUF buf;
53
54 // Count number of refreshes...
55 endtime -= glbGameStartFrame;
56
57 // Conver to seconds...
58 endtime += 30;
59 endtime /= 60;
60
61 sec = endtime % 60;
62 endtime /= 60;
63
64 min = endtime % 60;
65 endtime /= 60;
66
67 hour = endtime % 24;
68 endtime /= 24;
69 day = endtime % 365;
70 endtime /= 365;
71
72 year = endtime;
73
74 // Build the buffer...
75 if (year)
76 {
77 if (scale)
78 *scale = 4;
79 buf.sprintf("%d year%s, %d day%s, %d hour%s, %d minute%s and %d second%s",
80 year, (year == 1) ? "" : "s",
81 day, (day == 1) ? "" : "s",
82 hour, (hour == 1) ? "" : "s",
83 min, (min == 1) ? "" : "s",
84 sec, (sec == 1) ? "" : "s");
85 }
86 else if (day)
87 {
88 if (scale)
89 *scale = 3;
90 buf.sprintf("%d day%s and %dh%dm%ds",
91 day, (day == 1) ? "" : "s",
92 hour,
93 min,
94 sec);
95 }
96 else if (hour)
97 {
98 if (scale)
99 *scale = 2;
100 buf.sprintf("%dh%dm%ds",
101 hour,
102 min,
103 sec);
104 }
105 else if (min)
106 {
107 if (scale)
108 *scale = 1;
109 buf.sprintf("%d minute%s and %d second%s",
110 min, (min == 1) ? "" : "s",
111 sec, (sec == 1) ? "" : "s");
112 }
113 else
114 {
115 if (scale)
116 *scale = 0;
117 buf.sprintf("%d second%s",
118 sec, (sec == 1) ? "" : "s");
119 }
120 return buf;
121 }
122
123 void
glbShowIntrinsic(MOB * mob)124 glbShowIntrinsic(MOB *mob)
125 {
126 int i, j;
127 int aorb;
128
129 BUF buf;
130 const char *intrinsiclist[NUM_INTRINSICS+1];
131
132 buf.sprintf("%s intrinsics are:", mob->getPossessive());
133 buf = gram_capitalize(buf);
134 gfx_printtext(0, 3, buf.buffer());
135
136 j = 0;
137 for (i = 0; i < NUM_INTRINSICS; i++)
138 {
139 if (mob->hasIntrinsic((INTRINSIC_NAMES) i))
140 {
141 intrinsiclist[j++] = glb_intrinsicdefs[i].name;
142 }
143 }
144 if (!j)
145 intrinsiclist[j++] = "Intrinsicless";
146 intrinsiclist[j++] = 0;
147
148 if (!glbStressTest)
149 gfx_selectmenu(5, 4, intrinsiclist, aorb);
150
151 // And clear it...
152 for (j = 3; j < 18; j++)
153 {
154 gfx_cleartextline(j);
155 }
156 }
157
158 static int
mob_compare(const void * v1,const void * v2)159 mob_compare(const void *v1, const void *v2)
160 {
161 MOB_NAMES *m1 = (MOB_NAMES *) v1;
162 MOB_NAMES *m2 = (MOB_NAMES *) v2;
163
164 // We want to sort most experienced mobs first.
165 if (glb_mobdefs[*m1].explevel > glb_mobdefs[*m2].explevel)
166 return -1;
167 else if (glb_mobdefs[*m1].explevel < glb_mobdefs[*m2].explevel)
168 return 1;
169
170 // Now, sort by name...
171 return stricmp(glb_mobdefs[*m1].name, glb_mobdefs[*m2].name);
172 }
173
174 void
victory_buildSortedKillList(MOB_NAMES * mobtypes,int * numtypes,int * totalkill)175 victory_buildSortedKillList(MOB_NAMES *mobtypes, int *numtypes, int *totalkill)
176 {
177 int j;
178 MOB_NAMES mob;
179
180 if (totalkill)
181 *totalkill = 0;
182
183 j = 0;
184 FOREACH_MOB(mob)
185 {
186 if (glbKillCount[mob])
187 {
188 if (mob != MOB_AVATAR)
189 {
190 mobtypes[j++] = mob;
191 if (totalkill)
192 *totalkill += glbKillCount[mob];
193 }
194 else
195 {
196 // Maybe put something cocky here?
197 }
198 }
199 }
200
201 qsort(mobtypes, j, sizeof(MOB_NAMES), mob_compare);
202
203 if (numtypes)
204 *numtypes = j;
205 }
206
207 void
glbShowKillCount()208 glbShowKillCount()
209 {
210 int i, j, k;
211 MOB_NAMES mobtypes[NUM_MOBS];
212 char *moblist[NUM_MOBS+1]; // room for null`
213 int aorb;
214 int totalkill;
215
216 gfx_printtext(0, 3, "Creatures vanquished:");
217
218 victory_buildSortedKillList(mobtypes, &j, &totalkill);
219
220 // Create names.
221 for (i = 0; i < j; i++)
222 {
223 k = mobtypes[i];
224 // What was I drinking when I put this second check in here?
225 // We already only list things with a non-zero kill count?
226 // This might mean the polycontrol code is broken...
227 if (glbKillCount[k])
228 {
229 moblist[i] = gram_createcount(glb_mobdefs[k].name,
230 glbKillCount[k],
231 true).strdup();
232 }
233 }
234 if (!j)
235 {
236 moblist[j++] = strdup("no one");
237 }
238 moblist[j++] = 0;
239
240 // Output the total number
241 {
242 BUF buf;
243
244 // We want to put a comma at the thousand mark.
245 // Anyone who gets to the million mark will overflow our
246 // display space, so we'll laugh at them.
247 // HAHAHAHAHAHAHA.
248 // Okay, I'll have mercy on the poor fool and switch to k
249 // notation.
250 if (totalkill >= 1000000)
251 {
252 buf.sprintf("%d,%03dk", (totalkill / 1000000),
253 ((totalkill / 1000) % 1000));
254 }
255 else if (totalkill >= 1000)
256 {
257 buf.sprintf("%d,%03d", (totalkill / 1000), (totalkill % 1000));
258 }
259 else
260 buf.sprintf("%d", totalkill);
261
262 gfx_printtext(22, 3, buf);
263 }
264
265 gfx_selectmenu(5, 4, (const char **) moblist, aorb);
266
267 // Free the created list.
268 for (i = 0; i < j; i++)
269 {
270 if (moblist[i])
271 free(moblist[i]);
272 }
273
274 // And clear it...
275 for (j = 3; j < 18; j++)
276 {
277 gfx_cleartextline(j);
278 }
279 }
280
281 void writeItemActionBar(ITEM *item, bool onlyexamine);
282 ACTION_NAMES getInventoryActionStrip(int button);
283
284 void
glbVictoryScreen(bool didwin,const ATTACK_DEF * attack,MOB * src,ITEM * weapon)285 glbVictoryScreen(bool didwin, const ATTACK_DEF *attack, MOB *src, ITEM *weapon)
286 {
287 // Two very different possibilities: The avatar died, or lived?
288 BUF buf;
289 const char *prof;
290 MOB *avatar;
291 int h, m;
292 int endtime;
293 bool ischeater = false;
294
295 h = 1;
296 m = 0;
297
298 endtime = gfx_getframecount();
299
300 if (!glbAvatarName[0])
301 strcpy(glbAvatarName, "Lazy Player");
302
303 // Determine if we are a cheater.
304 // Check for save scumming.
305 if (!hiscore_isnewgame() && hiscore_savecount() > 2)
306 ischeater = true;
307 // Check for Wizard mode.
308 if (glbWizard)
309 ischeater = true;
310
311 if (glbTutorial)
312 {
313 // Inform the player of the nature of death in roguelikes.
314 encyc_viewentry("HELP", HELP_DEATH);
315 return;
316 }
317
318 BUF posavatar;
319
320 // Force upper case as GBA players are lazy.
321 // (Well, in defense, it used to be impossible to input upper case)
322 glbAvatarName[0] = toupper(glbAvatarName[0]);
323 posavatar = gram_makepossessive(glbAvatarName);
324
325 // Determine the avatar's profession...
326 if ((avatar = MOB::getAvatar()))
327 {
328 if (glbHasAlreadyWon)
329 {
330 // However, there is some sadness as coming here implies
331 // you died.
332 avatar->formatAndReport("%R <return> from retirement ended tragically!");
333 }
334
335 h = avatar->getHitDie();
336 m = avatar->getMagicDie();
337
338 if (h + m < 2)
339 {
340 prof = "newbie";
341 }
342 else if (h + m < 4)
343 {
344 prof = "neophyte";
345 }
346 else if (h + m > 50)
347 prof = "power gamer";
348 else if (h > m + 2)
349 {
350 if (h + m < 6)
351 prof = "fighter";
352 else if (h + m < 10)
353 prof = "warrior";
354 else
355 prof = "great warrior";
356 }
357 else if (m > h + 2)
358 {
359 if (h + m < 6)
360 prof = "magician";
361 else if (h + m < 10)
362 prof = "wizard";
363 else
364 prof = "great wizard";
365 }
366 else
367 {
368 // Balanced....
369 if (h + m < 6)
370 prof = "adventurer";
371 else if (h + m < 10)
372 prof = "battle-mage";
373 else
374 prof = "great battle-mage";
375 }
376 }
377 else
378 prof = "WHAT HAPPEND TO THE AVATAR?";
379
380 if (didwin)
381 {
382 // Congratulations are in order!
383 msg_report("Banzai! Banzai! Banzai! ");
384 if (src == avatar)
385 {
386 // You killed it in a manly fight.
387 buf.sprintf("Baezl'bub has been vanquished by the %s "
388 "%s! All on the surface praise %s's bravery! ",
389 prof, glbAvatarName, glbAvatarName);
390 msg_report(buf);
391 }
392 else
393 {
394 // You won by some clever strategem...
395
396 // First, announce how Baezl'bub fell...
397 if (src)
398 {
399 BUF name = src->getName(true, true, true, true);
400 buf.sprintf("Baezl'bub has fallen to %s! ", name.buffer());
401 }
402 else
403 {
404 if (weapon)
405 {
406 BUF name = weapon->getName();
407 buf.sprintf("Baezl'bub's cruel reign of terror has ended, felled by %s! ", name.buffer());
408 }
409 else
410 buf.sprintf("While many stories have spread about how Baezl'bub died, his death is one fact none can doubt! ");
411 }
412 msg_report(buf);
413
414 // Next, make sure you get the credit you deserve... :>
415 buf.sprintf("All of the surface know who to thank - the %s known as %s! ", prof, glbAvatarName);
416
417 msg_report(buf);
418 }
419 }
420 else
421 {
422 // You died. Loser. I mean that in a good way.
423
424 // First, the death text from the attack, if any...
425 if (attack && attack->deathtext[0])
426 {
427 buf.sprintf("%s. ", attack->deathtext);
428 msg_report(buf);
429 }
430 else if (src == avatar)
431 {
432 // If you killed yourself, say so...
433 if (weapon)
434 {
435 BUF name = weapon->getName();
436 buf.sprintf("You committed suicide with the aid of %s. ",
437 name.buffer());
438 }
439 else
440 buf.sprintf("You committed suicide. ");
441 msg_report(buf);
442 }
443
444 if (h + m == 1)
445 {
446 // Newbie death...
447 switch (rand_choice(2))
448 {
449 case 0:
450 buf.sprintf("%s was struck down at a young age. ",
451 glbAvatarName);
452 break;
453 case 1:
454 buf.sprintf("The %s %s became another statistic. ",
455 prof, glbAvatarName);
456 break;
457 }
458 msg_report(buf);
459 if (src == avatar)
460 src = 0;
461
462 const char *formattxt = "Hopefully future adventurers will learn from %s's sad example to be more careful playing with %s. ";
463
464 if (src)
465 {
466 BUF name;
467
468 if (src->hasIntrinsic(INTRINSIC_UNIQUE))
469 name = src->getName(false, true, true, true);
470 else
471 name = gram_makeplural(src->getName(false, true, true, true));
472 buf.sprintf(formattxt,
473 glbAvatarName,
474 name.buffer());
475 }
476 else if (weapon)
477 {
478 BUF name;
479
480 if (weapon->isArtifact())
481 name = weapon->getName(false);
482 else
483 name = gram_makeplural(weapon->getName(false));
484
485 buf.sprintf(formattxt,
486 glbAvatarName,
487 name.buffer());
488 }
489 else
490 {
491 // No known source of the death.
492 buf.sprintf(formattxt,
493 glbAvatarName,
494 "themselves"
495 );
496 }
497
498 msg_report(buf);
499 }
500 else if (h + m > 15)
501 {
502 // A great has died!
503 buf.sprintf("Men cry and women wail as word reaches the surface of the death of the %s %s! ",
504 prof, glbAvatarName);
505 msg_report(buf);
506 BUF name;
507 if (src)
508 name = src->getName(true, true, true, true);
509 else if (weapon)
510 name = weapon->getName();
511 else
512 name.reference("overconfidence");
513 buf.sprintf("So much hope crushed by that most vile of %s, %s. ",
514 src ? "creatures" : "things",
515 name.buffer());
516 msg_report(buf);
517 }
518 else
519 {
520 // A mundane has died.
521 if (src && src != avatar)
522 {
523 const char *difficulty, *likelyerror;
524
525 if (h + m > src->getExpLevel() + 10)
526 {
527 difficulty = "mere ";
528 likelyerror = "rash overconfidence";
529 }
530 else if (h + m > src->getExpLevel() + 5)
531 {
532 difficulty = "lowly ";
533 likelyerror = "overconfidence";
534 }
535 else if (h + m > src->getExpLevel() + 2)
536 {
537 difficulty = "out-classed ";
538 likelyerror = "poor planning";
539 }
540 else if (h + m > src->getExpLevel() - 2)
541 {
542 difficulty = 0;
543 likelyerror = "piss-poor luck";
544 }
545 else if (h + m > src->getExpLevel() - 5)
546 {
547 difficulty = "more powerful ";
548 likelyerror = "biting more than can be chewed";
549 }
550 else if (h + m > src->getExpLevel() - 10)
551 {
552 difficulty = "much more powerful ";
553 likelyerror = "picking a fight with someone bigger";
554 }
555 else
556 {
557 // 10 levels upwards.
558 difficulty = "vastly more powerful ";
559 likelyerror = "foolishly underestimating one's foes";
560 }
561
562 // Killed by a creature...
563 BUF name = src->getName(false, true, true, true);
564 buf.sprintf("The %s %s death at the hands of %s%s%s will "
565 "serve to remind future %ss of the dangers of "
566 "%s. ",
567 prof, posavatar.buffer(),
568 gram_getarticle(difficulty ? difficulty
569 : name.buffer()),
570 difficulty ? difficulty : "",
571 name.buffer(),
572 prof,
573 likelyerror);
574 msg_report(buf);
575 }
576 else if (src)
577 {
578 buf.sprintf("Why did %s think life was not worth living? "
579 "We will never know. ",
580 glbAvatarName);
581 msg_report(buf);
582 }
583 else if (weapon)
584 {
585 BUF name = gram_makeplural(weapon->getName(false));
586 buf.sprintf("The %s %s death is used to this day to remind "
587 "children the dangers of playing with %s. ",
588 prof, posavatar.buffer(),
589 name.buffer());
590 msg_report(buf);
591 }
592 else
593 {
594 buf.sprintf("The %s %s reputation is sullied by an ignoble death. ",
595 prof, posavatar.buffer());
596 msg_report(buf);
597 }
598 }
599 }
600
601 // For the purpose of hiscores, out of retirement players can
602 // enter again.
603 if (glbHasAlreadyWon)
604 {
605 didwin = true;
606 }
607
608 // Report hiscore results.
609 hiscore_addAndDisplayEntry(avatar, didwin, ischeater);
610
611 // Show mob kills
612 glbShowKillCount();
613
614 // First, list intrinsics...
615 glbShowIntrinsic(avatar);
616
617 // Then give the current god situation.
618 avatar->pietyReport();
619
620 // Now, onto listing the avatar's equipment and what not!
621 // First, the generic character dump.
622 avatar->characterDump(true, didwin, true, endtime);
623
624 // We always examine the equipment. The time spent asking the
625 // question is wasted as cancelling the examine is the same
626 // number of keys!
627 if (1)
628 {
629 ITEM *id;
630 int y, x;
631
632 for (y = 0; y < MOBINV_HEIGHT; y++)
633 for (x = 0; x < MOBINV_WIDTH; x++)
634 {
635 id = avatar->getItem(x, y);
636 #if 1
637 if (id)
638 id->markIdentified();
639 #endif
640 }
641
642 STYLUSLOCK styluslock(REGION_BOTTOMBUTTON);
643
644 // Let the user peruse their inventory...
645 gfx_showinventory(avatar);
646
647 while (1)
648 {
649 int dx = 0, dy = 0, cx, cy, select;
650
651 if (!gfx_isnewframe())
652 continue;
653 if (hamfake_forceQuit())
654 break;
655
656 // Prep the action bar...
657 {
658 ITEM *item;
659 gfx_getinvcursor(cx, cy);
660 item = avatar->getItem(cx, cy);
661 writeItemActionBar(item, true);
662 }
663
664 // Handle input from external UI.
665 {
666 ACTION_NAMES action;
667 int iact, ispell;
668
669 hamfake_externalaction(iact, ispell);
670
671 if (iact != ACTION_NONE)
672 {
673 action = (ACTION_NAMES) iact;
674 if (action == ACTION_INVENTORY)
675 {
676 // Done
677 break;
678 }
679 else if (action == ACTION_EXAMINE)
680 {
681 ITEM *item;
682
683 // count as an examine.
684 gfx_setinvcursor(cx, cy, false);
685 item = avatar->getItem(cx, cy);
686 // We don't want to quit on a click off as it is
687 // more likely in the stylus world.
688 if (!item)
689 continue;
690
691 item->viewDescription();
692 gfx_showinventory(avatar);
693
694 continue;
695 }
696 }
697 }
698
699 // Check for a view request...
700 if (styluslock.getbottombutton(select))
701 {
702 if (select >= 0)
703 {
704 ACTION_NAMES action;
705
706 action = getInventoryActionStrip(select);
707
708 if (action == ACTION_INVENTORY)
709 {
710 // Done
711 break;
712 }
713 else if (action == ACTION_EXAMINE)
714 {
715 ITEM *item;
716
717 // count as an examine.
718 gfx_setinvcursor(cx, cy, false);
719 item = avatar->getItem(cx, cy);
720 // We don't want to quit on a click off as it is
721 // more likely in the stylus world.
722 if (!item)
723 continue;
724
725 item->viewDescription();
726 gfx_showinventory(avatar);
727
728 continue;
729 }
730 }
731 }
732
733 if (ctrl_hit(BUTTON_SELECT) ||
734 ctrl_hit(BUTTON_B))
735 break;
736
737 if (ctrl_hit(BUTTON_A))
738 {
739 // Examine the item...
740 ITEM *item;
741
742 gfx_getinvcursor(cx, cy);
743 item = avatar->getItem(cx, cy);
744 if (!item)
745 break;
746
747 item->viewDescription();
748 gfx_showinventory(avatar);
749
750 continue;
751 }
752
753 ctrl_getdir(dx, dy);
754
755 if (!dx && !dy)
756 {
757 if (stylus_queryinventoryitem(cx, cy))
758 {
759 gfx_setinvcursor(cx, cy, false);
760 }
761
762 // Consume any key in case an invalid key was hit
763 hamfake_getKeyPress(false);
764 continue;
765 }
766
767 // A direction key is hit, update the cursor pos.
768 gfx_getinvcursor(cx, cy);
769 cx += dx;
770 cy += dy;
771 gfx_setinvcursor(cx, cy, false);
772 }
773
774 gfx_hideinventory();
775 }
776
777 buf.sprintf("You took %d moves. ", speed_gettime());
778 msg_report(buf);
779
780 // Report the time.
781 {
782 BUF timename;
783 int scale;
784
785 timename = victory_formattime(endtime, &scale);
786
787 // Build the buffer...
788 if (scale == 4)
789 {
790 buf.sprintf("You took %s. That is outright insanity. ",
791 timename.buffer());
792 }
793 else if (scale == 3)
794 {
795 buf.sprintf("You took %s. Impressive dedication! ",
796 timename.buffer());
797 }
798 else if (scale == 2)
799 {
800 if (didwin)
801 buf.sprintf("It took you %s to defeat Baezl'bub. ",
802 timename.buffer());
803 else
804 buf.sprintf("You spent %s. And you still didn't win. ",
805 timename.buffer());
806 }
807 else if (scale == 1)
808 {
809 buf.sprintf("You spent %s. %s",
810 timename.buffer(),
811 didwin ?
812 "An impressive achievement! " :
813 "Keep playing and you will defeat Baezl'bub! ");
814 }
815 else
816 {
817 buf.sprintf("That was quick - %s. ",
818 timename.buffer());
819 }
820 msg_report(buf);
821 }
822
823 if (didwin)
824 {
825 msg_report("Post of your success on RGRM! ");
826 msg_awaitaccept();
827 }
828 else
829 {
830 msg_report("Better luck next life! ");
831 msg_awaitaccept();
832 }
833 }
834