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