1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-12-24 01:00:25 +00:00

multigame support, including dualmode

This commit is contained in:
Zeno Rogue 2019-05-29 01:09:38 +02:00
parent 587652e540
commit 8295251cdc
17 changed files with 492 additions and 126 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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<cell*, animation> animations[ANIMLAYERS];
array<map<cell*, animation>, ANIMLAYERS> animations;
int revhint(cell *c, int hint) {
if(hint >= 0 && hint < c->type) return c->c.spin(hint);

61
hyper.h
View File

@ -2090,7 +2090,7 @@ struct animation {
#define LAYER_SMALL 1 // for others
#define LAYER_BOAT 2 // mark that a boat has moved
extern map<cell*, animation> animations[ANIMLAYERS];
extern array<map<cell*, animation>, 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_extension> 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<eLand> currentlands;
}
struct gamedata {
// important parameters should be visible
eGeometry geo;
eVariation var;
eLand specland;
bool active;
// other properties are recorded
vector<char> record;
int index, mode;
void storegame();
void restoregame();
template<class T> 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<void(gamedata*)> *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();
}
}

View File

@ -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)) {

View File

@ -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;

View File

@ -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;
}

View File

@ -37,7 +37,10 @@ void showOverview() {
bool pages;
{
dynamicval<int> ds(dual::state, dual::state ? 2 : 0);
generateLandList(isLandIngame);
}
bool not_in_game = false;

281
multigame.cpp Normal file
View File

@ -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<void(gamedata*)> *hooks_gamedata;
namespace gamestack {
vector<gamedata> 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<int> dm(dual::state, 2);
int cg = currently_loaded;
bool orbusedbak[ittypes];
for(int i=0; i<ittypes; i++) orbusedbak[i] = orbused[i];
if(d < 0) {
if(d == -2 && items[itGreenStone] == 1) {
switch_to(cg);
return false;
}
bool ok = true;
for(int k=0; k<2; k++) {
switch_to(k);
ok = ok && movepcto(d, subdir, true);
for(int i=0; i<ittypes; i++) orbused[i] = orbusedbak[i];
}
if(ok && checkonly) {
switch_to(cg);
return true;
}
if(ok) for(int k=0; k<2; k++) {
switch_to(k);
movepcto(d, subdir, false);
}
switch_to(cg);
return true;
}
bool lms[2][5];
for(int k=0; k<2; k++) {
switch_to(k);
for(eForcemovetype fm: { fmMove, fmAttack, fmInstant, fmActivate }) {
forcedmovetype = fm;
lms[k][fm] = movepcto(fm == fmMove ? d : 0, subdir, true);
println(hlog, k, int(fm), " -> ", lms[k][fm]);
forcedmovetype = fmSkip;
for(int i=0; i<ittypes; i++) orbused[i] = orbusedbak[i];
}
}
if(lms[0][fmActivate]) {
if(checkonly) { switch_to(cg); return true; }
switch_to(0); forcedmovetype = fmActivate; movepcto(0, subdir, false); forcedmovetype = fmSkip;
if(!lms[1][fmActivate]) return true;
}
if(lms[1][fmActivate]) {
if(checkonly) { switch_to(cg); return true; }
switch_to(1); forcedmovetype = fmActivate; movepcto(0, subdir, false); forcedmovetype = fmSkip;
switch_to(cg);
return true;
}
for(auto fm: {fmMove, fmInstant, fmAttack}) if(lms[0][fm] && lms[1][fm]) {
println(hlog, "apply ", int(fm));
if(checkonly) { switch_to(cg); return true; }
int flash = items[itOrbFlash], lgt = items[itOrbLightning];
switch_to(0); forcedmovetype = fm; movepcto(0, subdir, false); forcedmovetype = fmSkip;
if(fm == fmInstant) { items[itOrbFlash] = flash, items[itOrbLightning] = lgt; }
switch_to(1); forcedmovetype = fm; movepcto(0, subdir, false); forcedmovetype = fmSkip;
switch_to(cg);
reduceOrbPowers();
return true;
}
println(hlog, "no match");
switch_to(cg);
return false;
}
void in_subscreen(reaction_t what) {
dynamicval<ld> xmax(current_display->xmax, 0.5 * (currently_loaded+1));
dynamicval<ld> 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<display_data*> 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<int> 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<landtypes; i++) {
eLand l = eLand(i);
auto& v = landsides[i];
land_validity_t lv[2];
for(int s=0; s<2; s++) {
switch_to(s);
lv[s] = land_validity(l);
}
if(!(lv[0].flags & lv::appears_in_full) && !(lv[1].flags & lv::appears_in_full)) {
v = -1;
continue;
}
else if(isCrossroads(l))
v = -1; /* simply boring */
else if(isGravityLand(l))
v = -1; /* too confusing */
else if(among(l, laTortoise))
v = -1; /* does not work in hyperbolic geos available, and better not do it in Euclidean ones either */
else if(among(l, laHaunted))
v = -1; /* graveyard prefers Euclidean, while Haunted prefers hyperbolic */
else if(l == dgd[0].specland && l == dgd[1].specland)
v = 2;
else if(l == dgd[0].specland)
v = 0;
else if(l == dgd[1].specland)
v = 1;
else if(isElemental(l))
v = 1;
else if(!(lv[0].flags & lv::appears_in_full))
v = 1;
else if(!(lv[1].flags & lv::appears_in_full))
v = 0;
else if(lv[0].quality_level > 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);
}
}
}
}

View File

@ -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);
}

View File

@ -3272,6 +3272,7 @@ hookset<bool(int)> *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;

View File

@ -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<gamedata> 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);

View File

@ -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();
}
}
},