1 // Hyperbolic Rogue -- Orb Strategy Mode
2 // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file inventory.cpp
5  *  \brief Orb Strategy Mode
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 /** \brief Implementation of the Orb Strategy Mode.
12  *
13  * The most important functions called outside is hr::inv::show().
14  */
15 EX namespace inv {
16 
17 #if CAP_INV
18   /** \brief is the Orb Strategy Mode active? */
19   EX bool on;
20   /** \brief the number of Orbs used up in each type */
21   EX array<int, ittypes> usedup;
22   /** \brief the number of Orbs remaining in each type -- it is recalculated based on your treasure and hr::inv::usedup after every move */
23   EX array<int, ittypes> remaining;
24   /** \brief extra orbs can be added to OSM using -IX commandline option */
25   EX array<int, ittypes> extra_orbs;
26 
27   /** \brief random seed used for hr::inv::invr */
28   EX int rseed;
29   /** \brief have we used any 'forbidden' orbs? */
30   EX bool usedForbidden;
31 
32   /** \brief initialize the OSM data for a new game */
init()33   EX void init() {
34     rseed = hrandpos();
35     usedForbidden = false;
36     for(int i=0; i<ittypes; i++) usedup[i] = 0;
37     }
38 
39   static const int MIRRORED = 1000;
40   static const int TESTMIRRORED = 900;
41 
42   struct lateextraorb {
43     eItem treasure;
44     eItem orb;
45     };
46 
47   vector<lateextraorb> lateextraorbs = {
48     {itPower, itOrbFlash},
49     {itPower, itOrbSpeed},
50     {itPower, itOrbAether},
51     {itPower, itOrbWinter},
52 
53     {itTrollEgg, itOrbFish},
54     {itTrollEgg, itOrbStunning},
55     {itTrollEgg, itOrbLuck},
56     {itTrollEgg, itOrbLife},
57     {itTrollEgg, itOrbDigging},
58     {itTrollEgg, itOrbSpace},
59 
60     {itFulgurite, itOrbLightning},
61     {itWindstone, itOrbSpeed},
62     {itDragon, itOrbDragon},
63     {itSlime, itOrbFlash},
64     {itDodeca, itOrbShield},
65 
66     {itGreenGrass, itOrbHorns},
67     {itGreenGrass, itOrbShield},
68     {itGreenGrass, itOrbThorns}
69     };
70 
71   /** \brief how many orbs can we get from Orb-of-Mirroring orb */
mirrorqty0(eItem orb)72   int mirrorqty0(eItem orb) {
73     if(shmup::on && isShmupLifeOrb(orb))
74       return 3;
75     if(orb == itOrbWater) return 10;
76     if(orb == itOrbSummon) return 9;
77     if(orb == itOrbEmpathy) return 9;
78     if(orb == itOrbMatter) return 9;
79     if(orb == itOrbIntensity) return 8;
80     if(orb == itOrbLuck) return 8;
81     if(orb == itOrbSpace) return 7;
82 
83     if(orb == itOrbWinter) return 6;
84     if(orb == itOrbLife) return 6;
85     if(orb == itOrbLove) return 6;
86     if(orb == itOrbRecall) return 6;
87     if(orb == itOrbDigging) return 6;
88     if(orb == itOrbGravity) return 6;
89     if(orb == itOrbImpact) return 6;
90 
91     if(orb == itOrbTime) return 5;
92     if(orb == itOrbAir) return 5;
93     if(orb == itOrbFish) return 5;
94     if(orb == itOrbStunning) return 5;
95     if(orb == itOrbUndeath) return 5;
96     if(orb == itOrb37) return 5;
97     if(orb == itOrbDomination) return 5;
98     if(orb == itOrbBull) return 5;
99     if(orb == itOrbHorns) return 5;
100 
101     if(orb == itOrbAether) return 4;
102     if(orb == itOrbInvis) return 4;
103     if(orb == itOrbFire) return 4;
104     if(orb == itOrbDragon) return 4;
105     if(orb == itOrbIllusion) return 4;
106     if(orb == itOrbDiscord) return 4;
107     if(orb == itOrbBeauty) return 4;
108 
109     if(orb == itOrbMirror) return 1;
110     return 3;
111     }
112 
mirrorqty(eItem orb)113   int mirrorqty(eItem orb) {
114     if(orb == itOrbMirror) return 1;
115     return int(mirrorqty0(orb) * sqrt(1.000001+items[itPower]/20.));
116     }
117 
118   /** \brief PRNG used for calculating how many Orbs you get for your collected treasure */
119   std::mt19937 invr;
120 
121   /** \brief initialize hr::inv::invr */
sirand(int i)122   void sirand(int i) {
123     invr.seed(i);
124     }
125 
126   /** \brief get the next random value from hr::inv::invr */
irand(int i)127   int irand(int i) {
128     return invr() % i;
129     }
130 
131   EX eItem whichorbinfo;
132   EX string orbinfoline, extra;
133 
extraline(eItem it,string s)134   string extraline(eItem it, string s) {
135     return " "+XLAT1(iinf[it].name) + " ("+s+")";
136     }
137 
gainOrbs(eItem it,eItem o)138   void gainOrbs(eItem it, eItem o) {
139     int qty = items[it];
140     if(it == itHolyGrail) {
141       remaining[itOrbIllusion] += qty;
142       if(it == itOrbIllusion) {
143         orbinfoline += XLAT("Unlocked by: %1 in %2", it, landof(it));
144         orbinfoline += XLAT(" (next at %1)", its(qty+1));
145         }
146       }
147     else {
148       bool nextfound = false;
149       int fst = (ls::any_chaos() ? 5 : 10);
150       if(qty >= fst) remaining[o]++;
151       else {
152         if(whichorbinfo == o) {
153           if(it == itHyperstone) {
154             extra += extraline(it, its(fst));
155             }
156           else {
157             orbinfoline += XLAT("Unlocked by: %1 in %2", it, landof(it));
158             orbinfoline += XLAT(" (next at %1)", its(10));
159             }
160           }
161         nextfound = true;
162         }
163       int last = fst;
164       for(int k=0; k<30 || !nextfound; k++) {
165         int maxstep = ls::any_chaos() ? 10 + 2 * k : 15 + 5 * k;
166         if(o == itOrbMirror)
167           maxstep += 5 * (k-1) * (k-2);
168         else
169           maxstep += (k-1) * (k-2);
170         int xnext;
171         if(k >= 30 || o == itOrbMirror) {
172           xnext = last + maxstep/2; last = xnext-1;
173           maxstep = 1;
174           }
175         else
176           xnext = last + 1 + irand(maxstep);
177         if(xnext > qty && !nextfound) {
178           if(whichorbinfo == o) {
179             if(it == itHyperstone) {
180               extra += extraline(it, its(last+maxstep));
181               }
182             else {
183               orbinfoline += XLAT("Unlocked by: %1 in %2", it, landof(it));
184               if(maxstep == 1)
185                 orbinfoline += XLAT(" (next at %1)", its(last+1));
186               else
187                 orbinfoline += XLAT(" (next at %1 to %2)", its(last+1), its(last + maxstep));
188               }
189             }
190           nextfound = true;
191           }
192         if(xnext <= qty) remaining[o]++;
193         last = xnext;
194         }
195       }
196     }
197 
nextp2(int i)198   int nextp2(int i) {
199     int z = 1;
200     while(z <= i) z <<= 1;
201     return z;
202     }
203 
gainMirrors(eItem forwhich)204   void gainMirrors(eItem forwhich) {
205     int qtl = items[forwhich];
206     while(qtl > 0) qtl >>= 1, remaining[itOrbMirror]++;
207     if(whichorbinfo == itOrbMirror)
208       extra += extraline(forwhich, its(nextp2(items[forwhich])));
209     }
210 
211   vector<eItem> offensiveOrbs = {
212     itOrbFlash, itOrbLightning, itOrbPsi, itOrbThorns,
213     itOrbFreedom, itOrbSword, itOrbSword2,
214     itOrbHorns, itOrbDragon, itOrbStunning
215     };
216 
217   vector<eItem> elementalOrbs = {itOrbFire, itOrbWater, itOrbDigging, itOrbAir};
218 
219   vector<eItem> demonicOrbs = {itOrbFire, itOrbHorns, itOrbSummon};
220 
isIn(eItem o,vector<eItem> & l)221   bool isIn(eItem o, vector<eItem>& l) {
222     for(auto it: l) if(it == o) return true;
223     return false;
224     }
225 
gainRandomOrbs(vector<eItem> orblist,eItem which,int each,int reduce)226   void gainRandomOrbs(vector<eItem> orblist, eItem which, int each, int reduce) {
227     const int qoff = isize(orblist);
228     for(int i=1; i<qoff; i++) swap(orblist[i], orblist[irand(1+i)]);
229     for(int i=0; i<20; i++) {
230       int nextat = (i+1)*each + reduce;
231       if(items[which] >= nextat) {
232         remaining[orblist[i%qoff]]++;
233         }
234       else {
235         if(isIn(whichorbinfo, orblist))
236           extra += extraline(which, its(nextat) + "?");
237         break;
238         }
239       }
240     }
241 
gainGuestOrbs()242   void gainGuestOrbs() {
243     for(auto& oi: orbinfos) {
244       if(oi.flags & orbgenflags::OSM_AT10) {
245         eItem it = treasureType(oi.l);
246         int fst = ls::any_chaos() ? 5 : 10;
247         if(items[it] >= fst) {
248           remaining[oi.orb]++;
249           }
250         if(whichorbinfo == oi.orb) extra += extraline(it, its(fst));
251         }
252       }
253     }
254 
gainLove()255   void gainLove() {
256     if(princess::reviveAt) {
257       remaining[itOrbLove]++;
258       int s = gold(NO_LOVE);
259       int last = princess::reviveAt;
260       for(int k=0;; k++) {
261         int nextstep = 50 + 20 * k;
262         last += nextstep;
263         if(last > s) {
264           if(whichorbinfo == itOrbLove) {
265             orbinfoline += XLAT("Unlocked by: %1 in %2", itSavedPrincess, laPrincessQuest);
266             orbinfoline += XLAT(" (next at %1)", its(last));
267             }
268           break;
269           }
270         else { last += nextstep; remaining[itOrbLove]++; }
271         }
272       }
273     }
274 
gainLate(eItem tr,eItem orb)275   void gainLate(eItem tr, eItem orb) {
276     int at = 10 + irand(41);
277     int itr = items[tr];
278     if(itr >= at) remaining[orb]++;
279     if(whichorbinfo == orb)
280       extra += extraline(tr, itr >= at ? (its(at)+"!") : "10-50");
281     }
282 
283   /** \brief Compute how many orbs you get for your current treasure. This is called after every move, and should give consistent results */
compute()284   EX void compute() {
285     extra = "";
286     orbinfoline = "";
287 
288     for(int i=0; i<ittypes; i++) remaining[i] = extra_orbs[i]-usedup[i];
289     for(int i=0; i<ittypes; i++) if(usedup[i] >= TESTMIRRORED) {
290       remaining[i] += MIRRORED;
291       remaining[i] -= mirrorqty0(eItem(i));
292       remaining[i] += mirrorqty(eItem(i));
293       }
294 
295     sirand(rseed);
296 
297     gainGuestOrbs();
298 
299     gainOrbs(itShard, itOrbMirror);
300     gainOrbs(itHyperstone, itOrbMirror);
301     gainOrbs(itDiamond, itOrbFlash);
302     gainOrbs(itGold, itOrbLife);
303     gainOrbs(itSpice, itOrbShield);
304     gainOrbs(itRuby, itOrbLightning);
305     gainOrbs(itElixir, itOrbSpeed);
306     gainOrbs(itBone, itGreenStone);
307     gainOrbs(itHell, itOrbYendor);
308     gainOrbs(itStatue, itOrbTeleport);
309     gainOrbs(itFeather, itOrbSafety);
310     gainOrbs(itSapphire, itOrbMorph);
311     gainOrbs(itFernFlower, itOrbThorns);
312     gainOrbs(itWine, itOrbAether);
313     gainOrbs(itSilver, itOrbDigging);
314     gainOrbs(itRoyalJelly, itOrbInvis);
315     gainOrbs(itEmerald, itOrbPsi);
316     gainOrbs(itPower, itOrbFire);
317     gainOrbs(itHolyGrail, itOrbIllusion);
318     gainOrbs(itGrimoire, itOrbDragon);
319     gainOrbs(itPirate, itOrbTime);
320     gainOrbs(itRedGem, itOrbSpace);
321     gainOrbs(itBombEgg, itOrbFriend);
322     gainOrbs(itCoast, itOrbEmpathy);
323     gainOrbs(itWhirlpool, itOrbWater);
324     gainOrbs(itPalace, itOrbDiscord);
325     gainOrbs(itFjord, itOrbFish);
326     gainOrbs(itSavedPrincess, itOrbLove);
327     gainOrbs(itIvory, itOrbMatter);
328     gainOrbs(itZebra, itOrbFrog);
329     gainOrbs(itElemental, itOrbSummon);
330     gainOrbs(itFulgurite, itOrbStunning);
331     gainOrbs(itMutant, itOrbWoods);
332     gainOrbs(itMutant2, itOrbFreedom);
333     gainOrbs(itLotus, itOrbUndeath);
334     gainOrbs(itWindstone, itOrbAir);
335     gainOrbs(itRose, itOrbBeauty);
336     gainOrbs(itCoral, itOrb37);
337     gainOrbs(itBabyTortoise, itOrbShell);
338     gainOrbs(itApple, itOrbEnergy);
339     gainOrbs(itDragon, itOrbDomination);
340     gainOrbs(itKraken, itOrbSword);
341     gainOrbs(itBarrow, itOrbSword2);
342     gainOrbs(itTrollEgg, itOrbStone);
343     gainOrbs(itSlime, itOrbRecall);
344     gainOrbs(itAmethyst, itOrbNature);
345     gainOrbs(itDodeca, itOrbDash);
346     gainOrbs(itGreenGrass, itOrbBull);
347     gainOrbs(itBull, itOrbHorns);
348     if(items[itOrbYendor]) remaining[itOrbMirror]++;
349     gainMirrors(itOrbYendor);
350     gainMirrors(itHolyGrail);
351     gainLove();
352     gainRandomOrbs(offensiveOrbs, itBone, 25, 0);
353     gainRandomOrbs(elementalOrbs, itElemental, 12, 0);
354     gainRandomOrbs(demonicOrbs, itHell, 20, 100);
355     gainOrbs(itLavaLily, itOrbLava);
356     gainOrbs(itHunting, itOrbSide3);
357     gainOrbs(itBlizzard, itOrbWinter);
358     gainOrbs(itTerra, itOrbSide1);
359 
360     for(auto& it: lateextraorbs) gainLate(it.treasure, it.orb);
361 
362     gainOrbs(itGlowCrystal, itOrbSide2);
363     gainOrbs(itSwitch, itOrbPhasing);
364     gainOrbs(itMagnet, itOrbMagnetism);
365     gainOrbs(itRuins, itOrbSlaying);
366 
367     gainOrbs(itWest, itOrbGravity);
368     gainOrbs(itVarTreasure, itOrbIntensity);
369     gainOrbs(itBrownian, itOrbChoice);
370 
371     gainOrbs(itFrog, itOrbImpact);
372     gainOrbs(itWet, itOrbPlague);
373     gainOrbs(itEclectic, itOrbChaos);
374 
375     gainOrbs(itCursed, itOrbPurity);
376     gainOrbs(itDice, itOrbLuck);
377 
378 #if CAP_DAILY
379     daily::gifts();
380 #endif
381 
382     if(items[itOrbLove] && !items[itSavedPrincess]) items[itSavedPrincess] = 1;
383 
384     int& r = remaining[itGreenStone];
385 
386     if(items[itBone] >= 0) {
387       for(int i=0; i<ittypes; i++) if(i != itGreenStone) {
388         r += usedup[i];
389         if(usedup[i] >= TESTMIRRORED) r -= (MIRRORED - mirrorqty0(eItem(i)));
390         }
391       }
392 
393     items[itGreenStone] += r;
394     usedup[itGreenStone] += r;
395     r = 0;
396 
397     if(shmup::on) for(int i=0; i<ittypes; i++) {
398       if(remaining[i] && isShmupLifeOrb(eItem(i))) {
399         gainLife();
400         remaining[i]--;
401         usedup[i]++;
402         }
403       }
404 
405     items[itInventory] = 0;
406     for(int i=0; i<ittypes; i++)
407       if(i != itGreenStone && i != itOrbYendor)
408         items[itInventory] += remaining[i];
409     }
410 
411   map<char, eItem> orbmap;
412   string orbkeys = "zfwplSetsTaMIYgCcPOWAFydLGRUkouE.,bVNxDjJZnrvhBm!23456789@#$%()";
413 
414   typedef pair<int, int> pxy;
415   vector<pxy> orbcoord;
416 
sq(pxy p)417   int sq(pxy p) {
418     int zz = (2*p.first+p.second)*(2*p.first+p.second) + 3*p.second*p.second;
419     zz *= 20; zz += abs(p.second);
420     zz *= 20; zz += abs(p.first);
421     zz *= 4; zz += (p.first>0)*2+(p.second>0);
422     return zz;
423     }
424 
425   bool plain;
426 
427   eItem which;
428 
429   bool mirroring;
430 
431   EX const char* helptext =
432     "You are playing in the Orb Strategy Mode. Collecting treasure "
433     "gives you access to magical Orb powers. In this mode, "
434     "unlocking requirements are generally higher, and "
435     "several quests and lands "
436     "give you extremely powerful Orbs of the Mirror.\n";
437 
evokeBeautyAt(cell * c)438   void evokeBeautyAt(cell *c) {
439     forCellEx(c2, c)
440       if(c2->monst && !isFriendly(c2->monst) && !isIvy(c2->monst)) {
441         c2->stuntime += 3;
442         checkStunKill(c2);
443         }
444     }
445 
evokeOrb(eItem it)446   void evokeOrb(eItem it) {
447     if(it == itOrbFreedom)
448       for(cell *pc: player_positions())
449         checkFreedom(pc);
450 
451     if(it == itOrbBeauty) {
452       for(cell *pc: player_positions())
453           evokeBeautyAt(pc);
454       if(items[itOrbEmpathy])
455         for(cell *c: dcal) if(isFriendly(c->monst))
456           evokeBeautyAt(c);
457       }
458 
459     if(it == itOrbDigging) {
460       forCellCM(c2, cwt.at) {
461         earthFloor(c2);
462         if(c2->wall == waCavewall && !c2->monst)
463           c2->wall = waNone;
464         }
465       }
466 
467     if(it == itOrbSword || it == itOrbSword2) {
468       for(int i: player_indices()) {
469         cwt.at = playerpos(i);
470         multi::cpid = i;
471         swordAttackStatic(it == itOrbSword2);
472         }
473       }
474     }
475 
osminfo(eItem orb)476   EX string osminfo(eItem orb) {
477     string s = XLAT("Number of uses left: %1", its(remaining[orb]));
478     int us = usedup[orb];
479     if(us >= TESTMIRRORED) s += XLAT(" (mirrored)"), us = us - MIRRORED + mirrorqty0(orb);
480     if(us) s += XLAT(" (used %1 times)", its(us));
481     return s;
482     }
483 
484   EX bool activating;
485 
486   /** \brief show the OSM Orb screen */
show()487   EX void show() {
488 
489     multi::cpid = 0; /* just in case */
490 
491     if(remaining[itOrbSword]) items[itOrbSword]++;
492     if(remaining[itOrbSword2]) items[itOrbSword2]++;
493     gamescreen(2);
494     if(remaining[itOrbSword]) items[itOrbSword]--;
495     if(remaining[itOrbSword2]) items[itOrbSword2]--;
496     cmode = sm::CENTER;
497 
498     orbcoord.clear();
499     for(int y=-3; y<=3; y++) for(int x=-5; x<=5; x++) if(x+y<=6 && x+y >= -6 && (x||y))
500       orbcoord.emplace_back(x,y);
501     sort(orbcoord.begin(), orbcoord.end(), [](pxy p1, pxy p2) {
502       return sq(p1) < sq(p2); });
503 
504     ld rad = min(vid.xres, vid.yres) / 20;
505     ld rad3 = int(rad * sqrt(3));
506 
507     compute();
508     orbmap.clear();
509     which = itNone;
510 
511     if(plain) dialog::init(mirroring ? XLAT("mirror what?") : XLAT("inventory"), forecolor, 150, 100);
512 
513     int j = 0, oc = 6;
514 
515     if(1) {
516     flat_model_enabler fme;
517 
518     for(int i=0; i<ittypes; i++) {
519       eItem o = eItem(i);
520       if(itemclass(o) == IC_ORB && !(classflag(o) & IF_CURSE)) {
521         char c = orbkeys[j++];
522         if(c == 0) println(hlog, "missing char for ", dnameof(o));
523         if(remaining[i] || usedup[i]) {
524           orbmap[c] = o;
525           if(plain)
526             dialog::addSelItem(XLAT1(iinf[o].name), its(remaining[i]), c);
527           else {
528             if(oc >= isize(orbcoord)) {
529               println(hlog, "error: oc=", oc, " with only ", isize(orbcoord), " positions");
530               continue;
531               }
532             auto pos = orbcoord[oc++];
533             ld px = current_display->xcenter + 2*rad*pos.first + rad*pos.second;
534             ld py = current_display->ycenter + pos.second * rad3;
535             int icol = iinf[o].color;
536             if(!remaining[i]) icol = gradient(icol, 0, 0, .5, 1);
537             bool gg = graphglyph(false);
538 
539             if(!hiliteclick) {
540               if(gg) {
541                 initquickqueue();
542                 transmatrix V = atscreenpos(px, py, rad*2);
543                 drawItemType(o, NULL, shiftless(V), icol, ticks/3 + i * 137, false);
544                 quickqueue();
545                 }
546 
547               int tcol = remaining[i] ? darkenedby(icol, 1) : 0;
548 
549               if(remaining[i] != 1 || !gg)
550                 displaystr(px, py, 2, gg?rad:rad*3/2, remaining[i] <= 0 ? "X" : remaining[i] == 1 ? "o" : its(remaining[i]), tcol, 8);
551               }
552 
553             bool b = hypot(mousex-px, mousey-py) < rad;
554             if(b) {
555               getcstat = c,
556               which = o;
557               }
558             }
559           }
560         }
561       }
562     }
563 
564     if(plain) {
565       dialog::addBreak(750);
566       dialog::addItem(XLAT("help"), SDLK_F1);
567       dialog::addItem(XLAT("return to the game"), 'i');
568       dialog::display();
569       which = orbmap[getcstat];
570       }
571     else {
572       if(which == itNone) {
573         displaystr(vid.xres/2, vid.fsize*2, 2, vid.fsize*2, XLAT("Which orb to use?"), 0xC0C0C0, 8);
574         }
575       else {
576         int icol = iinf[which].color;
577         displaystr(vid.xres/2, vid.fsize*2, 2, vid.fsize*2, XLAT1(iinf[which].name), icol, 8);
578 
579         if(mirroring)
580           displaystr(vid.xres/2, vid.fsize*4, 2, vid.fsize, usedup[which] >= TESTMIRRORED ? XLAT("already mirrored") : XLAT("Uses to gain: %1", its(mirrorqty(which))), icol, 8);
581         else {
582           whichorbinfo = which;
583           compute();
584 
585           displaystr(vid.xres/2, vid.fsize*4, 2, vid.fsize, orbinfoline, icol, 8);
586 
587           if(extra != "")
588             displaystr(vid.xres/2, vid.fsize*5, 2, vid.fsize, XLAT("Extras:")+extra, icol, 8);
589           }
590 
591         if(remaining[which] != 1 || usedup[which]) {
592           displaystr(vid.xres/2, vid.yres - vid.fsize*6, 2, vid.fsize, osminfo(which), icol, 8);
593           }
594 
595 #if !ISMOBILE
596         string hot = XLAT1("Hotkey: "); hot += getcstat;
597         displaystr(vid.xres/2, vid.yres - vid.fsize*5, 2, vid.fsize, hot, icol, 8);
598 #endif
599 
600         eLand pl = getPrizeLand();
601         eOrbLandRelation olr = getOLR(which, pl);
602 
603         color_t col = 0;
604         const char *fmsg = NULL;
605         if(olr == olrDangerous)
606           col = 0xC00000,
607           fmsg = "Using %the1 in %the2 sounds dangerous...";
608         else if(olr == olrUseless)
609           col = 0xC00000,
610           fmsg = "%The1 is mostly useless in %the2...";
611         else if(olr == olrForbidden)
612           col = 0x804000,
613           fmsg = "%The1 is forbidden in %the2 (disables some achievements)";
614 
615         if(fmsg)
616           displaystr(vid.xres/2, vid.yres - vid.fsize*4, 2, vid.fsize, XLAT(fmsg, which, pl), col, 8);
617 
618         }
619       }
620     dialog::displayPageButtons(7, 0);
621     mouseovers = "";
622     keyhandler = [] (int sym, int uni) {
623       if(plain) dialog::handleNavigation(sym, uni);
624 
625       if(orbmap.count(uni)) {
626         eItem orb = orbmap[uni];
627         if(remaining[orb] <= 0) ;
628         else if(orb == itOrbMirror) {
629           mirroring = !mirroring;
630           // an amusing message
631           if(remaining[itOrbMirror] >= 2 && !mirroring)
632             addMessage(XLAT("You mirror %the1.", orb));
633           if(mirroring) {
634             bool next = false;
635             forCellEx(c2, cwt.at) if(c2->wall == waMirror || c2->wall == waCloud || c2->wall == waMirrorWall)
636               next = true;
637             if(!next) {
638               addMessage(XLAT("You need to stand next to a magic mirror or cloud to use %the1.", itOrbMirror));
639               mirroring = false;
640               }
641             }
642           }
643         else if(mirroring) {
644           if(usedup[orb] >= TESTMIRRORED) {
645             addMessage(XLAT("Each orb type can be mirrored only once."));
646             mirroring = false;
647             }
648           else if(remaining[orb] > 0) {
649             usedup[itOrbMirror]++;
650             usedup[orb] += MIRRORED;
651             usedup[orb] -= mirrorqty0(orb);
652             addMessage(XLAT("You mirror %the1.", orb));
653             mirroring = false;
654             }
655           else mirroring = false;
656           }
657         else if((isHaunted(cwt.at->land) || cwt.at->land == laDungeon) && orb == itOrbSafety) {
658           addMessage(XLAT("This would only move you deeper into the trap!"));
659           }
660         else {
661           eItem it = cwt.at->item;
662           cwt.at->item = orbmap[uni];
663           inv::activating = true;
664           collectItem(cwt.at, true);
665           inv::activating = false;
666           addMessage(XLAT("You activate %the1.", orbmap[uni]));
667           if(!cwt.at->item) usedup[orbmap[uni]]++;
668           if(getOLR(it, getPrizeLand()) == olrForbidden)
669             usedForbidden = true;
670           cwt.at->item = it;
671           evokeOrb(orbmap[uni]);
672           checkmove();
673           popScreenAll();
674           }
675         }
676 
677       else if(uni == '1') plain = !plain;
678       else if(sym == SDLK_F1)
679         gotoHelp(which ? generateHelpForItem(which) : XLAT(helptext));
680       else if(doexiton(sym, uni)) {
681         if(mirroring) mirroring = false;
682         popScreen();
683         }
684       };
685     }
686 
687 #if CAP_SAVE
applyBox(eItem it)688   EX void applyBox(eItem it) {
689     scores::applyBoxNum(inv::usedup[it], "@inv-" + dnameof(it));
690     }
691 #endif
692 
693   EX int incheck;
694 
check(int delta)695   EX void check(int delta) {
696     incheck += delta;
697     for(int i=0; i<ittypes; i++) {
698       eItem it = eItem(i);
699       if(itemclass(it) == IC_ORB)
700         items[it] += delta * remaining[it] * orbcharges(it);
701       }
702     }
703 
704 #endif
705 
706 #if !CAP_INV
707 EX always_false on, activating;
708 #endif
709 EX }
710 
711 }
712