// Hyperbolic Rogue - basic game routines // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file game.cpp * \brief basic game routines */ #include "hyper.h" namespace hr { /** \brief the main random number generator for the game. * * All the random calls related to the game mechanics (land generation, AI...) should use hrngen. * * Random calls not related to the game mechanics (graphical effects) should not use hrngen. * * This ensures that the game should unfold exactly the same if given the same seed and the same input. */ EX std::mt19937 hrngen; /** \brief initialize \link hrngen \endlink */ EX void shrand(int i) { hrngen.seed(i); } /** \brief generate a large number with \link hrngen \endlink */ EX int hrandpos() { return hrngen() & HRANDMAX; } /** \brief A random integer from [0..i), generated from \link hrngen \endlink. * * We are using our own implementations rather than ones from , * to make sure that they return the same values on different compilers. **/ EX int hrand(int i) { unsigned d = hrngen() - hrngen.min(); long long m = (long long) (hrngen.max() - hrngen.min()) + 1; m /= i; d /= m; if(d < (unsigned) i) return d; return hrand(i); } #if HDR template T pick(T x, U... u) { std::initializer_list i = {x,u...}; return *(i.begin() + hrand(1+sizeof...(u))); } template void hrandom_shuffle(T* x, int n) { for(int k=1; k void hrandom_shuffle(T& container) { hrandom_shuffle(container.data(), isize(container)); } template auto hrand_elt(U& container) -> decltype(container[0]) { return container[hrand(isize(container))]; } template T hrand_elt(U& container, T default_value) { if(container.empty()) return default_value; return container[hrand(isize(container))]; } #endif EX vector hrandom_permutation(int qty) { vector res(qty); for(int i=0; imonst)) { int lim = 0; // printf("w = %p mondir = %d **\n", w, w->mondir); while(w != killed && w->mondir != NODIR) { lim++; if(lim == 100000) { childbug = true; printf("childbug!\n"); w->item = itBuggy; break; } if(!isAnyIvy(w->monst)) { return false; } w = w->move(w->mondir); // printf("w = %p mondir = %d\n", w, w->mondir); } } return w == killed; } EX eMonster active_switch() { return eMonster(passive_switch ^ moSwitch1 ^ moSwitch2); } EX vector crush_now, crush_next; EX int getDistLimit() { return cgi.base_distlimit; } EX void activateFlashFrom(cell *cf, eMonster who, flagtype flags); EX bool saved_tortoise_on(cell *c) { return (c->monst == moTortoise && c->item == itBabyTortoise && !((tortoise::getb(c) ^ tortoise::babymap[c]) & tortoise::mask)); } EX bool normal_gravity_at(cell *c) { return !in_gravity_zone(c); } EX int countMyGolems(eMonster m) { int g=0, dcs = isize(dcal); for(int i=0; imonst == m) g++; } return g; } EX int savePrincesses() { int g=0, dcs = isize(dcal); for(int i=0; imonst)) princess::save(c); } return g; } EX int countMyGolemsHP(eMonster m) { int g=0, dcs = isize(dcal); for(int i=0; imonst == m) g += c->hitpoints; } return g; } EX void restoreGolems(int qty, eMonster m, int hp IS(0)) { int dcs = isize(dcal); for(int i=1; qty && impdist >= 3 && passable(c, NULL, P_FLYING)) : passable(c, NULL, 0)) { c->hitpoints = hp / qty; c->monst = m, qty--, hp -= c->hitpoints; if(m == moPrincess || m == moPrincessArmed) princess::newFakeInfo(c); } } } EX cellwalker recallCell; EX display_data recallDisplay; EX bool activateRecall() { if(!recallCell.at) { addMessage("Error: no recall"); return false; } items[itOrbRecall] = 0; items[itOrbSafety] = 0; if(!makeEmpty(recallCell.at)) { addMessage(XLAT("Your Orb of Recall is blocked by something big!")); recallCell.at = NULL; return false; } killFriendlyIvy(); movecost(cwt.at, recallCell.at, 3); playerMoveEffects(movei(cwt.at, recallCell.at, TELEPORT)); mirror::destroyAll(); sword::reset(); cwt = recallCell; recallCell.at = NULL; flipplayer = true; centerover = recallDisplay.precise_center; View = recallDisplay.view_matrix; // local_perspective = recallDisplay.local_perspective; gmatrix = recallDisplay.cellmatrices; gmatrix0 = recallDisplay.old_cellmatrices; current_display->which_copy = recallDisplay.which_copy; makeEmpty(cwt.at); forCellEx(c2, cwt.at) if(c2->monst != moMutant) c2->stuntime = 4; if(shmup::on) shmup::recall(); if(multi::players > 1) multi::recall(); bfs(); checkmove(); drawSafety(); addMessage(XLAT("You are recalled!")); return true; } EX void saveRecall(cellwalker cw2) { if(!recallCell.at) { changes.value_set(recallCell, cw2); changes.value_keep(recallDisplay); recallDisplay = *current_display; } } EX void teleportToLand(eLand l, bool make_it_safe) { if(recallCell.at && activateRecall()) return; savePrincesses(); int gg = countMyGolems(moGolem); int gb = countMyGolems(moTameBomberbird); int gp1 = countMyGolems(moPrincess); int gp2 = countMyGolems(moPrincessArmed); int gph1 = countMyGolemsHP(moPrincess); int gph2 = countMyGolemsHP(moPrincessArmed); drawSafety(); addMessage(XLAT("You fall into a wormhole!")); eLand f = firstland; if(l == laTemple) l = laRlyeh; if(l == laClearing) l = laOvergrown; if(l == laWhirlpool) l = laOcean; if(l == laCrossroads5) l = laCrossroads2; // could not fit! if(l == laCamelot && !ls::single()) l = laCrossroads; firstland = l; safetyland = l; safetyseed = hrandpos(); clear_euland(firstland); safety = make_it_safe; avengers = 0; clearMemory(); initcells(); initgame(); firstland = f; safety = false; restoreGolems(gg, moGolem); restoreGolems(gb, moTameBomberbird); restoreGolems(gp1, moPrincess, gph1); restoreGolems(gp2, moPrincessArmed, gph2); restartGraph(); } EX void activateSafety(eLand l) { teleportToLand(l, true); #if CAP_SAVE if(casual) { saveStats(); savecount++; save_turns = turncount; } #endif if(items[itOrbChoice]) { items[itOrbChoice] = 0; cwt.at->item = itOrbSafety; } } EX void placeGolem(cell *on, cell *moveto, eMonster m) { if(on->monst == moFriendlyIvy) killMonster(on, moPlayer); if(on->monst) { addMessage(XLAT("There is no room for %the1!", m)); return; } if(passable(on, moveto, P_ISFRIEND | (m == moTameBomberbird ? P_FLYING : 0))) on->monst = m; else { on->monst = m; flagtype f = AF_CRUSH; if(isFire(on)) addMessage(XLAT("%The1 burns!", m)); else if(on->wall == waChasm) addMessage(XLAT("%The1 falls!", m)), f = AF_FALL; else if(isWatery(on) && isNonliving(m)) addMessage(XLAT("%The1 sinks!", m)), f = AF_FALL; else if(isWatery(on)) addMessage(XLAT("%The1 drowns!", m)), f = AF_FALL; else if(isWall(on)) addMessage(XLAT("%The1 is crushed!", m)); else if(m == moTameBomberbird && cwt.at->wall == waBoat) return; else addMessage(XLAT("%The1 is destroyed!", m)); printf("mondir = %d\n", on->mondir); fallMonster(cwt.at, f); } } EX bool multiRevival(cell *on, cell *moveto) { int fl = 0; if(items[itOrbAether]) fl |= P_AETHER; if(items[itCurseWater]) fl |= P_WATERCURSE; if(items[itOrbFish]) fl |= P_FISH; if(items[itOrbWinter]) fl |= P_WINTER; if(passable(on, moveto, fl)) { int id = multi::revive_queue[0]; for(int i=1; iwall; if(th->land == laAlchemist) th->wall = isAlch(cwt.at) ? cwt.at->wall : cto->wall; else th->wall = waNone; int explode = 0; if(cto->wall == waArrowTrap && w == waExplosiveBarrel ) explode = max(cto->wparam, 1); if(cto->wall == waFireTrap) { if(w == waExplosiveBarrel) explode = max(cto->wparam, 1); if(w == waThumperOn) explode = 2; } if(w == waExplosiveBarrel && cto->wall == waMineMine) explode = 2; destroyTrapsOn(cto); if(cto->wall == waOpenPlate || cto->wall == waClosePlate) { toggleGates(cto, cto->wall); addMessage(XLAT("%The1 destroys %the2!", w, cto->wall)); } if(cellUnstable(cto) && cto->land == laMotion) { addMessage(XLAT("%The1 falls!", w)); doesFallSound(cto); } else if(cellUnstableOrChasm(cto)) { addMessage(XLAT("%The1 fills the hole!", w)); cto->wall = w == waThumperOn ? waTempFloor : waNone; cto->wparam = th->wparam; playSound(cto, "click"); } else if(isWatery(cto)) { addMessage(XLAT("%The1 fills the hole!", w)); cto->wall = w == waThumperOn ? waTempBridge : waShallow; cto->wparam = th->wparam; playSound(cto, "splash"+pick12()); } else if(cto->wall == waShallow) { addMessage(XLAT("%The1 fills the hole!", w)); cto->wall = waNone; playSound(cto, "splash"+pick12()); } else if(w == waCrateCrate && cto->wall == waCrateTarget) { cto->wall = waCrateOnTarget; th->wall = waNone; } else if(w == waCrateOnTarget && cto->wall == waNone) { cto->wall = waCrateCrate; th->wall = waCrateTarget; } else if(w == waCrateOnTarget && cto->wall == waCrateTarget) { cto->wall = waCrateOnTarget; th->wall = waCrateTarget; } #if CAP_COMPLEX2 else if(isDie(w)) { th->wall = waNone; cto->wall = w; dice::roll(mi); if(w == waRichDie && dice::data[cto].happy() > 0) { cto->wall = waHappyDie; if(cto->land == laDice && th->land == laDice) { int q = items[itDice]; gainItem(itDice); if(vid.bubbles_all || (threshold_met(items[itDice]) > threshold_met(q) && vid.bubbles_threshold)) { drawBubble(cto, iinf[itDice].color, its(items[itDice]), 0.5); } addMessage(XLAT("The die is now happy, and you are rewarded!")); } else { addMessage(XLAT("The die is now happy, but won't reward you outside of the Dice Reserve!")); } } if(w == waHappyDie && dice::data[cto].happy() <= 0) { cto->monst = moAngryDie; cto->wall = waNone; cto->stuntime = 5; addMessage(XLAT("You have made a Happy Die angry!")); animateMovement(mi, LAYER_SMALL); } else animateMovement(mi, LAYER_BOAT); } #endif else cto->wall = w; if(explode) cto->wall = waFireTrap, cto->wparam = explode; if(cto->wall == waThumperOn) cto->wparam = th->wparam; } EX bool canPushThumperOn(movei mi, cell *player) { cell *thumper = mi.s; cell *tgt = mi.t; #if CAP_COMPLEX2 if(dice::on(thumper) && !dice::can_roll(mi)) return false; #endif if(tgt->wall == waBoat || tgt->wall == waStrandedBoat) return false; if(isReptile(tgt->wall)) return false; if(isWatery(tgt) && !tgt->monst) return true; if(tgt->wall == waChasm && !tgt->monst) return true; return passable(tgt, thumper, P_MIRROR) && passable(tgt, player, P_MIRROR) && (!tgt->item || dice::on(thumper)); } EX void activateActiv(cell *c, bool msg) { if(msg) addMessage(XLAT("You activate %the1.", c->wall)); if(c->wall == waThumperOff) { playSound(c, "click"); c->wall = waThumperOn; } if(c->wall == waBonfireOff) { playSound(c, "fire"); c->wall = waFire; } c->wparam = 100; } /* bool isPsiTarget(cell *dst) { return dst->cpdist > 1 && dst->monst && !(isWorm(dst) || dst->monst == moShadow); } */ EX void fixWormBug(cell *c) { if(history::includeHistory) return; printf("worm bug!\n"); if(c->monst == moWormtail) c->monst = moWormwait; if(c->monst == moTentacletail || c->monst == moTentacleGhost) c->monst = moTentacle; if(c->monst == moHexSnakeTail) c->monst = moHexSnake; } EX bool isWormhead(eMonster m) { return among(m, moTentacle, moWorm, moHexSnake, moWormwait, moTentacleEscaping, moTentaclewait, moDragonHead); } EX cell *worm_tohead(cell *c) { for(int i=0; itype; i++) if(c->move(i) && isWorm(c->move(i)->monst) && c->move(i)->mondir == c->c.spin(i)) return c->move(i); return nullptr; } EX cell *wormhead(cell *c) { // cell *cor = c; int cnt = 0; while(cnt < iteration_limit) { if(isWormhead(c->monst)) return c; cell *c1 = worm_tohead(c); if(!c1) break; c = c1; cnt++; } fixWormBug(c); return c; } /** \brief currently works for worms only */ EX bool sameMonster(cell *c1, cell *c2) { if(!c1 || !c2) return false; if(c1 == c2) return true; if(isWorm(c1->monst) && isWorm(c2->monst)) return wormhead(c1) == wormhead(c2); if(isKraken(c1->monst) && isKraken(c2->monst)) return kraken::head(c1) == kraken::head(c2); return false; } EX eMonster haveMount() { for(cell *pc: player_positions()) { eMonster m = pc->monst; if(isWorm(m)) return m; } return moNone; } EX eMonster otherpole(eMonster m) { return eMonster(m ^ moNorthPole ^ moSouthPole); } }