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