subdivided game.cpp; split movepcto into separate functions

This commit is contained in:
Zeno Rogue 2019-12-08 19:17:28 +01:00
parent 2fb1210811
commit 26fb19e7a8
29 changed files with 8689 additions and 8479 deletions

View File

@ -159,7 +159,7 @@ makeh$(EXE_EXTENSION): makeh.cpp
$(CXX) makeh.cpp -o $@
autohdr.h: makeh$(EXE_EXTENSION) *.cpp
./makeh locations.cpp hyperpoint.cpp geometry.cpp goldberg.cpp init.cpp floorshapes.cpp cell.cpp multi.cpp shmup.cpp pattern2.cpp mapeditor.cpp graph.cpp textures.cpp hprint.cpp language.cpp *.cpp > autohdr.h
./makeh locations.cpp hyperpoint.cpp geometry.cpp goldberg.cpp init.cpp floorshapes.cpp cell.cpp multi.cpp shmup.cpp pattern2.cpp mapeditor.cpp graph.cpp textures.cpp hprint.cpp language.cpp complex.cpp *.cpp > autohdr.h
language-data.cpp: langen$(EXE_EXTENSION)
./langen > language-data.cpp

1198
attack.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -79,7 +79,7 @@ int utfsize(char c) {
}
EX int get_sightrange() { return getDistLimit() + sightrange_bonus; }
EX int get_sightrange_ambush() { return max(get_sightrange(), ambush_distance); }
EX int get_sightrange_ambush() { return max(get_sightrange(), ambush::distance); }
bool display_data::in_anaglyph() { return vid.stereo_mode == sAnaglyph; }
bool display_data::stereo_active() { return vid.stereo_mode != sOFF; }

View File

