game_ui.h 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473
  1. #ifndef __GAME_UI_H
  2. #define __GAME_UI_H
  3. std::string show_overmap(Player& p, const GameState& state, size_t scale = 12) {
  4. p.overmap.scale = scale;
  5. std::string ret = "\2The overview map. Use '+' and '-' to zoom in and out.\n\n"_m;
  6. std::string level_name = levelskins().get(p.worldz).name;
  7. if (p.worldx != 0 || p.worldy != 0) {
  8. static const std::string tunnels[3][3] = {
  9. { "due NW"_m, "due W"_m, "due SW"_m },
  10. { "due N"_m, ""_m, "due S"_m },
  11. { "due NE"_m, "due E"_m, "due SE"_m } };
  12. level_name += ", ";
  13. level_name += tunnels[p.worldx+1][p.worldy+1];
  14. }
  15. ret += nlp::message("\1Dungeon level %d (%s), %s moon.\n\n\3"_m,
  16. p.worldz+1, level_name, state.moon.pi.phase_str);
  17. for (unsigned int y = 0; y < state.render.h; y += scale) {
  18. for (unsigned int x = 0; x < state.render.w; x += scale) {
  19. std::string charz = " "_m;
  20. unsigned int intensity = 0;
  21. if (x == 0 || y == 0 ||
  22. x >= state.render.w - scale ||
  23. y >= state.render.h - scale) {
  24. charz = "."_m;
  25. }
  26. for (unsigned int yi = 0; yi < scale; ++yi) {
  27. for (unsigned int xi = 0; xi < scale; ++xi) {
  28. unsigned int xx = x + xi;
  29. unsigned int yy = y + yi;
  30. if (xx >= state.render.w || yy >= state.render.h)
  31. continue;
  32. const auto& gp = state.render._get(xx, yy);
  33. if (xx == p.px && yy == p.py) {
  34. charz = p.get_species().skin[0].get_text();
  35. intensity = 3;
  36. }
  37. items::Item item;
  38. if (state.items.get(xx, yy, 0, item) && intensity <= 2) {
  39. const Design& d = designs().get(item.tag);
  40. if (d.is_lit || gp.is_lit) {
  41. charz = d.skin[0].get_text();
  42. intensity = 2;
  43. }
  44. }
  45. features::Feature feat;
  46. if (state.features.get(xx, yy, feat) && intensity <= 1) {
  47. const Terrain& t = terrain().get(feat.tag);
  48. if (t.is_lit || gp.is_lit) {
  49. charz = t.skin[0].get_text();
  50. if (charz == std::string(" "_m))
  51. charz = "."_m;
  52. intensity = 1;
  53. }
  54. }
  55. }
  56. }
  57. ret += charz;
  58. }
  59. ret += "\n\3"_m;
  60. }
  61. return ret;
  62. }
  63. std::string show_victory(const Player& p, const GameState& state) {
  64. std::string ret = "\n\2Status of the \3Ring of Power:\1\n\n"_m;
  65. if (p.uniques_disabled) {
  66. ret += "Indeterminate status due to wishing.\n"_m;
  67. return ret;
  68. }
  69. std::vector<uniques::key_t> levels;
  70. time_t placetime;
  71. uniques::uniques().locate(placetime, levels);
  72. time_t now = ::time(NULL);
  73. time_t tdiff = now - placetime;
  74. unsigned int timeout = constants().uniques_timeout;
  75. if (levels.empty()) {
  76. bool found = false;
  77. for (const auto& is : state.items.stuff) {
  78. for (const auto& i : is.second) {
  79. if (i.tag == constants().unique_item) {
  80. found = true;
  81. }
  82. }
  83. }
  84. if (found) {
  85. ret += "Found somewhere on this level.\n"_m;
  86. } else {
  87. bool inv = false;
  88. for (const auto& i : p.inv.stuff) {
  89. if (i.second.tag == constants().unique_item) {
  90. inv = true;
  91. }
  92. }
  93. if (inv) {
  94. ret += "In your inventory.\n"_m;
  95. found = true;
  96. }
  97. }
  98. if (!found) {
  99. if (placetime == 0 || tdiff >= (time_t)timeout) {
  100. ret += "Ready to regenerate on dungeon level\2 1\1."_m;
  101. } else {
  102. ret += "Destroyed and not yet ready to regenerate.\n"_m;
  103. }
  104. }
  105. } else {
  106. const auto& wk = levels.back();
  107. static const std::string tunnels[3][3] = {
  108. { "follow a tunnel due north-west"_m,
  109. "follow a tunnel due west"_m,
  110. "follow a tunnel due south-west"_m },
  111. { "follow a tunnel due north"_m,
  112. "main branch"_m,
  113. "follow a tunnel due south"_m },
  114. { "follow a tunnel due north-east"_m,
  115. "follow a tunnel due east"_m,
  116. "follow a tunnel due south-east"_m } };
  117. if (wk.worldy < -1 || wk.worldy > 1 || wk.worldx < -1 || wk.worldy > 1)
  118. throw std::runtime_error("Sanity error in worldkey");
  119. tdiff /= 60;
  120. std::string units = "minutes"_m;
  121. if (tdiff > 1440) {
  122. tdiff /= 1440;
  123. units = "days"_m;
  124. } else if (tdiff > 60) {
  125. tdiff /= 60;
  126. units = "hours"_m;
  127. }
  128. ret += nlp::message("Found somewhere on dungeon level \2%d\1 (\2%s\1).\n"_m,
  129. wk.worldz+1, tunnels[wk.worldx+1][wk.worldy+1]);
  130. ret += nlp::message("It has been there for \2%d %s\1.\n"_m, tdiff, units);
  131. }
  132. return ret;
  133. }
  134. std::string show_stats(const Player& p) {
  135. std::string ret;
  136. tag_t polytag = p.polymorph.species;
  137. if (!polytag.null()) {
  138. ret = nlp::message("\n\2Character level:\1 %d (in the form of \2%s\1)\n"_m, p.get_level()+1,
  139. species().get(polytag));
  140. } else {
  141. ret = nlp::message("\n\2Character level:\1 %d\n"_m, p.get_level()+1);
  142. }
  143. damage::attacks_t att;
  144. p.get_attack(att);
  145. if (att.attacks.empty()) {
  146. ret += "\n\2You have no attack capabilities.\1\n\n"_m;
  147. } else {
  148. ret += "\n\2Your attack capabilities:\1\n\n"_m;
  149. for (const auto& i : att.attacks) {
  150. // HACK
  151. if (i.val < 0.01)
  152. continue;
  153. const Damage& dam = damages().get(i.type);
  154. std::string s = dam.name;
  155. if (s.size() < 25)
  156. s += std::string(25 - s.size(), ' ');
  157. ret += nlp::message(" \1%S\2: %d\n"_m, s, i.val);
  158. }
  159. }
  160. damage::defenses_t def;
  161. p.get_defense(def);
  162. if (def.defenses.empty()) {
  163. ret += "\n\2You have no defense capabilities.\1\n\n"_m;
  164. } else {
  165. ret += "\n\2Your defense capabilities:\1\n\n"_m;
  166. for (const auto& i : def.defenses) {
  167. // HACK
  168. if (i.second < 0.01)
  169. continue;
  170. const Damage& dam = damages().get(i.first);
  171. std::string s = dam.name;
  172. if (s.size() < 25)
  173. s += std::string(25 - s.size(), ' ');
  174. ret += nlp::message(" \1%S\2: %d\n"_m, s, i.second);
  175. }
  176. }
  177. auto j = constants().starsigns.names.find(p.starsign.sign);
  178. std::string sign(j == constants().starsigns.names.end() ? "Unnamed"_m : j->second);
  179. ret += nlp::message("\n\2Your starsign:\1 %d-%S\n\n"_m, p.starsign.day, sign);
  180. if (p.ailments.size() > 0) {
  181. ret += "\n\nYour degenerative ailments:\n\n"_m;
  182. std::map<std::string, std::pair<size_t,size_t> > ails;
  183. for (const auto& a : p.ailments) {
  184. const auto i = constants().ailments.find(a.second);
  185. if (i != constants().ailments.end()) {
  186. auto& zz = ails[i->second.name];
  187. zz.first++;
  188. zz.second = i->second.triggers;
  189. }
  190. }
  191. for (const auto& a : ails) {
  192. size_t n = a.second.first / a.second.second;
  193. if (n <= 1) {
  194. ret += nlp::message(" \2%S\1\n"_m, a.first);
  195. } else {
  196. ret += nlp::message(" \2%S\1 (x\2%d\1)\n"_m, a.first, n);
  197. }
  198. }
  199. }
  200. return ret;
  201. }
  202. std::string show_spells(const Player& p, const GameState& state) {
  203. const std::vector<Terrain::spell_t>& p_spells = p.spells;
  204. const std::vector<Design::spell_t>& i_spells = p.inv.spells();
  205. const std::vector<uint32_t>& r_spells = p.inv.random_spells();
  206. std::string m;
  207. m = "\2Your arcane powers:\n\n"_m;
  208. char z = 'a';
  209. for (const auto& sp : p_spells) {
  210. m += nlp::message(" \2%c\1) %S\n"_m, z, sp);
  211. ++z;
  212. }
  213. for (const auto& sp : i_spells) {
  214. m += nlp::message(" \2%c\1) %S\n"_m, z, sp);
  215. ++z;
  216. }
  217. for (const auto& rp : r_spells) {
  218. m += nlp::message(" \2%c\1) Labeled '%s'\n"_m, z, rcode::magick_encode(rp));
  219. ++z;
  220. }
  221. const Species& s = p.get_species();
  222. if (!s.magic_cost.stat.null() && p.stats.is_min(s.magic_cost.stat))
  223. return m;
  224. for (const auto& pb : s.blast) {
  225. m += nlp::message(" \2%c\1) %S%s\n"_m, z, pb.name,
  226. ((state.ticks % pb.turns) == 0 ? std::string() : std::string(" \2(not ready)\1"_m)));
  227. ++z;
  228. }
  229. for (const auto& pc : s.cast_cloud) {
  230. m += nlp::message(" \2%c\1) %S%s\n"_m, z, pc.name,
  231. ((state.ticks % pc.turns) == 0 ? std::string() : std::string(" \2(not ready)\1"_m)));
  232. ++z;
  233. }
  234. for (const auto& ps : s.summon) {
  235. m += nlp::message(" \2%c\1) Summon %s%s\n"_m, z, species().get(ps.speciestag),
  236. ((state.ticks % ps.turns) == 0 ? std::string() : std::string(" \2(not ready)\1"_m)));
  237. ++z;
  238. }
  239. for (const auto& ps : s.spawns) {
  240. m += nlp::message(" \2%c\1) Summon level %d monster%s\n"_m, z, ps.level,
  241. ((state.ticks % ps.turns) == 0 ? std::string() : std::string(" \2(not ready)\1"_m)));
  242. ++z;
  243. }
  244. return m;
  245. }
  246. std::string show_options(GameState& state, GameOptions& options) {
  247. std::string ret;
  248. ret += "\n";
  249. ret += "\2a\1) Always center view on player: \3"_m;
  250. ret += (options.center_view ? "Yes\n"_m : "No\n"_m);
  251. ret += "\2b\1) Don't fade colors with distance: \3"_m;
  252. ret += (options.no_fade_colors ? "Yes\n"_m : "No\n"_m);
  253. ret += "\2c\1) Color theme in menus: \3"_m;
  254. ret += (state.render.ui_symbol_index ? "Black\n"_m : "Blue\n"_m);
  255. return ret;
  256. }
  257. void handle_input_spells(Player& p, GameState& state, maudit::keypress k) {
  258. int _z = k.letter - 'a';
  259. if (_z < 0) {
  260. state.window_stack.pop_back();
  261. return;
  262. }
  263. size_t z = (size_t)_z;
  264. const auto& p_spells = p.spells;
  265. const auto& i_spells = p.inv.spells();
  266. const auto& r_spells = p.inv.random_spells();
  267. size_t size1 = p_spells.size();
  268. size_t size2 = size1 + i_spells.size();
  269. size_t size3 = size2 + r_spells.size();
  270. if (z < size1) {
  271. const auto& sp = p_spells[z];
  272. seed_celauto(state, p.px, p.py, sp.ca_tag);
  273. ++(state.ticks);
  274. } else if (z < size2) {
  275. const auto& sp = i_spells[z - size1];
  276. seed_celauto(state, p.px, p.py, sp.ca_tag);
  277. ++(state.ticks);
  278. } else if (z < size3) {
  279. uint32_t rspell = r_spells[z - size2];
  280. cast_random_spell(p, rspell, state);
  281. ++(state.ticks);
  282. }
  283. const Species& s = p.get_species();
  284. if (!s.magic_cost.stat.null()) {
  285. if (p.stats.is_min(s.magic_cost.stat)) {
  286. state.window_stack.pop_back();
  287. return;
  288. }
  289. if (p.stats.sinc(s.magic_cost.stat, -s.magic_cost.cost))
  290. p.dead = true;
  291. }
  292. size_t x = z - size3;
  293. size1 = s.blast.size();
  294. size2 = size1 + s.cast_cloud.size();
  295. size3 = size2 + s.summon.size();
  296. size_t size4 = size3 + s.spawns.size();
  297. if (x < size1) {
  298. start_poly_blast(p, x, state);
  299. } else if (x < size2) {
  300. start_poly_cloud(p, x - size1, state);
  301. } else if (x < size3) {
  302. summon_poly(p, x - size2, state);
  303. } else if (x < size4) {
  304. spawn_poly(p, x - size3, state);
  305. }
  306. state.window_stack.pop_back();
  307. }
  308. std::string show_achievements(const Player& p) {
  309. const auto& achievements = constants().achievements;
  310. // achieved, next_target
  311. std::map<tag_t, std::pair<bool, unsigned int> > resorted;
  312. for (const auto& z : achievements) {
  313. const auto& ach = z.second;
  314. auto j = p.kills.find(ach.genus);
  315. if (j == p.kills.end())
  316. continue;
  317. unsigned int kills = j->second;
  318. auto& ri = resorted[ach.genus];
  319. if (kills >= ach.kills) {
  320. ri.first = true;
  321. } else if (ach.kills < ri.second || ri.second == 0) {
  322. ri.second = ach.kills;
  323. }
  324. }
  325. std::string ret = "\nEnemies defeated:\n\n"_m;
  326. const auto& names = constants().genus_names;
  327. for (const auto& i : p.kills) {
  328. auto n = names.find(i.first);
  329. if (n == names.end())
  330. continue;
  331. std::string s = " ";
  332. unsigned int kills = i.second;
  333. if (kills < 1000) s += " ";
  334. if (kills < 100) s += " ";
  335. s += "\2";
  336. nlp::parsed_name pn(n->second);
  337. s += pn.make(kills, false);
  338. s += "\1";
  339. auto r = resorted.find(i.first);
  340. if (r != resorted.end()) {
  341. if (s.size() < 40) {
  342. s += std::string(40 - s.size(), ' ');
  343. }
  344. if (r->second.first) {
  345. s += " \3Achievement unlocked!\1"_m;
  346. }
  347. if (r->second.second != 0) {
  348. s += nlp::message(" (%d needed to unlock)"_m, r->second.second);
  349. }
  350. }
  351. s += "\n";
  352. ret += s;
  353. }
  354. return ret;
  355. }
  356. std::string show_monsters(const Player& p, const GameState& state) {
  357. std::string ret = "\nKnown enemies:\n"_m;
  358. for(auto t = p.seen_monsters.timeline.rbegin(); t != p.seen_monsters.timeline.rend(); ++t) {
  359. const Species& s = species().get(*t);
  360. double danger = 0;
  361. for (const auto& i : s.attacks.attacks) {
  362. danger += (i.val / 6.0);
  363. }
  364. for (const auto& i : s.cast_cloud) {
  365. danger += (-i.chance / i.turns) / 2.0;
  366. }
  367. for (const auto& i : s.summon) {
  368. danger += (-i.chance / i.turns);
  369. }
  370. for (const auto& i : s.spawns) {
  371. danger += (-i.chance / i.turns);
  372. }
  373. for (const auto& i : s.blast) {
  374. double f = (-i.chance / i.turns) * 2;
  375. for (const auto& j : i.attacks.attacks) {
  376. danger += (j.val / 6.0) / f;
  377. }
  378. }
  379. auto i = constants().genus_names.find(s.genus);
  380. if (i != constants().genus_names.end()) {
  381. nlp::parsed_name pn(i->second);
  382. ret += nlp::message("\n\3%S\1: \2%s\1 of level \2%d\1."_m, s, pn.make(1, false), s.get_computed_level() + 1);
  383. } else {
  384. ret += nlp::message("\n\3%S\1: level \2%d\1."_m, s, s.get_computed_level() + 1);
  385. }
  386. if (!s.ally.null()) {
  387. ret += " \2It is usually friendly.\1"_m;
  388. }
  389. if (s.count == 0) {
  390. ret += " (\2Exceedingly rare, "_m;
  391. } else if (s.count <= 5) {
  392. ret += " (\2Rare, "_m;
  393. } else if (s.count <= 10) {
  394. ret += " (\2Uncommon, "_m;
  395. } else if (s.count <= 20) {
  396. ret += " (\2Common, "_m;
  397. } else {
  398. ret += " (\2Very common, "_m;
  399. }
  400. if (danger >= 3) {
  401. ret += "epically dangerous.\1)"_m;
  402. } else if (danger >= 2.5) {
  403. ret += "terribly dangerous.\1)"_m;
  404. } else if (danger >= 1.7) {
  405. ret += "highly dangerous.\1)"_m;
  406. } else if (danger >= 0.8) {
  407. ret += "dangerous.\1)"_m;
  408. } else if (danger >= 0.6) {
  409. ret += "slightly dangerous.\1)"_m;
  410. } else if (danger >= 0.4) {
  411. ret += "mostly harmless.\1)"_m;
  412. } else {
  413. ret += "harmless.\1)"_m;
  414. }
  415. if (s.descr.size() > 0) {
  416. ret += "\n";
  417. ret += " \"";
  418. for (unsigned char c : s.descr) {
  419. if (c == '\n') {
  420. ret += "\n ";
  421. } else {
  422. ret += c;
  423. }
  424. }
  425. ret += "\"\n";
  426. } else {
  427. ret += "\n";
  428. }
  429. }
  430. return ret;
  431. }
  432. std::string help_text() {
  433. std::string ret =
  434. "\3Essential commands:\1\n"
  435. " \2h j k l\1 "
  436. " \2arrow keys\1 : Move left, down, up and right.\n"
  437. " \2y u b n\1 "
  438. " \2keypad\1 : Move diagonally.\n"
  439. " \2.\1 : Stand still.\n"
  440. " \2S\1 : Save and quit.\n"
  441. " \2Q\1 : Commit suicide and quit.\n"
  442. " \2>\1 : Use terrain. (Enter holes and tunnels, use statues, etc.)\n"
  443. " \2i\1 : Interact with inventory, use items and show character info.\n"
  444. " \2z\1 : Cast spells if you know them.\n"
  445. "\3Information commands:\1\n"
  446. " \2/\1 : Look around you using arrow keys.\n"
  447. " \2tab\1 : Look at monsters and items in view.\n"
  448. " \2P\1 : Show message history.\n"
  449. " \2@\1 : Show your attack and defense stats.\n"
  450. " \2#\1 : Show the current level's map overview.\n"
  451. " \2*\1 : Show the Ring of Power's current status.\n"
  452. " \2K\1 : Show kills and achievements.\n"
  453. " \2M\1 : Show a detailed log of all encountered enemies.\n"
  454. " \2=\1 : Set game options.\n"
  455. " \2\"\1 : Send a message in spectator mode chat. (Also useful for notes to self.)\n"
  456. " \2?\1 : Show this help message.\n"
  457. " \2??\1 : Show detailed instructions.\n"
  458. "\n\3Shortcut commands:\1\n"
  459. " \2T\1 : Take the first item laying on the floor.\n"
  460. " \1a\1 : (Same as 'T'.)\n"
  461. " \1s\1 : (Same as '>'.)\n"
  462. " \2q\1 : Move away from the monsters in view. (Optimal direction is chosen automatically.)\n"
  463. " \2,\1 : Examine the first item laying on the floor.\n"
  464. " \2R\1 : Stand still for 100 turns.\n"_m
  465. ;
  466. for (const auto& shortcut : constants().shortcuts) {
  467. ret += " \2";
  468. ret += shortcut.first;
  469. ret += "\1 : ";
  470. ret += shortcut.second.help_message;
  471. ret += '\n';
  472. }
  473. return ret;
  474. }
  475. void handle_input_main(Player& p, GameState& state, GameOptions& options,
  476. bool& done, bool& dead, bool& regen,
  477. maudit::keypress k, bool debug_enabled, size_t n_skin) {
  478. bool redraw = false;
  479. switch (k.letter) {
  480. case 'Q':
  481. state.render.do_message("Are you sure you want to commit suicide? (Press 'Y' if you are.)"_m, true);
  482. p.state |= Player::QUITTING;
  483. break;
  484. case 'S':
  485. state.render.do_message("Your game has been saved. (Press space to exit.)"_m, true);
  486. done = true;
  487. dead = false;
  488. break;
  489. case 'q':
  490. run_away(p, state, n_skin);
  491. break;
  492. case 'h':
  493. move(p, state, -1, 0, n_skin);
  494. break;
  495. case 'j':
  496. move(p, state, 0, 1, n_skin);
  497. break;
  498. case 'k':
  499. move(p, state, 0, -1, n_skin);
  500. break;
  501. case 'l':
  502. move(p, state, 1, 0, n_skin);
  503. break;
  504. case 'y':
  505. move(p, state, -1, -1, n_skin);
  506. break;
  507. case 'u':
  508. move(p, state, 1, -1, n_skin);
  509. break;
  510. case 'b':
  511. move(p, state, -1, 1, n_skin);
  512. break;
  513. case 'n':
  514. move(p, state, 1, 1, n_skin);
  515. break;
  516. case '>':
  517. case '<':
  518. case 's':
  519. use_terrain(p, state, regen, done, dead);
  520. break;
  521. case '.':
  522. rest(state);
  523. break;
  524. case 'R':
  525. p.stats.cinc(constants().rest_count, 100);
  526. ++(state.ticks);
  527. break;
  528. case 'T':
  529. case 'a':
  530. take_item(p.px, p.py, 0, p, state);
  531. break;
  532. case ',':
  533. if (state.items.stack_size(p.px, p.py) == 0) {
  534. state.render.do_message("There are no items here."_m);
  535. } else {
  536. state.push_window(select_floor_item(p.inv, state.items, p.px, p.py, 0), screens_t::floor_item);
  537. }
  538. break;
  539. case 'i':
  540. state.push_window(show_inventory(p, state.moon.pi.phase_str, state.items), screens_t::inventory);
  541. break;
  542. case 'z':
  543. state.push_window(show_spells(p, state), screens_t::spells);
  544. break;
  545. case 'P':
  546. state.push_window(state.render.all_messages(), screens_t::messages);
  547. break;
  548. case 'K':
  549. state.push_window(show_achievements(p), screens_t::achievements);
  550. break;
  551. case 'M':
  552. state.push_window(show_monsters(p, state), screens_t::monsters);
  553. break;
  554. case '*':
  555. state.push_window(show_victory(p, state), screens_t::victory_status);
  556. break;
  557. case '@':
  558. state.push_window(show_stats(p), screens_t::stats);
  559. break;
  560. case '#':
  561. state.push_window(show_overmap(p, state), screens_t::overmap);
  562. break;
  563. case '=':
  564. state.push_window(show_options(state, options), screens_t::options, false);
  565. break;
  566. case '/':
  567. start_look_plain(p.state, p.look, p.px, p.py, state);
  568. break;
  569. case '\t':
  570. start_look_cycle(p.state, p.look, p.px, p.py, state, k);
  571. break;
  572. case '?':
  573. state.push_window(help_text(), screens_t::help);
  574. break;
  575. case '\"':
  576. do_player_input(state, p, ">>> "_m);
  577. p.state |= Player::SELFNOTE;
  578. break;
  579. // WATCH OUT!
  580. case '!':
  581. if (debug_enabled) {
  582. p.state = Player::DEBUG;
  583. }
  584. break;
  585. default:
  586. break;
  587. }
  588. switch (k.key) {
  589. case maudit::keycode::up:
  590. move(p, state, 0, -1, n_skin);
  591. break;
  592. case maudit::keycode::left:
  593. move(p, state, -1, 0, n_skin);
  594. break;
  595. case maudit::keycode::right:
  596. move(p, state, 1, 0, n_skin);
  597. break;
  598. case maudit::keycode::down:
  599. move(p, state, 0, 1, n_skin);
  600. break;
  601. case maudit::keycode::kp_7:
  602. move(p, state, -1, -1, n_skin);
  603. break;
  604. case maudit::keycode::kp_9:
  605. move(p, state, 1, -1, n_skin);
  606. break;
  607. case maudit::keycode::kp_1:
  608. move(p, state, -1, 1, n_skin);
  609. break;
  610. case maudit::keycode::kp_3:
  611. move(p, state, 1, 1, n_skin);
  612. break;
  613. default:
  614. break;
  615. }
  616. {
  617. const auto& shortcut = constants().shortcuts.find(k.letter);
  618. if (shortcut != constants().shortcuts.end()) {
  619. bool ok = false;
  620. for (const auto& slot_keypress : shortcut->second.slot_keypress) {
  621. p.inv.selected_slot = slot_keypress.first;
  622. if (handle_input_inv_item(p, state, done, dead, regen,
  623. maudit::keypress(slot_keypress.second))) {
  624. ok = true;
  625. break;
  626. }
  627. }
  628. if (!ok) {
  629. state.render.do_message(shortcut->second.fail_message);
  630. }
  631. }
  632. }
  633. if (redraw) {
  634. state.render.clear();
  635. }
  636. }
  637. void handle_input_debug(Player& p, GameState& state, bool& regen, maudit::keypress k) {
  638. switch (k.letter) {
  639. case '@':
  640. {
  641. tag_mem_t tagmem;
  642. p.stats.sinc(tag_t("shield", tagmem), 1000);
  643. break;
  644. }
  645. case '2':
  646. {
  647. tag_mem_t tagmem;
  648. p.stats.stats[tag_t("shield", tagmem)].val = 0;
  649. break;
  650. }
  651. case '$':
  652. state.render.do_message(nlp::message("Monetary base: %d", finance::supply().get_base()));
  653. break;
  654. case 's':
  655. state.species_counts = species().counts;
  656. state.render.do_message("Wiped species counts.");
  657. break;
  658. case 'd':
  659. state.designs_counts = designs().counts;
  660. state.render.do_message("Wiped designs counts.");
  661. break;
  662. case '>':
  663. p.worldz++;
  664. regen = true;
  665. state.render.do_message("Descended.");
  666. break;
  667. case '<':
  668. p.worldz--;
  669. regen = true;
  670. state.render.do_message("Ascended.");
  671. break;
  672. case 't':
  673. {
  674. grid::pt xy;
  675. if (state.grid.one_of_walk(state.rng, xy)) {
  676. state.render.invalidate(p.px, p.py);
  677. p.px = xy.first;
  678. p.py = xy.second;
  679. state.render.invalidate(p.px, p.py);
  680. }
  681. break;
  682. }
  683. case '.':
  684. //state.render.do_message(nlp::message("%d %d | %d", p.px, p.py, state.grid._get(p.px, p.py)));
  685. state.render.do_message(dowsing_message(p, state));
  686. break;
  687. case 'k':
  688. state.render.do_message(nlp::message("%d %d | %d", p.px, p.py, state.grid.get_karma(p.px, p.py)));
  689. break;
  690. case '+':
  691. p.level++;
  692. state.render.do_message("Level gained.");
  693. break;
  694. case 'i':
  695. {
  696. std::map<tag_t, unsigned int> q = state.designs_counts.take(state.rng, p.worldz, 1);
  697. if (q.size() == 1) {
  698. state.items.place(p.px, p.py,
  699. state.items.make_item(q.begin()->first, items::pt(p.px, p.py), state.rng),
  700. state.render);
  701. }
  702. break;
  703. }
  704. case 'Z':
  705. {
  706. uint32_t rnd = state.rng.range(0u, 0xFFFFFFFF);
  707. state.render.do_message("** " + rcode::magick_encode(rnd));
  708. cast_random_spell(p, rnd, state);
  709. break;
  710. }
  711. case 'S':
  712. {
  713. tag_mem_t tagmem;
  714. unsigned int n = summon_out_of_view(p, state, tag_t("angelgift", tagmem), 0);
  715. state.render.do_message(nlp::message("Summoned %d", n));
  716. break;
  717. }
  718. case 'z':
  719. {
  720. tag_mem_t tagmem;
  721. for (const auto& c : celautos().bank) {
  722. Terrain::spell_t s;
  723. s.stat = tag_t("karma", tagmem);
  724. s.stat_min = -3;
  725. s.stat_max = 3;
  726. s.ca_tag = c.first;
  727. s.timeout = 9999;
  728. s.name = c.second.debug_name;
  729. p.spells.push_back(s);
  730. }
  731. state.render.do_message("Granted spells.");
  732. break;
  733. }
  734. case 'M':
  735. state.render.do_message("Long message message lorem ispum alpha beta gamma one "
  736. "two three four lorem ipsum long message spanning many lines "
  737. "qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, "
  738. "sed quia non numquam eius modi tempora incidunt.");
  739. break;
  740. case 'w':
  741. do_player_wish(state, p);
  742. return;
  743. case 'I':
  744. simple_wish(state, p, p.input.s);
  745. break;
  746. case 'O':
  747. special_wish(state, p, p.input.s);
  748. break;
  749. case 'l':
  750. {
  751. tag_mem_t tagmem;
  752. state.features.x_set(p.px, p.py, tag_t("light", tagmem), state.render);
  753. break;
  754. }
  755. case 'A':
  756. {
  757. tag_mem_t tagmem;
  758. state.monsters.summon_any(state.neigh, state.rng, state.grid, state.species_counts, state.render,
  759. p.px, p.py, p.px, p.py, p.level, 1, tag_t("something", tagmem));
  760. break;
  761. }
  762. case 'L':
  763. for (unsigned int y = 0; y < state.render.h; ++y) {
  764. for (unsigned int x = 0; x < state.render.w; ++x) {
  765. state.render.set_is_lit(x, y, 5, true);
  766. state.render.invalidate(x, y);
  767. }
  768. }
  769. break;
  770. }
  771. p.state &= ~(Player::DEBUG);
  772. }
  773. bool handle_input_pick_direction(unsigned int px, unsigned int py, GameState& state, maudit::keypress k,
  774. unsigned int& nx, unsigned int& ny) {
  775. nx = px;
  776. ny = py;
  777. switch (k.letter) {
  778. case 'h':
  779. nx--;
  780. break;
  781. case 'j':
  782. ny++;
  783. break;
  784. case 'k':
  785. ny--;
  786. break;
  787. case 'l':
  788. nx++;
  789. break;
  790. case 'y':
  791. nx--;
  792. ny--;
  793. break;
  794. case 'u':
  795. nx++;
  796. ny--;
  797. break;
  798. case 'b':
  799. nx--;
  800. ny++;
  801. break;
  802. case 'n':
  803. nx++;
  804. ny++;
  805. break;
  806. case '.':
  807. case '>':
  808. return true;
  809. default:
  810. break;
  811. }
  812. switch (k.key) {
  813. case maudit::keycode::up:
  814. ny--;
  815. break;
  816. case maudit::keycode::left:
  817. nx--;
  818. break;
  819. case maudit::keycode::right:
  820. nx++;
  821. break;
  822. case maudit::keycode::down:
  823. ny++;
  824. break;
  825. default:
  826. break;
  827. }
  828. if (!state.neigh.linked(neighbors::pt(px, py), neighbors::pt(nx, ny))) {
  829. return false;
  830. }
  831. return true;
  832. }
  833. void handle_input_messages(GameState& state, maudit::keypress k, bool do_howto) {
  834. if (do_howto && k.letter == '?') {
  835. state.push_window("howto_text_placeholder"_m, screens_t::howto);
  836. return;
  837. }
  838. state.window_stack.pop_back();
  839. }
  840. void handle_input_overmap(Player& p, GameState& state, maudit::keypress k) {
  841. if (k.letter == '-') {
  842. state.window_stack.pop_back();
  843. state.push_window(show_overmap(p, state, p.overmap.scale * 2), screens_t::overmap);
  844. } else if (k.letter == '+') {
  845. if (p.overmap.scale > 2) {
  846. state.window_stack.pop_back();
  847. state.push_window(show_overmap(p, state, p.overmap.scale / 2), screens_t::overmap);
  848. }
  849. } else {
  850. state.window_stack.pop_back();
  851. }
  852. }
  853. bool handle_input_input(GameState& state, std::string& input_string, int limit, maudit::keypress k) {
  854. if (k.letter == '\n') {
  855. return false;
  856. } else if (k.letter == '\x7F' || k.letter == '\x08' || k.key == maudit::keycode::del) {
  857. if (!input_string.empty()) {
  858. state.render.replace_message([](std::string& msg) {
  859. msg.pop_back();
  860. });
  861. input_string.pop_back();
  862. }
  863. } else if (k.letter >= ' ' && k.letter <= '~') {
  864. if (limit >= 0 && input_string.size() >= (size_t)limit)
  865. return true;
  866. unsigned char letter = k.letter;
  867. state.render.replace_message([letter](std::string& msg) {
  868. msg += letter;
  869. });
  870. input_string += k.letter;
  871. }
  872. return true;
  873. }
  874. void handle_input_options(GameState& state, GameOptions& options, maudit::keypress k) {
  875. switch (k.letter) {
  876. case 'a':
  877. options.center_view = !(options.center_view);
  878. break;
  879. case 'b':
  880. options.no_fade_colors = !(options.no_fade_colors);
  881. break;
  882. case 'c':
  883. case '\t':
  884. state.render.set_ui_symbol(1);
  885. break;
  886. default:
  887. state.window_stack.pop_back();
  888. return;
  889. }
  890. state.window_stack.back().message = show_options(state, options);
  891. }
  892. void start_digging(Player& p, GameState& state, unsigned int nx, unsigned int ny) {
  893. state.render.do_message("You start digging."_m);
  894. p.dig.x = nx;
  895. p.dig.y = ny;
  896. p.dig.h = state.grid.get(nx, ny);
  897. p.stats.cinc(constants().digging_count, 2 + ((p.dig.h + 10) / p.inv.get_digging()));
  898. }
  899. void Game::handle_input(GameState& state, GameOptions& options,
  900. bool& done, bool& dead, bool& regen,
  901. maudit::keypress k) {
  902. if (p.state == Player::DEBUG) {
  903. handle_input_debug(p, state, regen, k);
  904. return;
  905. }
  906. if (p.state & Player::QUITTING) {
  907. if (k.letter == 'y' || k.letter == 'Y') {
  908. state.render.do_message("See you later. (Press space to exit.)"_m);
  909. p.attacker = "suicide"_m;
  910. done = true;
  911. dead = true;
  912. return;
  913. }
  914. p.state &= ~(Player::QUITTING);
  915. }
  916. if (p.state & Player::INPUTTING) {
  917. if (!handle_input_input(state, p.input.s, p.input.limit, k)) {
  918. p.state &= ~(Player::INPUTTING);
  919. } else {
  920. return;
  921. }
  922. }
  923. // //
  924. if (p.state & Player::TERRAIN_STEP2) {
  925. use_terrain(p, state, regen, done, dead);
  926. p.state = Player::MAIN;
  927. return;
  928. }
  929. if (p.state & Player::DESIGN_STEP2) {
  930. apply_item(p, p.inv.selected_slot, state, regen);
  931. p.state = Player::MAIN;
  932. return;
  933. }
  934. if (p.state & Player::SELFNOTE) {
  935. state.render.do_message(std::string(">>> "_m) + p.input.s);
  936. p.state = Player::MAIN;
  937. return;
  938. }
  939. // //
  940. if (p.state & Player::LOOKING) {
  941. handle_input_looking(p.state, p.look, p.px, p.py, state, k);
  942. if (p.state & Player::FIRED) {
  943. if (p.state & Player::THROWING) {
  944. end_throw_item(p, p.inv.selected_slot, p.look.x, p.look.y, state);
  945. } else if (p.state & Player::BLASTING) {
  946. end_blast_item(p, p.inv.selected_slot, p.look.x, p.look.y, state);
  947. } else if (p.state & Player::CLOUDING) {
  948. end_cloud_item(p, p.inv.selected_slot, p.look.x, p.look.y, state);
  949. } else if (p.state & Player::P_BLASTING) {
  950. end_poly_blast(p, p.polymorph_ability, p.look.x, p.look.y, state);
  951. } else if (p.state & Player::P_CLOUDING) {
  952. end_poly_cloud(p, p.polymorph_ability, p.look.x, p.look.y, state);
  953. }
  954. ++(state.ticks);
  955. p.state = Player::MAIN;
  956. }
  957. return;
  958. }
  959. if (p.state & Player::DIGGING) {
  960. unsigned int nx;
  961. unsigned int ny;
  962. bool ok = handle_input_pick_direction(p.px, p.py, state, k, nx, ny);
  963. if (ok) {
  964. if (nx == p.px && ny == p.py) {
  965. bool ok = false;
  966. features::Feature feat;
  967. if (state.features.get(nx, ny, feat)) {
  968. if (feat.tag == constants().grave) {
  969. ok = true;
  970. }
  971. } else if (state.grid.is_floor(nx, ny)) {
  972. ok = true;
  973. }
  974. if (!ok) {
  975. state.render.do_message("You cannot dig here."_m);
  976. } else {
  977. start_digging(p, state, nx, ny);
  978. ++(state.ticks);
  979. }
  980. } else {
  981. if (state.grid.is_walk(nx, ny)) {
  982. state.render.do_message("There is nothing to dig there."_m);
  983. } else {
  984. start_digging(p, state, nx, ny);
  985. ++(state.ticks);
  986. }
  987. }
  988. }
  989. p.state = Player::MAIN;
  990. return;
  991. }
  992. if (state.window_stack.empty()) {
  993. handle_input_main(p, state, options, done, dead, regen, k, debug_enabled, n_skin);
  994. return;
  995. }
  996. switch ((screens_t)state.window_stack.back().type) {
  997. case screens_t::messages:
  998. case screens_t::tombstone:
  999. case screens_t::howto:
  1000. case screens_t::achievements:
  1001. case screens_t::victory_status:
  1002. case screens_t::stats:
  1003. handle_input_messages(state, k, false);
  1004. break;
  1005. case screens_t::help:
  1006. handle_input_messages(state, k, true);
  1007. break;
  1008. case screens_t::overmap:
  1009. handle_input_overmap(p, state, k);
  1010. break;
  1011. case screens_t::inventory:
  1012. handle_input_inventory(p, state, done, dead, regen, k);
  1013. break;
  1014. case screens_t::inv_item:
  1015. handle_input_inv_item(p, state, done, dead, regen, k);
  1016. break;
  1017. case screens_t::floor_item:
  1018. handle_input_floor_item(p, state, done, dead, regen, k);
  1019. break;
  1020. case screens_t::spells:
  1021. handle_input_spells(p, state, k);
  1022. break;
  1023. case screens_t::bank_main:
  1024. case screens_t::bank_withdrawal:
  1025. case screens_t::bank_deposit:
  1026. case screens_t::bank_buy:
  1027. case screens_t::bank_buy_confirm:
  1028. handle_input_banking(p, state, k);
  1029. break;
  1030. case screens_t::options:
  1031. handle_input_options(state, options, k);
  1032. break;
  1033. default:
  1034. // This is a sanity error check condition.
  1035. state.window_stack.pop_back();
  1036. break;
  1037. }
  1038. }
  1039. template <typename FUNC>
  1040. void Game::goodbye_message(GameState& state, FUNC println) {
  1041. highscore::Scores scores;
  1042. println("");
  1043. println("");
  1044. println("Highscore table:"_m);
  1045. println("");
  1046. scores.by_plev();
  1047. scores.process([println](size_t n, const bones::bone_t::fakeobj& name, const bones::bone_t::fakeobj& cause,
  1048. unsigned int plev, int dlev, double worth, bool victory, size_t rcodes) {
  1049. std::string line1;
  1050. std::string line2;
  1051. std::string pad;
  1052. ++n;
  1053. if (n < 10)
  1054. pad = " ";
  1055. if (victory) {
  1056. line1 = nlp::message("%s%d) %S, a glorious victor of level %d."_m, pad, n, name, plev+1);
  1057. line2 = nlp::message(" Last seen on level %d. Net worth: %d $ZM."_m,
  1058. dlev+1, worth);
  1059. } else {
  1060. line1 = nlp::message("%s%d) %S, level %d."_m, pad, n, name, plev+1);
  1061. line2 = nlp::message(" Killed on level %d by %s. Net worth: %d $ZM."_m,
  1062. dlev+1, cause, worth);
  1063. }
  1064. if (rcodes == 1) {
  1065. line1 += nlp::message(" (Replay codes were used once.)"_m);
  1066. } else if (rcodes > 1) {
  1067. line1 += nlp::message(" (Replay codes were used %d times.)"_m, rcodes);
  1068. }
  1069. println(line1);
  1070. println(line2);
  1071. println("");
  1072. });
  1073. println("(Press any key)"_m);
  1074. }
  1075. #endif