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