1 // Hyperbolic Rogue - basic game routines
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file game.cpp
5  *  \brief basic game routines
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 /** \brief the main random number generator for the game.
12  *
13  * All the random calls related to the game mechanics (land generation, AI...) should use hrngen.
14  *
15  * Random calls not related to the game mechanics (graphical effects) should not use hrngen.
16  *
17  * This ensures that the game should unfold exactly the same if given the same seed and the same input.
18  */
19 EX std::mt19937 hrngen;
20 
21 /** \brief initialize \link hrngen \endlink */
shrand(int i)22 EX void shrand(int i) {
23   hrngen.seed(i);
24   }
25 
26 /** \brief generate a large number with \link hrngen \endlink */
hrandpos()27 EX int hrandpos() { return hrngen() & HRANDMAX; }
28 
29 /** \brief A random integer from [0..i), generated from \link hrngen \endlink.
30  *
31  *  We are using our own implementations rather than ones from <random>,
32  *  to make sure that they return the same values on different compilers.
33  **/
34 
hrand(int i)35 EX int hrand(int i) {
36   unsigned d = hrngen() - hrngen.min();
37   long long m = (long long) (hrngen.max() - hrngen.min()) + 1;
38   m /= i;
39   d /= m;
40   if(d < (unsigned) i) return d;
41   return hrand(i);
42   }
43 
44 #if HDR
45 template<class T, class... U> T pick(T x, U... u) { std::initializer_list<T> i = {x,u...}; return *(i.begin() + hrand(1+sizeof...(u))); }
hrandom_shuffle(T * x,int n)46 template<class T> void hrandom_shuffle(T* x, int n) { for(int k=1; k<n; k++) swap(x[k], x[hrand(k+1)]); }
hrandom_shuffle(T & container)47 template<class T> void hrandom_shuffle(T& container) { hrandom_shuffle(container.data(), isize(container)); }
hrand_elt(U & container)48 template<class U> auto hrand_elt(U& container) -> decltype(container[0]) { return container[hrand(isize(container))]; }
49 template<class T, class U> T hrand_elt(U& container, T default_value) {
50   if(container.empty()) return default_value;
51   return container[hrand(isize(container))];
52   }
53 #endif
54 
hrandom_permutation(int qty)55 EX vector<int> hrandom_permutation(int qty) {
56   vector<int> res(qty);
57   for(int i=0; i<qty; i++) res[i] = i;
58   hrandom_shuffle(res);
59   return res;
60   }
61 
62 /** Use \link hrngen \endlink to generate a floating point number between 0 and 1.
63  */
64 
hrandf()65 EX ld hrandf() {
66   return (hrngen() - hrngen.min()) / (hrngen.max() + 1.0 - hrngen.min());
67   }
68 
69 /** Returns an integer corresponding to the current state of \link hrngen \endlink.
70  */
hrandstate()71 EX int hrandstate() {
72   std::mt19937 r2 = hrngen;
73   return r2() & HRANDMAX;
74   }
75 
76 EX int lastsafety;
77 
78 EX bool usedSafety = false;
79 EX eLand safetyland;
80 EX int safetyseed;
81 
82 EX bool childbug = false;
83 
84 /** Is `w` killed if the part of an ivy `killed` is killed? */
isChild(cell * w,cell * killed)85 EX bool isChild(cell *w, cell *killed) {
86   if(isAnyIvy(w->monst)) {
87     int lim = 0;
88     // printf("w = %p mondir = %d **\n", w, w->mondir);
89     while(w != killed && w->mondir != NODIR) {
90       lim++; if(lim == 100000) {
91         childbug = true;
92         printf("childbug!\n");
93         w->item = itBuggy; break;
94         }
95       if(!isAnyIvy(w->monst)) {
96         return false;
97         }
98       w = w->move(w->mondir);
99       // printf("w = %p mondir = %d\n", w, w->mondir);
100       }
101 
102     }
103   return w == killed;
104   }
105 
active_switch()106 EX eMonster active_switch() {
107   return eMonster(passive_switch ^ moSwitch1 ^ moSwitch2);
108   }
109 
110 EX vector<cell*> crush_now, crush_next;
111 
getDistLimit()112 EX int getDistLimit() { return cgi.base_distlimit; }
113 
114 EX void activateFlashFrom(cell *cf, eMonster who, flagtype flags);
115 
saved_tortoise_on(cell * c)116 EX bool saved_tortoise_on(cell *c) {
117   return
118     (c->monst == moTortoise && c->item == itBabyTortoise &&
119     !((tortoise::getb(c) ^ tortoise::babymap[c]) & tortoise::mask));
120   }
121 
normal_gravity_at(cell * c)122 EX bool normal_gravity_at(cell *c) {
123   return !in_gravity_zone(c);
124   }
125 
countMyGolems(eMonster m)126 EX int countMyGolems(eMonster m) {
127   int g=0, dcs = isize(dcal);
128   for(int i=0; i<dcs; i++) {
129     cell *c = dcal[i];
130     if(c->monst == m) g++;
131     }
132   return g;
133   }
134 
savePrincesses()135 EX int savePrincesses() {
136   int g=0, dcs = isize(dcal);
137   for(int i=0; i<dcs; i++) {
138     cell *c = dcal[i];
139     if(isPrincess(c->monst)) princess::save(c);
140     }
141   return g;
142   }
143 
countMyGolemsHP(eMonster m)144 EX int countMyGolemsHP(eMonster m) {
145   int g=0, dcs = isize(dcal);
146   for(int i=0; i<dcs; i++) {
147     cell *c = dcal[i];
148     if(c->monst == m) g += c->hitpoints;
149     }
150   return g;
151   }
152 
153 EX void restoreGolems(int qty, eMonster m, int hp IS(0)) {
154   int dcs = isize(dcal);
155   for(int i=1; qty && i<dcs; i++) {
156     cell *c = dcal[i];
157     if(m == moTameBomberbird ?
158         (c->mpdist >= 3 && passable(c, NULL, P_FLYING)) :
159         passable(c, NULL, 0)) {
160       c->hitpoints = hp / qty;
161       c->monst = m, qty--, hp -= c->hitpoints;
162       if(m == moPrincess || m == moPrincessArmed)
163         princess::newFakeInfo(c);
164       }
165     }
166   }
167 
168 EX cellwalker recallCell;
169 EX display_data recallDisplay;
170 
activateRecall()171 EX bool activateRecall() {
172   if(!recallCell.at) {
173     addMessage("Error: no recall");
174     return false;
175     }
176   items[itOrbRecall] = 0; items[itOrbSafety] = 0;
177   if(!makeEmpty(recallCell.at)) {
178     addMessage(XLAT("Your Orb of Recall is blocked by something big!"));
179     recallCell.at = NULL;
180     return false;
181     }
182 
183   killFriendlyIvy();
184   movecost(cwt.at, recallCell.at, 3);
185   playerMoveEffects(movei(cwt.at, recallCell.at, TELEPORT));
186   mirror::destroyAll();
187 
188   sword::reset();
189 
190   cwt = recallCell;
191   recallCell.at = NULL;
192   flipplayer = true;
193 
194   centerover = recallDisplay.precise_center;
195   View = recallDisplay.view_matrix;
196   // local_perspective = recallDisplay.local_perspective;
197   gmatrix = recallDisplay.cellmatrices;
198   gmatrix0 = recallDisplay.old_cellmatrices;
199   current_display->which_copy = recallDisplay.which_copy;
200 
201   makeEmpty(cwt.at);
202   forCellEx(c2, cwt.at)
203     if(c2->monst != moMutant)
204       c2->stuntime = 4;
205   if(shmup::on) shmup::recall();
206   if(multi::players > 1) multi::recall();
207   bfs();
208   checkmove();
209   drawSafety();
210   addMessage(XLAT("You are recalled!"));
211   return true;
212   }
213 
saveRecall(cellwalker cw2)214 EX void saveRecall(cellwalker cw2) {
215   if(!recallCell.at) {
216     changes.value_set(recallCell, cw2);
217     changes.value_keep(recallDisplay);
218     recallDisplay = *current_display;
219     }
220   }
221 
teleportToLand(eLand l,bool make_it_safe)222 EX void teleportToLand(eLand l, bool make_it_safe) {
223   if(recallCell.at && activateRecall())
224     return;
225   savePrincesses();
226   int gg = countMyGolems(moGolem);
227   int gb = countMyGolems(moTameBomberbird);
228   int gp1 = countMyGolems(moPrincess);
229   int gp2 = countMyGolems(moPrincessArmed);
230   int gph1 = countMyGolemsHP(moPrincess);
231   int gph2 = countMyGolemsHP(moPrincessArmed);
232   drawSafety();
233   addMessage(XLAT("You fall into a wormhole!"));
234 
235   eLand f = firstland;
236   if(l == laTemple) l = laRlyeh;
237   if(l == laClearing) l = laOvergrown;
238   if(l == laWhirlpool) l = laOcean;
239   if(l == laCrossroads5) l = laCrossroads2; // could not fit!
240   if(l == laCamelot && !ls::single())
241     l = laCrossroads;
242   firstland = l;
243   safetyland = l;
244   safetyseed = hrandpos();
245   clear_euland(firstland);
246   safety = make_it_safe; avengers = 0;
247   clearMemory();
248   initcells();
249   initgame();
250   firstland = f;
251   safety = false;
252   restoreGolems(gg, moGolem);
253   restoreGolems(gb, moTameBomberbird);
254   restoreGolems(gp1, moPrincess, gph1);
255   restoreGolems(gp2, moPrincessArmed, gph2);
256   restartGraph();
257   }
258 
259 
activateSafety(eLand l)260 EX void activateSafety(eLand l) {
261   teleportToLand(l, true);
262   #if CAP_SAVE
263   if(casual) {
264     saveStats();
265     savecount++;
266     save_turns = turncount;
267     }
268   #endif
269   }
270 
placeGolem(cell * on,cell * moveto,eMonster m)271 EX void placeGolem(cell *on, cell *moveto, eMonster m) {
272   if(on->monst == moFriendlyIvy)
273     killMonster(on, moPlayer);
274   if(on->monst) {
275     addMessage(XLAT("There is no room for %the1!", m));
276     return;
277     }
278   if(passable(on, moveto, P_ISFRIEND | (m == moTameBomberbird ? P_FLYING : 0)))
279     on->monst = m;
280   else {
281     on->monst = m;
282     flagtype f = AF_CRUSH;
283     if(isFire(on))
284       addMessage(XLAT("%The1 burns!", m));
285     else if(on->wall == waChasm)
286       addMessage(XLAT("%The1 falls!", m)), f = AF_FALL;
287     else if(isWatery(on) && isNonliving(m))
288       addMessage(XLAT("%The1 sinks!", m)), f = AF_FALL;
289     else if(isWatery(on))
290       addMessage(XLAT("%The1 drowns!", m)), f = AF_FALL;
291     else if(isWall(on))
292       addMessage(XLAT("%The1 is crushed!", m));
293     else if(m == moTameBomberbird && cwt.at->wall == waBoat)
294       return;
295     else
296       addMessage(XLAT("%The1 is destroyed!", m));
297 
298     printf("mondir = %d\n", on->mondir);
299     fallMonster(cwt.at, f);
300     }
301   }
302 
multiRevival(cell * on,cell * moveto)303 EX bool multiRevival(cell *on, cell *moveto) {
304   int fl = 0;
305   if(items[itOrbAether]) fl |= P_AETHER;
306   if(items[itCurseWater]) fl |= P_WATERCURSE;
307   if(items[itOrbFish]) fl |= P_FISH;
308   if(items[itOrbWinter]) fl |= P_WINTER;
309   if(passable(on, moveto, fl)) {
310     int id = multi::revive_queue[0];
311     for(int i=1; i<isize(multi::revive_queue); i++)
312       multi::revive_queue[i-1] = multi::revive_queue[i];
313     multi::revive_queue.pop_back();
314     multi::player[id].at = on;
315     multi::player[id].spin = neighborId(moveto, on);
316     if(multi::player[id].spin < 0) multi::player[id].spin = 0;
317     multi::flipped[id] = true;
318     multi::whereto[id].d = MD_UNDECIDED;
319     sword::reset();
320     return true;
321     }
322   return false;
323   }
324 
325 EX eMonster passive_switch = moSwitch2;
326 
checkSwitch()327 EX void checkSwitch() {
328   passive_switch = (gold() & 1) ? moSwitch1 : moSwitch2;
329   }
330 
pushThumper(const movei & mi)331 EX void pushThumper(const movei& mi) {
332   auto &cto = mi.t;
333   auto &th = mi.s;
334   eWall w = th->wall;
335   if(th->land == laAlchemist)
336     th->wall = isAlch(cwt.at) ? cwt.at->wall : cto->wall;
337   else th->wall = waNone;
338   int explode = 0;
339   if(cto->wall == waArrowTrap && w == waExplosiveBarrel ) explode = max<int>(cto->wparam, 1);
340   if(cto->wall == waFireTrap) {
341     if(w == waExplosiveBarrel)
342       explode = max<int>(cto->wparam, 1);
343     if(w == waThumperOn)
344       explode = 2;
345     }
346   destroyTrapsOn(cto);
347   if(cto->wall == waOpenPlate || cto->wall == waClosePlate) {
348     toggleGates(cto, cto->wall);
349     addMessage(XLAT("%The1 destroys %the2!", w, cto->wall));
350     }
351   if(cellUnstable(cto) && cto->land == laMotion) {
352     addMessage(XLAT("%The1 falls!", w));
353     doesFallSound(cto);
354     }
355   else if(cellUnstableOrChasm(cto)) {
356     addMessage(XLAT("%The1 fills the hole!", w));
357     cto->wall = w == waThumperOn ? waTempFloor : waNone;
358     cto->wparam = th->wparam;
359     playSound(cto, "click");
360     }
361   else if(isWatery(cto)) {
362     addMessage(XLAT("%The1 fills the hole!", w));
363     cto->wall = w == waThumperOn ? waTempBridge : waShallow;
364     cto->wparam = th->wparam;
365     playSound(cto, "splash"+pick12());
366     }
367   else if(cto->wall == waShallow) {
368     addMessage(XLAT("%The1 fills the hole!", w));
369     cto->wall = waNone;
370     playSound(cto, "splash"+pick12());
371     }
372   else if(w == waCrateCrate && cto->wall == waCrateTarget) {
373     cto->wall = waCrateOnTarget;
374     th->wall = waNone;
375     }
376   else if(w == waCrateOnTarget && cto->wall == waNone) {
377     cto->wall = waCrateCrate;
378     th->wall = waCrateTarget;
379     }
380   else if(w == waCrateOnTarget && cto->wall == waCrateTarget) {
381     cto->wall = waCrateOnTarget;
382     th->wall = waCrateTarget;
383     }
384   #if CAP_COMPLEX2
385   else if(isDie(w)) {
386     th->wall = waNone;
387     cto->wall = w;
388     dice::roll(mi);
389     if(w == waRichDie && dice::data[cto].happy() > 0) {
390       cto->wall = waHappyDie;
391       if(cto->land == laDice && th->land == laDice) {
392         gainItem(itDice);
393         addMessage(XLAT("The die is now happy, and you are rewarded!"));
394         }
395       else {
396         addMessage(XLAT("The die is now happy, but won't reward you outside of the Dice Reserve!"));
397         }
398       }
399     if(w == waHappyDie && dice::data[cto].happy() <= 0) {
400       cto->monst = moAngryDie;
401       cto->wall = waNone;
402       cto->stuntime = 5;
403       addMessage(XLAT("You have made a Happy Die angry!"));
404       animateMovement(mi, LAYER_SMALL);
405       }
406     else
407       animateMovement(mi, LAYER_BOAT);
408     }
409   #endif
410   else
411     cto->wall = w;
412   if(explode) cto->wall = waFireTrap, cto->wparam = explode;
413   if(cto->wall == waThumperOn)
414     cto->wparam = th->wparam;
415   }
416 
canPushThumperOn(movei mi,cell * player)417 EX bool canPushThumperOn(movei mi, cell *player) {
418   cell *thumper = mi.s;
419   cell *tgt = mi.t;
420   #if CAP_COMPLEX2
421   if(dice::on(thumper) && !dice::can_roll(mi))
422     return false;
423   #endif
424   if(tgt->wall == waBoat || tgt->wall == waStrandedBoat) return false;
425   if(isReptile(tgt->wall)) return false;
426   if(isWatery(tgt) && !tgt->monst)
427     return true;
428   if(tgt->wall == waChasm && !tgt->monst)
429     return true;
430   return
431     passable(tgt, thumper, P_MIRROR) &&
432     passable(tgt, player, P_MIRROR) &&
433     (!tgt->item || dice::on(thumper));
434   }
435 
activateActiv(cell * c,bool msg)436 EX void activateActiv(cell *c, bool msg) {
437   if(msg) addMessage(XLAT("You activate %the1.", c->wall));
438   if(c->wall == waThumperOff) {
439     playSound(c, "click");
440     c->wall = waThumperOn;
441     }
442   if(c->wall == waBonfireOff) {
443     playSound(c, "fire");
444     c->wall = waFire;
445     }
446   c->wparam = 100;
447   }
448 
449 /* bool isPsiTarget(cell *dst) {
450   return
451     dst->cpdist > 1 &&
452     dst->monst &&
453     !(isWorm(dst) || dst->monst == moShadow);
454   } */
455 
fixWormBug(cell * c)456 EX void fixWormBug(cell *c) {
457   if(history::includeHistory) return;
458   printf("worm bug!\n");
459   if(c->monst == moWormtail) c->monst = moWormwait;
460   if(c->monst == moTentacletail || c->monst == moTentacleGhost) c->monst = moTentacle;
461   if(c->monst == moHexSnakeTail) c->monst = moHexSnake;
462   }
463 
isWormhead(eMonster m)464 EX bool isWormhead(eMonster m) {
465   return among(m,
466     moTentacle, moWorm, moHexSnake, moWormwait, moTentacleEscaping,
467     moTentaclewait, moDragonHead);
468   }
469 
worm_tohead(cell * c)470 EX cell *worm_tohead(cell *c) {
471  for(int i=0; i<c->type; i++)
472    if(c->move(i) && isWorm(c->move(i)->monst) && c->move(i)->mondir == c->c.spin(i))
473      return c->move(i);
474   return nullptr;
475   }
476 
wormhead(cell * c)477 EX cell *wormhead(cell *c) {
478   // cell *cor = c;
479   int cnt = 0;
480   while(cnt < iteration_limit) {
481     if(isWormhead(c->monst))
482       return c;
483     cell *c1 = worm_tohead(c);
484     if(!c1) break;
485     c = c1;
486     cnt++;
487     }
488   fixWormBug(c);
489   return c;
490   }
491 
492 /** \brief currently works for worms only */
sameMonster(cell * c1,cell * c2)493 EX bool sameMonster(cell *c1, cell *c2) {
494   if(!c1 || !c2) return false;
495   if(c1 == c2) return true;
496   if(isWorm(c1->monst) && isWorm(c2->monst))
497     return wormhead(c1) == wormhead(c2);
498   if(isKraken(c1->monst) && isKraken(c2->monst))
499     return kraken::head(c1) == kraken::head(c2);
500   return false;
501   }
502 
haveMount()503 EX eMonster haveMount() {
504   for(cell *pc: player_positions()) {
505     eMonster m = pc->monst;
506     if(isWorm(m)) return m;
507     }
508   return moNone;
509   }
510 
otherpole(eMonster m)511 EX eMonster otherpole(eMonster m) {
512   return eMonster(m ^ moNorthPole ^ moSouthPole);
513   }
514 
515 
516 }
517