@ -23,7 +23,7 @@ EX int newRoundTableRadius() {
EX int getAnthraxData(cell *c, bool b) {
int d = celldistAlt(c);
int rad = 28 + 3 * anthraxBonus;
int rad = 28 + 3 * camelot::anthraxBonus;
while(d < -rad) {
d += rad + 12;
rad += 3;

116
cell.cpp
View File

@ -1120,4 +1120,120 @@ EX void clearCellMemory() {
auto cellhooks = addHook(clearmemory, 500, clearCellMemory);
EX bool isNeighbor(cell *c1, cell *c2) {
for(int i=0; i<c1->type; i++) if(c1->move(i) == c2) return true;
return false;
}
EX bool isNeighborCM(cell *c1, cell *c2) {
for(int i=0; i<c1->type; i++) if(createMov(c1, i) == c2) return true;
return false;
}
EX int neighborId(cell *ofWhat, cell *whichOne) {
for(int i=0; i<ofWhat->type; i++) if(ofWhat->move(i) == whichOne) return i;
return -1;
}
EX int mine_adjacency_rule = 0;
EX map<cell*, vector<cell*>> adj_memo;
EX bool geometry_has_alt_mine_rule() {
if(WDIM == 2) return VALENCE > 3;
if(WDIM == 3) return !among(geometry, gHoroHex, gCell5, gBitrunc3, gCell8, gECell8, gCell120, gECell120);
return true;
}
EX vector<cell*> adj_minefield_cells(cell *c) {
vector<cell*> res;
if(mine_adjacency_rule == 0 || !geometry_has_alt_mine_rule())
forCellCM(c2, c) res.push_back(c2);
else if(WDIM == 2) {
cellwalker cw(c, 0);
cw += wstep;
cw++;
cellwalker cw1 = cw;
do {
res.push_back(cw.at);
cw += wstep;
cw++;
if(cw.cpeek() == c) cw++;
}
while(cw != cw1);
}
else if(adj_memo.count(c)) return adj_memo[c];
else {
const vector<hyperpoint> vertices = currentmap->get_vertices(c);
manual_celllister cl;
cl.add(c);
for(int i=0; i<isize(cl.lst); i++) {
cell *c1 = cl.lst[i];
bool shares = false;
if(c != c1) {
transmatrix T = currentmap->relative_matrix(c1->master, c->master, C0);
for(hyperpoint h: vertices) for(hyperpoint h2: vertices)
if(hdist(h, T * h2) < 1e-6) shares = true;
if(shares) res.push_back(c1);
}
if(shares || c == c1) forCellEx(c2, c1) cl.add(c2);
}
println(hlog, "adjacent to ", c, " = ", isize(res));
adj_memo[c] = res;
}
return res;
}
EX vector<int> reverse_directions(cell *c, int dir) {
if(PURE) return reverse_directions(c->master, dir);
int d = c->degree();
if(d & 1)
return { gmod(dir + c->type/2, c->type), gmod(dir + (c->type+1)/2, c->type) };
else
return { gmod(dir + c->type/2, c->type) };
}
EX vector<int> reverse_directions(heptagon *c, int dir) {
int d = c->degree();
switch(geometry) {
case gBinary3:
if(dir < 4) return {8};
else if(dir >= 8) return {0, 1, 2, 3};
else return {dir ^ 1};
case gHoroTris:
if(dir < 4) return {7};
else if(dir == 4) return {5, 6};
else if(dir == 5) return {6, 4};
else if(dir == 6) return {4, 5};
else return {0, 1, 2, 3};
case gHoroRec:
if(dir < 2) return {6};
else if(dir == 6) return {0, 1};
else return {dir^1};
case gKiteDart3: {
if(dir < 4) return {dir ^ 2};
if(dir >= 6) return {4, 5};
vector<int> res;
for(int i=6; i<c->type; i++) res.push_back(i);
return res;
}
case gHoroHex: {
if(dir < 6) return {12, 13};
if(dir >= 12) return {0, 1, 2, 3, 4, 5};
const int dt[] = {0,0,0,0,0,0,10,11,9,8,6,7,0,0};
return {dt[dir]};
}
default:
if(d & 1)
return { gmod(dir + c->type/2, c->type), gmod(dir + (c->type+1)/2, c->type) };
else
return { gmod(dir + c->type/2, c->type) };
}
}
}

View File

@ -143,7 +143,7 @@ void celldrawer::setcolors() {
fcol = 0x404040;
for(int a=0; a<21; a++)
if((b >> a) & 1)
fcol += variant_features[a].color_change;
fcol += variant::features[a].color_change;
if(c->wall == waAncientGrave)
wcol = 0x080808;
else if(c->wall == waFreshGrave)
@ -506,9 +506,9 @@ void celldrawer::setcolors() {
break;
case waMineUnknown: case waMineMine:
if(mineMarkedSafe(c))
if(mine::marked_safe(c))
fcol = wcol = gradient(wcol, 0x40FF40, 0, 0.2, 1);
else if(mineMarked(c))
else if(mine::marked_mine(c))
fcol = wcol = gradient(wcol, 0xFF4040, -1, sintick(100), 1);
// fallthrough
@ -1255,7 +1255,7 @@ void celldrawer::draw_features() {
}
case waTerraWarrior:
drawTerraWarrior(V, randterra ? (c->landparam & 7) : (5 - (c->landparam & 7)), 7, 0);
drawTerraWarrior(V, terracotta::randterra ? (c->landparam & 7) : (5 - (c->landparam & 7)), 7, 0);
break;
case waBoat: case waStrandedBoat:

451
checkmove.cpp Normal file
View File

@ -0,0 +1,451 @@
// Hyperbolic Rogue - checkmate rule
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file checkmove.cpp
* \brief Check the validity of move (checkmate rule)
*/
#include "hyper.h"
namespace hr {
#if HDR
#define PUREHARDCORE_LEVEL 10
#endif
/** are we in the hardcore mode */
EX bool hardcore = false;
/** when did we switch to the hardcore mode */
EX int hardcoreAt;
EX bool pureHardcore() { return hardcore && hardcoreAt < PUREHARDCORE_LEVEL; }
/** can we still move? */
EX bool canmove = true;
// how many monsters are near
EX eMonster who_kills_me;
EX int lastkills;
EX bool legalmoves[MAX_EDGE+1];
EX bool hasSafeOrb(cell *c) {
return
c->item == itOrbSafety ||
c->item == itOrbShield ||
c->item == itOrbShell ||
(c->item == itOrbYendor && yendor::state(c) == yendor::ysUnlocked);
}
#if HDR
struct stalemate1 {
eMonster who;
cell *moveto;
cell *killed;
cell *pushto;
cell *comefrom;
cell *swordlast[2], *swordtransit[2], *swordnext[2];
bool isKilled(cell *c);
stalemate1(eMonster w, cell *mt, cell *ki, cell *pt, cell *cf) : who(w), moveto(mt), killed(ki), pushto(pt), comefrom(cf) {}
};
#endif
bool stalemate1::isKilled(cell *w) {
if(w->monst == moNone || w == killed) return true;
if(!moveto) return false;
for(int b=0; b<2; b++)
if((w == swordnext[b] || w == swordtransit[b]) && canAttack(moveto, who, w, w->monst, AF_SWORD))
return true;
if(logical_adjacent(moveto, who, w) && moveto != comefrom) {
int wid = neighborId(moveto, w);
int wfrom = neighborId(moveto, comefrom);
int flag = AF_APPROACH;
if(wid >= 0 && wfrom >= 0 && anglestraight(moveto, wfrom, wid)) flag |= AF_HORNS;
if(canAttack(moveto, who, w, w->monst, flag)) return true;
}
if(isNeighbor(w, comefrom) && comefrom == moveto && killed) {
int d1 = neighborId(comefrom, w);
int d2 = neighborId(comefrom, killed);
int di = angledist(comefrom->type, d1, d2);
if(di && items[itOrbSide1-1+di] && canAttack(moveto, who, w, w->monst, AF_SIDE))
return true;
}
if(logical_adjacent(comefrom, who, w) && logical_adjacent(moveto, who, w) && moveto != comefrom)
if(canAttack(moveto, who, w, w->monst, AF_STAB))
return true;
if(who == moPlayer && (killed || moveto != comefrom) && mirror::isKilledByMirror(w)) return true;
if(w->monst == moIvyHead || w->monst == moIvyBranch || isMutantIvy(w))
return isChild(w, killed);
if(isDragon(w->monst) && killed && isDragon(killed->monst) && killed->hitpoints) {
cell *head1 = dragon::findhead(w);
cell *head2 = dragon::findhead(killed);
if(head1 == head2 && dragon::totalhp(head1) ==1) return true;
}
if((w->monst == moPair || isMagneticPole(w->monst)) && killed && w->move(w->mondir) == killed)
return true;
if(w->monst == moKrakenT && killed && killed->monst == moKrakenT && killed->hitpoints) {
cell *head1 = w->move(w->mondir);
cell *head2 = killed->move(killed->mondir);
if(head1 == head2 && kraken::totalhp(head1) == 1) return true;
}
return false;
}
EX namespace stalemate {
EX bool isKilled(cell *w) {
for(int f=0; f<isize(moves); f++)
if(moves[f].isKilled(w)) return true;
return false;
};
EX }
EX bool krakensafe(cell *c) {
return items[itOrbFish] || items[itOrbAether] ||
(c->item == itOrbFish && c->wall == waBoat) ||
(c->item == itOrbAether && c->wall == waBoat);
}
EX bool monstersnear(stalemate1& sm) {
cell *c = sm.moveto;
bool eaten = false;
if(hardcore && sm.who == moPlayer) return false;
int res = 0;
bool fast = false;
elec::builder b;
if(elec::affected(c)) { who_kills_me = moLightningBolt; res++; }
if(c->wall == waArrowTrap && c->wparam == 2) {
who_kills_me = moArrowTrap; res++;
}
for(auto c1: crush_now) if(c == c1) {
who_kills_me = moCrusher; res++;
}
if(sm.who == moPlayer || items[itOrbEmpathy]) {
fast = (items[itOrbSpeed] && (items[itOrbSpeed] & 1));
if(sm.who == moPlayer && sm.moveto->item == itOrbSpeed && !items[itOrbSpeed]) fast = true;
}
if(havewhat&HF_OUTLAW) {
for(cell *c1: gun_targets(c))
if(c1->monst == moOutlaw && !c1->stuntime && !stalemate::isKilled(c1)) {
res++; who_kills_me = moOutlaw;
}
}
for(int t=0; t<c->type; t++) {
cell *c2 = c->move(t);
// consider monsters who attack from distance 2
if(c2) forCellEx(c3, c2) if(c3 != c) {
// only these monsters can attack from two spots...
if(!among(c3->monst, moLancer, moWitchSpeed, moWitchFlash))
continue;
// take logical_adjacent into account
if(c3->monst != moWitchFlash)
if(!logical_adjacent(c3, c3->monst, c2) || !logical_adjacent(c2, c3->monst, c))
continue;
if(elec::affected(c3) || stalemate::isKilled(c3)) continue;
if(c3->stuntime > (sm.who == moPlayer ? 0 : 1)) continue;
// speedwitches can only attack not-fastened monsters,
// others can only attack if the move is not fastened
if(c3->monst == moWitchSpeed && items[itOrbSpeed]) continue;
if(c3->monst != moWitchSpeed && fast) continue;
// cannot attack if the immediate cell is impassable (except flashwitches)
if(c3->monst != moWitchFlash) {
if(!passable(c2, c3, stalemate::isKilled(c2)?P_MONSTER:0)) continue;
if(isPlayerOn(c2) && items[itOrbFire]) continue;
}
// flashwitches cannot attack if it would kill another enemy
if(c3->monst == moWitchFlash && flashWouldKill(c3, 0)) continue;
res++, who_kills_me = c3->monst;
}
// consider normal monsters
if(c2 &&
isArmedEnemy(c2, sm.who) &&
!stalemate::isKilled(c2) &&
(c2->monst != moLancer || isUnarmed(sm.who) || !logical_adjacent(c, sm.who, c2))) {
eMonster m = c2->monst;
if(elec::affected(c2)) continue;
if(fast && c2->monst != moWitchSpeed) continue;
// Krakens just destroy boats
if(c2->monst == moKrakenT && onboat(sm)) {
if(krakensafe(c)) continue;
else if(warningprotection(XLAT("This move appears dangerous -- are you sure?")) && res == 0) m = moWarning;
else continue;
}
// they cannot attack through vines
if(!canAttack(c2, c2->monst, c, sm.who, AF_NEXTTURN)) continue;
if(c2->monst == moWorm || c2->monst == moTentacle || c2->monst == moHexSnake) {
if(passable_for(c2->monst, c, c2, 0))
eaten = true;
else if(c2->monst != moHexSnake) continue;
}
res++, who_kills_me = m;
}
}
if(sm.who == moPlayer && res && (markOrb2(itOrbShield) || markOrb2(itOrbShell)) && !eaten)
res = 0;
if(sm.who == moPlayer && res && markOrb2(itOrbDomination) && c->monst)
res = 0;
return !!res;
}
EX bool monstersnear2();
EX bool monstersnear2() {
multi::cpid++;
bool b = false;
bool recorduse[ittypes];
for(int i=0; i<ittypes; i++) recorduse[i] = orbused[i];
if(multi::cpid == multi::players || multi::players == 1 || multi::checkonly) {
dynamicval<eMonster> sw(passive_switch, passive_switch);
// check for safe orbs and switching first
for(auto &sm: stalemate::moves) if(sm.who == moPlayer) {
if(hasSafeOrb(sm.moveto)) {
multi::cpid--; return 0;
}
if(sm.moveto->item && itemclass(sm.moveto->item) == IC_TREASURE)
passive_switch = active_switch();
if(items[itOrbMagnetism]) forCellEx(c2, sm.moveto)
if(canPickupItemWithMagnetism(c2, sm.comefrom)) {
if(itemclass(c2->item) == IC_TREASURE)
passive_switch = active_switch();
if(hasSafeOrb(c2)) {
multi::cpid--;
return 0;
}
}
}
for(int i=0; i<isize(stalemate::moves); i++)
for(int j=0; j<isize(stalemate::moves); j++) if(i != j) {
if(swordConflict(stalemate::moves[i], stalemate::moves[j])) {
b = true;
who_kills_me = moEnergySword;
}
if(multi::player[i].at == multi::player[j].at)
{ b = true; who_kills_me = moFireball; }
if(celldistance(multi::player[i].at, multi::player[j].at) > 8)
{ b = true; who_kills_me = moAirball; }
}
for(int i=0; !b && i<isize(stalemate::moves); i++)
b = monstersnear(stalemate::moves[i]);
}
else b = !multimove();
multi::cpid--;
for(int i=0; i<ittypes; i++) orbused[i] = recorduse[i];
return b;
}
EX bool monstersnear(cell *c, cell *nocount, eMonster who, cell *pushto, cell *comefrom) {
if(peace::on) return 0; // you are safe
stalemate1 sm(who, c, nocount, pushto, comefrom);
if(who == moPlayer) for(int b=0; b<2; b++) sm.swordlast[b] = sword::pos(multi::cpid, b);
cell *none = NULL;
cell **wcw = &cwt.at;
if(who != moPlayer) wcw = &none;
else if(multi::players > 1) wcw = &multi::player[multi::cpid].at;
dynamicval<cell*> x5(*wcw, c);
dynamicval<bool> x6(stalemate::nextturn, true);
dynamicval<sword::sworddir> x7(sword::dir[multi::cpid],
who == moPlayer ? sword::shift(comefrom, c, sword::dir[multi::cpid]) :
sword::dir[multi::cpid]);
for(int b=0; b<2; b++) {
if(who == moPlayer) {
sm.swordnext[b] = sword::pos(multi::cpid, b);
sm.swordtransit[b] = NULL;
if(sm.swordnext[b] && sm.swordnext[b] != sm.swordlast[b] && !isNeighbor(sm.swordlast[b], sm.swordnext[b])) {
forCellEx(c2, sm.swordnext[b])
if(c2 != c && c2 != comefrom && isNeighbor(c2, S3==3 ? sm.swordlast[b] : *wcw))
sm.swordtransit[b] = c2;
if(S3 == 4)
forCellEx(c2, c)
if(c2 != comefrom && isNeighbor(c2, sm.swordlast[b]))
sm.swordtransit[b] = c2;
}
}
else {
sm.swordnext[b] = sm.swordtransit[b] = NULL;
}
}
stalemate::moves.push_back(sm);
// dynamicval<eMonster> x7(stalemate::who, who);
bool b;
if(who == moPlayer && c->wall == waBigStatue) {
eWall w = comefrom->wall;
c->wall = waNone;
if(doesnotFall(comefrom)) comefrom->wall = waBigStatue;
b = monstersnear2();
comefrom->wall = w;
c->wall = waBigStatue;
}
else if(who == moPlayer && isPushable(c->wall)) {
eWall w = c->wall;
c->wall = waNone;
b = monstersnear2();
c->wall = w;
}
else {
b = monstersnear2();
}
stalemate::moves.pop_back();
return b;
}
EX namespace stalemate {
EX vector<stalemate1> moves;
EX bool nextturn;
EX bool isMoveto(cell *c) {
for(int i=0; i<isize(moves); i++) if(moves[i].moveto == c) return true;
return false;
}
EX bool isKilledDirectlyAt(cell *c) {
for(int i=0; i<isize(moves); i++) if(moves[i].killed == c) return true;
return false;
}
EX bool isPushto(cell *c) {
for(int i=0; i<isize(moves); i++) if(moves[i].pushto == c) return true;
return false;
}
EX }
EX bool onboat(stalemate1& sm) {
cell *c = sm.moveto;
cell *cf = sm.comefrom;
return (c->wall == waBoat) || (cf->wall == waBoat && c->wall == waSea);
}
EX bool multimove() {
if(multi::cpid == 0) lastkills = tkills();
if(!multi::playerActive(multi::cpid)) return !monstersnear2();
cellwalker bcwt = cwt;
cwt = multi::player[multi::cpid];
bool b = movepcto(multi::whereto[multi::cpid]);
if(b) {
multi::aftermove = true;
multi::player[multi::cpid] = cwt;
multi::whereto[multi::cpid].d = MD_UNDECIDED;
int curkills = tkills();
multi::kills[multi::cpid] += (curkills - lastkills);
lastkills = curkills;
}
cwt = bcwt;
return b;
}
EX namespace multi {
EX bool checkonly = false;
EX bool aftermove;
EX }
EX bool swordConflict(const stalemate1& sm1, const stalemate1& sm2) {
if(items[itOrbSword] || items[itOrbSword2])
for(int b=0; b<2; b++)
if(sm1.comefrom == sm2.swordlast[b] || sm1.comefrom == sm2.swordtransit[b] || sm1.comefrom == sm2.swordnext[b])
if(sm1.moveto == sm2.swordlast[b] || sm1.moveto == sm2.swordtransit[b] || sm1.moveto == sm2.swordnext[b])
return true;
return false;
}
EX void checkmove() {
if(dual::state == 2) return;
if(shmup::on) return;
dynamicval<eGravity> gs(gravity_state, gravity_state);
#if CAP_INV
if(inv::on) inv::compute();
#endif
if(multi::players > 1 && !multi::checkonly) return;
if(hardcore) return;
bool orbusedbak[ittypes];
// do not activate orbs!
for(int i=0; i<ittypes; i++) orbusedbak[i] = orbused[i];
for(int i=0; i<=MAX_EDGE; i++) legalmoves[i] = false;
canmove = haveRangedTarget();
items[itWarning]+=2;
if(movepcto(-1, 0, true)) canmove = legalmoves[MAX_EDGE] = true;
if(vid.mobilecompasssize || !canmove)
for(int i=0; i<cwt.at->type; i++)
if(movepcto(1, -1, true))
canmove = legalmoves[cwt.spin] = true;
if(vid.mobilecompasssize || !canmove)
for(int i=0; i<cwt.at->type; i++)
if(movepcto(1, 1, true))
canmove = legalmoves[cwt.spin] = true;
if(kills[moPlayer]) canmove = false;
#if CAP_INV
if(inv::on && !canmove && !inv::incheck) {
if(inv::remaining[itOrbSafety] || inv::remaining[itOrbFreedom])
canmove = true;
else {
inv::check(1);
checkmove();
inv::check(-1);
}
if(canmove)
pushScreen(inv::show);
}
#endif
if(!canmove) {
achievement_final(true);
if(cmode & sm::NORMAL) showMissionScreen();
}
if(canmove && timerstopped) {
timerstart = time(NULL);
timerstopped = false;
}
items[itWarning]-=2;
for(int i=0; i<ittypes; i++) orbused[i] = orbusedbak[i];
if(recallCell.at && !markOrb(itOrbRecall)) activateRecall();
}
}

View File

@ -274,21 +274,22 @@ EX namespace westwall {
}
EX }
EX namespace variant {
#if HDR
struct variant_feature {
struct feature {
color_t color_change;
int rate_change;
eMonster wanderer;
void (*build)(cell*);
};
extern array<variant_feature, 21> variant_features;
extern array<feature, 21> features;
#endif
#define VF [] (cell *c)
array<variant_feature, 21> variant_features {{
variant_feature{(color_t)(-0x202020), 5, moNecromancer, VF {
array<feature, 21> features {{
feature{(color_t)(-0x202020), 5, moNecromancer, VF {
if(c->wall == waNone && hrand(1500) < 20) c->wall = waFreshGrave;
if(hrand(20000) < 10 + items[itVarTreasure])
c->monst = moNecromancer;
@ -322,6 +323,509 @@ array<variant_feature, 21> variant_features {{
{0x100708, 1, moRatling, VF { if(c->wall == waNone && !c->monst && hrand(50000) < 25 + items[itVarTreasure]) c->monst = moRatling; }}
}};
#undef VF
EX }
EX namespace camelot {
/** number of Grails collected, to show you as a knight */
EX int knighted = 0;
/** this value is used when using Orb of Safety in the Camelot in Pure Tactics Mode */
EX int anthraxBonus = 0;
EX void roundTableMessage(cell *c2) {
if(!euclid && !cwt.at->master->alt) return;
if(!euclid && !c2->master->alt) return;
int dd = celldistAltRelative(c2) - celldistAltRelative(cwt.at);
bool tooeasy = (roundTableRadius(c2) < newRoundTableRadius());
if(dd>0) {
if(grailWasFound(cwt.at)) {
addMessage(XLAT("The Knights congratulate you on your success!"));
knighted = roundTableRadius(cwt.at);
}
else if(!tooeasy)
addMessage(XLAT("The Knights laugh at your failure!"));
}
else {
if(grailWasFound(cwt.at))
addMessage(XLAT("The Knights stare at you!"));
else if(tooeasy) {
if(!tactic::on)
addMessage(XLAT("Come on, this is too easy... find a bigger castle!"));
}
else
addMessage(XLAT("The Knights wish you luck!"));
}
}
EX void knightFlavorMessage(cell *c2) {
if(!eubinary && !c2->master->alt) {
addMessage(XLAT("\"I am lost...\""));
return;
}
if(tactic::on) {
addMessage(XLAT("\"The Knights of the Horocyclic Table salute you!\""));
return;
}
bool grailfound = grailWasFound(c2);
int rad = roundTableRadius(c2);
bool tooeasy = (rad < newRoundTableRadius());
static int msgid = 0;
retry:
if(msgid >= 32) msgid = 0;
if(msgid == 0 && grailfound) {
addMessage(XLAT("\"I would like to congratulate you again!\""));
}
else if(msgid == 1 && !tooeasy) {
addMessage(XLAT("\"Find the Holy Grail to become one of us!\""));
}
else if(msgid == 2 && !tooeasy) {
addMessage(XLAT("\"The Holy Grail is in the center of the Round Table.\""));
}
#if CAP_CRYSTAL
else if(msgid == 3 && cryst) {
if(crystal::pure())
addMessage(XLAT("\"Each piece of the Round Table is exactly %1 steps away from the Holy Grail.\"", its(roundTableRadius(c2))));
else
addMessage(XLAT("\"According to Merlin, the Round Table is a perfect Euclidean sphere in %1 dimensions.\"", its(ginf[gCrystal].sides/2)));
}
#endif
else if(msgid == 3 && !peace::on && in_full_game()) {
addMessage(XLAT("\"I enjoy watching the hyperbug battles.\""));
}
else if(msgid == 4 && in_full_game()) {
addMessage(XLAT("\"Have you visited a temple in R'Lyeh?\""));
}
else if(msgid == 5 && in_full_game()) {
addMessage(XLAT("\"Nice castle, eh?\""));
}
else if(msgid == 6 && items[itSpice] < 10 && !peace::on && in_full_game()) {
addMessage(XLAT("\"The Red Rock Valley is dangerous, but beautiful.\""));
}
else if(msgid == 7 && items[itSpice] < 10 && !peace::on && in_full_game()) {
addMessage(XLAT("\"Train in the Desert first!\""));
}
else if(msgid == 8 && sizes_known() && !tactic::on) {
string s = "";
if(0) ;
#if CAP_CRYSTAL
else if(cryst)
s = crystal::get_table_boundary();
#endif
else if(!quotient)
s = expansion.get_descendants(rad).get_str(100);
if(s == "") { msgid++; goto retry; }
addMessage(XLAT("\"Our Table seats %1 Knights!\"", s));
}
else if(msgid == 9 && sizes_known() && !tactic::on) {
string s = "";
if(0);
#if CAP_CRYSTAL
else if(cryst)
s = crystal::get_table_volume();
#endif
else if(!quotient)
s = expansion.get_descendants(rad-1, expansion.diskid).get_str(100);
if(s == "") { msgid++; goto retry; }
addMessage(XLAT("\"There are %1 floor tiles inside our Table!\"", s));
}
else if(msgid == 10 && !items[itPirate] && !items[itWhirlpool] && !peace::on && in_full_game()) {
addMessage(XLAT("\"Have you tried to take a boat and go into the Ocean? Try it!\""));
}
else if(msgid == 11 && !princess::saved && in_full_game()) {
addMessage(XLAT("\"When I visited the Palace, a mouse wanted me to go somewhere.\""));
}
else if(msgid == 12 && !princess::saved && in_full_game()) {
addMessage(XLAT("\"I wonder what was there...\""));
}
else if(msgid == 13 && !peace::on && in_full_game()) {
addMessage(XLAT("\"Be careful in the Rose Garden! It is beautiful, but very dangerous!\""));
}
else if(msgid == 14) {
addMessage(XLAT("\"There is no royal road to geometry.\""));
}
else if(msgid == 15) {
addMessage(XLAT("\"There is no branch of mathematics, however abstract, "));
addMessage(XLAT("which may not some day be applied to phenomena of the real world.\""));
}
else if(msgid == 16) {
addMessage(XLAT("\"It is not possession but the act of getting there, "));
addMessage(XLAT("which grants the greatest enjoyment.\""));
}
else if(msgid == 17) {
addMessage(XLAT("\"We live in a beautiful and orderly world, "));
addMessage(XLAT("and not in a chaos without norms.\""));
}
else if(msgid == 25) {
addMessage(XLAT("\"Thank you very much for talking, and have a great rest of your day!\""));
}
else {
msgid++; goto retry;
}
msgid++;
}
EX }
EX namespace mine {
EX bool uncoverMines(cell *c, int lev, int dist, bool just_checking) {
bool b = false;
if(c->wall == waMineMine && just_checking) return true;
if(c->wall == waMineUnknown) {
if(just_checking)
return true;
else {
c->wall = waMineOpen;
b = true;
}
}
bool minesNearby = false;
bool nominesNearby = false;
bool mineopens = false;
auto adj = adj_minefield_cells(c);
for(cell *c2: adj) {
if(c2->wall == waMineMine) minesNearby = true;
if(c2->wall == waMineOpen) mineopens = true;
if(c2->wall == waMineUnknown && !c2->item) nominesNearby = true;
}
if(lev && (nominesNearby || mineopens) && !minesNearby) for(cell *c2: adj)
if(c2->wall == waMineUnknown || c2->wall == waMineOpen) {
b |= uncoverMines(c2, lev-1, dist+1, just_checking);
if(b && just_checking) return true;
}
if(minesNearby && !nominesNearby && dist == 0) {
for(cell *c2: adj)
if(c2->wall == waMineMine && c2->land == laMinefield)
c2->landparam |= 1;
}
return b;
}
EX bool mightBeMine(cell *c) {
return c->wall == waMineUnknown || c->wall == waMineMine;
}
EX hookset<bool(cell*)> *hooks_mark;
EX void performMarkCommand(cell *c) {
if(!c) return;
if(callhandlers(false, hooks_mark, c)) return;
if(c->land == laCA && c->wall == waNone)
c->wall = waFloorA;
else if(c->land == laCA && c->wall == waFloorA)
c->wall = waNone;
if(c->land != laMinefield) return;
if(c->item) return;
if(!mightBeMine(c)) return;
bool adj = false;
forCellEx(c2, c) if(c2->wall == waMineOpen) adj = true;
if(adj) c->landparam ^= 1;
}
EX bool marked_mine(cell *c) {
if(!mightBeMine(c)) return false;
if(c->item) return false;
if(c->land != laMinefield) return true;
return c->landparam & 1;
}
EX bool marked_safe(cell *c) {
if(!mightBeMine(c)) return false;
if(c->item) return true;
if(c->land != laMinefield) return false;
return c->landparam & 2;
}
EX bool safe() {
return items[itOrbAether];
}
EX void uncover_full(cell *c2) {
int mineradius =
bounded ? 3 :
(items[itBombEgg] < 1 && !tactic::on) ? 0 :
items[itBombEgg] < 20 ? 1 :
items[itBombEgg] < 30 ? 2 :
3;
bool nomine = !normal_gravity_at(c2);
if(!nomine && uncoverMines(c2, mineradius, 0, true) && markOrb(itOrbAether))
nomine = true;
if(!nomine) {
uncoverMines(c2, mineradius, 0, false);
mayExplodeMine(c2, moPlayer);
}
}
EX void auto_teleport_charges() {
if(specialland == laMinefield && firstland == laMinefield && bounded)
items[itOrbTeleport] = isFire(cwt.at->wall) ? 0 : 1;
}
EX }
EX namespace terracotta {
#if HDR
// predictable or not
static constexpr bool randterra = false;
#endif
EX void check(cell *c) {
if(c->wall == waTerraWarrior && !c->monst && !racing::on) {
bool live = false;
if(randterra) {
c->landparam++;
if((c->landparam == 3 && hrand(3) == 0) ||
(c->landparam == 4 && hrand(2) == 0) ||
c->landparam == 5)
live = true;
}
else {
c->landparam--;
live = !c->landparam;
}
if(live)
c->monst = moTerraWarrior,
c->hitpoints = 7,
c->wall = waNone;
}
}
EX void check_around(cell *c) {
forCellEx(c2, c)
check(c2);
}
EX void check() {
for(int i=0; i<numplayers(); i++)
forCellEx(c, playerpos(i)) {
if(shmup::on) {
forCellEx(c2, c)
check(c2);
}
else
check(c);
}
}
EX }
EX namespace ambush {
EX void mark(cell *c, manual_celllister& cl) {
if(!cl.add(c)) return;
forCellEx(c2, c)
if(c2->cpdist < c->cpdist)
mark(c2, cl);
}
EX int distance;
EX bool ambushed;
EX void check_state() {
if(havewhat & HF_HUNTER) {
manual_celllister cl;
for(cell *c: dcal) {
if(c->monst == moHunterDog) {
if(c->cpdist > distance)
distance = c->cpdist;
mark(c, cl);
}
if(c->monst == moHunterGuard && c->cpdist <= 4)
mark(c, cl);
}
if(items[itHunting] > 5 && items[itHunting] <= 22) {
int q = 0;
for(int i=0; i<numplayers(); i++)
forCellEx(c2, playerpos(i))
if(cl.listed(c2))
q++;
if(q == 1) havewhat |= HF_FAILED_AMBUSH;
if(q == 2) {
for(int i=0; i<numplayers(); i++)
forCellEx(c2, playerpos(i))
if(cl.listed(c2))
forCellEx(c3, playerpos(i))
if(c3 != c2 && isNeighbor(c2,c3))
if(cl.listed(c3))
havewhat |= HF_FAILED_AMBUSH;
}
if(havewhat & HF_FAILED_AMBUSH && ambushed) {
addMessage(XLAT("The Hunting Dogs give up."));
ambushed = false;
}
}
}
}
EX int fixed_size;
EX int size(cell *c, eItem what) {
bool restricted = false;
for(cell *c2: dcal) {
if(c2->cpdist > 3) break;
if(c2->monst && !isFriendly(c2) && !slowMover(c2) && !isMultitile(c2)) restricted = true;
}
int qty = items[itHunting];
if(fixed_size)
return fixed_size;
switch(what) {
case itCompass:
return 0;
case itHunting:
return min(min(qty, max(33-qty, 6)), 15);
case itOrbSide3:
return restricted ? 10 : 20;
case itOrbFreedom:
return restricted ? 10 : 60;
case itOrbThorns:
case itOrb37:
return 20;
case itOrbLava:
return 20;
case itOrbBeauty:
return 35;
case itOrbShell:
return 35;
case itOrbPsi:
// return 40; -> no benefits
return 20;
case itOrbDash:
case itOrbFrog:
return 40;
case itOrbAir:
case itOrbDragon:
return 50;
case itOrbStunning:
// return restricted ? 50 : 60; -> no benefits
return 30;
case itOrbBull:
case itOrbSpeed:
case itOrbShield:
return 60;
case itOrbInvis:
return 80;
case itOrbTeleport:
return 300;
case itGreenStone:
case itOrbSafety:
case itOrbYendor:
return 0;
case itKey:
return 16;
case itWarning:
return qty;
default:
return restricted ? 6 : 10;
break;
// Flash can survive about 70, but this gives no benefits
}
}
EX int ambush(cell *c, eItem what) {
int maxdist = gamerange();
celllister cl(c, maxdist, 1000000, NULL);
cell *c0 = c;
int d = 0;
int dogs0 = 0;
for(cell *cx: cl.lst) {
int dh = cl.getdist(cx);
if(dh <= 2 && cx->monst == moHunterGuard)
cx->monst = moHunterDog, dogs0++;
if(dh > d) c0 = cx, d = dh;
}
if(sphere) {
int dogs = size(c, what);
for(int i = cl.lst.size()-1; i>0 && dogs; i--)
if(!isPlayerOn(cl.lst[i]) && !cl.lst[i]->monst)
cl.lst[i]->monst = moHunterDog, dogs--;
}
vector<cell*> around;
cell *clast = NULL;
cell *ccur = c0;
int v = VALENCE;
if(v > 4) {
for(cell *c: cl.lst) if(cl.getdist(c) == d) around.push_back(c);
hrandom_shuffle(&around[0], isize(around));
}
else {
for(int tries=0; tries<10000; tries++) {
cell *c2 = NULL;
if(v == 3) {
forCellEx(c1, ccur)
if(c1 != clast && cl.listed(c1) && cl.getdist(c1) == d)
c2 = c1;
}
if(v == 4) {
for(int i=0; i<ccur->type; i++) {
cell *c1 = (cellwalker(ccur, i) + wstep + 1).peek();
if(!c1) continue;
if(c1 != clast && cl.listed(c1) && cl.getdist(c1) == d)
c2 = c1;
}
}
if(!c2) break;
if(c2->land == laHunting && c2->wall == waNone && c2->monst == moNone)
around.push_back(c2);
clast = ccur; ccur = c2;
if(c2 == c0) break;
}
}
int N = isize(around);
int dogs = size(c, what);
int gaps = dogs;
if(!N) return dogs0;
ambushed = true;
int shift = hrand(N);
dogs = min(dogs, N);
gaps = min(gaps, N);
for(int i=0; i<dogs; i++) {
int pos = (shift + (N * i) / gaps) % N;
cell *nextdog = around[pos];
nextdog->monst = moHunterDog;
nextdog->stuntime = 1;
drawFlash(nextdog);
}
return dogs + dogs0;
}
EX }
}
#endif

View File

@ -426,7 +426,7 @@ EX void handleKeyNormal(int sym, int uni) {
if(DEFAULTNOR(sym)) {
gmodekeys(sym, uni);
if(uni == 'm' && canmove && (centerover == cwt.at ? mouseover : centerover))
performMarkCommand(mouseover);
mine::performMarkCommand(mouseover);
}
if(DEFAULTCONTROL) {

View File

@ -664,7 +664,7 @@ int read_cheat_args() {
// make all ambushes use the given number of dogs
// example: hyper -W Hunt -IP Shield 1 -ambush 60
PHASE(3) cheat();
shift(); ambushval = argi();
shift(); ambush::fixed_size = argi();
}
else if(argis("-testdistances")) {
PHASE(3); shift(); test_distances(argi());

789
environment.cpp Normal file
View File

@ -0,0 +1,789 @@
// Hyperbolic Rogue - environment
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file environment.cpp
* \brief game environment: routines related to the game that affect all the map. Monsters to move are detected here, but their moves are implemented in monstermove.cpp
*/
#include "hyper.h"
namespace hr {
#if HDR
#define HF_BUG Flag(0)
#define HF_EARTH Flag(1)
#define HF_BIRD Flag(2)
#define HF_LEADER Flag(3)
#define HF_HEX Flag(4)
#define HF_WHIRLPOOL Flag(5)
#define HF_WATER Flag(6)
#define HF_AIR Flag(7)
#define HF_MUTANT Flag(8)
#define HF_OUTLAW Flag(9)
#define HF_WHIRLWIND Flag(10)
#define HF_ROSE Flag(11)
#define HF_DRAGON Flag(12)
#define HF_KRAKEN Flag(13)
#define HF_SHARK Flag(14)
#define HF_BATS Flag(15)
#define HF_REPTILE Flag(16)
#define HF_EAGLES Flag(17)
#define HF_SLOW Flag(18)
#define HF_FAST Flag(19)
#define HF_WARP Flag(20)
#define HF_MOUSE Flag(21)
#define HF_RIVER Flag(22)
#define HF_MIRROR Flag(23)
#define HF_VOID Flag(24)
#define HF_HUNTER Flag(25)
#define HF_FAILED_AMBUSH Flag(26)
#define HF_MAGNET Flag(27)
#define HF_HEXD Flag(28)
#define HF_ALT Flag(29)
#define HF_MONK Flag(30)
#define HF_WESTWALL Flag(31)
#endif
EX flagtype havewhat, hadwhat;
/** monsters of specific types to move */
EX vector<cell*> worms, ivies, ghosts, golems, hexsnakes;
/** temporary changes during bfs */
vector<pair<cell*, eMonster>> tempmonsters;
/** additional direction information for BFS algorithms.
* It remembers from where we have got to this location
* the opposite cell will be added to the queue first,
* which helps the AI.
**/
EX vector<int> reachedfrom;
/** The position of the first cell in dcal in distance 7. New wandering monsters can be generated in dcal[first7..]. */
EX int first7;
/** the list of all nearby cells, according to cpdist */
EX vector<cell*> dcal;
/** the list of all nearby cells, according to current pathdist */
EX vector<cell*> pathq;
/** the number of big statues -- they increase monster generation */
EX int statuecount;
/** list of monsters to move (pathq restriced to monsters) */
EX vector<cell*> pathqm;
/** which hex snakes are there */
EX set<int> snaketypes;
EX int gamerange_bonus = 0;
EX int gamerange() { return getDistLimit() + gamerange_bonus; }
// pathdist begin
EX cell *pd_from;
EX int pd_range;
EX void onpath(cell *c, int d) {
c->pathdist = d;
pathq.push_back(c);
}
EX void onpath(cell *c, int d, int sp) {
c->pathdist = d;
pathq.push_back(c);
reachedfrom.push_back(sp);
}
EX void clear_pathdata() {
for(auto c: pathq) c->pathdist = PINFD;
pathq.clear();
pathqm.clear();
reachedfrom.clear();
}
EX int pathlock = 0;
EX void compute_graphical_distance() {
if(pathlock) { printf("path error: compute_graphical_distance\n"); }
cell *c1 = centerover ? centerover : pd_from ? pd_from : cwt.at;
int sr = get_sightrange_ambush();
if(pd_from == c1 && pd_range == sr) return;
clear_pathdata();
pd_from = c1;
pd_range = sr;
c1->pathdist = 0;
pathq.push_back(pd_from);
for(int qb=0; qb<isize(pathq); qb++) {
cell *c = pathq[qb];
if(c->pathdist == pd_range) break;
if(qb == 0) forCellCM(c1, c) ;
forCellEx(c1, c)
if(c1->pathdist == PINFD)
onpath(c1, c->pathdist + 1);
}
}
EX void computePathdist(eMonster param) {
for(cell *c: targets)
onpath(c, isPlayerOn(c) ? 0 : 1, hrand(c->type));
int qtarg = isize(targets);
int limit = gamerange();
for(int qb=0; qb < isize(pathq); qb++) {
cell *c = pathq[qb];
int fd = reachedfrom[qb] + c->type/2;
if(c->monst && !isBug(c) && !(isFriendly(c) && !c->stuntime)) {
pathqm.push_back(c);
continue; // no paths going through monsters
}
if(isMounted(c) && !isPlayerOn(c)) {
// don't treat the Worm you are riding as passable
pathqm.push_back(c);
continue;
}
if(c->cpdist > limit && !(c->land == laTrollheim && turncount < c->landparam)) continue;
int d = c->pathdist;
if(d == PINFD - 1) continue;
for(int j=0; j<c->type; j++) {
int i = (fd+j) % c->type;
// printf("i=%d cd=%d\n", i, c->move(i)->cpdist);
cell *c2 = c->move(i);
if(c2 && c2->pathdist == PINFD &&
passable(c2, (qb<qtarg) && !nonAdjacent(c,c2) && !thruVine(c,c2) ?NULL:c, P_MONSTER | P_REVDIR)) {
if(qb >= qtarg) {
if(param == moTortoise && nogoSlow(c, c2)) continue;
if(param == moIvyRoot && strictlyAgainstGravity(c, c2, false, MF_IVY)) continue;
if(param == moWorm && (cellUnstable(c) || cellEdgeUnstable(c) || prairie::no_worms(c))) continue;
if(items[itOrbLava] && c2->cpdist <= 5 && pseudohept(c) && makeflame(c2, 1, true))
continue;
}
onpath(c2, d+1, c->c.spin(i));
}
}
}
}
#if HDR
struct pathdata {
void checklock() {
if(pd_from) pd_from = NULL, clear_pathdata();
if(pathlock) printf("path error\n");
pathlock++;
}
~pathdata() {
pathlock--;
clear_pathdata();
}
pathdata(eMonster m) {
checklock();
computePathdist(m);
}
pathdata(int i) {
checklock();
}
};
#endif
// pathdist end
/** calculate cpdist, 'have' flags, and do general fixings */
EX void bfs() {
calcTidalPhase();
yendor::onpath();
int dcs = isize(dcal);
for(int i=0; i<dcs; i++) dcal[i]->cpdist = INFD;
worms.clear(); ivies.clear(); ghosts.clear(); golems.clear();
tempmonsters.clear(); targets.clear();
statuecount = 0;
hexsnakes.clear();
hadwhat = havewhat;
havewhat = 0; jiangshi_on_screen = 0;
snaketypes.clear();
if(!(hadwhat & HF_WARP)) { avengers = 0; }
if(!(hadwhat & HF_MIRROR)) { mirrorspirits = 0; }
elec::havecharge = false;
elec::afterOrb = false;
elec::haveelec = false;
airmap.clear();
if(!(hadwhat & HF_ROSE)) rosemap.clear();
dcal.clear(); reachedfrom.clear();
recalcTide = false;
for(int i=0; i<numplayers(); i++) {
cell *c = playerpos(i);
if(!c) continue;
if(c->cpdist == 0) continue;
c->cpdist = 0;
checkTide(c);
dcal.push_back(c);
reachedfrom.push_back(hrand(c->type));
if(!invismove) targets.push_back(c);
}
int distlimit = gamerange();
for(int i=0; i<numplayers(); i++) {
cell *c = playerpos(i);
if(!c) continue;
if(items[itOrbDomination])
if(c->monst == moTentacle || c->monst == moTentaclewait || c->monst == moTentacleEscaping)
worms.push_back(c);
}
int qb = 0;
first7 = 0;
while(true) {
if(qb == isize(dcal)) break;
int i, fd = reachedfrom[qb] + 3;
cell *c = dcal[qb++];
int d = c->cpdist;
if(WDIM == 2 && d == distlimit) { first7 = qb; break; }
for(int j=0; j<c->type; j++) if(i = (fd+j) % c->type, c->move(i)) {
// printf("i=%d cd=%d\n", i, c->move(i)->cpdist);
cell *c2 = c->move(i);
if(!c2) continue;
if(isWarpedType(c2->land)) havewhat |= HF_WARP;
if(c2->land == laMirror) havewhat |= HF_MIRROR;
if((c->wall == waBoat || c->wall == waSea) &&
(c2->wall == waSulphur || c2->wall == waSulphurC))
c2->wall = waSea;
if(c2 && signed(c2->cpdist) > d+1) {
if(WDIM == 3 && !gmatrix.count(c2)) {
if(!first7) first7 = qb;
continue;
}
c2->cpdist = d+1;
// remove treasures
if(!peace::on && c2->item && c2->cpdist == distlimit && itemclass(c2->item) == IC_TREASURE &&
c2->item != itBabyTortoise &&
(items[c2->item] >= (chaosmode?10:20) + currentLocalTreasure || getGhostcount() >= 2)) {
c2->item = itNone;
if(c2->land == laMinefield) { c2->landparam &= ~3; }
}
if(c2->item == itBombEgg && c2->cpdist == distlimit && items[itBombEgg] >= c2->landparam) {
c2->item = itNone;
c2->landparam |= 2;
c2->landparam &= ~1;
if(!c2->monst) c2->monst = moBomberbird;
}
if(c2->item == itBarrow && c2->cpdist == distlimit && c2->wall != waBarrowDig) {
c2->item = itNone;
}
if(c2->item == itLotus && c2->cpdist == distlimit && items[itLotus] >= getHauntedDepth(c2)) {
c2->item = itNone;
}
if(c2->item == itMutant2 && timerghost) {
bool rotten = true;
for(int i=0; i<c2->type; i++)
if(c2->move(i) && c2->move(i)->monst == moMutant)
rotten = false;
if(rotten) c2->item = itNone;
}
if(c2->item == itDragon && (shmup::on ? shmup::curtime-c2->landparam>300000 :
turncount-c2->landparam > 500))
c2->item = itNone;
if(c2->item == itTrollEgg && c2->cpdist == distlimit && !shmup::on && c2->landparam && turncount-c2->landparam > 650)
c2->item = itNone;
if(c2->item == itWest && c2->cpdist == distlimit && items[itWest] >= c2->landparam + 4)
c2->item = itNone;
if(c2->item == itMutant && c2->cpdist == distlimit && items[itMutant] >= c2->landparam) {
c2->item = itNone;
}
if(c2->item == itIvory && c2->cpdist == distlimit && items[itIvory] >= c2->landparam) {
c2->item = itNone;
}
if(c2->item == itAmethyst && c2->cpdist == distlimit && items[itAmethyst] >= -celldistAlt(c2)/5) {
c2->item = itNone;
}
if(!keepLightning) c2->ligon = 0;
dcal.push_back(c2);
reachedfrom.push_back(c->c.spin(i));
checkTide(c2);
if(c2->wall == waBigStatue && c2->land != laTemple)
statuecount++;
if(cellHalfvine(c2) && isWarped(c2)) {
addMessage(XLAT("%The1 is destroyed!", c2->wall));
destroyHalfvine(c2);
}
if(c2->wall == waCharged) elec::havecharge = true;
if(c2->land == laStorms) elec::haveelec = true;
if(c2->land == laWhirlpool) havewhat |= HF_WHIRLPOOL;
if(c2->land == laWhirlwind) havewhat |= HF_WHIRLWIND;
if(c2->land == laWestWall) havewhat |= HF_WESTWALL;
if(c2->land == laPrairie) havewhat |= HF_RIVER;
if(c2->wall == waRose) havewhat |= HF_ROSE;
if((hadwhat & HF_ROSE) && (rosemap[c2] & 3)) havewhat |= HF_ROSE;
if(c2->monst) {
if(isHaunted(c2->land) &&
c2->monst != moGhost && c2->monst != moZombie && c2->monst != moNecromancer)
survivalist = false;
if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) {
havewhat |= HF_HEX;
if(c2->mondir != NODIR)
snaketypes.insert(snake_pair(c2));
if(c2->monst == moHexSnake) hexsnakes.push_back(c2);
else findWormIvy(c2);
}
else if(c2->monst == moKrakenT || c2->monst == moKrakenH) {
havewhat |= HF_KRAKEN;
}
else if(c2->monst == moDragonHead || c2->monst == moDragonTail) {
havewhat |= HF_DRAGON;
}
else if(c2->monst == moWitchSpeed)
havewhat |= HF_FAST;
else if(c2->monst == moMutant)
havewhat |= HF_MUTANT;
else if(c2->monst == moJiangshi)
jiangshi_on_screen++;
else if(c2->monst == moOutlaw)
havewhat |= HF_OUTLAW;
else if(isGhostMover(c2->monst))
ghosts.push_back(c2);
else if(isWorm(c2) || isIvy(c2)) findWormIvy(c2);
else if(isBug(c2)) {
havewhat |= HF_BUG;
targets.push_back(c2);
}
else if(isFriendly(c2)) {
if(c2->monst != moMouse && !markEmpathy(itOrbInvis) && !(isWatery(c2) && markEmpathy(itOrbFish)) &&
!c2->stuntime) targets.push_back(c2);
if(c2->monst == moGolem) golems.push_back(c2);
if(c2->monst == moFriendlyGhost) golems.push_back(c2);
if(c2->monst == moKnight) golems.push_back(c2);
if(c2->monst == moTameBomberbird) golems.push_back(c2);
if(c2->monst == moMouse) { golems.push_back(c2); havewhat |= HF_MOUSE; }
if(c2->monst == moPrincess || c2->monst == moPrincessArmed) golems.push_back(c2);
if(c2->monst == moIllusion) {
if(items[itOrbIllusion]) items[itOrbIllusion]--;
else c2->monst = moNone;
}
}
else if(c2->monst == moButterfly) {
addButterfly(c2);
}
else if(isAngryBird(c2->monst)) {
havewhat |= HF_BIRD;
if(c2->monst == moBat) havewhat |= HF_BATS | HF_EAGLES;
if(c2->monst == moEagle) havewhat |= HF_EAGLES;
}
else if(c2->monst == moReptile) havewhat |= HF_REPTILE;
else if(isLeader(c2->monst)) havewhat |= HF_LEADER;
else if(c2->monst == moEarthElemental) havewhat |= HF_EARTH;
else if(c2->monst == moWaterElemental) havewhat |= HF_WATER;
else if(c2->monst == moVoidBeast) havewhat |= HF_VOID;
else if(c2->monst == moHunterDog) havewhat |= HF_HUNTER;
else if(isMagneticPole(c2->monst)) havewhat |= HF_MAGNET;
else if(c2->monst == moAltDemon) havewhat |= HF_ALT;
else if(c2->monst == moHexDemon) havewhat |= HF_HEXD;
else if(c2->monst == moMonk) havewhat |= HF_MONK;
else if(c2->monst == moShark || c2->monst == moCShark) havewhat |= HF_SHARK;
else if(c2->monst == moAirElemental)
havewhat |= HF_AIR, airmap.push_back(make_pair(c2,0));
}
// pheromones!
if(c2->land == laHive && c2->landparam >= 50 && c2->wall != waWaxWall)
havewhat |= HF_BUG;
if(c2->wall == waThumperOn)
targets.push_back(c2);
}
}
}
while(recalcTide) {
recalcTide = false;
for(int i=0; i<isize(dcal); i++) checkTide(dcal[i]);
}
for(auto& t: tempmonsters) t.first->monst = t.second;
buildAirmap();
}
EX void moverefresh(bool turn IS(true)) {
int dcs = isize(dcal);
for(int i=0; i<dcs; i++) {
cell *c = dcal[i];
if(c->monst == moWolfMoved) c->monst = moWolf;
if(c->monst == moIvyNext) {
c->monst = moIvyHead; ivynext(c);
}
if(c->monst == moIvyDead)
removeIvy(c);
refreshFriend(c);
if(c->monst == moSlimeNextTurn) c->monst = moSlime;
if(c->monst == moLesser && !cellEdgeUnstable(c)) c->monst = moLesserM;
else if(c->monst == moLesserM) c->monst = moLesser;
if(c->monst == moGreater && !cellEdgeUnstable(c)) c->monst = moGreaterM;
else if(c->monst == moGreaterM) c->monst = moGreater;
if(c->monst == moPair && !c->stuntime) {
cell *c2 = c->move(c->mondir);
if(c2->monst != moPair) continue;
if(true) for(int i: {-1, 1}) {
cell *c3 = c->modmove(c->mondir + i);
if(among(c3->wall, waRuinWall, waColumn, waStone, waVinePlant, waPalace)) {
drawParticles(c3, winf[c3->wall].color, 30);
c3->wall = waNone;
}
}
}
if(c->stuntime && !isMutantIvy(c)) {
c->stuntime--;
int breathrange = sphere ? 2 : 3;
if(c->stuntime == 0 && c->monst == moDragonHead) {
// if moDragonHead is renamed to "Dragon Head", we might need to change this
eMonster subject = c->monst;
if(!c->hitpoints) c->hitpoints = 1;
else if(shmup::on && dragon::totalhp(c) > 2 && shmup::dragonbreath(c)) {
c->hitpoints = 0;
}
else if(dragon::totalhp(c) <= 2) ;
else if(isMounted(c)) {
if(dragon::target && celldistance(c, dragon::target) <= breathrange && makeflame(dragon::target, 5, true)) {
addMessage(XLAT("%The1 breathes fire!", subject));
makeflame(dragon::target, 5, false);
playSound(dragon::target, "fire");
c->hitpoints = 0;
}
}
else {
for(int i=0; i<isize(targets); i++) {
cell *t = targets[i];
if(celldistance(c, t) <= breathrange && makeflame(t, 5, true)) {
if(isPlayerOn(t)) addMessage(XLAT("%The1 breathes fire at you!", subject));
else if(t->monst)
addMessage(XLAT("%The1 breathes fire at %the2!", subject, t->monst));
else
addMessage(XLAT("%The1 breathes fire!", subject));
makeflame(t, 5, false);
playSound(t, "fire");
c->hitpoints = 0;
}
}
}
}
}
// tortoises who have found their children no longer move
if(saved_tortoise_on(c))
c->stuntime = 2;
if(c->monst == moReptile) {
if(c->wall == waChasm || cellUnstable(c)) {
c->monst = moNone;
c->wall = waReptile;
c->wparam = reptilemax();
playSound(c, "click");
}
else if(isChasmy(c) || isWatery(c)) {
if(c->wall == waMercury) {
fallMonster(c, AF_FALL);
c->wall = waNone;
}
else {
c->wall = waReptileBridge;
c->wparam = reptilemax();
c->monst = moNone;
}
c->item = itNone;
playSound(c, "click");
}
}
if(c->wall == waChasm) {
if(c->land != laWhirlwind) c->item = itNone;
if(c->monst && !survivesChasm(c->monst) && c->monst != moReptile && normal_gravity_at(c)) {
if(c->monst != moRunDog && c->land == laMotion)
achievement_gain("FALLDEATH1");
addMessage(XLAT("%The1 falls!", c->monst));
fallMonster(c, AF_FALL);
}
}
else if(isReptile(c->wall) && turn) {
if(c->monst || isPlayerOn(c)) c->wparam = -1;
else if(c->cpdist <= 7) {
c->wparam--;
if(c->wparam == 0) {
if(c->wall == waReptile) c->wall = waChasm;
else placeWater(c, NULL);
c->monst = moReptile;
c->hitpoints = 3;
c->stuntime = 0;
int gooddirs[MAX_EDGE], qdirs = 0;
// in the peace mode, a reptile will
// prefer to walk on the ground, rather than the chasm
for(int i=0; i<c->type; i++) {
int i0 = (i+3) % c->type;
int i1 = (i+c->type-3) % c->type;
if(c->move(i0) && passable(c->move(i0), c, 0))
if(c->move(i1) && passable(c->move(i1), c, 0))
gooddirs[qdirs++] = i;
}
if(qdirs) c->mondir = gooddirs[hrand(qdirs)];
playSound(c, "click");
}
}
}
else if(isFire(c)) {
if(c->monst == moSalamander) c->stuntime = max<int>(c->stuntime, 1);
else if(c->monst && !survivesFire(c->monst) && !isWorm(c->monst)) {
addMessage(XLAT("%The1 burns!", c->monst));
if(isBull(c->monst)) {
addMessage(XLAT("Fire is extinguished!"));
c->wall = waNone;
}
fallMonster(c, AF_CRUSH);
}
if(c->item && itemBurns(c->item)) {
addMessage(XLAT("%The1 burns!", c->item));
c->item = itNone;
}
}
else if(isWatery(c)) {
if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
c->monst = moGreaterShark;
if(c->monst && !survivesWater(c->monst) && normal_gravity_at(c)) {
playSound(c, "splash"+pick12());
if(isNonliving(c->monst))
addMessage(XLAT("%The1 sinks!", c->monst));
else
addMessage(XLAT("%The1 drowns!", c->monst));
if(isBull(c->monst)) {
addMessage(XLAT("%The1 is filled!", c->wall));
c->wall = waNone;
}
fallMonster(c, AF_FALL);
}
}
else if(c->wall == waSulphur || c->wall == waSulphurC || c->wall == waMercury) {
if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) {
playSound(c, "splash"+pick12());
if(isNonliving(c->monst))
addMessage(XLAT("%The1 sinks!", c->monst));
else
addMessage(XLAT("%The1 drowns!", c->monst));
if(isBull(c->monst)) {
addMessage(XLAT("%The1 is filled!", c->wall));
c->wall = waNone;
}
fallMonster(c, AF_FALL);
}
}
else if(c->wall == waMagma) {
if(c->monst == moSalamander) c->stuntime = max<int>(c->stuntime, 1);
else if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) {
if(isNonliving(c->monst))
addMessage(XLAT("%The1 is destroyed by lava!", c->monst));
else
addMessage(XLAT("%The1 is killed by lava!", c->monst));
playSound(c, "steamhiss", 70);
fallMonster(c, AF_FALL);
}
}
else if(!isWateryOrBoat(c)) {
if(c->monst == moGreaterShark)
c->monst = moGreaterM;
else if(c->monst == moShark || c->monst == moCShark) {
addMessage(XLAT("%The1 suffocates!", c->monst));
fallMonster(c, AF_CRUSH);
}
else if(c->monst == moKrakenH) {
addMessage(XLAT("%The1 suffocates!", c->monst));
kraken::kill(c, moNone);
}
}
if(c->monst == moVineSpirit && !cellHalfvine(c) && c->wall != waVinePlant) {
addMessage(XLAT("%The1 is destroyed!", c->monst));
fallMonster(c, AF_CRUSH);
}
if(c->monst) mayExplodeMine(c, c->monst);
if(c->monst && c->wall == waClosedGate && !survivesWall(c->monst)) {
playSound(c, "hit-crush"+pick123());
addMessage(XLAT("%The1 is crushed!", c->monst));
fallMonster(c, AF_CRUSH);
}
if(c->monst && cellUnstable(c) && !ignoresPlates(c->monst) && !shmup::on)
doesFallSound(c);
}
}
// find worms and ivies
EX void settemp(cell *c) {
tempmonsters.emplace_back(c, (eMonster) c->monst);
c->monst = moNone;
}
EX void findWormIvy(cell *c) {
while(true) {
if(c->monst == moWorm || c->monst == moTentacle || c->monst == moWormwait || c->monst == moTentaclewait ||
c->monst == moTentacleEscaping) {
worms.push_back(c); settemp(c);
break;
}
else if(c->monst == moHexSnake) {
hexsnakes.push_back(c); settemp(c);
}
else if(c->monst == moWormtail || c->monst == moHexSnakeTail) {
bool bug = true;
for(int i=0; i<c->type; i++) {
cell* c2 = c->move(i);
if(c2 && isWorm(c2) && c2->mondir != NODIR && c2->move(c2->mondir) == c) {
settemp(c);
c = c2;
bug = false;
}
}
if(bug) break;
}
else if(c->monst == moIvyWait) {
cell* c2 = c->move(c->mondir);
settemp(c); c=c2;
}
else if(c->monst == moIvyHead) {
ivies.push_back(c); settemp(c);
break;
}
else if(c->monst == moIvyBranch || c->monst == moIvyRoot) {
bool bug = true;
for(int i=0; i<c->type; i++) {
cell* c2 = c->move(i);
if(c2 && (c2->monst == moIvyHead || c2->monst == moIvyBranch) && c2->move(c2->mondir) == c) {
settemp(c);
c = c2;
bug = false;
}
}
if(bug) break;
}
else break;
}
}
EX void monstersTurn() {
checkSwitch();
mirror::breakAll();
DEBB(DF_TURN, ("bfs"));
bfs();
DEBB(DF_TURN, ("charge"));
if(elec::havecharge) elec::act();
DEBB(DF_TURN, ("mmo"));
int phase2 = (1 & items[itOrbSpeed]);
if(!phase2) movemonsters();
for(int i=0; i<numplayers(); i++) if(playerpos(i)->item == itOrbSafety) {
collectItem(playerpos(i), true);
return;
}
if(playerInPower() && (phase2 || !items[itOrbSpeed]) && (havewhat & HF_FAST))
moveNormals(moWitchSpeed);
if(phase2 && markOrb(itOrbEmpathy)) {
bfs();
movegolems(AF_FAST);
for(int i=0; i<isize(dcal); i++) {
if(dcal[i]->monst == moFriendlyGhost && dcal[i]->stuntime)
dcal[i]->stuntime--;
refreshFriend(dcal[i]);
}
}
DEBB(DF_TURN, ("rop"));
if(!dual::state) reduceOrbPowers();
int phase1 = (1 & items[itOrbSpeed]);
if(dual::state && items[itOrbSpeed]) phase1 = !phase1;
DEBB(DF_TURN, ("lc"));
if(!phase1) livecaves();
if(!phase1) ca::simulate();
if(!phase1) heat::processfires();
for(cell *c: crush_now) {
playSound(NULL, "closegate");
if(canAttack(c, moCrusher, c, c->monst, AF_GETPLAYER | AF_CRUSH)) {
attackMonster(c, AF_MSG | AF_GETPLAYER | AF_CRUSH, moCrusher);
}
moveEffect(movei(c, FALL), moDeadBird);
destroyBoats(c, NULL, true);
explodeBarrel(c);
}
crush_now = move(crush_next);
crush_next.clear();
DEBB(DF_TURN, ("heat"));
heat::processheat();
// if(elec::havecharge) elec::drawcharges();
orbbull::check();
if(!phase1) terracotta::check();
if(items[itOrbFreedom])
for(int i=0; i<numplayers(); i++)
if(multi::playerActive(i))
checkFreedom(playerpos(i));
DEBB(DF_TURN, ("check"));
checkmove();
if(canmove) elec::checklightningfast();
#if CAP_HISTORY
for(int i=0; i<numplayers(); i++)
if(multi::playerActive(i))
history::movehistory.push_back(playerpos(i));
#endif
}
}

8453
game.cpp

File diff suppressed because it is too large Load Diff

View File

@ -976,6 +976,7 @@ EX void drawTerraWarrior(const transmatrix& V, int t, int hp, double footphase)
void drawPlayer(eMonster m, cell *where, const transmatrix& V, color_t col, double footphase, bool stop = false) {
charstyle& cs = getcs();
auto& knighted = camelot::knighted;
if(mapeditor::drawplayer && !mapeditor::drawUserShape(V, mapeditor::sgPlayer, cs.charid, cs.skincolor, where)) {
@ -1219,7 +1220,7 @@ void drawMimic(eMonster m, cell *where, const transmatrix& V, color_t col, doubl
else if(!where || shmup::curtime >= shmup::getPlayer()->nextshot)
queuepoly(VBODY * VBS, cgi.shPKnife, darkena(col, 0, 0XC0));
if(knighted)
if(camelot::knighted)
queuepoly(VBODY3 * VBS, cgi.shKnightCloak, darkena(col, 1, 0xC0));
queuepoly(VHEAD1, (cs.charid&1) ? cgi.shFemaleHair : cgi.shPHead, darkena(col, 1, 0XC0));
@ -3263,7 +3264,7 @@ EX bool placeSidewall(cell *c, int i, int sidepar, const transmatrix& V, color_t
#endif
bool openorsafe(cell *c) {
return c->wall == waMineOpen || mineMarkedSafe(c);
return c->wall == waMineOpen || mine::marked_safe(c);
}
#define Dark(x) darkena(x,0,0xFF)
@ -3989,8 +3990,8 @@ EX void drawMarkers() {
queuecircleat(lmouseover, .8, darkena(lmouseover->cpdist > 1 ? 0x00FFFF : 0xFF0000, 0, 0xFF));
}
if(global_pushto && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) {
queuecircleat(global_pushto, .6, darkena(0xFFD500, 0, 0xFF));
if(pcm.mip.t && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) {
queuecircleat(pcm.mip.t, .6, darkena(0xFFD500, 0, 0xFF));
}
#endif

View File

@ -885,7 +885,7 @@ EX void describeMouseover() {
out += XLAT1(iinf[c->item].name);
if(c->item == itBarrow) out += " (x" + its(c->landparam) + ")";
if(c->land == laHunting) {
int i = ambushSize(c, c->item);
int i = ambush::size(c, c->item);
if(i) out += " (" + XLAT("ambush:") + " " + its(i) + ")";
}
if(c->item == itBabyTortoise && tortoise::seek())

View File

@ -55,6 +55,14 @@
#include "complex2.cpp"
#include "savemem.cpp"
#include "game.cpp"
#include "passable.cpp"
#include "checkmove.cpp"
#include "pcmove.cpp"
#include "environment.cpp"
#include "monstermove.cpp"
#include "mapeffects.cpp"
#include "attack.cpp"
#include "items.cpp"
#include "orbgen.cpp"
#include "monstergen.cpp"
#include "landlock.cpp"

View File

@ -410,7 +410,6 @@ typedef function<int(struct cell*)> cellfunction;
#define PT(x, y) ((tactic::on || quotient == 2 || daily::on) ? (y) : inv::on ? min(2*(y),x) : (x))
#define ROCKSNAKELENGTH 50
#define WORMLENGTH 15
#define PUREHARDCORE_LEVEL 10
#define PRIZEMUL 7
#define INF 9999

631
items.cpp Normal file
View File

@ -0,0 +1,631 @@
// Hyperbolic Rogue - items
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file item.cpp
* \brief routines related to items
*/
#include "hyper.h"
namespace hr {
EX int currentLocalTreasure;
/** for treasures, the number collected; for orbs, the number of charges */
EX array<int, ittypes> items;
EX map<modecode_t, array<int, ittypes> > hiitems;
EX bool cannotPickupItem(cell *c, bool telekinesis) {
return itemHidden(c) && !telekinesis && !(isWatery(c) && markOrb(itOrbFish));
}
EX bool canPickupItemWithMagnetism(cell *c, cell *from) {
if(!c->item || c->item == itOrbYendor || isWall(c) || cannotPickupItem(c, false))
return false;
if(c->item == itCompass && from->item)
return false;
return true;
}
EX bool doPickupItemsWithMagnetism(cell *c) {
cell *csaf = NULL;
if(items[itOrbMagnetism])
forCellEx(c3, c) if(canPickupItemWithMagnetism(c3, c)) {
if(c3->item == itCompass) {
if(!c->item)
moveItem(c3, c, false);
}
else if(c3->item == itOrbSafety || c3->item == itBuggy || c3->item == itBuggy2)
csaf = c3;
else if(markOrb(itOrbMagnetism))
collectItem(c3, false);
}
if(csaf)
return collectItem(csaf, false);
return false;
}
EX void pickupMovedItems(cell *c) {
if(!c->item) return;
if(c->item == itOrbSafety) return;
if(isPlayerOn(c)) collectItem(c, true);
if(items[itOrbMagnetism])
forCellEx(c2, c)
if(isPlayerOn(c2) && canPickupItemWithMagnetism(c, c2))
collectItem(c, true);
}
EX bool collectItem(cell *c2, bool telekinesis IS(false)) {
bool dopickup = true;
bool had_choice = false;
if(cannotPickupItem(c2, telekinesis))
return false;
/* if(c2->item == itHolyGrail && telekinesis)
return false; */
if(c2->item) {
invismove = false;
if(shmup::on) shmup::visibleFor(2000);
string s0 = "";
if(c2->item == itPalace && items[c2->item] == 12)
princess::forceVizier = true;
if(!cantGetGrimoire(c2, false)) collectMessage(c2, c2->item);
if(c2->item == itDodeca && peace::on) peace::simon::extend();
}
if(c2->land == laHunting && c2->item && !inv::activating) {
int dogs = ambush::ambush(c2, c2->item);
if(dogs)
addMessage(XLAT("You are ambushed!"));
}
if(isRevivalOrb(c2->item) && multi::revive_queue.size()) {
multiRevival(cwt.at, c2);
}
else if(isShmupLifeOrb(c2->item) && shmup::on) {
playSound(c2, "pickup-orb"); // TODO summon
gainLife();
}
else if(orbcharges(c2->item)) {
eItem it = c2->item;
if(it == itOrbRecall && !dual::state) {
cellwalker cw2 = cwt;
cw2++;
cw2.at = c2;
saveRecall(cw2);
}
if(it == itOrbFire) playSound(c2, "fire");
else if(it == itOrbFire) playSound(c2, "fire");
else if(it == itOrbWinter) playSound(c2, "pickup-winter");
else if(it == itOrbSpeed) playSound(c2, "pickup-speed");
else if(it == itRevolver) playSound(c2, "pickup-key");
else playSound(c2, "pickup-orb");
if(items[itOrbChoice]) items[itOrbChoice] = 0, had_choice = true;
int oc = orbcharges(it);
if(dual::state && among(it, itOrbTeleport, itOrbFrog, itOrbPhasing, itOrbDash, itOrbRecall)) {
oc = 10;
it = itOrbSpeed;
}
if(c2->land == laAsteroids) oc = 10;
if(markOrb(itOrbIntensity)) oc = intensify(oc);
if(!items[it]) items[it]++;
items[it] += oc;
}
else if(c2->item == itOrbLife) {
playSound(c2, "pickup-orb"); // TODO summon
placeGolem(cwt.at, c2, moGolem);
}
else if(c2->item == itOrbFriend) {
playSound(c2, "pickup-orb"); // TODO summon
placeGolem(cwt.at, c2, moTameBomberbird);
}
#if CAP_TOUR
else if(tour::on && (c2->item == itOrbSafety || c2->item == itOrbRecall)) {
addMessage(XLAT("This Orb is not compatible with the Tutorial."));
return true;
}
#endif
else if(c2->item == itOrbSafety) {
playSound(c2, "pickup-orb"); // TODO safety
if(!dual::state) items[c2->item] = 7;
if(shmup::on)
shmup::delayed_safety = true;
else
activateSafety(c2->land);
return true;
}
else if(c2->item == itBabyTortoise) {
using namespace tortoise;
int bnew = babymap[c2];
babymap.erase(c2);
int bold = seekbits;
seekbits = bnew;
tortoise::last = seekbits;
if(seek()) {
cell *c = passable(cwt.at, NULL, 0) ? cwt.at : c2;
c->item = itBabyTortoise;
if(c == c2) dopickup = false;
babymap[c] = bold;
}
else items[itBabyTortoise]++;
}
else if(c2->item == itOrbYendor && peace::on) {
if(!items[itDodeca]) {
addMessage(XLAT("Collect as many Dodecahedra as you can, then return here!"));
}
else {
addMessage(XLAT("Your score: %1", its(items[itDodeca])));
peace::simon::restore();
}
dopickup = false;
}
else if(c2->item == itOrbYendor && yendor::state(c2) != yendor::ysUnlocked) {
dopickup = false;
}
else if(c2->item == itOrbYendor)
yendor::collected(c2);
else if(c2->item == itHolyGrail) {
playSound(c2, "tada");
int v = newRoundTableRadius() + 12;
items[itOrbTeleport] += v;
items[itOrbSpeed] += v;
items[itHolyGrail]++;
addMessage(XLAT("Congratulations! You have found the Holy Grail!"));
if(!eubinary) c2->master->alt->emeraldval |= GRAIL_FOUND;
achievement_collection(c2->item);
}
else if(c2->item == itKey) {
playSound(c2, "pickup-key");
for(int i=0; i<isize(yendor::yi); i++) if(yendor::yi[i].actual_key() == c2)
yendor::yi[i].found = true;
items[itKey]++;
}
else if(!telekinesis && cantGetGrimoire(c2)) {
// telekinesis checks the condition earlier
dopickup = false;
}
else if(c2->item == itCompass) {
dopickup = false;
}
else if(c2->item == itBuggy || c2->item == itBuggy2) {
items[itOrbSafety] += 7;
if(shmup::on)
shmup::delayed_safety = true;
else {
buggyGeneration = false;
activateSafety(laCrossroads);
}
return true;
}
else if(c2->item == itTreat) {
playSound(c2, "pickup-scroll");
halloween::getTreat(c2);
}
else {
if(c2->item == itBarrow)
for(int i=0; i<c2->landparam; i++) gainItem(c2->item);
else if(c2->item) gainItem(c2->item);
if(c2->item) {
char ch = iinf[c2->item].glyph;
if(ch == '*') playSound(c2, "pickup-gem");
else if(ch == '$' || ch == 'x') playSound(c2, "pickup-gold");
else if(ch == '%' || ch == ';') playSound(c2, "pickup-potion");
else playSound(c2, "pickup-scroll");
}
}
if(dopickup && c2->item) {
#if CAP_HISTORY
// temporary variable to avoid the "cannot bind bitfield" problem in C++11
eItem dummy = c2->item;
history::findhistory.emplace_back(c2, dummy);
#endif
if(c2->item == itBombEgg && c2->land == laMinefield) {
c2->landparam |= 2;
c2->landparam &= ~1;
}
if(!had_choice)
c2->item = itNone;
}
// if(c2->land == laHive)
// c2->heat = 1;
int numOrb = 0;
for(int i=0; i<ittypes; i++)
if(itemclass(eItem(i)) == IC_ORB && items[i])
numOrb++;
if(numOrb) achievement_count("ORB", numOrb, 0);
if(princess::reviveAt && gold(NO_LOVE) >= princess::reviveAt && !inv::on) {
princess::reviveAt = 0,
items[itSavedPrincess] = 1;
addMessage("You have enough treasure now to revive the Princess!");
}
return false;
}
EX void glance_message() {
if(gold() >= 300)
addMessage(XLAT("You feel great, like a true treasure hunter."));
else if(gold() >= 200)
addMessage(XLAT("Your eyes shine like gems."));
else if(gold() >= 100)
addMessage(XLAT("Your eyes shine as you glance at your precious treasures."));
else if(gold() >= 50)
addMessage(XLAT("You glance at your great treasures."));
else if(gold() >= 10)
addMessage(XLAT("You glance at your precious treasures."));
else if(gold() > 0)
addMessage(XLAT("You glance at your precious treasure."));
else
addMessage(XLAT("Your inventory is empty."));
}
EX void dropGreenStone(cell *c) {
if(items[itGreenStone] && !passable(c, NULL, P_MONSTER)) {
// NOTE: PL/CZ translations assume that itGreenStone is dropped to avoid extra forms!
addMessage(XLAT("Cannot drop %the1 here!", itGreenStone));
return;
}
if(items[itGreenStone] && c->item == itNone) {
items[itGreenStone]--;
if(false) {
c->item = itNone;
spill(c, eWall(c->wall ^ waFloorA ^ waFloorB), 3);
addMessage(XLAT("The slime reacts with %the1!", itGreenStone));
}
else {
c->item = itGreenStone;
addMessage(XLAT("You drop %the1.", itGreenStone));
if(isHaunted(cwt.at->land)) survivalist = false;
}
}
else {
if(items[itGreenStone] && c->item == itGreenStone)
addMessage(XLAT("You juggle the Dead Orbs."));
else if(items[itGreenStone] && c->item)
addMessage(XLAT("You give %the1 a grim look.", c->item));
else if(items[itGreenStone]) {
addMessage(XLAT("Cannot drop %the1 here!", itGreenStone));
return;
}
else glance_message();
}
}
EX void moveItem1(cell *from, cell *to, bool activateYendor) {
if(from->item == itOrbYendor) {
bool xnew = true;
for(int i=0; i<isize(yendor::yi); i++)
if(yendor::yi[i].path[0] == from) xnew = false;
if(xnew && activateYendor) yendor::check(from);
for(int i=0; i<isize(yendor::yi); i++)
if(yendor::yi[i].path[0] == from)
yendor::yi[i].path[0] = to;
}
if(from->item == itKey) {
for(int i=0; i<isize(yendor::yi); i++) if(yendor::yi[i].path[YDIST-1] == from)
yendor::yi[i].path[YDIST-1] = to;
for(int i=0; i<isize(yendor::yi); i++) if(yendor::yi[i].actualKey == from)
yendor::yi[i].actualKey = to;
}
if(from->item == itBabyTortoise) {
tortoise::babymap[to] = tortoise::babymap[from];
tortoise::babymap.erase(from);
}
eItem i = to->item;
to->item = from->item;
from->item = i;
}
EX void moveItem (cell *from, cell *to, bool activateYendor) {
static cell dummy;
dummy.item = itNone;
moveItem1(from, &dummy, activateYendor);
moveItem1(to, from, activateYendor);
moveItem1(&dummy, to, activateYendor);
}
EX bool itemHidden(cell *c) {
return isWatery(c) && !(shmup::on && shmup::boatAt(c));
}
EX eItem localTreasureType() {
lastland = singlepos()->land;
return treasureType(lastland);
}
EX void countLocalTreasure() {
eItem i = localTreasureType();
currentLocalTreasure = i ? items[i] : 0;
if(i != itHyperstone) for(int i=0; i<isize(dcal); i++) {
cell *c2 = dcal[i];
if(c2->cpdist > 3) break;
eItem i2 = treasureType(c2->land);
if(i2 && items[i2] < currentLocalTreasure)
currentLocalTreasure = items[i2];
}
}
#if HDR
static const int NO_TREASURE = 1;
static const int NO_YENDOR = 2;
static const int NO_GRAIL = 4;
static const int NO_LOVE = 8;
#endif
EX int gold(int no IS(0)) {
int i = 0;
if(!(no & NO_YENDOR)) i += items[itOrbYendor] * 50;
if(!(no & NO_GRAIL)) i += items[itHolyGrail] * 10;
if(!(no & NO_LOVE)) {
bool love = items[itOrbLove];
#if CAP_INV
if(inv::on && inv::remaining[itOrbLove])
love = true;
#endif
#if CAP_DAILY
if(daily::on) love = false;
#endif
if(love) i += 30;
}
if(!(no & NO_TREASURE))
for(int t=0; t<ittypes; t++)
if(itemclass(eItem(t)) == IC_TREASURE)
i += items[t];
return i;
}
EX int maxgold() {
int mg = 0;
for(int i=0; i<ittypes; i++)
if(itemclass(eItem(i)) == IC_TREASURE && items[i] > mg)
mg = items[i];
return mg;
}
EX void updateHi(eItem it, int v) {
if(!yendor::on)
if(v > hiitems[modecode()][it]) hiitems[modecode()][it] = v;
}
EX void gainItem(eItem it) {
int g = gold();
bool lhu = landUnlocked(laHell);
items[it]++; if(it != itLotus) updateHi(it, items[it]);
if(it == itRevolver && items[it] > 6) items[it] = 6;
achievement_collection(it);
multi::treasures[multi::cpid]++;
#if CAP_DAILY
if(daily::on) achievement_final(false);
#endif
int g2 = gold();
if(items[itFireShard] && items[itAirShard] && items[itWaterShard] && items[itEarthShard]) {
items[itFireShard]--;
items[itAirShard]--;
items[itWaterShard]--;
items[itEarthShard]--;
gainItem(itElemental);
gainItem(itElemental);
gainItem(itElemental);
gainItem(itElemental);
addMessage(XLAT("You construct some Elemental Gems!", it) + itemcounter(items[itElemental]));
}
if(it == itBounty)
items[itRevolver] = 6;
if(it == itHyperstone && items[itHyperstone] == 10)
achievement_victory(true);
if(chaosmode && gold() >= 300 && !chaosAchieved) {
achievement_gain("CHAOS", rg::chaos);
chaosAchieved = true;
}
#if ISMOBILE==1
if(g < lastsafety + R30*3/2 && g2 >= lastsafety + R30*3/2)
addMessage(XLAT("The Orb of Safety from the Land of Eternal Motion might save you."));
#endif
#define IF(x) if(g < (x) && g2 >= x && !peace::on)
IF(R60/4)
addMessage(XLAT("Collect treasure to access more different lands..."));
IF(R30)
addMessage(XLAT("You feel that you have enough treasure to access new lands!"));
IF(R30*3/2)
addMessage(XLAT("Collect more treasures, there are still more lands waiting..."));
IF(R60)
addMessage(XLAT("You feel that the stars are right, and you can access R'Lyeh!"));
IF(R30*5/2)
addMessage(XLAT("Kill monsters and collect treasures, and you may get access to Hell..."));
IF(R10 * 9)
addMessage(XLAT("To access Hell, collect %1 treasures each of 9 kinds...", its(R10)));
if(landUnlocked(laHell) && !lhu) {
addMessage(XLAT("Abandon all hope, the gates of Hell are opened!"));
addMessage(XLAT("And the Orbs of Yendor await!"));
}
}
EX string itemcounter(int qty) {
string s = ""; s += " ("; s += its(qty); s += ")"; return s;
}
EX void gainShard(cell *c2, const char *msg) {
invismove = false;
string s = XLAT(msg);
if(is_mirrorland(c2) && !peace::on) {
collectMessage(c2, itShard);
gainItem(itShard);
s += itemcounter(items[itShard]);
}
addMessage(s);
c2->wall = waNone;
invismove = false;
}
EX void placeItems(int qty, eItem it) {
int dcs = isize(dcal);
for(int i=1; qty && i<dcs; i++) {
cell *c = dcal[i];
if(!c->monst && !c->item && passable(c, NULL, 0))
c->item = it, qty--;
}
}
EX bool cantGetGrimoire(cell *c2, bool verbose IS(true)) {
if(chaosmode) return false;
if(!eubinary && !c2->master->alt) return false;
if(c2->item == itGrimoire && items[itGrimoire] > celldistAlt(c2)/-TEMPLE_EACH) {
if(verbose)
addMessage(XLAT("You already have this Grimoire! Seek new tomes in the inner circles."));
return true;
}
return false;
}
EX void gainLife() {
items[itOrbLife] ++;
if(items[itOrbLife] > 5 && !inv::on) items[itOrbLife] = 5;
}
EX void collectMessage(cell *c2, eItem which) {
bool specialmode = yendor::on || princess::challenge || cheater || !in_full_game();
if(which == itDodeca && peace::on) return;
if(which == itTreat) ;
else if(isElementalShard(which)) {
int tsh =
items[itFireShard] + items[itAirShard] + items[itWaterShard] + items[itEarthShard] +
items[itElemental];
if(tsh == 0) {
addMessage(XLAT("Collect four different Elemental Shards!"));
addMessage(XLAT("Unbalanced shards in your inventory are dangerous."));
}
else {
string t = XLAT("You collect %the1. (%2)", which, its(items[which]+1));
addMessage(t);
}
}
else if(which == itKey) {
addMessage(XLAT("You have found the Key! Now unlock this Orb of Yendor!"));
}
else if(which == itGreenStone && !items[itGreenStone])
addMessage(XLAT("This orb is dead..."));
else if(which == itGreenStone)
addMessage(XLAT("Another Dead Orb."));
else if(itemclass(which) != IC_TREASURE) {
if(!inv::activating)
addMessage(XLAT("You have found %the1!", which));
}
else if(which == itBabyTortoise) {
playSound(c2, playergender() ? "speak-princess" : "speak-prince");
addMessage(XLAT("Aww, poor %1... where is your family?", which));
}
else if(gold() == 0 && !specialmode)
addMessage(XLAT("Wow! %1! This trip should be worth it!", which));
else if(gold() == 1 && !specialmode)
addMessage(XLAT("For now, collect as much treasure as possible..."));
else if(gold() == 2 && !specialmode)
addMessage(XLAT("Prove yourself here, then find new lands, with new quests..."));
else if(!items[which] && itemclass(which) == IC_TREASURE)
addMessage(XLAT("You collect your first %1!", which));
else if(items[which] == 4 && maxgold() == U5-1 && !specialmode) {
addMessage(XLAT("You feel that %the2 become%s2 more dangerous.", which, c2->land));
addMessage(XLAT("With each %1 you collect...", which, c2->land));
}
else if(items[which] == 9 && maxgold() == 9 && !specialmode) {
if(inv::on) {
addMessage(XLAT("The treasure gives your magical powers!", c2->land));
if(!ISMOBILE)
addMessage(XLAT("Press 'i' to access your magical powers.", c2->land));
}
else
addMessage(XLAT("Are there any magical orbs in %the1?...", c2->land));
}
else if(items[which] == R10 && maxgold() == R10 && !specialmode && !inv::on) {
addMessage(XLAT("You feel that %the1 slowly become%s1 dangerous...", c2->land));
addMessage(XLAT("Better find some other place."));
}
else if(which == itHunting && items[itHunting] == 4 && !specialmode && !ISMOBWEB)
addMessage(XLAT("Hint: hold Alt to highlights enemies and other important features."));
else if(which == itSpice && items[itSpice] == U10*7/10 && !specialmode && isLandIngame(laHell))
addMessage(XLAT("You have a vision of the future, fighting demons in Hell..."));
else if(which == itSpice && items[itSpice] == U10-1 && !specialmode && isLandIngame(laRedRock))
addMessage(XLAT("You will be fighting red rock snakes, too..."));
else if(which == itKraken && items[itKraken] == U10-1 && !specialmode)
addMessage(XLAT("You feel that a magical weapon is waiting for you..."));
// else if(which == itFeather && items[itFeather] == 10)
// addMessage(XLAT("There should be a Palace somewhere nearby..."));
else if(which == itElixir && items[itElixir] == U5-1 && !specialmode)
addMessage(XLAT("With this Elixir, your life should be long and prosperous..."));
else if(which == itRuby && items[itRuby] == U5-1 && !specialmode && isLandIngame(laMountain)) {
addMessage(XLAT("You feel something strange about gravity here..."));
}
else if(which == itPalace && items[itPalace] == U5-1 && !specialmode && isLandIngame(laDungeon)) {
addMessage(XLAT("The rug depicts a man in a deep dungeon, unable to leave."));
}
else if(which == itFeather && items[itFeather] == 25-1 && !specialmode && inv::on && !chaosmode)
addMessage(XLAT("You feel the presence of free saves on the Crossroads."));
else if(which == itHell && items[itHell] == 25-1 && !specialmode && inv::on && !chaosmode)
addMessage(XLAT("You feel the Orbs of Yendor nearby..."));
else if(which == itHell && items[itHell] == 50-1 && !specialmode && inv::on && !chaosmode)
addMessage(XLAT("You feel the Orbs of Yendor in the Crossroads..."));
else if(which == itHell && items[itHell] == 100-1 && !specialmode && inv::on && !chaosmode)
addMessage(XLAT("You feel the Orbs of Yendor everywhere..."));
else if(which == itBone && items[itBone] % 25 == 24 && !specialmode && inv::on)
addMessage(XLAT("You have gained an offensive power!"));
else if(which == itHell && items[itHell] >= 100 && items[itHell] % 25 == 24 && !specialmode && inv::on)
addMessage(XLAT("A small reward for braving the Hell."));
else if(which == itIvory && items[itIvory] == U5-1 && !specialmode && (isLandIngame(laMountain) || isLandIngame(laDungeon))) {
addMessage(XLAT("You feel attuned to gravity, ready to face mountains and dungeons."));
}
else if(which == itBone && items[itBone] == U5+1 && !specialmode && isLandIngame(laHell))
addMessage(XLAT("The Necromancer's Totem contains hellish incantations..."));
else if(which == itStatue && items[itStatue] == U5+1 && !specialmode)
addMessage(XLAT("The inscriptions on the Statue of Cthulhu point you toward your destiny..."));
else if(which == itStatue && items[itStatue] == U5-1 && isLandIngame(laTemple))
addMessage(XLAT("There must be some temples of Cthulhu in R'Lyeh..."));
else if(which == itDiamond && items[itDiamond] == U10-2 && !specialmode)
addMessage(XLAT("Still, even greater treasures lie ahead..."));
else if(which == itFernFlower && items[itFernFlower] == U5-1 && isLandIngame(laEmerald))
addMessage(XLAT("You overheard Hedgehog Warriors talking about emeralds..."));
else if(which == itEmerald && items[itEmerald] == U5-1 && !specialmode && isLandIngame(laCamelot))
addMessage(XLAT("You overhear miners talking about a castle..."));
else if(which == itEmerald && items[itEmerald] == U5 && !specialmode && isLandIngame(laCamelot))
addMessage(XLAT("A castle in the Crossroads..."));
else if(which == itShard) ;
else {
int qty = (which == itBarrow) ? c2->landparam : 1;
string t;
if(which == itBarrow && items[which] < 25 && items[which] + qty >= 25)
t = XLAT("Your energy swords get stronger!");
else if(maxgold() < 25 && items[which] + qty >= 25)
t = XLAT("You feel even more attuned to the magic of this land!");
else t = XLAT("You collect %the1. (%2)", which, its(items[which]+qty));
addMessage(t);
}
}
EX bool itemHiddenFromSight(cell *c) {
return isWatery(c) && !items[itOrbInvis] && !(items[itOrbFish] && playerInWater())
&& !(shmup::on && shmup::boatAt(c));
}
}

View File

@ -12,6 +12,8 @@ namespace hr {
// land generation routines
EX int explore[10], exploreland[10][landtypes], landcount[landtypes];
EX bool safety = false;
EX eLand lastland;
@ -1118,7 +1120,7 @@ EX void giantLandSwitch(cell *c, int d, cell *from) {
createArrowTrapAt(c, laTerracotta);
if(pseudohept(c) && hrand(100) < 40 && c->wall == waNone && !racing::on) {
c->wall = waTerraWarrior;
c->landparam = randterra ? 0 : 3 + hrand(3);
c->landparam = terracotta::randterra ? 0 : 3 + hrand(3);
if(hrand(100) < items[itTerra]-10)
c->landparam--;
if(hrand(100) < items[itTerra]-10)
@ -2430,8 +2432,8 @@ EX void giantLandSwitch(cell *c, int d, cell *from) {
if(fargen) {
int treasure_rate = 2;
for(int i=0; i<21; i++) if((b>>i) & 1) {
treasure_rate += variant_features[i].rate_change;
variant_features[i].build(c);
treasure_rate += variant::features[i].rate_change;
variant::features[i].build(c);
}
if(hrand(2000 - PT(kills[moVariantWarrior] * 5, 250)) < treasure_rate && !c->wall && !c->monst)
c->item = itVarTreasure;

View File

@ -8,6 +8,18 @@
#include "hyper.h"
namespace hr {
EX bool in_full_game() {
if(tactic::on) return false;
if(princess::challenge) return false;
if(chaosmode) return true;
if(euclid && isCrossroads(specialland)) return true;
if(weirdhyperbolic && specialland == laCrossroads4) return true;
if(cryst && isCrossroads(specialland)) return true;
if((in_s2xe() || nonisotropic || (hybri && hybrid::under_class() != gcSphere)) && isCrossroads(specialland)) return true;
if(geometry == gNormal && !NONSTDVAR) return true;
return false;
}
EX bool nodisplay(eMonster m) {
return
m == moIvyDead ||

929
mapeffects.cpp Normal file
View File

@ -0,0 +1,929 @@
// Hyperbolic Rogue - Map effects
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file mapeffects.cpp
* \brief Routines handling the map effects
*/
#include "hyper.h"
namespace hr {
/** offscreen cells to take care off */
EX vector<cell*> offscreen;
EX void initcell(cell *c) {
c->mpdist = INFD; // minimum distance from the player, ever
c->cpdist = INFD; // current distance from the player
c->pathdist = PINFD;// current distance from the player, along paths (used by yetis)
c->landparam = 0; c->landflags = 0; c->wparam = 0;
c->listindex = -1;
c->wall = waNone;
c->item = itNone;
c->monst = moNone;
c->bardir = NODIR;
c->mondir = NODIR;
c->barleft = c->barright = laNone;
c->land = laNone;
c->ligon = 0;
c->stuntime = 0;
c->monmirror = 0;
}
EX bool doesnotFall(cell *c) {
if(c->wall == waChasm) return false;
else if(cellUnstable(c) && !in_gravity_zone(c)) {
fallingFloorAnimation(c);
c->wall = waChasm;
return false;
}
return true;
}
EX bool doesFall(cell *c) { return !doesnotFall(c); }
EX bool doesFallSound(cell *c) {
if(c->land != laMotion && c->land != laZebra)
playSound(c, "trapdoor");
return !doesnotFall(c);
}
EX void destroyBoats(cell *c, cell *c2, bool strandedToo) {
if(c->wall == waBoat) placeWater(c, c2);
if(strandedToo && c->wall == waStrandedBoat) c->wall = waNone;
shmup::destroyBoats(c);
}
#if HDR
enum eGravity { gsNormal, gsLevitation, gsAnti };
#endif
EX eGravity gravity_state, last_gravity_state;
EX bool bird_disruption(cell *c) {
return c->cpdist <= 5 && items[itOrbGravity];
}
EX bool in_gravity_zone(cell *c) {
return gravity_state && c->cpdist <= 5;
}
EX int gravity_zone_diff(cell *c) {
if(in_gravity_zone(c)) {
if(gravity_state == gsLevitation) return 0;
if(gravity_state == gsAnti) return -1;
}
return 1;
}
EX bool isJWall(cell *c) {
return isWall(c) || c->monst == passive_switch;
}
EX eGravity get_static_gravity(cell *c) {
if(isGravityLand(c->land))
return gsLevitation;
if(among(c->wall, waArrowTrap, waFireTrap, waClosePlate, waOpenPlate, waTrapdoor))
return gsNormal;
forCellEx(c2, c) if(isJWall(c2))
return gsAnti;
if(isWatery(c) || isChasmy(c) || among(c->wall, waMagma, waMineUnknown, waMineMine, waMineOpen))
return gsLevitation;
return gsNormal;
}
EX eGravity get_move_gravity(cell *c, cell *c2) {
if(isGravityLand(c->land) && isGravityLand(c2->land)) {
int d = gravityLevelDiff(c, c2);
if(d > 0) return gsNormal;
if(d == 0) return gsLevitation;
if(d < 0) return gsAnti;
return gsNormal;
}
else {
if(snakelevel(c) != snakelevel(c2)) {
int d = snakelevel(c2) - snakelevel(c);
if(d > 0) return gsAnti;
if(d == -3) return gsLevitation;
return gsNormal;
}
forCellEx(c3, c) if(isJWall(c3))
return gsAnti;
forCellEx(c3, c2) if(isJWall(c3))
return gsAnti;
if(isWatery(c2) && c->wall == waBoat && !againstCurrent(c2, c))
return gsNormal;
if(isWatery(c2) || isChasmy(c2) || among(c2->wall, waMagma, waMineUnknown, waMineMine, waMineOpen) || anti_alchemy(c2, c))
return gsLevitation;
return gsNormal;
}
}
EX bool isWarped(cell *c) {
return isWarpedType(c->land) || (!inmirrororwall(c->land) && (items[itOrb37] && c->cpdist <= 4));
}
EX bool nonAdjacent(cell *c, cell *c2) {
if(isWarped(c) && isWarped(c2) && warptype(c) == warptype(c2)) {
/* int i = neighborId(c, c2);
cell *c3 = c->modmove(i+1), *c4 = c->modmove(i-1);
if(c3 && !isTrihepta(c3)) return false;
if(c4 && !isTrihepta(c4)) return false; */
return true;
}
return false;
}
EX bool nonAdjacentPlayer(cell *c, cell *c2) {
return nonAdjacent(c, c2) && !markOrb(itOrb37);
}
EX bool thruVine(cell *c, cell *c2) {
return (cellHalfvine(c) && c2->wall == c->wall && c2 != c);
// ((c->wall == waFloorC || c->wall == waFloorD) && c2->wall == c->wall && !c2->item && !c->item);
}
EX void useup(cell *c) {
c->wparam--;
if(c->wparam == 0) {
drawParticles(c, c->wall == waFire ? 0xC00000 : winf[c->wall].color, 10, 50);
if(c->wall == waTempFloor)
c->wall = waChasm;
else if(c->wall == waTempBridge || c->wall == waTempBridgeBlocked || c->wall == waBurningDock || c->land == laBrownian)
placeWater(c, c);
else {
c->wall = c->land == laCaribbean ? waCIsland2 : waNone;
}
}
}
EX bool earthFloor(cell *c) {
if(c->monst) return false;
if(c->wall == waDeadwall) { c->wall = waDeadfloor; return true; }
if(c->wall == waDune) { c->wall = waNone; return true; }
if(c->wall == waStone && c->land != laTerracotta) { c->wall = waNone; return true; }
if(c->wall == waAncientGrave || c->wall == waFreshGrave || c->wall == waRuinWall) {
c->wall = waNone;
return true;
}
if((c->wall == waSea || c->wall == waNone) && c->land == laOcean) {
c->wall = waCIsland;
return true;
}
if(c->wall == waSea && c->land == laCaribbean) {
c->wall = waCIsland;
return true;
}
if(c->wall == waSea && c->land == laWarpSea)
c->wall = waNone;
if(c->wall == waBoat && c->land == laWarpSea)
c->wall = waStrandedBoat;
if(c->wall == waMercury) {
c->wall = waNone;
return true;
}
if((c->wall == waBarrowDig || c->wall == waBarrowWall) && c->land == laBurial) {
c->item = itNone;
c->wall = waNone;
return true;
}
if(c->wall == waPlatform && c->land == laMountain) {
c->wall = waNone;
return true;
}
if(c->wall == waChasm && c->land == laHunting) {
c->wall = waNone;
return true;
}
return false;
}
EX bool earthWall(cell *c) {
if(c->wall == waDeadfloor || c->wall == waDeadfloor2 || c->wall == waEarthD) {
c->item = itNone;
c->wall = waDeadwall;
return true;
}
if(c->wall == waNone && c->land == laMountain) {
c->wall = waPlatform;
return true;
}
if(c->wall == waNone && c->land == laDesert) {
c->item = itNone;
c->wall = waDune;
return true;
}
if(c->wall == waNone && c->land == laRuins) {
c->item = itNone;
c->wall = waRuinWall;
return true;
}
if(c->wall == waNone && isElemental(c->land)) {
c->item = itNone;
c->wall = waStone;
return true;
}
if(c->wall == waNone && c->land == laRedRock) {
c->item = itNone;
c->wall = waRed3;
return true;
}
if(c->wall == waNone && c->land == laSnakeNest) {
c->item = itNone;
c->wall = waRed3;
return true;
}
if(c->wall == waNone && c->land == laBurial) {
c->item = itNone;
c->wall = waBarrowDig;
return true;
}
if(c->wall == waNone && c->land == laHunting) {
c->item = itNone;
c->wall = waChasm;
return true;
}
if(c->wall == waNone && c->land == laTerracotta) {
c->wall = waMercury;
return true;
}
if(c->wall == waArrowTrap && c->land == laTerracotta) {
destroyTrapsOn(c);
c->wall = waMercury;
return true;
}
if(c->wall == waCIsland || c->wall == waCIsland2 || (c->wall == waNone && c->land == laOcean)) {
c->item = itNone;
c->wall = waSea;
if(c->land == laOcean) c->landparam = 40;
return true;
}
return false;
}
EX bool snakepile(cell *c, eMonster m) {
if(c->wall == waSea && c->land == laOcean) {
c->land = laBrownian, c->landparam = 0;
}
if(c->land == laWestWall) return false;
if(c->land == laBrownian) {
if(c->wall == waNone) {
#if CAP_COMPLEX2
c->landparam += brownian::level;
#endif
return true;
}
if(c->wall == waSea || c->wall == waBoat) {
c->wall = waNone;
c->landparam++;
return true;
}
}
if(c->item && c->wall != waRed3) c->item = itNone;
if(c->wall == waRed1 || c->wall == waOpenGate) c->wall = waRed2;
else if(c->wall == waRed2) c->wall = waRed3;
else if(doesFall(c)) return false;
else if((c->wall == waSea && c->land == laLivefjord))
c->wall = waNone;
else if((c->wall == waSea && isWarpedType(c->land)))
c->wall = waNone;
else if(isGravityLand(c->land)) {
if(m == moHexSnake)
c->wall = waPlatform;
else
c->wall = waDeadTroll2;
}
else if(c->wall == waNone || isAlchAny(c) ||
c->wall == waCIsland || c->wall == waCIsland2 ||
c->wall == waOpenPlate || c->wall == waClosePlate ||
c->wall == waMineUnknown || c->wall == waMineOpen || isReptile(c->wall)) {
if(isReptile(c->wall)) kills[moReptile]++;
c->wall = waRed1;
if(among(m, moDarkTroll, moBrownBug)) c->wall = waDeadfloor2;
}
else if(c->wall == waDeadfloor)
c->wall = waDeadfloor2;
else if(c->wall == waDeadfloor2) {
if(m == moDarkTroll && c->land == laDeadCaves) return false;
else
c->wall = waDeadwall;
}
else if(c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waGargoyleBridge ||
c->wall == waTempFloor || c->wall == waTempBridge || c->wall == waPetrifiedBridge) {
if(c->land == laWhirlpool) return false;
c->wall = waRed2;
if(m == moDarkTroll) c->wall = waDeadwall;
}
else if(c->wall == waCavefloor) c->wall = waCavewall;
else if(c->wall == waSea && c->land == laCaribbean) c->wall = waCIsland;
else if(c->wall == waSea && c->land == laWhirlpool) return false;
else if(c->wall == waSea) c->wall = waNone;
else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone;
else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone;
else if(cellHalfvine(c)) {
destroyHalfvine(c, waRed1);
if(c->wall == waRed1 && m == moDarkTroll) c->wall = waDeadfloor2;
}
else return false;
return true;
}
EX bool makeflame(cell *c, int timeout, bool checkonly) {
if(!checkonly) destroyTrapsOn(c);
if(itemBurns(c->item)) {
if(checkonly) return true;
if(c->cpdist <= 7)
addMessage(XLAT("%The1 burns!", c->item));
c->item = itNone;
}
if(cellUnstable(c)) {
if(checkonly) return true;
doesFall(c);
}
else if(c->wall == waChasm || c->wall == waOpenGate || c->wall == waRed2 || c->wall == waRed3 ||
c->wall == waTower)
return false;
else if(c->wall == waBoat) {
if(isPlayerOn(c) && markOrb(itOrbWinter)) {
addMessage(XLAT("%the1 protects your boat!", itOrbWinter));
}
if(checkonly) return true;
if(c->cpdist <= 7)
addMessage(XLAT("%The1 burns!", winf[c->wall].name));
drawFireParticles(c, 24);
placeWater(c, c);
if(isIcyLand(c)) HEAT(c) += 1;
}
else if(c->wall == waNone && c->land == laCocytus) {
if(checkonly) return true;
c->wall = waLake, HEAT(c) += 1;
}
else if(c->wall == waFireTrap) {
if(checkonly) return true;
if(c->wparam == 0) c->wparam = 1;
}
else if(c->wall == waFrozenLake) {
if(checkonly) return true;
drawParticles(c, MELTCOLOR, 8, 8);
c->wall = waLake, HEAT(c) += 1;
}
else if(c->wall == waIcewall) {
if(checkonly) return true;
drawParticles(c, MELTCOLOR, 8, 8);
c->wall = waNone;
}
else if(c->wall == waMineMine) {
if(checkonly) return true;
explodeMine(c);
}
else if(c->wall != waCTree && c->wall != waBigTree && c->wall != waSmallTree &&
c->wall != waVinePlant && !passable(c, NULL, P_MONSTER | P_MIRROR) &&
c->wall != waSaloon && c->wall != waRose) return false;
// reptiles are able to use the water to put the fire off
else if(c->wall == waReptileBridge) return false;
else if(c->wall == waDock) {
if(checkonly) return true;
c->wall = waBurningDock;
c->wparam = 3;
return false;
}
else {
eWall w = eternalFire(c) ? waEternalFire : waFire;
if(!checkonly) drawFireParticles(c, 10);
if(w == c->wall) return false;
if(checkonly) return true;
if(isReptile(c->wall)) kills[moReptile]++;
destroyHalfvine(c);
if(!isFire(c)) c->wparam = 0;
c->wall = w;
c->wparam = max(c->wparam, (char) timeout);
if(c->land == laBrownian) c->landparam = 0;
}
return true;
}
EX void explosion(cell *c, int power, int central) {
playSound(c, "explosion");
drawFireParticles(c, 30, 150);
brownian::dissolve_brownian(c, 2);
makeflame(c, central, false);
forCellEx(c2, c) {
destroyTrapsOn(c2);
brownian::dissolve_brownian(c2, 1);
if(c2->wall == waRed2 || c2->wall == waRed3)
c2->wall = waRed1;
else if(c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waPetrified || c2->wall == waGargoyle) {
c2->wall = waNone;
makeflame(c2, power/2, false);
}
else if(c2->wall == waPetrifiedBridge || c2->wall == waGargoyleBridge) {
placeWater(c, c);
}
else if(c2->wall == waPalace || c2->wall == waOpenGate || c2->wall == waClosedGate ||
c2->wall == waSandstone || c2->wall == waMetal || c2->wall == waSaloon || c2->wall == waRuinWall) {
c2->wall = waNone;
makeflame(c2, power/2, false);
}
else if(c2->wall == waTower)
c2->wall = waRubble;
else if(c2->wall == waBarrowWall)
c2->wall = waBarrowDig;
else if(c2->wall == waBarrowDig)
c2->wall = waNone;
else if(c2->wall == waFireTrap) {
if(c2->wparam == 0)
c2->wparam = 1;
}
else if(c2->wall == waExplosiveBarrel)
explodeBarrel(c2);
else makeflame(c2, power, false);
}
}
EX void explodeMine(cell *c) {
if(c->wall != waMineMine)
return;
c->wall = waMineOpen;
explosion(c, 20, 20);
mine::auto_teleport_charges();
}
EX void explodeBarrel(cell *c) {
if(c->wall != waExplosiveBarrel)
return;
c->wall = waNone;
explosion(c, 20, 20);
}
EX bool mayExplodeMine(cell *c, eMonster who) {
if(c->wall != waMineMine) return false;
if(ignoresPlates(who)) return false;
explodeMine(c);
return true;
}
EX void flameHalfvine(cell *c, int val) {
if(itemBurns(c->item)) {
addMessage(XLAT("%The1 burns!", c->item));
c->item = itNone;
}
c->wall = waPartialFire;
c->wparam = val;
}
EX bool destroyHalfvine(cell *c, eWall newwall IS(waNone), int tval IS(6)) {
if(cellHalfvine(c)) {
for(int t=0; t<c->type; t++) if(c->move(t)->wall == c->wall) {
if(newwall == waPartialFire) flameHalfvine(c->move(t), tval);
else if(newwall == waRed1) c->move(t)->wall = waVinePlant;
else c->move(t)->wall = newwall;
}
if(newwall == waPartialFire) flameHalfvine(c, tval);
else c->wall = newwall;
return true;
}
return false;
}
EX int coastvalEdge(cell *c) { return coastval(c, laIvoryTower); }
EX int gravityLevel(cell *c) {
if(c->land == laIvoryTower || c->land == laEndorian)
return coastval(c, laIvoryTower);
if(c->land == laDungeon)
return -coastval(c, laIvoryTower);
if(c->land == laMountain)
return 1-celldistAlt(c);
return 0;
}
EX int gravityLevelDiff(cell *c, cell *d) {
if(c->land != laWestWall || d->land != laWestWall)
return gravityLevel(c) - gravityLevel(d);
if(shmup::on) return 0;
int nid = neighborId(c, d);
int id1 = parent_id(c, 1, coastvalEdge) + 1;
int di1 = angledist(c->type, id1, nid);
int id2 = parent_id(c, -1, coastvalEdge) - 1;
int di2 = angledist(c->type, id2, nid);
if(di1 < di2) return 1;
if(di1 > di2) return -1;
return 0;
}
EX bool canUnstable(eWall w, flagtype flags) {
return w == waNone || w == waCanopy || w == waOpenPlate || w == waClosePlate ||
w == waOpenGate || ((flags & MF_STUNNED) && (w == waLadder || w == waTrunk || w == waSolidBranch || w == waWeakBranch
|| w == waBigBush || w == waSmallBush));
}
EX bool cellEdgeUnstable(cell *c, flagtype flags IS(0)) {
if(!isGravityLand(c->land) || !canUnstable(c->wall, flags)) return false;
if(shmup::on && c->land == laWestWall) return false;
forCellEx(c2, c) {
if(isAnyIvy(c2->monst) &&
c->land == laMountain && !(flags & MF_IVY)) return false;
int d = gravityLevelDiff(c, c2);
if(d == gravity_zone_diff(c)) {
if(againstWind(c2, c)) return false;
if(!passable(c2, NULL, P_MONSTER | P_DEADLY))
return false;
if(isWorm(c2))
return false;
}
if(WDIM == 3) {
if(d == 0 && !passable(c2, NULL, P_MONSTER | P_DEADLY)) return false;
if(d == -1 && !passable(c2, NULL, P_MONSTER | P_DEADLY)) forCellEx(c3, c2) if(c3 != c && gravityLevelDiff(c3, c) == 0) return false;
}
}
return true;
}
int tidalphase;
EX int tidalsize, tide[200];
EX void calcTidalPhase() {
if(!tidalsize) {
for(int i=0; i<5; i++) tide[tidalsize++] = 1;
for(int i=1; i<4; i++) for(int j=0; j<3; j++)
tide[tidalsize++] = i;
for(int i=4; i<7; i++) for(int j=0; j<2; j++)
tide[tidalsize++] = i;
for(int i=7; i<20; i++)
tide[tidalsize++] = i;
for(int i=20; i<23; i++) for(int j=0; j<2; j++)
tide[tidalsize++] = i;
for(int i=23; i<26; i++) for(int j=0; j<3; j++)
tide[tidalsize++] = i;
for(int i=0; i+i<tidalsize; i++) tide[tidalsize++] = 27 - tide[i];
/* printf("tidalsize = %d\n", tidalsize);
for(int i=0; i<tidalsize; i++) printf("%d ", tide[i]);
printf("\n"); */
}
tidalphase = tide[
(shmup::on ? shmup::curtime/600 : turncount)
% tidalsize];
if(peace::on)
tidalphase = 5 + tidalphase / 6;
}
EX int tidespeed() {
return tide[(turncount+6) % tidalsize] - tidalphase;
}
EX bool recalcTide;
#if HDR
#define SEADIST LHU.bytes[0]
#define LANDDIST LHU.bytes[1]
#define CHAOSPARAM LHU.bytes[2]
#endif
#if CAP_FIELD
EX int lavatide(cell *c, int t) {
int tc = (shmup::on ? shmup::curtime/400 : turncount);
return (windmap::at(c) + (tc+t)*4) & 255;
}
#endif
EX void checkTide(cell *c) {
if(c->land == laOcean) {
int t = c->landparam;
if(chaosmode) {
char& csd(c->SEADIST); if(csd == 0) csd = 7;
char& cld(c->LANDDIST); if(cld == 0) cld = 7;
int seadist=csd, landdist=cld;
for(int i=0; i<c->type; i++) {
cell *c2 = c->move(i);
if(!c2) continue;
if(c2->land == laBarrier || c2->land == laOceanWall) ;
else if(c2->land == laOcean)
seadist = min(seadist, c2->SEADIST ? c2->SEADIST+1 : 7),
landdist = min(landdist, c2->LANDDIST ? c2->LANDDIST+1 : 7);
else if(isSealand(c2->land)) seadist = 1;
else landdist = 1;
}
if(seadist < csd) csd = seadist, recalcTide = true;
if(landdist < cld) cld = landdist, recalcTide = true;
if(seadist == 1 && landdist == 1) t = 15;
else t = c->CHAOSPARAM = 1 + (29 * (landdist-1)) / (seadist+landdist-2);
}
if(c->wall == waStrandedBoat || c->wall == waBoat)
c->wall = t >= tidalphase ? waBoat : waStrandedBoat;
if(c->wall == waSea || c->wall == waNone)
c->wall = t >= tidalphase ? waSea : waNone;
if(isFire(c) && t >= tidalphase)
c->wall = waSea;
}
#if CAP_FIELD
if(c->land == laVolcano) {
int id = lavatide(c, 0);
if(id < 96) {
if(c->wall == waNone || isWateryOrBoat(c) || c->wall == waVinePlant || isAlch(c)) {
if(isWateryOrBoat(c) || isAlch(c))
playSound(c, "steamhiss");
c->wall = waMagma;
if(itemBurns(c->item)) {
addMessage(XLAT("%The1 burns!", c->item)), c->item = itNone;
playSound(c, "steamhiss", 30);
}
}
}
else if(c->wall == waMagma) c->wall = waNone;
}
#endif
}
EX bool makeEmpty(cell *c) {
if(c->monst != moPrincess) {
if(isAnyIvy(c->monst)) killMonster(c, moPlayer, 0);
else if(c->monst == moPair) {
if(c->move(c->mondir)->monst == moPair)
c->move(c->mondir)->monst = moNone;
}
else if(isWorm(c->monst)) {
if(!items[itOrbDomination]) return false;
}
else c->monst = moNone;
}
if(c->land == laCocytus)
c->wall = waFrozenLake;
else if(c->land == laAlchemist || c->land == laCanvas)
;
else if(c->land == laDual)
;
else if(c->land == laCaves || c->land == laEmerald)
c->wall = waCavefloor;
else if(c->land == laDeadCaves)
c->wall = waDeadfloor2;
else if(c->land == laCaribbean || c->land == laOcean || c->land == laWhirlpool || c->land == laLivefjord || c->land == laWarpSea || c->land == laKraken || c->wall == waBoat)
c->wall = waBoat; // , c->item = itOrbYendor;
else if(c->land == laMinefield)
c->wall = waMineOpen;
else if(c->wall == waFan && bounded)
;
else if(c->wall == waOpenPlate && bounded)
;
else if(c->wall == waTrunk || c->wall == waSolidBranch || c->wall == waWeakBranch)
;
else if(c->wall == waGiantRug)
;
else if(c->wall == waInvisibleFloor)
;
else if(c->wall == waDock)
;
else if(c->wall == waLadder)
;
else if(c->land == laDocks)
c->wall = waBoat;
else if(c->wall == waFreshGrave && bounded)
;
else if(c->wall == waBarrier && sphere && WDIM == 3)
;
else if(isReptile(c->wall))
c->wparam = reptilemax();
else if(c->wall == waAncientGrave && bounded)
;
else
c->wall = waNone;
if(c->land == laBrownian && c->wall == waNone && c->landparam == 0)
c->landparam = 1;
if(c->item != itCompass)
c->item = itNone;
if(c->land == laWildWest) {
forCellEx(c2, c)
forCellEx(c3, c2)
if(c3->wall != waBarrier)
c3->wall = waNone;
}
return true;
}
int numgates = 0;
EX void toggleGates(cell *c, eWall type, int rad) {
if(!c) return;
celllister cl(c, rad, 1000000, NULL);
for(cell *ct: cl.lst) {
if(ct->wall == waOpenGate && type == waClosePlate) {
bool onWorm = false;
if(isWorm(ct)) onWorm = true;
for(int i=0; i<ct->type; i++)
if(ct->move(i) && ct->move(i)->wall == waOpenGate && isWorm(ct->move(i))) onWorm = true;
if(!onWorm) {
ct->wall = waClosedGate, numgates++;
if(ct->item) {
playSound(ct, "hit-crush"+pick123());
addMessage(XLAT("%The1 is crushed!", ct->item));
ct->item = itNone;
}
toggleGates(ct, type, 1);
}
}
if(ct->wall == waClosedGate && type == waOpenPlate) {
ct->wall = waOpenGate, numgates++;
toggleGates(ct, type, 1);
}
}
}
EX void toggleGates(cell *ct, eWall type) {
playSound(ct, "click");
numgates = 0;
if(type == waClosePlate && PURE)
toggleGates(ct, type, 2);
else
toggleGates(ct, type, (GOLDBERG && !sphere && !a4) ? gp::dist_3() : 3);
if(numgates && type == waClosePlate)
playSound(ct, "closegate");
if(numgates && type == waOpenPlate)
playSound(ct, "opengate");
}
EX void destroyTrapsOn(cell *c) {
if(c->wall == waArrowTrap) c->wall = waNone, destroyTrapsAround(c);
}
EX void destroyTrapsAround(cell *c) {
forCellEx(c2, c) destroyTrapsOn(c2);
}
EX void destroyWeakBranch(cell *cf, cell *ct, eMonster who) {
if(cf && ct && cf->wall == waWeakBranch && cellEdgeUnstable(ct) &&
gravityLevelDiff(ct, cf) >= 0 && !ignoresPlates(who)) {
cf->wall = waCanopy;
if(!cellEdgeUnstable(cf)) { cf->wall = waWeakBranch; return; }
playSound(cf, "trapdoor");
drawParticles(cf, winf[waWeakBranch].color, 4);
}
if(cf && ct && cf->wall == waSmallBush && cellEdgeUnstable(ct) &&
gravityLevelDiff(ct, cf) >= 0 && !ignoresPlates(who)) {
cf->wall = waNone;
if(!cellEdgeUnstable(cf)) { cf->wall = waSmallBush; return; }
playSound(cf, "trapdoor");
drawParticles(cf, winf[waWeakBranch].color, 4);
}
}
EX bool isCentralTrap(cell *c) {
if(c->wall != waArrowTrap) return false;
int i = 0;
forCellEx(c2, c) if(c2->wall == waArrowTrap) i++;
return i == 2;
}
EX array<cell*, 5> traplimits(cell *c) {
array<cell*, 5> res;
int q = 0;
res[2] = c;
for(int d=0; d<c->type; d++) {
cellwalker cw(c, d);
cw += wstep;
if(cw.at->wall != waArrowTrap) continue;
res[1+q*2] = cw.at;
cw += (cw.at->type/2);
if((cw.at->type&1) && (cw+wstep).at->wall != waStone) cw += 1;
cw += wstep;
res[(q++)*4] = cw.at;
}
while(q<2) { res[q*4] = res[1+q*2] = NULL; q++; }
return res;
}
EX void activateArrowTrap(cell *c) {
if(c->wall == waArrowTrap && c->wparam == 0) {
playSound(c, "click");
c->wparam = shmup::on ? 2 : 1;
forCellEx(c2, c) activateArrowTrap(c2);
if(shmup::on) shmup::activateArrow(c);
}
}
#if HDR
template<class T>
movei determinePush(cellwalker who, int subdir, const T& valid) {
if(subdir != 1 && subdir != -1) {
subdir = 1;
static bool first = true;
if(first)
first = false,
addMessage("bad push: " + its(subdir));
}
cellwalker push = who;
push += wstep;
cell *c2 = push.at;
if(binarytiling) {
auto rd = reverse_directions(push.at, push.spin);
for(int i: rd) {
push.spin = i;
if(valid(push.cpeek())) return movei(push.at, push.spin);
}
return movei(c2, NO_SPACE);
}
int pd = push.at->type/2;
push += pd * -subdir;
push += wstep;
if(valid(push.at)) return movei(c2, (push+wstep).spin);
if(c2->type&1) {
push = push + wstep - subdir + wstep;
if(valid(push.at)) return movei(c2, (push+wstep).spin);
}
if(gravityLevelDiff(push.at, c2) < 0) {
push = push + wstep + 1 + wstep;
if(gravityLevelDiff(push.at, c2) < 0) {
push = push + wstep - 2 + wstep;
}
if(gravityLevelDiff(push.at, c2) < 0) {
push = push + wstep + 1 + wstep;
}
if(valid(push.at)) return movei(c2, (push+wstep).spin);
}
return movei(c2, NO_SPACE);
}
#endif
// for sandworms
EX void explodeAround(cell *c) {
for(int j=0; j<c->type; j++) {
cell* c2 = c->move(j);
if(c2) {
if(isIcyLand(c2)) HEAT(c2) += 0.5;
eWall ow = c2->wall;
if((c2->wall == waDune || c2->wall == waIcewall ||
c2->wall == waAncientGrave || c2->wall == waFreshGrave ||
c2->wall == waColumn || c2->wall == waThumperOff || c2->wall == waThumperOn ||
(isFire(c2) && !eternalFire(c2)) ||
c2->wall == waBigTree || c2->wall == waSmallTree ||
c2->wall == waVinePlant || c2->wall == waVineHalfA || c2->wall == waVineHalfB)) {
destroyHalfvine(c2);
c2->wall = waNone;
}
if(c2->wall == waExplosiveBarrel) explodeBarrel(c2);
if(c2->wall == waCavewall || c2->wall == waDeadTroll) c2->wall = waCavefloor;
if(c2->wall == waDeadTroll2) c2->wall = waNone;
if(c2->wall == waPetrified) c2->wall = waNone;
if(c2->wall == waDeadfloor2) c2->wall = waDeadfloor;
if(c2->wall == waGargoyleFloor) c2->wall = waChasm;
if(c2->wall == waGargoyleBridge || c2->wall == waPetrifiedBridge) placeWater(c2, c2);
if(c2->wall == waRubble) c2->wall = waNone;
if(c2->wall == waPlatform) c2->wall = waNone;
if(c2->wall == waStone) c2->wall = waNone, destroyTrapsAround(c2);
if(c2->wall == waRose) c2->wall = waNone;
if(c2->wall == waRuinWall) c2->wall = waNone;
if(c2->wall == waLadder) c2->wall = waNone;
if(c2->wall == waGargoyle) c2->wall = waNone;
if(c2->wall == waSandstone) c2->wall = waNone;
if(c2->wall == waSaloon) c2->wall = waNone;
if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
if(c2->wall == waBigStatue) c2->wall = waNone;
if(c2->wall == waPalace || c2->wall == waOpenGate || c2->wall == waClosedGate)
c2->wall = waNone;
if(isAlch(c2) && isAlch(c))
c2->wall = c->wall;
if(c2->wall != ow && ow) drawParticles(c2, winf[ow].color, 16);
}
}
}
EX bool earthMove(const movei& mi) {
auto& from = mi.s;
bool b = false;
cell *c2 = mi.t;
b |= earthWall(from);
if(!mi.proper()) return b;
int d = mi.rev_dir_or(0);
if(c2) for(int u=2; u<=c2->type-2; u++) {
cell *c3 = c2->modmove(d + u);
if(c3) b |= earthFloor(c3);
}
return b;
}
EX bool cellDangerous(cell *c) {
return cellUnstableOrChasm(c) || isFire(c) || c->wall == waClosedGate;
}
}

View File

@ -8,6 +8,8 @@
#include "hyper.h"
namespace hr {
EX int avengers, mirrorspirits, wandering_jiangshi, jiangshi_on_screen;
EX bool timerghost = true;
EX bool gen_wandering = true;
@ -312,7 +314,7 @@ EX void wandering() {
kills[moBomberbird] = 0;
kills[moTameBomberbird] = 0;
for(cell *c: currentmap->allcells()) if(c->wall == waMineUnknown) kills[moBomberbird]++;
for(cell *c: currentmap->allcells()) if(among(c->wall, waMineMine, waMineUnknown) && mineMarked(c)) kills[moTameBomberbird]++;
for(cell *c: currentmap->allcells()) if(among(c->wall, waMineMine, waMineUnknown) && mine::marked_mine(c)) kills[moTameBomberbird]++;
return;
}
if(!canmove) return;
@ -440,7 +442,7 @@ EX void wandering() {
else if(c->land == laVariant && wchance(items[itVarTreasure], 50)) {
int i = hrand(21);
if(getBits(c) & (1>>i)) {
eMonster m = variant_features[i].wanderer;
eMonster m = variant::features[i].wanderer;
if(m) c->monst = m, c->hitpoints = 3;
}
continue;

2083
monstermove.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -413,6 +413,10 @@ EX bool buildPrizeMirror(cell *c, int freq) {
return mirror::build(c);
}
#if HDR
extern cellwalker cwt;
#endif
EX eLand getPrizeLand(cell *c IS(cwt.at)) {
eLand l = c->land;
if(isElemental(l)) l = laElementalWall;

View File

@ -8,6 +8,8 @@
#include "hyper.h"
namespace hr {
EX bool orbused[ittypes], lastorbused[ittypes];
EX bool markOrb(eItem it) {
if(!items[it]) return false;
orbused[it] = true;
@ -85,7 +87,7 @@ EX bool reduceOrbPower(eItem it, int cap) {
return true;
}
if(items[it] > cap && timerghost) items[it] = cap;
auto_teleport_charges();
mine::auto_teleport_charges();
return false;
}
@ -579,7 +581,7 @@ void teleportTo(cell *dest) {
checkmoveO();
movecost(from, dest, 2);
auto_teleport_charges();
mine::auto_teleport_charges();
}
EX void jumpTo(cell *dest, eItem byWhat, int bonuskill IS(0), eMonster dashmon IS(moNone)) {

647
passable.cpp Normal file
View File

@ -0,0 +1,647 @@
// Hyperbolic Rogue - passability
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file checkmove.cpp
* \brief check whether monster/PC can move in the given direction
*/
#include "hyper.h"
namespace hr {
// === MOVEMENT FUNCTIONS ===
// w = from->move(d)
EX bool againstCurrent(cell *w, cell *from) {
if(from->land != laWhirlpool) return false;
if(againstWind(from, w)) return false; // wind is stronger than current
if(!eubinary && (!from->master->alt || !w->master->alt)) return false;
int dfrom = celldistAlt(from);
int dw = celldistAlt(w);
if(dw < dfrom) return false;
if(dfrom < dw) return true;
for(int d=0; d<from->type; d++)
if(from->move(d) == w) {
cell *c3 = from->modmove(d-1);
if(!c3) return false;
return celldistAlt(c3) < dfrom;
}
return false;
}
EX bool boatGoesThrough(cell *c) {
if(isGravityLand(c->land)) return false;
return
(c->wall == waNone && c->land != laMotion && c->land != laZebra && c->land != laReptile) ||
isAlchAny(c) ||
c->wall == waCavefloor || c->wall == waFrozenLake || isReptile(c->wall) ||
c->wall == waDeadfloor || c->wall == waCIsland || c->wall == waCIsland2 ||
c->wall == waMineUnknown || c->wall == waMineMine || c->wall == waMineOpen ||
c->wall == waBonfireOff || c->wall == waFire || c->wall == waPartialFire ||
c->wall == waArrowTrap;
}
EX void placeWater(cell *c, cell *c2) {
destroyTrapsOn(c);
if(isWatery(c)) ;
else if(c2 && isAlchAny(c2))
c->wall = c2->wall;
else if(isIcyLand(c))
c->wall = waLake;
else
c->wall = waSea;
// destroy the ancient treasure!
if(c->item == itBarrow) c->item = itNone;
}
EX int incline(cell *cfrom, cell *cto) {
return snakelevel(cto) - snakelevel(cfrom);
}
#define F(x) checkflags(flags,x)
EX bool checkflags(flagtype flags, flagtype x) {
if(flags & x) return true;
if(flags & P_ISPLAYER) {
if((x & P_WINTER) && markOrb(itOrbWinter)) return true;
if((x & P_IGNORE37) && markOrb(itOrb37)) return true;
if((x & P_FISH) && markOrb(itOrbFish)) return true;
if((x & P_MARKWATER) && markOrb(itOrbWater)) return true;
if((x & P_AETHER) && markOrb2(itOrbAether) && !(flags&P_NOAETHER)) return true;
}
if(flags & P_ISFRIEND) if(items[itOrbEmpathy])
if(checkflags(flags ^ P_ISPLAYER ^ P_ISFRIEND, x) && markOrb(itOrbEmpathy))
return true;
return false;
}
EX bool strictlyAgainstGravity(cell *w, cell *from, bool revdir, flagtype flags) {
return
cellEdgeUnstable(w, flags) && cellEdgeUnstable(from, flags) &&
!(shmup::on && from == w) && gravityLevelDiff(from, w) != (revdir?-1:1) * gravity_zone_diff(from);
}
EX bool anti_alchemy(cell *w, cell *from) {
bool alch1 = w->wall == waFloorA && from && from->wall == waFloorB && !w->item && !from->item;
alch1 |= w->wall == waFloorB && from && from->wall == waFloorA && !w->item && !from->item;
return alch1;
}
#if HDR
#define P_MONSTER Flag(0) // can move through monsters
#define P_MIRROR Flag(1) // can move through mirrors
#define P_REVDIR Flag(2) // reverse direction movement
#define P_WIND Flag(3) // can move against the wind
#define P_GRAVITY Flag(4) // can move against the gravity
#define P_ISPLAYER Flag(5) // player-only moves (like the Round Table jump)
#define P_ONPLAYER Flag(6) // always can step on the player
#define P_FLYING Flag(7) // is flying
#define P_BULLET Flag(8) // bullet can fly through more things
#define P_MIRRORWALL Flag(9) // mirror images go through mirror walls
#define P_JUMP1 Flag(10) // first part of a jump
#define P_JUMP2 Flag(11) // second part of a jump
#define P_TELE Flag(12) // teleport onto
#define P_BLOW Flag(13) // Orb of Air -- blow, or push
#define P_AETHER Flag(14) // aethereal
#define P_FISH Flag(15) // swimming
#define P_WINTER Flag(16) // fire resistant
#define P_USEBOAT Flag(17) // can use boat
#define P_NOAETHER Flag(18) // disable AETHER
#define P_FRIENDSWAP Flag(19) // can move on friends (to swap with tem)
#define P_ISFRIEND Flag(20) // is a friend (can use Empathy + Winter/Aether/Fish combo)
#define P_LEADER Flag(21) // can push statues and use boats
#define P_MARKWATER Flag(22) // mark Orb of Water as used
#define P_EARTHELEM Flag(23) // Earth Elemental
#define P_WATERELEM Flag(24) // Water Elemental
#define P_IGNORE37 Flag(25) // ignore the triheptagonal board
#define P_CHAIN Flag(26) // for chaining moves with boats
#define P_DEADLY Flag(27) // suicide moves allowed
#define P_ROSE Flag(28) // rose smell
#define P_CLIMBUP Flag(29) // allow climbing up
#define P_CLIMBDOWN Flag(30) // allow climbing down
#define P_REPTILE Flag(31) // is reptile
#define P_VOID Flag(32) // void beast
#define P_PHASE Flag(33) // phasing movement
#define P_PULLMAGNET Flag(34) // pull the other part of the magnet
#endif
EX bool passable(cell *w, cell *from, flagtype flags) {
bool revdir = (flags&P_REVDIR);
bool vrevdir = revdir ^ bool(flags&P_VOID);
if(from && from != w && nonAdjacent(from, w) && !F(P_IGNORE37 | P_BULLET)) return false;
for(int i=0; i<numplayers(); i++) {
cell *pp = playerpos(i);
if(!pp) continue;
if(w == pp && F(P_ONPLAYER)) return true;
if(from == pp && F(P_ONPLAYER) && F(P_REVDIR)) return true;
if(from && !((flags & P_ISPLAYER) && pp->monst)) {
int i = vrevdir ? incline(w, from) : incline(from, w);
if(in_gravity_zone(w)) {
if(gravity_state == gsLevitation) i = 0;
if(gravity_state == gsAnti && i > 1) i = 1;
}
if(i < -1 && F(P_ROSE)) return false;
if((i > 1) && !F(P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBUP | P_AETHER | P_REPTILE))
return false;
if((i < -2) && !F(P_DEADLY | P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBDOWN | P_AETHER | P_REPTILE))
return false;
}
}
if(F(P_ROSE)) {
if(airdist(w) < 3) return false;
if(againstWind(w,from)) return false;
}
if(from && strictlyAgainstGravity(w, from, vrevdir, flags)
&& !((flags & P_ISPLAYER) && shmup::on)
&& !F(P_GRAVITY | P_BLOW | P_JUMP1 | P_JUMP2 | P_FLYING | P_BULLET | P_AETHER)
) return false;
if(from && (vrevdir ? againstWind(from,w) : againstWind(w, from)) && !F(P_WIND | P_BLOW | P_JUMP1 | P_JUMP2 | P_BULLET | P_AETHER)) return false;
if(revdir && from && w->monst && passable(from, w, flags &~ (P_REVDIR|P_MONSTER)))
return true;
if(!shmup::on && sword::at(w, flags & P_ISPLAYER) && !F(P_DEADLY | P_BULLET | P_ROSE))
return false;
bool alch1 = anti_alchemy(w, from);
if(alch1) {
bool alchok = (in_gravity_zone(w) || in_gravity_zone(from));
alchok = alchok || (F(P_JUMP1 | P_JUMP2 | P_FLYING | P_TELE | P_BLOW | P_AETHER | P_BULLET)
&& !F(P_ROSE));
if(!alchok) return false;
}
if(from && thruVine(from, w) && !F(P_AETHER)) return false;
if(w->monst == moMouse && F(P_JUMP1)) ;
else if(w->monst && isFriendly(w) && F(P_FRIENDSWAP)) ;
else if(w->monst && !F(P_MONSTER)) return false;
if(w->wall == waMirror || w->wall == waCloud)
return F(P_MIRROR | P_AETHER);
if(w->wall == waMirrorWall)
return F(P_MIRRORWALL);
if(F(P_BULLET)) {
if(isFire(w) || w->wall == waBonfireOff || cellHalfvine(w) ||
w->wall == waMagma ||
w->wall == waAncientGrave || w->wall == waFreshGrave || w->wall == waRoundTable)
return true;
}
if(F(P_LEADER)) {
if(from && from->wall == waBoat && isWatery(w) && from->item == itOrbYendor)
return false;
if(from && from->wall == waBoat && isWateryOrBoat(w) && !againstCurrent(w, from))
return true;
if(from && isWatery(from) && w->wall == waBoat && F(P_CHAIN))
return true;
if(from && isWatery(from) && isWatery(w) && F(P_CHAIN) && !againstCurrent(w, from))
return true;
if(w->wall == waBigStatue && from && canPushStatueOn(from)) return true;
}
if(F(P_EARTHELEM)) {
// cannot go through Living Caves...
if(w->wall == waCavefloor) return false;
// but can dig through...
if(w->wall == waDeadwall || w->wall == waDune || w->wall == waStone)
return true;
// and can swim through...
if(w->wall == waSea && w->land == laLivefjord)
return true;
}
if(F(P_WATERELEM)) {
if(isWatery(w) || boatGoesThrough(w) ||
w->wall == waBoat ||
w->wall == waDeadTroll || w->wall == waDeadTroll2) return true;
return false;
}
if(isThorny(w->wall) && F(P_BLOW | P_DEADLY)) return true;
if(isFire(w) || w->wall == waMagma) {
if(w->wall == waMagma && in_gravity_zone(w)) ;
else if(!F(P_AETHER | P_WINTER | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false;
}
if(in_gravity_zone(w) && gravity_state == gsAnti && !isGravityLand(w->land) && (!from || !isGravityLand(from->land)))
if(!F(P_AETHER | P_BLOW | P_JUMP1 | P_BULLET | P_FLYING)) {
bool next_to_wall = false;
forCellEx(c2, w) if(isJWall(c2)) next_to_wall = true;
if(from) forCellEx(c2, from) if(isJWall(c2)) next_to_wall = true;
if(!next_to_wall && (!from || incline(from, w) * (vrevdir?-1:1) <= 0)) return false;
}
if(isWatery(w)) {
if(in_gravity_zone(w)) ;
else if(from && from->wall == waBoat && F(P_USEBOAT) &&
(!againstCurrent(w, from) || F(P_MARKWATER)) && !(from->item == itOrbYendor)) ;
else if(!F(P_AETHER | P_FISH | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false;
}
if(isChasmy(w)) {
if(in_gravity_zone(w)) ;
else if(!F(P_AETHER | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false;
}
if(w->wall == waRoundTable && from && from->wall != waRoundTable && (flags & P_ISPLAYER)) return true;
if(isNoFlight(w) && F(P_FLYING | P_BLOW | P_JUMP1)) return false;
if(isWall(w)) {
// a special case: empathic aethereal beings cannot go through Round Table
// (but truly aetheral beings can)
if(w->wall == waRoundTable) {
if(!(flags & P_AETHER)) return false;
}
else if(!F(P_AETHER)) return false;
}
return true;
}
EX vector<pair<cell*, int> > airmap;
EX int airdist(cell *c) {
if(!(havewhat & HF_AIR)) return 3;
vector<pair<cell*, int> >::iterator it =
lower_bound(airmap.begin(), airmap.end(), make_pair(c,0));
if(it != airmap.end() && it->first == c) return it->second;
return 3;
}
EX ld calcAirdir(cell *c) {
if(!c || c->monst == moAirElemental || !passable(c, NULL, P_BLOW))
return 0;
for(int i=0; i<c->type; i++) {
cell *c2 = c->move(i);
if(c2 && c2->monst == moAirElemental) {
return c->c.spin(i) * 2 * M_PI / c2->type;
}
}
for(int i=0; i<c->type; i++) {
cell *c2 = c->move(i);
if(!c2) continue;
if(!passable(c2, c, P_BLOW | P_MONSTER)) continue;
if(!passable(c, c2, P_BLOW | P_MONSTER)) continue;
for(int i=0; i<c2->type; i++) {
cell *c3 = c2->move(i);
if(c3 && c3->monst == moAirElemental) {
return c2->c.spin(i) * 2 * M_PI / c3->type;
}
}
}
return 0;
}
EX bool againstWind(cell *cto, cell *cfrom) {
if(!cfrom || !cto) return false;
int dcto = airdist(cto), dcfrom = airdist(cfrom);
if(dcto < dcfrom) return true;
#if CAP_FIELD
if(cfrom->land == laBlizzard && !shmup::on && cto->land == laBlizzard && dcto == 3 && dcfrom == 3) {
char vfrom = windmap::at(cfrom);
char vto = windmap::at(cto);
int z = (vfrom-vto) & 255;
if(z >= windmap::NOWINDBELOW && z < windmap::NOWINDFROM)
return true;
}
#endif
whirlwind::calcdirs(cfrom);
int d = neighborId(cfrom, cto);
if(whirlwind::winddir(d) == -1) return true;
return false;
}
EX bool ghostmove(eMonster m, cell* to, cell* from) {
if(!isGhost(m) && nonAdjacent(to, from)) return false;
if(sword::at(to, 0)) return false;
if(!shmup::on && isPlayerOn(to)) return false;
if(to->monst && !(to->monst == moTentacletail && isGhost(m) && m != moFriendlyGhost)
&& !(to->monst == moTortoise && isGhost(m) && m != moFriendlyGhost))
return false;
if((m == moWitchGhost || m == moWitchWinter) && to->land != laPower)
return false;
if(isGhost(m))
for(int i=0; i<to->type; i++) if(to->move(i)) {
if(inmirror(to->move(i))) return false;
if(to->move(i) && to->move(i) != from && isGhost(to->move(i)->monst) &&
(to->move(i)->monst == moFriendlyGhost) == (m== moFriendlyGhost))
return false;
}
if(isGhost(m) || m == moWitchGhost) return true;
if(m == moGreaterShark) return isWatery(to);
if(m == moWitchWinter)
return passable(to, from, P_WINTER | P_ONPLAYER);
return false;
}
bool slimepassable(cell *w, cell *c) {
if(w == c || !c) return true;
int u = neighborId(c, w);
if(nonAdjacent(w,c)) return false;
if(isPlayerOn(w)) return true;
int group = slimegroup(c);
if(!group) return false;
int ogroup = slimegroup(w);
if(!ogroup) return false;
bool hv = (group == ogroup);
if(sword::at(w, 0)) return false;
if(w->item) return false;
// only travel to halfvines correctly
if(cellHalfvine(c)) {
int i=0;
for(int t=0; t<c->type; t++) if(c->move(t) && c->move(t)->wall == c->wall) i=t;
int z = i-u; if(z<0) z=-z; z%=6;
if(z>1) return false;
hv=(group == ogroup);
}
// only travel from halfvines correctly
if(cellHalfvine(w)) {
int i=0;
for(int t=0; t<w->type; t++) if(w->move(t) && w->move(t)->wall == w->wall) i=t;
int z = i-c->c.spin(u); if(z<0) z=-z; z%=6;
if(z>1) return false;
hv=(group == ogroup);
}
if(!hv) return false;
return true;
}
bool sharkpassable(cell *w, cell *c) {
if(w == c || !c) return true;
if(nonAdjacent(w,c)) return false;
if(isPlayerOn(w)) return true;
if(!isWatery(w)) return false;
if(sword::at(w, 0)) return false;
// don't go against the current
if(isWateryOrBoat(w) && isWateryOrBoat(c))
return !againstCurrent(w, c);
return true;
}
EX bool canPushStatueOn(cell *c) {
return passable(c, NULL, P_MONSTER) && !snakelevel(c) &&
!isWorm(c->monst) && !isReptile(c->wall) && !peace::on &&
!among(c->wall, waBoat, waFireTrap, waArrowTrap);
}
EX void moveBoat(const movei& mi) {
eWall x = mi.t->wall; mi.t->wall = mi.s->wall; mi.s->wall = x;
mi.t->mondir = mi.rev_dir_or(NODIR);
moveItem(mi.s, mi.t, false);
animateMovement(mi, LAYER_BOAT);
}
EX void moveBoatIfUsingOne(const movei& mi) {
if(mi.s->wall == waBoat && isWatery(mi.t)) moveBoat(mi);
else if(mi.s->wall == waBoat && boatGoesThrough(mi.t) && isFriendly(mi.t) && markEmpathy(itOrbWater)) {
placeWater(mi.t, mi.s);
moveBoat(mi);
}
}
bool againstMagnet(cell *c1, cell *c2, eMonster m) { // (from, to)
if(false) forCellEx(c3, c2) {
if(c3 == c1) continue;
if(c3->monst == m)
return true;
/* if(c3->monst == otherpole(m) && c3->move(c3->mondir) != c1) {
int i = 0;
forCellEx(c4, c3) if(c4->monst == m) i++;
if(i == 2) return true;
} */
}
if(c1->monst == m && !isNeighbor(c2, c1->move(c1->mondir)))
return true;
forCellEx(c3, c1)
if(c3->monst != m && isMagneticPole(c3->monst))
if(!isNeighbor(c3, c2))
return true;
return false;
}
EX bool againstPair(cell *c1, cell *c2, eMonster m) { // (from, to)
if(c1->monst == m && !isNeighbor(c2, c1->move(c1->mondir)))
return true;
return false;
}
EX bool notNearItem(cell *c) {
forCellCM(c2, c) if(c2->item) return false;
return true;
}
EX bool passable_for(eMonster m, cell *w, cell *from, flagtype extra) {
if(w->monst && !(extra & P_MONSTER) && !isPlayerOn(w))
return false;
if(m == moWolf) {
return (isIcyLand(w) || w->land == laVolcano) && (isPlayerOn(w) || passable(w, from, extra));
}
if(isMagneticPole(m))
return !(w && from && againstMagnet(from, w, m)) && passable(w, from, extra);
if(m == moPair)
return !(w && from && againstPair(from, w, m)) && passable(w, from, extra);
if(m == passive_switch) return false;
if(minf[m].mgroup == moYeti || isBug(m) || isDemon(m) || m == moHerdBull || m == moMimic || m == moAsteroid) {
if((isWitch(m) || m == moEvilGolem) && w->land != laPower && w->land != laHalloween)
return false;
return passable(w, from, extra);
}
if(m == moDragonHead && prairie::isriver(w))
return false;
if(isShark(m))
return sharkpassable(w, from);
if(isSlimeMover(m))
return slimepassable(w, from);
if(m == moKrakenH) {
if(extra & P_ONPLAYER) {
if(isPlayerOn(w)) return true;
}
if((extra & P_ONPLAYER) && isPlayerOn(w))
return true;
if(kraken_pseudohept(w) || kraken_pseudohept(from)) return false;
if(w->wall != waBoat && !slimepassable(w, from)) return false;
forCellEx(w2, w) if(w2->wall != waBoat && !passable(w2, w, P_FISH | P_MONSTER)) return false;
return true;
}
if(m == moEarthElemental)
return passable(w, from, extra | P_EARTHELEM);
if(m == moWaterElemental)
return passable(w, from, extra | P_WATERELEM);
if(m == moGreaterShark)
return isWatery(w) || w->wall == waBoat || w->wall == waFrozenLake;
if(isGhostMover(m) || m == moFriendlyGhost)
return ghostmove(m, w, from);
// for the purpose of Shmup this is correct
if(m == moTameBomberbird)
return passable(w, from, extra | P_FLYING | P_ISFRIEND);
if(m == moHexSnake)
return !pseudohept(w) && passable(w, from, extra|P_WIND|P_FISH);
if(isBird(m)) {
if(bird_disruption(w) && (!from || bird_disruption(from)) && markOrb(itOrbGravity))
return passable(w, from, extra);
else
return passable(w, from, extra | P_FLYING);
}
if(m == moReptile)
return passable(w, from, extra | P_REPTILE);
if(isDragon(m))
return passable(w, from, extra | P_FLYING | P_WINTER);
if(m == moAirElemental)
return passable(w, from, extra | P_FLYING | P_WIND);
if(isLeader(m)) {
if(from && from->wall == waBoat && from->item == itCoral && !from->monst) return false; // don't move Corals!
return passable(w, from, extra | P_LEADER);
}
if(isPrincess(m))
return passable(w, from, extra | P_ISFRIEND | P_USEBOAT);
if(isGolemOrKnight(m))
return passable(w, from, extra | P_ISFRIEND);
if(isWorm(m))
return passable(w, from, extra) && !cellUnstable(w) && ((m != moWorm && m != moTentacle) || !cellEdgeUnstable(w));
if(m == moVoidBeast)
return passable(w, from, extra | P_VOID);
if(m == moHexDemon) {
if(extra & P_ONPLAYER) {
if(isPlayerOn(w)) return true;
}
return !pseudohept(w) && passable(w, from, extra);
}
if(m == moAltDemon) {
if(extra & P_ONPLAYER) {
if(isPlayerOn(w)) return true;
}
return (!w || !from || w==from || pseudohept(w) || pseudohept(from)) && passable(w, from, extra);
}
if(m == moMonk) {
if(extra & P_ONPLAYER) {
if(isPlayerOn(w)) return true;
}
return notNearItem(w) && passable(w, from, extra);
}
return false;
}
EX eMonster movegroup(eMonster m) { return minf[m].mgroup; }
EX bool logical_adjacent(cell *c1, eMonster m1, cell *c2) {
if(!c1 || !c2) return true; // cannot really check
eMonster m2 = c2->monst;
if(!isNeighbor(c1, c2))
return false;
if(thruVine(c1, c2) && !attackThruVine(m1) && !attackThruVine(m2) &&
!checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether))
return false;
if(nonAdjacent(c1, c2) && !attackNonAdjacent(m1) && !attackNonAdjacent(m2) &&
!checkOrb(m1, itOrb37) && !checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether))
return false;
return true;
}
EX void buildAirmap() {
for(int k=0; k<isize(airmap); k++) {
int d = airmap[k].second;
if(d == 2) break;
cell *c = airmap[k].first;
for(int i=0; i<c->type; i++) {
cell *c2 = c->move(i);
if(!c2) continue;
if(!passable(c2, c, P_BLOW | P_MONSTER)) continue;
if(!passable(c, c2, P_BLOW | P_MONSTER)) continue;
airmap.push_back(make_pair(c2, d+1));
}
}
sort(airmap.begin(), airmap.end());
}
EX int rosewave, rosephase;
/** current state of the rose scent
* rosemap[c] &3 can be:
* 0 - wave not reached
* 1 - wave expanding
* 2 - wave phase 1
* 3 - wave phase 2
*/
EX map<cell*, int> rosemap;
EX int rosedist(cell *c) {
if(!(havewhat&HF_ROSE)) return 0;
int&r (rosemap[c]);
if((r&7) == 7) return 0;
if(r&3) return (r&3)-1;
return 0;
}
EX bool againstRose(cell *cfrom, cell *cto) {
if(rosedist(cfrom) != 1) return false;
if(cto && rosedist(cto) == 2) return false;
return true;
}
EX bool withRose(cell *cfrom, cell *cto) {
if(rosedist(cfrom) != 1) return false;
if(rosedist(cto) != 2) return false;
return true;
}
EX void buildRosemap() {
rosephase++; rosephase &= 7;
if((havewhat&HF_ROSE) && !rosephase) {
rosewave++;
for(int k=0; k<isize(dcal); k++) {
cell *c = dcal[k];
if(c->wall == waRose && c->cpdist <= gamerange() - 2)
rosemap[c] = rosewave * 8 + 2;
}
}
for(map<cell*, int>::iterator it = rosemap.begin(); it != rosemap.end(); it++) {
cell *c = it->first;
int r = it->second;
if(r < (rosewave) * 8) continue;
if((r&7) == 2) if(c->wall == waRose || !isWall(c)) for(int i=0; i<c->type; i++) {
cell *c2 = c->move(i);
if(!c2) continue;
// if(snakelevel(c2) <= snakelevel(c) - 2) continue;
if(!passable(c2, c, P_BLOW | P_MONSTER | P_ROSE)) continue;
int& r2 = rosemap[c2];
if(r2 < r) r2 = r-1;
}
}
for(map<cell*, int>::iterator it = rosemap.begin(); it != rosemap.end(); it++) {
int& r = it->second;
if((r&7) == 1 || (r&7) == 2 || (r&7) == 3) r++;
if(airdist(it->first) < 3 || whirlwind::cat(it->first)) r |= 7;
if(it->first->land == laBlizzard) r |= 7;
forCellEx(c2, it->first) if(airdist(c2) < 3) r |= 7;
}
}
EX bool scentResistant() {
return markOrb(itOrbBeauty) || markOrb(itOrbAether) || markOrb(itOrbShield);
}
}

1267
pcmove.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1155,7 +1155,7 @@ void movePlayer(monster *m, int delta) {
cwt.at = c2; afterplayermoved();
if(c2->item && c2->land == laAlchemist) c2->wall = m->base->wall;
if(m->base->wall == waRoundTable)
roundTableMessage(c2);
camelot::roundTableMessage(c2);
if(c2->wall == waCloud || c2->wall == waMirror) {
visibleFor(500);
cellwalker cw(c2, 0, false);
@ -1176,7 +1176,7 @@ void movePlayer(monster *m, int delta) {
items[itOrbLife] = 0;
m->dead = true;
}
uncoverMinesFull(c2);
mine::uncover_full(c2);
if(isWatery(c2) && isWatery(m->base) && m->inBoat)
moveItem(m->base, c2, true);
@ -2622,7 +2622,7 @@ EX void turn(int delta) {
#if CAP_INV
if(inv::on) inv::compute();
#endif
terracotta();
terracotta::check();
heat::processfires();
if(havewhat&HF_WHIRLPOOL) whirlpool::move();
if(havewhat&HF_WHIRLWIND) whirlwind::move();

View File

@ -182,7 +182,7 @@ void celldrawer::draw_ceiling() {
col = 0x404040;
for(int a=0; a<21; a++)
if((b >> a) & 1)
col += variant_features[a].color_change;
col += variant::features[a].color_change;
col = col & 0x00FF00;
break;
}

View File

@ -203,7 +203,7 @@ EX void initgame() {
}
if((tactic::on || yendor::on || peace::on) && isCyclic(firstland)) {
anthraxBonus = items[itHolyGrail];
camelot::anthraxBonus = items[itHolyGrail];
cwt.at->move(0)->land = firstland;
if(firstland == laWhirlpool) cwt.at->move(0)->wall = waSea;
@ -358,7 +358,7 @@ EX void initgame() {
#if CAP_INV
if(inv::on) inv::init();
#endif
auto_teleport_charges();
mine::auto_teleport_charges();
if(!use_special_land) {
if(firstland != (princess::challenge ? laPalace : laIce)) cheater++;
}
@ -1125,10 +1125,10 @@ EX void loadsave() {
// printf("boxid = %d\n", boxid);
if(items[itHolyGrail]) {
items[itHolyGrail]--;
knighted = newRoundTableRadius();
camelot::knighted = newRoundTableRadius();
items[itHolyGrail]++;
}
else knighted = 0;
else camelot::knighted = 0;
safety = true;
if(items[itSavedPrincess] < 0) items[itSavedPrincess] = 0;
addMessage(XLAT("Game loaded."));
@ -1169,7 +1169,7 @@ EX void stop_game() {
princess::reviveAt = 0;
princess::forceVizier = false;
princess::forceMouse = false;
knighted = 0;
camelot::knighted = 0;
// items[itGreenStone] = 100;
clearMemory();
game_active = false;