1 // Hyperbolic Rogue -- multi-game features
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3
4 /** \file multi.cpp
5 * \brief running several games at once -- used in the Tutorial and Dual Geometry mode
6 */
7
8 #include "hyper.h"
9 namespace hr {
10
11 #if HDR
12 /** gamedata structure, for recording the game data in memory temporarily */
13 struct gamedata {
14 /** important parameters should be visible */
15 eGeometry geo;
16 eVariation var;
17 eLand specland;
18 bool active;
19 /** other properties are recorded here */
20 vector<char> record;
21 int index, mode;
22 void storegame();
23 void restoregame();
storehr::gamedata24 template<class T> void store(T& x) {
25 int ssize = sizeof(x);
26 if(ssize & 7) ssize = (ssize | 7) + 1;
27 if(mode == 0) {
28 record.resize(index+ssize);
29 T& at = *(new (&record[index]) T());
30 at = move(x);
31 }
32 else {
33 T& at = (T&) record[index];
34 x = move(at);
35 at.~T();
36 }
37 index += ssize;
38 }
store_ptrhr::gamedata39 template<class T> void store_ptr(T& x) {
40 T* copy;
41 if(mode == 0) {
42 copy = new T;
43 *copy = move(x);
44 }
45 store(copy);
46 if(mode != 0) {
47 x = move(*copy);
48 delete copy;
49 }
50 }
51 };
52 #endif
53
gamedata_all(gamedata & gd)54 void gamedata_all(gamedata& gd) {
55 gd.index = 0;
56 gd.store(firstland);
57 gd.store(currentmap);
58 gd.store(cwt);
59 gd.store(allmaps);
60 gd.store(shmup::on);
61 gd.store(land_structure);
62 gd.store(*current_display);
63 gd.store(cgip);
64 gd.store_ptr(vid);
65 gd.store(sightrange_bonus);
66 gd.store(genrange_bonus);
67 gd.store(gamerange_bonus);
68 gd.store(targets);
69 if(GDIM == 3) {
70 gd.store(radarlines);
71 gd.store(radarpoints);
72 }
73 if(GOLDBERG) gd.store(gp::param);
74 callhooks(hooks_gamedata, &gd);
75 }
76
storegame()77 void gamedata::storegame() {
78 geo = geometry;
79 var = variation;
80 specland = specialland;
81 active = game_active;
82 record.clear();
83 mode = 0;
84 gamedata_all(*this);
85 game_active = false;
86 }
87
restoregame()88 void gamedata::restoregame() {
89 geometry = geo;
90 variation = var;
91 specialland = specland;
92 game_active = active;
93 mode = 1;
94 gamedata_all(*this);
95 }
96
97 EX hookset<void(gamedata*)> hooks_gamedata;
98
99 EX namespace gamestack {
100
101 vector<gamedata> gd;
102
pushed()103 EX bool pushed() { return isize(gd); }
104
push()105 EX void push() {
106 gd.emplace_back();
107 gd.back().storegame();
108 }
109
pop()110 EX void pop() {
111 if(!pushed()) return;
112 if(game_active) stop_game();
113 gd.back().restoregame();
114 gd.pop_back();
115 }
116
117 EX }
118
119 EX namespace dual {
120 /** 0 = dualmode off, 1 = in dualmode (no game chosen), 2 = in dualmode (working on one of subgames) */
121 EX int state;
122
123 /** exactly one side is Euclidean -- it should not be synchronized with the other side */
124 EX bool one_euclidean;
125
126 EX int currently_loaded;
127 EX int main_side;
128 EX bool affect_both;
129
130 gamedata dgd[2];
131 EX transmatrix player_orientation[2];
132
133 hyperpoint which_dir;
134
remap_direction(int d,int cg)135 int remap_direction(int d, int cg) {
136 if(WDIM == 2 || cg == currently_loaded) return d;
137
138 hyperpoint h = sword::dir[0].T * which_dir;
139
140 h = hpxy3(h[0]/10, h[1]/10, h[2]/10);
141 ld b = HUGE_VAL;
142 for(int i=0; i<S7; i++) {
143 hyperpoint checked = tC0(currentmap->relative_matrix(cwt.at->cmove(i)->master, cwt.at->master, C0));
144 ld dist = hdist(checked, h);
145 if(dist < b) { b = dist; d = i; }
146 }
147 d = gmod(d - cwt.spin, S7);
148
149 return d;
150 }
151
get_orientation()152 EX transmatrix get_orientation() {
153 if(WDIM == 2)
154 return gpushxto0(tC0(cwtV.T)) * cwtV.T;
155 else if(cwt.at) {
156 transmatrix T = unshift(ggmatrix(cwt.at));
157 return gpushxto0(tC0(T)) * T * sword::dir[0].T;
158 }
159 else
160 return Id;
161 }
162
switch_to(int k)163 EX void switch_to(int k) {
164 if(k != currently_loaded) {
165 // gamedata has shmup::on because tutorial needs changing it, but dual should keep it fixed
166 dynamicval<bool> smon(shmup::on);
167 player_orientation[currently_loaded] = get_orientation();
168 dgd[currently_loaded].storegame();
169 currently_loaded = k;
170 dgd[currently_loaded].restoregame();
171 }
172 }
173
movepc(int d,int subdir,bool checkonly)174 EX bool movepc(int d, int subdir, bool checkonly) {
175 dynamicval<int> dm(dual::state, 2);
176 int cg = currently_loaded;
177
178 bool orbusedbak[ittypes];
179 for(int i=0; i<ittypes; i++) orbusedbak[i] = orbused[i];
180
181 if(d < 0) {
182 if(d == -2 && items[itGreenStone] < 2) {
183 switch_to(cg);
184 glance_message();
185 return false;
186 }
187 bool ok = true;
188
189 for(int k=0; k<2; k++) {
190 switch_to(k);
191 ok = ok && movepcto(d, subdir, true);
192 for(int i=0; i<ittypes; i++) orbused[i] = orbusedbak[i];
193 }
194 if(ok && checkonly) {
195 switch_to(cg);
196 return true;
197 }
198 if(ok) for(int k=0; k<2; k++) {
199 switch_to(k);
200 movepcto(d, subdir, false);
201 if(k == 0) turncount--;
202 }
203 if(!ok) {
204 addMessage(XLAT("Impossible."));
205 }
206 switch_to(cg);
207 return ok;
208 }
209
210 which_dir = inverse(sword::dir[0].T) * tC0(currentmap->relative_matrix((cwt+d).cpeek()->master, cwt.at->master, C0));
211
212 bool lms[2][5];
213 eLastmovetype lmt[2][5];
214 for(int k=0; k<2; k++) {
215 switch_to(k);
216 for(eForcemovetype fm: { fmMove, fmAttack, fmInstant, fmActivate }) {
217 forcedmovetype = fm;
218 lms[k][fm] = movepcto(fm == fmMove ? remap_direction(d, cg) : 0, subdir, true);
219 lmt[k][fm] = nextmovetype;
220 forcedmovetype = fmSkip;
221 for(int i=0; i<ittypes; i++) orbused[i] = orbusedbak[i];
222 }
223 }
224
225 if(lms[0][fmActivate]) {
226 if(checkonly) { switch_to(cg); return true; }
227 switch_to(0); forcedmovetype = fmActivate; movepcto(0, subdir, false); forcedmovetype = fmSkip;
228 if(!lms[1][fmActivate]) return true;
229 }
230 if(lms[1][fmActivate]) {
231 if(checkonly) { switch_to(cg); return true; }
232 switch_to(1); forcedmovetype = fmActivate; movepcto(0, subdir, false); forcedmovetype = fmSkip;
233 switch_to(cg);
234 return true;
235 }
236 for(auto fm: {fmMove, fmInstant, fmAttack}) if(lms[0][fm] && lms[1][fm]) {
237 if(lmt[0][fm] == lmSkip && lmt[1][fm] == lmSkip)
238 continue;
239 if(checkonly) { switch_to(cg); return true; }
240 int flash = items[itOrbFlash], lgt = items[itOrbLightning];
241 switch_to(0); forcedmovetype = fm; movepcto(0, subdir, false); forcedmovetype = fmSkip;
242 if(fm == fmInstant) { items[itOrbFlash] = flash, items[itOrbLightning] = lgt; }
243 turncount--;
244 switch_to(1); forcedmovetype = fm; movepcto(0, subdir, false); forcedmovetype = fmSkip;
245 switch_to(cg);
246 reduceOrbPowers();
247 dpgen::check();
248 return true;
249 }
250 addMessage(XLAT("Impossible."));
251 flipplayer = false;
252 switch_to(cg);
253 return false;
254 }
255
in_subscreen(reaction_t what)256 EX void in_subscreen(reaction_t what) {
257 dynamicval<ld> xmax(current_display->xmax, 0.5 * (currently_loaded+1));
258 dynamicval<ld> xmin(current_display->xmin, 0.5 * (currently_loaded));
259 what();
260 }
261
split(reaction_t what)262 EX bool split(reaction_t what) {
263 if(state != 1) return false;
264 state = 2;
265
266 for(int a=0; a<2; a++) {
267 switch_to(currently_loaded ^ 1);
268 what();
269 }
270
271 state = 1;
272 return true;
273 }
274
enable()275 EX void enable() {
276 if(dual::state) return;
277 stop_game();
278 eGeometry b = geometry;
279 eVariation v = variation;
280 for(int s=0; s<2; s++) {
281 // dynamicval<display_data*> pds(current_display, &subscreens::player_displays[s]);
282 if(WDIM == 3) {
283 variation = eVariation::pure;
284 geometry = s == 0 ? gCubeTiling : gSpace435;
285 }
286 else if(shmup::on) {
287 geometry = b;
288 variation = v;
289 // 'do what I mean'
290 if(euclid)
291 geometry = s == 0 ? b : (ginf[geometry].vertex == 3 ? gNormal : g45);
292 else
293 geometry = s == 0 ? (ginf[geometry].vertex == 3 ? gEuclid : gEuclidSquare) : b;
294 if(geometry == gEuclid) variation = eVariation::bitruncated;
295 }
296 else {
297 variation = eVariation::pure;
298 #if CAP_ARCM
299 geometry = s == 0 ? gEuclidSquare : gArchimedean;
300 #else
301 geometry = gEuclidSquare;
302 #endif
303 }
304 firstland = specialland = laCrossroads4;
305 #if CAP_ARCM
306 if(geometry == gArchimedean)
307 arcm::current.parse("4,4,4,4,4");
308 #endif
309 check_cgi();
310 cgi.require_basics();
311 dgd[s].storegame();
312 }
313
314 currently_loaded = 0;
315 dgd[0].restoregame();
316 state = 1;
317 }
318
disable()319 EX void disable() {
320 if(!dual::state) return;
321 stop_game();
322 state = 0;
323 }
324
325 #if CAP_COMMANDLINE
args()326 int args() {
327 using namespace arg;
328
329 if(0) ;
330 else if(argis("-dual0")) {
331 PHASEFROM(2);
332 enable();
333 switch_to(0);
334 }
335 else if(argis("-dual1")) {
336 PHASEFROM(2);
337 enable();
338 switch_to(1);
339 }
340 else if(argis("-dualoff")) {
341 PHASEFROM(2);
342 disable();
343 }
344 else return 1;
345 return 0;
346 }
347
348 auto hook = addHook(hooks_args, 100, args);
349 #endif
350
351 vector<int> landsides;
352
check_side(eLand l)353 EX bool check_side(eLand l) {
354 return landsides[l] == currently_loaded || landsides[l] == 2;
355 }
356
assign_landsides()357 EX void assign_landsides() {
358 switch_to(!currently_loaded);
359 one_euclidean = euclid;
360 switch_to(!currently_loaded);
361 one_euclidean ^= euclid;
362
363 landsides.resize(landtypes);
364 int which_hyperbolic = -1;
365 if(ginf[dgd[0].geo].cclass == gcHyperbolic && ginf[dgd[1].geo].cclass != gcHyperbolic)
366 which_hyperbolic = 0;
367 else if(ginf[dgd[1].geo].cclass == gcHyperbolic && ginf[dgd[0].geo].cclass != gcHyperbolic)
368 which_hyperbolic = 1;
369 int nxt = 0;
370 for(int i=0; i<landtypes; i++) {
371 eLand l = eLand(i);
372 auto& v = landsides[i];
373 land_validity_t lv[2];
374 for(int s=0; s<2; s++) {
375 switch_to(s);
376 lv[s] = land_validity(l);
377 }
378 if(!(lv[0].flags & lv::appears_in_full) && !(lv[1].flags & lv::appears_in_full)) {
379 v = -1;
380 continue;
381 }
382 else if(isCrossroads(l))
383 v = -1; /* simply boring */
384 else if(isGravityLand(l))
385 v = -1; /* too confusing */
386 else if(among(l, laTortoise))
387 v = -1; /* does not work in hyperbolic geos available, and better not do it in Euclidean ones either */
388 else if(among(l, laHaunted))
389 v = -1; /* graveyard prefers Euclidean, while Haunted prefers hyperbolic */
390 else if(l == laPower)
391 v = which_hyperbolic;
392 else if(l == dgd[0].specland && l == dgd[1].specland)
393 v = 2;
394 else if(l == dgd[0].specland)
395 v = 0;
396 else if(l == dgd[1].specland)
397 v = 1;
398 else if(isElemental(l))
399 v = 1;
400 else if(!(lv[0].flags & lv::appears_in_full))
401 v = 1;
402 else if(!(lv[1].flags & lv::appears_in_full))
403 v = 0;
404 else if(lv[0].quality_level > lv[1].quality_level)
405 v = 0;
406 else if(lv[1].quality_level > lv[0].quality_level)
407 v = 1;
408 else if(isEquidLand(l) && which_hyperbolic >= 0)
409 v = which_hyperbolic;
410 else if(among(l, laHunting, laMotion, laCaves, laAlchemist) && which_hyperbolic >= 0)
411 v = which_hyperbolic;
412 else if(among(l, laMirrorOld, laIce, laJungle, laDesert, laDryForest, laStorms) && which_hyperbolic >= 0)
413 v = 1 - which_hyperbolic;
414 else if(which_hyperbolic >= 0)
415 v = which_hyperbolic;
416 else {
417 println(hlog, "equivalent");
418 v = nxt, nxt = 1 - nxt;
419 }
420 // println(hlog, dnameof(l), ": ", lv[0].msg, " vs ", lv[1].msg, " verdict = ", v);
421 }
422 }
423
424
add_choice()425 EX void add_choice() {
426 if(!state) return;
427 dialog::addSelItem(XLAT("subgame affected"),
428 affect_both ? XLAT("both") : main_side == 0 ? XLAT("left") : XLAT("right"), '`');
429 dialog::add_action([] () {
430 affect_both = !affect_both;
431 if(!affect_both) {
432 main_side = !main_side;
433 switch_to(main_side);
434 }
435 });
436 }
437
split_or_do(reaction_t what)438 EX void split_or_do(reaction_t what) {
439 if(split(what)) return;
440 else what();
441 }
442
may_split(reaction_t what)443 EX bool may_split(reaction_t what) {
444 if(state == 1 && affect_both) {
445 split(what);
446 return true;
447 }
448 return false;
449 }
450
may_split_or_do(reaction_t what)451 EX void may_split_or_do(reaction_t what) {
452 if(state == 1 && affect_both) {
453 split(what);
454 }
455 else what();
456 }
457 }
458
459 #if HDR
mayboth(reaction_t what)460 inline reaction_t mayboth(reaction_t what) { return [=] { may_split_or_do(what); }; }
461 #endif
462
463
464 }
465