diff --git a/achievement.cpp b/achievement.cpp index c2e60a6b..7f7632cc 100644 --- a/achievement.cpp +++ b/achievement.cpp @@ -187,6 +187,8 @@ void achievement_gain(const char* s, char flags IS(0)) { EX void achievement_collection(eItem it) { if(cheater) return; if(randomPatternsMode) return; + LATE( achievement_collection(it); ) + int q = items[it]; 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(shmup::on) return; if(randomPatternsMode) return; + LATE( achievement_count(s, current, prev); ) + if(s == "GOLEM" && current >= 5) achievement_gain("GOLEM2"); if(s == "GOLEM" && current >= 10) @@ -563,6 +567,7 @@ int specific_what = 0; EX void improve_score(int i, eItem what) { if(offlineMode) return; + LATE( improve_score(i, what); ) #ifdef HAVE_ACHIEVEMENTS if(haveLeaderboard(i)) updateHi(what, get_currentscore(i)); if(items[what] && haveLeaderboard(i)) { @@ -579,6 +584,7 @@ EX void achievement_score(int cat, int number) { if(offlineMode) return; #ifdef HAVE_ACHIEVEMENTS if(cheater) return; + LATE( achievement_score(cat, number); ) if(cat == LB_HALLOWEEN) { if(geometry != gSphere && geometry != gElliptic) return; @@ -597,6 +603,7 @@ EX void achievement_score(int cat, int number) { } EX void improveItemScores() { + LATE( improveItemScores(); ) for(int i=1; i<=12; i++) improve_score(i, eItem(i)); improve_score(17, itOrbYendor); improve_score(18, itFernFlower); @@ -669,6 +676,8 @@ int next_stat_tick; EX void achievement_final(bool really_final) { if(offlineMode) return; + LATE( achievement_final(really_final); ) + #ifdef HAVE_ACHIEVEMENTS if(ticks > next_stat_tick) { upload_score(LB_STATISTICS, time(NULL)); @@ -778,6 +787,7 @@ EX void check_total_victory() { if(!items[itHolyGrail]) return; if(items[itHyperstone] < 50) return; if(!princess::reviveAt) return; + LATE( check_total_victory(); ) hadtotalvictory = true; achievement_gain("TOTALVICTORY"); } @@ -798,6 +808,7 @@ EX void achievement_victory(bool hyper) { if(peace::on) return; if(tactic::on) return; if(chaosmode) return; + LATE( achievement_victory(hyper); ) DEBB(DF_STEAM, ("after checks")) int t = getgametime(); diff --git a/attack.cpp b/attack.cpp index abbc3ebe..0d489e9c 100644 --- a/attack.cpp +++ b/attack.cpp @@ -213,6 +213,7 @@ EX bool petrify(cell *c, eWall walltype, eMonster m) { EX void killIvy(cell *c, eMonster who) { if(c->monst == moIvyDead) return; + changes.ccell(c); if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, c->monst); c->monst = moIvyDead; // NEWYEARFIX for(int i=0; itype; 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) { if(againstWind(c, from)) return; + changes.ccell(c); // these monsters block spilling if(c->monst == moSeep || c->monst == moVineSpirit || c->monst == moShark || 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) { - 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)) spillfix(c2, t, rad-1); } @@ -314,12 +319,14 @@ EX void degradeDemons() { int dcs = isize(dcal); for(int i=0; imonst == moGreaterM || c->monst == moGreater) + if(c->monst == moGreaterM || c->monst == moGreater) { + changes.ccell(c); achievement_gain("DEMONSLAYER"); - if(c->monst == moGreaterM) c->monst = moLesserM; - if(c->monst == moGreater) c->monst = moLesser; - shmup::degradeDemons(); + if(c->monst == moGreaterM) c->monst = moLesserM; + if(c->monst == moGreater) c->monst = moLesser; + } } + shmup::degradeDemons(); } EX void stunMonster(cell *c2, eMonster killer, flagtype flags) { @@ -367,6 +374,7 @@ EX bool attackJustStuns(cell *c2, flagtype f, eMonster attacker) { } EX void minerEffect(cell *c) { + changes.ccell(c); eWall ow = c->wall; if(c->wall == waOpenGate || c->wall == waFrozenLake || c->wall == waBoat || c->wall == waStrandedBoat || @@ -390,6 +398,7 @@ EX void minerEffect(cell *c) { EX void killMutantIvy(cell *c, eMonster who) { if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, moMutant); + changes.ccell(c); removeIvy(c); for(int i=0; itype; i++) 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; for(int i=0; itype; i++) if(!isWarpedType(c->move(i)->land)) avenge = true; - if(avenge) { avengers += 2; } + if(avenge) + changes.value_add(avengers, 2); } if(m == moMirrorSpirit && who != moMimic && !(deathflags & (AF_MAGIC | AF_CRUSH))) { kills[m]--; - mirrorspirits++; + changes.value_inc(mirrorspirits); } if(isMutantIvy(m) || m == moFriendlyIvy) { pcount = 0; - if(isMutantIvy(m)) clearing::direct++; + if(isMutantIvy(m)) changes.at_commit([] { clearing::direct++; }); bignum s = ivy_total() - 1; killMutantIvy(c, who); s = ivy_total() - s; @@ -481,6 +491,7 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) { if(m == moPrincess) { princess::info *i = princess::getPrincessInfo(c); + changes.value_keep(*i); if(i) { i->princess = NULL; 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(petrify(c, waIcewall, m)) pcount = 0; heat::affect(c, -1); - forCellEx(c2, c) heat::affect(c2, -.5); + forCellEx(c2, c) { + changes.ccell(c2); + heat::affect(c2, -.5); + } } if(m == moTroll) { petrify(c, waDeadTroll, m); pcount = 0; - for(int i=0; itype; i++) if(c->move(i)) { - c->move(i)->item = itNone; - if(c->move(i)->wall == waDeadwall || c->move(i)->wall == waDeadfloor2) c->move(i)->wall = waCavewall; - if(c->move(i)->wall == waDeadfloor) c->move(i)->wall = waCavefloor; + forCellEx(c1, c) { + changes.ccell(c1); + c1->item = itNone; + if(c1->wall == waDeadwall || c1->wall == waDeadfloor2) c1->wall = waCavewall; + if(c1->wall == waDeadfloor) c1->wall = waCavefloor; } } if(m == moFjordTroll || m == moForestTroll || m == moStormTroll) { @@ -543,14 +558,16 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) { destroyHalfvine(c); minerEffect(c); brownian::dissolve_brownian(c, 1); - for(int i=0; itype; i++) if(passable(c->move(i), c, P_MONSTER | P_MIRROR | P_CLIMBUP | P_CLIMBDOWN)) { - destroyHalfvine(c->move(i)); - minerEffect(c->move(i)); - brownian::dissolve_brownian(c->move(i), 1); - if(c->move(i)->monst == moSlime || c->move(i)->monst == moSlimeNextTurn) - killMonster(c->move(i), who); + forCellEx(c1, c) if(passable(c1, c, P_MONSTER | P_MIRROR | P_CLIMBUP | P_CLIMBDOWN)) { + changes.ccell(c1); + destroyHalfvine(c1); + minerEffect(c1); + brownian::dissolve_brownian(c1, 1); + if(c1->monst == moSlime || c1->monst == moSlimeNextTurn) + killMonster(c1, who); } forCellEx(c2, c) { + changes.ccell(c2); if(c2->wall == waPalace) c2->wall = waRubble; if(c2->wall == waDeadwall) c2->wall = waDeadfloor2; if(c2->wall == waExplosiveBarrel) explodeBarrel(c2); @@ -716,12 +733,13 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) { degradeDemons(); } } - if(isIvy(c)) { + if(isIvy(c)) { pcount = 0; eMonster m = c->monst; bignum s = ivy_total() - 1; /*if((m == moIvyBranch || m == moIvyHead) && c->move(c->mondir)->monst == moIvyRoot) ivynext(c, moIvyNext); */ + changes.value_keep(clearing::imputed); killIvy(c, who); s = ivy_total() - s; 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); if(m == moAirElemental) { + changes.value_keep(airmap); airmap.clear(); for(int i=0; imonst == moAirElemental) @@ -1033,7 +1052,7 @@ EX bool flashWouldKill(cell *c, flagtype extra) { cell *c3 = c2->move(u); if(isWorm(c3)) continue; // immune to Flash 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); if(b) return true; } diff --git a/basegraph.cpp b/basegraph.cpp index e8c47bc8..791279ff 100644 --- a/basegraph.cpp +++ b/basegraph.cpp @@ -816,6 +816,7 @@ void addMessageToLog(msginfo& m, vector& log) { EX void clearMessages() { msgs.clear(); } EX void addMessage(string s, char spamtype) { + LATE( addMessage(s, spamtype); ) DEBB(DF_MSG, ("addMessage: ", s)); msginfo m; diff --git a/checkmove.cpp b/checkmove.cpp index bad4217f..5aa3756f 100644 --- a/checkmove.cpp +++ b/checkmove.cpp @@ -42,79 +42,13 @@ EX bool hasSafeOrb(cell *c) { 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) {} + stalemate1(eMonster w, cell *mt, cell *pt, cell *cf) : who(w), moveto(mt), pushto(pt), comefrom(cf) {} }; #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; fitem == itOrbFish && c->wall == waBoat) || @@ -149,7 +83,7 @@ EX bool monstersnear(stalemate1& sm) { if(havewhat&HF_OUTLAW) { 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; } } @@ -166,7 +100,7 @@ EX bool monstersnear(stalemate1& sm) { 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(elec::affected(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 @@ -174,7 +108,7 @@ EX bool monstersnear(stalemate1& sm) { 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(!passable(c2, c3, 0)) continue; if(isPlayerOn(c2) && items[itOrbFire]) continue; } // flashwitches cannot attack if it would kill another enemy @@ -185,7 +119,6 @@ EX bool monstersnear(stalemate1& sm) { // 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; @@ -267,11 +200,11 @@ EX bool monstersnear2() { 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 - 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); @@ -340,11 +273,6 @@ EX namespace stalemate { return false; } - EX bool isKilledDirectlyAt(cell *c) { - for(int i=0; iwall == waChasm) 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(afterOrb) return ecIsolator; 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)); - else if( - (c->monst || stalemate::isPushto(c)) - && - (stalemate::isPushto(c) || !stalemate::isKilled(c)) - && - c->monst != moGhost && c->monst != moIvyDead && c->monst != moIvyNext + else if(c->monst + && c->monst != moGhost && c->monst != moIvyDead && c->monst != moIvyNext && !(isDragon(c->monst) && !c->hitpoints) ) 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 EX int lightningfast; EX void checklightningfast() { + changes.value_keep(lightningfast); if(lightningfast == 1) { addMessage(XLAT("Wow! That was close.")); lightningfast = 2; @@ -700,6 +697,7 @@ struct info { eMonster m = c->monst; static int msgid = 0; + changes.value_keep(msgid); playSound(c, princessgender() ? "speak-princess" : "speak-prince"); retry: @@ -990,8 +988,10 @@ EX namespace clearing { EX void imput(cell *c) { if(bounded) return; if(score.count(c)) return; + changes.map_value(score, c); auto& is = score[c]; celltype t = get_celltype(c); + changes.map_value(stats, t); auto& stat = stats[t]; is.second = c->mondir; if(c->mpdist <= 6) { @@ -1413,10 +1413,12 @@ EX namespace mirror { bool survive = true; if(m.first == multi::cpid) { cell *c = m.second.at; + changes.ccell(c); if(!m.second.mirrored) nummirage++; auto cw2 = m.second + wstep; if(inmirror(cw2)) cw2 = reflect(cw2); cell *c2 = cw2.at; + changes.ccell(c2); if(c2->monst) { c->monst = moMimic; eMonster m2 = c2->monst; @@ -1453,6 +1455,7 @@ EX namespace mirror { } EX void act(int d, int flags) { + changes.value_keep(mirrors); destroyKilled(); unlist(); if(multi::players == 1) multi::cpid = 0; @@ -1469,6 +1472,7 @@ EX namespace mirror { } EX void breakAll() { + changes.value_keep(mirrors); destroyKilled(); unlist(); if(numplayers() == 1) @@ -2619,6 +2623,7 @@ EX namespace dragon { int penalty = 0; int maxlen = 1000; while(maxlen-->0) { + changes.ccell(c); makeflame(c, 5, false); eMonster m = c->monst; drawFireParticles(c, 16); @@ -2879,24 +2884,25 @@ EX namespace kraken { if(c->land == laKraken && !c->item) c->item = itKraken; kills[moKrakenH]++; if(checkOrb(who, itOrbStone)) c->wall = waNone; - for(int i=0; itype; i++) - if(c->move(i)->monst == moKrakenT) { + forCellEx(c1, c) + if(c1->monst == moKrakenT) { + changes.ccell(c1); drawParticles(c, minf[moKrakenT].color, 16); - c->move(i)->monst = moNone; + c1->monst = moNone; if(checkOrb(who, itOrbStone)) { - if(isWatery(c->move(i))) - c->move(i)->wall = waNone; + if(isWatery(c1)) + c1->wall = waNone; else - c->move(i)->wall = waPetrified, c->move(i)->wparam = moKrakenT; + c1->wall = waPetrified, c1->wparam = moKrakenT; } } } EX int totalhp(cell *c) { int total = 0; - for(int i=0; itype; i++) - if(c->move(i)->monst == moKrakenT) - total += c->move(i)->hitpoints; + forCellEx(c1, c) + if(c1->monst == moKrakenT) + total += c1->hitpoints; return total; } @@ -3616,6 +3622,7 @@ EX namespace halloween { void putMonster(eMonster m) { cell *c = farempty(); + changes.ccell(c); c->hitpoints = 3; c->monst = m; playSeenSound(c); @@ -3655,6 +3662,10 @@ EX namespace halloween { farempty()->item = itTreat; int itr = items[itTreat]; items[itOrbTime] += 30; + changes.value_keep(dragoncount); + changes.value_keep(demoncount); + changes.value_keep(swordpower); + int monpower = 19 + itr + hrand(itr); int mcount = 0; while(monpower > 0) { diff --git a/complex2.cpp b/complex2.cpp index 5d3cb90c..e31491ad 100644 --- a/complex2.cpp +++ b/complex2.cpp @@ -378,6 +378,7 @@ EX void knightFlavorMessage(cell *c2) { bool tooeasy = (rad < newRoundTableRadius()); static int msgid = 0; + changes.value_keep(msgid); retry: if(msgid >= 32) msgid = 0; diff --git a/environment.cpp b/environment.cpp index f1ed3d29..da93956f 100644 --- a/environment.cpp +++ b/environment.cpp @@ -358,7 +358,7 @@ EX void bfs() { if(c2->monst) { if(isHaunted(c2->land) && c2->monst != moGhost && c2->monst != moZombie && c2->monst != moNecromancer) - survivalist = false; + fail_survivalist(); if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) { havewhat |= HF_HEX; if(c2->mondir != NODIR) @@ -753,6 +753,7 @@ EX void monstersTurn() { if(!phase1) heat::processfires(); for(cell *c: crush_now) { + changes.ccell(c); playSound(NULL, "closegate"); if(canAttack(c, moCrusher, c, c->monst, AF_GETPLAYER | AF_CRUSH)) { attackMonster(c, AF_MSG | AF_GETPLAYER | AF_CRUSH, moCrusher); @@ -762,6 +763,8 @@ EX void monstersTurn() { explodeBarrel(c); } + changes.value_keep(crush_now); + changes.value_keep(crush_next); crush_now = move(crush_next); crush_next.clear(); diff --git a/graph.cpp b/graph.cpp index 0a1d15ce..6657789b 100644 --- a/graph.cpp +++ b/graph.cpp @@ -3875,6 +3875,7 @@ struct flashdata { vector flashes; 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); fd.text = s; fd.angle = size; @@ -3889,6 +3890,7 @@ EX void drawBigFlash(cell *c) { } EX void drawParticleSpeed(cell *c, color_t col, int speed) { + LATE( drawParticleSpeed(c, col, speed); ) if(vid.particles && !confusingGeometry()) 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)) { if(!wmspatial) return; + LATE( fallingFloorAnimation(c, w, m); ) fallanim& fa = fallanims[c]; fa.t_floor = ticks; 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)) { if(!mmspatial) return; + LATE( fallingMonsterAnimation(c, m, id); ) fallanim& fa = fallanims[c]; fa.t_mon = ticks; fa.m = m; @@ -5167,6 +5171,7 @@ EX transmatrix iadj(const movei& m) { EX void animateMovement(const movei& m, int layer) { if(vid.mspeed >= 5) return; // no animations! + LATE ( animateMovement(m, layer); ) transmatrix T = iadj(m); bool found_s = animations[layer].count(m.s); 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) { + LATE( animateAttack(m, layer); ) if(vid.mspeed >= 5) return; // no animations! transmatrix T = iadj(m); bool newanim = !animations[layer].count(m.s); @@ -5200,6 +5206,7 @@ vector > animstack; EX void indAnimateMovement(const movei& m, int layer) { if(vid.mspeed >= 5) return; // no animations! + LATE( indAnimateMovement(m, layer); ) if(animations[layer].count(m.t)) { animation res = animations[layer][m.t]; animations[layer].erase(m.t); @@ -5218,6 +5225,7 @@ EX void indAnimateMovement(const movei& m, int layer) { } EX void commitAnimations(int layer) { + LATE( commitAnimations(layer); ) for(int i=0; iitem = itBabyTortoise; if(c == c2) dopickup = false; + changes.map_value(babymap, c); babymap[c] = bold; } else items[itBabyTortoise]++; @@ -289,7 +290,8 @@ EX void dropGreenStone(cell *c) { else { c->item = itGreenStone; addMessage(XLAT("You drop %the1.", itGreenStone)); - if(isHaunted(cwt.at->land)) survivalist = false; + if(isHaunted(cwt.at->land)) + fail_survivalist(); } } else { @@ -402,7 +404,8 @@ EX int maxgold() { EX void updateHi(eItem it, int v) { 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) { diff --git a/mapeffects.cpp b/mapeffects.cpp index 1a2872cb..a074eb4d 100644 --- a/mapeffects.cpp +++ b/mapeffects.cpp @@ -8,9 +8,6 @@ #include "hyper.h" namespace hr { -/** offscreen cells to take care off */ -EX vector offscreen; - EX void initcell(cell *c) { c->mpdist = INFD; // minimum distance from the player, ever c->cpdist = INFD; // current distance from the player diff --git a/monstermove.cpp b/monstermove.cpp index bc5734d8..2496cfd7 100644 --- a/monstermove.cpp +++ b/monstermove.cpp @@ -85,11 +85,15 @@ EX void moveEffect(const movei& mi, eMonster m) { if(cf && isPrincess(m)) princess::move(mi); if(cf && m == moTortoise) { + changes.map_value(tortoise::emap, ct); + changes.map_value(tortoise::emap, cf); tortoise::emap[ct] = tortoise::getb(cf); tortoise::emap.erase(cf); } if(cf && ct->item == itBabyTortoise && !cf->item) { + changes.map_value(tortoise::babymap, ct); + changes.map_value(tortoise::babymap, cf); cf->item = itBabyTortoise; ct->item = itNone; animateMovement(mi.rev(), LAYER_BOAT); @@ -1520,7 +1524,7 @@ EX int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) { isInactiveEnemy(c2,m) ? 1000 : -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)) { if(mine::marked_mine(c2) && !ignoresPlates(m)) val = 50; diff --git a/orbs.cpp b/orbs.cpp index d0a62211..de9a570d 100644 --- a/orbs.cpp +++ b/orbs.cpp @@ -78,7 +78,8 @@ EX int intensify(int val) { EX bool reduceOrbPower(eItem it, int cap) { if(items[it] && (lastorbused[it] || (it == itOrbShield && items[it]>3) || !markOrb(itOrbTime))) { 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] > cap && markOrb(itOrbIntensity)) cap = intensify(cap); if(items[it] > cap && timerghost) items[it] = cap; @@ -589,7 +590,7 @@ void teleportTo(cell *dest) { 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"); 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; flipplayer = true; } + + if(!monstersnearO(a, dest, moPlayer, NULL, cwt.at)) { + changes.rollback(); + return false; + } + + if(isCheck(a)) { + changes.rollback(); + return true; + } + countLocalTreasure(); sword::reset(); @@ -650,6 +662,8 @@ EX void jumpTo(cell *dest, eItem byWhat, int bonuskill IS(0), eMonster dashmon I shmup::teleported(); else monstersTurn(); + + return true; } void growIvyTo(const movei& mi) { @@ -1019,18 +1033,13 @@ void useOrbOfDragon(cell *c) { 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); if(shmup::on) return false; if(a == roCheck && multi::players > 1) return true; else if(a == roMultiCheck) return false; - else return monstersnear(c, nocount, who, pushto, comefrom); - } - -bool monstersnearOS(orbAction a, cell *c, cell *nocount, eMonster who, cell *pushto, cell *comefrom) { - dynamicval b(used_impact, items[itOrbImpact]); - return monstersnearO(a, c, nocount, who, pushto, comefrom); + else return monstersnear(c, who, pushto, comefrom); } EX bool isCheck(orbAction a) { return a == roCheck || a == roMultiCheck; } @@ -1149,9 +1158,9 @@ EX eItem targetRangedOrb(cell *c, orbAction a) { } // (0-) strong wind - if(items[itStrongWind] && c->cpdist == 2 && cwt.at == whirlwind::jumpFromWhereTo(c, true) && !monstersnearO(a, c, NULL, moPlayer, NULL, cwt.at)) { - if(!isCheck(a)) jumpTo(c, itStrongWind); - return itStrongWind; + if(items[itStrongWind] && c->cpdist == 2 && cwt.at == whirlwind::jumpFromWhereTo(c, true)) { + changes.init(); + if(jumpTo(a, c, itStrongWind)) return itStrongWind; } // (0x) control @@ -1195,12 +1204,12 @@ EX eItem targetRangedOrb(cell *c, orbAction a) { if(c->monst) { 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 { if(!passable(c, cf, P_ISPLAYER | P_MONSTER)) 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); } @@ -1223,22 +1232,19 @@ EX eItem targetRangedOrb(cell *c, orbAction a) { jumpstate = 10 + check_vault(cwt.at, c, P_ISPLAYER, jumpthru); items[itOrbAether] = i; - if(jumpstate == 16 && !monstersnearOS(a, c, jumpthru, moPlayer, NULL, cwt.at)) jumpstate = 17; - - if(jumpstate == 17) { - if(!isCheck(a)) { - int k = tkills(); - eMonster m = jumpthru->monst; - if(jumpthru->wall == waShrub) { - addMessage(XLAT("You chop down the shrub.")); - jumpthru->wall = waNone; - } - if(m) - attackMonster(jumpthru, AF_NORMAL | AF_MSG, moPlayer); - k = tkills() - k; - jumpTo(c, itOrbDash, k, m); + if(jumpstate == 15) { + changes.init(); + int k = tkills(); + eMonster m = jumpthru->monst; + if(jumpthru->wall == waShrub) { + addMessage(XLAT("You chop down the shrub.")); + jumpthru->wall = waNone; } - return itOrbDash; + if(m) + attackMonster(jumpthru, AF_NORMAL | AF_MSG, moPlayer); + k = tkills() - k; + if(jumpTo(a, c, itOrbDash, k, m)) jumpstate = 16; + if(jumpstate == 16) return itOrbDash; } } @@ -1247,10 +1253,10 @@ EX eItem targetRangedOrb(cell *c, orbAction a) { if(i) items[itOrbAether] = i-1; jumpstate = check_jump(cwt.at, c, P_ISPLAYER, jumpthru); items[itOrbAether] = i; - if(jumpstate == 3 && !monstersnearOS(a, c, NULL, moPlayer, NULL, cwt.at)) { - jumpstate = 4; - if(!isCheck(a)) jumpTo(c, itOrbFrog); - return itOrbFrog; + if(jumpstate == 3) { + changes.init(); + if(jumpTo(a, c, itOrbFrog)) jumpstate = 4; + if(jumpstate == 4) return itOrbFrog; } } @@ -1260,9 +1266,9 @@ EX eItem targetRangedOrb(cell *c, orbAction a) { if(i) items[itOrbAether] = i-1; jumpstate = 20 + check_phase(cwt.at, c, P_ISPLAYER, jumpthru); items[itOrbAether] = i; - if(jumpstate == 23 && !monstersnearOS(a, c, NULL, moPlayer, NULL, cwt.at)) { - jumpstate = 24; - if(!isCheck(a)) jumpTo(c, itOrbPhasing); + if(jumpstate == 23) { + changes.init(); + if(jumpTo(a, c, itOrbPhasing)) jumpstate = 24; } if(shmup::on) shmup::popmonsters(); if(jumpstate == 24) return itOrbPhasing; @@ -1302,9 +1308,16 @@ EX eItem targetRangedOrb(cell *c, orbAction a) { if(!shmup::on && items[itRevolver] && c->monst && canAttack(cwt.at, moPlayer, c, c->monst, AF_GUN)) { bool inrange = false; for(cell *c1: gun_targets(cwt.at)) if(c1 == c) inrange = true; - if(inrange && !monstersnearOS(a, cwt.at, c, moPlayer, NULL, cwt.at)) { - if(!isCheck(a)) gun_attack(c), apply_impact(c); - return itRevolver; + if(inrange) { + 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; + } } } diff --git a/pcmove.cpp b/pcmove.cpp index 33105a20..1416715f 100644 --- a/pcmove.cpp +++ b/pcmove.cpp @@ -19,6 +19,10 @@ EX bool hauntedWarning; /** is the Survivalist achievement still valid? have we received it? */ EX bool survivalist, got_survivalist; +EX void fail_survivalist() { + changes.value_set(survivalist, false); + } + /** last move was invisible */ EX bool invismove = false; /** 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; int flags = 0; if(cwt.at->monst) { - if(checkonly) return true; if(isMountable(cwt.at->monst)) addMessage(XLAT("You need to dismount %the1!", cwt.at->monst)); else @@ -128,13 +131,11 @@ EX bool checkNeedMove(bool checkonly, bool attacking) { if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbFish)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; - if(checkonly) return true; flags |= AF_FALL; addMessage(XLAT("Ice below you is melting! RUN!")); } else if(!attacking && cellEdgeUnstable(cwt.at)) { if(markOrb2(itOrbAether)) return false; - if(checkonly) return true; if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; addMessage(XLAT("Nothing to stand on here!")); } @@ -142,29 +143,24 @@ EX bool checkNeedMove(bool checkonly, bool attacking) { if(markOrb(itOrbFish)) return false; if(markOrb2(itOrbAether)) 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!")); } else if(cwt.at->wall == waClosedGate) { if(markOrb2(itOrbAether)) return false; - if(checkonly) return true; addMessage(XLAT("The gate is closing right on you! RUN!")); } else if(isFire(cwt.at) && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) { if(markOrb2(itOrbAether)) return false; - if(checkonly) return true; addMessage(XLAT("This spot will be burning soon! RUN!")); } else if(cwt.at->wall == waMagma && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) { if(markOrb2(itOrbAether)) 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!")); } else if(cwt.at->wall == waChasm) { if(markOrb2(itOrbAether)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false; - if(checkonly) return true; flags |= AF_FALL; 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)) { if(isFire(cwt.at)) return false; // already checked: have Shield if(markOrb2(itOrbAether)) return false; - if(checkonly) return true; addMessage(XLAT("Your Aether power has expired! RUN!")); } else return false; @@ -260,7 +255,7 @@ bool pcmove::movepcto() { lastmountpos[0] = cwt.at; 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; } @@ -270,7 +265,16 @@ bool pcmove::movepcto() { fmsAttack = forcedmovetype == fmSkip || forcedmovetype == fmAttack; 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() { @@ -294,17 +298,17 @@ bool pcmove::after_move() { check_total_victory(); if(items[itWhirlpool] && cwt.at->land != laWhirlpool && !whirlpool::escaped) { - whirlpool::escaped = true; + changes.value_set(whirlpool::escaped, true); achievement_gain("WHIRL1"); } if(items[itLotus] >= 25 && !isHaunted(cwt.at->land) && survivalist && !got_survivalist) { - got_survivalist = true; + changes.value_set(got_survivalist, true); achievement_gain("SURVIVAL"); } if(seenSevenMines && cwt.at->land != laMinefield) { - seenSevenMines = false; + changes.value_set(seenSevenMines, false); achievement_gain("SEVENMINE"); } @@ -314,11 +318,21 @@ bool pcmove::after_move() { bool pcmove::swing() { 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)) survivalist = false; - mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK); lastmovetype = lmTree; lastmove = mi.t; swordAttackStatic(); @@ -334,72 +348,129 @@ bool pcmove::after_instant(bool kl) { return true; } -struct chaos_data { - cell *ca, *cb; - gcell coa, cob; +EX void copy_metadata(cell *x, const 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; + } + +#if HDR + +extern void playSound(cell *c, const string& fname, int vol); + +struct changes_t { + vector rollbacks; + vector commits; + bool on; - bool done; - - chaos_data() { - done = false; - ca = (cwt+1).cpeek(); - cb = (cwt-1).cpeek(); - if(!items[itOrbChaos] || chaos_forbidden(ca) || chaos_forbidden(cb)) { - ca = cb = nullptr; - return; + 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(); } - markOrb(itOrbChaos); - coa = *ca; - cob = *cb; - copy_metadata(ca, &cob); - copy_metadata(cb, &coa); - int sa = ca->mondir - ((cwt+1)+wstep).spin; - int sb = cb->mondir - ((cwt-1)+wstep).spin; - ca->stuntime = min(ca->stuntime + 3, 15); - cb->stuntime = min(cb->stuntime + 3, 15); - ca->monmirror = !ca->monmirror; - cb->monmirror = !cb->monmirror; - if(ca->mondir < ca->type) - ca->mondir = ((cwt+1)+wstep-sb).spin; - if(cb->mondir < cb->type) - cb->mondir = ((cwt+1)+wstep-sa).spin; + rollbacks.clear(); + commits.clear(); } - void rollback() { - done = true; - if(!ca) return; - copy_metadata(ca, &coa); - copy_metadata(cb, &cob); + void ccell(cell *c) { + if(!on) return; + gcell a = *c; + rollbacks.push_back([c, a] { copy_metadata(c, &a); }); } - ~chaos_data() { - if(!done) { - println(hlog, "chaos_data not done"); - breakhere(); + template 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 void value_add(T& what, T step) { + value_keep(what); what += step; + } + + template void value_inc(T& what) { value_add(what, 1); } + + template void value_keep(T& what) { + if(!on) return; + T old = what; + rollbacks.push_back([&what, old] { what = old; }); + } + + template void map_value(map& 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 commit() { - if(cb && cb->monst == moPair) { - gcell tmp = *ca; - ca->monst = moPair; - ca->mondir = coa.mondir; - killMonster(ca, moPlayer); - cb->monst = ca->monst; - copy_metadata(ca, &tmp); - } - if(ca && ca->monst == moPair) { - gcell tmp = *cb; - cb->monst = moPair; - cb->mondir = cob.mondir; - killMonster(cb, moPlayer); - ca->monst = cb->monst; - copy_metadata(cb, &tmp); - } - done = true; + 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(cb, &coa); + int sa = ca->mondir - ((cwt+1)+wstep).spin; + int sb = cb->mondir - ((cwt-1)+wstep).spin; + ca->stuntime = min(ca->stuntime + 3, 15); + cb->stuntime = min(cb->stuntime + 3, 15); + ca->monmirror = !ca->monmirror; + cb->monmirror = !cb->monmirror; + if(ca->mondir < ca->type) + ca->mondir = ((cwt+1)+wstep-sb).spin; + if(cb->mondir < cb->type) + cb->mondir = ((cwt+1)+wstep-sa).spin; + if(cb && cb->monst == moPair) { + killMonster(cb, moPlayer); + } + if(ca && ca->monst == moPair) { + killMonster(ca, moPlayer); + } + } + bool pcmove::actual_move() { origd = d; @@ -423,7 +494,7 @@ bool pcmove::actual_move() { } 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; } @@ -463,26 +534,18 @@ bool pcmove::actual_move() { if(isPushable(c2->wall) && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { mip = determinePush(cwt, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, cwt.at); }); 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; } - chaos_data cdata; - 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; } + nextmovetype = lmMove; addMessage(XLAT("You push %the1.", c2->wall)); lastmovetype = lmPush; lastmove = cwt.at; pushThumper(mip); - cdata.commit(); return perform_actual_move(); } 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; } @@ -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(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; placeWater(c2, cwt.at); moveBoat(mi); + changes.ccell(c2); c2->mondir = revhint(cwt.at, d); if(c2->item) boatmove = !boatmove; - cdata.commit(); return perform_actual_move(); } @@ -522,39 +579,27 @@ bool pcmove::boat_move() { if(havePushConflict(cwt.at, checkonly)) return false; 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; } if(againstCurrent(c2, cwt.at) && !markOrb(itOrbWater)) { if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state) return after_escape(); - if(!checkonly) - addMessage(XLAT("You cannot go against the current!")); + addMessage(XLAT("You cannot go against the current!")); return false; } if(cwt.at->item == itOrbYendor) { if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state) 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; } - 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; } + nextmovetype = lmMove; moveBoat(mi); boatmove = true; - cdata.commit(); return perform_actual_move(); } @@ -603,36 +648,25 @@ bool pcmove::after_escape() { if(c2->wall == waBigStatue && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { if(!canPushStatueOn(cwt.at)) { - if(!checkonly) { - if(isFire(cwt.at)) - addMessage(XLAT("You have to escape first!")); - else - addMessage(XLAT("There is not enough space!")); - } + if(isFire(cwt.at)) + addMessage(XLAT("You have to escape first!")); + else + addMessage(XLAT("There is not enough space!")); return false; } if(havePushConflict(cwt.at, checkonly)) return false; - eWall save_c2 = c2->wall; - eWall save_cw = cwt.at->wall; + changes.ccell(c2); + changes.ccell(cwt.at); + c2->wall = cwt.at->wall; if(doesnotFall(cwt.at)) cwt.at->wall = waBigStatue; - chaos_data cdata; - - 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; } + nextmovetype = lmMove; addMessage(XLAT("You push %the1 behind you!", waBigStatue)); animateMovement(mi.rev(), LAYER_BOAT); - cdata.commit(); return perform_actual_move(); } @@ -650,15 +684,12 @@ bool pcmove::after_escape() { if(attackable && fmsAttack) { if(checkNeedMove(checkonly, true)) return false; - if(monstersnear(cwt.at,c2,moPlayer,NULL,cwt.at)) { - if(!checkonly && errormsgs) wouldkill("%The1 would get you!"); - return false; - } - if(checkonly) { nextmovetype = nm ? lmAttack : lmSkip; return true; } + nextmovetype = nm ? lmAttack : lmSkip; if(c2->wall == waSmallTree) { drawParticles(c2, winf[c2->wall].color, 4); addMessage(XLAT("You chop down the tree.")); playSound(c2, "hit-axe" + pick123()); + changes.ccell(c2); c2->wall = waNone; return swing(); } @@ -666,6 +697,7 @@ bool pcmove::after_escape() { drawParticles(c2, winf[c2->wall].color, 8); addMessage(XLAT("You start chopping down the tree.")); playSound(c2, "hit-axe" + pick123()); + changes.ccell(c2); c2->wall = waSmallTree; return swing(); } @@ -681,14 +713,14 @@ bool pcmove::after_escape() { return false; } else if(c2->monst == moKnight) { - if(!checkonly) camelot::knightFlavorMessage(c2); + camelot::knightFlavorMessage(c2); return false; } else if(c2->monst && (!isFriendly(c2) || c2->monst == moTameBomberbird || isMountable(c2->monst)) && !(peace::on && !isMultitile(c2->monst) && !good_tortoise)) return attack(); else if(!passable(c2, cwt.at, P_USEBOAT | P_ISPLAYER | P_MIRROR | P_MONSTER)) { - if(!checkonly) tell_why_impassable(); + tell_why_impassable(); return false; } else if(fmsMove) @@ -709,64 +741,12 @@ bool pcmove::move_if_okay() { return false; } - chaos_data cdata; - 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; itype; i++) { - cell *c3 = c2->move(i); - if(c3) for(int i=0; itype; 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(!checkonly && errormsgs) - wouldkill("%The1 would kill you there!"); + if(switchplace_prevent(cwt.at, c2, checkonly)) return false; - } - - if(switchplace_prevent(cwt.at, c2, checkonly)) { - cdata.rollback(); + if(!checkonly && warningprotection_hit(do_we_stab_a_friend(cwt.at, c2, moPlayer))) 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; - return true; - } - - cdata.commit(); + nextmovetype = lmMove; return perform_actual_move(); } @@ -810,15 +790,16 @@ bool pcmove::attack() { if(!ca) { if(forcedmovetype == fmAttack) { - if(monstersnear(cwt.at,c2,moPlayer,NULL,cwt.at)) { - if(!checkonly && errormsgs) wouldkill("%The1 would get you!"); + if(monstersnear(cwt.at,moPlayer,NULL,cwt.at)) { + changes.rollback(); + if(errormsgs && !checkonly) wouldkill("%The1 would get you!"); return false; } - if(checkonly) { nextmovetype = lmSkip; return true; } + nextmovetype = lmSkip; addMessage(XLAT("You swing your sword at %the1.", c2->monst)); return swing(); } - if(!checkonly) tell_why_cannot_attack(); + tell_why_cannot_attack(); return false; } @@ -842,29 +823,25 @@ bool pcmove::attack() { if(!(isWatery(cwt.at) && c2->monst == moWaterElemental) && checkNeedMove(checkonly, true)) 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(checkonly) { nextmovetype = lmAttack; return true; } + nextmovetype = lmAttack; mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK); int tk = tkills(); if(good_tortoise) { - items[itBabyTortoise] += 4; - updateHi(itBabyTortoise, items[itBabyTortoise]); - c2->item = itBabyTortoise; - tortoise::babymap[c2] = tortoise::seekbits; - playSound(c2, playergender() ? "heal-princess" : "heal-prince"); - addMessage(XLAT(playergender() == GEN_F ? "You are now a tortoise heroine!" : "You are now a tortoise hero!")); - c2->stuntime = 2; - achievement_collection(itBabyTortoise); + changes.at_commit([c2] { + items[itBabyTortoise] += 4; + updateHi(itBabyTortoise, items[itBabyTortoise]); + c2->item = itBabyTortoise; + tortoise::babymap[c2] = tortoise::seekbits; + playSound(c2, playergender() ? "heal-princess" : "heal-prince"); + addMessage(XLAT(playergender() == GEN_F ? "You are now a tortoise heroine!" : "You are now a tortoise hero!")); + c2->stuntime = 2; + achievement_collection(itBabyTortoise); + }); } else { eMonster m = c2->monst; @@ -873,13 +850,15 @@ bool pcmove::attack() { markOrb(itOrbSlaying); if(c2->monst == moTerraWarrior && hrand(100) > 2 * items[itTerra]) { if(hrand(2 + jiangshi_on_screen) < 2) - wandering_jiangshi++; + changes.value_add(wandering_jiangshi, 1); } attackMonster(c2, attackflags | AF_MSG, moPlayer); if(m == moRusalka) { + changes.ccell(cwt.at); if(cwt.at->wall == waNone) cwt.at->wall = waShallow; else if(cwt.at->wall == waShallow) cwt.at->wall = waDeepWater; } + changes.ccell(c2); // 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) produceGhost(c2, m, moPlayer); @@ -891,6 +870,16 @@ bool pcmove::attack() { sideAttack(cwt.at, d, moPlayer, tkills() - tk); lastmovetype = lmAttack; lastmove = c2; 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(); } @@ -898,20 +887,6 @@ EX bool chaos_forbidden(cell *c) { 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() { cell*& c2 = mi.t; 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() { lastmovetype = lmMove; lastmove = cwt.at; + apply_chaos(); stabbingAttack(cwt.at, mi.t, moPlayer); cell *c1 = cwt.at; + changes.value_keep(cwt); 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; itype; i++) { + cell *c3 = c2->move(i); + if(c3) for(int i=0; itype; 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) { indAnimateMovement(mi, LAYER_SMALL); indAnimateMovement(mi.rev(), LAYER_SMALL); @@ -1001,12 +1029,6 @@ bool pcmove::perform_move_or_jump() { else animateMovement(mi, LAYER_SMALL); 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(); landvisited[cwt.at->land] = true; @@ -1023,13 +1045,16 @@ bool pcmove::stay() { lastmovetype = lmSkip; lastmove = NULL; if(checkNeedMove(checkonly, 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) wouldkill("%The1 would get you!"); return false; } - if(checkonly) { nextmovetype = lmSkip; return true; } - swordAttackStatic(); + if(checkonly) return true; + if(changes.on) changes.commit(); if(d == -2) dropGreenStone(cwt.at); if(cellUnstable(cwt.at) && !markOrb(itOrbAether)) @@ -1138,7 +1163,8 @@ EX bool playerInPower() { EX void playerMoveEffects(cell *c1, cell *c2) { 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]); destroyWeakBranch(c1, c2, moPlayer); @@ -1172,7 +1198,7 @@ EX void playerMoveEffects(cell *c1, cell *c2) { c2->landparam = 40; if((c2->land == laHauntedWall || c2->land == laHaunted) && !hauntedWarning) { - hauntedWarning = true; + changes.value_set(hauntedWarning, true); addMessage(XLAT("You become a bit nervous...")); addMessage(XLAT("Better not to let your greed make you stray from your path.")); playSound(c2, "nervous"); @@ -1344,14 +1370,12 @@ EX bool havePushConflict(cell *pushto, bool checkonly) { if(pushto && multi::activePlayers() > 1) { for(int i=0; i *hooks_sound; EX void playSound(cell *c, const string& fname, int vol IS(100)) { + LATE( hr::playSound(c, fname, vol); ) if(effvolume == 0) return; if(callhandlers(false, hooks_sound, fname, vol)) return; // printf("Play sound: %s\n", fname.c_str()); diff --git a/system.cpp b/system.cpp index 5d94e29a..e978fc62 100644 --- a/system.cpp +++ b/system.cpp @@ -1518,7 +1518,6 @@ EX void finishAll() { #if CAP_SAVE saveStats(); #endif - offscreen.clear(); clearMemory(); #if ISMOBILE==0 cleargraph(); diff --git a/yendor.cpp b/yendor.cpp index 3a8695d8..9f37d4c6 100644 --- a/yendor.cpp +++ b/yendor.cpp @@ -750,6 +750,7 @@ EX namespace yendor { } EX void collected(cell* c2) { + LATE( collected(c2); ) playSound(c2, "tada"); items[itOrbShield] += 31; for(int i=0; i