// Hyperbolic Rogue - checkmate rule // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file checkmove.cpp * \brief Check the validity of move (checkmate rule) */ #include "hyper.h" namespace hr { #if HDR #define PUREHARDCORE_LEVEL 10 #endif /** are we in the hardcore mode */ EX bool hardcore = false; /** when did we switch to the hardcore mode */ EX int hardcoreAt; EX bool pureHardcore() { return hardcore && hardcoreAt < PUREHARDCORE_LEVEL; } /** can we still move? */ EX bool canmove = true; // how many monsters are near EX eMonster who_kills_me; EX int lastkills; EX vector legalmoves; /* why is a move illegal */ EX vector move_issues; #if HDR static const int miVALID = 10000; static const int miENTITY = 11000; static const int miRESTRICTED = 10100; static const int miTHREAT = 10010; static const int miWALL = 10001; #endif EX int checked_move_issue; EX int yasc_code; EX void check_if_monster() { eMonster m = cwt.peek()->monst; if(m && m != passive_switch && !isFriendly(m)) checked_move_issue = miENTITY; } EX bool hasSafeOrb(cell *c) { return c->item == itOrbSafety || c->item == itOrbShield || c->item == itOrbShell || (c->item == itOrbYendor && yendor::state(c) == yendor::ysUnlocked); } #if HDR struct player_move_info { movei mi; cell *swordlast[2], *swordtransit[2], *swordnext[2]; player_move_info(movei mi); }; #endif EX vector pmi; EX vector pushes; player_move_info::player_move_info(movei _mi) : mi(_mi) { for(int b=0; b<2; b++) swordlast[b] = sword::pos(multi::cpid, b); dynamicval x7(sword::dir[multi::cpid], sword::shift(mi, sword::dir[multi::cpid])); for(int b=0; b<2; b++) { swordnext[b] = sword::pos(multi::cpid, b); swordtransit[b] = NULL; if(swordnext[b] && swordnext[b] != swordlast[b] && !isNeighbor(swordlast[b], swordnext[b])) { forCellEx(c2, swordnext[b]) if(c2 != mi.t && c2 != mi.s && isNeighbor(c2, S3==3 ? swordlast[b] : mi.t)) swordtransit[b] = c2; if(S3 == 4) forCellEx(c2, mi.s) if(c2 != mi.s && isNeighbor(c2, swordlast[b])) swordtransit[b] = c2; } } } EX bool krakensafe(cell *c) { return items[itOrbFish] || items[itOrbAether] || (c->item == itOrbFish && c->wall == waBoat) || (c->item == itOrbAether && c->wall == waBoat); } EX bool monstersnear(cell *c, eMonster who) { bool eaten = false; if(hardcore && who == moPlayer) return false; int res = 0; bool fast = false; elec::builder b; if(elec::affected(c)) { who_kills_me = moLightningBolt; res++; } if(c->wall == waArrowTrap && c->wparam == 2) { who_kills_me = moArrowTrap; res++; } for(auto c1: crush_now) if(c == c1) { who_kills_me = moCrusher; res++; } if(who == moPlayer || items[itOrbEmpathy]) { fast = (items[itOrbSpeed] && (items[itOrbSpeed] & 1)); if(who == moPlayer && c->item == itOrbSpeed && !items[itOrbSpeed]) fast = true; } if(havewhat&HF_OUTLAW) { for(cell *c1: gun_targets(c)) if(c1->monst == moOutlaw && !c1->stuntime) { res++; who_kills_me = moOutlaw; } } for(int t=0; ttype; t++) { cell *c2 = c->move(t); // consider monsters who attack from distance 2 if(c2) forCellEx(c3, c2) if(c3 != c) { // only these monsters can attack from two spots... if(!among(c3->monst, moLancer, moWitchSpeed, moWitchFlash)) continue; if(c3->monst == moWitchSpeed && cwt.at->land != laPower) continue; // take logical_adjacent into account if(c3->monst != moWitchFlash) if(!logical_adjacent(c3, c3->monst, c2) || !logical_adjacent(c2, c3->monst, c) || (c3->monst == moWitchSpeed && c2->land != laPower)) continue; if(elec::affected(c3)) continue; if(c3->stuntime > (who == moPlayer ? 0 : 1)) continue; // speedwitches can only attack not-fastened monsters, // others can only attack if the move is not fastened if(c3->monst == moWitchSpeed && items[itOrbSpeed]) continue; if(c3->monst != moWitchSpeed && fast) continue; // cannot attack if the immediate cell is impassable (except flashwitches) if(c3->monst != moWitchFlash) { if(!passable(c2, c3, 0)) continue; if(isPlayerOn(c2) && items[itOrbFire]) continue; } // flashwitches cannot attack if it would kill another enemy if(c3->monst == moWitchFlash && flashWouldKill(c3, 0)) continue; res++, who_kills_me = c3->monst; } // consider normal monsters if(c2 && isArmedEnemy(c2, who) && (c2->monst != moLancer || isUnarmed(who) || !logical_adjacent(c, who, c2))) { eMonster m = c2->monst; if(elec::affected(c2)) continue; if(fast && c2->monst != moWitchSpeed) continue; // Krakens just destroy boats if(c2->monst == moKrakenT && c->wall == waBoat) { if(krakensafe(c)) continue; else if(warningprotection(XLAT("This move appears dangerous -- are you sure?")) && res == 0) m = moWarning; else continue; } // they cannot attack through vines if(!canAttack(c2, c2->monst, c, who, AF_NEXTTURN)) continue; if(c2->monst == moWorm || c2->monst == moTentacle || c2->monst == moHexSnake) { if(passable_for(c2->monst, c, c2, 0)) eaten = true; else if(c2->monst != moHexSnake) continue; } res++, who_kills_me = m; } } if(who == moPlayer && res && (markOrb2(itOrbShield) || markOrb2(itOrbShell)) && !eaten) res = 0; if(who == moPlayer && res && markOrb2(itOrbDomination) && c->monst) res = 0; return !!res; } EX bool monstersnear_aux() { changes.value_set(passive_switch, (gold() & 1) ? moSwitch1 : moSwitch2); multi::cpid++; bool b = false; bool recorduse[ittypes]; for(int i=0; i 8) { b = true; who_kills_me = moAirball; } } for(auto& pushto: pushes) for(auto& mi: pmi) if(pushto == mi.mi.t) { b = true; who_kills_me = moTongue; } for(int i=0; i gs(gravity_state, gravity_state); #if CAP_INV if(inv::on) inv::compute(); #endif if(multi::players > 1 && !multi::checkonly) return; if(hardcore) return; bool orbusedbak[ittypes]; // do not activate orbs! for(int i=0; itype+1, false); move_issues.clear(); move_issues.resize(cwt.at->type, 0); canmove = haveRangedTarget(); items[itWarning]+=2; if(movepcto(-1, 0, true)) canmove = legalmoves[cwt.at->type] = true; if(true) { for(int i=0; itype; i++) { if(movepcto(1, -1, true)) { canmove = legalmoves[cwt.spin] = true; } check_if_monster(); move_issues[cwt.spin] = checked_move_issue; if(!legalmoves[cwt.spin]) { if(movepcto(1, 1, true)) { canmove = legalmoves[cwt.spin] = true; } check_if_monster(); move_issues[cwt.spin] = checked_move_issue; } } } if(kills[moPlayer]) canmove = false; yasc_code = 0; for(int i=0; itype; i++) yasc_code += move_issues[i]; #if CAP_INV if(inv::on && !canmove && !inv::incheck) { if(inv::remaining[itOrbSafety] || inv::remaining[itOrbFreedom]) canmove = true; else { inv::check(1); checkmove(); inv::check(-1); } if(canmove) pushScreen(inv::show); } #endif if(!canmove) { achievement_final(true); if(cmode & sm::NORMAL) showMissionScreen(); } if(canmove && timerstopped) { timerstart = time(NULL); timerstopped = false; } items[itWarning]-=2; for(int i=0; i