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 }