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