diff --git a/basegraph.cpp b/basegraph.cpp index 7dd92a00..9cfa0b26 100644 --- a/basegraph.cpp +++ b/basegraph.cpp @@ -942,7 +942,9 @@ ld realradius() { void drawmessage(const string& s, int& y, color_t col) { int rrad = (int) realradius(); int space; - if(y > current_display->ycenter + rrad * vid.stretch) + if(dual::state) + space = vid.xres; + else if(y > current_display->ycenter + rrad * vid.stretch) space = vid.xres; else if(y > current_display->ycenter) space = current_display->xcenter - rhypot(rrad, (y-current_display->ycenter) / vid.stretch); diff --git a/complex.cpp b/complex.cpp index fdf0919c..66878dfc 100644 --- a/complex.cpp +++ b/complex.cpp @@ -3202,6 +3202,20 @@ auto ccm = addHook(clearmemory, 0, [] () { #endif mirror::clearcache(); }) + + addHook(hooks_gamedata, 0, [] (gamedata* gd) { + gd->store(heat::offscreen_heat); + gd->store(heat::offscreen_fire); + gd->store(princess::infos); + gd->store(mirror::mirrors); + gd->store(clearing::bpdata); + gd->store(tortoise::emap); + gd->store(tortoise::babymap); + gd->store(prairie::lasttreasure); + gd->store(prairie::enter); + gd->store(prairie::tchoices); + gd->store(prairie::beaststogen); + gd->store(sword::angle); + }) + addHook(hooks_removecells, 0, [] () { eliminate_if(heat::offscreen_heat, is_cell_removed); eliminate_if(heat::offscreen_fire, is_cell_removed); diff --git a/config.cpp b/config.cpp index 04ce7a44..251dfb10 100644 --- a/config.cpp +++ b/config.cpp @@ -515,7 +515,10 @@ void reset_graph_settings() { } void resetModes(char leave) { - popAllGames(); + while(game_active || gamestack::pushed()) { + if(game_active) stop_game(); + if(gamestack::pushed()) gamestack::pop(); + } if(shmup::on != (leave == rg::shmup)) stop_game_and_switch_mode(rg::shmup); if(inv::on != (leave == rg::inv)) stop_game_and_switch_mode(rg::inv); if(chaosmode != (leave == rg::chaos)) stop_game_and_switch_mode(rg::chaos); diff --git a/control.cpp b/control.cpp index 4c1e17e9..5fdac158 100644 --- a/control.cpp +++ b/control.cpp @@ -247,6 +247,7 @@ typedef SDL_Event eventtype; bool smooth_scrolling = false; void handlePanning(int sym, int uni) { + if(dual::split([=] { handlePanning(sym, uni); })) return; if(DIM == 3) { if(sym == PSEUDOKEY_WHEELUP) View = cpush(2, -0.05*shiftmul) * View, didsomething = true, playermoved = false; if(sym == PSEUDOKEY_WHEELDOWN) View = cpush(2, 0.05*shiftmul) * View, didsomething = true, playermoved = false; @@ -473,8 +474,16 @@ void fix_mouseh() { else if(rug::rugged) mouseh = rug::gethyper(mousex, mousey); #endif - else - mouseh = gethyper(mousex, mousey); + else { + if(dual::state) { + if(cmode & (sm::NORMAL | sm::DRAW | sm::MAP)) { + dual::main_side = (mousex >= current_display->xcenter); + dual::switch_to(dual::main_side); + } + dual::in_subscreen([=] () { calcparam(); mouseh = gethyper(mousex, mousey); }); + } + else mouseh = gethyper(mousex, mousey); + } need_mouseh = false; } diff --git a/game.cpp b/game.cpp index f4542d1a..de70ccd8 100644 --- a/game.cpp +++ b/game.cpp @@ -15,8 +15,8 @@ int gamerange_bonus = 0; int gamerange() { return getDistLimit() + gamerange_bonus; } cell *lastmove; -enum eLastmovetype {lmSkip, lmMove, lmAttack, lmSpecial, lmPush, lmTree}; -eLastmovetype lastmovetype; +eLastmovetype lastmovetype, nextmovetype; +eForcemovetype forcedmovetype; bool hauntedWarning; bool survivalist; @@ -7581,8 +7581,9 @@ void monstersTurn() { } } DEBB(DF_TURN, ("rop")); - reduceOrbPowers(); + if(!dual::state) reduceOrbPowers(); int phase1 = (1 & items[itOrbSpeed]); + if(dual::state && items[itOrbSpeed]) phase1 = !phase1; DEBB(DF_TURN, ("lc")); if(!phase1) livecaves(); if(!phase1) ca::simulate(); @@ -7787,6 +7788,7 @@ void handle_switchplaces(cell *c1, cell *c2, bool& switchplaces) { } bool movepcto(int d, int subdir, bool checkonly) { + if(dual::state == 1) return dual::movepc(d, subdir, checkonly); if(d >= 0 && !checkonly && subdir != 1 && subdir != -1) printf("subdir = %d\n", subdir); global_pushto = NULL; bool switchplaces = false; @@ -7824,6 +7826,10 @@ bool movepcto(int d, int subdir, bool checkonly) { gravity_state = gsNormal; + bool fmsMove = forcedmovetype == fmSkip || forcedmovetype == fmMove; + bool fmsAttack = forcedmovetype == fmSkip || forcedmovetype == fmAttack; + bool fmsActivate = forcedmovetype == fmSkip || forcedmovetype == fmActivate; + if(d >= 0) { cell *c2 = cwt.at->move(d); bool goodTortoise = c2->monst == moTortoise && tortoise::seek() && !tortoise::diff(tortoise::getb(c2)) && !c2->item; @@ -7842,8 +7848,10 @@ bool movepcto(int d, int subdir, bool checkonly) { return false; } - if(items[itOrbDomination] > ORBBASE && isMountable(c2->monst) && !monstersnear2()) { - if(checkonly) return true; + bool try_instant = (forcedmovetype == fmInstant) || (forcedmovetype == fmSkip && !passable(c2, cwt.at, P_ISPLAYER | P_MIRROR | P_USEBOAT | P_FRIENDSWAP)); + + if(items[itOrbDomination] > ORBBASE && isMountable(c2->monst) && !monstersnear2() && fmsMove) { + if(checkonly) { nextmovetype = lmMove; return true; } if(!isMountable(cwt.at->monst)) dragon::target = NULL; movecost(cwt.at, c2, 3); @@ -7853,8 +7861,8 @@ bool movepcto(int d, int subdir, bool checkonly) { goto mountjump; } - if(!passable(c2, cwt.at, P_ISPLAYER | P_MIRROR | P_USEBOAT | P_FRIENDSWAP) && items[itOrbFlash]) { - if(checkonly) return true; + if(items[itOrbFlash] && try_instant) { + if(checkonly) { nextmovetype = lmInstant; return true; } if(orbProtection(itOrbFlash)) return true; activateFlash(); bfs(); @@ -7863,8 +7871,8 @@ bool movepcto(int d, int subdir, bool checkonly) { return true; } - if(!passable(c2, cwt.at, P_ISPLAYER | P_MIRROR | P_USEBOAT | P_FRIENDSWAP) && items[itOrbLightning]) { - if(checkonly) return true; + if(items[itOrbLightning] && try_instant) { + if(checkonly) { nextmovetype = lmInstant; return true; } if(orbProtection(itOrbLightning)) return true; activateLightning(); keepLightning = true; @@ -7875,8 +7883,8 @@ bool movepcto(int d, int subdir, bool checkonly) { return true; } - if(isActivable(c2)) { - if(checkonly) return true; + if(isActivable(c2) && fmsActivate) { + if(checkonly) { nextmovetype = lmInstant; return true; } activateActiv(c2, true); bfs(); if(multi::players > 1) { multi::whereto[multi::cpid].d = MD_UNDECIDED; return false; } @@ -7884,7 +7892,7 @@ bool movepcto(int d, int subdir, bool checkonly) { return true; } - if(isPushable(c2->wall) && !c2->monst && !nonAdjacentPlayer(c2, cwt.at)) { + if(isPushable(c2->wall) && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { int pushdir; cell *c3 = determinePush(cwt, c2, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, cwt.at); }, pushdir); if(c3 == c2) { @@ -7897,7 +7905,7 @@ bool movepcto(int d, int subdir, bool checkonly) { return false; } global_pushto = c3; - if(checkonly) return true; + if(checkonly) { nextmovetype = lmMove; return true; } addMessage(XLAT("You push %the1.", c2->wall)); lastmovetype = lmPush; lastmove = cwt.at; pushThumper(c2, c3); @@ -7913,7 +7921,7 @@ bool movepcto(int d, int subdir, bool checkonly) { return false; } - if(isWatery(c2) && !nonAdjacentPlayer(cwt.at,c2) && !c2->monst && cwt.at->wall == waBoat) { + if(isWatery(c2) && !nonAdjacentPlayer(cwt.at,c2) && !c2->monst && cwt.at->wall == waBoat && fmsMove) { if(havePushConflict(cwt.at, checkonly)) return false; @@ -7939,14 +7947,14 @@ bool movepcto(int d, int subdir, bool checkonly) { if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); return false; } - - if(checkonly) return true; + + if(checkonly) { nextmovetype = lmMove; return true; } moveBoat(c2, cwt.at, d); boatmove = true; goto boatjump; } - if(!c2->monst && cwt.at->wall == waBoat && boatGoesThrough(c2) && markOrb(itOrbWater) && !nonAdjacentPlayer(c2, cwt.at)) { + if(!c2->monst && cwt.at->wall == waBoat && boatGoesThrough(c2) && markOrb(itOrbWater) && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { if(havePushConflict(cwt.at, checkonly)) return false; if(monstersnear(c2,NULL,moPlayer,NULL,cwt.at)) { @@ -7954,7 +7962,7 @@ bool movepcto(int d, int subdir, bool checkonly) { return false; } - if(checkonly) return true; + if(checkonly) { nextmovetype = lmMove; return true; } if(c2->item && !cwt.at->item) moveItem(c2, cwt.at, false), boatmove = true; placeWater(c2, cwt.at); moveBoat(c2, cwt.at, d); @@ -7964,7 +7972,7 @@ bool movepcto(int d, int subdir, bool checkonly) { } escape: - if(c2->wall == waBigStatue && !c2->monst && !nonAdjacentPlayer(c2, cwt.at)) { + if(c2->wall == waBigStatue && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { if(!canPushStatueOn(cwt.at)) { if(checkonly) return false; if(isFire(cwt.at)) @@ -7988,7 +7996,7 @@ bool movepcto(int d, int subdir, bool checkonly) { return false; } - if(checkonly) { c2->wall = save_c2; cwt.at->wall = save_cw; return true; } + if(checkonly) { c2->wall = save_c2; cwt.at->wall = save_cw; nextmovetype = lmMove; return true; } addMessage(XLAT("You push %the1 behind you!", waBigStatue)); animateMovement(c2, cwt.at, LAYER_BOAT, cwt.at->c.spin(d)); goto statuejump; @@ -7999,18 +8007,19 @@ bool movepcto(int d, int subdir, bool checkonly) { c2->wall == waBigTree || c2->wall == waSmallTree || c2->wall == waMirrorWall; - attackable = attackable && (!c2->monst || isFriendly(c2)); if(attackable && markOrb(itOrbAether) && c2->wall != waMirrorWall) attackable = false; + if(forcedmovetype == fmAttack) attackable = true; + attackable = attackable && (!c2->monst || isFriendly(c2)); attackable = attackable && !nonAdjacentPlayer(cwt.at,c2); - if(attackable) { + if(attackable && fmsAttack) { if(checkNeedMove(checkonly, true)) return false; if(monstersnear(cwt.at,c2,moPlayer,NULL,cwt.at)) { if(!checkonly && errormsgs) wouldkill("%The1 would get you!"); return false; } - if(checkonly) return true; + if(checkonly) { nextmovetype = lmAttack; return true; } if(c2->wall == waSmallTree) { drawParticles(c2, winf[c2->wall].color, 4); addMessage(XLAT("You chop down the tree.")); @@ -8029,7 +8038,12 @@ bool movepcto(int d, int subdir, bool checkonly) { } else { if(!peace::on) { - addMessage(XLAT("You swing your sword at the mirror.")); + if(c2->wall == waMirrorWall) + addMessage(XLAT("You swing your sword at the mirror.")); + else if(c2->wall) + addMessage(XLAT("You swing your sword at %the1.", c2->wall)); + else + addMessage(XLAT("You swing your sword.")); sideAttack(cwt.at, d, moPlayer, 0); animateAttack(cwt.at, c2, LAYER_SMALL, d); } @@ -8047,6 +8061,7 @@ bool movepcto(int d, int subdir, bool checkonly) { } else if(c2->monst && (!isFriendly(c2) || c2->monst == moTameBomberbird || isMountable(c2->monst)) && !(peace::on && !isMultitile(c2->monst) && !goodTortoise)) { + if(!fmsAttack) return false; flagtype attackflags = AF_NORMAL; if(items[itOrbSpeed]&1) attackflags |= AF_FAST; @@ -8122,7 +8137,7 @@ bool movepcto(int d, int subdir, bool checkonly) { if(c2->monst == moTameBomberbird && warningprotection_hit(moTameBomberbird)) return false; - if(checkonly) return true; + if(checkonly) { nextmovetype = lmAttack; return true; } /* if(c2->monst == moTortoise) { printf("seek=%d get=%d <%x/%x> item=%d\n", @@ -8197,14 +8212,14 @@ bool movepcto(int d, int subdir, bool checkonly) { } return false; } - else { + else if(fmsMove) { if(mineMarked(c2) && !minesafe() && !checkonly && warningprotection(XLAT("Are you sure you want to step there?"))) return false; if(monstersnear(c2, NULL, moPlayer, NULL, cwt.at)) { if(checkonly) return false; if(items[itOrbFlash]) { - if(checkonly) return true; + if(checkonly) { nextmovetype = lmInstant; return true; } if(orbProtection(itOrbFlash)) return true; activateFlash(); checkmove(); @@ -8212,7 +8227,7 @@ bool movepcto(int d, int subdir, bool checkonly) { } if(items[itOrbLightning]) { - if(checkonly) return true; + if(checkonly) { nextmovetype = lmInstant; return true; } if(orbProtection(itOrbLightning)) return true; activateLightning(); checkmove(); @@ -8244,7 +8259,7 @@ bool movepcto(int d, int subdir, bool checkonly) { if(!checkonly && warningprotection_hit(do_we_stab_a_friend(cwt.at, c2, moPlayer))) return false; - if(checkonly) return true; + if(checkonly) { nextmovetype = lmMove; return true; } boatjump: statuejump: flipplayer = true; if(multi::players > 1) multi::flipped[multi::cpid] = true; @@ -8336,6 +8351,7 @@ bool movepcto(int d, int subdir, bool checkonly) { landvisited[cwt.at->land] = true; afterplayermoved(); } + else return false; } else { if(items[itOrbGravity]) { @@ -8350,7 +8366,7 @@ bool movepcto(int d, int subdir, bool checkonly) { wouldkill("%The1 would get you!"); return false; } - if(checkonly) return true; + if(checkonly) { nextmovetype = lmSkip; return true; } swordAttackStatic(); if(d == -2) dropGreenStone(cwt.at); diff --git a/geometry.cpp b/geometry.cpp index 3cea4969..35da18e1 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -361,6 +361,7 @@ namespace geom3 { namespace geom3 { #if MAXMDIM >= 4 void switch_always3() { + if(dual::split(switch_always3)) return; if(rug::rugged) rug::close(); geom3::always3 = !geom3::always3; swapmatrix(View); @@ -369,6 +370,7 @@ void switch_always3() { #endif void switch_tpp() { + if(dual::split(switch_fpp)) return; if(pmodel == mdDisk && vid.camera_angle) { vid.yshift = 0; vid.camera_angle = 0; @@ -391,6 +393,7 @@ void switch_always3() { void switch_fpp() { #if MAXMDIM >= 4 if(rug::rugged) rug::close(); + if(dual::split(switch_fpp)) return; if(!geom3::always3) { geom3::always3 = true; geom3::wall_height = 1.5; diff --git a/graph.cpp b/graph.cpp index d0dace05..5aab5089 100644 --- a/graph.cpp +++ b/graph.cpp @@ -7240,7 +7240,14 @@ void gamescreen(int _darken) { just_gmatrix = false; return; } - + + if(dual::split([=] () { + dual::in_subscreen([=] () { gamescreen(_darken); }); + })) { + calcparam(); + return; + } + if((cmode & sm::MAYDARK) && !current_display->sidescreen) { _darken += 2; } @@ -7463,11 +7470,19 @@ auto graphcm = addHook(clearmemory, 0, [] () { mouseover = centerover.at = lmouseover = NULL; gmatrix.clear(); gmatrix0.clear(); clearAnimations(); + }) ++ addHook(hooks_gamedata, 0, [] (gamedata* gd) { + gd->store(mouseover); + gd->store(lmouseover); + gd->store(animations); + gd->store(flashes); + gd->store(fallanims); }); +; //=== animation -map animations[ANIMLAYERS]; +array, ANIMLAYERS> animations; int revhint(cell *c, int hint) { if(hint >= 0 && hint < c->type) return c->c.spin(hint); diff --git a/hyper.h b/hyper.h index 7e48c5d7..83fbdf63 100644 --- a/hyper.h +++ b/hyper.h @@ -2090,7 +2090,7 @@ struct animation { #define LAYER_SMALL 1 // for others #define LAYER_BOAT 2 // mark that a boat has moved -extern map animations[ANIMLAYERS]; +extern array, ANIMLAYERS> animations; void animateAttack(cell *src, cell *tgt, int layer, int direction_hint); @@ -4196,8 +4196,6 @@ bool score_loaded(int id); int score_default(int id); void handle_event(SDL_Event& ev); -void pop_game(); -void push_game(); void start_game(); void stop_game(); void switch_game_mode(char switchWhat); @@ -4612,6 +4610,8 @@ extern vector help_extensions; namespace gamestack { bool pushed(); + void push(); + void pop(); } namespace geom3 { @@ -5407,5 +5407,58 @@ static const int POLY_INTENSE = (1<<23); // extra intense colors void pregen(); extern vector currentlands; -} +struct gamedata { + // important parameters should be visible + eGeometry geo; + eVariation var; + eLand specland; + bool active; + // other properties are recorded + vector record; + int index, mode; + void storegame(); + void restoregame(); + template void store(T& x) { + int ssize = sizeof(x); + if(ssize & 7) ssize = (ssize | 7) + 1; + if(mode == 0) { + record.resize(index+ssize); + T& at = *(new (&record[index]) T()); + at = move(x); + } + else { + T& at = (T&) record[index]; + x = move(at); + at.~T(); + } + index += ssize; + } + }; + +/* lastmovetype uses lmSkip, lmMove, lmAttack, lmPush, lmTree */ +enum eLastmovetype { lmSkip, lmMove, lmAttack, lmPush, lmTree, lmInstant }; +extern eLastmovetype lastmovetype, nextmovetype; + +enum eForcemovetype { fmSkip, fmMove, fmAttack, fmInstant, fmActivate }; +extern eForcemovetype forcedmovetype; + +extern hookset *hooks_gamedata; + +namespace dual { + // 0 = dualmode off, 1 = in dualmode (no game chosen), 2 = in dualmode (working on one of subgames) + extern int state; + extern int currently_loaded, main_side; + + bool movepc(int d, int subdir, bool checkonly); + extern transmatrix player_orientation[2]; + + bool split(reaction_t what); + void switch_to(int i); + void in_subscreen(reaction_t what); + + bool check_side(eLand l); + void assign_landsides(); + } + +} \ No newline at end of file diff --git a/hypgraph.cpp b/hypgraph.cpp index 8f479b87..1c9e9062 100644 --- a/hypgraph.cpp +++ b/hypgraph.cpp @@ -1063,7 +1063,19 @@ transmatrix eumovedir(int d) { void spinEdge(ld aspd) { ld downspin = 0; - if(playerfound && vid.fixed_facing) { + if(dual::state == 2 && dual::currently_loaded != dual::main_side) { + transmatrix our = gpushxto0(tC0(cwtV)) * cwtV; + transmatrix their = dual::player_orientation[dual::main_side]; + fixmatrix(our); + fixmatrix(their); + if(DIM == 2) { + transmatrix T = their * inverse(our); + hyperpoint H = T * xpush0(1); + downspin = -atan2(H[1], H[0]); + } + else View = their * inverse(our) * View; + } + else if(playerfound && vid.fixed_facing) { hyperpoint H = gpushxto0(playerV * C0) * playerV * xpush0(5); downspin = atan2(H[1], H[0]); downspin += vid.fixed_facing_dir * degree; @@ -1099,6 +1111,7 @@ void spinEdge(ld aspd) { void centerpc(ld aspd) { if(subscreens::split([=] () {centerpc(aspd);})) return; + if(dual::split([=] () { centerpc(aspd); })) return; #if CAP_CRYSTAL if(geometry == gCrystal) @@ -1179,6 +1192,7 @@ void centerpc(ld aspd) { void optimizeview() { if(subscreens::split(optimizeview)) return; + if(dual::split(optimizeview)) return; #if CAP_ANIMATIONS if(centerover.at && inmirror(centerover.at)) { diff --git a/landgen.cpp b/landgen.cpp index dcb2aca8..1cdfa505 100644 --- a/landgen.cpp +++ b/landgen.cpp @@ -156,6 +156,9 @@ void place_elemental_wall(cell *c) { } int hrand_monster(int x) { + // dual geometry mode is much harder, so generate less monsters to balance it + if(dual::state) x *= 3; + // in 3D monster generation depends on the sight range if(WDIM == 3 && !sphere) { int t = isize(gmatrix); if(t > 300) x = ((long long)(x)) * t / 300; diff --git a/landlock.cpp b/landlock.cpp index 2032ed11..7a1eacfc 100644 --- a/landlock.cpp +++ b/landlock.cpp @@ -572,6 +572,7 @@ eLand getLandForList(cell *c) { bool isLandIngame(eLand l) { if(isElemental(l)) l = laElementalWall; + if(dual::state == 2 && !dual::check_side(l)) return false; return land_validity(l).flags & lv::appears_in_full; } diff --git a/menus.cpp b/menus.cpp index 7fc1640d..0d486e74 100644 --- a/menus.cpp +++ b/menus.cpp @@ -37,7 +37,10 @@ void showOverview() { bool pages; + { + dynamicval ds(dual::state, dual::state ? 2 : 0); generateLandList(isLandIngame); + } bool not_in_game = false; diff --git a/multigame.cpp b/multigame.cpp new file mode 100644 index 00000000..2a1a90a6 --- /dev/null +++ b/multigame.cpp @@ -0,0 +1,281 @@ +// Hyperbolic Rogue + +// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details + +// gamedata structure, for recording the game data in memory temporarily +// namespace dual (dual mode) + +#include "allhyper.h" + +namespace hr { + +bool isEquidLand(eLand); +extern bool orbused[ittypes]; + +void gamedata_all(gamedata& gd) { + gd.index = 0; + gd.store(currentmap); + gd.store(cwt); + gd.store(allmaps); + gd.store(shmup::on); + gd.store(*current_display); + gd.store(cgip); + if(GOLDBERG) gd.store(gp::param); + callhooks(hooks_gamedata, &gd); + } + +void gamedata::storegame() { + geo = geometry; + var = variation; + specland = specialland; + active = game_active; + record.clear(); + mode = 0; + gamedata_all(*this); + game_active = false; + } + +void gamedata::restoregame() { + geometry = geo; + variation = var; + specialland = specland; + game_active = active; + mode = 1; + gamedata_all(*this); + } + +hookset *hooks_gamedata; + +namespace gamestack { + + vector gd; + + bool pushed() { return isize(gd); } + + void push() { + gd.emplace_back(); + gd.back().storegame(); + } + + void pop() { + if(!pushed()) return; + if(game_active) stop_game(); + gd.back().restoregame(); + gd.pop_back(); + } + + }; + +namespace dual { + int state; + + int currently_loaded; + int main_side; + + gamedata dgd[2]; + ld scales[2]; + transmatrix player_orientation[2]; + + void switch_to(int k) { + if(k != currently_loaded) { + scales[currently_loaded] = vid.scale; + player_orientation[currently_loaded] = gpushxto0(tC0(cwtV)) * cwtV; + dgd[currently_loaded].storegame(); + currently_loaded = k; + dgd[currently_loaded].restoregame(); + vid.scale = scales[currently_loaded]; + } + } + + bool movepc(int d, int subdir, bool checkonly) { + dynamicval dm(dual::state, 2); + int cg = currently_loaded; + + bool orbusedbak[ittypes]; + for(int i=0; i ", lms[k][fm]); + forcedmovetype = fmSkip; + for(int i=0; i xmax(current_display->xmax, 0.5 * (currently_loaded+1)); + dynamicval xmin(current_display->xmin, 0.5 * (currently_loaded)); + what(); + } + + bool split(reaction_t what) { + if(state != 1) return false; + state = 2; + + for(int a=0; a<2; a++) { + switch_to(currently_loaded ^ 1); + what(); + } + + state = 1; + return true; + } + + int args() { + using namespace arg; + + if(0) ; + else if(argis("-dual")) { + PHASEFROM(3); + stop_game(); + subscreens::prepare(); + + for(int s=0; s<2; s++) { + // dynamicval pds(current_display, &subscreens::player_displays[s]); + variation = eVariation::pure; + geometry = s == 0 ? gEuclidSquare : gArchimedean; + arcm::current.parse("4,4,4,4,4"); + scales[s] = vid.scale; + dgd[s].storegame(); + } + + currently_loaded = 0; + dgd[0].restoregame(); + state = 1; + } + else if(argis("-dual0:")) { + switch_to(0); + } + else if(argis("-dual1:")) { + switch_to(1); + } + else return 1; + return 0; + } + + auto hook = addHook(hooks_args, 100, args); + + vector landsides; + + bool check_side(eLand l) { + return landsides[l] == currently_loaded || landsides[l] == 2; + } + + void assign_landsides() { + landsides.resize(landtypes); + int which_hyperbolic = -1; + if(ginf[dgd[0].geo].cclass == gcHyperbolic && ginf[dgd[1].geo].cclass != gcHyperbolic) + which_hyperbolic = 0; + else if(ginf[dgd[1].geo].cclass == gcHyperbolic && ginf[dgd[0].geo].cclass != gcHyperbolic) + which_hyperbolic = 1; + int nxt = 0; + for(int i=0; i lv[1].quality_level) + v = 0; + else if(lv[1].quality_level > lv[0].quality_level) + v = 1; + else if(isEquidLand(l) && which_hyperbolic >= 0) + v = which_hyperbolic; + else if(among(l, laHunting, laMotion, laCaves, laAlchemist) && which_hyperbolic >= 0) + v = which_hyperbolic; + else if(among(l, laMirrorOld, laIce, laJungle, laDesert, laDryForest, laStorms) && which_hyperbolic >= 0) + v = 1 - which_hyperbolic; + else if(which_hyperbolic >= 0) + v = which_hyperbolic; + else { + println(hlog, "equivalent"); + v = nxt, nxt = 1 - nxt; + } + println(hlog, dnameof(l), ": ", lv[0].msg, " vs ", lv[1].msg, " verdict = ", v); + } + } + + } + +} diff --git a/rug.cpp b/rug.cpp index 3f235fe1..b9d80f1f 100644 --- a/rug.cpp +++ b/rug.cpp @@ -1491,6 +1491,7 @@ void init_model() { } void init() { + if(dual::state) return; reopen(); if(rugged) init_model(); } @@ -2016,6 +2017,7 @@ void show() { } void select() { + if(dual::state) return; pushScreen(rug::show); } diff --git a/shmup.cpp b/shmup.cpp index e7db4832..66821cfd 100644 --- a/shmup.cpp +++ b/shmup.cpp @@ -3272,6 +3272,7 @@ hookset *hooks_turn; void turn(int delta) { if(racing::on && subscreens::split( [delta] () { turn(delta); })) return; + if(dual::split( [delta] () { turn(delta); })) return; if(callhandlers(false, hooks_turn, delta)) return; if(!shmup::on) return; diff --git a/system.cpp b/system.cpp index 1ed26640..0934936a 100644 --- a/system.cpp +++ b/system.cpp @@ -1082,76 +1082,9 @@ void loadsave() { } #endif -namespace gamestack { - - struct gamedata { - hrmap *hmap; - cellwalker cwt; - display_data d; - eGeometry geometry; - eVariation variation; - bool shmup; - void store(); - void restore(); - }; - - vector gd; - - bool pushed() { return isize(gd); } - - void gamedata::store() { - hmap = currentmap; - cwt = hr::cwt; - geometry = hr::geometry; - shmup = hr::shmup::on; - variation = hr::variation; - d = *current_display; - } - - void gamedata::restore() { - currentmap = hmap; - hr::cwt = cwt; - hr::geometry = geometry; - hr::variation = variation; - if(shmup::on) shmup::clearMonsters(); - shmup::on = shmup; - check_cgi(); - cgi.require_basics(); - *current_display = d; - bfs(); - } - - void push() { - if(geometry) { - printf("ERROR: push implemented only in non-hyperbolic geometry\n"); - exit(1); - } - gd.emplace_back(); - gd.back().store(); - } - - void pop() { - gd.back().restore(); - gd.pop_back(); - } - - }; - -void pop_game() { - if(gamestack::pushed()) { - gamestack::pop(); - game_active = true; - } - } - -void popAllGames() { - while(gamestack::pushed()) { - gamestack::pop(); - } - } - void stop_game() { if(!game_active) return; + if(dual::split(stop_game)) return; DEBBI(DF_INIT, ("stop_game")); achievement_final(true); #if CAP_SAVE @@ -1186,13 +1119,6 @@ void stop_game() { #endif } -void push_game() { - gamestack::push(); - pd_from = NULL; - centerover.at = NULL; - game_active = false; - } - void set_geometry(eGeometry target) { if(geometry != target) { int old_DIM = DIM; @@ -1269,6 +1195,10 @@ void switch_game_mode(char switchWhat) { #if CAP_TOUR case rg::tour: + while(gamestack::pushed()) { + gamestack::pop(); + stop_game(); + } geometry = gNormal; yendor::on = tactic::on = princess::challenge = peace::on = inv::on = false; chaosmode = randomPatternsMode = false; @@ -1360,6 +1290,8 @@ void switch_game_mode(char switchWhat) { void start_game() { if(game_active) return; DEBBI(DF_INIT, ("start_game")); + if(dual::state == 1) dual::assign_landsides(); + if(dual::split(start_game)) return; restart: game_active = true; gamegen_failure = false; @@ -1397,7 +1329,6 @@ void start_game() { void restart_game(char switchWhat) { popScreenAll(); - popAllGames(); stop_game(); switch_game_mode(switchWhat); start_game(); @@ -1428,6 +1359,21 @@ auto cgm = addHook(clearmemory, 40, [] () { rosemap.clear(); adj_memo.clear(); }) + +addHook(hooks_gamedata, 0, [] (gamedata* gd) { + gd->store(pathq); + gd->store(dcal); + gd->store(recallCell); + gd->store(butterflies); + gd->store(buggycells); + gd->store(crush_now); + gd->store(crush_next); + gd->store(rosemap); + gd->store(adj_memo); + gd->store(pd_from); + gd->store(pd_range); + gd->store(pathqm); + gd->store(reachedfrom); + }) + addHook(hooks_removecells, 0, [] () { eliminate_if(crush_next, is_cell_removed); eliminate_if(crush_now, is_cell_removed); diff --git a/tour.cpp b/tour.cpp index 88502b15..9cd7051d 100644 --- a/tour.cpp +++ b/tour.cpp @@ -22,7 +22,7 @@ void setCanvas(presmode mode, char canv) { static char wc; static eLand ld; if(mode == pmStart) { - push_game(); + gamestack::push(); wc = patterns::whichCanvas; patterns::whichCanvas = canv; ld = firstland; @@ -30,9 +30,9 @@ void setCanvas(presmode mode, char canv) { start_game(); } if(mode == pmStop) { + gamestack::pop(); patterns::whichCanvas = wc; firstland = ld; - pop_game(); } } @@ -77,7 +77,7 @@ void slidehelp() { } void return_geometry() { - pop_game(); + gamestack::pop(); vid.scale = 1; vid.alpha = 1; presentation(pmGeometryReset); addMessage(XLAT("Returned to your game.")); @@ -103,7 +103,7 @@ bool handleKeyTour(int sym, int uni) { } if(sym == SDLK_BACKSPACE) { if(gamestack::pushed()) { - pop_game(); + gamestack::pop(); if(!(flags & QUICKGEO)) return true; } if(currentslide == 0) { slidehelp(); return true; } @@ -158,7 +158,7 @@ bool handleKeyTour(int sym, int uni) { presentation(pmGeometry); firstland = specialland = cwt.at->land; - push_game(); + gamestack::push(); switch(NUMBERKEY) { case '3': set_variation(eVariation::pure); @@ -290,8 +290,8 @@ namespace ss { for(int i=0;; i++) { dialog::addBoolItem(XLAT(wts[i].name), wts == slides && i == currentslide, slidechars[i]); dialog::add_action([i] { - if(geometry || CHANGED_VARIATION) { - pop_game(); + if(gamestack::pushed()) { + gamestack::pop(); presentation(pmGeometryReset); } if(slides != wts) { @@ -770,13 +770,13 @@ slide default_slides[] = { [] (presmode mode) { if(mode == 1) { firstland = cwt.at->land; - push_game(); + gamestack::push(); switch_game_mode(rg::shmup); start_game(); } if(mode == 3) { shmup::clearMonsters(); - pop_game(); + gamestack::pop(); } } },