1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-11-27 14:37:16 +00:00

rewritten undo: part 1

This commit is contained in:
Zeno Rogue 2020-02-29 17:58:59 +01:00
parent eb15070ac4
commit e564622a48
17 changed files with 428 additions and 400 deletions

View File

@ -187,6 +187,8 @@ void achievement_gain(const char* s, char flags IS(0)) {
EX void achievement_collection(eItem it) { EX void achievement_collection(eItem it) {
if(cheater) return; if(cheater) return;
if(randomPatternsMode) return; if(randomPatternsMode) return;
LATE( achievement_collection(it); )
int q = items[it]; int q = items[it];
if(it == itTreat && q == 50 && (geometry == gSphere || geometry == gElliptic) && BITRUNCATED) if(it == itTreat && q == 50 && (geometry == gSphere || geometry == gElliptic) && BITRUNCATED)
@ -518,6 +520,8 @@ EX void achievement_count(const string& s, int current, int prev) {
if(cheater) return; if(cheater) return;
if(shmup::on) return; if(shmup::on) return;
if(randomPatternsMode) return; if(randomPatternsMode) return;
LATE( achievement_count(s, current, prev); )
if(s == "GOLEM" && current >= 5) if(s == "GOLEM" && current >= 5)
achievement_gain("GOLEM2"); achievement_gain("GOLEM2");
if(s == "GOLEM" && current >= 10) if(s == "GOLEM" && current >= 10)
@ -563,6 +567,7 @@ int specific_what = 0;
EX void improve_score(int i, eItem what) { EX void improve_score(int i, eItem what) {
if(offlineMode) return; if(offlineMode) return;
LATE( improve_score(i, what); )
#ifdef HAVE_ACHIEVEMENTS #ifdef HAVE_ACHIEVEMENTS
if(haveLeaderboard(i)) updateHi(what, get_currentscore(i)); if(haveLeaderboard(i)) updateHi(what, get_currentscore(i));
if(items[what] && haveLeaderboard(i)) { if(items[what] && haveLeaderboard(i)) {
@ -579,6 +584,7 @@ EX void achievement_score(int cat, int number) {
if(offlineMode) return; if(offlineMode) return;
#ifdef HAVE_ACHIEVEMENTS #ifdef HAVE_ACHIEVEMENTS
if(cheater) return; if(cheater) return;
LATE( achievement_score(cat, number); )
if(cat == LB_HALLOWEEN) { if(cat == LB_HALLOWEEN) {
if(geometry != gSphere && geometry != gElliptic) if(geometry != gSphere && geometry != gElliptic)
return; return;
@ -597,6 +603,7 @@ EX void achievement_score(int cat, int number) {
} }
EX void improveItemScores() { EX void improveItemScores() {
LATE( improveItemScores(); )
for(int i=1; i<=12; i++) improve_score(i, eItem(i)); for(int i=1; i<=12; i++) improve_score(i, eItem(i));
improve_score(17, itOrbYendor); improve_score(17, itOrbYendor);
improve_score(18, itFernFlower); improve_score(18, itFernFlower);
@ -669,6 +676,8 @@ int next_stat_tick;
EX void achievement_final(bool really_final) { EX void achievement_final(bool really_final) {
if(offlineMode) return; if(offlineMode) return;
LATE( achievement_final(really_final); )
#ifdef HAVE_ACHIEVEMENTS #ifdef HAVE_ACHIEVEMENTS
if(ticks > next_stat_tick) { if(ticks > next_stat_tick) {
upload_score(LB_STATISTICS, time(NULL)); upload_score(LB_STATISTICS, time(NULL));
@ -778,6 +787,7 @@ EX void check_total_victory() {
if(!items[itHolyGrail]) return; if(!items[itHolyGrail]) return;
if(items[itHyperstone] < 50) return; if(items[itHyperstone] < 50) return;
if(!princess::reviveAt) return; if(!princess::reviveAt) return;
LATE( check_total_victory(); )
hadtotalvictory = true; hadtotalvictory = true;
achievement_gain("TOTALVICTORY"); achievement_gain("TOTALVICTORY");
} }
@ -798,6 +808,7 @@ EX void achievement_victory(bool hyper) {
if(peace::on) return; if(peace::on) return;
if(tactic::on) return; if(tactic::on) return;
if(chaosmode) return; if(chaosmode) return;
LATE( achievement_victory(hyper); )
DEBB(DF_STEAM, ("after checks")) DEBB(DF_STEAM, ("after checks"))
int t = getgametime(); int t = getgametime();

View File

@ -213,6 +213,7 @@ EX bool petrify(cell *c, eWall walltype, eMonster m) {
EX void killIvy(cell *c, eMonster who) { EX void killIvy(cell *c, eMonster who) {
if(c->monst == moIvyDead) return; if(c->monst == moIvyDead) return;
changes.ccell(c);
if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, c->monst); if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, c->monst);
c->monst = moIvyDead; // NEWYEARFIX c->monst = moIvyDead; // NEWYEARFIX
for(int i=0; i<c->type; i++) if(c->move(i)) for(int i=0; i<c->type; i++) if(c->move(i))
@ -222,6 +223,7 @@ EX void killIvy(cell *c, eMonster who) {
EX void prespill(cell* c, eWall t, int rad, cell *from) { EX void prespill(cell* c, eWall t, int rad, cell *from) {
if(againstWind(c, from)) return; if(againstWind(c, from)) return;
changes.ccell(c);
// these monsters block spilling // these monsters block spilling
if(c->monst == moSeep || c->monst == moVineSpirit || c->monst == moShark || if(c->monst == moSeep || c->monst == moVineSpirit || c->monst == moShark ||
c->monst == moGreaterShark || c->monst == moParrot || c->monst == moCShark) c->monst == moGreaterShark || c->monst == moParrot || c->monst == moCShark)
@ -300,7 +302,10 @@ EX void prespill(cell* c, eWall t, int rad, cell *from) {
} }
EX void spillfix(cell* c, eWall t, int rad) { EX void spillfix(cell* c, eWall t, int rad) {
if(c->wall == waTemporary) c->wall = t; if(c->wall == waTemporary) {
changes.ccell(c);
c->wall = t;
}
if(rad) for(cell *c2: adj_minefield_cells(c)) if(rad) for(cell *c2: adj_minefield_cells(c))
spillfix(c2, t, rad-1); spillfix(c2, t, rad-1);
} }
@ -314,13 +319,15 @@ EX void degradeDemons() {
int dcs = isize(dcal); int dcs = isize(dcal);
for(int i=0; i<dcs; i++) { for(int i=0; i<dcs; i++) {
cell *c = dcal[i]; cell *c = dcal[i];
if(c->monst == moGreaterM || c->monst == moGreater) if(c->monst == moGreaterM || c->monst == moGreater) {
changes.ccell(c);
achievement_gain("DEMONSLAYER"); achievement_gain("DEMONSLAYER");
if(c->monst == moGreaterM) c->monst = moLesserM; if(c->monst == moGreaterM) c->monst = moLesserM;
if(c->monst == moGreater) c->monst = moLesser; if(c->monst == moGreater) c->monst = moLesser;
shmup::degradeDemons();
} }
} }
shmup::degradeDemons();
}
EX void stunMonster(cell *c2, eMonster killer, flagtype flags) { EX void stunMonster(cell *c2, eMonster killer, flagtype flags) {
int newtime = ( int newtime = (
@ -367,6 +374,7 @@ EX bool attackJustStuns(cell *c2, flagtype f, eMonster attacker) {
} }
EX void minerEffect(cell *c) { EX void minerEffect(cell *c) {
changes.ccell(c);
eWall ow = c->wall; eWall ow = c->wall;
if(c->wall == waOpenGate || c->wall == waFrozenLake || c->wall == waBoat || if(c->wall == waOpenGate || c->wall == waFrozenLake || c->wall == waBoat ||
c->wall == waStrandedBoat || c->wall == waStrandedBoat ||
@ -390,6 +398,7 @@ EX void minerEffect(cell *c) {
EX void killMutantIvy(cell *c, eMonster who) { EX void killMutantIvy(cell *c, eMonster who) {
if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, moMutant); if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, moMutant);
changes.ccell(c);
removeIvy(c); removeIvy(c);
for(int i=0; i<c->type; i++) for(int i=0; i<c->type; i++)
if(c->move(i)->mondir == c->c.spin(i) && (isMutantIvy(c->move(i)) || c->move(i)->monst == moFriendlyIvy)) if(c->move(i)->mondir == c->c.spin(i) && (isMutantIvy(c->move(i)) || c->move(i)->monst == moFriendlyIvy))
@ -461,17 +470,18 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) {
bool avenge = false; bool avenge = false;
for(int i=0; i<c->type; i++) if(!isWarpedType(c->move(i)->land)) for(int i=0; i<c->type; i++) if(!isWarpedType(c->move(i)->land))
avenge = true; avenge = true;
if(avenge) { avengers += 2; } if(avenge)
changes.value_add(avengers, 2);
} }
if(m == moMirrorSpirit && who != moMimic && !(deathflags & (AF_MAGIC | AF_CRUSH))) { if(m == moMirrorSpirit && who != moMimic && !(deathflags & (AF_MAGIC | AF_CRUSH))) {
kills[m]--; kills[m]--;
mirrorspirits++; changes.value_inc(mirrorspirits);
} }
if(isMutantIvy(m) || m == moFriendlyIvy) { if(isMutantIvy(m) || m == moFriendlyIvy) {
pcount = 0; pcount = 0;
if(isMutantIvy(m)) clearing::direct++; if(isMutantIvy(m)) changes.at_commit([] { clearing::direct++; });
bignum s = ivy_total() - 1; bignum s = ivy_total() - 1;
killMutantIvy(c, who); killMutantIvy(c, who);
s = ivy_total() - s; s = ivy_total() - s;
@ -481,6 +491,7 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) {
if(m == moPrincess) { if(m == moPrincess) {
princess::info *i = princess::getPrincessInfo(c); princess::info *i = princess::getPrincessInfo(c);
changes.value_keep(*i);
if(i) { if(i) {
i->princess = NULL; i->princess = NULL;
if(i->bestdist == OUT_OF_PALACE) { if(i->bestdist == OUT_OF_PALACE) {
@ -523,15 +534,19 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) {
if(m == moIceGolem) { if(m == moIceGolem) {
if(petrify(c, waIcewall, m)) pcount = 0; if(petrify(c, waIcewall, m)) pcount = 0;
heat::affect(c, -1); heat::affect(c, -1);
forCellEx(c2, c) heat::affect(c2, -.5); forCellEx(c2, c) {
changes.ccell(c2);
heat::affect(c2, -.5);
}
} }
if(m == moTroll) { if(m == moTroll) {
petrify(c, waDeadTroll, m); pcount = 0; petrify(c, waDeadTroll, m); pcount = 0;
for(int i=0; i<c->type; i++) if(c->move(i)) { forCellEx(c1, c) {
c->move(i)->item = itNone; changes.ccell(c1);
if(c->move(i)->wall == waDeadwall || c->move(i)->wall == waDeadfloor2) c->move(i)->wall = waCavewall; c1->item = itNone;
if(c->move(i)->wall == waDeadfloor) c->move(i)->wall = waCavefloor; if(c1->wall == waDeadwall || c1->wall == waDeadfloor2) c1->wall = waCavewall;
if(c1->wall == waDeadfloor) c1->wall = waCavefloor;
} }
} }
if(m == moFjordTroll || m == moForestTroll || m == moStormTroll) { if(m == moFjordTroll || m == moForestTroll || m == moStormTroll) {
@ -543,14 +558,16 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) {
destroyHalfvine(c); destroyHalfvine(c);
minerEffect(c); minerEffect(c);
brownian::dissolve_brownian(c, 1); brownian::dissolve_brownian(c, 1);
for(int i=0; i<c->type; i++) if(passable(c->move(i), c, P_MONSTER | P_MIRROR | P_CLIMBUP | P_CLIMBDOWN)) { forCellEx(c1, c) if(passable(c1, c, P_MONSTER | P_MIRROR | P_CLIMBUP | P_CLIMBDOWN)) {
destroyHalfvine(c->move(i)); changes.ccell(c1);
minerEffect(c->move(i)); destroyHalfvine(c1);
brownian::dissolve_brownian(c->move(i), 1); minerEffect(c1);
if(c->move(i)->monst == moSlime || c->move(i)->monst == moSlimeNextTurn) brownian::dissolve_brownian(c1, 1);
killMonster(c->move(i), who); if(c1->monst == moSlime || c1->monst == moSlimeNextTurn)
killMonster(c1, who);
} }
forCellEx(c2, c) { forCellEx(c2, c) {
changes.ccell(c2);
if(c2->wall == waPalace) c2->wall = waRubble; if(c2->wall == waPalace) c2->wall = waRubble;
if(c2->wall == waDeadwall) c2->wall = waDeadfloor2; if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
if(c2->wall == waExplosiveBarrel) explodeBarrel(c2); if(c2->wall == waExplosiveBarrel) explodeBarrel(c2);
@ -722,6 +739,7 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) {
bignum s = ivy_total() - 1; bignum s = ivy_total() - 1;
/*if((m == moIvyBranch || m == moIvyHead) && c->move(c->mondir)->monst == moIvyRoot) /*if((m == moIvyBranch || m == moIvyHead) && c->move(c->mondir)->monst == moIvyRoot)
ivynext(c, moIvyNext); */ ivynext(c, moIvyNext); */
changes.value_keep(clearing::imputed);
killIvy(c, who); killIvy(c, who);
s = ivy_total() - s; s = ivy_total() - s;
if(s > bignum(1) && vid.bubbles_special) if(s > bignum(1) && vid.bubbles_special)
@ -759,6 +777,7 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) {
useupOrb(itOrbLuck, items[itOrbLuck] / 2); useupOrb(itOrbLuck, items[itOrbLuck] / 2);
if(m == moAirElemental) { if(m == moAirElemental) {
changes.value_keep(airmap);
airmap.clear(); airmap.clear();
for(int i=0; i<isize(dcal); i++) for(int i=0; i<isize(dcal); i++)
if(dcal[i]->monst == moAirElemental) if(dcal[i]->monst == moAirElemental)
@ -1033,7 +1052,7 @@ EX bool flashWouldKill(cell *c, flagtype extra) {
cell *c3 = c2->move(u); cell *c3 = c2->move(u);
if(isWorm(c3)) continue; // immune to Flash if(isWorm(c3)) continue; // immune to Flash
if(c3->monst == moEvilGolem) continue; // evil golems don't count if(c3->monst == moEvilGolem) continue; // evil golems don't count
if(c3 != c && (c3->monst || isPlayerOn(c3)) && !stalemate::isKilled(c3)) { if(c3 != c && (c3->monst || isPlayerOn(c3))) {
bool b = canAttack(NULL, moWitchFlash, c3, c3->monst, AF_MAGIC | extra); bool b = canAttack(NULL, moWitchFlash, c3, c3->monst, AF_MAGIC | extra);
if(b) return true; if(b) return true;
} }

View File

@ -816,6 +816,7 @@ void addMessageToLog(msginfo& m, vector<msginfo>& log) {
EX void clearMessages() { msgs.clear(); } EX void clearMessages() { msgs.clear(); }
EX void addMessage(string s, char spamtype) { EX void addMessage(string s, char spamtype) {
LATE( addMessage(s, spamtype); )
DEBB(DF_MSG, ("addMessage: ", s)); DEBB(DF_MSG, ("addMessage: ", s));
msginfo m; msginfo m;

View File

@ -42,79 +42,13 @@ EX bool hasSafeOrb(cell *c) {
struct stalemate1 { struct stalemate1 {
eMonster who; eMonster who;
cell *moveto; cell *moveto;
cell *killed;
cell *pushto; cell *pushto;
cell *comefrom; cell *comefrom;
cell *swordlast[2], *swordtransit[2], *swordnext[2]; cell *swordlast[2], *swordtransit[2], *swordnext[2];
bool isKilled(cell *c); stalemate1(eMonster w, cell *mt, cell *pt, cell *cf) : who(w), moveto(mt), pushto(pt), comefrom(cf) {}
stalemate1(eMonster w, cell *mt, cell *ki, cell *pt, cell *cf) : who(w), moveto(mt), killed(ki), pushto(pt), comefrom(cf) {}
}; };
#endif #endif
EX bool used_impact;
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(used_impact && !isMultitile(w) && isNeighbor(w, moveto))
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) { EX bool krakensafe(cell *c) {
return items[itOrbFish] || items[itOrbAether] || return items[itOrbFish] || items[itOrbAether] ||
(c->item == itOrbFish && c->wall == waBoat) || (c->item == itOrbFish && c->wall == waBoat) ||
@ -149,7 +83,7 @@ EX bool monstersnear(stalemate1& sm) {
if(havewhat&HF_OUTLAW) { if(havewhat&HF_OUTLAW) {
for(cell *c1: gun_targets(c)) for(cell *c1: gun_targets(c))
if(c1->monst == moOutlaw && !c1->stuntime && !stalemate::isKilled(c1)) { if(c1->monst == moOutlaw && !c1->stuntime) {
res++; who_kills_me = moOutlaw; res++; who_kills_me = moOutlaw;
} }
} }
@ -166,7 +100,7 @@ EX bool monstersnear(stalemate1& sm) {
if(c3->monst != moWitchFlash) if(c3->monst != moWitchFlash)
if(!logical_adjacent(c3, c3->monst, c2) || !logical_adjacent(c2, c3->monst, c)) if(!logical_adjacent(c3, c3->monst, c2) || !logical_adjacent(c2, c3->monst, c))
continue; continue;
if(elec::affected(c3) || stalemate::isKilled(c3)) continue; if(elec::affected(c3)) continue;
if(c3->stuntime > (sm.who == moPlayer ? 0 : 1)) continue; if(c3->stuntime > (sm.who == moPlayer ? 0 : 1)) continue;
// speedwitches can only attack not-fastened monsters, // speedwitches can only attack not-fastened monsters,
// others can only attack if the move is not fastened // others can only attack if the move is not fastened
@ -174,7 +108,7 @@ EX bool monstersnear(stalemate1& sm) {
if(c3->monst != moWitchSpeed && fast) continue; if(c3->monst != moWitchSpeed && fast) continue;
// cannot attack if the immediate cell is impassable (except flashwitches) // cannot attack if the immediate cell is impassable (except flashwitches)
if(c3->monst != moWitchFlash) { if(c3->monst != moWitchFlash) {
if(!passable(c2, c3, stalemate::isKilled(c2)?P_MONSTER:0)) continue; if(!passable(c2, c3, 0)) continue;
if(isPlayerOn(c2) && items[itOrbFire]) continue; if(isPlayerOn(c2) && items[itOrbFire]) continue;
} }
// flashwitches cannot attack if it would kill another enemy // flashwitches cannot attack if it would kill another enemy
@ -185,7 +119,6 @@ EX bool monstersnear(stalemate1& sm) {
// consider normal monsters // consider normal monsters
if(c2 && if(c2 &&
isArmedEnemy(c2, sm.who) && isArmedEnemy(c2, sm.who) &&
!stalemate::isKilled(c2) &&
(c2->monst != moLancer || isUnarmed(sm.who) || !logical_adjacent(c, sm.who, c2))) { (c2->monst != moLancer || isUnarmed(sm.who) || !logical_adjacent(c, sm.who, c2))) {
eMonster m = c2->monst; eMonster m = c2->monst;
if(elec::affected(c2)) continue; if(elec::affected(c2)) continue;
@ -267,11 +200,11 @@ EX bool monstersnear2() {
return b; return b;
} }
EX bool monstersnear(cell *c, cell *nocount, eMonster who, cell *pushto, cell *comefrom) { EX bool monstersnear(cell *c, eMonster who, cell *pushto, cell *comefrom) {
if(peace::on) return 0; // you are safe if(peace::on) return 0; // you are safe
stalemate1 sm(who, c, nocount, pushto, comefrom); stalemate1 sm(who, c, pushto, comefrom);
if(who == moPlayer) for(int b=0; b<2; b++) sm.swordlast[b] = sword::pos(multi::cpid, b); if(who == moPlayer) for(int b=0; b<2; b++) sm.swordlast[b] = sword::pos(multi::cpid, b);
@ -340,11 +273,6 @@ EX namespace stalemate {
return false; 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) { EX bool isPushto(cell *c) {
for(int i=0; i<isize(moves); i++) if(moves[i].pushto == c) return true; for(int i=0; i<isize(moves); i++) if(moves[i].pushto == c) return true;
return false; return false;

View File

@ -221,7 +221,7 @@ EX namespace elec {
if(c->wall == waChasm) if(c->wall == waChasm)
return ecIsolator; return ecIsolator;
if(shmup::on ? isPlayerOn(c) : (isPlayerOn(c) || stalemate::isMoveto(c) || (items[itOrbEmpathy] && isFriendly(c)))) { if(shmup::on ? isPlayerOn(c) : (isPlayerOn(c) || (items[itOrbEmpathy] && isFriendly(c)))) {
if(items[itOrbShield]) return ecIsolator; if(items[itOrbShield]) return ecIsolator;
if(afterOrb) return ecIsolator; if(afterOrb) return ecIsolator;
if(!items[itOrbAether]) return isElectricLand(c) ? ecConductor : ecGrounded; if(!items[itOrbAether]) return isElectricLand(c) ? ecConductor : ecGrounded;
@ -229,12 +229,8 @@ EX namespace elec {
// if(c->monst && stalemate::moveto) printf("%p: isKilled = %d\n", c, stalemate::isKilled(c)); // if(c->monst && stalemate::moveto) printf("%p: isKilled = %d\n", c, stalemate::isKilled(c));
else if( else if(c->monst
(c->monst || stalemate::isPushto(c)) && c->monst != moGhost && c->monst != moIvyDead && c->monst != moIvyNext
&&
(stalemate::isPushto(c) || !stalemate::isKilled(c))
&&
c->monst != moGhost && c->monst != moIvyDead && c->monst != moIvyNext
&& !(isDragon(c->monst) && !c->hitpoints) && !(isDragon(c->monst) && !c->hitpoints)
) )
return isElectricLand(c) ? (ao ? ecCharged : ecConductor) : ecGrounded; return isElectricLand(c) ? (ao ? ecCharged : ecConductor) : ecGrounded;
@ -473,6 +469,7 @@ EX namespace elec {
// 0 = no close escape, 1 = close escape, 2 = message already shown // 0 = no close escape, 1 = close escape, 2 = message already shown
EX int lightningfast; EX int lightningfast;
EX void checklightningfast() { EX void checklightningfast() {
changes.value_keep(lightningfast);
if(lightningfast == 1) { if(lightningfast == 1) {
addMessage(XLAT("Wow! That was close.")); addMessage(XLAT("Wow! That was close."));
lightningfast = 2; lightningfast = 2;
@ -700,6 +697,7 @@ struct info {
eMonster m = c->monst; eMonster m = c->monst;
static int msgid = 0; static int msgid = 0;
changes.value_keep(msgid);
playSound(c, princessgender() ? "speak-princess" : "speak-prince"); playSound(c, princessgender() ? "speak-princess" : "speak-prince");
retry: retry:
@ -990,8 +988,10 @@ EX namespace clearing {
EX void imput(cell *c) { EX void imput(cell *c) {
if(bounded) return; if(bounded) return;
if(score.count(c)) return; if(score.count(c)) return;
changes.map_value(score, c);
auto& is = score[c]; auto& is = score[c];
celltype t = get_celltype(c); celltype t = get_celltype(c);
changes.map_value(stats, t);
auto& stat = stats[t]; auto& stat = stats[t];
is.second = c->mondir; is.second = c->mondir;
if(c->mpdist <= 6) { if(c->mpdist <= 6) {
@ -1413,10 +1413,12 @@ EX namespace mirror {
bool survive = true; bool survive = true;
if(m.first == multi::cpid) { if(m.first == multi::cpid) {
cell *c = m.second.at; cell *c = m.second.at;
changes.ccell(c);
if(!m.second.mirrored) nummirage++; if(!m.second.mirrored) nummirage++;
auto cw2 = m.second + wstep; auto cw2 = m.second + wstep;
if(inmirror(cw2)) cw2 = reflect(cw2); if(inmirror(cw2)) cw2 = reflect(cw2);
cell *c2 = cw2.at; cell *c2 = cw2.at;
changes.ccell(c2);
if(c2->monst) { if(c2->monst) {
c->monst = moMimic; c->monst = moMimic;
eMonster m2 = c2->monst; eMonster m2 = c2->monst;
@ -1453,6 +1455,7 @@ EX namespace mirror {
} }
EX void act(int d, int flags) { EX void act(int d, int flags) {
changes.value_keep(mirrors);
destroyKilled(); destroyKilled();
unlist(); unlist();
if(multi::players == 1) multi::cpid = 0; if(multi::players == 1) multi::cpid = 0;
@ -1469,6 +1472,7 @@ EX namespace mirror {
} }
EX void breakAll() { EX void breakAll() {
changes.value_keep(mirrors);
destroyKilled(); destroyKilled();
unlist(); unlist();
if(numplayers() == 1) if(numplayers() == 1)
@ -2619,6 +2623,7 @@ EX namespace dragon {
int penalty = 0; int penalty = 0;
int maxlen = 1000; int maxlen = 1000;
while(maxlen-->0) { while(maxlen-->0) {
changes.ccell(c);
makeflame(c, 5, false); makeflame(c, 5, false);
eMonster m = c->monst; eMonster m = c->monst;
drawFireParticles(c, 16); drawFireParticles(c, 16);
@ -2879,24 +2884,25 @@ EX namespace kraken {
if(c->land == laKraken && !c->item) c->item = itKraken; if(c->land == laKraken && !c->item) c->item = itKraken;
kills[moKrakenH]++; kills[moKrakenH]++;
if(checkOrb(who, itOrbStone)) c->wall = waNone; if(checkOrb(who, itOrbStone)) c->wall = waNone;
for(int i=0; i<c->type; i++) forCellEx(c1, c)
if(c->move(i)->monst == moKrakenT) { if(c1->monst == moKrakenT) {
changes.ccell(c1);
drawParticles(c, minf[moKrakenT].color, 16); drawParticles(c, minf[moKrakenT].color, 16);
c->move(i)->monst = moNone; c1->monst = moNone;
if(checkOrb(who, itOrbStone)) { if(checkOrb(who, itOrbStone)) {
if(isWatery(c->move(i))) if(isWatery(c1))
c->move(i)->wall = waNone; c1->wall = waNone;
else else
c->move(i)->wall = waPetrified, c->move(i)->wparam = moKrakenT; c1->wall = waPetrified, c1->wparam = moKrakenT;
} }
} }
} }
EX int totalhp(cell *c) { EX int totalhp(cell *c) {
int total = 0; int total = 0;
for(int i=0; i<c->type; i++) forCellEx(c1, c)
if(c->move(i)->monst == moKrakenT) if(c1->monst == moKrakenT)
total += c->move(i)->hitpoints; total += c1->hitpoints;
return total; return total;
} }
@ -3616,6 +3622,7 @@ EX namespace halloween {
void putMonster(eMonster m) { void putMonster(eMonster m) {
cell *c = farempty(); cell *c = farempty();
changes.ccell(c);
c->hitpoints = 3; c->hitpoints = 3;
c->monst = m; c->monst = m;
playSeenSound(c); playSeenSound(c);
@ -3655,6 +3662,10 @@ EX namespace halloween {
farempty()->item = itTreat; farempty()->item = itTreat;
int itr = items[itTreat]; int itr = items[itTreat];
items[itOrbTime] += 30; items[itOrbTime] += 30;
changes.value_keep(dragoncount);
changes.value_keep(demoncount);
changes.value_keep(swordpower);
int monpower = 19 + itr + hrand(itr); int monpower = 19 + itr + hrand(itr);
int mcount = 0; int mcount = 0;
while(monpower > 0) { while(monpower > 0) {

View File

@ -378,6 +378,7 @@ EX void knightFlavorMessage(cell *c2) {
bool tooeasy = (rad < newRoundTableRadius()); bool tooeasy = (rad < newRoundTableRadius());
static int msgid = 0; static int msgid = 0;
changes.value_keep(msgid);
retry: retry:
if(msgid >= 32) msgid = 0; if(msgid >= 32) msgid = 0;

View File

@ -358,7 +358,7 @@ EX void bfs() {
if(c2->monst) { if(c2->monst) {
if(isHaunted(c2->land) && if(isHaunted(c2->land) &&
c2->monst != moGhost && c2->monst != moZombie && c2->monst != moNecromancer) c2->monst != moGhost && c2->monst != moZombie && c2->monst != moNecromancer)
survivalist = false; fail_survivalist();
if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) { if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) {
havewhat |= HF_HEX; havewhat |= HF_HEX;
if(c2->mondir != NODIR) if(c2->mondir != NODIR)
@ -753,6 +753,7 @@ EX void monstersTurn() {
if(!phase1) heat::processfires(); if(!phase1) heat::processfires();
for(cell *c: crush_now) { for(cell *c: crush_now) {
changes.ccell(c);
playSound(NULL, "closegate"); playSound(NULL, "closegate");
if(canAttack(c, moCrusher, c, c->monst, AF_GETPLAYER | AF_CRUSH)) { if(canAttack(c, moCrusher, c, c->monst, AF_GETPLAYER | AF_CRUSH)) {
attackMonster(c, AF_MSG | AF_GETPLAYER | AF_CRUSH, moCrusher); attackMonster(c, AF_MSG | AF_GETPLAYER | AF_CRUSH, moCrusher);
@ -762,6 +763,8 @@ EX void monstersTurn() {
explodeBarrel(c); explodeBarrel(c);
} }
changes.value_keep(crush_now);
changes.value_keep(crush_next);
crush_now = move(crush_next); crush_now = move(crush_next);
crush_next.clear(); crush_next.clear();

View File

@ -3875,6 +3875,7 @@ struct flashdata {
vector<flashdata> flashes; vector<flashdata> flashes;
EX void drawBubble(cell *c, color_t col, string s, ld size) { EX void drawBubble(cell *c, color_t col, string s, ld size) {
LATE( drawBubble(c, col, s, size); )
auto fd = flashdata(ticks, 1000, c, col, 0); auto fd = flashdata(ticks, 1000, c, col, 0);
fd.text = s; fd.text = s;
fd.angle = size; fd.angle = size;
@ -3889,6 +3890,7 @@ EX void drawBigFlash(cell *c) {
} }
EX void drawParticleSpeed(cell *c, color_t col, int speed) { EX void drawParticleSpeed(cell *c, color_t col, int speed) {
LATE( drawParticleSpeed(c, col, speed); )
if(vid.particles && !confusingGeometry()) if(vid.particles && !confusingGeometry())
flashes.push_back(flashdata(ticks, rand() % 16, c, col, speed)); flashes.push_back(flashdata(ticks, rand() % 16, c, col, speed));
} }
@ -3906,6 +3908,7 @@ EX void drawFireParticles(cell *c, int qty, int maxspeed IS(100)) {
} }
EX void fallingFloorAnimation(cell *c, eWall w IS(waNone), eMonster m IS(moNone)) { EX void fallingFloorAnimation(cell *c, eWall w IS(waNone), eMonster m IS(moNone)) {
if(!wmspatial) return; if(!wmspatial) return;
LATE( fallingFloorAnimation(c, w, m); )
fallanim& fa = fallanims[c]; fallanim& fa = fallanims[c];
fa.t_floor = ticks; fa.t_floor = ticks;
fa.walltype = w; fa.m = m; fa.walltype = w; fa.m = m;
@ -3913,6 +3916,7 @@ EX void fallingFloorAnimation(cell *c, eWall w IS(waNone), eMonster m IS(moNone)
} }
EX void fallingMonsterAnimation(cell *c, eMonster m, int id IS(multi::cpid)) { EX void fallingMonsterAnimation(cell *c, eMonster m, int id IS(multi::cpid)) {
if(!mmspatial) return; if(!mmspatial) return;
LATE( fallingMonsterAnimation(c, m, id); )
fallanim& fa = fallanims[c]; fallanim& fa = fallanims[c];
fa.t_mon = ticks; fa.t_mon = ticks;
fa.m = m; fa.m = m;
@ -5167,6 +5171,7 @@ EX transmatrix iadj(const movei& m) {
EX void animateMovement(const movei& m, int layer) { EX void animateMovement(const movei& m, int layer) {
if(vid.mspeed >= 5) return; // no animations! if(vid.mspeed >= 5) return; // no animations!
LATE ( animateMovement(m, layer); )
transmatrix T = iadj(m); transmatrix T = iadj(m);
bool found_s = animations[layer].count(m.s); bool found_s = animations[layer].count(m.s);
animation& a = animations[layer][m.t]; animation& a = animations[layer][m.t];
@ -5187,6 +5192,7 @@ EX void animateMovement(const movei& m, int layer) {
} }
EX void animateAttack(const movei& m, int layer) { EX void animateAttack(const movei& m, int layer) {
LATE( animateAttack(m, layer); )
if(vid.mspeed >= 5) return; // no animations! if(vid.mspeed >= 5) return; // no animations!
transmatrix T = iadj(m); transmatrix T = iadj(m);
bool newanim = !animations[layer].count(m.s); bool newanim = !animations[layer].count(m.s);
@ -5200,6 +5206,7 @@ vector<pair<cell*, animation> > animstack;
EX void indAnimateMovement(const movei& m, int layer) { EX void indAnimateMovement(const movei& m, int layer) {
if(vid.mspeed >= 5) return; // no animations! if(vid.mspeed >= 5) return; // no animations!
LATE( indAnimateMovement(m, layer); )
if(animations[layer].count(m.t)) { if(animations[layer].count(m.t)) {
animation res = animations[layer][m.t]; animation res = animations[layer][m.t];
animations[layer].erase(m.t); animations[layer].erase(m.t);
@ -5218,6 +5225,7 @@ EX void indAnimateMovement(const movei& m, int layer) {
} }
EX void commitAnimations(int layer) { EX void commitAnimations(int layer) {
LATE( commitAnimations(layer); )
for(int i=0; i<isize(animstack); i++) for(int i=0; i<isize(animstack); i++)
animations[layer][animstack[i].first] = animstack[i].second; animations[layer][animstack[i].first] = animstack[i].second;
animstack.clear(); animstack.clear();

View File

@ -777,6 +777,10 @@ static inline void set_flag(flagtype& f, flagtype which, bool b) {
} }
/** this macro is used to delay performing the action in case if everything is rolled back */
#define LATE(x) \
if(changes.on) { changes.at_commit([=] { x; }); return; }
// assert macro // assert macro
#ifdef NDEBUG #ifdef NDEBUG
#define hassert(condition) if(!condition) __builtin_unreachable() #define hassert(condition) if(!condition) __builtin_unreachable()

View File

@ -146,11 +146,12 @@ EX bool collectItem(cell *c2, bool telekinesis IS(false)) {
babymap.erase(c2); babymap.erase(c2);
int bold = seekbits; int bold = seekbits;
seekbits = bnew; seekbits = bnew;
tortoise::last = seekbits; changes.value_set(tortoise::last, seekbits);
if(seek()) { if(seek()) {
cell *c = passable(cwt.at, NULL, 0) ? cwt.at : c2; cell *c = passable(cwt.at, NULL, 0) ? cwt.at : c2;
c->item = itBabyTortoise; c->item = itBabyTortoise;
if(c == c2) dopickup = false; if(c == c2) dopickup = false;
changes.map_value(babymap, c);
babymap[c] = bold; babymap[c] = bold;
} }
else items[itBabyTortoise]++; else items[itBabyTortoise]++;
@ -289,7 +290,8 @@ EX void dropGreenStone(cell *c) {
else { else {
c->item = itGreenStone; c->item = itGreenStone;
addMessage(XLAT("You drop %the1.", itGreenStone)); addMessage(XLAT("You drop %the1.", itGreenStone));
if(isHaunted(cwt.at->land)) survivalist = false; if(isHaunted(cwt.at->land))
fail_survivalist();
} }
} }
else { else {
@ -402,7 +404,8 @@ EX int maxgold() {
EX void updateHi(eItem it, int v) { EX void updateHi(eItem it, int v) {
if(!yendor::on) if(!yendor::on)
if(v > hiitems[modecode()][it]) hiitems[modecode()][it] = v; if(v > hiitems[modecode()][it])
changes.value_set(hiitems[modecode()][it], v);
} }
EX void gainItem(eItem it) { EX void gainItem(eItem it) {

View File

@ -8,9 +8,6 @@
#include "hyper.h" #include "hyper.h"
namespace hr { namespace hr {
/** offscreen cells to take care off */
EX vector<cell*> offscreen;
EX void initcell(cell *c) { EX void initcell(cell *c) {
c->mpdist = INFD; // minimum distance from the player, ever c->mpdist = INFD; // minimum distance from the player, ever
c->cpdist = INFD; // current distance from the player c->cpdist = INFD; // current distance from the player

View File

@ -85,11 +85,15 @@ EX void moveEffect(const movei& mi, eMonster m) {
if(cf && isPrincess(m)) princess::move(mi); if(cf && isPrincess(m)) princess::move(mi);
if(cf && m == moTortoise) { if(cf && m == moTortoise) {
changes.map_value(tortoise::emap, ct);
changes.map_value(tortoise::emap, cf);
tortoise::emap[ct] = tortoise::getb(cf); tortoise::emap[ct] = tortoise::getb(cf);
tortoise::emap.erase(cf); tortoise::emap.erase(cf);
} }
if(cf && ct->item == itBabyTortoise && !cf->item) { if(cf && ct->item == itBabyTortoise && !cf->item) {
changes.map_value(tortoise::babymap, ct);
changes.map_value(tortoise::babymap, cf);
cf->item = itBabyTortoise; cf->item = itBabyTortoise;
ct->item = itNone; ct->item = itNone;
animateMovement(mi.rev(), LAYER_BOAT); animateMovement(mi.rev(), LAYER_BOAT);
@ -1520,7 +1524,7 @@ EX int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) {
isInactiveEnemy(c2,m) ? 1000 : isInactiveEnemy(c2,m) ? 1000 :
-500; -500;
else if(monstersnear(c2, NULL, m, NULL, c)) val = 50; // linked with mouse suicide! else if(monstersnear(c2, m, NULL, c)) val = 50; // linked with mouse suicide!
else if(passable_for(m, c2, c, 0)) { else if(passable_for(m, c2, c, 0)) {
if(mine::marked_mine(c2) && !ignoresPlates(m)) if(mine::marked_mine(c2) && !ignoresPlates(m))
val = 50; val = 50;

View File

@ -78,7 +78,8 @@ EX int intensify(int val) {
EX bool reduceOrbPower(eItem it, int cap) { EX bool reduceOrbPower(eItem it, int cap) {
if(items[it] && (lastorbused[it] || (it == itOrbShield && items[it]>3) || !markOrb(itOrbTime))) { if(items[it] && (lastorbused[it] || (it == itOrbShield && items[it]>3) || !markOrb(itOrbTime))) {
items[it] -= multi::activePlayers(); items[it] -= multi::activePlayers();
if(isHaunted(cwt.at->land)) survivalist = false; if(isHaunted(cwt.at->land))
fail_survivalist();
if(items[it] < 0) items[it] = 0; if(items[it] < 0) items[it] = 0;
if(items[it] > cap && markOrb(itOrbIntensity)) cap = intensify(cap); if(items[it] > cap && markOrb(itOrbIntensity)) cap = intensify(cap);
if(items[it] > cap && timerghost) items[it] = cap; if(items[it] > cap && timerghost) items[it] = cap;
@ -589,7 +590,7 @@ void teleportTo(cell *dest) {
mine::auto_teleport_charges(); mine::auto_teleport_charges();
} }
EX void jumpTo(cell *dest, eItem byWhat, int bonuskill IS(0), eMonster dashmon IS(moNone)) { EX bool jumpTo(orbAction a, cell *dest, eItem byWhat, int bonuskill IS(0), eMonster dashmon IS(moNone)) {
if(byWhat != itStrongWind) playSound(dest, "orb-frog"); if(byWhat != itStrongWind) playSound(dest, "orb-frog");
cell *from = cwt.at; cell *from = cwt.at;
@ -620,6 +621,17 @@ EX void jumpTo(cell *dest, eItem byWhat, int bonuskill IS(0), eMonster dashmon I
cwt.spin = i; cwt.spin = i;
flipplayer = true; flipplayer = true;
} }
if(!monstersnearO(a, dest, moPlayer, NULL, cwt.at)) {
changes.rollback();
return false;
}
if(isCheck(a)) {
changes.rollback();
return true;
}
countLocalTreasure(); countLocalTreasure();
sword::reset(); sword::reset();
@ -650,6 +662,8 @@ EX void jumpTo(cell *dest, eItem byWhat, int bonuskill IS(0), eMonster dashmon I
shmup::teleported(); shmup::teleported();
else else
monstersTurn(); monstersTurn();
return true;
} }
void growIvyTo(const movei& mi) { void growIvyTo(const movei& mi) {
@ -1019,18 +1033,13 @@ void useOrbOfDragon(cell *c) {
checkmoveO(); checkmoveO();
} }
bool monstersnearO(orbAction a, cell *c, cell *nocount, eMonster who, cell *pushto, cell *comefrom) { EX bool monstersnearO(orbAction a, cell *c, eMonster who, cell *pushto, cell *comefrom) {
// printf("[a = %d] ", a); // printf("[a = %d] ", a);
if(shmup::on) return false; if(shmup::on) return false;
if(a == roCheck && multi::players > 1) if(a == roCheck && multi::players > 1)
return true; return true;
else if(a == roMultiCheck) return false; else if(a == roMultiCheck) return false;
else return monstersnear(c, nocount, who, pushto, comefrom); else return monstersnear(c, who, pushto, comefrom);
}
bool monstersnearOS(orbAction a, cell *c, cell *nocount, eMonster who, cell *pushto, cell *comefrom) {
dynamicval<bool> b(used_impact, items[itOrbImpact]);
return monstersnearO(a, c, nocount, who, pushto, comefrom);
} }
EX bool isCheck(orbAction a) { return a == roCheck || a == roMultiCheck; } EX bool isCheck(orbAction a) { return a == roCheck || a == roMultiCheck; }
@ -1149,9 +1158,9 @@ EX eItem targetRangedOrb(cell *c, orbAction a) {
} }
// (0-) strong wind // (0-) strong wind
if(items[itStrongWind] && c->cpdist == 2 && cwt.at == whirlwind::jumpFromWhereTo(c, true) && !monstersnearO(a, c, NULL, moPlayer, NULL, cwt.at)) { if(items[itStrongWind] && c->cpdist == 2 && cwt.at == whirlwind::jumpFromWhereTo(c, true)) {
if(!isCheck(a)) jumpTo(c, itStrongWind); changes.init();
return itStrongWind; if(jumpTo(a, c, itStrongWind)) return itStrongWind;
} }
// (0x) control // (0x) control
@ -1195,12 +1204,12 @@ EX eItem targetRangedOrb(cell *c, orbAction a) {
if(c->monst) { if(c->monst) {
if(!canAttack(cf, moFriendlyIvy, c, c->monst, 0)) continue; if(!canAttack(cf, moFriendlyIvy, c, c->monst, 0)) continue;
if(monstersnear(cwt.at, c, moPlayer, NULL, cwt.at)) continue; if(monstersnear(cwt.at, moPlayer, NULL, cwt.at)) continue;
} }
else { else {
if(!passable(c, cf, P_ISPLAYER | P_MONSTER)) continue; if(!passable(c, cf, P_ISPLAYER | P_MONSTER)) continue;
if(strictlyAgainstGravity(c, cf, false, MF_IVY)) continue; if(strictlyAgainstGravity(c, cf, false, MF_IVY)) continue;
if(monstersnear(cwt.at, NULL, moPlayer, c, cwt.at)) continue; if(monstersnear(cwt.at, moPlayer, c, cwt.at)) continue;
} }
dirs.push_back(d); dirs.push_back(d);
} }
@ -1223,10 +1232,8 @@ EX eItem targetRangedOrb(cell *c, orbAction a) {
jumpstate = 10 + check_vault(cwt.at, c, P_ISPLAYER, jumpthru); jumpstate = 10 + check_vault(cwt.at, c, P_ISPLAYER, jumpthru);
items[itOrbAether] = i; items[itOrbAether] = i;
if(jumpstate == 16 && !monstersnearOS(a, c, jumpthru, moPlayer, NULL, cwt.at)) jumpstate = 17; if(jumpstate == 15) {
changes.init();
if(jumpstate == 17) {
if(!isCheck(a)) {
int k = tkills(); int k = tkills();
eMonster m = jumpthru->monst; eMonster m = jumpthru->monst;
if(jumpthru->wall == waShrub) { if(jumpthru->wall == waShrub) {
@ -1236,9 +1243,8 @@ EX eItem targetRangedOrb(cell *c, orbAction a) {
if(m) if(m)
attackMonster(jumpthru, AF_NORMAL | AF_MSG, moPlayer); attackMonster(jumpthru, AF_NORMAL | AF_MSG, moPlayer);
k = tkills() - k; k = tkills() - k;
jumpTo(c, itOrbDash, k, m); if(jumpTo(a, c, itOrbDash, k, m)) jumpstate = 16;
} if(jumpstate == 16) return itOrbDash;
return itOrbDash;
} }
} }
@ -1247,10 +1253,10 @@ EX eItem targetRangedOrb(cell *c, orbAction a) {
if(i) items[itOrbAether] = i-1; if(i) items[itOrbAether] = i-1;
jumpstate = check_jump(cwt.at, c, P_ISPLAYER, jumpthru); jumpstate = check_jump(cwt.at, c, P_ISPLAYER, jumpthru);
items[itOrbAether] = i; items[itOrbAether] = i;
if(jumpstate == 3 && !monstersnearOS(a, c, NULL, moPlayer, NULL, cwt.at)) { if(jumpstate == 3) {
jumpstate = 4; changes.init();
if(!isCheck(a)) jumpTo(c, itOrbFrog); if(jumpTo(a, c, itOrbFrog)) jumpstate = 4;
return itOrbFrog; if(jumpstate == 4) return itOrbFrog;
} }
} }
@ -1260,9 +1266,9 @@ EX eItem targetRangedOrb(cell *c, orbAction a) {
if(i) items[itOrbAether] = i-1; if(i) items[itOrbAether] = i-1;
jumpstate = 20 + check_phase(cwt.at, c, P_ISPLAYER, jumpthru); jumpstate = 20 + check_phase(cwt.at, c, P_ISPLAYER, jumpthru);
items[itOrbAether] = i; items[itOrbAether] = i;
if(jumpstate == 23 && !monstersnearOS(a, c, NULL, moPlayer, NULL, cwt.at)) { if(jumpstate == 23) {
jumpstate = 24; changes.init();
if(!isCheck(a)) jumpTo(c, itOrbPhasing); if(jumpTo(a, c, itOrbPhasing)) jumpstate = 24;
} }
if(shmup::on) shmup::popmonsters(); if(shmup::on) shmup::popmonsters();
if(jumpstate == 24) return itOrbPhasing; if(jumpstate == 24) return itOrbPhasing;
@ -1302,11 +1308,18 @@ EX eItem targetRangedOrb(cell *c, orbAction a) {
if(!shmup::on && items[itRevolver] && c->monst && canAttack(cwt.at, moPlayer, c, c->monst, AF_GUN)) { if(!shmup::on && items[itRevolver] && c->monst && canAttack(cwt.at, moPlayer, c, c->monst, AF_GUN)) {
bool inrange = false; bool inrange = false;
for(cell *c1: gun_targets(cwt.at)) if(c1 == c) inrange = true; for(cell *c1: gun_targets(cwt.at)) if(c1 == c) inrange = true;
if(inrange && !monstersnearOS(a, cwt.at, c, moPlayer, NULL, cwt.at)) { if(inrange) {
if(!isCheck(a)) gun_attack(c), apply_impact(c); changes.init();
gun_attack(c), apply_impact(c);
if(monstersnearO(a, cwt.at, moPlayer, NULL, cwt.at)) {
changes.rollback();
}
else {
if(isCheck(a)) changes.rollback();
return itRevolver; return itRevolver;
} }
} }
}
// (5) psi blast (non-shmup variant) // (5) psi blast (non-shmup variant)
if(!shmup::on && items[itOrbPsi] && c->monst && (isDragon(c->monst) || !isWorm(c)) && c->monst != moShadow && c->monst != moKrakenH) { if(!shmup::on && items[itOrbPsi] && c->monst && (isDragon(c->monst) || !isWorm(c)) && c->monst != moShadow && c->monst != moKrakenH) {

View File

@ -19,6 +19,10 @@ EX bool hauntedWarning;
/** is the Survivalist achievement still valid? have we received it? */ /** is the Survivalist achievement still valid? have we received it? */
EX bool survivalist, got_survivalist; EX bool survivalist, got_survivalist;
EX void fail_survivalist() {
changes.value_set(survivalist, false);
}
/** last move was invisible */ /** last move was invisible */
EX bool invismove = false; EX bool invismove = false;
/** last move was invisible due to Orb of Fish (thus Fish still see you)*/ /** last move was invisible due to Orb of Fish (thus Fish still see you)*/
@ -113,7 +117,6 @@ EX bool checkNeedMove(bool checkonly, bool attacking) {
return false; return false;
int flags = 0; int flags = 0;
if(cwt.at->monst) { if(cwt.at->monst) {
if(checkonly) return true;
if(isMountable(cwt.at->monst)) if(isMountable(cwt.at->monst))
addMessage(XLAT("You need to dismount %the1!", cwt.at->monst)); addMessage(XLAT("You need to dismount %the1!", cwt.at->monst));
else else
@ -128,13 +131,11 @@ EX bool checkNeedMove(bool checkonly, bool attacking) {
if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbAether)) return false;
if(markOrb2(itOrbFish)) return false; if(markOrb2(itOrbFish)) return false;
if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false;
if(checkonly) return true;
flags |= AF_FALL; flags |= AF_FALL;
addMessage(XLAT("Ice below you is melting! RUN!")); addMessage(XLAT("Ice below you is melting! RUN!"));
} }
else if(!attacking && cellEdgeUnstable(cwt.at)) { else if(!attacking && cellEdgeUnstable(cwt.at)) {
if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbAether)) return false;
if(checkonly) return true;
if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false;
addMessage(XLAT("Nothing to stand on here!")); addMessage(XLAT("Nothing to stand on here!"));
} }
@ -142,29 +143,24 @@ EX bool checkNeedMove(bool checkonly, bool attacking) {
if(markOrb(itOrbFish)) return false; if(markOrb(itOrbFish)) return false;
if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbAether)) return false;
if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false;
if(checkonly) return true;
addMessage(XLAT("You have to run away from the water!")); addMessage(XLAT("You have to run away from the water!"));
} }
else if(cwt.at->wall == waClosedGate) { else if(cwt.at->wall == waClosedGate) {
if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbAether)) return false;
if(checkonly) return true;
addMessage(XLAT("The gate is closing right on you! RUN!")); addMessage(XLAT("The gate is closing right on you! RUN!"));
} }
else if(isFire(cwt.at) && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) { else if(isFire(cwt.at) && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) {
if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbAether)) return false;
if(checkonly) return true;
addMessage(XLAT("This spot will be burning soon! RUN!")); addMessage(XLAT("This spot will be burning soon! RUN!"));
} }
else if(cwt.at->wall == waMagma && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) { else if(cwt.at->wall == waMagma && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) {
if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbAether)) return false;
if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false;
if(checkonly) return true;
addMessage(XLAT("Run away from the magma!")); addMessage(XLAT("Run away from the magma!"));
} }
else if(cwt.at->wall == waChasm) { else if(cwt.at->wall == waChasm) {
if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbAether)) return false;
if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false;
if(checkonly) return true;
flags |= AF_FALL; flags |= AF_FALL;
addMessage(XLAT("The floor has collapsed! RUN!")); addMessage(XLAT("The floor has collapsed! RUN!"));
} }
@ -175,7 +171,6 @@ EX bool checkNeedMove(bool checkonly, bool attacking) {
else if(!passable(cwt.at, NULL, P_ISPLAYER)) { else if(!passable(cwt.at, NULL, P_ISPLAYER)) {
if(isFire(cwt.at)) return false; // already checked: have Shield if(isFire(cwt.at)) return false; // already checked: have Shield
if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbAether)) return false;
if(checkonly) return true;
addMessage(XLAT("Your Aether power has expired! RUN!")); addMessage(XLAT("Your Aether power has expired! RUN!"));
} }
else return false; else return false;
@ -260,7 +255,7 @@ bool pcmove::movepcto() {
lastmountpos[0] = cwt.at; lastmountpos[0] = cwt.at;
if(againstRose(cwt.at, NULL) && d<0 && !scentResistant()) { if(againstRose(cwt.at, NULL) && d<0 && !scentResistant()) {
if(!checkonly) addMessage("You just cannot stand in place, those roses smell too nicely."); addMessage("You just cannot stand in place, those roses smell too nicely.");
return false; return false;
} }
@ -270,7 +265,16 @@ bool pcmove::movepcto() {
fmsAttack = forcedmovetype == fmSkip || forcedmovetype == fmAttack; fmsAttack = forcedmovetype == fmSkip || forcedmovetype == fmAttack;
fmsActivate = forcedmovetype == fmSkip || forcedmovetype == fmActivate; fmsActivate = forcedmovetype == fmSkip || forcedmovetype == fmActivate;
return (d >= 0) ? actual_move() : stay(); changes.init();
bool b = (d >= 0) ? actual_move() : stay();
if(checkonly || !b) {
changes.rollback();
}
else if(changes.on) {
println(hlog, "error: not commited!");
changes.commit();
}
return b;
} }
bool pcmove::after_move() { bool pcmove::after_move() {
@ -294,17 +298,17 @@ bool pcmove::after_move() {
check_total_victory(); check_total_victory();
if(items[itWhirlpool] && cwt.at->land != laWhirlpool && !whirlpool::escaped) { if(items[itWhirlpool] && cwt.at->land != laWhirlpool && !whirlpool::escaped) {
whirlpool::escaped = true; changes.value_set(whirlpool::escaped, true);
achievement_gain("WHIRL1"); achievement_gain("WHIRL1");
} }
if(items[itLotus] >= 25 && !isHaunted(cwt.at->land) && survivalist && !got_survivalist) { if(items[itLotus] >= 25 && !isHaunted(cwt.at->land) && survivalist && !got_survivalist) {
got_survivalist = true; changes.value_set(got_survivalist, true);
achievement_gain("SURVIVAL"); achievement_gain("SURVIVAL");
} }
if(seenSevenMines && cwt.at->land != laMinefield) { if(seenSevenMines && cwt.at->land != laMinefield) {
seenSevenMines = false; changes.value_set(seenSevenMines, false);
achievement_gain("SEVENMINE"); achievement_gain("SEVENMINE");
} }
@ -314,11 +318,21 @@ bool pcmove::after_move() {
bool pcmove::swing() { bool pcmove::swing() {
sideAttack(cwt.at, d, moPlayer, 0); sideAttack(cwt.at, d, moPlayer, 0);
animateAttack(mi, LAYER_SMALL);
mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK);
if(monstersnear(cwt.at, moPlayer, nullptr, cwt.at)) {
changes.rollback();
if(errormsgs && !checkonly)
wouldkill("You would be killed by %the1!");
return false;
}
if(checkonly) return true;
if(changes.on) changes.commit();
animateAttack(mi, LAYER_SMALL);
if(survivalist && isHaunted(mi.t->land)) if(survivalist && isHaunted(mi.t->land))
survivalist = false; survivalist = false;
mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK);
lastmovetype = lmTree; lastmove = mi.t; lastmovetype = lmTree; lastmove = mi.t;
swordAttackStatic(); swordAttackStatic();
@ -334,23 +348,109 @@ bool pcmove::after_instant(bool kl) {
return true; return true;
} }
struct chaos_data { EX void copy_metadata(cell *x, const gcell *y) {
cell *ca, *cb; x->wall = y->wall;
gcell coa, cob; x->monst = y->monst;
x->item = y->item;
bool done; x->mondir = y->mondir;
x->stuntime = y->stuntime;
chaos_data() { x->hitpoints = y->hitpoints;
done = false; x->monmirror = y->monmirror;
ca = (cwt+1).cpeek(); if(isIcyLand(x)) {
cb = (cwt-1).cpeek(); x->landparam = y->landparam;
if(!items[itOrbChaos] || chaos_forbidden(ca) || chaos_forbidden(cb)) {
ca = cb = nullptr;
return;
} }
markOrb(itOrbChaos); x->wparam = y->wparam;
coa = *ca; }
cob = *cb;
#if HDR
extern void playSound(cell *c, const string& fname, int vol);
struct changes_t {
vector<reaction_t> rollbacks;
vector<reaction_t> commits;
bool on;
void init() {
on = true;
for(cell *dc: dcal) ccell(dc);
value_keep(kills);
value_keep(items);
}
void commit() {
on = false;
for(auto& p: commits) p();
rollbacks.clear();
commits.clear();
}
void rollback(int pos = 0) {
on = false;
while(!rollbacks.empty()) {
rollbacks.back()();
rollbacks.pop_back();
}
rollbacks.clear();
commits.clear();
}
void ccell(cell *c) {
if(!on) return;
gcell a = *c;
rollbacks.push_back([c, a] { copy_metadata(c, &a); });
}
template<class T> void value_set(T& what, T value) {
if(what == value || !on) return;
T old = what;
rollbacks.push_back([&what, old] { what = old; });
what = value;
}
template<class T> void value_add(T& what, T step) {
value_keep(what); what += step;
}
template<class T> void value_inc(T& what) { value_add(what, 1); }
template<class T> void value_keep(T& what) {
if(!on) return;
T old = what;
rollbacks.push_back([&what, old] { what = old; });
}
template<class T, class U, class V> void map_value(map<T, U>& vmap, V& key) {
if(vmap.count(key)) {
auto val = vmap[key];
at_rollback([&vmap, key, val] { vmap[key] = val; });
}
else {
at_rollback([&vmap, key] { vmap.erase(key); });
}
}
void at_commit(reaction_t act) {
if(!on) act();
else commits.emplace_back(act);
}
void at_rollback(reaction_t act) {
if(on) rollbacks.emplace_back(act);
}
};
#endif
EX changes_t changes;
void apply_chaos() {
cell *ca = (cwt+1).cpeek();
cell *cb = (cwt-1).cpeek();
if(!items[itOrbChaos] || chaos_forbidden(ca) || chaos_forbidden(cb)) return;
changes.ccell(ca);
changes.ccell(cb);
gcell coa = *ca;
gcell cob = *cb;
copy_metadata(ca, &cob); copy_metadata(ca, &cob);
copy_metadata(cb, &coa); copy_metadata(cb, &coa);
int sa = ca->mondir - ((cwt+1)+wstep).spin; int sa = ca->mondir - ((cwt+1)+wstep).spin;
@ -363,42 +463,13 @@ struct chaos_data {
ca->mondir = ((cwt+1)+wstep-sb).spin; ca->mondir = ((cwt+1)+wstep-sb).spin;
if(cb->mondir < cb->type) if(cb->mondir < cb->type)
cb->mondir = ((cwt+1)+wstep-sa).spin; cb->mondir = ((cwt+1)+wstep-sa).spin;
}
void rollback() {
done = true;
if(!ca) return;
copy_metadata(ca, &coa);
copy_metadata(cb, &cob);
}
~chaos_data() {
if(!done) {
println(hlog, "chaos_data not done");
breakhere();
}
}
void commit() {
if(cb && cb->monst == moPair) { if(cb && cb->monst == moPair) {
gcell tmp = *ca; killMonster(cb, moPlayer);
ca->monst = moPair;
ca->mondir = coa.mondir;
killMonster(ca, moPlayer);
cb->monst = ca->monst;
copy_metadata(ca, &tmp);
} }
if(ca && ca->monst == moPair) { if(ca && ca->monst == moPair) {
gcell tmp = *cb; killMonster(ca, moPlayer);
cb->monst = moPair;
cb->mondir = cob.mondir;
killMonster(cb, moPlayer);
ca->monst = cb->monst;
copy_metadata(cb, &tmp);
} }
done = true;
} }
};
bool pcmove::actual_move() { bool pcmove::actual_move() {
@ -423,7 +494,7 @@ bool pcmove::actual_move() {
} }
if(againstRose(cwt.at, c2) && !scentResistant()) { if(againstRose(cwt.at, c2) && !scentResistant()) {
if(!checkonly) addMessage("Those roses smell too nicely. You have to come towards them."); addMessage("Those roses smell too nicely. You have to come towards them.");
return false; return false;
} }
@ -463,25 +534,17 @@ bool pcmove::actual_move() {
if(isPushable(c2->wall) && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { if(isPushable(c2->wall) && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) {
mip = determinePush(cwt, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, cwt.at); }); mip = determinePush(cwt, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, cwt.at); });
if(mip.d == NO_SPACE) { if(mip.d == NO_SPACE) {
if(!checkonly) addMessage(XLAT("No room to push %the1.", c2->wall)); addMessage(XLAT("No room to push %the1.", c2->wall));
return false; return false;
} }
chaos_data cdata; nextmovetype = lmMove;
if(monstersnear(c2, NULL, moPlayer, NULL, cwt.at)) {
cdata.rollback();
if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!");
return false;
}
if(checkonly) { nextmovetype = lmMove; cdata.rollback(); return true; }
addMessage(XLAT("You push %the1.", c2->wall)); addMessage(XLAT("You push %the1.", c2->wall));
lastmovetype = lmPush; lastmove = cwt.at; lastmovetype = lmPush; lastmove = cwt.at;
pushThumper(mip); pushThumper(mip);
cdata.commit();
return perform_actual_move(); return perform_actual_move();
} }
if(c2->item == itHolyGrail && roundTableRadius(c2) < newRoundTableRadius()) { if(c2->item == itHolyGrail && roundTableRadius(c2) < newRoundTableRadius()) {
if(!checkonly)
addMessage(XLAT("That was not a challenge. Find a larger castle!")); addMessage(XLAT("That was not a challenge. Find a larger castle!"));
return false; return false;
} }
@ -496,20 +559,14 @@ bool pcmove::actual_move() {
if(!c2->monst && cwt.at->wall == waBoat && cwt.at->item != itOrbYendor && boatGoesThrough(c2) && markOrb(itOrbWater) && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { if(!c2->monst && cwt.at->wall == waBoat && cwt.at->item != itOrbYendor && boatGoesThrough(c2) && markOrb(itOrbWater) && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) {
if(havePushConflict(cwt.at, checkonly)) return false; if(havePushConflict(cwt.at, checkonly)) return false;
chaos_data cdata;
if(monstersnear(c2,NULL,moPlayer,NULL,cwt.at)) {
if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!");
cdata.rollback();
return false;
}
if(checkonly) { nextmovetype = lmMove; cdata.rollback(); return true; } if(checkonly) { nextmovetype = lmMove; return true; }
if(c2->item && !cwt.at->item) moveItem(c2, cwt.at, false), boatmove = true; if(c2->item && !cwt.at->item) moveItem(c2, cwt.at, false), boatmove = true;
placeWater(c2, cwt.at); placeWater(c2, cwt.at);
moveBoat(mi); moveBoat(mi);
changes.ccell(c2);
c2->mondir = revhint(cwt.at, d); c2->mondir = revhint(cwt.at, d);
if(c2->item) boatmove = !boatmove; if(c2->item) boatmove = !boatmove;
cdata.commit();
return perform_actual_move(); return perform_actual_move();
} }
@ -522,7 +579,6 @@ bool pcmove::boat_move() {
if(havePushConflict(cwt.at, checkonly)) return false; if(havePushConflict(cwt.at, checkonly)) return false;
if(againstWind(c2, cwt.at)) { if(againstWind(c2, cwt.at)) {
if(!checkonly)
addMessage(XLAT(airdist(c2) < 3 ? "The Air Elemental blows you away!" : "You cannot go against the wind!")); addMessage(XLAT(airdist(c2) < 3 ? "The Air Elemental blows you away!" : "You cannot go against the wind!"));
return false; return false;
} }
@ -530,7 +586,6 @@ bool pcmove::boat_move() {
if(againstCurrent(c2, cwt.at) && !markOrb(itOrbWater)) { if(againstCurrent(c2, cwt.at) && !markOrb(itOrbWater)) {
if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state) if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state)
return after_escape(); return after_escape();
if(!checkonly)
addMessage(XLAT("You cannot go against the current!")); addMessage(XLAT("You cannot go against the current!"));
return false; return false;
} }
@ -538,23 +593,13 @@ bool pcmove::boat_move() {
if(cwt.at->item == itOrbYendor) { if(cwt.at->item == itOrbYendor) {
if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state) if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state)
return after_escape(); return after_escape();
if(!checkonly)
addMessage(XLAT("The Orb of Yendor is locked in with powerful magic.")); addMessage(XLAT("The Orb of Yendor is locked in with powerful magic."));
return false; return false;
} }
chaos_data cdata; nextmovetype = lmMove;
if(monstersnear(c2, NULL, moPlayer, NULL, cwt.at)) {
if(!checkonly && errormsgs)
wouldkill("%The1 would kill you there!");
cdata.rollback();
return false;
}
if(checkonly) { nextmovetype = lmMove; cdata.rollback(); return true; }
moveBoat(mi); moveBoat(mi);
boatmove = true; boatmove = true;
cdata.commit();
return perform_actual_move(); return perform_actual_move();
} }
@ -603,36 +648,25 @@ bool pcmove::after_escape() {
if(c2->wall == waBigStatue && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { if(c2->wall == waBigStatue && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) {
if(!canPushStatueOn(cwt.at)) { if(!canPushStatueOn(cwt.at)) {
if(!checkonly) {
if(isFire(cwt.at)) if(isFire(cwt.at))
addMessage(XLAT("You have to escape first!")); addMessage(XLAT("You have to escape first!"));
else else
addMessage(XLAT("There is not enough space!")); addMessage(XLAT("There is not enough space!"));
}
return false; return false;
} }
if(havePushConflict(cwt.at, checkonly)) return false; if(havePushConflict(cwt.at, checkonly)) return false;
eWall save_c2 = c2->wall; changes.ccell(c2);
eWall save_cw = cwt.at->wall; changes.ccell(cwt.at);
c2->wall = cwt.at->wall; c2->wall = cwt.at->wall;
if(doesnotFall(cwt.at)) if(doesnotFall(cwt.at))
cwt.at->wall = waBigStatue; cwt.at->wall = waBigStatue;
chaos_data cdata; nextmovetype = lmMove;
if(monstersnear(c2,NULL,moPlayer,NULL,cwt.at)) {
cdata.rollback();
if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!");
c2->wall = save_c2; cwt.at->wall = save_cw;
return false;
}
if(checkonly) { cdata.rollback(); c2->wall = save_c2; cwt.at->wall = save_cw; nextmovetype = lmMove; return true; }
addMessage(XLAT("You push %the1 behind you!", waBigStatue)); addMessage(XLAT("You push %the1 behind you!", waBigStatue));
animateMovement(mi.rev(), LAYER_BOAT); animateMovement(mi.rev(), LAYER_BOAT);
cdata.commit();
return perform_actual_move(); return perform_actual_move();
} }
@ -650,15 +684,12 @@ bool pcmove::after_escape() {
if(attackable && fmsAttack) { if(attackable && fmsAttack) {
if(checkNeedMove(checkonly, true)) return false; if(checkNeedMove(checkonly, true)) return false;
if(monstersnear(cwt.at,c2,moPlayer,NULL,cwt.at)) { nextmovetype = nm ? lmAttack : lmSkip;
if(!checkonly && errormsgs) wouldkill("%The1 would get you!");
return false;
}
if(checkonly) { nextmovetype = nm ? lmAttack : lmSkip; return true; }
if(c2->wall == waSmallTree) { if(c2->wall == waSmallTree) {
drawParticles(c2, winf[c2->wall].color, 4); drawParticles(c2, winf[c2->wall].color, 4);
addMessage(XLAT("You chop down the tree.")); addMessage(XLAT("You chop down the tree."));
playSound(c2, "hit-axe" + pick123()); playSound(c2, "hit-axe" + pick123());
changes.ccell(c2);
c2->wall = waNone; c2->wall = waNone;
return swing(); return swing();
} }
@ -666,6 +697,7 @@ bool pcmove::after_escape() {
drawParticles(c2, winf[c2->wall].color, 8); drawParticles(c2, winf[c2->wall].color, 8);
addMessage(XLAT("You start chopping down the tree.")); addMessage(XLAT("You start chopping down the tree."));
playSound(c2, "hit-axe" + pick123()); playSound(c2, "hit-axe" + pick123());
changes.ccell(c2);
c2->wall = waSmallTree; c2->wall = waSmallTree;
return swing(); return swing();
} }
@ -681,14 +713,14 @@ bool pcmove::after_escape() {
return false; return false;
} }
else if(c2->monst == moKnight) { else if(c2->monst == moKnight) {
if(!checkonly) camelot::knightFlavorMessage(c2); camelot::knightFlavorMessage(c2);
return false; return false;
} }
else if(c2->monst && (!isFriendly(c2) || c2->monst == moTameBomberbird || isMountable(c2->monst)) else if(c2->monst && (!isFriendly(c2) || c2->monst == moTameBomberbird || isMountable(c2->monst))
&& !(peace::on && !isMultitile(c2->monst) && !good_tortoise)) && !(peace::on && !isMultitile(c2->monst) && !good_tortoise))
return attack(); return attack();
else if(!passable(c2, cwt.at, P_USEBOAT | P_ISPLAYER | P_MIRROR | P_MONSTER)) { else if(!passable(c2, cwt.at, P_USEBOAT | P_ISPLAYER | P_MIRROR | P_MONSTER)) {
if(!checkonly) tell_why_impassable(); tell_why_impassable();
return false; return false;
} }
else if(fmsMove) else if(fmsMove)
@ -709,64 +741,12 @@ bool pcmove::move_if_okay() {
return false; return false;
} }
chaos_data cdata; if(switchplace_prevent(cwt.at, c2, checkonly))
if(monstersnear(c2, NULL, moPlayer, NULL, cwt.at)) {
cdata.rollback();
if(checkonly) return false;
if(items[itOrbFlash]) {
if(checkonly) { nextmovetype = lmInstant; return true; }
if(orbProtection(itOrbFlash)) return true;
activateFlash();
checkmove();
return true;
}
if(items[itOrbLightning]) {
if(checkonly) { nextmovetype = lmInstant; return true; }
if(orbProtection(itOrbLightning)) return true;
activateLightning();
checkmove();
return true;
}
if(who_kills_me == moOutlaw && items[itRevolver]) {
for(int i=0; i<c2->type; i++) {
cell *c3 = c2->move(i);
if(c3) for(int i=0; i<c3->type; i++) {
cell *c4 = c3->move(i);
if(c4 && c4->monst == moOutlaw) {
eItem i = targetRangedOrb(c4, roCheck);
if(i == itRevolver) {
targetRangedOrb(c4, roKeyboard);
return false; return false;
} if(!checkonly && warningprotection_hit(do_we_stab_a_friend(cwt.at, c2, moPlayer)))
}
}
}
}
if(!checkonly && errormsgs)
wouldkill("%The1 would kill you there!");
return false; return false;
}
if(switchplace_prevent(cwt.at, c2, checkonly)) {
cdata.rollback();
return false;
}
if(!checkonly && warningprotection_hit(do_we_stab_a_friend(cwt.at, c2, moPlayer))) {
cdata.rollback();
return false;
}
if(checkonly) {
cdata.rollback();
nextmovetype = lmMove; nextmovetype = lmMove;
return true;
}
cdata.commit();
return perform_actual_move(); return perform_actual_move();
} }
@ -810,15 +790,16 @@ bool pcmove::attack() {
if(!ca) { if(!ca) {
if(forcedmovetype == fmAttack) { if(forcedmovetype == fmAttack) {
if(monstersnear(cwt.at,c2,moPlayer,NULL,cwt.at)) { if(monstersnear(cwt.at,moPlayer,NULL,cwt.at)) {
if(!checkonly && errormsgs) wouldkill("%The1 would get you!"); changes.rollback();
if(errormsgs && !checkonly) wouldkill("%The1 would get you!");
return false; return false;
} }
if(checkonly) { nextmovetype = lmSkip; return true; } nextmovetype = lmSkip;
addMessage(XLAT("You swing your sword at %the1.", c2->monst)); addMessage(XLAT("You swing your sword at %the1.", c2->monst));
return swing(); return swing();
} }
if(!checkonly) tell_why_cannot_attack(); tell_why_cannot_attack();
return false; return false;
} }
@ -842,21 +823,16 @@ bool pcmove::attack() {
if(!(isWatery(cwt.at) && c2->monst == moWaterElemental) && checkNeedMove(checkonly, true)) if(!(isWatery(cwt.at) && c2->monst == moWaterElemental) && checkNeedMove(checkonly, true))
return false; return false;
if(monstersnear(cwt.at, c2, moPlayer, mip.t, cwt.at)) {
if(errormsgs && !checkonly)
wouldkill("You would be killed by %the1!");
return false;
}
if(c2->monst == moTameBomberbird && warningprotection_hit(moTameBomberbird)) return false; if(c2->monst == moTameBomberbird && warningprotection_hit(moTameBomberbird)) return false;
if(checkonly) { nextmovetype = lmAttack; return true; } nextmovetype = lmAttack;
mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK); mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK);
int tk = tkills(); int tk = tkills();
if(good_tortoise) { if(good_tortoise) {
changes.at_commit([c2] {
items[itBabyTortoise] += 4; items[itBabyTortoise] += 4;
updateHi(itBabyTortoise, items[itBabyTortoise]); updateHi(itBabyTortoise, items[itBabyTortoise]);
c2->item = itBabyTortoise; c2->item = itBabyTortoise;
@ -865,6 +841,7 @@ bool pcmove::attack() {
addMessage(XLAT(playergender() == GEN_F ? "You are now a tortoise heroine!" : "You are now a tortoise hero!")); addMessage(XLAT(playergender() == GEN_F ? "You are now a tortoise heroine!" : "You are now a tortoise hero!"));
c2->stuntime = 2; c2->stuntime = 2;
achievement_collection(itBabyTortoise); achievement_collection(itBabyTortoise);
});
} }
else { else {
eMonster m = c2->monst; eMonster m = c2->monst;
@ -873,13 +850,15 @@ bool pcmove::attack() {
markOrb(itOrbSlaying); markOrb(itOrbSlaying);
if(c2->monst == moTerraWarrior && hrand(100) > 2 * items[itTerra]) { if(c2->monst == moTerraWarrior && hrand(100) > 2 * items[itTerra]) {
if(hrand(2 + jiangshi_on_screen) < 2) if(hrand(2 + jiangshi_on_screen) < 2)
wandering_jiangshi++; changes.value_add(wandering_jiangshi, 1);
} }
attackMonster(c2, attackflags | AF_MSG, moPlayer); attackMonster(c2, attackflags | AF_MSG, moPlayer);
if(m == moRusalka) { if(m == moRusalka) {
changes.ccell(cwt.at);
if(cwt.at->wall == waNone) cwt.at->wall = waShallow; if(cwt.at->wall == waNone) cwt.at->wall = waShallow;
else if(cwt.at->wall == waShallow) cwt.at->wall = waDeepWater; else if(cwt.at->wall == waShallow) cwt.at->wall = waDeepWater;
} }
changes.ccell(c2);
// salamanders are stunned for longer time when pushed into a wall // salamanders are stunned for longer time when pushed into a wall
if(c2->monst == moSalamander && (mip.t == c2 || !mip.t)) c2->stuntime = 10; if(c2->monst == moSalamander && (mip.t == c2 || !mip.t)) c2->stuntime = 10;
if(!c2->monst) produceGhost(c2, m, moPlayer); if(!c2->monst) produceGhost(c2, m, moPlayer);
@ -891,6 +870,16 @@ bool pcmove::attack() {
sideAttack(cwt.at, d, moPlayer, tkills() - tk); sideAttack(cwt.at, d, moPlayer, tkills() - tk);
lastmovetype = lmAttack; lastmove = c2; lastmovetype = lmAttack; lastmove = c2;
swordAttackStatic(); swordAttackStatic();
if(monstersnear(cwt.at, moPlayer, nullptr, cwt.at)) {
changes.rollback();
if(errormsgs && !checkonly)
wouldkill("You would be killed by %the1!");
return false;
}
if(checkonly) return true;
if(changes.on) changes.commit();
return after_move(); return after_move();
} }
@ -898,20 +887,6 @@ EX bool chaos_forbidden(cell *c) {
return among(c->wall, waMirrorWall, waBarrier, waRoundTable) || isMultitile(c->monst); return among(c->wall, waMirrorWall, waBarrier, waRoundTable) || isMultitile(c->monst);
} }
EX void copy_metadata(cell *x, gcell *y) {
x->wall = y->wall;
x->monst = y->monst;
x->item = y->item;
x->mondir = y->mondir;
x->stuntime = y->stuntime;
x->hitpoints = y->hitpoints;
x->monmirror = y->monmirror;
if(isIcyLand(x)) {
x->landparam = y->landparam;
}
x->wparam = y->wparam;
}
bool pcmove::perform_actual_move() { bool pcmove::perform_actual_move() {
cell*& c2 = mi.t; cell*& c2 = mi.t;
flipplayer = true; if(multi::players > 1) multi::flipped[multi::cpid] = true; flipplayer = true; if(multi::players > 1) multi::flipped[multi::cpid] = true;
@ -989,10 +964,63 @@ void pcmove::handle_friendly_ivy() {
bool pcmove::perform_move_or_jump() { bool pcmove::perform_move_or_jump() {
lastmovetype = lmMove; lastmove = cwt.at; lastmovetype = lmMove; lastmove = cwt.at;
apply_chaos();
stabbingAttack(cwt.at, mi.t, moPlayer); stabbingAttack(cwt.at, mi.t, moPlayer);
cell *c1 = cwt.at; cell *c1 = cwt.at;
changes.value_keep(cwt);
cwt += wstep; cwt += wstep;
mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK | mirror::GO);
playerMoveEffects(c1, mi.t);
if(mi.t->monst == moFriendlyIvy) changes.ccell(mi.t), mi.t->monst = moNone;
if(monstersnear(cwt.at, moPlayer, nullptr, c1)) {
changes.rollback();
/* todo
if(items[itOrbFlash]) {
if(checkonly) { nextmovetype = lmInstant; return true; }
if(orbProtection(itOrbFlash)) return true;
activateFlash();
checkmove();
return true;
}
if(items[itOrbLightning]) {
if(checkonly) { nextmovetype = lmInstant; return true; }
if(orbProtection(itOrbLightning)) return true;
activateLightning();
checkmove();
return true;
}
if(who_kills_me == moOutlaw && items[itRevolver]) {
for(int i=0; i<c2->type; i++) {
cell *c3 = c2->move(i);
if(c3) for(int i=0; i<c3->type; i++) {
cell *c4 = c3->move(i);
if(c4 && c4->monst == moOutlaw) {
eItem i = targetRangedOrb(c4, roCheck);
if(i == itRevolver) {
targetRangedOrb(c4, roKeyboard);
return false;
}
}
}
}
}
*/
if(errormsgs && !checkonly) wouldkill("%The1 would kill you there!");
return false;
}
if(checkonly) return true;
if(changes.on) changes.commit();
if(switchplaces) { if(switchplaces) {
indAnimateMovement(mi, LAYER_SMALL); indAnimateMovement(mi, LAYER_SMALL);
indAnimateMovement(mi.rev(), LAYER_SMALL); indAnimateMovement(mi.rev(), LAYER_SMALL);
@ -1002,12 +1030,6 @@ bool pcmove::perform_move_or_jump() {
animateMovement(mi, LAYER_SMALL); animateMovement(mi, LAYER_SMALL);
current_display->which_copy = current_display->which_copy * adj(mi); current_display->which_copy = current_display->which_copy * adj(mi);
mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK | mirror::GO);
playerMoveEffects(c1, mi.t);
if(mi.t->monst == moFriendlyIvy) mi.t->monst = moNone;
countLocalTreasure(); countLocalTreasure();
landvisited[cwt.at->land] = true; landvisited[cwt.at->land] = true;
afterplayermoved(); afterplayermoved();
@ -1023,13 +1045,16 @@ bool pcmove::stay() {
lastmovetype = lmSkip; lastmove = NULL; lastmovetype = lmSkip; lastmove = NULL;
if(checkNeedMove(checkonly, false)) if(checkNeedMove(checkonly, false))
return false; return false;
if(monstersnear(cwt.at, NULL, moPlayer, NULL, cwt.at)) { swordAttackStatic();
nextmovetype = lmSkip;
if(monstersnear(cwt.at, moPlayer, nullptr, cwt.at)) {
changes.rollback();
if(errormsgs && !checkonly) if(errormsgs && !checkonly)
wouldkill("%The1 would get you!"); wouldkill("%The1 would get you!");
return false; return false;
} }
if(checkonly) { nextmovetype = lmSkip; return true; } if(checkonly) return true;
swordAttackStatic(); if(changes.on) changes.commit();
if(d == -2) if(d == -2)
dropGreenStone(cwt.at); dropGreenStone(cwt.at);
if(cellUnstable(cwt.at) && !markOrb(itOrbAether)) if(cellUnstable(cwt.at) && !markOrb(itOrbAether))
@ -1139,6 +1164,7 @@ EX void playerMoveEffects(cell *c1, cell *c2) {
if(peace::on) items[itOrbSword] = c2->land == laBurial ? 100 : 0; if(peace::on) items[itOrbSword] = c2->land == laBurial ? 100 : 0;
changes.value_keep(sword::dir[multi::cpid]);
sword::dir[multi::cpid] = sword::shift(c1, c2, sword::dir[multi::cpid]); sword::dir[multi::cpid] = sword::shift(c1, c2, sword::dir[multi::cpid]);
destroyWeakBranch(c1, c2, moPlayer); destroyWeakBranch(c1, c2, moPlayer);
@ -1172,7 +1198,7 @@ EX void playerMoveEffects(cell *c1, cell *c2) {
c2->landparam = 40; c2->landparam = 40;
if((c2->land == laHauntedWall || c2->land == laHaunted) && !hauntedWarning) { if((c2->land == laHauntedWall || c2->land == laHaunted) && !hauntedWarning) {
hauntedWarning = true; changes.value_set(hauntedWarning, true);
addMessage(XLAT("You become a bit nervous...")); addMessage(XLAT("You become a bit nervous..."));
addMessage(XLAT("Better not to let your greed make you stray from your path.")); addMessage(XLAT("Better not to let your greed make you stray from your path."));
playSound(c2, "nervous"); playSound(c2, "nervous");
@ -1344,13 +1370,11 @@ EX bool havePushConflict(cell *pushto, bool checkonly) {
if(pushto && multi::activePlayers() > 1) { if(pushto && multi::activePlayers() > 1) {
for(int i=0; i<multi::players; i++) if(i != multi::cpid && multi::playerActive(i)) for(int i=0; i<multi::players; i++) if(i != multi::cpid && multi::playerActive(i))
if(multi::origpos[i] == pushto || multi::origtarget[i] == pushto) { if(multi::origpos[i] == pushto || multi::origtarget[i] == pushto) {
if(!checkonly)
addMessage(XLAT("Cannot push into another player!")); addMessage(XLAT("Cannot push into another player!"));
return true; return true;
} }
for(int i=0; i<isize(stalemate::moves); i++) { for(int i=0; i<isize(stalemate::moves); i++) {
if(pushto == stalemate::moves[i].pushto) { if(pushto == stalemate::moves[i].pushto) {
if(!checkonly)
addMessage(XLAT("Cannot push into the same location!")); addMessage(XLAT("Cannot push into the same location!"));
return true; return true;
} }

View File

@ -216,6 +216,7 @@ string wheresounds = HYPERPATH "sounds/";
hookset<bool(const string& s, int vol)> *hooks_sound; hookset<bool(const string& s, int vol)> *hooks_sound;
EX void playSound(cell *c, const string& fname, int vol IS(100)) { EX void playSound(cell *c, const string& fname, int vol IS(100)) {
LATE( hr::playSound(c, fname, vol); )
if(effvolume == 0) return; if(effvolume == 0) return;
if(callhandlers(false, hooks_sound, fname, vol)) return; if(callhandlers(false, hooks_sound, fname, vol)) return;
// printf("Play sound: %s\n", fname.c_str()); // printf("Play sound: %s\n", fname.c_str());

View File

@ -1518,7 +1518,6 @@ EX void finishAll() {
#if CAP_SAVE #if CAP_SAVE
saveStats(); saveStats();
#endif #endif
offscreen.clear();
clearMemory(); clearMemory();
#if ISMOBILE==0 #if ISMOBILE==0
cleargraph(); cleargraph();

View File

@ -750,6 +750,7 @@ EX namespace yendor {
} }
EX void collected(cell* c2) { EX void collected(cell* c2) {
LATE( collected(c2); )
playSound(c2, "tada"); playSound(c2, "tada");
items[itOrbShield] += 31; items[itOrbShield] += 31;
for(int i=0; i<isize(yendor::yi); i++) for(int i=0; i<isize(yendor::yi); i++)