diff --git a/mapeditor.cpp b/mapeditor.cpp index 761db819..20f2053b 100644 --- a/mapeditor.cpp +++ b/mapeditor.cpp @@ -819,6 +819,15 @@ EX namespace mapstream { } #endif + if(f.vernum >= 0xA912) { + f.write(racing::on); + if(racing::on) { + f.write(isize(racing::track)); + for(auto& t: racing::track) f.write(cellids[t]); + racing::save_ghosts(f); + } + } + callhooks(hooks_savemap, f); f.write(0); @@ -1062,6 +1071,20 @@ EX namespace mapstream { } } #endif + + if(f.vernum >= 0xA912) { + f.read(racing::on); + if(racing::on) { + if(!shmup::on) { + shmup::on = true; + shmup::init(); + } + racing::track.resize(f.get()); + for(auto& t: racing::track) t = cellbyid[f.get()]; + racing::load_ghosts(f); + racing::configure_track(false); + } + } if(f.vernum >= 0xA848) { int i; diff --git a/racing.cpp b/racing.cpp index 5166dbe2..3a65a57d 100644 --- a/racing.cpp +++ b/racing.cpp @@ -20,8 +20,7 @@ EX bool on; EX bool player_relative = false; EX bool standard_centering = false; EX bool track_ready; - -bool official_race = false; +EX bool official_race = false; int TWIDTH; @@ -46,18 +45,10 @@ map rti_id; EX int trophy[MAXPLAYER]; -EX string track_code = "OFFICIAL"; - transmatrix straight; int race_try; -EX void apply_seed() { - int s = race_try; - for(char c: track_code) s = 713 * s + c; - shrand(s); - } - EX int race_start_tick, race_finish_tick[MAXPLAYER]; typedef unsigned char uchar; @@ -71,7 +62,8 @@ transmatrix spin_uchar(uchar x) { return spin(uchar_to_frac(x) * 2 * M_PI); } static const ld distance_multiplier = 4; struct ghostmoment { - int step, where_id; + int step; + cell *where_cell; uchar alpha, distance, beta, footphase; }; @@ -83,54 +75,24 @@ struct ghost { vector history; }; -typedef map> raceset; -map, raceset> race_ghosts; - -map, raceset> official_race_ghosts; - -raceset& ghostset() { return race_ghosts[make_pair(track_code, modecode())]; } -raceset& oghostset() { return official_race_ghosts[make_pair(track_code, modecode())]; } - -int get_score_in_land(eLand l) { - auto& gh = ghostset(); - if(!gh.count(l)) return 0; - auto& v = gh[l]; - if(!isize(v)) return 0; - return v[0].result; - } +vector ghostset; array, MAXPLAYER> current_history; +EX map best_scores; + string ghost_prefix = "default"; #if CAP_FILES -string ghost_filename(string seed, modecode_t mcode) { - if(ghost_prefix == "default") { - #ifdef FHS - if(getenv("HOME")) { - string s = getenv("HOME"); - mkdir((s + "/.hyperrogue").c_str(), 0755); - mkdir((s + "/.hyperrogue/racing").c_str(), 0755); - ghost_prefix = s + "/.hyperrogue/racing/"; - } - #else - #if WINDOWS - mkdir("racing"); - #else - mkdir("racing", 0755); - #endif - ghost_prefix = "racing/"; - #endif - } - return ghost_prefix + seed + "-" + itsh(mcode) + ".data"; - } - void hread(hstream& hs, ghostmoment& m) { - hread(hs, m.step, m.where_id, m.alpha, m.distance, m.beta, m.footphase); + int id; + hread(hs, m.step, id, m.alpha, m.distance, m.beta, m.footphase); + m.where_cell = mapstream::cellbyid[id]; } void hwrite(hstream& hs, const ghostmoment& m) { - hwrite(hs, m.step, m.where_id, m.alpha, m.distance, m.beta, m.footphase); + int id = mapstream::cellids[m.where_cell]; + hwrite(hs, m.step, id, m.alpha, m.distance, m.beta, m.footphase); } void hread(hstream& hs, ghost& gh) { @@ -141,37 +103,18 @@ void hwrite(hstream& hs, const ghost& gh) { hwrite(hs, gh.cs, gh.result, gh.timestamp, gh.checksum, gh.history); } -bool read_ghosts(string seed, modecode_t mcode) { - - if(seed == "OFFICIAL" && mcode == 2) { - fhstream f("officials.data", "rb"); - if(f.f) { - hread(f, f.vernum); - hread(f, oghostset()); - } - } - - string fname = ghost_filename(seed, mcode); - println(hlog, "trying to read ghosts from: ", fname); - fhstream f(fname, "rb"); - if(!f.f) return false; - hread(f, f.vernum); - if(f.vernum <= 0xA600) return true; // scores removed due to the possibility of cheating - hread(f, ghostset()); - return true; +EX void save_ghosts(hstream& f) { + hwrite(f, ghostset); } -void write_ghosts(string seed, int mcode) { - fhstream f; - f.f = fopen(ghost_filename(seed, mcode).c_str(), "wb"); - if(!f.f) throw hstream_exception(); // ("failed to write the ghost file"); - hwrite(f, f.vernum); - hwrite(f, ghostset()); +EX void load_ghosts(hstream& f) { + hread(f, ghostset); } + #endif shiftmatrix get_ghostmoment_matrix(ghostmoment& p) { - cell *w = rti[p.where_id].c; + cell *w = p.where_cell; transmatrix T = spin_uchar(p.alpha) * xpush(uchar_to_frac(p.distance) * distance_multiplier) * spin_uchar(p.beta); return gmatrix[w] * T; } @@ -241,8 +184,6 @@ race_cellinfo& get_info(cell *c) { return rti[rti_id.at(c)]; } -int race_checksum; - ld start_line_width; struct hr_track_failure : hr_exception {}; @@ -427,16 +368,13 @@ EX bool bounded_track; EX void generate_track() { + official_race = false; + ghostset.clear(); TWIDTH = getDistLimit() - 1; if(TWIDTH == 1) TWIDTH = 2; if(sl2) TWIDTH = 2; TWIDTH += race_try / 8; - #if CAP_FILES - if(ghostset().empty()) - read_ghosts(track_code, modecode()); - #endif - track.clear(); /* @@ -520,8 +458,24 @@ EX void generate_track() { if(WDIM == 3) dl = 7 - TWIDTH; for(cell *c:track) setdist(c, dl, NULL); + configure_track(true); + } + +EX vector reachable_goals; + +EX void restore_goals() { + for(auto c: reachable_goals) + c->wall = pick(waCloud, waMirror); + } + +EX void configure_track(bool gen) { + cell *s = track[0]; + if(true) { manual_celllister cl; + + rti.clear(); + rti_id.clear(); for(int i=0; iitem = itNone; if(c->wall == waMirror || c->wall == waCloud) c->wall = waNone; if(!isIvy(c)) @@ -567,7 +521,7 @@ EX void generate_track() { for(const auto s: rti) byat[s.from_track]++; for(int a=0; a<16; a++) printf("%d: %d\n", a, byat[a]); - if(s->land == laCaves || (s->land == laCrossroads && !keep_to_crossroads())) { + if(gen) if(s->land == laCaves || (s->land == laCrossroads && !keep_to_crossroads())) { set hash; while(true) { unsigned hashval = 7; @@ -596,10 +550,11 @@ EX void generate_track() { hyperpoint h = straight * parabolic1(a) * C0; cell *at = s; virtualRebase(at, h); - if(!rti_id.count(at) || get_info(at).from_track >= TWIDTH) break; + if(!rti_id.count(at)) break; + if(at->land == laMemory) break; } - if(WDIM == 2 && !bounded_track) for(ld cleaner=0; cleanermonst == moIvyDead) s.c->monst = moNone; + if(gen) for(auto s: rti) if(s.c->monst == moIvyDead) s.c->monst = moNone; for(int i=0; i forbidden; - for(auto& ghost: ghostset()[specialland]) - forbidden.push_back(get_ghostmoment_matrix(ghost.history[0])); - for(auto& ghost: oghostset()[specialland]) - forbidden.push_back(get_ghostmoment_matrix(ghost.history[0])); - - for(int i=0; iat = Id; // straight * cspin(0, 2, rand() % 360) * cspin(1, 2, rand() % 360); - else - who->at = straight * parabolic1(start_line_width * (rand() % 20000 - 10000) / 40000) * spin(rand() % 360); - who->base = s; - bool ok = true; - for(const shiftmatrix& t: forbidden) if(hdist(t*C0, shiftless(who->at) * C0) < 10. / (j+10)) ok = false; - if(ok) break; - } - virtualRebase(who); - } - - if(bounded_track) track.back()->wall = waCloud; + if(gen && bounded_track) track.back()->wall = waCloud; if(1) { manual_celllister cl; cl.add(s); - bool goal = false; for(int i=0; iwall, waCloud, waMirror)) goal = true; + if(among(c2->wall, waCloud, waMirror)) reachable_goals.push_back(c2); if(passable(c2, c, P_ISPLAYER)) cl.add(c2); } } - if(!goal) { + if(reachable_goals.empty()) { printf("error: goal unreachable\n"); gamegen_failure = true; race_try++; @@ -729,24 +658,40 @@ EX void generate_track() { */ track_ready = true; - race_checksum = hrand(1000000); + reset_race(); + } + +EX void reset_race() { + cell *s = track[0]; + + vector forbidden; + for(auto& ghost: ghostset) + forbidden.push_back(get_ghostmoment_matrix(ghost.history[0])); + race_start_tick = 0; for(int i=0; iat = Id; // straight * cspin(0, 2, rand() % 360) * cspin(1, 2, rand() % 360); + else + who->at = straight * parabolic1(start_line_width * (rand() % 20000 - 10000) / 40000) * spin(rand() % 360); + who->base = s; + bool ok = true; + for(const shiftmatrix& t: forbidden) if(hdist(t*C0, shiftless(who->at) * C0) < 10. / (j+10)) ok = false; + if(ok) break; } + virtualRebase(who); } + } bool inrec = false; @@ -789,7 +734,7 @@ EX bool set_view() { ld alpha = -atan2(T * C0); ld distance = hdist0(T * C0); ld beta = -atan2(xpush(-distance) * spin(-alpha) * T * Cx1); - current_history[multi::cpid].emplace_back(ghostmoment{ticks - race_start_tick, rti_id[who->base], + current_history[multi::cpid].emplace_back(ghostmoment{ticks - race_start_tick, who->base, angle_to_uchar(alpha), frac_to_uchar(distance / distance_multiplier), angle_to_uchar(beta), @@ -862,6 +807,10 @@ int readArgs() { stop_game(); switch_game_mode(rg::racing); } + else if(argis("-racing-official")) { + PHASEFROM(2); + load_official_track(); + } else if(argis("-rsc")) { standard_centering = true; } @@ -890,6 +839,7 @@ auto hook = track.clear(); rti.clear(); rti_id.clear(); + reachable_goals.clear(); for(auto &ch: current_history) ch.clear(); }) + addHook(hooks_configfile, 100, [] { @@ -949,10 +899,27 @@ EX string racetimeformat(int t) { extern int playercfg; -void track_chooser(string new_track) { +EX void load_official_track() { + fhstream f("officials.data", "rb"); + hread(f, f.vernum); + map tracks; + hread(f, tracks); + eLand l = specialland; + if(!tracks.count(l)) { + println(hlog, "ERROR: no official track in the database"); + throw hstream_exception(); + } + string s = decompress_string(tracks[l]); + shstream sf(s); + mapstream::loadMap(sf); + cheater = autocheat = 0; + official_race = true; + } + +void track_chooser(bool official) { cmode = 0; gamescreen(2); - dialog::init(XLAT("Racing")); + dialog::init(XLAT(official ? "Official tracks" : "Generate a racing track")); map landmap; @@ -961,25 +928,23 @@ void track_chooser(string new_track) { char let = 'a'; for(eLand l: race_lands) { - auto& ghs = race_ghosts[make_pair(new_track, modecode())]; - const int LOST = 3600000; - int best = LOST; - if(ghs.count(l)) { - auto& gh = ghs[l]; - for(auto& gc: gh) best = min(best, gc.result); - } - string s = (best == LOST) ? "" : racetimeformat(best); landmap[let] = l; + string s = ""; + if(best_scores.count(l)) + s = racetimeformat(best_scores[l]); dialog::addSelItem(XLAT1(linf[l].name), s, let++); - dialog::add_action([l, new_track] () { + dialog::add_action([l, official] () { stop_game(); multi::players = playercfg; - if(!racing::on) switch_game_mode(rg::racing); - track_code = new_track; specialland = l; - // because of an earlier issue, some races in the Official track start with race_try = 1 - race_try = among(l, laCaves, laWildWest, laHell, laTerracotta, laElementalWall, laDryForest, laDeadCaves) ? 1 : 0; - start_game(); + if(official) { + racing::on = false; + load_official_track(); + } + else { + if(!racing::on) switch_game_mode(rg::racing); + start_game(); + } popScreenAll(); }); } @@ -987,8 +952,8 @@ void track_chooser(string new_track) { dialog::addBack(); dialog::display(); - if(landmap.count(getcstat) && new_track == "OFFICIAL" && modecode() == 2) - displayScore(landmap[getcstat]); + if(landmap.count(getcstat) && official) + displayScore(landmap[getcstat]); } void race_projection() { @@ -1090,32 +1055,11 @@ void race_projection() { } int playercfg; - bool editing_track; - string new_track; /* struct race_configurer { */ - void set_race_configurer() { editing_track = false; new_track = track_code; playercfg = multi::players; } + void set_race_configurer() { playercfg = multi::players; } - static string random_track_name() { - string s = ""; - for(int a = 0; a < 4; a++) { - int u = rand() % 2; - if(u == 0) - s += "AEIOUY" [ rand() % 6]; - s += "BCDFGHJKLMNPRSTVWZ" [ rand() % 18]; - if(u == 1) - s += "AEIOUY" [ rand() % 6]; - } - return s; - } - - static string racecheck(int sym, int uni) { - if(uni >= 'A' && uni <= 'Z') return string("") + char(uni); - if(uni >= 'a' && uni <= 'z') return string("") + char(uni - 32); - return ""; - } - bool alternate = false; #if MAXMDIM >= 4 @@ -1133,7 +1077,6 @@ void race_projection() { if(!racing::on) switch_game_mode(rg::racing); racing::standard_centering = true; launcher(); - track_code = "OFFICIAL"; start_game(); popScreenAll(); }); @@ -1178,24 +1121,13 @@ void race_projection() { gamescreen(1); dialog::init(XLAT("Racing")); + + dialog::addItem(XLAT("play on an official track"), 'O'); + dialog::add_action_push([/*this*/] () { track_chooser(true); }); + + dialog::addItem(XLAT("generate a random track"), 's'); + dialog::add_action_push([/*this*/] () { track_chooser(false); }); - if(false) - dialog::addInfo(XLAT("Racing available only in unbounded worlds."), 0xFF0000); - else { - dialog::addItem(XLAT("select the track and start!"), 's'); - dialog::add_action([/*this*/] () { - dynamicval so(shmup::on, true); - dynamicval ro(racing::on, true); - #if CAP_FILES - if(race_ghosts[make_pair(new_track, modecode())].empty()) - read_ghosts(new_track, modecode()); - else - println(hlog, "known ghosts: ", isize(race_ghosts[make_pair(new_track, modecode())])); - #endif - pushScreen([/*this*/] () { track_chooser(new_track); }); - }); - } - dialog::addBreak(100); if(WDIM == 3) { @@ -1229,18 +1161,6 @@ void race_projection() { dialog::addBreak(100); - dialog::addSelItem(XLAT("track seed"), editing_track ? dialog::view_edited_string() : new_track, '/'); - dialog::add_action([/*this*/] () { - editing_track = !editing_track; - if(editing_track) dialog::start_editing(new_track); - }); - dialog::addItem(XLAT("play the official seed"), 'o'); - dialog::add_action([/*this*/] () { new_track = "OFFICIAL"; }); - dialog::addItem(XLAT("play a random seed"), 'r'); - dialog::add_action([/*this*/] () { new_track = random_track_name(); }); - - dialog::addBreak(100); - dialog::addSelItem(XLAT("best scores to show as ghosts"), its(ghosts_to_show), 'c'); dialog::add_action([]() { dialog::editNumber(ghosts_to_show, 0, 100, 1, 5, "best scores to show as ghosts", ""); }); @@ -1268,12 +1188,8 @@ void race_projection() { dialog::display(); keyhandler = [/*this*/] (int sym, int uni) { - if(editing_track) { - if(sym == SDLK_RETURN) sym = uni = '/'; - if(dialog::handle_edit_string(sym, uni, racecheck)) return; - } dialog::handleNavigation(sym, uni); - if(doexiton(sym, uni)) { if(editing_track) editing_track = false; else popScreen(); } + if(doexiton(sym, uni)) popScreen(); }; } @@ -1333,8 +1249,7 @@ EX void race_won() { int losers = 0; int place = 1; for(int i=0; i>= 2; } - auto &subtrack = ghostset() [specialland]; - - int ngh = 0; - for(int i=0; i ghosts_to_save && ghosts_to_save > 0) + subtrack.resize(ghosts_to_save); } - subtrack.resize(ngh); - - subtrack.emplace_back(ghost{gcs, result, race_checksum, time(NULL), current_history[multi::cpid]}); - sort(subtrack.begin(), subtrack.end(), [] (const ghost &g1, const ghost &g2) { return g1.result < g2.result; }); - if(isize(subtrack) > ghosts_to_save && ghosts_to_save > 0) - subtrack.resize(ghosts_to_save); - #if CAP_FILES - if(ghosts_to_save > 0) - write_ghosts(track_code, modecode()); - #endif - if(official_race) uploadScore(); + if(official_race && !cheater) { + if(!best_scores.count(specialland)) + best_scores[specialland] = result; + best_scores[specialland] = min(best_scores[specialland], result); + saveStats(); + if(official_race) uploadScore(); + } } } @@ -1408,7 +1313,7 @@ ghostmoment& get_ghostmoment(ghost& ghost) { void draw_ghost(ghost& ghost) { auto& p = get_ghostmoment(ghost); - cell *w = rti[p.where_id].c; + cell *w = p.where_cell; if(!gmatrix.count(w)) return; draw_ghost_at(ghost, w, get_ghostmoment_matrix(p), p); } @@ -1428,8 +1333,7 @@ EX int get_percentage(int i) { void draw_ghost_state(ghost& ghost) { auto& p = get_ghostmoment(ghost); - if(p.where_id >= isize(rti)) return; - cell *w = rti[p.where_id].c; + cell *w = p.where_cell; ld result = ghost_finished(ghost) ? 100 : get_percentage(w); draw_ghost_at(ghost, w, racerel(result), p); } @@ -1450,8 +1354,7 @@ EX void drawStats() { } queuecurve(shiftless(Id), 0xFFFFFFFF, 0, PPR::ZERO); - for(auto& ghost: ghostset()[specialland]) draw_ghost_state(ghost); - for(auto& ghost: oghostset()[specialland]) draw_ghost_state(ghost); + for(auto& ghost: ghostset) draw_ghost_state(ghost); for(int i=0; i d(multi::cpid, i); @@ -1489,14 +1392,8 @@ EX void markers() { its(cd), 0x10101 * int(128 - 100 * sintick(150))); addauraspecial(H, 0xFFD500, 0); } - int ghosts_left = ghosts_to_show; - for(auto& ghost: ghostset()[specialland]) { - if(!ghosts_left) break; - ghosts_left--; - draw_ghost(ghost); - } - for(auto& ghost: oghostset()[specialland]) + for(auto& ghost: ghostset) draw_ghost(ghost); if(gmatrix.count(track[0])) {