hyperrogue/game.cpp

527 lines
14 KiB
C++
Raw Permalink Normal View History

// Hyperbolic Rogue - basic game routines
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
2015-08-08 13:57:52 +00:00
/** \file game.cpp
* \brief basic game routines
*/
2015-08-08 13:57:52 +00:00
#include "hyper.h"
namespace hr {
2020-03-27 20:47:09 +00:00
/** \brief the main random number generator for the game.
2019-08-10 17:30:21 +00:00
*
* 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;
2016-01-02 10:09:13 +00:00
2020-03-27 20:47:09 +00:00
/** \brief initialize \link hrngen \endlink */
2019-08-09 19:00:52 +00:00
EX void shrand(int i) {
hrngen.seed(i);
2016-01-02 10:09:13 +00:00
}
2020-03-27 20:47:09 +00:00
/** \brief generate a large number with \link hrngen \endlink */
2019-08-09 19:00:52 +00:00
EX int hrandpos() { return hrngen() & HRANDMAX; }
2016-08-26 09:58:03 +00:00
2020-03-27 20:47:09 +00:00
/** \brief A random integer from [0..i), generated from \link hrngen \endlink.
*
* We are using our own implementations rather than ones from <random>,
* to make sure that they return the same values on different compilers.
**/
2019-08-09 19:00:52 +00:00
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);
2016-01-02 10:09:13 +00:00
}
2019-08-09 23:56:00 +00:00
#if HDR
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))); }
template<class T> void hrandom_shuffle(T* x, int n) { for(int k=1; k<n; k++) swap(x[k], x[hrand(k+1)]); }
2021-07-12 03:54:25 +00:00
template<class T> void hrandom_shuffle(T& container) { hrandom_shuffle(container.data(), isize(container)); }
2020-01-18 15:03:32 +00:00
template<class U> auto hrand_elt(U& container) -> decltype(container[0]) { return container[hrand(isize(container))]; }
template<class T, class U> T hrand_elt(U& container, T default_value) {
if(container.empty()) return default_value;
return container[hrand(isize(container))];
}
2019-08-09 23:56:00 +00:00
#endif
2020-01-18 15:03:32 +00:00
EX vector<int> hrandom_permutation(int qty) {
vector<int> res(qty);
for(int i=0; i<qty; i++) res[i] = i;
hrandom_shuffle(res);
return res;
}
2019-08-10 17:30:21 +00:00
/** Use \link hrngen \endlink to generate a floating point number between 0 and 1.
*/
2019-08-09 19:00:52 +00:00
EX ld hrandf() {
return (hrngen() - hrngen.min()) / (hrngen.max() + 1.0 - hrngen.min());
2017-12-16 08:03:50 +00:00
}
2019-08-10 17:30:21 +00:00
/** Returns an integer corresponding to the current state of \link hrngen \endlink.
*/
2019-08-09 19:00:52 +00:00
EX int hrandstate() {
std::mt19937 r2 = hrngen;
return r2() & HRANDMAX;
}
EX int lastsafety;
2016-01-02 10:09:13 +00:00
EX bool usedSafety = false;
EX eLand safetyland;
EX int safetyseed;
2017-03-23 10:53:57 +00:00
EX bool childbug = false;
2017-03-23 10:53:57 +00:00
/** Is `w` killed if the part of an ivy `killed` is killed? */
EX bool isChild(cell *w, cell *killed) {
if(isAnyIvy(w->monst)) {
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;
2017-03-23 10:53:57 +00:00
}
EX eMonster active_switch() {
return eMonster(passive_switch ^ moSwitch1 ^ moSwitch2);
2017-03-23 10:53:57 +00:00
}
EX vector<cell*> crush_now, crush_next;
EX int getDistLimit() { return cgi.base_distlimit; }
2017-03-23 10:53:57 +00:00
EX void activateFlashFrom(cell *cf, eMonster who, flagtype flags);
2017-03-23 10:53:57 +00:00
EX bool saved_tortoise_on(cell *c) {
return
(c->monst == moTortoise && c->item == itBabyTortoise &&
!((tortoise::getb(c) ^ tortoise::babymap[c]) & tortoise::mask));
2016-01-02 10:09:13 +00:00
}
EX bool normal_gravity_at(cell *c) {
return !in_gravity_zone(c);
2015-08-08 13:57:52 +00:00
}
EX int countMyGolems(eMonster m) {
int g=0, dcs = isize(dcal);
for(int i=0; i<dcs; i++) {
cell *c = dcal[i];
if(c->monst == m) g++;
2016-08-26 09:58:03 +00:00
}
return g;
2016-01-02 10:09:13 +00:00
}
EX int savePrincesses() {
int g=0, dcs = isize(dcal);
for(int i=0; i<dcs; i++) {
cell *c = dcal[i];
if(isPrincess(c->monst)) princess::save(c);
2017-07-12 16:03:53 +00:00
}
return g;
}
EX int countMyGolemsHP(eMonster m) {
int g=0, dcs = isize(dcal);
for(int i=0; i<dcs; i++) {
cell *c = dcal[i];
if(c->monst == 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 && i<dcs; i++) {
cell *c = dcal[i];
if(m == moTameBomberbird ?
(c->mpdist >= 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;
2016-08-26 09:58:03 +00:00
}
2016-01-02 10:09:13 +00:00
killFriendlyIvy();
movecost(cwt.at, recallCell.at, 3);
playerMoveEffects(movei(cwt.at, recallCell.at, TELEPORT));
mirror::destroyAll();
sword::reset();
2015-08-08 13:57:52 +00:00
cwt = recallCell;
recallCell.at = NULL;
flipplayer = true;
2016-01-02 10:09:13 +00:00
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;
2015-08-08 13:57:52 +00:00
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;
2015-08-08 13:57:52 +00:00
}
EX void saveRecall(cellwalker cw2) {
if(!recallCell.at) {
2020-08-02 10:52:13 +00:00
changes.value_set(recallCell, cw2);
changes.value_keep(recallDisplay);
recallDisplay = *current_display;
2016-08-26 09:58:03 +00:00
}
2017-03-23 10:53:57 +00:00
}
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!"));
2016-08-26 09:58:03 +00:00
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!
2021-04-23 17:14:37 +00:00
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);
2021-06-01 08:10:13 +00:00
#if CAP_SAVE
2021-05-27 10:57:12 +00:00
if(casual) {
saveStats();
savecount++;
save_turns = turncount;
}
2021-06-01 08:10:13 +00:00
#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));
2016-08-26 09:58:03 +00:00
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));
2017-03-23 10:53:57 +00:00
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;
2021-05-02 13:16:29 +00:00
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; i<isize(multi::revive_queue); i++)
multi::revive_queue[i-1] = multi::revive_queue[i];
multi::revive_queue.pop_back();
multi::player[id].at = on;
multi::player[id].spin = neighborId(moveto, on);
if(multi::player[id].spin < 0) multi::player[id].spin = 0;
multi::flipped[id] = true;
multi::whereto[id].d = MD_UNDECIDED;
sword::reset();
return true;
}
return false;
2017-09-30 09:46:41 +00:00
}
2019-08-10 08:57:14 +00:00
EX eMonster passive_switch = moSwitch2;
2017-12-30 14:12:15 +00:00
2019-08-09 19:00:52 +00:00
EX void checkSwitch() {
2017-12-30 14:12:15 +00:00
passive_switch = (gold() & 1) ? moSwitch1 : moSwitch2;
}
EX void pushThumper(const movei& mi) {
auto &cto = mi.t;
auto &th = mi.s;
2016-08-26 09:58:03 +00:00
eWall w = th->wall;
if(th->land == laAlchemist)
th->wall = isAlch(cwt.at) ? cwt.at->wall : cto->wall;
2016-08-26 09:58:03 +00:00
else th->wall = waNone;
2018-12-16 23:04:59 +00:00
int explode = 0;
if(cto->wall == waArrowTrap && w == waExplosiveBarrel ) explode = max<int>(cto->wparam, 1);
if(cto->wall == waFireTrap) {
if(w == waExplosiveBarrel)
explode = max<int>(cto->wparam, 1);
if(w == waThumperOn)
explode = 2;
}
if(w == waExplosiveBarrel && cto->wall == waMineMine)
explode = 2;
2018-12-16 23:04:59 +00:00
destroyTrapsOn(cto);
2016-08-26 09:58:03 +00:00
if(cto->wall == waOpenPlate || cto->wall == waClosePlate) {
toggleGates(cto, cto->wall);
2018-12-16 23:04:59 +00:00
addMessage(XLAT("%The1 destroys %the2!", w, cto->wall));
2016-08-26 09:58:03 +00:00
}
if(cellUnstable(cto) && cto->land == laMotion) {
2018-12-16 23:04:59 +00:00
addMessage(XLAT("%The1 falls!", w));
2017-03-23 10:53:57 +00:00
doesFallSound(cto);
2016-08-26 09:58:03 +00:00
}
2017-08-18 01:39:55 +00:00
else if(cellUnstableOrChasm(cto)) {
2018-12-16 23:04:59 +00:00
addMessage(XLAT("%The1 fills the hole!", w));
cto->wall = w == waThumperOn ? waTempFloor : waNone;
2019-04-08 12:55:26 +00:00
cto->wparam = th->wparam;
playSound(cto, "click");
2016-08-26 09:58:03 +00:00
}
else if(isWatery(cto)) {
2018-12-16 23:04:59 +00:00
addMessage(XLAT("%The1 fills the hole!", w));
2020-02-29 03:07:17 +00:00
cto->wall = w == waThumperOn ? waTempBridge : waShallow;
2019-04-08 12:55:26 +00:00
cto->wparam = th->wparam;
playSound(cto, "splash"+pick12());
2016-08-26 09:58:03 +00:00
}
2020-02-29 03:07:17 +00:00
else if(cto->wall == waShallow) {
addMessage(XLAT("%The1 fills the hole!", w));
cto->wall = waNone;
playSound(cto, "splash"+pick12());
}
2021-03-30 09:29:18 +00:00
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;
}
2021-06-09 02:33:55 +00:00
#if CAP_COMPLEX2
2021-05-30 11:48:24 +00:00
else if(isDie(w)) {
2021-05-27 11:00:20 +00:00
th->wall = waNone;
cto->wall = w;
dice::roll(mi);
if(w == waRichDie && dice::data[cto].happy() > 0) {
2021-05-27 14:38:20 +00:00
cto->wall = waHappyDie;
2021-05-27 11:00:20 +00:00
if(cto->land == laDice && th->land == laDice) {
2022-06-23 11:46:34 +00:00
int q = items[itDice];
2021-06-03 13:11:32 +00:00
gainItem(itDice);
2022-06-23 11:46:34 +00:00
if(vid.bubbles_all || (threshold_met(items[itDice]) > threshold_met(q) && vid.bubbles_threshold)) {
drawBubble(cto, iinf[itDice].color, its(items[itDice]), 0.5);
}
2021-05-27 11:00:20 +00:00
addMessage(XLAT("The die is now happy, and you are rewarded!"));
}
else {
2021-05-29 18:07:59 +00:00
addMessage(XLAT("The die is now happy, but won't reward you outside of the Dice Reserve!"));
2021-05-27 11:00:20 +00:00
}
}
2021-05-29 13:45:37 +00:00
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!"));
2021-05-29 18:08:08 +00:00
animateMovement(mi, LAYER_SMALL);
2021-05-29 13:45:37 +00:00
}
2021-05-29 18:08:08 +00:00
else
animateMovement(mi, LAYER_BOAT);
2021-05-27 11:00:20 +00:00
}
2021-06-09 02:33:55 +00:00
#endif
2021-03-30 09:29:18 +00:00
else
2016-08-26 09:58:03 +00:00
cto->wall = w;
2018-12-16 23:04:59 +00:00
if(explode) cto->wall = waFireTrap, cto->wparam = explode;
if(cto->wall == waThumperOn)
cto->wparam = th->wparam;
2015-08-08 13:57:52 +00:00
}
EX bool canPushThumperOn(movei mi, cell *player) {
cell *thumper = mi.s;
cell *tgt = mi.t;
2021-06-09 02:33:55 +00:00
#if CAP_COMPLEX2
if(dice::on(thumper) && !dice::can_roll(mi))
2021-05-23 13:57:39 +00:00
return false;
2021-06-09 02:33:55 +00:00
#endif
2021-05-23 13:57:39 +00:00
if(tgt->wall == waBoat || tgt->wall == waStrandedBoat) return false;
2017-03-23 10:53:57 +00:00
if(isReptile(tgt->wall)) return false;
2016-08-26 09:58:03 +00:00
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) &&
2021-07-05 12:25:59 +00:00
(!tgt->item || dice::on(thumper));
2016-01-02 10:09:13 +00:00
}
2019-08-09 19:00:52 +00:00
EX void activateActiv(cell *c, bool msg) {
2016-08-26 09:58:03 +00:00
if(msg) addMessage(XLAT("You activate %the1.", c->wall));
2017-03-23 10:53:57 +00:00
if(c->wall == waThumperOff) {
playSound(c, "click");
c->wall = waThumperOn;
}
if(c->wall == waBonfireOff) {
playSound(c, "fire");
c->wall = waFire;
}
2016-08-26 09:58:03 +00:00
c->wparam = 100;
2015-08-08 13:57:52 +00:00
}
2016-08-26 09:58:03 +00:00
/* bool isPsiTarget(cell *dst) {
return
dst->cpdist > 1 &&
dst->monst &&
!(isWorm(dst) || dst->monst == moShadow);
2017-03-23 10:53:57 +00:00
} */
2015-08-08 13:57:52 +00:00
2019-08-09 19:00:52 +00:00
EX void fixWormBug(cell *c) {
if(history::includeHistory) return;
2017-03-23 10:53:57 +00:00
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);
2016-08-26 09:58:03 +00:00
}
EX cell *worm_tohead(cell *c) {
for(int i=0; i<c->type; 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) {
2017-03-23 10:53:57 +00:00
// 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++;
}
2017-03-23 10:53:57 +00:00
fixWormBug(c);
return c;
}
2017-03-23 10:53:57 +00:00
2020-03-27 20:47:09 +00:00
/** \brief currently works for worms only */
2019-08-09 19:00:52 +00:00
EX bool sameMonster(cell *c1, cell *c2) {
2018-02-27 22:43:21 +00:00
if(!c1 || !c2) return false;
2016-08-26 09:58:03 +00:00
if(c1 == c2) return true;
if(isWorm(c1->monst) && isWorm(c2->monst))
return wormhead(c1) == wormhead(c2);
2017-03-23 10:53:57 +00:00
if(isKraken(c1->monst) && isKraken(c2->monst))
return kraken::head(c1) == kraken::head(c2);
2016-08-26 09:58:03 +00:00
return false;
}
2019-08-09 19:00:52 +00:00
EX eMonster haveMount() {
2021-03-06 10:46:13 +00:00
for(cell *pc: player_positions()) {
eMonster m = pc->monst;
if(isWorm(m)) return m;
2017-03-23 10:53:57 +00:00
}
return moNone;
}
EX eMonster otherpole(eMonster m) {
return eMonster(m ^ moNorthPole ^ moSouthPole);
2015-08-08 13:57:52 +00:00
}
2016-01-02 10:09:13 +00:00
}