diff --git a/rogueviz/ads/ads-game.cpp b/rogueviz/ads/ads-game.cpp index 4555a13a..2548fc3f 100644 --- a/rogueviz/ads/ads-game.cpp +++ b/rogueviz/ads/ads-game.cpp @@ -2,7 +2,7 @@ /* Compile with mymake -O3 -rv rogueviz/ads/ads-game */ /* Best run with -ads-menu; more detailed options are available too */ -#define VER_RH "0.2" +#define VER_RH "1.0" #ifdef RELHELL @@ -42,6 +42,7 @@ namespace rogueviz { std::vector cleanup; } #include "views.cpp" #include "tour.cpp" #include "help.cpp" +#include "hiscore.cpp" namespace hr { @@ -84,6 +85,7 @@ void restart() { paused = false; ship_pt = 0; + init_gamedata(); } void run_ads_game_hooks() { @@ -216,6 +218,7 @@ void gamedata(hr::gamedata* gd) { } void set_config() { + load_hiscores(); lps_enable(&lps_relhell); // enable_canvas(); } @@ -329,6 +332,9 @@ auto shot_hooks = param_color(ghost_color, "color:ghost", true); + param_enum(hi_sort_by, "sort_by", 3) + ->editable({{"platinum", ""}, {"plasteel", ""}, {"uranium", ""}, {"total score", ""}}, "sort high scores by", 'h'); + rsrc_config(); }); diff --git a/rogueviz/ads/control.cpp b/rogueviz/ads/control.cpp index 923cb7f3..db1fbae5 100644 --- a/rogueviz/ads/control.cpp +++ b/rogueviz/ads/control.cpp @@ -223,7 +223,7 @@ bool ads_turn(int idelta) { pdata.oxygen -= pt; if(pdata.oxygen < 0) { pdata.oxygen = 0; - game_over = true; + game_over_with_message("suffocated"); } } else view_pt += tc; diff --git a/rogueviz/ads/ds-game.cpp b/rogueviz/ads/ds-game.cpp index 995a37c3..12497476 100644 --- a/rogueviz/ads/ds-game.cpp +++ b/rogueviz/ads/ds-game.cpp @@ -265,9 +265,9 @@ void ds_gen_particles(int qty, transmatrix from, ld shift, color_t col, ld spd, } } -void ds_crash_ship() { +void ds_crash_ship(const string &reason) { if(ship_pt < invincibility_pt) return; - common_crash_ship(); + common_crash_ship(reason); dynamicval g(geometry, gSpace435); ds_gen_particles(rpoisson(crash_particle_qty * 2), inverse(current.T) * spin(ang*degree), current.shift, rsrc_color[rtHull], crash_particle_rapidity, crash_particle_life); } @@ -292,6 +292,7 @@ void ds_handle_crashes() { for(auto r: drocks) { if(pointcrash(h, r->pts)) { m->life_end = m->pt_main.shift; + cur.rocks_hit++; if(r->type != oMainRock) r->life_end = r->pt_main.shift; dynamicval g(geometry, gSpace435); @@ -308,11 +309,12 @@ void ds_handle_crashes() { ld ds_scale = get_scale(); hyperpoint h = spin(ang*degree) * hpxyz(shape_ship[i] * ds_scale, shape_ship[i+1] * ds_scale, 1); for(auto r: drocks) { - if(pointcrash(h, r->pts)) ds_crash_ship(); + if(pointcrash(h, r->pts)) ds_crash_ship(r == main_rock ? "crashed into the home star" : "crashed into a star"); } for(auto r: dresources) { if(pointcrash(h, r->pts)) { r->life_end = r->pt_main.shift; + cur.rsrc_collected++; gain_resource(r->resource); } } @@ -408,7 +410,7 @@ bool ds_turn(int idelta) { pdata.oxygen -= pt; if(pdata.oxygen < 0) { pdata.oxygen = 0; - game_over = true; + game_over_with_message("suffocated"); } } else view_pt += tc; @@ -732,6 +734,7 @@ void ds_restart() { reset_textures(); pick_textures(); init_rsrc(); + init_gamedata(); } void run_ds_game_hooks() { diff --git a/rogueviz/ads/globals.cpp b/rogueviz/ads/globals.cpp index 70a57f57..3a540860 100644 --- a/rogueviz/ads/globals.cpp +++ b/rogueviz/ads/globals.cpp @@ -228,4 +228,23 @@ struct cellinfo { } }; +struct gamedata { + int gameid; + string myname; + string timerstart, timerend; + string variant; + string deathreason; + ld scores[8]; + int seconds; + int turrets_hit, rocks_hit, rsrc_collected; + }; + +extern gamedata cur; + +void init_gamedata(); +void game_over_with_message(const string& reason); +void save_to_hiscores(); +void hiscore_menu(); +void load_hiscores(); + }} diff --git a/rogueviz/ads/hiscore.cpp b/rogueviz/ads/hiscore.cpp new file mode 100644 index 00000000..924ccb59 --- /dev/null +++ b/rogueviz/ads/hiscore.cpp @@ -0,0 +1,154 @@ +namespace hr { + +namespace ads_game { + +gamedata cur; +vector allsaves; + +void init_gamedata() { + cur.rocks_hit = 0; + cur.rsrc_collected = 0; + cur.turrets_hit = 0; + cur.deathreason = "still alive"; + char buf[128]; strftime(buf, 128, "%c", localtime(&timerstart)); cur.timerstart = buf; + cur.myname = cur.timerstart; + } + +void fill_gamedata() { + time_t timer = time(NULL); + char buf[128]; strftime(buf, 128, "%c", localtime(&timer)); cur.timerend = buf; + cur.seconds = int(timer - timerstart); + for(int a=0; a<3; a++) cur.scores[a] = pdata.score[a]; + shstream hs; + print(hs, main_rock ? "2 " : "1 "); + print(hs, DS_(simspeed), " ", DS_(accel), " ", DS_(how_much_invincibility), " ", vid.creature_scale, " ", DS_(missile_rapidity)); + if(!main_rock) print(hs, " ", rock_max_rapidity, " ", rock_density, " ", max_gen_per_frame, " ", draw_per_frame); + + auto all = [&] (player_data& d) { + print(hs, " ", d.hitpoints, " ", d.ammo, " ", d.fuel, " ", d.oxygen); + }; + all(DS_(max_pdata)); + all(DS_(tank_pdata)); + + cur.variant = hs.s; + } + +unsigned myhash(const string& s) { + std::size_t seed = s.size(); + for(auto& i : s) { + seed ^= i + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; + } + +void save(const gamedata& sd) { + #if CAP_SAVE + fhstream f("relhell.save", "at"); + println(f, "Relative Hell ", VER_RH); + println(f, sd.myname); + println(f, sd.timerstart); + println(f, sd.timerend); + println(f, sd.variant); + println(f, sd.deathreason); + shstream hs; + print(hs, sd.scores[0], " ", sd.scores[1], " ", sd.scores[2], " ", sd.seconds, " ", sd.rocks_hit, " ", sd.rsrc_collected, " ", sd.turrets_hit); + println(f, hs.s); + println(f, myhash(sd.variant + "#" + hs.s)); + #endif + } + +void game_over_with_message(const string& reason) { + if(game_over) return; + fill_gamedata(); + cur.deathreason = reason; + if(pdata.fuel <= 0) cur.deathreason += " while out of fuel"; + if(pdata.ammo <= 0) cur.deathreason += " while out of ammo"; + game_over = true; + } + +void save_to_hiscores() { + if(!main_rock && (pdata.score[0] + pdata.score[1] + pdata.score[2] == 0)) return; + if(main_rock && pdata.score[0] < 5) return; + save(cur); + allsaves.push_back(cur); + } + +void load_hiscores() { + allsaves.clear(); + fhstream f("relhell.save", "rt"); + if(!f.f) return; + string s; + while(!feof(f.f)) { + s = scanline_noblank(f); + if(s == "Relative Hell 1.0") { + gamedata gd; + gd.myname = scanline_noblank(f); + gd.timerstart = scanline_noblank(f); + gd.timerend = scanline_noblank(f); + gd.variant = scanline_noblank(f); + gd.deathreason = scanline_noblank(f); + sscanf(scanline_noblank(f).c_str(), "%lf%lf%lf%d%d%d%d", + &gd.scores[0], &gd.scores[1], &gd.scores[2], &gd.seconds, &gd.rocks_hit, &gd.rsrc_collected, &gd.turrets_hit); + allsaves.push_back(std::move(gd)); + } + } + } + +int hi_sort_by = 3; + +void hiscore_menu() { + emptyscreen(); + dialog::init("High scores"); + fill_gamedata(); + vector v; + for(auto& ad: allsaves) + if(ad.variant == cur.variant) + v.push_back(&ad); + v.push_back(&cur); + + auto getval = [] (gamedata *g) { + if(!main_rock) return g->scores[0]; + if(hi_sort_by == 3) return g->scores[0] + g->scores[1] + g->scores[2]; + return g->scores[hi_sort_by]; + }; + + sort(v.begin(), v.end(), [&] (gamedata* g1, gamedata* g2) { return getval(g1) > getval(g2); }); + dialog::start_list(900, 900, '1'); + for(auto ad: v) { + dialog::addSelItem(ad->myname + " (" + ad->deathreason + ")", main_rock ? fts(getval(ad)) : its(getval(ad)), dialog::list_fake_key++); + dialog::add_action_push([ad] { + emptyscreen(); + dialog::init(ad->myname); + if(!main_rock) { + dialog::addSelItem("platinum", its(ad->scores[0]), 'p'); + dialog::addSelItem("plasteel", its(ad->scores[1]), 'l'); + dialog::addSelItem("uranium", its(ad->scores[2]), 'u'); + } + else { + dialog::addSelItem("score", fts(ad->scores[0]), 's'); + } + + if(!main_rock) dialog::addSelItem("stars hit", its(ad->rocks_hit), 'h'); + if(main_rock) dialog::addSelItem("rocks hit", its(ad->rocks_hit), 'r'); + if(!main_rock) dialog::addSelItem("turrets hit", its(ad->turrets_hit), 't'); + dialog::addSelItem("resources collected", its(ad->rsrc_collected), 'c'); + + dialog::addSelItem("played", ad->timerstart, 'T'); + dialog::addSelItem("seconds", its(ad->seconds), 'S'); + + dialog::addBack(); + dialog::display(); + }); + } + + dialog::end_list(); + + if(!main_rock) add_edit(hi_sort_by); + dialog::addSelItem("name", cur.myname, 'n'); + dialog::add_action([] { dialog::edit_string(cur.myname, "enter your name", ""); }); + dialog::addBack(); + dialog::display(); + } + +} +} diff --git a/rogueviz/ads/map.cpp b/rogueviz/ads/map.cpp index cd155390..926583ce 100644 --- a/rogueviz/ads/map.cpp +++ b/rogueviz/ads/map.cpp @@ -281,16 +281,16 @@ bool pointcrash(hyperpoint h, const vector& vf) { return winding & 1; } -void common_crash_ship() { +void common_crash_ship(const string &reason) { invincibility_pt = ship_pt + DS_(how_much_invincibility); pdata.hitpoints--; - if(pdata.hitpoints <= 0) game_over = true; + if(pdata.hitpoints <= 0) game_over_with_message(reason); playSound(nullptr, "explosion"); } -void ads_crash_ship() { +void ads_crash_ship(const string &reason) { if(ship_pt < invincibility_pt) return; - common_crash_ship(); + common_crash_ship(reason); 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); }); @@ -458,6 +458,8 @@ void handle_crashes() { if(pointcrash(h, r->pts)) { m->life_end = m->pt_main.shift; r->life_end = r->pt_main.shift; + if(r->type == oTurret) cur.turrets_hit++; + if(r->type == oRock) cur.rocks_hit++; 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); @@ -471,23 +473,24 @@ void handle_crashes() { ld ads_scale = get_scale(); 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(); + if(pointcrash(h, r->pts)) ads_crash_ship(r->type == oTurret ? "crashed into a turret" : "crashed into an asteroid"); } for(auto r: enemy_missiles) { if(pointcrash(h, r->pts)) { r->life_end = r->pt_main.shift; - ads_crash_ship(); + ads_crash_ship("hit by a missile"); } } for(auto r: resources) { if(pointcrash(h, r->pts)) { r->life_end = r->pt_main.shift; + cur.rsrc_collected++; gain_resource(r->resource); } } hyperpoint h1 = normalize(h); - bool crashed = false; + bool crashed = false; string crash_what; hybrid::in_actual([&] { h1[3] = h1[2]; h1[2] = 0; ads_point rel = ads_inverse(current * vctrV) * ads_point(h1, 0); @@ -500,10 +503,13 @@ void handle_crashes() { if(ci.type == wtDestructible || ci.type == wtSolid || (ci.type == wtGate && (int(floor(t)) & 3) == 0) || ci.type == wtBarrier) { 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(ci.type == wtGate) crash_what = "crashed into a gate"; + if(ci.type == wtDestructible || ci.type == wtSolid) crash_what = "crashed into a wall"; + if(ci.type == wtBarrier) crash_what = "hit a Great Wall"; } }); - if(crashed) ads_crash_ship(); + if(crashed) ads_crash_ship(crash_what); } }); } diff --git a/rogueviz/ads/menu.cpp b/rogueviz/ads/menu.cpp index 78e6d02c..42f0d356 100644 --- a/rogueviz/ads/menu.cpp +++ b/rogueviz/ads/menu.cpp @@ -171,10 +171,15 @@ void game_menu() { dialog::addItem(XLAT("restart game"), 'r'); dialog::add_action([] { + game_over_with_message("restarted"); + save_to_hiscores(); if(main_rock) ds_restart(); else restart(); popScreen(); }); + dialog::addItem(XLAT("highscores"), 'h'); + dialog::add_action_push(hiscore_menu); + dialog::addItem(XLAT("refill cheat"), 'R'); dialog::add_action([] { init_rsrc(); popScreen(); }); @@ -209,6 +214,8 @@ void game_menu() { dialog::addItem("quit the game", 'q'); dialog::add_action([] { + game_over_with_message("quit"); + save_to_hiscores(); quitmainloop = true; });