1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-10-18 14:35:47 +00:00
hyperrogue/rogueviz/ads/map.cpp
2024-10-01 15:13:56 +02:00

481 lines
15 KiB
C++

namespace hr {
namespace ads_game {
int gen_expire() {
return 20 / randd() - 15;
}
vector<shipstate> history;
std::unordered_map<cell*, cellinfo> ci_at;
using worldline_visitor = std::function<bool(cell*, ld)>;
void compute_life(cell *c, transmatrix S1, const worldline_visitor& wv) {
ld t = 0;
int iter = 0;
cell *cur_c = c;
auto cur_w = hybrid::get_where(c);
while(t < TAU) {
iter++;
auto last_w = cur_w;
auto next_w = cur_w;
transmatrix next_S1;
ld next_t;
ld last_time = t;
cell *next_c = nullptr;
binsearch(t, t+90._deg, [&] (ld t1) {
S1 = S1 * chg_shift(t1 - last_time);
last_time = t1;
virtualRebase(cur_c, S1);
cur_w = hybrid::get_where(cur_c);
if(cur_w.first != last_w.first) {
next_c = cur_c;
next_w = cur_w;
next_S1 = S1;
next_t = t1;
return true;
}
return false;
}, 10);
if(!next_c) return;
S1 = next_S1;
cur_w = next_w;
t = next_t;
cur_c = next_c;
if(iter > 1000) {
println(hlog, "compute_life c=", cur_c, " w=", cur_w, "t=", t, " S1=", S1);
fixmatrix_ads(S1);
}
if(iter > 1100) break;
if(wv(cur_w.first, t)) break;
}
}
map<int, int> genstats;
int gen_budget;
void gen_terrain(cell *c, cellinfo& ci, int level = 0) {
setdist(c, 7, nullptr);
if(level >= ci.mpd_terrain) return;
if(!hyperbolic) { println(hlog, "wrong geometry detected in gen_terrain!"); exit(1); }
if(ci.mpd_terrain > level + 1) gen_terrain(c, ci, level+1);
forCellCM(c1, c) gen_terrain(c1, ci_at[c1], level+1);
genstats[level]++;
if(level == 2) {
int r = hrand(100);
if(r < wall_frequency(c)) {
int t = hrand(2);
if(t == 0)
forCellCM(c1, c) if(hrand(100) < 50) if(c1->land == c->land)
forCellCM(c2, c1) if(hrand(100) < 50) if(c2->land == c->land)
if(ci_at[c2].type == wtNone) ci_at[c2].type = wtDestructible;
if(t == 1)
forCellCM(c1, c) if(hrand(100) < 50) if(c1->land == c->land)
forCellCM(c2, c1) if(hrand(100) < 50) if(c1->land == c->land)
if(ci_at[c2].type < wtSolid)
ci_at[c2].type = wtSolid;
}
r = hrand(100);
if(r < gate_frequency(c))
ci_at[c].type = wtGate;
}
ci.mpd_terrain = level;
if(c->land == laBarrier)
ci_at[c].type = wtBarrier;
}
void add_rock(cell *c, cellinfo& ci, const ads_matrix& T) {
bool fail = false;
compute_life(hybrid::get_at(c, 0), unshift(T), [&] (cell *c, ld t) {
setdist(c, 7, nullptr);
if(c->land == laBarrier) fail = true;
return false;
});
if(fail) return;
eResourceType rt = eResourceType(rand() % 6);
auto r = std::make_unique<ads_object> (oRock, c, T, rock_color[rt]);
r->resource = rt;
r->expire = gen_expire();
r->shape = &(rand() % 2 ? shape_rock2 : shape_rock);
if(geometry != gTwistedProduct) { println(hlog, "wrong geometry detected in gen_rocks 2!"); exit(1); }
int q = 0;
auto cleanup = [&] (cell *c, ld t) {
auto& ci = ci_at[c];
hybrid::in_underlying_geometry([&] { gen_terrain(c, ci); });
ci.type = wtNone;
q++;
return false;
};
if(q == 0) ci.type = wtNone;
compute_life(hybrid::get_at(c, 0), unshift(T), cleanup);
/* for(int i=0; i<isize(r->shape[0]); i += 2) { // exact check is too slow here
hyperpoint h;
h[0] = r->shape[0][i];
h[1] = r->shape[0][i+1];
h[2] = 0;
h[3] = 1; */
if(0) for(int i=0; i<4; i++) {
hyperpoint h = spin(90*degree*i) * twist::uxpush(0.15) * C0;
compute_life(hybrid::get_at(c, 0), unshift(r->at) * rgpushxto0(h), cleanup);
}
ci.rocks.emplace_back(std::move(r));
}
void add_turret(cell *c, cellinfo& ci, const ads_matrix& T) {
auto r = std::make_unique<ads_object> (oTurret, c, T, 0xC0C060FF);
r->expire = gen_expire();
r->shape = &shape_turret;
r->last_shot = -1;
r->hlast = 0;
if(geometry != gTwistedProduct) { println(hlog, "wrong geometry detected in gen_turret!"); exit(1); }
int q = 0;
auto cleanup = [&] (cell *c, ld t) {
auto& ci = ci_at[c];
hybrid::in_underlying_geometry([&] { gen_terrain(c, ci); });
ci.type = wtNone;
q++;
return false;
};
if(q == 0) ci.type = wtNone;
compute_life(hybrid::get_at(c, 0), unshift(r->at), cleanup);
ci.rocks.emplace_back(std::move(r));
}
void gen_resource(cell *c, shiftmatrix from, eResourceType rsrc, int expire);
void add_rsrc(cell *c, cellinfo& ci, const ads_matrix& T) {
eResourceType rt = eResourceType(rand() % 6);
gen_resource(c, T, rt, gen_expire());
}
int turrets;
struct placement {
ld alpha;
ld r;
ld shift;
ld spinshift;
ld rapidity;
ads_matrix get() {
return spin(alpha) * twist::uxpush(r/2) * chg_shift(shift) * spin(spinshift) * lorentz(0, 3, rapidity);
};
};
/* if maxr equals cgi.rhexf, any point inside the cell equally likely */
placement get_placement(cell *c, ld maxr, ld max_rapidity) {
cell *c1 = nullptr;
placement p;
while(c1 != c) {
ld vol = randd() * wvolarea_auto(maxr);
p.r = binsearch(0, maxr, [vol] (ld r) { return wvolarea_auto(r) > vol; });
p.alpha = randd() * TAU;
hyperpoint h = spin(p.alpha) * xpush0(p.r);
c1 = c;
virtualRebase(c1, h);
}
p.shift = randd() * TAU;
p.spinshift = randd() * TAU;
p.rapidity = randd() * max_rapidity;
return p;
}
void gen_rocks(cell *c, cellinfo& ci, int radius) {
if(radius <= ci.rock_dist) return;
if(ci.rock_dist < radius - 1) gen_rocks(c, ci, radius-1);
forCellCM(c1, c) gen_rocks(c1, ci_at[c1], radius-1);
if(!hyperbolic) { println(hlog, "wrong geometry detected in gen_rocks 1!"); exit(1); }
if(radius == 0) {
int q = rpoisson(rock_density * rock_frequency(c));
for(int i=0; i<q; i++) {
auto p = get_placement(c, cgi.rhexf, rock_max_rapidity);
hybrid::in_actual([&] {
add_rock(c, ci, p.get());
});
}
if(ci.type == wtGate && hrand(100) < 20) {
auto p = get_placement(c, cgi.rhexf / 2, rock_max_rapidity / 100);
hybrid::in_actual([&] {
add_rsrc(c, ci, p.get());
});
}
q = rpoisson(rock_density * turret_frequency(c));
// if(celldist(c) == 2) q += rpoisson(0.1);
for(int i=0; i<q; i++) {
auto p = get_placement(c, cgi.rhexf, rock_max_rapidity / 10);
hybrid::in_actual([&] {
add_turret(c, ci, p.get());
turrets++;
});
}
}
ci.rock_dist = radius;
}
void gen_particles(int qty, cell *c, shiftmatrix from, color_t col, ld spd, ld t, ld spread = 1) {
auto& ro = ci_at[c].rocks;
for(int i=0; i<qty; i++) {
auto r = std::make_unique<ads_object>(oParticle, c, from * spin(randd() * TAU * spread) * lorentz(0, 2, (.5 + randd() * .5) * spd), col );
r->shape = &shape_particle;
r->life_end = randd() * t;
r->life_start = 0;
ro.emplace_back(std::move(r));
}
}
void gen_resource(cell *c, shiftmatrix from, eResourceType rsrc, int expire) {
if(!rsrc) return;
auto r = std::make_unique<ads_object>(oResource, c, from, rsrc_color[rsrc]);
r->shape = rsrc_shape[rsrc];
r->life_end = HUGE_VAL;
r->life_start = 0;
r->resource = rsrc;
r->expire = expire;
ci_at[c].rocks.emplace_back(std::move(r));
}
bool pointcrash(hyperpoint h, const vector<cross_result>& vf) {
int winding = 0;
vector<hyperpoint> kleins;
for(auto& p: vf) kleins.push_back(kleinize(p.h) - h);
auto take = [&] (hyperpoint& a, hyperpoint& b) {
if(asign(a[1], b[1]) && xcross(b[0], b[1], a[0], a[1]) < 1e-6)
winding++;
};
for(int i=1; i<isize(kleins); i++) take(kleins[i-1], kleins[i]);
take(kleins.back(), kleins[0]);
return winding & 1;
}
void common_crash_ship() {
invincibility_pt = ship_pt + DS_(how_much_invincibility);
pdata.hitpoints--;
if(pdata.hitpoints <= 0) game_over = true;
playSound(nullptr, "explosion");
}
void ads_crash_ship() {
if(ship_pt < invincibility_pt) return;
common_crash_ship();
hybrid::in_actual([&] {
gen_particles(rpoisson(crash_particle_qty * 2), vctr, ads_inverse(current * vctrV) * spin(ang*degree), rsrc_color[rtHull], crash_particle_rapidity, crash_particle_life);
});
}
// -1 : T1 is in the past of T2
// =0 : T1 is elsewhere from T2
// +1 : T1 is in the future of T2
hyperpoint hcopy;
int spacetime_relation(const ads_matrix& T1, const ads_matrix& T2) {
auto h = ads_inverse(T1) * (T2 * C0);
if(h.shift > 90._deg) return 1;
if(h.shift < -90._deg) return -1;
auto h1 = unshift(h);
hcopy = h1;
if(h1[0] * h1[0] + h1[1] * h1[1] > h1[2] * h1[2]) return 0;
return h1[2] > 0 ? 1 : -1;
}
bool bad_turret = false;
void handle_turret(ads_object *t, ld& angle_at_time) {
ld ctime = t->pt_main.shift;
auto p = at_or_null(cds_last, t->owner);
if(!p) return;
auto t1 = p->V * t->at * ads_matrix(Id, ctime);
auto& ts = t->turret_states;
auto it1 = ts.lower_bound(ctime);
if(it1->first == ctime) {
if(it1->second.err < 0.01) angle_at_time = it1->second.angle;
return;
}
auto it0 = it1; if(it0 != ts.begin()) it0--;
int tv0 = (it1 == ts.begin()) ? 0 : it0->second.index;
int tv1 = (it1 == ts.end()) ? isize(history) : it1->second.index;
while(tv0 < tv1) {
int tvm = (tv0 + tv1) / 2;
auto& hi = history[tvm];
auto p1 = at_or_null(cds_last, hi.vctr);
if(!p1) { tv0 = tvm+1; continue; }
ads_matrix at1 = p1->V * hi.at;
auto rel = spacetime_relation(t1, at1);
if(rel == -1) tv0 = tvm+1;
else tv1 = tvm;
}
// println(hlog, "tv0 search returns ", tv0, "/", isize(history), " for ctime = ", ctime);
if(tv0 == 0 || tv0 == isize(history)) { return; }
auto& hi = history[tv0];
auto p1 = at_or_null(cds_last, hi.vctr);
if(!p1) return;
ads_matrix at1 = p1->V * hi.at;
turret_state nts;
if(bad_turret) {
auto h = ads_inverse(t1) * (at1 * C0);
auto h1 = unshift(h);
nts.angle = -atan2(h1[1], h1[0]);
nts.dist = acosh(h1[3]) - turret_length;
nts.err = 0;
}
else {
auto hitpoint = [&] (ld alph, ld dist) {
return ads_inverse(at1) * t1 * spin(alph) * twist::uxpush(turret_length) * lorentz(0, 2, ads_missile_rapidity) * ads_point(C0, dist);
};
auto opt_hitpoint = [&] (ld alph, ld dist) { return unshift(hitpoint(alph, dist)); };
if(it1 == ts.begin() || it0->first < ctime - 0.1 || it0->second.err > 0.01) {
ld best_err = HUGE_VAL;
for(int av=0; av<24; av++) for(ld dist=0.01; dist < 2; dist += 0.01) {
ld alph = av * TAU / 24;
ld err = sqhypot_d(2, hitpoint(alph, dist).h);
if(err < best_err) { best_err = err; nts.angle = alph; nts.dist = dist; }
}
// println(hlog, "the closest hit at alpha = ", nts.angle, " and dist = ", nts.dist, " (err = ", best_err, ")");
}
else {
nts.angle = it0->second.angle;
nts.dist = it0->second.dist;
}
/* Newton method */
for(int it=0; it<3; it++) {
ld eps = 1e-4;
hyperpoint h0 = opt_hitpoint(nts.angle, nts.dist);
hyperpoint hx = opt_hitpoint(nts.angle + eps, nts.dist);
hyperpoint hy = opt_hitpoint(nts.angle, nts.dist + eps);
// println(hlog, tie(nts.angle, nts.dist), " : ", h0);
transmatrix T = Id;
set_column(T, 0, hx-h0);
set_column(T, 1, hy-h0);
transmatrix T2 = inverse2(T);
// f(x) = h0 + T * (x-x0) / eps = 0
// -h0 * eps = T * (x-x0)
// T2 * (-h0 * eps) = x - x0
hyperpoint x = T2 * (-h0 * eps);
nts.angle += x[0];
nts.dist += x[1];
}
nts.err = sqhypot_d(2, hitpoint(nts.angle, nts.dist).h);
}
// println(hlog, "nts values are: ", tie(nts.angle, nts.dist, nts.err));
if(nts.err < 0.01 && ctime > t->last_shot + 1 && it0->second.err < 0.01) {
t->last_shot = t->last_shot + floor(ctime - t->last_shot);
ld angle = lerp(it0->second.angle, nts.angle, ilerp(it0->first, ctime, t->last_shot));
// println(hlog, "shooting at angle ", angle, " at time ", t->last_shot);
ads_matrix S0 = ads_inverse(p->V) * t1 * spin(angle) * twist::uxpush(turret_length * ads_scale) * lorentz(0, 2, ads_missile_rapidity);
auto r = std::make_unique<ads_object> (oTurretMissile, t->owner, S0, rsrc_color[rtAmmo]);
r->shape = &shape_missile;
r->life_start = 0; r->life_end = M_PI;
ci_at[t->owner].rocks.emplace_back(std::move(r));
// println(hlog, "OK");
}
nts.index = tv0;
t->turret_states[ctime] = nts;
if(nts.err < 0.01) angle_at_time = nts.angle;
}
void handle_crashes() {
if(paused) return;
if(mtwisted) {
if(!currentmap) { println(hlog, "no currentmap!"); return; }
PIU({ handle_crashes(); });
return;
}
vector<ads_object*> missiles;
vector<ads_object*> rocks;
vector<ads_object*> resources;
vector<ads_object*> turrets;
for(auto m: displayed) {
if(m->type == oMissile)
missiles.push_back(m);
if(m->type == oRock || m->type == oTurret)
rocks.push_back(m);
if(m->type == oTurret)
turrets.push_back(m);
if(m->type == oResource)
resources.push_back(m);
}
hybrid::in_underlying_geometry([&] {
for(auto m: missiles) {
hyperpoint h = kleinize(m->pt_main.h);
for(auto r: rocks) {
if(pointcrash(h, r->pts)) {
m->life_end = m->pt_main.shift;
r->life_end = r->pt_main.shift;
hybrid::in_actual([&] {
gen_particles(rpoisson(crash_particle_qty), m->owner, m->at * ads_matrix(Id, m->life_end), missile_color, crash_particle_rapidity, crash_particle_life);
gen_particles(rpoisson(crash_particle_qty), r->owner, r->at * ads_matrix(Id, r->life_end), r->col, crash_particle_rapidity, crash_particle_life);
if(r->type != oTurret) gen_resource(r->owner, r->at * ads_matrix(Id, r->life_end), r->resource, r->expire);
playSound(nullptr, "hit-crush3");
});
}
}
}
if(!game_over) for(int i=0; i<isize(shape_ship); i+=2) {
hyperpoint h = spin(ang*degree) * hpxyz(shape_ship[i] * ads_scale, shape_ship[i+1] * ads_scale, 1);
for(auto r: rocks) {
if(pointcrash(h, r->pts)) ads_crash_ship();
}
for(auto r: resources) {
if(pointcrash(h, r->pts)) {
r->life_end = r->pt_main.shift;
gain_resource(r->resource);
}
}
hyperpoint h1 = normalize(h);
bool crashed = false;
hybrid::in_actual([&] {
swap(h1[2], h1[3]);
ads_point rel = ads_inverse(current * vctrV) * ads_point(h1, 0);
cell *c = hybrid::get_at(vctr, 0);
virtualRebase(c, rel.h);
optimize_shift(rel);
auto w = hybrid::get_where(c);
auto& ci = ci_at[w.first];
ld t = rel.shift + w.second * cgi.plevel;
if(ci.type == wtDestructible || ci.type == wtSolid || (ci.type == wtGate && (int(floor(t)) & 3) == 0)) {
if(!crashed && ship_pt > invincibility_pt) println(hlog, "crashed at t = ", t / TAU, " shift = ", rel.shift/TAU, " sec = ", w.second*cgi.plevel/TAU);
crashed = true;
}
});
if(crashed) ads_crash_ship();
}
});
}
}}