mirror of
https://github.com/zenorogue/hyperrogue.git
synced 2025-08-29 00:42:19 +00:00
2100 lines
59 KiB
C++
2100 lines
59 KiB
C++
// Hyperbolic Rogue -- main graphics file
|
|
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
|
|
|
|
/** \file graph.cpp
|
|
* \brief Drawing cells, monsters, items, etc.
|
|
*/
|
|
|
|
#include "hyper.h"
|
|
namespace hr {
|
|
|
|
EX int last_firelimit;
|
|
EX int firelimit;
|
|
|
|
EX int inmirrorcount = 0;
|
|
|
|
/** wall optimization: do not draw things beyond walls */
|
|
EX bool wallopt;
|
|
|
|
EX bool in_wallopt() { return wallopt || racing::on; }
|
|
|
|
EX bool spatial_graphics;
|
|
EX bool wmspatial, wmescher, wmplain, wmblack, wmascii, wmascii3;
|
|
EX bool mmspatial, mmhigh, mmmon, mmitem;
|
|
|
|
EX int detaillevel = 0;
|
|
|
|
EX bool first_cell_to_draw = true;
|
|
|
|
EX bool zh_ascii = false;
|
|
|
|
EX bool in_perspective() {
|
|
return models::is_perspective(pconf.model);
|
|
}
|
|
|
|
EX bool in_perspective_v() {
|
|
return models::is_perspective(vpconf.model);
|
|
}
|
|
|
|
EX bool hide_player() {
|
|
return GDIM == 3 && playermoved && vid.yshift == 0 && vid.sspeed > -5 && in_perspective() && (first_cell_to_draw || elliptic) && (WDIM == 3 || vid.camera == 0) && !inmirrorcount
|
|
#if CAP_RACING
|
|
&& !(racing::on && !racing::use_standard_centering() && !racing::player_relative)
|
|
#endif
|
|
;
|
|
}
|
|
|
|
EX transmatrix ddspin180(cell *c, int dir) { return ddspin(c, dir, M_PI); }
|
|
EX transmatrix iddspin180(cell *c, int dir) { return iddspin(c, dir, M_PI); }
|
|
|
|
EX transmatrix lpispin() {
|
|
return spin180();
|
|
}
|
|
|
|
EX hookset<bool(int sym, int uni)> hooks_handleKey;
|
|
EX hookset<bool(cell *c, const shiftmatrix& V)> hooks_drawcell;
|
|
EX purehookset hooks_frame, hooks_markers;
|
|
|
|
#define WOLNIEJ 1
|
|
#define BTOFF 0x404040
|
|
#define BTON 0xC0C000
|
|
|
|
// #define PANDORA
|
|
|
|
int colorbar;
|
|
|
|
EX bool inHighQual; // taking high quality screenshot
|
|
EX bool auraNOGL; // aura without GL
|
|
|
|
//
|
|
int axestate;
|
|
|
|
EX int ticks;
|
|
EX int frameid;
|
|
|
|
EX bool nomap;
|
|
|
|
EX eItem orbToTarget;
|
|
EX eMonster monsterToSummon;
|
|
|
|
EX int sightrange_bonus = 0;
|
|
|
|
EX string mouseovers;
|
|
|
|
EX int darken = 0;
|
|
|
|
EX bool doHighlight() {
|
|
return mmhigh;
|
|
}
|
|
|
|
int dlit;
|
|
|
|
ld spina(cell *c, int dir) {
|
|
return TAU * dir / c->type;
|
|
}
|
|
|
|
/** @brief used to alternate colors depending on distance to something. In chessboard-patterned geometries, automatically a third step.
|
|
* In some cases, we want to avoid a number of colors in the table -- set @param subtract to the number of such colors.
|
|
*/
|
|
EX color_t get_color_auto3(int f, const colortable& ctab, int subtract IS(0)) {
|
|
int size = ctab.size() - subtract;
|
|
if(size < 1) return 0;
|
|
if(geosupport_chessboard() && size == 2) {
|
|
f = gmod(f, 3);
|
|
if(f == 1)
|
|
return gradient(ctab[0], ctab[1], 0, 1, 2);
|
|
return ctab[f/2];
|
|
}
|
|
else
|
|
return ctab[gmod(f, size)];
|
|
}
|
|
|
|
EX ld cheilevel(ld v) {
|
|
return cgi.FLOOR + (cgi.HEAD - cgi.FLOOR) * v;
|
|
}
|
|
|
|
EX transmatrix chei(const transmatrix V, int a, int b) {
|
|
#if MAXMDIM >= 4
|
|
if(GDIM == 2) return V;
|
|
return V * lzpush(cheilevel((a+.5) / b));
|
|
#else
|
|
return V;
|
|
#endif
|
|
}
|
|
|
|
EX shiftmatrix chei(const shiftmatrix V, int a, int b) {
|
|
#if MAXMDIM >= 4
|
|
if(GDIM == 2) return V;
|
|
return V * lzpush(cheilevel((a+.5) / b));
|
|
#else
|
|
return V;
|
|
#endif
|
|
}
|
|
|
|
EX bool ivoryz;
|
|
|
|
/** Change the level of V. Takes ivoryz and all geometries into account */
|
|
EX transmatrix at_smart_lof(const transmatrix& V, ld lev) {
|
|
if(!mmspatial) return V;
|
|
if(ivoryz) return mzscale(V, lev);
|
|
return orthogonal_move_fol(V, lev);
|
|
}
|
|
|
|
EX shiftmatrix at_smart_lof(const shiftmatrix& V, ld lev) { return shiftless(at_smart_lof(V.T, lev), V.shift); }
|
|
|
|
EX color_t kind_outline(eItem it) {
|
|
int k = itemclass(it);
|
|
if(k == IC_TREASURE)
|
|
return OUTLINE_TREASURE;
|
|
else if(k == IC_ORB)
|
|
return OUTLINE_ORB;
|
|
else
|
|
return OUTLINE_OTHER;
|
|
}
|
|
|
|
/** should objects fly slightly up and down in product/twisted product geometries */
|
|
EX bool bobbing = true;
|
|
|
|
EX void draw_ascii(const shiftmatrix& V, const string& s, color_t col, ld size, ld size2) {
|
|
int id = isize(ptds);
|
|
if(WDIM == 2 && GDIM == 3)
|
|
queuestrn(V * lzpush(cgi.FLOOR - cgi.scalefactor * size / 4), size * mapfontscale / 100, s, darkenedby(col, darken), 0);
|
|
else
|
|
queuestrn(V, size2 * mapfontscale / 100, s, darkenedby(col, darken), GDIM == 3 ? 0 : 2);
|
|
while(id < isize(ptds)) ptds[id++]->prio = PPR::MONSTER_BODY;
|
|
}
|
|
|
|
EX void draw_ascii(const shiftmatrix& V, char glyph, color_t col, ld size) {
|
|
draw_ascii(V, s0 + glyph, col, size, 1);
|
|
}
|
|
|
|
EX void draw_ascii_or_zh(const shiftmatrix& V, char glyph, const string& name, color_t col, ld size, ld zh_size) {
|
|
#if CAP_TRANS
|
|
if(zh_ascii) {
|
|
auto p = XLAT1_acc(name, 8);
|
|
if(p) {
|
|
string chinese = p;
|
|
chinese.resize(utfsize(chinese[0]));
|
|
dynamicval<fontdata*> df(cfont, cfont_chinese);
|
|
draw_ascii(V, chinese, col, size, zh_size);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
draw_ascii(V, glyph, col, size);
|
|
}
|
|
|
|
// push down the queue after q-th element, `down` absolute units down,
|
|
// based on cell c and transmatrix V
|
|
// do change the zoom factor? do change the priorities?
|
|
|
|
EX int cellcolor(cell *c) {
|
|
if(isPlayerOn(c) || isFriendly(c)) return OUTLINE_FRIEND;
|
|
if(noHighlight(c->monst)) return OUTLINE_NONE;
|
|
if(c->monst) return OUTLINE_ENEMY;
|
|
|
|
if(c->wall == waMirror) return c->land == laMirror ? OUTLINE_TREASURE : OUTLINE_ORB;
|
|
|
|
if(c->item && !itemHiddenFromSight(c)) {
|
|
int k = itemclass(c->item);
|
|
if(k == IC_TREASURE)
|
|
return OUTLINE_TREASURE;
|
|
else if(k == IC_ORB)
|
|
return OUTLINE_ORB;
|
|
else
|
|
return OUTLINE_OTHER;
|
|
}
|
|
|
|
return OUTLINE_NONE;
|
|
}
|
|
|
|
#define AURA 180
|
|
|
|
array<array<int,4>,AURA+1> aurac;
|
|
|
|
int haveaura_cached;
|
|
|
|
/** 0 = no aura, 1 = standard aura, 2 = Joukowsky aura */
|
|
EX int haveaura() {
|
|
if(!(vid.aurastr>0 && !svg::in && (auraNOGL || vid.usingGL))) return 0;
|
|
if(vrhr::active()) return 0;
|
|
if(sphere && mdAzimuthalEqui()) return 0;
|
|
if(among(pmodel, mdJoukowsky, mdJoukowskyInverted) && hyperbolic && pconf.model_transition < 1)
|
|
return 2;
|
|
if(among(pmodel, mdFisheye, mdFisheye2)) return 1;
|
|
return pmodel == mdDisk && (!sphere || pconf.alpha > 10) && !euclid;
|
|
}
|
|
|
|
vector<pair<int, int> > auraspecials;
|
|
|
|
int auramemo;
|
|
|
|
EX void clearaura() {
|
|
haveaura_cached = haveaura();
|
|
if(!haveaura_cached) return;
|
|
for(int a=0; a<AURA; a++) for(int b=0; b<4; b++)
|
|
aurac[a][b] = 0;
|
|
auraspecials.clear();
|
|
auramemo = 128 * 128 / vid.aurastr;
|
|
}
|
|
|
|
void apply_joukowsky_aura(shiftpoint& h) {
|
|
if(haveaura_cached == 2) {
|
|
hyperpoint ret;
|
|
applymodel(h, ret);
|
|
h.h = ret;
|
|
}
|
|
if(nonisotropic) {
|
|
h.h = lp_apply(inverse_exp(h, pfNO_DISTANCE));
|
|
}
|
|
}
|
|
|
|
EX void addauraspecial(shiftpoint h, color_t col, int dir) {
|
|
if(!haveaura_cached) return;
|
|
apply_joukowsky_aura(h);
|
|
int r = int(2*AURA + dir + atan2(h[1], h[0]) * AURA / TAU) % AURA;
|
|
auraspecials.emplace_back(r, col);
|
|
}
|
|
|
|
EX void addaura(shiftpoint h, color_t col, int fd) {
|
|
if(!haveaura_cached) return;
|
|
apply_joukowsky_aura(h);
|
|
|
|
int r = gmod(atan2(h[1], h[0]) * AURA / TAU, AURA);
|
|
aurac[r][3] += auramemo << fd;
|
|
col = darkened(col);
|
|
aurac[r][0] += (col>>16)&255;
|
|
aurac[r][1] += (col>>8)&255;
|
|
aurac[r][2] += (col>>0)&255;
|
|
}
|
|
|
|
void sumaura(int v) {
|
|
int auc[AURA];
|
|
for(int t=0; t<AURA; t++) auc[t] = aurac[t][v];
|
|
int val = 0;
|
|
if(vid.aurasmoothen < 1) vid.aurasmoothen = 1;
|
|
if(vid.aurasmoothen > AURA) vid.aurasmoothen = AURA;
|
|
int SMO = vid.aurasmoothen;
|
|
for(int t=0; t<SMO; t++) val += auc[t];
|
|
for(int t=0; t<AURA; t++) {
|
|
int tt = (t + SMO/2) % AURA;
|
|
aurac[tt][v] = val;
|
|
val -= auc[t];
|
|
val += auc[(t+SMO) % AURA];
|
|
}
|
|
aurac[AURA][v] = aurac[0][v];
|
|
}
|
|
|
|
#if CAP_GL
|
|
vector<glhr::colored_vertex> auravertices;
|
|
#endif
|
|
|
|
EX void drawaura() {
|
|
DEBBI(DF_GRAPH, ("draw aura"));
|
|
if(!haveaura()) return;
|
|
if(vid.stereo_mode) return;
|
|
double rad = current_display->radius;
|
|
if(sphere && !mdAzimuthalEqui()) rad /= sqrt(pconf.alpha*pconf.alpha - 1);
|
|
if(hyperbolic && pmodel == mdFisheye) {
|
|
ld h = 1;
|
|
h /= pconf.fisheye_param;
|
|
ld nrad = h / sqrt(2 + h*h);
|
|
rad *= nrad;
|
|
}
|
|
|
|
for(int v=0; v<4; v++) sumaura(v);
|
|
for(auto& p: auraspecials) {
|
|
int r = p.first;
|
|
aurac[r][3] = auramemo;
|
|
for(int k=0; k<3; k++) aurac[r][k] = (p.second >> (16-8*k)) & 255;
|
|
}
|
|
|
|
#if CAP_SDL || CAP_GL
|
|
ld bak[3];
|
|
bak[0] = ((backcolor>>16)&255)/255.;
|
|
bak[1] = ((backcolor>>8)&255)/255.;
|
|
bak[2] = ((backcolor>>0)&255)/255.;
|
|
#endif
|
|
|
|
#if CAP_SDL
|
|
if(!vid.usingGL) {
|
|
SDL_LockSurface(s);
|
|
for(int y=0; y<vid.yres; y++)
|
|
for(int x=0; x<vid.xres; x++) {
|
|
|
|
ld hx = (x * 1. - current_display->xcenter) / rad;
|
|
ld hy = (y * 1. - current_display->ycenter) / rad / pconf.stretch;
|
|
|
|
if(!models::camera_straight) camrotate(hx, hy);
|
|
|
|
ld fac = sqrt(hx*hx+hy*hy);
|
|
if(fac < 1) continue;
|
|
ld dd = log((fac - .99999) / .00001);
|
|
ld cmul = 1 - dd/10.;
|
|
if(cmul>1) cmul=1;
|
|
if(cmul<0) cmul=0;
|
|
|
|
ld alpha = AURA * atan2(hx,hy) / TAU;
|
|
if(alpha<0) alpha += AURA;
|
|
if(alpha >= AURA) alpha -= AURA;
|
|
|
|
int rm = int(alpha);
|
|
ld fr = alpha-rm;
|
|
|
|
if(rm<0 || rm >= AURA) continue;
|
|
|
|
color_t& p = qpixel(s, x, y);
|
|
for(int c=0; c<3; c++) {
|
|
ld c1 = aurac[rm][2-c] / (aurac[rm][3]+.1);
|
|
ld c2 = aurac[rm+1][2-c] / (aurac[rm+1][3]+.1);
|
|
const ld one = 1;
|
|
part(p, c) = int(255 * min(one, bak[2-c] + cmul * ((c1 + fr * (c2-c1) - bak[2-c]))));
|
|
}
|
|
}
|
|
SDL_UnlockSurface(s);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if CAP_GL
|
|
float cx[AURA+1][11][5];
|
|
|
|
double facs[11] = {1, 1.01, 1.02, 1.04, 1.08, 1.70, 1.95, 1.5, 2, 6, 10};
|
|
double cmul[11] = {1, .8, .7, .6, .5, .16, .12, .08, .07, .06, 0};
|
|
double d2[11] = {0, 2, 4, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10};
|
|
|
|
for(int d=0; d<11; d++) {
|
|
double dd = d2[d];
|
|
cmul[d] = (1- dd/10.);
|
|
facs[d] = .99999 + .00001 * exp(dd);
|
|
}
|
|
facs[10] = 10;
|
|
cmul[1] = cmul[0];
|
|
|
|
bool inversion = pconf.alpha <= -1 || pmodel == mdJoukowsky;
|
|
bool joukowsky = among(pmodel, mdJoukowskyInverted, mdJoukowsky) && hyperbolic && pconf.model_transition < 1;
|
|
|
|
for(int r=0; r<=AURA; r++) for(int z=0; z<11; z++) {
|
|
float rr = (TAU * r) / AURA;
|
|
float rad0 = inversion ? rad / facs[z] : rad * facs[z];
|
|
int rm = r % AURA;
|
|
ld c = cos(rr);
|
|
ld s = sin(rr);
|
|
|
|
if(joukowsky) {
|
|
hyperpoint v(c, s, 0, 1);
|
|
if(inversion)
|
|
models::ori_to_scr(v);
|
|
else
|
|
models::scr_to_ori(v);
|
|
ld c1 = v[0], s1 = v[1];
|
|
|
|
ld& mt = pconf.model_transition;
|
|
ld mt2 = 1 - mt;
|
|
|
|
ld m = sqrt(c1*c1 + s1*s1 / mt2 / mt2);
|
|
m *= 2;
|
|
if(inversion) rad0 /= m;
|
|
else rad0 *= m;
|
|
}
|
|
|
|
ld x = rad0 * c;
|
|
ld y = rad0 * s;
|
|
|
|
if(!models::camera_straight) {
|
|
hyperpoint p = hyperpoint(x, y, rad0, 1);
|
|
p = rot_inverse(pconf.cam()) * p;
|
|
x = p[0] * rad0 / p[2];
|
|
y = p[1] * rad0 / p[2];
|
|
}
|
|
cx[r][z][0] = x;
|
|
cx[r][z][1] = y * pconf.stretch;
|
|
|
|
for(int u=0; u<3; u++)
|
|
cx[r][z][u+2] = bak[u] + (aurac[rm][u] / (aurac[rm][3]+.1) - bak[u]) * cmul[z];
|
|
}
|
|
|
|
auravertices.clear();
|
|
for(int r=0; r<AURA; r++) for(int z=0;z<10;z++) {
|
|
for(int c=0; c<6; c++) {
|
|
int br = (c == 1 || c == 3 || c == 5) ? r+1 : r;
|
|
int bz = (c == 2 || c == 4 || c == 5) ? z+1 : z;
|
|
auravertices.emplace_back(
|
|
cx[br][bz][0], cx[br][bz][1], cx[br][bz][2], cx[br][bz][3], cx[br][bz][4]
|
|
);
|
|
}
|
|
}
|
|
glflush();
|
|
current_display->next_shader_flags = GF_VARCOLOR;
|
|
dynamicval<eModel> m(pmodel, mdPixel);
|
|
current_display->set_all(0, 0);
|
|
glhr::id_modelview();
|
|
glhr::prepare(auravertices);
|
|
glhr::set_depthtest(false);
|
|
glDrawArrays(GL_TRIANGLES, 0, isize(auravertices));
|
|
#endif
|
|
}
|
|
|
|
// int fnt[100][7];
|
|
|
|
bool bugsNearby(cell *c, int dist = 2) {
|
|
if(!(havewhat&HF_BUG)) return false;
|
|
if(isBug(c)) return true;
|
|
if(dist) for(int t=0; t<c->type; t++) if(c->move(t) && bugsNearby(c->move(t), dist-1)) return true;
|
|
return false;
|
|
}
|
|
|
|
EX int celldistAltPlus(cell *c) { return 1000000 + celldistAlt(c); }
|
|
|
|
EX bool drawstaratvec(double dx, double dy) {
|
|
return dx*dx+dy*dy > .05;
|
|
}
|
|
|
|
ld wavefun(ld x) {
|
|
return sin(x);
|
|
/* x /= TAU;
|
|
x -= (int) x;
|
|
if(x > .5) return (x-.5) * 2;
|
|
else return 0; */
|
|
}
|
|
|
|
// does the current geometry allow nice duals
|
|
EX bool has_nice_dual() {
|
|
#if CAP_IRR
|
|
if(IRREGULAR) return irr::bitruncations_performed > 0;
|
|
#endif
|
|
#if CAP_ARCM
|
|
if(arcm::in()) return geosupport_football() >= 2;
|
|
#endif
|
|
if(bt::in()) return false;
|
|
if(BITRUNCATED) return true;
|
|
if(a4) return false;
|
|
if((S7 & 1) == 0) return true;
|
|
if(PURE) return false;
|
|
#if CAP_GP
|
|
return (gp::param.first + gp::param.second * 2) % 3 == 0;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// does the current geometry allow nice duals
|
|
EX bool is_nice_dual(cell *c) {
|
|
return c->land == laDual && has_nice_dual();
|
|
}
|
|
|
|
EX bool use_swapped_duals() {
|
|
return (euclid && !a4) || GOLDBERG;
|
|
}
|
|
|
|
EX bool openorsafe(cell *c) {
|
|
#if CAP_COMPLEX2
|
|
return c->wall == waMineOpen || mine::marked_safe(c);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#define Dark(x) darkena(x,0,0xFF)
|
|
|
|
EX color_t stdgridcolor = 0x202020FF;
|
|
|
|
EX int gridcolor(cell *c1, cell *c2) {
|
|
if(cmode & sm::DRAW && !mapeditor::drawing_tool) return Dark(forecolor);
|
|
if(!c2)
|
|
return 0x202020 >> darken;
|
|
int rd1 = rosedist(c1), rd2 = rosedist(c2);
|
|
if(rd1 != rd2) {
|
|
int r = rd1+rd2;
|
|
if(r == 1) return Dark(0x802020);
|
|
if(r == 3) return Dark(0xC02020);
|
|
if(r == 2) return Dark(0xF02020);
|
|
}
|
|
if((get_spatial_info(c1).deep<SIDE::SHALLOW) != (get_spatial_info(c2).deep<SIDE::SHALLOW) && c1->land != laAsteroids && c2->land != laAsteroids)
|
|
return Dark(0x808080);
|
|
if(c1->land == laAlchemist && c2->land == laAlchemist && c1->wall != c2->wall && !c1->item && !c2->item)
|
|
return Dark(0xC020C0);
|
|
if((c1->land == laWhirlpool || c2->land == laWhirlpool) && (celldistAlt(c1) != celldistAlt(c2)))
|
|
return Dark(0x2020A0);
|
|
if(c1->land == laMinefield && c2->land == laMinefield && (openorsafe(c1) != openorsafe(c2)))
|
|
return Dark(0xA0A0A0);
|
|
if(!darken) return stdgridcolor;
|
|
return Dark(0x202020);
|
|
}
|
|
|
|
#if CAP_SHAPES
|
|
EX void pushdown(cell *c, int& q, const shiftmatrix &V, double down, bool rezoom, bool repriority) {
|
|
|
|
#if MAXMDIM >= 4
|
|
if(GDIM == 3) {
|
|
for(int i=q; i<isize(ptds); i++) {
|
|
auto pp = ptds[q++]->as_poly();
|
|
if(!pp) continue;
|
|
auto& ptd = *pp;
|
|
ptd.V = ptd.V * lzpush(+down);
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// since we might be changing priorities, we have to make sure that we are sorting correctly
|
|
if(down > 0 && repriority) {
|
|
int qq = q+1;
|
|
while(qq < isize(ptds))
|
|
if(qq > q && ptds[qq]->prio < ptds[qq-1]->prio) {
|
|
swap(ptds[qq], ptds[qq-1]);
|
|
qq--;
|
|
}
|
|
else qq++;
|
|
}
|
|
|
|
while(q < isize(ptds)) {
|
|
auto pp = ptds[q++]->as_poly();
|
|
if(!pp) continue;
|
|
auto& ptd = *pp;
|
|
|
|
double z2;
|
|
|
|
double z = zlevel(tC0(ptd.V.T));
|
|
double lev = geom3::factor_to_lev(z);
|
|
double nlev = lev - down;
|
|
|
|
double xyscale = rezoom ? geom3::scale_at_lev(lev) / geom3::scale_at_lev(nlev) : 1;
|
|
z2 = geom3::lev_to_factor(nlev);
|
|
double zscale = z2 / z;
|
|
|
|
// xyscale = xyscale + (zscale-xyscale) * (1+sin(ticks / 1000.0)) / 2;
|
|
|
|
ptd.V.T = xyzscale( V.T, xyscale*zscale, zscale)
|
|
* z_inverse(V.T) * unshift(ptd.V, V.shift);
|
|
|
|
if(!repriority) ;
|
|
else if(nlev < -vid.lake_bottom-1e-3) {
|
|
ptd.prio = PPR::DEEP_FALLANIM;
|
|
if(c->wall != waChasm)
|
|
ptd.color = 0; // disappear!
|
|
}
|
|
else if(nlev < -vid.lake_top-1e-3)
|
|
ptd.prio = PPR::SHALLOW_FALLANIM;
|
|
else if(nlev < 0)
|
|
ptd.prio = PPR::FLOOR_FALLANIM;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool allemptynear(cell *c) {
|
|
if(c->wall) return false;
|
|
forCellEx(c2, c) if(c2->wall) return false;
|
|
return true;
|
|
}
|
|
|
|
EX bool bright;
|
|
EX int canvasdark;
|
|
|
|
// how much to darken
|
|
EX int getfd(cell *c) {
|
|
if(bright) return 0;
|
|
if(among(c->land, laAlchemist, laHell, laVariant, laEclectic) && WDIM == 2 && GDIM == 3) return 0;
|
|
switch(c->land) {
|
|
case laCanvas:
|
|
return min(2,max(0,canvasdark));
|
|
|
|
case laRedRock:
|
|
case laReptile:
|
|
return 0;
|
|
|
|
case laSnakeNest:
|
|
return realred(c->wall) ? 0 : 1;
|
|
|
|
case laTerracotta:
|
|
case laMercuryRiver:
|
|
return (c->wall == waMercury && wmspatial) ? 0 : 1;
|
|
|
|
case laKraken:
|
|
case laDocks:
|
|
case laBurial:
|
|
case laIvoryTower:
|
|
case laDungeon:
|
|
case laMountain:
|
|
case laEndorian:
|
|
case laCaribbean:
|
|
case laWhirlwind:
|
|
case laRose:
|
|
case laWarpSea:
|
|
case laTortoise:
|
|
case laDragon:
|
|
case laHalloween:
|
|
case laHunting:
|
|
case laOcean:
|
|
case laLivefjord:
|
|
case laWhirlpool:
|
|
case laAlchemist:
|
|
case laIce:
|
|
case laGraveyard:
|
|
case laBlizzard:
|
|
case laRlyeh:
|
|
case laTemple:
|
|
case laWineyard:
|
|
case laDeadCaves:
|
|
case laPalace:
|
|
case laCA:
|
|
case laDual:
|
|
case laBrownian:
|
|
return 1;
|
|
|
|
case laVariant:
|
|
if(isWateryOrBoat(c)) return 1;
|
|
return 2;
|
|
|
|
case laTrollheim:
|
|
default:
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
EX bool just_gmatrix;
|
|
|
|
EX int colorhash(color_t i) {
|
|
return (i * 0x471211 + i*i*0x124159 + i*i*i*0x982165) & 0xFFFFFF;
|
|
}
|
|
|
|
EX ld mousedist(shiftmatrix T) {
|
|
if(GDIM == 2) return hdist(mouseh, tC0(T));
|
|
shiftpoint T1 = orthogonal_move_fol(T, cgi.FLOOR) * tile_center();
|
|
hyperpoint h1;
|
|
applymodel(T1, h1);
|
|
if(mouseaim_sensitivity) return sqhypot_d(2, h1) + (point_behind(T1) ? 1e10 : 0);
|
|
h1 = h1 - point2((mousex - current_display->xcenter) / current_display->radius, (mousey - current_display->ycenter) / current_display->radius);
|
|
return sqhypot_d(2, h1) + (point_behind(T1) ? 1e10 : 0);
|
|
}
|
|
|
|
EX vector<vector<hyperpoint>> clipping_plane_sets;
|
|
EX int noclipped;
|
|
|
|
EX bool frustum_culling = true;
|
|
|
|
EX ld threshold, xyz_threshold;
|
|
|
|
EX bool clip_checked = false;
|
|
|
|
EX bool other_stereo_mode() {
|
|
return vid.stereo_mode != sOFF;
|
|
}
|
|
|
|
EX void make_clipping_planes() {
|
|
#if MAXMDIM >= 4
|
|
clip_checked = false;
|
|
if(!frustum_culling || PIU(sphere) || experimental || other_stereo_mode() || gproduct || embedded_plane) return;
|
|
|
|
if(WDIM == 3 && pmodel == mdPerspective && !nonisotropic && !in_s2xe())
|
|
threshold = sin_auto(cgi.corner_bonus), xyz_threshold = 0, clip_checked = true;
|
|
else if(pmodel == mdGeodesic && sn::in())
|
|
threshold = .6, xyz_threshold = 3, clip_checked = true;
|
|
else if(pmodel == mdGeodesic && nil)
|
|
threshold = 2, xyz_threshold = 3, clip_checked = true;
|
|
else return;
|
|
|
|
clipping_plane_sets.clear();
|
|
|
|
auto add_clipping_plane_txy = [] (transmatrix T, const transmatrix& nlp, ld x1, ld y1, ld x2, ld y2) {
|
|
ld z1 = 1, z2 = 1;
|
|
hyperpoint sx = point3(y1 * z2 - y2 * z1, z1 * x2 - z2 * x1, x1 * y2 - x2 * y1);
|
|
sx /= hypot_d(3, sx);
|
|
sx[3] = 0;
|
|
sx = T * sx;
|
|
if(nisot::local_perspective_used) sx = ortho_inverse(nlp) * sx;
|
|
clipping_plane_sets.back().push_back(sx);
|
|
};
|
|
|
|
#if CAP_VR
|
|
auto add_clipping_plane_proj = [&] (transmatrix T, const transmatrix& nlp, const transmatrix& iproj, ld x1, ld y1, ld x2, ld y2) {
|
|
hyperpoint h1 = iproj * point31(x1, y1, .5);
|
|
hyperpoint h2 = iproj * point31(x2, y2, .5);
|
|
h1 /= h1[2]; h2 /= h2[2];
|
|
add_clipping_plane_txy(T, nlp, h1[0], h1[1], h2[0], h2[1]);
|
|
};
|
|
#endif
|
|
|
|
auto clipping_planes_screen = [&] (const transmatrix& T, const transmatrix& nlp) {
|
|
ld tx = current_display->tanfov;
|
|
ld ty = tx * current_display->ysize / current_display->xsize;
|
|
clipping_plane_sets.push_back({});
|
|
add_clipping_plane_txy(T, nlp, +tx, +ty, -tx, +ty);
|
|
add_clipping_plane_txy(T, nlp, -tx, +ty, -tx, -ty);
|
|
add_clipping_plane_txy(T, nlp, -tx, -ty, +tx, -ty);
|
|
add_clipping_plane_txy(T, nlp, +tx, -ty, +tx, +ty);
|
|
};
|
|
|
|
bool stdview = true;
|
|
|
|
#if CAP_VR
|
|
if(vrhr::active()) {
|
|
for(auto p: vrhr::frusta) {
|
|
if(p.screen)
|
|
clipping_planes_screen(inverse(p.pre), p.nlp);
|
|
else {
|
|
auto iproj = inverse(p.proj);
|
|
auto ipre = inverse(p.pre);
|
|
clipping_plane_sets.push_back({});
|
|
add_clipping_plane_proj(ipre, p.nlp, iproj, 1, 1, 0, 1);
|
|
add_clipping_plane_proj(ipre, p.nlp, iproj, 0, 1, 0, 0);
|
|
add_clipping_plane_proj(ipre, p.nlp, iproj, 0, 0, 1, 0);
|
|
add_clipping_plane_proj(ipre, p.nlp, iproj, 1, 0, 1, 1);
|
|
}
|
|
stdview = false;
|
|
}
|
|
}
|
|
#endif
|
|
if(stdview) clipping_planes_screen(Id, NLP);
|
|
#endif
|
|
}
|
|
|
|
EX bool clipped_by(const hyperpoint& H, const vector<hyperpoint>& v) {
|
|
for(auto& cpoint: v) if((H|cpoint) < -threshold) return true;
|
|
return false;
|
|
}
|
|
|
|
EX bool clipped_by(const hyperpoint& H, const vector<vector<hyperpoint>>& vv) {
|
|
for(auto& cps: vv) if(!clipped_by(H, cps)) return false;
|
|
return true;
|
|
}
|
|
|
|
bool celldrawer::cell_clipped() {
|
|
|
|
if(!clip_checked) return false;
|
|
|
|
hyperpoint H = unshift(tC0(V));
|
|
|
|
if(xyz_threshold && abs(H[0]) <= xyz_threshold && abs(H[1]) <= xyz_threshold && abs(H[2]) <= xyz_threshold) {
|
|
noclipped++;
|
|
return false;
|
|
}
|
|
|
|
if(clipped_by(H, clipping_plane_sets)) {
|
|
drawcell_in_radar();
|
|
return true;
|
|
}
|
|
|
|
noclipped++;
|
|
return false;
|
|
}
|
|
|
|
EX ld precise_width = .5;
|
|
|
|
int grid_depth = 0;
|
|
|
|
EX bool fat_edges = false;
|
|
|
|
EX bool gridbelow;
|
|
|
|
EX void gridline(const shiftmatrix& V1, const hyperpoint h1, const shiftmatrix& V2, const hyperpoint h2, color_t col, int prec) {
|
|
transmatrix U2 = unshift(V2, V1.shift);
|
|
|
|
int c1 = safe_classify_ideals(h1);
|
|
int c2 = safe_classify_ideals(h2);
|
|
ld d = (c1 <= 0 || c2 <= 0) ? 99 : hdist(V1.T*h1, U2*h2);
|
|
|
|
#if MAXMDIM >= 4
|
|
if(GDIM == 3 && fat_edges) {
|
|
if(nonisotropic) {
|
|
auto nV1 = V1 * rgpushxto0(h1);
|
|
hyperpoint U2 = inverse_shift(nV1, V2*rgpushxto0(h2)) * C0;
|
|
auto& p = cgi.get_pipe_noniso(U2, vid.linewidth, ePipeEnd::ball);
|
|
queuepoly(nV1, p, col);
|
|
return;
|
|
}
|
|
|
|
shiftmatrix T = V1 * rgpushxto0(h1);
|
|
transmatrix S = rspintox(inverse_shift(T, V2) * h2);
|
|
transmatrix U = rspintoc(inverse_shift(T*S, shiftless(C0)), 2, 1);
|
|
auto& p = queuepoly(T * S * U, cgi.get_pipe_iso(d, vid.linewidth, ePipeEnd::ball), col);
|
|
p.intester = xpush0(d/2);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
while(d > precise_width && d < 100 && grid_depth < 10) {
|
|
if(V1.shift != V2.shift || !eqmatrix(V1.T, V2.T, 1e-6)) { gridline(V1, h1, V1, inverse_shift(V1, V2) * h2, col, prec); return; }
|
|
hyperpoint h;
|
|
if(c1 <= 0 && c2 <= 0) {
|
|
h = closest_to_zero(h1, h2);
|
|
if(safe_classify_ideals(h) <= 0) return;
|
|
h = normalize(h);
|
|
}
|
|
else if(c2 <= 0) {
|
|
dynamicval<int> dw(grid_depth, 99);
|
|
for(ld a=0; a<ideal_limit; a+=precise_width)
|
|
gridline(V1, towards_inf(h1, h2, a), V1, towards_inf(h1, h2, a+precise_width), col, prec);
|
|
return;
|
|
}
|
|
else if(c1 <= 0) {
|
|
dynamicval<int> dw(grid_depth, 99);
|
|
for(ld a=0; a<ideal_limit; a+=precise_width)
|
|
gridline(V1, towards_inf(h2, h1, a), V1, towards_inf(h2, h1, a+precise_width), col, prec);
|
|
return;
|
|
}
|
|
else h = midz(h1, h2);
|
|
grid_depth++;
|
|
gridline(V1, h1, V1, h, col, prec);
|
|
gridline(V1, h, V1, h2, col, prec);
|
|
grid_depth--;
|
|
return;
|
|
}
|
|
#if MAXMDIM >= 4
|
|
if(WDIM == 2 && GDIM == 3) {
|
|
ld eps = cgi.human_height/100;
|
|
queueline(V1*orthogonal_move(h1,cgi.FLOOR+eps), V2*orthogonal_move(h2,cgi.FLOOR+eps), col, prec);
|
|
queueline(V1*orthogonal_move(h1,cgi.WALL-eps), V2*orthogonal_move(h2,cgi.WALL-eps), col, prec);
|
|
}
|
|
else
|
|
#endif
|
|
queueline(V1*h1, V2*h2, col, prec, gridbelow ? PPR::FLOORd : PPR::LINE);
|
|
}
|
|
|
|
EX void gridline(const shiftmatrix& V, const hyperpoint h1, const hyperpoint h2, color_t col, int prec) {
|
|
gridline(V, h1, V, h2, col, prec);
|
|
}
|
|
|
|
EX void set_detail_level(const shiftmatrix& V) {
|
|
ld dist0 = hdist0(tC0(V)) - 1e-6;
|
|
if(vid.use_smart_range) detaillevel = 2;
|
|
else if(dist0 < vid.highdetail) detaillevel = 2;
|
|
else if(dist0 < vid.middetail) detaillevel = 1;
|
|
else detaillevel = 0;
|
|
|
|
if((cmode & sm::NUMBER) && (dialog::editingDetail())) {
|
|
color_t col =
|
|
dist0 < vid.highdetail ? 0xFF80FF80 :
|
|
dist0 >= vid.middetail ? 0xFFFF8080 :
|
|
0XFFFFFF80;
|
|
queuepoly(V, cgi.shHeptaMarker, darkena(col & 0xFFFFFF, 0, 0xFF));
|
|
}
|
|
}
|
|
|
|
#if CAP_QUEUE
|
|
EX void queuecircleat1(cell *c, const shiftmatrix& V, double rad, color_t col) {
|
|
if(WDIM == 3) {
|
|
dynamicval<color_t> p(poly_outline, col);
|
|
int ofs = currentmap->wall_offset(c);
|
|
for(int i=0; i<c->type; i++) {
|
|
queuepolyat(V, cgi.shWireframe3D[ofs + i], 0, PPR::SUPERLINE);
|
|
}
|
|
return;
|
|
}
|
|
if(spatial_graphics || GDIM == 3) {
|
|
vector<hyperpoint> corners(c->type+1);
|
|
for(int i=0; i<c->type; i++) corners[i] = get_corner_position(c, i, 3 / rad);
|
|
corners[c->type] = corners[0];
|
|
for(int i=0; i<c->type; i++) {
|
|
queueline(V * orthogonal_move_fol(corners[i], cgi.FLOOR), V * orthogonal_move_fol(corners[i+1], cgi.FLOOR), col, 2, PPR::SUPERLINE);
|
|
queueline(V * orthogonal_move_fol(corners[i], cgi.WALL), V * orthogonal_move_fol(corners[i+1], cgi.WALL), col, 2, PPR::SUPERLINE);
|
|
queueline(V * orthogonal_move_fol(corners[i], cgi.FLOOR), V * orthogonal_move_fol(corners[i], cgi.WALL), col, 2, PPR::SUPERLINE);
|
|
}
|
|
return;
|
|
}
|
|
#if CAP_SHAPES
|
|
if(vid.stereo_mode || sphere) {
|
|
dynamicval<color_t> p(poly_outline, col);
|
|
queuepolyat(V * spintick(100), cgi.shGem[1], 0, PPR::LINE);
|
|
return;
|
|
}
|
|
#endif
|
|
queuecircle(V, rad, col);
|
|
if(!wmspatial) return;
|
|
auto si = get_spatial_info(c);
|
|
if(si.top == SIDE::WALL)
|
|
queuecircle(orthogonal_move_fol(V, cgi.WALL), rad, col);
|
|
if(si.top == SIDE::RED1)
|
|
queuecircle(orthogonal_move_fol(V, cgi.RED[1]), rad, col);
|
|
if(si.top == SIDE::RED2)
|
|
queuecircle(orthogonal_move_fol(V, cgi.RED[2]), rad, col);
|
|
if(si.top == SIDE::RED3)
|
|
queuecircle(orthogonal_move_fol(V, cgi.RED[3]), rad, col);
|
|
if(si.top <= SIDE::WATERLEVEL)
|
|
queuecircle(orthogonal_move_fol(V, cgi.WATERLEVEL), rad, col);
|
|
}
|
|
|
|
EX void queuecircleat(cell *c, double rad, color_t col) {
|
|
if(!c) return;
|
|
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, c))
|
|
queuecircleat1(c, V, rad, col);
|
|
}
|
|
#endif
|
|
|
|
#if ISMOBILE
|
|
#define MOBON (clicked)
|
|
#else
|
|
#define MOBON true
|
|
#endif
|
|
|
|
EX cell *forwardcell() {
|
|
#if CAP_VR
|
|
if(vrhr::active()) {
|
|
return vrhr::forward_cell;
|
|
}
|
|
#endif
|
|
movedir md = vectodir(move_destination_vec(6));
|
|
cellwalker xc = cwt + md.d + wstep;
|
|
return xc.at;
|
|
}
|
|
|
|
EX bool draw_centerover = true;
|
|
|
|
EX bool should_draw_mouse_cursor() {
|
|
if(!mousing || inHighQual) return false;
|
|
if(outofmap(mouseh.h)) return false;
|
|
if(rug::rugged && !rug::renderonce) return true;
|
|
return false;
|
|
}
|
|
|
|
EX void drawMarkers() {
|
|
|
|
shmup::draw_collision_debug();
|
|
|
|
if(!(cmode & sm::NORMAL)) return;
|
|
|
|
if(should_draw_mouse_cursor()) {
|
|
for(int i: player_indices()) {
|
|
queueline(ggmatrix(playerpos(i)) * (WDIM == 2 && GDIM == 3 ? zpush0(cgi.WALL) : C0), mouseh, 0xFF00FF, grid_prec() + 1);
|
|
}
|
|
}
|
|
|
|
callhooks(hooks_markers);
|
|
#if CAP_SHAPES
|
|
viewmat();
|
|
#endif
|
|
|
|
#if CAP_QUEUE
|
|
for(cell *c1: crush_now)
|
|
queuecircleat(c1, .8, darkena(minf[moCrusher].color, 0, 0xFF));
|
|
#endif
|
|
|
|
if(!inHighQual) {
|
|
|
|
bool ok = !ISPANDORA || mousepressed;
|
|
|
|
ignore(ok);
|
|
|
|
#if CAP_QUEUE
|
|
if(haveMount())
|
|
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, dragon::target)) {
|
|
queuestr(V, mapfontscale/100, "X",
|
|
gradient(0, iinf[itOrbDomination].color, -1, sintick(dragon::whichturn == turncount ? 75 : 150), 1));
|
|
}
|
|
#endif
|
|
|
|
/* for(int i=0; i<12; i++) if(c->type == 5 && c->master == &dodecahedron[i])
|
|
queuestr(xc, yc, sc, 4*vid.fsize, s0+('A'+i), iinf[itOrbDomination].color); */
|
|
|
|
if(1) {
|
|
using namespace yendor;
|
|
if(yii < isize(yi) && !yi[yii].found) {
|
|
cell *keycell = NULL;
|
|
int last_i = 0;
|
|
for(int i=0; i<YDIST; i++)
|
|
if(yi[yii].path[i]->cpdist <= get_sightrange_ambush()) {
|
|
keycell = yi[yii].path[i]; last_i = i;
|
|
}
|
|
if(keycell) {
|
|
for(int i = last_i+1; i<YDIST; i++) {
|
|
cell *c = yi[yii].path[i];
|
|
if(inscreenrange(c))
|
|
keycell = c;
|
|
}
|
|
shiftpoint H = tC0(ggmatrix(keycell));
|
|
#if CAP_QUEUE
|
|
queue_goal_text(H, 2, "X", 0x10101 * int(128 + 100 * sintick(150)));
|
|
int cd = celldistance(yi[yii].key(), cwt.at);
|
|
if(cd == DISTANCE_UNKNOWN) for(int i2 = 0; i2<YDIST; i2++) {
|
|
int cd2 = celldistance(cwt.at, yi[yii].path[i2]);
|
|
if(cd2 != DISTANCE_UNKNOWN) {
|
|
cd = cd2 + (YDIST-1-i2);
|
|
}
|
|
}
|
|
queue_goal_text(H, 1, its(cd), 0x10101 * int(128 - 100 * sintick(150)));
|
|
#endif
|
|
addauraspecial(H, iinf[itOrbYendor].color, 0);
|
|
addradar(ggmatrix(keycell), 'X', iinf[itKey].color, kind_outline(itKey), true);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if CAP_RACING
|
|
racing::markers();
|
|
#endif
|
|
|
|
#if CAP_QUEUE
|
|
if(lmouseover && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) {
|
|
cell *at = lmouseover;
|
|
#if CAP_VR
|
|
if(vrhr::active() && vrhr::forward_cell)
|
|
at = vrhr::forward_cell;
|
|
#endif
|
|
queuecircleat(at, .8, darkena(lmouseover->cpdist > 1 ? 0x00FFFF : 0xFF0000, 0, 0xFF));
|
|
}
|
|
|
|
if(global_pushto && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) {
|
|
queuecircleat(global_pushto, .6, darkena(0xFFD500, 0, 0xFF));
|
|
}
|
|
#endif
|
|
|
|
#if CAP_SDLJOY && CAP_QUEUE
|
|
if(joydir.d >= 0 && WDIM == 2)
|
|
queuecircleat(cwt.at->modmove(joydir.d+cwt.spin), .78 - .02 * sintick(199),
|
|
darkena(0x00FF00, 0, 0xFF));
|
|
#endif
|
|
|
|
bool m = true;
|
|
ignore(m);
|
|
#if CAP_MODEL
|
|
m = netgen::mode == 0;
|
|
#endif
|
|
|
|
#if CAP_QUEUE
|
|
if(centerover && !playermoved && m && !anims::any_animation() && WDIM == 2 && draw_centerover)
|
|
queuecircleat(centerover, .70 - .06 * sintick(200),
|
|
darkena(int(175 + 25 * sintick(200)), 0, 0xFF));
|
|
|
|
if(multi::players > 1 || multi::alwaysuse) for(int i=0; i<numplayers(); i++) {
|
|
multi::cpid = i;
|
|
if(multi::players == 1) multi::player[i] = cwt;
|
|
cell *ctgt = multi::multiPlayerTarget(i);
|
|
queuecircleat(ctgt, .40 - .06 * sintick(200, i / numplayers()), getcs().uicolor);
|
|
}
|
|
#endif
|
|
|
|
// process mouse
|
|
#if CAP_SHAPES
|
|
if((vid.axes >= 4 || (vid.axes == 1 && !mousing)) && !shmup::on && GDIM == 2) {
|
|
if(multi::players == 1) {
|
|
forCellIdAll(c2, d, cwt.at) if(gmatrix.count(cwt.at)) draw_movement_arrows(c2, unshift(gmatrix[cwt.at]) * currentmap->adj(cwt.at, d), d);
|
|
}
|
|
else if(multi::players > 1) for(int p=0; p<multi::players; p++) {
|
|
if(multi::playerActive(p) && (vid.axes >= 4 || !drawstaratvec(multi::mdx[p], multi::mdy[p])))
|
|
forCellIdAll(c2, d, multi::player[p].at) if(gmatrix.count(cwt.at)) {
|
|
multi::cpid = p;
|
|
dynamicval<shiftmatrix> ttm(cwtV, multi::whereis[p]);
|
|
dynamicval<cellwalker> tcw(cwt, multi::player[p]);
|
|
draw_movement_arrows(c2, unshift(gmatrix[cwt.at]) * currentmap->adj(cwt.at, d), d);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(GDIM == 3 && !inHighQual && !shmup::on && vid.axes3 && playermoved) {
|
|
cell *c = forwardcell();
|
|
if(c) queuecircleat(c, .8, getcs().uicolor);
|
|
}
|
|
|
|
#endif
|
|
|
|
if(mhybrid && !shmup::on) {
|
|
|
|
using namespace sword;
|
|
int& ang = sword::dir[multi::cpid].angle;
|
|
ang %= sword_angles;
|
|
|
|
int adj = 1 - ((sword_angles/cwt.at->type)&1);
|
|
|
|
if(items[itOrbSword])
|
|
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
|
|
queuestr(V * spin(M_PI+(-adj-2*ang)*M_PI/sword_angles) * xpush0(cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword].color);
|
|
if(items[itOrbSword2])
|
|
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
|
|
queuestr(V * spin((-adj-2*ang)*M_PI/sword_angles) * xpush0(-cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword2].color);
|
|
}
|
|
if(SWORDDIM == 3 && !shmup::on) {
|
|
if(items[itOrbSword])
|
|
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
|
|
queuestr(V * sword::dir[multi::cpid].T * xpush0(cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword].color);
|
|
if(items[itOrbSword2])
|
|
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
|
|
queuestr(V * sword::dir[multi::cpid].T * xpush0(-cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword2].color);
|
|
}
|
|
}
|
|
|
|
monsterToSummon = moNone;
|
|
orbToTarget = itNone;
|
|
|
|
if(mouseover && targetclick) {
|
|
multi::cpid = 0;
|
|
orbToTarget = targetRangedOrb(mouseover, roCheck);
|
|
#if CAP_QUEUE
|
|
if(bow::fire_mode) {
|
|
queuestr(mousex, mousey, 0, vid.fsize, "+", getcs().bowcolor >> 8);
|
|
orbToTarget = itNone;
|
|
}
|
|
else if(orbToTarget == itOrbSummon) {
|
|
monsterToSummon = summonedAt(mouseover);
|
|
queuestr(mousex, mousey, 0, vid.fsize, s0+minf[monsterToSummon].glyph, minf[monsterToSummon].color);
|
|
queuecircleat(mouseover, 0.6, darkena(minf[monsterToSummon].color, 0, 0xFF));
|
|
}
|
|
else if(orbToTarget) {
|
|
queuestr(mousex, mousey, 0, vid.fsize, "@", iinf[orbToTarget].color);
|
|
queuecircleat(mouseover, 0.6, darkena(iinf[orbToTarget].color, 0, 0xFF));
|
|
}
|
|
#endif
|
|
#if CAP_SHAPES
|
|
if(orbToTarget && rand() % 200 < ticks - lastt) {
|
|
if(orbToTarget == itOrbDragon)
|
|
drawFireParticles(mouseover, 2);
|
|
else if(orbToTarget == itOrbSummon) {
|
|
drawParticles(mouseover, iinf[orbToTarget].color, 1);
|
|
drawParticles(mouseover, minf[monsterToSummon].color, 1);
|
|
}
|
|
else {
|
|
drawParticles(mouseover, iinf[orbToTarget].color, 2);
|
|
}
|
|
}
|
|
if(items[itOrbAir] && mouseover->cpdist > 1) {
|
|
cell *c1 = mouseover;
|
|
int dir = c1->monst == moVoidBeast ? -1 : 1;
|
|
for(int it=0; it<10; it++) {
|
|
int di;
|
|
auto mib = blowoff_destination_dir(c1, di, dir);
|
|
if(!mib.proper()) break;
|
|
auto& c2 = mib.t;
|
|
shiftmatrix T1 = ggmatrix(c1);
|
|
shiftmatrix T2 = ggmatrix(c2);
|
|
shiftmatrix T = T1 * lrspintox(inverse_shift(T1,T2*C0)) * xpush(hdist(T1*C0, T2*C0) * fractick(50, 0));
|
|
color_t aircol = (orbToTarget == itOrbAir ? 0x8080FF40 : 0x8080FF20);
|
|
queuepoly(T, cgi.shDisk, aircol);
|
|
c1 = c2;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
EX bool allowIncreasedSight() {
|
|
if(cheater || autocheat) return true;
|
|
if(peace::on) return true;
|
|
#if CAP_TOUR
|
|
if(tour::on) return true;
|
|
#endif
|
|
if(randomPatternsMode) return true;
|
|
if(racing::on) return true;
|
|
if(quotient || !hyperbolic || arcm::in() || arb::in()) return true;
|
|
if(WDIM == 3) return true;
|
|
if(!canmove) return true;
|
|
return false;
|
|
}
|
|
|
|
EX bool allowChangeRange() {
|
|
if(cheater || peace::on || randomPatternsMode) return true;
|
|
#if CAP_TOUR
|
|
if(tour::on) return true;
|
|
#endif
|
|
if(racing::on) return true;
|
|
if(arcm::in() || arb::in()) return true;
|
|
if(WDIM == 3) return true;
|
|
return false;
|
|
}
|
|
|
|
EX purehookset hooks_drawmap;
|
|
|
|
EX transmatrix actual_view_transform;
|
|
|
|
EX ld wall_radar(cell *c, transmatrix T, transmatrix LPe, ld max) {
|
|
if(!in_perspective() || !vid.use_wall_radar) return max;
|
|
transmatrix ori;
|
|
if(gproduct) ori = ortho_inverse(LPe);
|
|
ld step = max / 20;
|
|
ld fixed_yshift = 0;
|
|
for(int i=0; i<20; i++) {
|
|
T = shift_object(T, ori, ztangent(-step), shift_method(smaWallRadar));
|
|
virtualRebase(c, T);
|
|
color_t col;
|
|
if(isWall3(c, col) || (WDIM == 2 && GDIM == 3 && tC0(T)[2] > cgi.FLOOR)) {
|
|
T = shift_object(T, ori, ztangent(step), shift_method(smaWallRadar));
|
|
step /= 2; i = 17;
|
|
if(step < 1e-3) break;
|
|
}
|
|
else fixed_yshift += step;
|
|
}
|
|
return fixed_yshift;
|
|
}
|
|
|
|
/** if this is set to ON, just transform non-isotropic spaces according to View, and apply NLP to view */
|
|
EX bool nonisotropic_weird_transforms;
|
|
|
|
EX void decide_lpu() {
|
|
nisot::local_perspective_used = gproduct;
|
|
}
|
|
|
|
EX void make_actual_view() {
|
|
decide_lpu();
|
|
if(!nisot::local_perspective_used) NLP = Id;
|
|
sphereflip = Id;
|
|
sphere_flipped = flip_sphere();
|
|
if(sphere_flipped) sphereflip[LDIM][LDIM] = -1;
|
|
actual_view_transform = sphereflip;
|
|
if(vid.yshift && WDIM == 2) actual_view_transform = ypush(vid.yshift) * actual_view_transform;
|
|
#if MAXMDIM >= 4
|
|
if(GDIM == 3) {
|
|
ld max = WDIM == 2 ? vid.camera : vid.yshift;
|
|
if(max) {
|
|
transmatrix Start = view_inverse(actual_view_transform * View);
|
|
ld d = wall_radar(centerover, Start, NLP, max);
|
|
actual_view_transform = get_shift_view_of(ztangent(d), actual_view_transform * View) * view_inverse(View);
|
|
}
|
|
hyperpoint h = tC0(view_inverse(actual_view_transform * View));
|
|
|
|
camera_level = cgi.emb->get_logical_z(h);
|
|
|
|
camera_sign = cgi.FLOOR > cgi.WALL;
|
|
}
|
|
if((nonisotropic || (hyperbolic && bt::in() && !nisot::geodesic_movement)) && !nonisotropic_weird_transforms) {
|
|
transmatrix T = actual_view_transform * View;
|
|
transmatrix T2 = eupush( tC0(view_inverse(T)) );
|
|
NLP = T * T2;
|
|
actual_view_transform = ortho_inverse(NLP) * actual_view_transform;
|
|
nisot::local_perspective_used = true;
|
|
}
|
|
#endif
|
|
cgi.emb->set_radar_transform();
|
|
Viewbase = View;
|
|
}
|
|
|
|
EX shiftmatrix cview(ld base_shift IS(0)) {
|
|
return shiftless(actual_view_transform * View, base_shift);
|
|
}
|
|
|
|
EX int point_direction;
|
|
|
|
EX int through_wall(cell *c, hyperpoint at) {
|
|
ld dist = hdist0(at);
|
|
int nei = -1;
|
|
for(int i=0; i<c->type; i++) {
|
|
ld dist1 = hdist0(currentmap->ray_iadj(c, i) * at);
|
|
if(dist1 < dist) nei = i, dist = dist1;
|
|
}
|
|
return nei;
|
|
}
|
|
|
|
EX void precise_mouseover() {
|
|
if(WDIM == 3 && (cmode & (sm::EDIT_INSIDE_WALLS | sm::EDIT_BEFORE_WALLS))) {
|
|
transmatrix T = view_inverse(View);
|
|
transmatrix ori = Id;
|
|
if(gproduct) ori = ortho_inverse(NLP);
|
|
ld step = 0.2;
|
|
cell *c = centerover;
|
|
for(int i=0; i<100; i++) {
|
|
apply_shift_object(T, ori, ztangent(step));
|
|
int pd = through_wall(c, T * C0);
|
|
if(pd != -1) {
|
|
color_t col;
|
|
cell *c1 = c->cmove(pd);
|
|
if(isWall3(c1, col)) {
|
|
mouseover = c;
|
|
mouseover2 = c1;
|
|
point_direction = pd;
|
|
if(cmode & sm::EDIT_INSIDE_WALLS) {
|
|
swap(mouseover, mouseover2);
|
|
}
|
|
else {
|
|
point_direction =c->c.spin(pd);
|
|
}
|
|
return;
|
|
}
|
|
else {
|
|
T = currentmap->iadj(c, pd) * T;
|
|
c = c1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(WDIM == 3) {
|
|
mouseover2 = mouseover = centerover;
|
|
ld best = HUGE_VAL;
|
|
shiftpoint h = shiftless(direct_exp(lp_iapply(ztangent(0.01))));
|
|
point_direction = -1;
|
|
|
|
shiftmatrix cov = ggmatrix(mouseover2);
|
|
forCellIdEx(c1, i, mouseover2) {
|
|
shiftpoint h1 = tC0(cov * currentmap->adj(mouseover2, i));
|
|
ld dist = geo_dist(h, h1) - geo_dist(shiftless(C0), h1);
|
|
if(dist < best) mouseover = c1, best = dist, point_direction = i;
|
|
}
|
|
return;
|
|
}
|
|
if(!mouseover) return;
|
|
if(GDIM == 3) return;
|
|
cell *omouseover = mouseover;
|
|
for(int loop = 0; loop < 10; loop++) {
|
|
bool found = false;
|
|
if(!gmatrix.count(mouseover)) return;
|
|
hyperpoint r_mouseh = inverse_shift(gmatrix[mouseover], mouseh);
|
|
for(int i=0; i<mouseover->type; i++) {
|
|
hyperpoint h1 = get_corner_position(mouseover, gmod(i-1, mouseover->type));
|
|
hyperpoint h2 = get_corner_position(mouseover, i);
|
|
if(det3(build_matrix(h1, h2, C0, C0)) * det3(build_matrix(h1, h2, r_mouseh, C0)) < 0) {
|
|
mouseover2 = mouseover;
|
|
mouseover = mouseover->move(i);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) return;
|
|
}
|
|
// probably some error... just return the original
|
|
mouseover = omouseover;
|
|
}
|
|
|
|
EX transmatrix Viewbase;
|
|
|
|
EX bool no_wall_rendering;
|
|
|
|
EX bool set_multi = false;
|
|
EX hyperpoint multi_point;
|
|
|
|
EX void center_multiplayer_map(const vector<hyperpoint>& hs) {
|
|
hyperpoint h = Hypc;
|
|
for(auto h1: hs) h += h1;
|
|
h /= isize(hs);
|
|
h = cgi.emb->normalize_flat(h);
|
|
cwtV = shiftless(rgpushxto0(h));
|
|
if(isize(hs) == 2) {
|
|
set_multi = true;
|
|
multi_point = hs[1];
|
|
}
|
|
}
|
|
|
|
EX void drawthemap() {
|
|
check_cgi();
|
|
cgi.require_shapes();
|
|
|
|
DEBBI(DF_GRAPH, ("draw the map"));
|
|
|
|
last_firelimit = firelimit;
|
|
firelimit = 0;
|
|
|
|
make_clipping_planes();
|
|
current_display->radarpoints.clear();
|
|
current_display->radarlines.clear();
|
|
callhooks(hooks_drawmap);
|
|
|
|
frameid++;
|
|
cells_drawn = 0;
|
|
cells_generated = 0;
|
|
noclipped = 0;
|
|
first_cell_to_draw = true;
|
|
|
|
if(sightrange_bonus > 0 && !allowIncreasedSight())
|
|
sightrange_bonus = 0;
|
|
|
|
swap(gmatrix0, gmatrix);
|
|
gmatrix.clear();
|
|
current_display->all_drawn_copies.clear();
|
|
|
|
wmspatial = vid.wallmode == 4 || vid.wallmode == 5;
|
|
wmescher = vid.wallmode == 3 || vid.wallmode == 5;
|
|
wmplain = vid.wallmode == 2 || vid.wallmode == 4;
|
|
wmascii = vid.wallmode == 0 || vid.wallmode == 6;
|
|
wmascii3 = vid.wallmode == 6;
|
|
wmblack = vid.wallmode == 1;
|
|
|
|
mmitem = vid.monmode >= 1;
|
|
mmmon = vid.monmode >= 2;
|
|
mmspatial = vid.monmode >= 3;
|
|
|
|
mmhigh = vid.highlightmode >= 1;
|
|
if(hiliteclick) mmhigh = !mmhigh;
|
|
|
|
spatial_graphics = wmspatial || mmspatial;
|
|
spatial_graphics = spatial_graphics && GDIM == 2;
|
|
#if CAP_RUG
|
|
if(rug::rugged && !rug::spatial_rug) spatial_graphics = false;
|
|
#endif
|
|
if(non_spatial_model())
|
|
spatial_graphics = false;
|
|
if(pmodel == mdDisk && abs(pconf.alpha) < 1e-6) spatial_graphics = false;
|
|
|
|
if(!spatial_graphics) wmspatial = mmspatial = false;
|
|
if(GDIM == 3) wmspatial = mmspatial = true;
|
|
|
|
for(int m=0; m<motypes; m++) if(isPrincess(eMonster(m)))
|
|
minf[m].name = princessgender() ? "Princess" : "Prince";
|
|
|
|
#if CAP_RAY
|
|
ray::in_use = ray::requested();
|
|
#endif
|
|
no_wall_rendering = ray::in_use;
|
|
// ray::comparison_mode = true;
|
|
if(ray::comparison_mode) no_wall_rendering = false;
|
|
|
|
iinf[itSavedPrincess].name = minf[moPrincess].name;
|
|
|
|
for(int i=0; i<NUM_GS; i++) {
|
|
genderswitch_t& g = genderswitch[i];
|
|
if(g.gender != princessgender()) continue;
|
|
minf[g.m].help = g.desc;
|
|
minf[g.m].name = g.name;
|
|
}
|
|
|
|
if(mapeditor::autochoose) mapeditor::ew = mapeditor::ewsearch;
|
|
mapeditor::ewsearch.dist = 1e30;
|
|
modist = 1e20; mouseover = NULL;
|
|
modist2 = 1e20; mouseover2 = NULL;
|
|
|
|
compute_graphical_distance();
|
|
|
|
for(int i=0; i<multi::players; i++) {
|
|
multi::ccdist[i] = 1e20; multi::ccat[i] = NULL;
|
|
}
|
|
|
|
downseek.reset();
|
|
|
|
#if ISMOBILE
|
|
mouseovers = XLAT("No info about this...");
|
|
#endif
|
|
if(mouseout() && !mousepan)
|
|
modist = -5;
|
|
playerfound = false;
|
|
// playerfoundL = false;
|
|
// playerfoundR = false;
|
|
|
|
arrowtraps.clear();
|
|
|
|
make_actual_view();
|
|
currentmap->draw_all();
|
|
drawWormSegments();
|
|
drawBlizzards();
|
|
drawArrowTraps();
|
|
|
|
precise_mouseover();
|
|
|
|
ivoryz = false;
|
|
|
|
linepatterns::drawAll();
|
|
|
|
callhooks(hooks_frame);
|
|
|
|
drawMarkers();
|
|
drawFlashes();
|
|
|
|
mapeditor::draw_dtshapes();
|
|
set_multi = false;
|
|
|
|
if(multi::players > 1 && !shmup::on) {
|
|
if(multi::split_screen)
|
|
cwtV = multi::whereis[subscreens::current_player];
|
|
else if(multi::centerplayer != -1)
|
|
cwtV = multi::whereis[multi::centerplayer];
|
|
else {
|
|
vector<hyperpoint> pts;
|
|
for(int p=0; p<multi::players; p++) if(multi::playerActive(p))
|
|
pts.push_back(unshift(multi::whereis[p] * tile_center()));
|
|
center_multiplayer_map(pts);
|
|
}
|
|
}
|
|
|
|
if(shmup::on) {
|
|
if(multi::split_screen)
|
|
cwtV = shmup::pc[subscreens::current_player]->pat;
|
|
else if(multi::players == 1)
|
|
cwtV = shmup::pc[0]->pat;
|
|
else if(multi::centerplayer != -1)
|
|
cwtV = shmup::pc[multi::centerplayer]->pat;
|
|
else {
|
|
vector<hyperpoint> pts;
|
|
for(int p=0; p<multi::players; p++)
|
|
pts.push_back(unshift(shmup::pc[p]->pat * tile_center()));
|
|
center_multiplayer_map(pts);
|
|
}
|
|
}
|
|
|
|
#if CAP_SDL
|
|
const sdl_keystate_type *keystate = SDL12_GetKeyState(NULL);
|
|
lmouseover = mouseover;
|
|
lmouseover_distant = lmouseover;
|
|
bool useRangedOrb = (!(vid.shifttarget & 1) && haveRangedOrb() && lmouseover && lmouseover->cpdist > 1) || (keystate[SDL12(SDLK_RSHIFT, SDL_SCANCODE_RSHIFT)] | keystate[SDL12(SDLK_LSHIFT, SDL_SCANCODE_LSHIFT)]);
|
|
if(!useRangedOrb && !(cmode & sm::MAP) && !(cmode & sm::DRAW) && DEFAULTCONTROL && !mouseout() && !dual::state) {
|
|
dynamicval<eGravity> gs(gravity_state, gravity_state);
|
|
calcMousedest();
|
|
cellwalker cw = cwt; bool f = flipplayer;
|
|
items[itWarning]+=2;
|
|
|
|
movepcto(mousedest.d, mousedest.subdir, true);
|
|
items[itWarning] -= 2;
|
|
if(cw.spin != cwt.spin) mirror::act(-mousedest.d, mirror::SPINSINGLE);
|
|
cwt = cw; flipplayer = f;
|
|
lmouseover = mousedest.d >= 0 ? cwt.at->modmove(cwt.spin + mousedest.d) : cwt.at;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// old style joystick control
|
|
|
|
EX bool dronemode;
|
|
|
|
purehookset hooks_calcparam;
|
|
|
|
EX int corner_centering;
|
|
|
|
EX bool permaside;
|
|
|
|
EX bool old_center;
|
|
|
|
EX ld min_scale = 1e-6;
|
|
|
|
EX int forced_center_down = ISANDROID ? 2 : ISIOS ? 40 : 40;
|
|
|
|
EX ld get_stereo_param() {
|
|
if(among(vid.stereo_mode, sPanini, sStereographic)) return vid.stereo_param;
|
|
return 0;
|
|
}
|
|
|
|
EX void calcparam() {
|
|
|
|
DEBBI(DF_GRAPH, ("calc param"));
|
|
auto cd = current_display;
|
|
|
|
cd->xtop = vid.xres * cd->xmin;
|
|
cd->ytop = vid.yres * cd->ymin;
|
|
|
|
cd->xsize = vid.xres * (cd->xmax - cd->xmin);
|
|
cd->ysize = vid.yres * (cd->ymax - cd->ymin);
|
|
|
|
cd->xcenter = cd->xtop + cd->xsize / 2;
|
|
cd->ycenter = cd->ytop + cd->ysize / 2;
|
|
|
|
if(abs(pconf.scale) < min_scale) pconf.scale = 1;
|
|
|
|
ld realradius = min(cd->xsize / 2, cd->ysize / 2);
|
|
|
|
cd->scrsize = realradius;
|
|
if(!inHighQual) cd->scrsize -= forced_center_down;
|
|
|
|
current_display->sidescreen = permaside;
|
|
|
|
if(vid.xres < vid.yres - 2 * vid.fsize && !inHighQual && (old_center || !in_perspective())) {
|
|
cd->ycenter = lerp(vid.fsize + cd->scrsize, vid.yres - cd->scrsize - vid.fsize, .8);
|
|
}
|
|
else {
|
|
bool ok = !vrhr::active();
|
|
if(vid.xres > vid.yres * 4/3+16 && (cmode & sm::SIDE) && ok && !((cmode & sm::MAYDARK) && centered_menus))
|
|
current_display->sidescreen = true;
|
|
#if CAP_TOUR
|
|
if(tour::on && (tour::slides[tour::currentslide].flags & tour::SIDESCREEN) && ok)
|
|
current_display->sidescreen = true;
|
|
#endif
|
|
if((cmode & sm::DIALOG_OFFMAP) && !centered_menus && vid.xres > vid.yres * 11/10)
|
|
current_display->sidescreen = true;
|
|
|
|
if(current_display->sidescreen) cd->xcenter = vid.yres/2;
|
|
}
|
|
|
|
cd->radius = pconf.scale * cd->scrsize;
|
|
if(GDIM == 3 && in_perspective()) cd->radius = cd->scrsize;
|
|
realradius = min(realradius, cd->radius);
|
|
|
|
ld aradius = sphere ? cd->radius / (pconf.alpha - 1) : cd->radius;
|
|
#if MAXMDIM >= 4
|
|
if(euclid && hybrid::drawing_underlying) aradius *= 2.5;
|
|
#endif
|
|
|
|
if(dronemode) { cd->ycenter -= cd->radius; cd->ycenter += vid.fsize/2; cd->ycenter += vid.fsize/2; cd->radius *= 2; }
|
|
|
|
if(corner_centering) {
|
|
cd->ycenter = cd->ytop + cd->ysize - vid.fsize - aradius;
|
|
if(corner_centering == 1)
|
|
cd->xcenter = cd->xtop + vid.fsize + aradius;
|
|
if(corner_centering == 2)
|
|
cd->xcenter = cd->xtop + cd->xsize - vid.fsize - aradius;
|
|
}
|
|
|
|
cd->xcenter += cd->scrsize * pconf.xposition;
|
|
cd->ycenter += cd->scrsize * pconf.yposition;
|
|
|
|
ld fov = vid.fov * degree / 2;
|
|
cd->tanfov = sin(fov) / (cos(fov) + get_stereo_param());
|
|
|
|
#if CAP_SDLTTF
|
|
set_cfont();
|
|
#endif
|
|
callhooks(hooks_calcparam);
|
|
reset_projection();
|
|
}
|
|
|
|
EX function<void()> wrap_drawfullmap = drawfullmap;
|
|
|
|
bool force_sphere_outline = false;
|
|
|
|
EX void drawfullmap() {
|
|
|
|
DEBBI(DF_GRAPH, ("draw full map"));
|
|
|
|
check_cgi();
|
|
cgi.require_shapes();
|
|
|
|
ptds.clear();
|
|
|
|
|
|
/*
|
|
if(models::on) {
|
|
char ch = 'A';
|
|
for(auto& v: history::v) {
|
|
queuepoly(ggmatrix(v->base) * v->at, cgi.shTriangle, 0x306090C0);
|
|
queuestr(ggmatrix(v->base) * v->at * C0, 10, s0+(ch++), 0xFF0000);
|
|
}
|
|
}
|
|
*/
|
|
|
|
#if CAP_QUEUE
|
|
draw_boundary(0);
|
|
draw_boundary(1);
|
|
|
|
draw_model_elements();
|
|
#if MAXMDIM >= 4 && CAP_GL
|
|
prepare_sky();
|
|
#endif
|
|
#endif
|
|
|
|
/* if(vid.wallmode < 2 && !euclid && !patterns::whichShape) {
|
|
int ls = isize(lines);
|
|
if(ISMOBILE) ls /= 10;
|
|
for(int t=0; t<ls; t++) queueline(View * lines[t].P1, View * lines[t].P2, lines[t].col >> (darken+1));
|
|
} */
|
|
|
|
clearaura();
|
|
if(!nomap) drawthemap();
|
|
else callhooks(hooks_frame);
|
|
|
|
if(!inHighQual) {
|
|
if((cmode & sm::NORMAL) && !rug::rugged) {
|
|
if(multi::players > 1) {
|
|
auto bcwtV = cwtV;
|
|
for(int i=0; i<multi::players; i++) if(multi::playerActive(i))
|
|
cwtV = multi::whereis[i], multi::cpid = i, drawmovestar(multi::mdx[i], multi::mdy[i]);
|
|
cwtV = bcwtV;
|
|
}
|
|
else if(multi::alwaysuse)
|
|
drawmovestar(multi::mdx[0], multi::mdy[0]);
|
|
else
|
|
drawmovestar(0, 0);
|
|
}
|
|
#if CAP_EDIT
|
|
if(cmode & sm::DRAW) mapeditor::drawGrid();
|
|
#endif
|
|
}
|
|
|
|
drawaura();
|
|
#if CAP_QUEUE
|
|
drawqueue();
|
|
#endif
|
|
}
|
|
|
|
#if ISMOBILE
|
|
extern bool wclick;
|
|
#endif
|
|
|
|
EX bool just_refreshing;
|
|
|
|
EX int menu_darkening = 2;
|
|
EX bool centered_menus = false;
|
|
|
|
EX string menu_format = "";
|
|
|
|
EX void gamescreen() {
|
|
|
|
if(cmode & sm::NOSCR) {
|
|
stillscreen = true;
|
|
emptyscreen();
|
|
return;
|
|
}
|
|
|
|
if(just_refreshing) return;
|
|
|
|
if(subscreens::split([=] () {
|
|
calcparam();
|
|
compute_graphical_distance();
|
|
gamescreen();
|
|
})) {
|
|
if(racing::on) return;
|
|
// create the gmatrix
|
|
View = subscreens::player_displays[0].view_matrix;
|
|
centerover = subscreens::player_displays[0].precise_center;
|
|
just_gmatrix = true;
|
|
currentmap->draw_all();
|
|
just_gmatrix = false;
|
|
return;
|
|
}
|
|
|
|
stillscreen = false;
|
|
|
|
auto gx = vid.xres;
|
|
auto gy = vid.yres;
|
|
|
|
if(dual::split([=] () {
|
|
vid.xres = gx;
|
|
vid.yres = gy;
|
|
dual::in_subscreen([=] () { gamescreen(); });
|
|
})) {
|
|
calcparam();
|
|
return;
|
|
}
|
|
|
|
calcparam();
|
|
|
|
darken = 0;
|
|
|
|
if(!inHighQual && !vrhr::active()) {
|
|
if((cmode & sm::MAYDARK) && !current_display->sidescreen)
|
|
darken += menu_darkening;
|
|
else if(cmode & sm::DARKEN)
|
|
darken += menu_darkening;
|
|
}
|
|
if(vid.highlightmode == (hiliteclick ? 0 : 2))
|
|
darken++;
|
|
if(darken >= 8) {
|
|
emptyscreen();
|
|
return;
|
|
}
|
|
|
|
if(history::includeHistory) history::restore();
|
|
|
|
festive = festive_date && festive_option;
|
|
old_shines = std::move(shines); shines.clear();
|
|
|
|
anims::apply();
|
|
#if CAP_RUG
|
|
if(rug::rugged) {
|
|
if(!nomap) rug::actDraw();
|
|
} else
|
|
#endif
|
|
wrap_drawfullmap();
|
|
anims::rollback();
|
|
|
|
if(history::includeHistory) history::restoreBack();
|
|
|
|
poly_outline = OUTLINE_DEFAULT;
|
|
|
|
#if ISMOBILE
|
|
|
|
buttonclicked = false;
|
|
|
|
if((cmode & sm::NORMAL) && vid.stereo_mode != sLR && !inHighQual) {
|
|
if(andmode == 0 && shmup::on) {
|
|
using namespace shmupballs;
|
|
calc();
|
|
drawCircle(xmove, yb, rad, OUTLINE_FORE);
|
|
drawCircle(xmove, yb, rad/2, OUTLINE_FORE);
|
|
drawCircle(xfire, yb, rad, 0xFF0000FF);
|
|
drawCircle(xfire, yb, rad/2, 0xFF0000FF);
|
|
}
|
|
else {
|
|
if(!haveMobileCompass()) displayabutton(-1, +1, andmode == 0 && useRangedOrb ? XLAT("FIRE") : andmode == 0 && WDIM == 3 && wclick ? XLAT("WAIT") : XLAT("MOVE"), andmode == 0 ? BTON : BTOFF);
|
|
displayabutton(+1, +1, rug::rugged ? XLAT("RUG") :andmode == 1 ? XLAT("BACK") : GDIM == 3 ? XLAT("CAM") : XLAT("DRAG"), andmode == 1 ? BTON : BTOFF);
|
|
}
|
|
displayabutton(-1, -1, XLAT("INFO"), andmode == 12 ? BTON : BTOFF);
|
|
displayabutton(+1, -1, XLAT("MENU"), andmode == 3 ? BTON : BTOFF);
|
|
}
|
|
|
|
#endif
|
|
|
|
darken = 0;
|
|
|
|
#if CAP_TEXTURE
|
|
if(texture::config.tstate == texture::tsAdjusting)
|
|
texture::config.drawRawTexture();
|
|
#endif
|
|
|
|
#if CAP_VR
|
|
vrhr::size_and_draw_ui_box();
|
|
#endif
|
|
}
|
|
|
|
EX void emptyscreen() {
|
|
check_cgi();
|
|
cgi.require_shapes();
|
|
make_actual_view();
|
|
ptds.clear();
|
|
ray::in_use = false;
|
|
drawqueue();
|
|
}
|
|
|
|
EX int nohelp;
|
|
EX bool no_find_player;
|
|
|
|
EX void show_menu_button() {
|
|
if(menu_format != "")
|
|
displayButton(vid.xres-8, vid.yres-vid.fsize, eval_programmable_string(menu_format), 'v', 16);
|
|
else if(nomenukey || ISMOBILE)
|
|
;
|
|
#if CAP_TOUR
|
|
else if(tour::on)
|
|
displayButton(vid.xres-8, vid.yres-vid.fsize, XLAT("(ESC) tour menu"), SDLK_ESCAPE, 16);
|
|
#endif
|
|
else
|
|
displayButton(vid.xres-8, vid.yres-vid.fsize, XLAT("(v) menu"), 'v', 16);
|
|
}
|
|
|
|
EX void normalscreen() {
|
|
help = "@";
|
|
|
|
mouseovers = standard_help();
|
|
|
|
#if CAP_TOUR
|
|
if(tour::on) mouseovers = (tour::slides[tour::currentslide].flags & tour::NOTITLE) ? "" : tour::tourhelp;
|
|
#endif
|
|
|
|
if(GDIM == 3 || !outofmap(mouseh.h)) getcstat = '-';
|
|
cmode = sm::NORMAL | sm::DOTOUR | sm::CENTER;
|
|
if(viewdists && show_distance_lists) cmode |= sm::SIDE | sm::MAYDARK;
|
|
gamescreen(); drawStats();
|
|
|
|
show_menu_button();
|
|
keyhandler = handleKeyNormal;
|
|
dialog::key_actions.clear();
|
|
|
|
if(!playerfound && !anims::any_on() && !sphere && !no_find_player && mapeditor::drawplayer)
|
|
displayButton(current_display->xcenter, current_display->ycenter, mousing ? XLAT("find the player") : XLAT("press SPACE to find the player"), ' ', 8);
|
|
|
|
if(!mapeditor::drawplayer && playermoved && !no_find_player)
|
|
displayButton(current_display->xcenter, current_display->ycenter, XLAT("move the camera with arrow keys and Home/End"), PSEUDOKEY_NOHINT, 8);
|
|
|
|
describeMouseover();
|
|
}
|
|
|
|
EX vector< function<void()> > screens = { normalscreen };
|
|
|
|
#if HDR
|
|
template<class T> void pushScreen(const T& x) { screens.push_back(x); }
|
|
inline void popScreen() { if(isize(screens)>1) screens.pop_back(); }
|
|
inline void popScreenAll() { while(isize(screens)>1) popScreen(); }
|
|
typedef void (*cfunction)();
|
|
#endif
|
|
|
|
EX cfunction current_screen_cfunction() {
|
|
auto tgt = screens.back().target<cfunction>();
|
|
if(!tgt) return nullptr;
|
|
return *tgt;
|
|
}
|
|
|
|
#if HDR
|
|
namespace sm {
|
|
static constexpr int NORMAL = 1;
|
|
static constexpr int MISSION = 2;
|
|
static constexpr int HELP = 4;
|
|
static constexpr int MAP = 8;
|
|
static constexpr int DRAW = 16;
|
|
static constexpr int NUMBER = 32;
|
|
static constexpr int SHMUPCONFIG = 64;
|
|
static constexpr int OVERVIEW = 128;
|
|
static constexpr int SIDE = 256;
|
|
static constexpr int DOTOUR = 512;
|
|
static constexpr int CENTER = 1024;
|
|
static constexpr int ZOOMABLE = 4096;
|
|
static constexpr int TORUSCONFIG = 8192;
|
|
static constexpr int MAYDARK = 16384; // use together with SIDE; if the screen is not wide or centered_menus is set, it will disable SIDE and instead darken the screen
|
|
static constexpr int DIALOG_STRICT_X = 32768; // do not interpret dialog clicks outside of the X region
|
|
static constexpr int EXPANSION = (1<<16);
|
|
static constexpr int HEXEDIT = (1<<17);
|
|
static constexpr int VR_MENU = (1<<18); // always show the menu in VR
|
|
static constexpr int SHOWCURSOR = (1<<19); // despite MAP/DRAW always show the cursor, no panning
|
|
static constexpr int PANNING = (1<<20); // smooth scrolling works
|
|
static constexpr int DARKEN = (1<<21); // darken the game background
|
|
static constexpr int NOSCR = (1<<22); // do not show the game background
|
|
static constexpr int AUTO_VALUES = (1<<23); // automatic place for values
|
|
static constexpr int NARROW_LINES = (1<<24); // do make the lines narrower if we needed to reduce width
|
|
static constexpr int EDIT_BEFORE_WALLS = (1<<25); // mouseover targets before walls
|
|
static constexpr int EDIT_INSIDE_WALLS = (1<<26); // mouseover targets inside walls
|
|
static constexpr int DIALOG_WIDE = (1<<27); // make dialogs wide
|
|
static constexpr int MOUSEAIM = (1<<28); // mouse aiming active here
|
|
static constexpr int DIALOG_OFFMAP = (1<<29); // try hard to keep dialogs off the map
|
|
}
|
|
#endif
|
|
|
|
EX int cmode;
|
|
|
|
EX bool dont_display_minecount = false;
|
|
|
|
EX color_t titlecolor;
|
|
|
|
EX void drawscreen() {
|
|
|
|
DEBBI(DF_GRAPH, ("drawscreen"));
|
|
#if CAP_GL
|
|
GLWRAP;
|
|
#endif
|
|
|
|
if(vid.xres == 0 || vid.yres == 0) return;
|
|
|
|
calcparam();
|
|
// rug::setVidParam();
|
|
|
|
#if CAP_GL
|
|
if(vid.usingGL) setGLProjection();
|
|
#endif
|
|
|
|
#if CAP_XGD
|
|
if(!vid.usingGL) {
|
|
gdpush(5); gdpush(backcolor);
|
|
}
|
|
#endif
|
|
|
|
#if CAP_VR
|
|
vrhr::clear();
|
|
#endif
|
|
|
|
|
|
#if CAP_SDL
|
|
// SDL_LockSurface(s);
|
|
// unsigned char *b = (unsigned char*) s->pixels;
|
|
// int n = vid.xres * vid.yres * 4;
|
|
// while(n) *b >>= 1, b++, n--;
|
|
// memset(s->pixels, 0, vid.xres * vid.yres * 4);
|
|
#if CAP_GL
|
|
if(!vid.usingGL)
|
|
#endif
|
|
SDL_FillSurfaceRect(s, NULL, backcolor);
|
|
#endif
|
|
|
|
// displaynum(vx,100, 0, 24, 0xc0c0c0, celldist(cwt.at), ":");
|
|
|
|
lgetcstat = getcstat;
|
|
getcstat = 0; inslider = false;
|
|
|
|
mouseovers = " ";
|
|
|
|
cmode = 0;
|
|
reset_handlers();
|
|
if(!isize(screens)) pushScreen(normalscreen);
|
|
screens.back()();
|
|
|
|
#if !ISMOBILE
|
|
color_t col = linf[cwt.at->land].color;
|
|
if(cwt.at->land == laRedRock) col = 0xC00000;
|
|
if(titlecolor) col = titlecolor;
|
|
if(nohelp != 1) {
|
|
int size = vid.fsize;
|
|
while(size > 3 && textwidth(size, mouseovers) > vid.xres) size--;
|
|
poly_outline = (backcolor << 8) | 0xFF;
|
|
displayfr(vid.xres/2, vid.fsize, 2, size, mouseovers, col, 8);
|
|
}
|
|
#endif
|
|
|
|
drawmessages();
|
|
|
|
bool normal = cmode & sm::NORMAL;
|
|
|
|
if((havewhat&HF_BUG) && darken == 0 && normal) if(hive::bugcount[0] || hive::bugcount[1] || hive::bugcount[2]) for(int k=0; k<3; k++)
|
|
displayfr(vid.xres/2 + vid.fsize * 5 * (k-1), vid.fsize*2, 2, vid.fsize,
|
|
its(hive::bugcount[k]), minf[moBug0+k].color, 8);
|
|
|
|
bool minefieldNearby = false;
|
|
int mines[MAXPLAYER], tmines=0;
|
|
for(int p=0; p<numplayers(); p++) {
|
|
mines[p] = 0;
|
|
cell *c = playerpos(p);
|
|
if(!c) continue;
|
|
for(cell *c2: adj_minefield_cells(c)) {
|
|
if(c2->land == laMinefield)
|
|
minefieldNearby = true;
|
|
if(c2->wall == waMineMine) {
|
|
bool ep = false;
|
|
if(!ep) mines[p]++, tmines++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if((minefieldNearby || tmines) && !items[itOrbAether] && !last_gravity_state && darken == 0 && normal) {
|
|
string s;
|
|
if(tmines > 9) tmines = 9;
|
|
color_t col = minecolors[tmines];
|
|
|
|
if(tmines == 7) seenSevenMines = true;
|
|
|
|
if(!dont_display_minecount) for(int p: player_indices())
|
|
displayfr(vid.xres * (p+.5) / numplayers(),
|
|
current_display->ycenter - current_display->radius * 3/4, 2,
|
|
vid.fsize,
|
|
mines[p] > 7 ? its(mines[p]) : XLAT(minetexts[mines[p]]), minecolors[mines[p]], 8);
|
|
|
|
if(minefieldNearby && !shmup::on && cwt.at->land != laMinefield && cwt.peek()->land != laMinefield && !dont_display_minecount) {
|
|
displayfr(vid.xres/2, current_display->ycenter - current_display->radius * 3/4 - vid.fsize*3/2, 2,
|
|
vid.fsize,
|
|
XLAT("WARNING: you are entering a minefield!"),
|
|
col, 8);
|
|
}
|
|
}
|
|
|
|
// SDL_UnlockSurface(s);
|
|
|
|
glflush();
|
|
DEBB(DF_GRAPH, ("swapbuffers"));
|
|
|
|
#if CAP_VR
|
|
vrhr::submit();
|
|
#endif
|
|
|
|
#if CAP_SDL
|
|
present_screen();
|
|
#endif
|
|
|
|
#if CAP_VR
|
|
vrhr::handoff();
|
|
#endif
|
|
|
|
//printf("\ec");
|
|
}
|
|
|
|
EX void restartGraph() {
|
|
DEBBI(DF_INIT, ("restartGraph"));
|
|
|
|
if(!autocheat) linepatterns::clearAll();
|
|
if(currentmap) resetview();
|
|
}
|
|
|
|
auto graphcm = addHook(hooks_clearmemory, 0, [] () {
|
|
DEBBI(DF_MEMORY, ("clear graph memory"));
|
|
mouseover = centerover = lmouseover = NULL;
|
|
gmatrix.clear(); gmatrix0.clear(); current_display->all_drawn_copies.clear();
|
|
clearAnimations();
|
|
})
|
|
+ addHook(hooks_gamedata, 0, [] (gamedata* gd) {
|
|
gd->store(mouseover);
|
|
gd->store(lmouseover);
|
|
gd->store(current_display->radar_transform);
|
|
gd->store(actual_view_transform);
|
|
});
|
|
|
|
EX void drawBug(const cellwalker& cw, color_t col) {
|
|
#if CAP_SHAPES
|
|
initquickqueue();
|
|
shiftmatrix V = ggmatrix(cw.at);
|
|
if(cw.spin) V = V * ddspin180(cw.at, cw.spin);
|
|
queuepoly(V, cgi.shBugBody, col);
|
|
quickqueue();
|
|
#endif
|
|
}
|
|
|
|
EX bool inscreenrange_actual(cell *c) {
|
|
if(GDIM == 3) return true;
|
|
hyperpoint h1; applymodel(ggmatrix(c) * tile_center(), h1);
|
|
if(invalid_point(h1)) return false;
|
|
auto hscr = toscrcoord(h1);
|
|
auto& x = hscr[0], y = hscr[1];
|
|
if(x > current_display->xtop + current_display->xsize) return false;
|
|
if(x < current_display->xtop) return false;
|
|
if(y > current_display->ytop + current_display->ysize) return false;
|
|
if(y < current_display->ytop) return false;
|
|
return true;
|
|
}
|
|
|
|
EX bool inscreenrange(cell *c) {
|
|
if(sphere) return true;
|
|
if(euclid) return celldistance(centerover, c) <= get_sightrange_ambush() && inscreenrange_actual(c);
|
|
if(nonisotropic) return gmatrix.count(c);
|
|
if(geometry == gCrystal344) return gmatrix.count(c) && inscreenrange_actual(c);
|
|
auto hd = heptdistance(centerover, c);
|
|
if(hd <= 1) return true;
|
|
return hd <= 8 && inscreenrange_actual(c);
|
|
}
|
|
|
|
#if MAXMDIM >= 4
|
|
auto hooksw = addHook(hooks_swapdim, 100, [] { clearAnimations(); gmatrix.clear(); gmatrix0.clear(); current_display->all_drawn_copies.clear(); });
|
|
#endif
|
|
|
|
}
|