1 /**
2  * @file
3  * @brief Functions used to print information about gods.
4  **/
5 
6 #include "AppHdr.h"
7 
8 #include "describe-god.h"
9 
10 #include <iomanip>
11 
12 #include "act-iter.h"
13 #include "ability.h"
14 #include "branch.h"
15 #include "cio.h"
16 #include "database.h"
17 #include "decks.h"
18 #include "describe.h"
19 #include "english.h"
20 #include "env.h"
21 #include "god-abil.h"
22 #include "god-companions.h"
23 #include "god-conduct.h"
24 #include "god-passive.h"
25 #include "god-type.h"
26 #include "items.h"
27 #include "item-name.h"
28 #include "libutil.h"
29 #include "menu.h"
30 #include "message.h"
31 #include "religion.h"
32 #include "skills.h"
33 #include "spl-util.h"
34 #include "stringutil.h"
35 #include "tag-version.h"
36 #include "terrain.h"
37 #include "tilepick.h"
38 #include "unicode.h"
39 #include "xom.h"
40 
41 using namespace ui;
42 
_piety_level(int piety)43 static int _piety_level(int piety)
44 {
45     return (piety >= piety_breakpoint(5)) ? 7 :
46            (piety >= piety_breakpoint(4)) ? 6 :
47            (piety >= piety_breakpoint(3)) ? 5 :
48            (piety >= piety_breakpoint(2)) ? 4 :
49            (piety >= piety_breakpoint(1)) ? 3 :
50            (piety >= piety_breakpoint(0)) ? 2 :
51            (piety >                    0) ? 1
52                                           : 0;
53 }
54 
_gold_level()55 static int _gold_level()
56 {
57     return (you.gold >= 50000) ? 7 :
58            (you.gold >= 10000) ? 6 :
59            (you.gold >=  5000) ? 5 :
60            (you.gold >=  1000) ? 4 :
61            (you.gold >=   500) ? 3 :
62            (you.gold >=   100) ? 2
63                                : 1;
64 }
65 
_invocations_level()66 static int _invocations_level()
67 {
68     int invo = you.skills[SK_INVOCATIONS];
69     return (invo == 27) ? 7 :
70            (invo >= 24) ? 6 :
71            (invo >= 20) ? 5 :
72            (invo >= 16) ? 4 :
73            (invo >= 12) ? 3 :
74            (invo >= 8)  ? 2
75                         : 1;
76 }
77 
god_favour_rank(god_type which_god)78 int god_favour_rank(god_type which_god)
79 {
80     if (which_god == GOD_GOZAG)
81         return _gold_level();
82     else if (which_god == GOD_USKAYAW)
83         return _invocations_level();
84     else
85         return _piety_level(you.piety);
86 }
87 
_describe_favour(god_type which_god)88 static string _describe_favour(god_type which_god)
89 {
90     if (player_under_penance())
91     {
92         const int penance = you.penance[which_god];
93         return (penance >= 50) ? "Godly wrath is upon you!" :
94                (penance >= 20) ? "You've transgressed heavily! Be penitent!" :
95                (penance >=  5) ? "You are under penance."
96                                : "You should show more discipline.";
97     }
98 
99     if (which_god == GOD_XOM)
100         return uppercase_first(describe_xom_favour());
101 
102 
103     const string godname = god_name(which_god);
104     switch (god_favour_rank(which_god))
105     {
106         case 7:  return "A prized avatar of " + godname;
107         case 6:  return "A favoured servant of " + godname + ".";
108         case 5:
109 
110             if (you_worship(GOD_DITHMENOS))
111                 return "A glorious shadow in the eyes of " + godname + ".";
112             else
113                 return "A shining star in the eyes of " + godname + ".";
114 
115         case 4:
116 
117             if (you_worship(GOD_DITHMENOS))
118                 return "A rising shadow in the eyes of " + godname + ".";
119             else
120                 return "A rising star in the eyes of " + godname + ".";
121 
122         case 3:  return uppercase_first(godname) + " is pleased with you.";
123         case 2:  return uppercase_first(godname) + " is aware of your devotion.";
124         default: return uppercase_first(godname) + " is noncommittal.";
125     }
126 }
127 
128 // The various titles granted by the god of your choice. Note that Xom
129 // doesn't use piety the same way as the other gods, so these are just
130 // placeholders.
131 static const char *divine_title[][8] =
132 {
133     // No god.
134     {"Buglet",             "Firebug",               "Bogeybug",                 "Bugger",
135         "Bugbear",            "Bugged One",            "Giant Bug",                "Lord of the Bugs"},
136 
137     // Zin.
138     {"Blasphemer",         "Anchorite",             "Apologist",                "Pious",
139         "Devout",             "Orthodox",              "Immaculate",               "Bringer of Law"},
140 
141     // The Shining One.
142     {"Honourless",         "Acolyte",               "Righteous",                "Unflinching",
143         "Holy Warrior",       "Exorcist",              "Demon Slayer",             "Bringer of Light"},
144 
145     // Kikubaaqudgha -- scholarly death.
146     {"Tormented",          "Purveyor of Pain",      "Scholar of Death",         "Merchant of Misery",
147         "Artisan of Death",   "Dealer of Despair",     "Black Sun",                "Lord of Darkness"},
148 
149     // Yredelemnul -- zombie death.
150     {"Traitor",            "Tainted",                "Torchbearer",             "Fey @Genus@",
151         "Black Crusader",     "Sculptor of Flesh",     "Harbinger of Death",       "Grim Reaper"},
152 
153     // Xom.
154     {"Toy",                "Toy",                   "Toy",                      "Toy",
155         "Toy",                "Toy",                   "Toy",                      "Toy"},
156 
157     // Vehumet -- battle mage theme.
158     {"Meek",               "Sorcerer's Apprentice", "Scholar of Destruction",   "Caster of Ruination",
159         "Traumaturge",        "Battlemage",            "Warlock",                  "Luminary of Lethal Lore"},
160 
161     // Okawaru -- battle theme.
162     {"Coward",             "Struggler",             "Combatant",                "Warrior",
163         "Knight",             "Warmonger",             "Commander",                "Victor of a Thousand Battles"},
164 
165     // Makhleb -- chaos theme.
166     {"Orderly",            "Spawn of Chaos",        "Disciple of Destruction",  "Fanfare of Bloodshed",
167         "Fiendish",           "Demolition @Genus@",    "Pandemonic",               "Champion of Chaos"},
168 
169     // Sif Muna -- scholarly theme.
170     {"Ignorant",           "Disciple",              "Student",                  "Adept",
171         "Scribe",             "Scholar",               "Sage",                     "Genius of the Arcane"},
172 
173     // Trog -- anger theme.
174     {"Puny",               "Troglodyte",            "Angry Troglodyte",         "Frenzied",
175         "@Genus@ of Prey",    "Rampant",               "Wild @Genus@",             "Bane of Scribes"},
176 
177     // Nemelex Xobeh -- alluding to Tarot and cards.
178     {"Unlucky @Genus@",    "Pannier",               "Jester",                   "Fortune-Teller",
179         "Soothsayer",         "Magus",                 "Cardsharp",                "Hand of Fortune"},
180 
181     // Elyvilon.
182     {"Sinner",                "Practitioner",       "Comforter",             "Caregiver",
183         "Mender",           "Pacifist",                "Purifying @Genus@",        "Bringer of Life"},
184 
185     // Lugonu -- distortion theme.
186     {"Pure",               "Abyss-Baptised",        "Unweaver",                 "Distorting @Genus@",
187         "Agent of Entropy",   "Schismatic",            "Envoy of Void",            "Corrupter of Planes"},
188 
189     // Beogh -- messiah theme.
190     {"Apostate",           "Messenger",             "Proselytiser",             "Priest",
191         "Missionary",         "Evangelist",            "Apostle",                  "Messiah"},
192 
193     // Jiyva -- slime and jelly theme.
194     {"Scum",               "Squelcher",             "Ooze",                     "Jelly",
195         "Slime Creature",     "Dissolving @Genus@",    "Blob",                     "Royal Jelly"},
196 
197     // Fedhas Madash -- nature theme.
198     {"@Walking@ Fertiliser", "Fungal",              "Green @Genus@",            "Cultivator",
199         "Fruitful",           "Photosynthesist",       "Green Death",              "Force of Nature"},
200 
201     // Cheibriados -- slow theme
202     {"Hasty",              "Sluggish @Genus@",      "Deliberate",               "Unhurried",
203      "Contemplative",         "Epochal",               "Timeless",                 "@Adj@ Aeon"},
204 
205     // Ashenzari -- divination theme
206     {"Star-crossed",       "Cursed",                "Initiated",                "Soothsayer",
207         "Seer",               "Oracle",                "Illuminatus",              "Omniscient"},
208 
209     // Dithmenos -- darkness theme
210     {"Ember",              "Gloomy",                "Darkened",                 "Extinguished",
211         "Caliginous",         "Umbral",                "Hand of Shadow",           "Eternal Night"},
212 
213     // Gozag -- entrepreneur theme
214     {"Profligate",         "Pauper",                "Entrepreneur",             "Capitalist",
215         "Rich",               "Opulent",               "Tycoon",                   "Plutocrat"},
216 
217     // Qazlal -- natural disaster theme
218     {"Unspoiled",          "@Adj@ Mishap",          "Lightning Rod",            "@Adj@ Disaster",
219         "Eye of the Storm",   "@Adj@ Catastrophe",     "@Adj@ Cataclysm",          "End of an Era"},
220 
221     // Ru -- enlightenment theme
222     {"Sleeper",           "Questioner",             "Initiate",                 "Seeker of Truth",
223         "@Walker@ of the Path","Lifter of the Veil",     "Transcendent",     "Drop of Water"},
224 
225 #if TAG_MAJOR_VERSION == 34
226     // Pakellas -- inventor theme
227     {"Reactionary",       "Apprentice",             "Inquisitive",              "Experimenter",
228         "Inventor",           "Pioneer",               "Brilliant",                "Grand Gadgeteer"},
229 #endif
230 
231     // Uskayaw -- reveler theme
232     {"Prude",             "Wallflower",             "Party-goer",              "Dancer",
233         "Impassioned",        "Rapturous",             "Ecstatic",                "Rhythm of Life and Death"},
234 
235     // Hepliaklqana -- memory/ancestry theme
236     {"Damnatio Memoriae",       "Hazy",             "@Adj@ Child",              "Storyteller",
237         "Brooding",           "Anamnesiscian",               "Grand Scion",                "Unforgettable"},
238 
239     // Wu Jian -- animal/chinese martial arts monk theme
240     {"Wooden Rat",          "Young Dog",             "Young Crane",              "Young Tiger",
241         "Young Dragon",     "Red Sash",               "Golden Sash",              "Sifu"},
242 };
243 COMPILE_CHECK(ARRAYSZ(divine_title) == NUM_GODS);
244 
god_title(god_type which_god,species_type which_species,int piety)245 string god_title(god_type which_god, species_type which_species, int piety)
246 {
247     string title;
248     if (player_under_penance(which_god))
249         title = divine_title[which_god][0];
250     else if (which_god == GOD_USKAYAW)
251         title = divine_title[which_god][_invocations_level()];
252     else if (which_god == GOD_GOZAG)
253         title = divine_title[which_god][_gold_level()];
254     else
255         title = divine_title[which_god][_piety_level(piety)];
256 
257     const map<string, string> replacements =
258     {
259         { "Adj", species::name(which_species, species::SPNAME_ADJ) },
260         { "Genus", species::name(which_species, species::SPNAME_GENUS) },
261         { "Walking", species::walking_verb(which_species) + "ing" },
262         { "Walker", species::walking_verb(which_species) + "er" },
263     };
264 
265     return replace_keys(title, replacements);
266 }
267 
_describe_item_curse(const item_def & item)268 static string _describe_item_curse(const item_def& item)
269 {
270     if (!item.props.exists(CURSE_KNOWLEDGE_KEY))
271         return "None";
272 
273     const CrawlVector &curses = item.props[CURSE_KNOWLEDGE_KEY].get_vector();
274 
275     if (curses.empty())
276         return "None";
277 
278     return comma_separated_fn(curses.begin(), curses.end(),
279             curse_name, ", ", ", ");
280 }
281 
_describe_ash_skill_boost()282 static string _describe_ash_skill_boost()
283 {
284     ostringstream desc;
285     desc.setf(ios::left);
286     desc << "<white>";
287     desc << setw(40) << "Bound item";
288     desc << setw(30) << "Curse bonuses";
289     desc << "</white>\n";
290 
291     for (int j = EQ_FIRST_EQUIP; j < NUM_EQUIP; j++)
292     {
293         const equipment_type i = static_cast<equipment_type>(j);
294         if (you.equip[i] != -1)
295         {
296             const item_def& item = you.inv[you.equip[i]];
297             const bool meld = item_is_melded(item);
298             if (item.cursed())
299             {
300                 desc << (meld ? "<darkgrey>" : "<lightred>");
301                 desc << setw(40) << item.name(DESC_QUALNAME, true, false, false);
302                 desc << setw(30) << (meld ? "melded" : _describe_item_curse(item));
303                 desc << (meld ? "</darkgrey>" : "</lightred>");
304                 desc << "\n";
305             }
306         }
307     }
308 
309 
310     return desc.str();
311 }
312 
313 typedef pair<int, string> ancestor_upgrade;
314 
315 static const map<monster_type, vector<ancestor_upgrade> > ancestor_data =
316 {
317     { MONS_ANCESTOR_KNIGHT,
318       { { 1,  "Flail" },
319         { 1,  "Shield" },
320         { 1,  "Chain mail (+AC)" },
321         { 15, "Broad axe (flame)" },
322         { 19, "Tower shield (reflect)" },
323         { 19, "Haste" },
324         { 24, "Broad axe (speed)" },
325       }
326     },
327     { MONS_ANCESTOR_BATTLEMAGE,
328       { { 1,  "Quarterstaff" },
329         { 1,  "Throw Frost" },
330         { 1,  "Stone Arrow" },
331         { 1,  "Increased melee damage" },
332         { 15, "Bolt of Magma" },
333         { 19, "Lajatang (freeze)" },
334         { 19, "Haste" },
335         { 24, "Lehudib's Crystal Spear" },
336       }
337     },
338     { MONS_ANCESTOR_HEXER,
339       { { 1,  "Dagger (drain)" },
340         { 1,  "Slow" },
341         { 1,  "Confuse" },
342         { 15, "Paralyse" },
343         { 19, "Mass Confusion" },
344         { 19, "Haste" },
345         { 24, "Quick blade (antimagic)" },
346       }
347     },
348 };
349 
350 /// Build & return a table of Hep's upgrades for your chosen ancestor type.
_describe_ancestor_upgrades()351 static string _describe_ancestor_upgrades()
352 {
353     if (!you.props.exists(HEPLIAKLQANA_ALLY_TYPE_KEY))
354         return "";
355 
356     string desc;
357     const monster_type ancestor =
358         static_cast<monster_type>(you.props[HEPLIAKLQANA_ALLY_TYPE_KEY].get_int());
359     const vector<ancestor_upgrade> *upgrades = map_find(ancestor_data,
360                                                         ancestor);
361 
362     if (upgrades)
363     {
364         desc = "Ancestor Upgrades:\n\n<white>XL              Upgrade\n</white>";
365         for (auto &entry : *upgrades)
366         {
367             desc += make_stringf("%s%2d              %s%s\n",
368                                  you.experience_level < entry.first
369                                      ? "<darkgrey>" : "",
370                                  entry.first,
371                                  entry.second.c_str(),
372                                  you.experience_level < entry.first
373                                      ? "</darkgrey>" : "");
374         }
375     }
376 
377     // XXX: maybe it'd be nice to let you see other ancestor types'...?
378     return desc;
379 }
380 
381 // from dgn-overview.cc
382 extern map<branch_type, set<level_id> > stair_level;
383 
384 /**
385  * Populate a provided vector with a list of bribable branches which are known
386  * to the player.
387  *
388  * @param[out] targets      A list of bribable branches.
389  */
_list_bribable_branches(vector<branch_type> & targets)390 static void _list_bribable_branches(vector<branch_type> &targets)
391 {
392     for (branch_iterator it; it; ++it)
393     {
394         const branch_type br = it->id;
395         if (!gozag_branch_bribable(br))
396             continue;
397 
398         // Only list the Hells once.
399         if (is_hell_subbranch(br))
400             continue;
401 
402         // If you don't know the branch exists, don't list it;
403         // this mainly plugs info leaks about Lair branch structure.
404         if (!stair_level.count(br))
405             continue;
406 
407         targets.push_back(br);
408     }
409 }
410 
411 /**
412  * Describe the current options for Gozag's bribe branch ability.
413  *
414  * @return      A description of branches' bribe status.
415  */
_describe_branch_bribability()416 static string _describe_branch_bribability()
417 {
418     string ret = "You can bribe the following branches of the dungeon:\n";
419     vector<branch_type> targets;
420     _list_bribable_branches(targets);
421 
422     size_t width = 0;
423     for (branch_type br : targets)
424         width = max(width, strlen(branches[br].shortname));
425 
426     for (branch_type br : targets)
427     {
428         string line = " ";
429         line += branches[br].shortname;
430         line += string(width + 3 - strwidth(line), ' ');
431 
432         if (!branch_bribe[br])
433             line += "not bribed";
434         else
435             line += make_stringf("$%d", branch_bribe[br]);
436 
437         ret += line + "\n";
438     }
439 
440     return ret;
441 }
442 
_add_par(formatted_string & desc,const string & str)443 static inline void _add_par(formatted_string &desc, const string &str)
444 {
445     if (!str.empty())
446         desc += formatted_string::parse_string(trimmed_string(str) + "\n\n");
447 }
448 
449 /**
450  * Describe the causes of the given god's wrath.
451  *
452  * @param which_god     The god in question.
453  * @return              A description of the actions that cause this god's
454  *                      wrath.
455  */
_describe_god_wrath_causes(god_type which_god)456 static string _describe_god_wrath_causes(god_type which_god)
457 {
458     if (which_god == GOD_RU)
459         return ""; // no wrath
460     vector<god_type> evil_gods;
461     vector<god_type> chaotic_gods;
462     for (god_iterator it; it; ++it)
463     {
464         const god_type god = *it;
465         if (is_evil_god(god))
466             evil_gods.push_back(god);
467         else if (is_chaotic_god(god)) // intentionally not including evil!
468             chaotic_gods.push_back(god);
469         // XXX: refactor this if any god hates chaotic but not evil gods
470     }
471 
472     switch (which_god)
473     {
474         case GOD_SHINING_ONE:
475         case GOD_ELYVILON:
476             return uppercase_first(god_name(which_god)) +
477                    " forgives followers for abandonment; however, those who"
478                    " later take up the worship of an evil god will be"
479                    " punished. (" +
480                    comma_separated_fn(begin(evil_gods), end(evil_gods),
481                                       bind(god_name, placeholders::_1, false)) +
482                    " are evil gods.)";
483 
484         case GOD_ZIN:
485             return uppercase_first(god_name(which_god)) +
486                    " forgives followers for abandonment; however, those who"
487                    " later take up the worship of an evil or chaotic god will"
488                    " be scourged. (" +
489                    comma_separated_fn(begin(evil_gods), end(evil_gods),
490                                       bind(god_name, placeholders::_1, false)) +
491                    " are evil, and " +
492                    comma_separated_fn(begin(chaotic_gods), end(chaotic_gods),
493                                       bind(god_name, placeholders::_1, false)) +
494                    " are chaotic.)";
495         default:
496             return uppercase_first(god_name(which_god)) +
497                    " does not appreciate abandonment, and will call down"
498                    " fearful punishments on disloyal followers!";
499     }
500 }
501 
502 /**
503  * Print a description of the given god's dislikes & wrath effects.
504  *
505  * @param which_god     The god in question.
506  */
_god_wrath_description(god_type which_god)507 static formatted_string _god_wrath_description(god_type which_god)
508 {
509     formatted_string desc;
510 
511     _add_par(desc, get_god_dislikes(which_god));
512     _add_par(desc, _describe_god_wrath_causes(which_god));
513     _add_par(desc, getLongDescription(god_name(which_god) + " wrath"));
514 
515     if (which_god != GOD_RU) // Permanent wrath.
516     {
517         const bool long_wrath = initial_wrath_penance_for(which_god) > 30;
518         _add_par(desc, apostrophise(uppercase_first(god_name(which_god)))
519                               + " wrath lasts for a relatively " +
520                               (long_wrath ? "long" : "short") + " duration.");
521     }
522 
523     return desc;
524 }
525 
_beogh_extra_description()526 static formatted_string _beogh_extra_description()
527 {
528     formatted_string desc;
529 
530     _add_par(desc, "Named Followers:");
531 
532     vector<monster*> followers;
533 
534     for (monster_iterator mi; mi; ++mi)
535         if (is_orcish_follower(**mi))
536             followers.push_back(*mi);
537     for (auto &entry : companion_list)
538         // if not elsewhere, follower already seen by monster_iterator
539         if (companion_is_elsewhere(entry.second.mons.mons.mid, true))
540             followers.push_back(&entry.second.mons.mons);
541 
542     sort(followers.begin(), followers.end(),
543         [] (monster* a, monster* b) { return a->experience > b->experience;});
544 
545     bool has_named_followers = false;
546     for (auto mons : followers)
547     {
548         if (!mons->is_named()) continue;
549         has_named_followers = true;
550 
551         desc += mons->full_name(DESC_PLAIN);
552         if (companion_is_elsewhere(mons->mid))
553         {
554             desc += formatted_string::parse_string(
555                             " (<blue>on another level</blue>)");
556         }
557         else if (given_gift(mons))
558         {
559             mon_inv_type slot =
560                 mons->props.exists(BEOGH_SH_GIFT_KEY) ? MSLOT_SHIELD :
561                 mons->props.exists(BEOGH_ARM_GIFT_KEY) ? MSLOT_ARMOUR :
562                 mons->props.exists(BEOGH_RANGE_WPN_GIFT_KEY) ? MSLOT_ALT_WEAPON :
563                 MSLOT_WEAPON;
564 
565             // An orc can still lose its gift, e.g. by being turned into a
566             // shapeshifter via a chaos cloud. TODO: should the gift prop be
567             // deleted at that point?
568             if (mons->inv[slot] != NON_ITEM)
569             {
570                 desc.cprintf(" (");
571 
572                 item_def &gift = env.item[mons->inv[slot]];
573                 desc += formatted_string::parse_string(
574                                     menu_colour_item_name(gift,DESC_PLAIN));
575                 desc.cprintf(")");
576             }
577         }
578         desc.cprintf("\n");
579     }
580 
581     if (!has_named_followers)
582         _add_par(desc, "None");
583 
584     return desc;
585 }
586 
_describe_deck_summary()587 static string _describe_deck_summary()
588 {
589     ostringstream desc;
590     desc << "Decks of power:\n";
591     for (int i = FIRST_PLAYER_DECK; i <= LAST_PLAYER_DECK; i++)
592         desc << " " << deck_status((deck_type) i) << "\n";
593 
594     string stack = stack_contents();
595     if (!stack.empty())
596         desc << "\n stacked deck: " << stack << "\n";
597 
598     return desc.str();
599 }
600 
_god_extra_description(god_type which_god)601 static formatted_string _god_extra_description(god_type which_god)
602 {
603     formatted_string desc;
604 
605     switch (which_god)
606     {
607         case GOD_ASHENZARI:
608             desc = formatted_string::parse_string(
609                        getLongDescription(god_name(which_god) + " extra"));
610             if (have_passive(passive_t::bondage_skill_boost))
611             {
612                 desc.cprintf("\n");
613                 _add_par(desc, "Ashenzari supports the following skill groups because of your curses:");
614                 _add_par(desc,  _describe_ash_skill_boost());
615             }
616             break;
617         case GOD_BEOGH:
618             if (you_worship(GOD_BEOGH))
619                 desc = _beogh_extra_description();
620             break;
621         case GOD_GOZAG:
622             if (you_worship(GOD_GOZAG))
623                 _add_par(desc, _describe_branch_bribability());
624             break;
625         case GOD_HEPLIAKLQANA:
626             if (you_worship(GOD_HEPLIAKLQANA))
627                 desc = formatted_string::parse_string(_describe_ancestor_upgrades());
628             break;
629         case GOD_NEMELEX_XOBEH:
630             if (you_worship(GOD_NEMELEX_XOBEH))
631                 _add_par(desc, _describe_deck_summary());
632             break;
633         case GOD_WU_JIAN:
634             _add_par(desc, "Martial attacks:");
635             desc += formatted_string::parse_string(
636                         getLongDescription(god_name(which_god) + " extra"));
637             break;
638         default:
639             break;
640     }
641 
642     return desc;
643 }
644 
645 /**
646  * Describe miscellaneous information about the given god.
647  *
648  * @param which_god     The god in question.
649  * @return              Info about gods which isn't covered by their powers,
650  *                      likes, or dislikes.
651  */
_get_god_misc_info(god_type which_god)652 static string _get_god_misc_info(god_type which_god)
653 {
654     string info = "";
655     skill_type skill = invo_skill(which_god);
656 
657     switch (skill)
658     {
659         case SK_INVOCATIONS:
660             break;
661         case SK_NONE:
662             info += uppercase_first(apostrophise(god_name(which_god))) +
663                 " powers are not affected by the Invocations skill.";
664             break;
665         default:
666             info += uppercase_first(apostrophise(god_name(which_god))) +
667                     " powers are based on " + skill_name(skill) + " instead"
668                     " of Invocations skill.";
669             break;
670     }
671 
672     if (!info.empty())
673         info += "\n\n";
674 
675     return info;
676 }
677 
678 /**
679  * Print a detailed description of the given god's likes and powers.
680  *
681  * @param god       The god in question.
682  */
_detailed_god_description(god_type which_god)683 static formatted_string _detailed_god_description(god_type which_god)
684 {
685     formatted_string desc;
686     _add_par(desc, getLongDescription(god_name(which_god) + " powers"));
687     _add_par(desc, get_god_likes(which_god));
688     _add_par(desc, _get_god_misc_info(which_god));
689     return desc;
690 }
691 
692 /**
693  * Describe the given god's level of irritation at the player.
694  *
695  * Player may or may not be currently under penance.
696  *
697  * @param which_god     The god in question.
698  * @return              A format string, describing the god's ire (or lack of).
699  */
_raw_penance_message(god_type which_god)700 static string _raw_penance_message(god_type which_god)
701 {
702     const int penance = you.penance[which_god];
703 
704     // Give more appropriate message for the good gods.
705     if (penance > 0 && is_good_god(which_god))
706     {
707         if (is_good_god(you.religion))
708             return "%s is ambivalent towards you.";
709         if (!god_hates_your_god(which_god))
710         {
711             return "%s is almost ready to forgive your sins.";
712                  // == "Come back to the one true church!"
713         }
714     }
715 
716     const int initial_penance = initial_wrath_penance_for(which_god);
717     // could do some math tricks to turn this into a table, but it seems fiddly
718     if (penance > initial_penance * 3 / 4)
719         return "%s's wrath is upon you!";
720     if (penance > initial_penance / 2)
721         return "%s well remembers your sins.";
722     if (penance > initial_penance / 4)
723         return "%s's wrath is beginning to fade.";
724     if (penance > 0)
725         return "%s is almost ready to forgive your sins.";
726     return "%s is neutral towards you.";
727 }
728 
729 /**
730  * Describe the given god's level of irritation at the player.
731  *
732  * Player may or may not be currently under penance.
733  *
734  * @param which_god     The god in question.
735  * @return              A description of the god's ire (or lack thereof).
736  */
_god_penance_message(god_type which_god)737 static string _god_penance_message(god_type which_god)
738 {
739     const string message = _raw_penance_message(which_god);
740     return make_stringf(message.c_str(),
741                         uppercase_first(god_name(which_god)).c_str());
742 }
743 
_lifesaving_chance(god_type which_god)744 static int _lifesaving_chance(god_type which_god)
745 {
746     const int default_prot_chance = 10 + you.piety/10; // chance * 100
747     if (which_god != GOD_ELYVILON)
748         return default_prot_chance;
749 
750     switch (elyvilon_lifesaving())
751     {
752         case lifesaving_chance::sometimes:
753             return default_prot_chance + 100 - 3000/you.piety;
754         case lifesaving_chance::always:
755             return 100;
756         default:
757             return default_prot_chance;
758     }
759 }
760 
_lifesave_desc(god_type which_god)761 static string _lifesave_desc(god_type which_god)
762 {
763     if (which_god != you.religion)
764         return "";
765 
766     switch (elyvilon_lifesaving())
767     {
768         case lifesaving_chance::sometimes:
769             return ", especially when called upon";
770         case lifesaving_chance::always:
771             return ", and always does so when called upon";
772         default:
773             return "";
774     }
775 }
776 
777 /**
778  * Print a description of the powers & abilities granted to the player by the
779  * given god. If player worships the god, the currently available powers are
780  * highlighted.
781  *
782  * @param which_god     The god in question.
783  */
_describe_god_powers(god_type which_god)784 static formatted_string _describe_god_powers(god_type which_god)
785 {
786     formatted_string desc;
787 
788     int piety = you_worship(which_god) ? you.piety : 0;
789 
790     desc.textcolour(LIGHTGREY);
791     const char *header = "Granted powers:";
792     const char *cost   = "(Cost)";
793     desc.cprintf("\n\n%s%*s%s\n", header,
794             80 - strwidth(header) - strwidth(cost),
795             "", cost);
796 
797     bool have_any = false;
798 
799     // set default color here, so we don't have to set in multiple places for
800     // always available passive abilities
801     if (!you_worship(which_god))
802         desc.textcolour(DARKGREY);
803     else
804         desc.textcolour(god_colour(which_god));
805 
806     // mv: Some gods can protect you from harm.
807     // The god isn't really protecting the player - only sometimes saving
808     // their life.
809     if (god_gives_passive(which_god, passive_t::protect_from_harm))
810     {
811         have_any = true;
812 
813         const char *how = "";
814         const string when = _lifesave_desc(which_god).c_str();
815 
816         if (which_god == you.religion)
817         {
818             const int prot_chance = _lifesaving_chance(which_god);
819             how = (prot_chance >= 85) ? "carefully " :
820                   (prot_chance >= 55) ? "often " :
821                   (prot_chance >= 25) ? "sometimes "
822                                       : "occasionally ";
823         }
824 
825         desc.cprintf("%s %sguards your life%s.\n",
826                 uppercase_first(god_name(which_god)).c_str(),
827                 how,
828                 when.c_str());
829     }
830 
831     switch (which_god)
832     {
833     case GOD_ZIN:
834     {
835         have_any = true;
836         const char *how =
837             (piety >= piety_breakpoint(5)) ? "always" :
838             (piety >= piety_breakpoint(3)) ? "often" :
839             (piety >= piety_breakpoint(1)) ? "sometimes" :
840                                              "occasionally";
841 
842         desc.cprintf("%s %s shields you from chaos.\n",
843                 uppercase_first(god_name(which_god)).c_str(), how);
844         break;
845     }
846 
847     case GOD_SHINING_ONE:
848     {
849         have_any = true;
850         desc.cprintf("%s prevents you from stabbing unaware foes.\n",
851                 uppercase_first(god_name(which_god)).c_str());
852         if (piety < piety_breakpoint(1))
853             desc.textcolour(DARKGREY);
854         else
855             desc.textcolour(god_colour(which_god));
856         const char *how =
857             (piety >= piety_breakpoint(5)) ? "completely" :
858             (piety >= piety_breakpoint(3)) ? "mostly" :
859                                              "partially";
860 
861         desc.cprintf("%s %s shields you from negative energy.\n",
862                 uppercase_first(god_name(which_god)).c_str(), how);
863 
864         const int halo_size = you_worship(which_god) ? you.halo_radius() : -1;
865         if (halo_size < 0)
866             desc.textcolour(DARKGREY);
867         else
868             desc.textcolour(god_colour(which_god));
869         desc.cprintf("You radiate a%s righteous aura, and others within it are "
870                 "easier to hit.\n",
871                 halo_size > 5 ? " large" :
872                 halo_size > 3 ? "" :
873                                 " small");
874         break;
875     }
876 
877     case GOD_JIYVA:
878         have_any = true;
879         if (have_passive(passive_t::slime_feed))
880             desc.textcolour(god_colour(which_god));
881         else
882             desc.textcolour(DARKGREY);
883 
884         if (have_passive(passive_t::slime_hp))
885             desc.cprintf("You gain magic and health when your fellow slimes consume items.\n");
886         else if (have_passive(passive_t::slime_mp))
887             desc.cprintf("You gain magic when your fellow slimes consume items.\n");
888 
889         break;
890 
891     case GOD_FEDHAS:
892         have_any = true;
893         desc.cprintf("You can walk through plants and fire through allied plants.\n");
894         break;
895 
896     case GOD_CHEIBRIADOS:
897         have_any = true;
898         if (have_passive(passive_t::stat_boost))
899             desc.textcolour(god_colour(which_god));
900         else
901             desc.textcolour(DARKGREY);
902         desc.cprintf("%s %sslows your movement.\n",
903                 uppercase_first(god_name(which_god)).c_str(),
904                 piety >= piety_breakpoint(5) ? "greatly " :
905                 piety >= piety_breakpoint(2) ? "" :
906                                                "slightly ");
907         desc.cprintf("%s supports your attributes. (+%d)\n",
908                 uppercase_first(god_name(which_god)).c_str(),
909                 chei_stat_boost(piety));
910         break;
911 
912     case GOD_VEHUMET:
913         have_any = true;
914         if (const int numoffers = you.vehumet_gifts.size())
915         {
916             const char* offer = numoffers == 1
917                                ? spell_title(*you.vehumet_gifts.begin())
918                                : "some of Vehumet's most lethal spells";
919             desc.cprintf("You can memorise %s.\n", offer);
920         }
921         else if (!you.has_mutation(MUT_INNATE_CASTER))
922         {
923             desc.textcolour(DARKGREY);
924             desc.cprintf("You can memorise some of Vehumet's spells.\n");
925         }
926         break;
927 
928     case GOD_DITHMENOS:
929     {
930         have_any = true;
931         const int umbra_size = you_worship(which_god) ? you.umbra_radius() : -1;
932         if (umbra_size < 0)
933             desc.textcolour(DARKGREY);
934         else
935             desc.textcolour(god_colour(which_god));
936         desc.cprintf("You radiate a%s aura of darkness, enhancing your stealth "
937                 "and reducing the accuracy of your foes.\n",
938                 umbra_size > 5 ? " large" :
939                 umbra_size > 3 ? "n" :
940                                  " small");
941         break;
942     }
943 
944     case GOD_GOZAG:
945         have_any = true;
946         desc.cprintf("You passively detect gold.\n");
947         desc.cprintf("%s turns your defeated foes' bodies to gold.\n",
948                 uppercase_first(god_name(which_god)).c_str());
949         desc.cprintf("Your enemies may become distracted by gold.\n");
950         break;
951 
952     case GOD_HEPLIAKLQANA:
953         if (have_passive(passive_t::frail))
954             desc.textcolour(god_colour(which_god));
955         else
956             desc.textcolour(DARKGREY);
957 
958         have_any = true;
959         desc.cprintf("Your life essence is reduced. (-10%% HP)\n");
960         desc.cprintf("Your ancestor manifests to aid you.\n");
961         break;
962 
963 #if TAG_MAJOR_VERSION == 34
964     case GOD_PAKELLAS:
965     {
966         have_any = true;
967         desc.cprintf("%s prevents your magic from regenerating.\n",
968                 uppercase_first(god_name(which_god)).c_str());
969         desc.cprintf("%s identifies device charges for you.\n",
970                 uppercase_first(god_name(which_god)).c_str());
971         if (you.can_drink(false))
972         {
973             if (have_passive(passive_t::bottle_mp))
974                 desc.textcolour(god_colour(which_god));
975             else
976                 desc.textcolour(DARKGREY);
977 
978             desc.cprintf("%s will collect and distill excess magic from your "
979                     "kills.\n",
980                     uppercase_first(god_name(which_god)).c_str());
981         }
982         break;
983     }
984 #endif
985 
986     case GOD_LUGONU:
987         have_any = true;
988         desc.cprintf("You are protected from the effects of unwielding distortion weapons.\n");
989         break;
990 
991     default:
992         break;
993     }
994 
995     for (const auto& power : get_god_powers(which_god))
996     {
997         // hack: don't mention the necronomicon alone unless it
998         // wasn't already mentioned by the other description
999         if (power.abil == ABIL_KIKU_GIFT_CAPSTONE_SPELLS
1000             && !you.has_mutation(MUT_NO_GRASPING))
1001         {
1002             continue;
1003         }
1004         have_any = true;
1005 
1006         if (you_worship(which_god)
1007             && (power.rank <= 0
1008                 || power.rank == 7 && can_do_capstone_ability(which_god)
1009                 || piety_rank(piety) >= power.rank)
1010             && (!player_under_penance()
1011                 || power.rank == -1))
1012         {
1013             desc.textcolour(god_colour(which_god));
1014         }
1015         else
1016             desc.textcolour(DARKGREY);
1017 
1018         string buf = power.general;
1019         if (!isupper(buf[0])) // Complete sentence given?
1020             buf = "You can " + buf + ".";
1021         const int desc_len = buf.size();
1022 
1023         string abil_cost = "(" + make_cost_description(power.abil) + ")";
1024         if (abil_cost == "(None)")
1025             abil_cost = "";
1026 
1027         desc.cprintf("%s%*s%s\n", buf.c_str(), 80 - desc_len - (int)abil_cost.size(),
1028                 "", abil_cost.c_str());
1029     }
1030 
1031     if (!have_any)
1032         desc.cprintf("None.\n");
1033 
1034     return desc;
1035 }
1036 
_god_overview_description(god_type which_god)1037 static formatted_string _god_overview_description(god_type which_god)
1038 {
1039     formatted_string desc;
1040 
1041     // Print god's description.
1042     const string god_desc = getLongDescription(god_name(which_god));
1043     desc += trimmed_string(god_desc) + "\n";
1044 
1045     // Title only shown for our own god.
1046     if (you_worship(which_god))
1047     {
1048         // Print title based on piety.
1049         desc.cprintf("\nTitle  - ");
1050         desc.textcolour(god_colour(which_god));
1051 
1052         string title = god_title(which_god, you.species, you.piety);
1053         desc.cprintf("%s", title.c_str());
1054     }
1055 
1056     // mv: Now let's print favour as Brent suggested.
1057     // I know these messages aren't perfect so if you can think up
1058     // something better, do it.
1059 
1060     desc.textcolour(LIGHTGREY);
1061     desc.cprintf("\nFavour - ");
1062     desc.textcolour(god_colour(which_god));
1063 
1064     if (!you_worship(which_god))
1065         desc.cprintf("%s", _god_penance_message(which_god).c_str());
1066     else
1067         desc.cprintf("%s", _describe_favour(which_god).c_str());
1068     desc += _describe_god_powers(which_god);
1069     desc.cprintf("\n\n");
1070 
1071     return desc;
1072 }
1073 
build_partial_god_ui(god_type which_god,shared_ptr<ui::Popup> & popup,shared_ptr<Switcher> & desc_sw,shared_ptr<Switcher> & more_sw)1074 static void build_partial_god_ui(god_type which_god, shared_ptr<ui::Popup>& popup, shared_ptr<Switcher>& desc_sw, shared_ptr<Switcher>& more_sw)
1075 {
1076     formatted_string topline;
1077     topline.textcolour(god_colour(which_god));
1078     topline += formatted_string(uppercase_first(god_name(which_god, true)));
1079 
1080     auto vbox = make_shared<Box>(Widget::VERT);
1081     vbox->set_cross_alignment(Widget::STRETCH);
1082     auto title_hbox = make_shared<Box>(Widget::HORZ);
1083 
1084 #ifdef USE_TILE
1085     auto icon = make_shared<Image>();
1086     const tileidx_t idx = tileidx_feature_base(altar_for_god(which_god));
1087     icon->set_tile(tile_def(idx));
1088     title_hbox->add_child(move(icon));
1089 #endif
1090 
1091     auto title = make_shared<Text>(topline.trim());
1092     title->set_margin_for_sdl(0, 0, 0, 16);
1093     title_hbox->add_child(move(title));
1094 
1095     title_hbox->set_main_alignment(Widget::CENTER);
1096     title_hbox->set_cross_alignment(Widget::CENTER);
1097     vbox->add_child(move(title_hbox));
1098 
1099     desc_sw = make_shared<Switcher>();
1100     more_sw = make_shared<Switcher>();
1101     desc_sw->current() = 0;
1102     more_sw->current() = 0;
1103 
1104     const formatted_string descs[4] = {
1105         _god_overview_description(which_god),
1106         _detailed_god_description(which_god),
1107         _god_wrath_description(which_god),
1108         _god_extra_description(which_god)
1109     };
1110 
1111 #ifdef USE_TILE_LOCAL
1112 # define MORE_PREFIX "[<w>!</w>/<w>^</w>" "|<w>Right-click</w>" "]: "
1113 #else
1114 # define MORE_PREFIX "[<w>!</w>/<w>^</w>" "]: "
1115 #endif
1116 
1117     int mores_index = descs[3].empty() ? 0 : 1;
1118     const char* mores[2][4] =
1119     {
1120         {
1121             MORE_PREFIX "<w>Overview</w>|Powers|Wrath",
1122             MORE_PREFIX "Overview|<w>Powers</w>|Wrath",
1123             MORE_PREFIX "Overview|Powers|<w>Wrath</w>",
1124             MORE_PREFIX "Overview|Powers|Wrath"
1125         },
1126         {
1127             MORE_PREFIX "<w>Overview</w>|Powers|Wrath|Extra",
1128             MORE_PREFIX "Overview|<w>Powers</w>|Wrath|Extra",
1129             MORE_PREFIX "Overview|Powers|<w>Wrath</w>|Extra",
1130             MORE_PREFIX "Overview|Powers|Wrath|<w>Extra</w>"
1131         }
1132     };
1133 
1134     for (int i = 0; i < 4; i++)
1135     {
1136         const auto &desc = descs[i];
1137         if (desc.empty())
1138             continue;
1139 
1140         auto scroller = make_shared<Scroller>();
1141         auto text = make_shared<Text>(desc.trim());
1142         text->set_wrap_text(true);
1143         scroller->set_child(text);
1144         desc_sw->add_child(move(scroller));
1145 
1146         more_sw->add_child(make_shared<Text>(
1147                 formatted_string::parse_string(mores[mores_index][i])));
1148     }
1149 
1150     desc_sw->set_margin_for_sdl(20, 0);
1151     desc_sw->set_margin_for_crt(1, 0);
1152     desc_sw->expand_h = false;
1153 #ifdef USE_TILE_LOCAL
1154     desc_sw->max_size().width = tiles.get_crt_font()->char_width()*80;
1155 #endif
1156     vbox->add_child(desc_sw);
1157 
1158     vbox->add_child(more_sw);
1159 
1160     popup = make_shared<ui::Popup>(vbox);
1161 }
1162 
_god_service_fee_description(god_type which_god)1163 static const string _god_service_fee_description(god_type which_god)
1164 {
1165     const int fee = (which_god == GOD_GOZAG) ? gozag_service_fee() : 0;
1166 
1167     if (which_god == GOD_GOZAG)
1168     {
1169         if (fee == 0)
1170         {
1171             return string(" (no fee if you ")
1172                           + random_choose("act now", "join today") + ")";
1173         }
1174         else
1175             return make_stringf(" (%d gold; you have %d)", fee, you.gold);
1176     }
1177 
1178     return "";
1179 }
1180 
1181 #ifdef USE_TILE_WEB
_send_god_ui(god_type god,bool is_altar)1182 static void _send_god_ui(god_type god, bool is_altar)
1183 {
1184     tiles.json_open_object();
1185 
1186     const tileidx_t idx = tileidx_feature_base(altar_for_god(god));
1187     tiles.json_open_object("tile");
1188     tiles.json_write_int("t", idx);
1189     tiles.json_write_int("tex", get_tile_texture(idx));
1190     tiles.json_close_object();
1191 
1192     tiles.json_write_int("colour", god_colour(god));
1193     tiles.json_write_string("name", god_name(god, true));
1194     tiles.json_write_bool("is_altar", is_altar);
1195 
1196     tiles.json_write_string("description", getLongDescription(god_name(god)));
1197     if (you_worship(god))
1198         tiles.json_write_string("title", god_title(god, you.species, you.piety));
1199     tiles.json_write_string("favour", you_worship(god) ?
1200             _describe_favour(god) : _god_penance_message(god));
1201     tiles.json_write_string("powers_list",
1202             _describe_god_powers(god).to_colour_string());
1203     tiles.json_write_string("info_table", "");
1204 
1205     tiles.json_write_string("powers",
1206             _detailed_god_description(god).to_colour_string());
1207     tiles.json_write_string("wrath",
1208             _god_wrath_description(god).to_colour_string());
1209     tiles.json_write_string("extra",
1210             _god_extra_description(god).to_colour_string());
1211     tiles.json_write_string("service_fee",
1212             _god_service_fee_description(god));
1213     tiles.push_ui_layout("describe-god", 1);
1214 }
1215 #endif
1216 
describe_god(god_type which_god)1217 void describe_god(god_type which_god)
1218 {
1219     if (which_god == GOD_NO_GOD) //mv: No god -> say it and go away.
1220     {
1221         mpr("You are not religious.");
1222         return;
1223     }
1224 
1225     shared_ptr<ui::Popup> popup;
1226     shared_ptr<Switcher> desc_sw;
1227     shared_ptr<Switcher> more_sw;
1228     build_partial_god_ui(which_god, popup, desc_sw, more_sw);
1229 
1230     bool done = false;
1231     popup->on_keydown_event([&](const KeyEvent& ev) {
1232         const auto key = ev.key();
1233         if (key == '!' || key == CK_MOUSE_CMD || key == '^')
1234         {
1235             int n = (desc_sw->current() + 1) % desc_sw->num_children();
1236             desc_sw->current() = more_sw->current() = n;
1237 #ifdef USE_TILE_WEB
1238                 tiles.json_open_object();
1239                 tiles.json_write_int("pane", n);
1240                 tiles.ui_state_change("describe-god", 0);
1241 #endif
1242             return true;
1243         }
1244         return done = !desc_sw->current_widget()->on_event(ev);
1245     });
1246 
1247 #ifdef USE_TILE_WEB
1248     _send_god_ui(which_god, false);
1249     popup->on_layout_pop([](){ tiles.pop_ui_layout(); });
1250 #endif
1251 
1252     ui::run_layout(popup, done);
1253 }
1254 
describe_god_with_join(god_type which_god)1255 bool describe_god_with_join(god_type which_god)
1256 {
1257     const string service_fee = _god_service_fee_description(which_god);
1258 
1259     shared_ptr<ui::Popup> popup;
1260     shared_ptr<Switcher> desc_sw;
1261     shared_ptr<Switcher> more_sw;
1262     build_partial_god_ui(which_god, popup, desc_sw, more_sw);
1263 
1264     for (auto& child : *more_sw)
1265     {
1266         Text* label = static_cast<Text*>(child.get());
1267         formatted_string text = label->get_text();
1268         text += formatted_string::parse_string("  [<w>J</w>/<w>Enter</w>]: "
1269                                                "join");
1270 
1271         // We assume that a player who has enough gold such that
1272         // the join fee plus accumulated gold overflows knows what this menu
1273         // does.
1274         if (text.width() + service_fee.length() + 9 <= MIN_COLS)
1275             text += " religion";
1276         if (!service_fee.empty())
1277             text += service_fee;
1278         label->set_text(text);
1279     }
1280 
1281     // States for the state machine
1282     enum join_step_type {
1283         SHOW = -1, // Show the usual god UI
1284         ABANDON, // Ask whether to abandon god, if applicable
1285     };
1286 
1287     // Add separate text widgets the possible abandon-god prompts;
1288     // then when a different prompt needs to be shown, we switch to that prompt.
1289     // This is somewhat brittle, but ensures that the UI doesn't resize when
1290     // switching between prompts.
1291     const string abandon_prompt =
1292         make_stringf("Are you sure you want to abandon %s?",
1293                 god_name(you.religion).c_str());
1294     formatted_string prompt_fs(abandon_prompt, channel_to_colour(MSGCH_PROMPT));
1295 
1296     more_sw->add_child(make_shared<Text>(prompt_fs));
1297 
1298     prompt_fs.cprintf(" [Y]es or [n]o only, please.");
1299     more_sw->add_child(make_shared<Text>(prompt_fs));
1300 
1301     join_step_type step = SHOW;
1302     bool yesno_only = false;
1303     bool done = false, join = false;
1304 
1305     // The join-god UI state machine transition function
1306     popup->on_keydown_event([&](const KeyEvent& ev) {
1307         const auto keyin = ev.key();
1308 
1309         // Always handle escape and pane-switching keys the same way
1310         if (keyin == CK_ESCAPE)
1311             return done = true;
1312         if (keyin == '!' || keyin == CK_MOUSE_CMD || keyin == '^')
1313         {
1314             int n = (desc_sw->current() + 1) % desc_sw->num_children();
1315             desc_sw->current() = n;
1316 #ifdef USE_TILE_WEB
1317             tiles.json_open_object();
1318             tiles.json_write_int("pane", n);
1319             tiles.ui_state_change("describe-god", 0);
1320 #endif
1321             if (step == SHOW)
1322             {
1323                 more_sw->current() = n;
1324                 return true;
1325             }
1326             else
1327                 yesno_only = false;
1328         }
1329 
1330         // Next, allow child widgets to handle scrolling keys
1331         // NOTE: these key exceptions are also specified in ui-layouts.js
1332         if (keyin != 'J' && keyin != CK_ENTER)
1333         if (desc_sw->current_widget()->on_event(ev))
1334             return true;
1335 
1336         if (step == ABANDON)
1337         {
1338             if (keyin != 'Y' && toupper_safe(keyin) != 'N')
1339                 yesno_only = true;
1340             else
1341                 yesno_only = false;
1342 
1343             if (toupper_safe(keyin) == 'N')
1344             {
1345                 canned_msg(MSG_OK);
1346                 return done = true;
1347             }
1348 
1349             if (keyin == 'Y')
1350                 return done = join = true;
1351         }
1352         else if ((keyin == 'J' || keyin == CK_ENTER) && step == SHOW)
1353         {
1354             if (you_worship(GOD_NO_GOD))
1355                 return done = join = true;
1356 
1357             step = ABANDON;
1358         }
1359         else
1360             return done = true;
1361 
1362 #ifdef USE_TILE_WEB
1363         tiles.json_open_object();
1364         string prompt = abandon_prompt + (yesno_only ? " [Y]es or [n]o only, please." : "");
1365         tiles.json_write_string("prompt", prompt);
1366         tiles.json_write_int("pane", desc_sw->current());
1367         tiles.ui_state_change("describe-god", 0);
1368 #endif
1369         if (step == ABANDON)
1370             more_sw->current() = desc_sw->num_children() + step*2 + yesno_only;
1371         return true;
1372     });
1373 
1374 #ifdef USE_TILE_WEB
1375     _send_god_ui(which_god, true);
1376     popup->on_layout_pop([](){ tiles.pop_ui_layout(); });
1377 #endif
1378 
1379     ui::run_layout(popup, done);
1380 
1381     return join;
1382 }
1383