1 // Hyperbolic Rogue - checkmate rule
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file checkmove.cpp
5  *  \brief Check the validity of move (checkmate rule)
6  */
7 
8 #include "hyper.h"
9 
10 namespace hr {
11 
12 #if HDR
13 #define PUREHARDCORE_LEVEL 10
14 #endif
15 
16 /** are we in the hardcore mode */
17 EX bool hardcore = false;
18 /** when did we switch to the hardcore mode */
19 EX int hardcoreAt;
20 /** are we in the casual mode */
21 EX bool casual = false;
22 
pureHardcore()23 EX bool pureHardcore() { return hardcore && hardcoreAt < PUREHARDCORE_LEVEL; }
24 
25 /** can we still move? */
26 EX bool canmove = true;
27 
28 // how many monsters are near
29 EX eMonster who_kills_me;
30 
31 EX int lastkills;
32 
33 EX vector<bool> legalmoves;
34 
35 /* why is a move illegal */
36 EX vector<int> move_issues;
37 
38 #if HDR
39 static const int miVALID = 10000;
40 static const int miENTITY = 11000;
41 static const int miRESTRICTED = 10100;
42 static const int miTHREAT = 10010;
43 static const int miWALL = 10001;
44 #endif
45 
46 EX int checked_move_issue;
47 EX int yasc_code;
48 
check_if_monster()49 EX void check_if_monster() {
50   eMonster m = cwt.peek()->monst;
51   if(m && m != passive_switch && !isFriendly(m))
52     checked_move_issue = miENTITY;
53   }
54 
hasSafeOrb(cell * c)55 EX bool hasSafeOrb(cell *c) {
56   return
57     c->item == itOrbSafety ||
58     c->item == itOrbShield ||
59     c->item == itOrbShell  ||
60     (c->item == itOrbYendor && yendor::state(c) == yendor::ysUnlocked);
61   }
62 
63 #if HDR
64 struct player_move_info {
65   movei mi;
66   cell *swordlast[2], *swordtransit[2], *swordnext[2];
67   player_move_info(movei mi);
68   };
69 #endif
70 
71 EX vector<player_move_info> pmi;
72 EX vector<cell*> pushes;
73 
player_move_info(movei _mi)74 player_move_info::player_move_info(movei _mi) : mi(_mi) {
75   for(int b=0; b<2; b++) swordlast[b] = sword::pos(multi::cpid, b);
76 
77   dynamicval<sword::sworddir> x7(sword::dir[multi::cpid], sword::shift(mi, sword::dir[multi::cpid]));
78 
79   for(int b=0; b<2; b++) {
80     swordnext[b] = sword::pos(multi::cpid, b);
81     swordtransit[b] = NULL;
82     if(swordnext[b] && swordnext[b] != swordlast[b] && !isNeighbor(swordlast[b], swordnext[b])) {
83       forCellEx(c2, swordnext[b])
84         if(c2 != mi.t && c2 != mi.s && isNeighbor(c2, S3==3 ? swordlast[b] : mi.t))
85           swordtransit[b] = c2;
86       if(S3 == 4)
87         forCellEx(c2, mi.s)
88           if(c2 != mi.s && isNeighbor(c2, swordlast[b]))
89             swordtransit[b] = c2;
90       }
91     }
92   }
93 
krakensafe(cell * c)94 EX bool krakensafe(cell *c) {
95   return items[itOrbFish] || items[itOrbAether] ||
96     (c->item == itOrbFish && c->wall == waBoat) ||
97     (c->item == itOrbAether && c->wall == waBoat);
98   }
99 
monstersnear(cell * c,eMonster who)100 EX bool monstersnear(cell *c, eMonster who) {
101 
102   bool eaten = false;
103 
104   if(hardcore && who == moPlayer) return false;
105 
106   int res = 0;
107   bool fast = false;
108   bool kraken_will_destroy_boat = false;
109 
110   elec::builder b;
111   if(elec::affected(c)) { who_kills_me = moLightningBolt; res++; }
112 
113   if(c->wall == waArrowTrap && c->wparam == 2) {
114     who_kills_me = moArrowTrap; res++;
115     }
116 
117   for(auto c1: crush_now) if(c == c1) {
118     who_kills_me = moCrusher; res++;
119     }
120 
121   if(who == moPlayer || items[itOrbEmpathy]) {
122     fast = (items[itOrbSpeed] && (items[itOrbSpeed] & 1));
123     if(who == moPlayer && !isPlayerOn(c) && c->item == itOrbSpeed && !items[itOrbSpeed]) fast = true;
124     }
125 
126   if(havewhat&HF_OUTLAW) {
127     for(cell *c1: gun_targets(c))
128       if(c1->monst == moOutlaw && !c1->stuntime) {
129         res++; who_kills_me = moOutlaw;
130         }
131     }
132 
133   for(int t=0; t<c->type; t++) {
134     cell *c2 = c->move(t);
135 
136     // consider monsters who attack from distance 2
137     if(c2) forCellEx(c3, c2) if(c3 != c) {
138       // only these monsters can attack from two spots...
139       if(!among(c3->monst, moLancer, moWitchSpeed, moWitchFlash))
140         continue;
141       if(c3->monst == moWitchSpeed && cwt.at->land != laPower)
142         continue;
143       // take logical_adjacent into account
144       if(c3->monst != moWitchFlash)
145         if(!logical_adjacent(c3, c3->monst, c2) || !logical_adjacent(c2, c3->monst, c) || (c3->monst == moWitchSpeed && c2->land != laPower))
146           continue;
147       if(elec::affected(c3)) continue;
148       if(c3->stuntime > (who == moPlayer ? 0 : 1)) continue;
149       // speedwitches can only attack not-fastened monsters,
150       // others can only attack if the move is not fastened
151       if(c3->monst == moWitchSpeed && items[itOrbSpeed]) continue;
152       if(c3->monst != moWitchSpeed && fast) continue;
153       // cannot attack if the immediate cell is impassable (except flashwitches)
154       if(c3->monst != moWitchFlash) {
155         if(!passable(c2, c3, 0)) continue;
156         if(isPlayerOn(c2) && items[itOrbFire]) continue;
157         }
158       // flashwitches cannot attack if it would kill another enemy
159       if(c3->monst == moWitchFlash && flashWouldKill(c3, 0)) continue;
160       res++, who_kills_me = c3->monst;
161       }
162 
163     // consider normal monsters
164     if(c2 &&
165       isArmedEnemy(c2, who) &&
166       (c2->monst != moLancer || isUnarmed(who) || !logical_adjacent(c, who, c2))) {
167       eMonster m = c2->monst;
168       if(elec::affected(c2)) continue;
169       if(fast && c2->monst != moWitchSpeed) continue;
170       // Krakens just destroy boats
171       if(who == moPlayer && c2->monst == moKrakenT && c->wall == waBoat) {
172         kraken_will_destroy_boat = true;
173         continue;
174         }
175       // they cannot attack through vines
176       if(!canAttack(c2, c2->monst, c, who, AF_NEXTTURN)) continue;
177       if(c2->monst == moWorm || c2->monst == moTentacle || c2->monst == moHexSnake) {
178         if(passable_for(c2->monst, c, c2, 0))
179           eaten = true;
180         else if(c2->monst != moHexSnake) continue;
181         }
182       res++, who_kills_me = m;
183       }
184     }
185 
186   if(kraken_will_destroy_boat && !krakensafe(c) && warningprotection(XLAT("This move appears dangerous -- are you sure?"))) {
187     if (res == 0) who_kills_me = moWarning;
188     res++;
189   } else {
190     if(who == moPlayer && res && (markOrb2(itOrbShield) || markOrb2(itOrbShell)) && !eaten)
191       res = 0;
192     if(who == moPlayer && res && markOrb2(itOrbDomination) && c->monst)
193       res = 0;
194   }
195 
196   return !!res;
197   }
198 
monstersnear_aux()199 EX bool monstersnear_aux() {
200   changes.value_set(passive_switch, (gold() & 1) ? moSwitch1 : moSwitch2);
201   multi::cpid++;
202   bool b = false;
203   bool recorduse[ittypes];
204   for(int i=0; i<ittypes; i++) recorduse[i] = orbused[i];
205   if(multi::cpid == multi::players || multi::players == 1 || multi::checkonly) {
206 
207     if(shmup::delayed_safety) return false;
208 
209     for(int i=0; i<isize(pmi); i++)
210     for(int j=0; j<isize(pmi); j++) if(i != j) {
211       if(swordConflict(pmi[i], pmi[j])) {
212           b = true;
213           who_kills_me = moEnergySword;
214           }
215       if(pmi[i].mi.t == pmi[j].mi.t)
216         { b = true; who_kills_me = moFireball; }
217       if(celldistance(pmi[i].mi.t, pmi[j].mi.t) > 8)
218         { b = true; who_kills_me = moAirball; }
219       }
220 
221     for(auto& pushto: pushes)
222     for(auto& mi: pmi)
223       if(pushto == mi.mi.t) {
224         b = true; who_kills_me = moTongue;
225         }
226 
227     for(int i=0; i<isize(pushes); i++)
228       for(int j=0; j<i; j++)
229         if(pushes[i] == pushes[j]) {
230           b = true; who_kills_me = moCrushball;
231           }
232 
233     for(int i=0; !b && i<isize(pmi); i++)
234       b = monstersnear(pmi[i].mi.t, moPlayer);
235     }
236   else b = !multimove();
237   multi::cpid--;
238   for(int i=0; i<ittypes; i++) orbused[i] = recorduse[i];
239   return b;
240   }
241 
242 /** like monstersnear but add the potential moves of other players into account */
monstersnear_add_pmi(player_move_info pmi0)243 EX bool monstersnear_add_pmi(player_move_info pmi0) {
244   pmi.push_back(pmi0);
245   bool b = monstersnear_aux();
246   pmi.pop_back();
247   return b;
248   }
249 
multimove()250 EX bool multimove() {
251   if(multi::cpid == 0) lastkills = tkills();
252   if(!multi::playerActive(multi::cpid)) return !monstersnear_aux();
253   cellwalker bcwt = cwt;
254   cwt = multi::player[multi::cpid];
255   bool b = movepcto(multi::whereto[multi::cpid]);
256   if(b) {
257     multi::aftermove = true;
258     multi::player[multi::cpid] = cwt;
259     multi::whereto[multi::cpid].d = MD_UNDECIDED;
260     int curkills = tkills();
261     multi::kills[multi::cpid] += (curkills - lastkills);
262     lastkills = curkills;
263     }
264   cwt = bcwt;
265   return b;
266   }
267 
268 EX namespace multi {
269   EX bool checkonly = false;
270   EX bool aftermove;
271   EX }
272 
swordConflict(const player_move_info & sm1,const player_move_info & sm2)273 EX bool swordConflict(const player_move_info& sm1, const player_move_info& sm2) {
274   if(items[itOrbSword] || items[itOrbSword2])
275   for(int b=0; b<2; b++)
276     if(sm1.mi.s == sm2.swordlast[b] || sm1.mi.s == sm2.swordtransit[b] || sm1.mi.s == sm2.swordnext[b])
277     if(sm1.mi.t == sm2.swordlast[b] || sm1.mi.t == sm2.swordtransit[b] || sm1.mi.t == sm2.swordnext[b])
278       return true;
279   return false;
280   }
281 
checkmove()282 EX void checkmove() {
283 
284   if(dual::state == 2) return;
285   if(shmup::on) return;
286 
287   dynamicval<eGravity> gs(gravity_state, gravity_state);
288 
289 #if CAP_INV
290   if(inv::on) inv::compute();
291 #endif
292 
293   if(multi::players > 1 && !multi::checkonly) return;
294   if(hardcore) return;
295   bool orbusedbak[ittypes];
296 
297   // do not activate orbs!
298   for(int i=0; i<ittypes; i++) orbusedbak[i] = orbused[i];
299 
300   legalmoves.clear(); legalmoves.resize(cwt.at->type+1, false);
301   move_issues.clear(); move_issues.resize(cwt.at->type, 0);
302 
303   canmove = haveRangedTarget();
304   items[itWarning]+=2;
305   if(movepcto(-1, 0, true))
306     canmove = legalmoves[cwt.at->type] = true;
307 
308   if(true) {
309     for(int i=0; i<cwt.at->type; i++) {
310       if(movepcto(1, -1, true)) {
311         canmove = legalmoves[cwt.spin] = true;
312         }
313       check_if_monster();
314       move_issues[cwt.spin] = checked_move_issue;
315       if(!legalmoves[cwt.spin]) {
316         if(movepcto(0, 1, true)) {
317           canmove = legalmoves[cwt.spin] = true;
318           }
319         check_if_monster();
320         move_issues[cwt.spin] = checked_move_issue;
321         }
322       }
323     }
324   if(kills[moPlayer]) canmove = false;
325 
326   yasc_code = 0;
327   for(int i=0; i<cwt.at->type; i++)
328     yasc_code += move_issues[i];
329 
330 #if CAP_INV
331   if(inv::on && !canmove && !inv::incheck) {
332     if(inv::remaining[itOrbSafety] || inv::remaining[itOrbFreedom])
333       canmove = true;
334     else {
335       inv::check(1);
336       checkmove();
337       inv::check(-1);
338       }
339     if(canmove)
340       pushScreen(inv::show);
341     }
342 #endif
343 
344   if(!canmove) {
345     achievement_final(true);
346     if(cmode & sm::NORMAL) showMissionScreen();
347     }
348 
349   if(canmove && timerstopped) {
350     timerstart = time(NULL);
351     timerstopped = false;
352     }
353   items[itWarning]-=2;
354 
355   for(int i=0; i<ittypes; i++) orbused[i] = orbusedbak[i];
356   if(recallCell.at && !markOrb(itOrbRecall)) activateRecall();
357   }
358 
359 
360 }