hyperrogue/multigame.cpp

475 lines
12 KiB
C++
Raw Permalink Normal View History

// Hyperbolic Rogue -- multi-game features
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
2019-05-28 23:09:38 +00:00
/** \file multi.cpp
* \brief running several games at once -- used in the Tutorial and Dual Geometry mode
*/
2019-05-28 23:09:38 +00:00
#include "hyper.h"
2019-05-28 23:09:38 +00:00
namespace hr {
2019-08-09 19:18:13 +00:00
#if HDR
/** gamedata structure, for recording the game data in memory temporarily */
2019-08-09 19:18:13 +00:00
struct gamedata {
/** important parameters should be visible */
2019-08-09 19:18:13 +00:00
eGeometry geo;
eVariation var;
eLand specland;
bool active;
/** other properties are recorded here */
2019-08-09 19:18:13 +00:00
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);
::new (&record[index]) T(std::move(x));
2019-08-09 19:18:13 +00:00
}
else {
T& at = (T&) record[index];
x = std::move(at);
2019-08-09 19:18:13 +00:00
at.~T();
}
index += ssize;
}
2020-07-12 22:07:39 +00:00
template<class T> void store_ptr(T& x) {
if(mode == 0) {
T* copy = new T(std::move(x));
store(copy);
2020-07-12 22:07:39 +00:00
}
else {
T* copy = nullptr;
store(copy);
x = std::move(*copy);
2020-07-12 22:07:39 +00:00
delete copy;
}
}
2019-08-09 19:18:13 +00:00
};
#endif
2019-05-28 23:09:38 +00:00
void gamedata_all(gamedata& gd) {
gd.index = 0;
gd.store(firstland);
2019-05-28 23:09:38 +00:00
gd.store(currentmap);
gd.store(cwt);
gd.store(allmaps);
gd.store(shmup::on);
2021-04-11 20:15:40 +00:00
gd.store(land_structure);
2019-05-28 23:09:38 +00:00
gd.store(*current_display);
gd.store(cgip);
if(gd.mode == 0) cgip->use_count++;
if(gd.mode != 0) cgip->use_count--;
2021-09-16 19:30:26 +00:00
gd.store(hybrid::underlying);
gd.store(hybrid::csteps);
2022-12-08 18:38:06 +00:00
if(mhybrid && hybrid::underlying_cgip) {
if(gd.mode == 0) hybrid::underlying_cgip->use_count++;
if(gd.mode != 0) hybrid::underlying_cgip->use_count--;
}
2021-09-16 19:30:26 +00:00
gd.store(hybrid::underlying_cgip);
gd.store_ptr(vid.projection_config);
gd.store_ptr(vid.rug_config);
gd.store(vid.yshift);
gd.store(vid.plevel_factor);
gd.store(vid.binary_width);
2019-05-29 14:37:20 +00:00
gd.store(sightrange_bonus);
gd.store(genrange_bonus);
gd.store(gamerange_bonus);
2019-05-29 18:20:52 +00:00
gd.store(targets);
gd.store(patterns::rwalls);
2019-05-28 23:09:38 +00:00
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);
}
EX hookset<void(gamedata*)> hooks_gamedata;
2019-05-28 23:09:38 +00:00
2019-08-09 19:00:52 +00:00
EX namespace gamestack {
2019-05-28 23:09:38 +00:00
vector<gamedata> gd;
2019-08-09 19:00:52 +00:00
EX bool pushed() { return isize(gd); }
2019-05-28 23:09:38 +00:00
2019-08-09 19:00:52 +00:00
EX void push() {
2019-05-28 23:09:38 +00:00
gd.emplace_back();
gd.back().storegame();
}
2019-08-09 19:00:52 +00:00
EX void pop() {
2019-05-28 23:09:38 +00:00
if(!pushed()) return;
if(game_active) stop_game();
gd.back().restoregame();
gd.pop_back();
}
2019-08-09 19:00:52 +00:00
EX }
2019-05-28 23:09:38 +00:00
2019-08-09 19:00:52 +00:00
EX namespace dual {
/** 0 = dualmode off, 1 = in dualmode (no game chosen), 2 = in dualmode (working on one of subgames) */
2019-08-09 19:00:52 +00:00
EX int state;
/** exactly one side is Euclidean -- it should not be synchronized with the other side */
EX bool one_euclidean;
2019-08-09 19:00:52 +00:00
EX int currently_loaded;
EX int main_side;
EX bool affect_both;
2019-05-28 23:09:38 +00:00
gamedata dgd[2];
2019-08-09 19:00:52 +00:00
EX transmatrix player_orientation[2];
2019-05-28 23:09:38 +00:00
2019-06-24 21:00:12 +00:00
hyperpoint which_dir;
2022-06-16 23:20:34 +00:00
EX purehookset hooks_after_move;
2019-06-24 21:00:12 +00:00
int remap_direction(int d, int cg) {
if(WDIM == 2 || cg == currently_loaded) return d;
hyperpoint h = sword::dir[0].T * which_dir;
h = hpxy3(h[0]/10, h[1]/10, h[2]/10);
ld b = HUGE_VAL;
for(int i=0; i<S7; i++) {
2019-11-26 23:39:41 +00:00
hyperpoint checked = tC0(currentmap->relative_matrix(cwt.at->cmove(i)->master, cwt.at->master, C0));
2019-06-24 21:00:12 +00:00
ld dist = hdist(checked, h);
if(dist < b) { b = dist; d = i; }
}
d = gmod(d - cwt.spin, S7);
return d;
}
2019-08-09 19:00:52 +00:00
EX transmatrix get_orientation() {
2019-06-24 21:00:12 +00:00
if(WDIM == 2)
return gpushxto0(tC0(cwtV.T)) * cwtV.T;
else if(cwt.at) {
transmatrix T = unshift(ggmatrix(cwt.at));
return gpushxto0(tC0(T)) * T * sword::dir[0].T;
}
2019-06-24 21:00:12 +00:00
else
return Id;
}
2019-08-09 19:00:52 +00:00
EX void switch_to(int k) {
2019-05-28 23:09:38 +00:00
if(k != currently_loaded) {
// gamedata has shmup::on because tutorial needs changing it, but dual should keep it fixed
dynamicval<bool> smon(shmup::on);
2019-06-24 21:00:12 +00:00
player_orientation[currently_loaded] = get_orientation();
2019-05-28 23:09:38 +00:00
dgd[currently_loaded].storegame();
currently_loaded = k;
dgd[currently_loaded].restoregame();
}
}
2019-08-09 19:00:52 +00:00
EX bool movepc(int d, int subdir, bool checkonly) {
2019-05-28 23:09:38 +00:00
dynamicval<int> dm(dual::state, 2);
int cg = currently_loaded;
auto orbusedbak = orbused;
2019-05-28 23:09:38 +00:00
if(d < 0) {
2019-05-29 21:07:18 +00:00
if(d == -2 && items[itGreenStone] < 2) {
2019-05-28 23:09:38 +00:00
switch_to(cg);
2019-05-29 21:07:18 +00:00
glance_message();
2019-05-28 23:09:38 +00:00
return false;
}
bool ok = true;
for(int k=0; k<2; k++) {
switch_to(k);
ok = ok && movepcto(d, subdir, true);
orbused = orbusedbak;
2019-05-28 23:09:38 +00:00
}
if(ok && checkonly) {
switch_to(cg);
return true;
}
if(ok) for(int k=0; k<2; k++) {
switch_to(k);
movepcto(d, subdir, false);
2019-05-30 14:20:09 +00:00
if(k == 0) turncount--;
2019-05-28 23:09:38 +00:00
}
if(!ok) {
addMessage(XLAT("Impossible."));
}
2019-05-28 23:09:38 +00:00
switch_to(cg);
return ok;
2019-05-28 23:09:38 +00:00
}
2019-11-26 23:39:41 +00:00
which_dir = inverse(sword::dir[0].T) * tC0(currentmap->relative_matrix((cwt+d).cpeek()->master, cwt.at->master, C0));
2019-06-24 21:00:12 +00:00
2019-05-28 23:09:38 +00:00
bool lms[2][5];
eLastmovetype lmt[2][5];
2019-05-28 23:09:38 +00:00
for(int k=0; k<2; k++) {
switch_to(k);
for(eForcemovetype fm: { fmMove, fmAttack, fmInstant, fmActivate }) {
forcedmovetype = fm;
2019-06-24 21:00:12 +00:00
lms[k][fm] = movepcto(fm == fmMove ? remap_direction(d, cg) : 0, subdir, true);
lmt[k][fm] = nextmovetype;
2019-05-28 23:09:38 +00:00
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]) {
if(lmt[0][fm] == lmSkip && lmt[1][fm] == lmSkip)
continue;
2019-05-28 23:09:38 +00:00
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; }
2019-05-30 14:20:09 +00:00
turncount--;
2019-05-28 23:09:38 +00:00
switch_to(1); forcedmovetype = fm; movepcto(0, subdir, false); forcedmovetype = fmSkip;
switch_to(cg);
reduceOrbPowers();
2022-06-16 23:20:34 +00:00
callhooks(hooks_after_move);
2019-05-28 23:09:38 +00:00
return true;
}
2019-05-29 21:10:36 +00:00
addMessage(XLAT("Impossible."));
flipplayer = false;
2019-05-28 23:09:38 +00:00
switch_to(cg);
return false;
}
2019-08-09 19:00:52 +00:00
EX void in_subscreen(reaction_t what) {
2019-05-28 23:09:38 +00:00
dynamicval<ld> xmax(current_display->xmax, 0.5 * (currently_loaded+1));
dynamicval<ld> xmin(current_display->xmin, 0.5 * (currently_loaded));
what();
}
2019-08-09 19:00:52 +00:00
EX bool split(reaction_t what) {
2019-05-28 23:09:38 +00:00
if(state != 1) return false;
state = 2;
for(int a=0; a<2; a++) {
switch_to(currently_loaded ^ 1);
what();
}
state = 1;
return true;
}
2019-08-09 19:00:52 +00:00
EX void enable() {
2019-05-29 00:33:33 +00:00
if(dual::state) return;
stop_game();
eGeometry b = geometry;
eVariation v = variation;
2019-05-29 00:33:33 +00:00
for(int s=0; s<2; s++) {
// dynamicval<display_data*> pds(current_display, &subscreens::player_displays[s]);
2019-05-29 20:12:58 +00:00
if(WDIM == 3) {
variation = eVariation::pure;
geometry = s == 0 ? gCubeTiling : gSpace435;
}
else if(shmup::on) {
geometry = b;
variation = v;
// 'do what I mean'
if(euclid)
geometry = s == 0 ? b : (ginf[geometry].vertex == 3 ? gNormal : g45);
else
geometry = s == 0 ? (ginf[geometry].vertex == 3 ? gEuclid : gEuclidSquare) : b;
if(geometry == gEuclid) variation = eVariation::bitruncated;
}
else {
variation = eVariation::pure;
2020-10-15 14:33:52 +00:00
#if CAP_ARCM
geometry = s == 0 ? gEuclidSquare : gArchimedean;
2020-10-15 14:33:52 +00:00
#else
geometry = gEuclidSquare;
#endif
}
firstland = specialland = laCrossroads4;
2020-10-15 14:33:52 +00:00
#if CAP_ARCM
if(geometry == gArchimedean)
arcm::current.parse("4,4,4,4,4");
2020-10-15 14:33:52 +00:00
#endif
check_cgi();
cgi.require_basics();
2019-05-29 00:33:33 +00:00
dgd[s].storegame();
}
currently_loaded = 0;
dgd[0].restoregame();
state = 1;
}
2019-08-09 19:00:52 +00:00
EX void disable() {
2019-05-29 00:33:33 +00:00
if(!dual::state) return;
stop_game();
state = 0;
}
2019-06-28 07:54:10 +00:00
#if CAP_COMMANDLINE
2019-05-28 23:09:38 +00:00
int args() {
using namespace arg;
if(0) ;
2019-05-29 00:33:33 +00:00
else if(argis("-dual0")) {
PHASEFROM(2);
2019-05-29 00:33:33 +00:00
enable();
2019-05-28 23:09:38 +00:00
switch_to(0);
}
2019-05-29 00:33:33 +00:00
else if(argis("-dual1")) {
PHASEFROM(2);
2019-05-29 00:33:33 +00:00
enable();
2019-05-28 23:09:38 +00:00
switch_to(1);
}
2019-05-29 00:33:33 +00:00
else if(argis("-dualoff")) {
PHASEFROM(2);
2019-05-29 00:33:33 +00:00
disable();
}
2019-05-28 23:09:38 +00:00
else return 1;
return 0;
}
auto hook = addHook(hooks_args, 100, args);
2019-06-28 07:54:10 +00:00
#endif
2019-05-28 23:09:38 +00:00
vector<int> landsides;
2019-08-09 19:00:52 +00:00
EX bool check_side(eLand l) {
2019-05-28 23:09:38 +00:00
return landsides[l] == currently_loaded || landsides[l] == 2;
}
2019-08-09 19:00:52 +00:00
EX void assign_landsides() {
switch_to(!currently_loaded);
one_euclidean = euclid;
switch_to(!currently_loaded);
one_euclidean ^= euclid;
2019-05-28 23:09:38 +00:00
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 == laPower)
v = which_hyperbolic;
2019-05-28 23:09:38 +00:00
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;
}
2019-05-30 14:19:22 +00:00
// println(hlog, dnameof(l), ": ", lv[0].msg, " vs ", lv[1].msg, " verdict = ", v);
2019-05-28 23:09:38 +00:00
}
}
2019-08-09 19:00:52 +00:00
EX void add_choice() {
if(!state) return;
dialog::addSelItem(XLAT("subgame affected"),
2021-05-23 12:33:25 +00:00
affect_both ? XLAT("both") : main_side == 0 ? XLAT("left") : XLAT("right"), '`');
dialog::add_action([] () {
affect_both = !affect_both;
if(!affect_both) {
main_side = !main_side;
switch_to(main_side);
}
});
}
2019-08-09 19:00:52 +00:00
EX void split_or_do(reaction_t what) {
if(split(what)) return;
else what();
}
2019-08-09 19:00:52 +00:00
EX bool may_split(reaction_t what) {
if(state == 1 && affect_both) {
split(what);
return true;
}
return false;
}
2019-08-09 19:00:52 +00:00
EX void may_split_or_do(reaction_t what) {
if(state == 1 && affect_both) {
split(what);
}
else what();
}
2019-05-28 23:09:38 +00:00
}
2019-08-09 19:00:52 +00:00
#if HDR
inline reaction_t mayboth(reaction_t what) { return [=] { may_split_or_do(what); }; }
#endif
2019-05-28 23:09:38 +00:00
}