diff --git a/graph.cpp b/graph.cpp index 9515bc03..e202f4c3 100644 --- a/graph.cpp +++ b/graph.cpp @@ -33,7 +33,7 @@ ld fractick(int period, ld phase = 0) { return t; } -ld sintick(int period, ld phase = 0) { +ld sintick(int period, ld phase) { return sin(ptick(period, phase)); } @@ -5136,15 +5136,7 @@ void drawMarkers() { } #if CAP_RACING - if(racing::on && racing::player_relative) { - using namespace racing; - cell *goal = NULL; - for(cell *c: track) if(inscreenrange(c)) goal = c; - hyperpoint H = tC0(ggmatrix(goal)); - queuechr(H, 2*vid.fsize, 'X', 0x10100 * int(128 + 100 * sintick(150))); - queuestr(H, vid.fsize, its(celldistance(cwt.at, track.back())), 0x10101 * int(128 - 100 * sintick(150))); - addauraspecial(H, 0x10100, 0); - } + racing::markers(); #endif if(lmouseover && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON) { diff --git a/hud.cpp b/hud.cpp index 9cabc9b0..9b3d1585 100644 --- a/hud.cpp +++ b/hud.cpp @@ -343,22 +343,6 @@ void drawMobileArrow(int i) { bool nofps = false; -string racetimeformat(int t) { - string times = ""; - int digits = 0; - bool minus = (t < 0); - if(t < 0) t = -t; - while(t || digits < 6) { - int mby = (digits == 5 ? 6 : 10); - times = char('0'+(t%mby)) + times; - t /= mby; digits++; - if(digits == 3) times = "." + times; - if(digits == 5) times = ":" + times; - } - if(minus) times = "-" + times; - return times; - } - void drawStats() { if(nohud || vid.stereo_mode == sLR) return; if(callhandlers(false, hooks_prestats)) return; @@ -501,21 +485,29 @@ void drawStats() { string s0; if(racing::on) { #if CAP_RACING + using namespace racing; color_t col; - if(ticks >= racing::race_start_tick) + if(ticks >= race_start_tick) col = 0x00FF00; - else if(ticks >= racing::race_start_tick - 2000) + else if(ticks >= race_start_tick - 2000) col = 0xFFFF00; else col = 0xFF0000; - for(int i=0; i x(vid.fsize, vid.fsize*2); - if(displayButtonS(vid.xres - 8, vid.fsize, racetimeformat(ticks - racing::race_start_tick), col, 16, vid.fsize)); - for(int i=0; i> 8), 16, vid.fsize)); + if(displayButtonS(vid.xres - 8, vid.fsize, racetimeformat(ticks - race_start_tick), col, 16, vid.fsize)); + + for(int i=0; i> 8), 16, vid.fsize)); + } + else { + int comp = get_percentage(i); + if(displayButtonS(vid.xres - 8, vid.fsize * (3+i), its(comp) + "%", (getcs().uicolor >> 8), 16, vid.fsize)); + } } #endif } diff --git a/hyper.h b/hyper.h index 4ca061c8..16ae61a7 100644 --- a/hyper.h +++ b/hyper.h @@ -4674,6 +4674,7 @@ hyperpoint nearcorner(cell *c, int i); extern bool showquotients; bool do_draw(cell *c, const transmatrix& T); +ld sintick(int period, ld phase = 0); #if CAP_RACING namespace racing { @@ -4682,11 +4683,13 @@ namespace racing { void show(); void prepare_subscreens(); extern vector track; - extern map > trackstage; extern int current_player; extern vector race_lands; extern string track_code; extern int race_start_tick, race_finish_tick[MAXPLAYER]; + void race_won(); + void apply_seed(); + string racetimeformat(int t); } bool subscreen_split(reaction_t for_each_subscreen); diff --git a/racing.cpp b/racing.cpp index fdeb2853..6dd252e1 100644 --- a/racing.cpp +++ b/racing.cpp @@ -13,26 +13,46 @@ bool on; bool player_relative = false; bool track_ready; -static const int LENGTH = 250; +static const int LENGTH = 50; static const int TWIDTH = 6; +static const int DROP = 1; +struct race_cellinfo { + cell *c; + int from_track; + int completion; + }; + +vector rti; vector track; -map > trackstage; +map rti_id; string track_code = "OFFICIAL"; +void apply_seed() { + int s = 0; + for(char c: track_code) s = 713 * s + c; + shrand(s); + } + int race_start_tick, race_finish_tick[MAXPLAYER]; struct ghostmoment { int step; - cell *where; + int where_id; transmatrix T; ld footphase; }; -map > > race_ghosts; +struct ghost { + charstyle cs; + int result; + vector history; + }; -vector current_history[MAXPLAYER]; +map, map > > race_ghosts; + +array, MAXPLAYER> current_history; void fix_cave(cell *c) { int v = 0; @@ -71,6 +91,15 @@ int trackval(cell *c) { return v + bonus; } +void tie_info(cell *c, int from_track, int comp) { + rti_id[c] = isize(rti); + rti.emplace_back(race_cellinfo{c, from_track, comp}); + } + +race_cellinfo& get_info(cell *c) { + return rti[rti_id.at(c)]; + } + void generate_track() { track.clear(); @@ -211,18 +240,18 @@ void generate_track() { if(1) { manual_celllister cl; - for(int i=0; iitem = itNone; @@ -232,17 +261,17 @@ void generate_track() { if(c->monst == moIvyHead) c->monst = moIvyWait; if(inmirror(c->land)) ; - else if(p.first == TWIDTH) { + else if(p.from_track == TWIDTH) { killMonster(c, moNone, 0); c->wall = waBarrier; c->land = laBarrier; } - else if(p.first > TWIDTH) { + else if(p.from_track > TWIDTH) { killMonster(c, moNone, 0); c->land = laMemory; c->wall = waChasm; } - if(p.second >= win && p.first < TWIDTH) { + if(p.completion >= win && p.from_track < TWIDTH) { c->wall = hrand(2) ? waMirror : waCloud; killMonster(c, moNone, 0); } @@ -252,7 +281,7 @@ void generate_track() { for(int i=0; iland == laCaves) { @@ -260,11 +289,11 @@ void generate_track() { while(true) { unsigned hashval = 7; int id = 0; - for(auto s: trackstage) { - fix_cave(s.first); - if(s.first->wall == waCavewall) + for(auto s: rti) { + fix_cave(s.c); + if(s.c->wall == waCavewall) hashval = (3+2*(id++)) * hashval + 1; - if(s.first->wall == waCavefloor) + if(s.c->wall == waCavefloor) hashval = (3+2*(id++)) * hashval + 2; } printf("hashval = %x id = %d\n", hashval, id); @@ -324,15 +353,14 @@ void set_view() { shmup::monster *who = shmup::pc[current_player]; - safety = true; - if(!inrec) current_history[current_player].emplace_back(ghostmoment{ticks - race_start_tick, who->base, who->at, who->footphase}); + if(!inrec) current_history[current_player].emplace_back(ghostmoment{ticks - race_start_tick, rti_id[who->base], who->at, who->footphase}); transmatrix at = ggmatrix(who->base) * who->at; if(racing::player_relative) View = spin(race_angle * degree) * inverse(at) * View; else { - int z = racing::trackstage[who->base].second; + int z = get_info(who->base).completion; int steps = euclid ? 1000 : 20; cell *c1 = racing::track[max(z-steps, 0)]; cell *c2 = racing::track[min(z+steps, isize(racing::track)-1)]; @@ -428,7 +456,9 @@ auto hook = + addHook(clearmemory, 0, []() { track_ready = false; track.clear(); - trackstage.clear(); + rti.clear(); + rti_id.clear(); + for(auto &ch: current_history) ch.clear(); }) // + addHook(hooks_handleKey, 120, akh); ; @@ -461,6 +491,47 @@ vector playercmds_race = { "", "change camera", "", "" }; +string racetimeformat(int t) { + string times = ""; + int digits = 0; + bool minus = (t < 0); + if(t < 0) t = -t; + while(t || digits < 6) { + int mby = (digits == 5 ? 6 : 10); + times = char('0'+(t%mby)) + times; + t /= mby; digits++; + if(digits == 3) times = "." + times; + if(digits == 5) times = ":" + times; + } + if(minus) times = "-" + times; + return times; + } + +void track_chooser() { + dialog::init(XLAT("Racing")); + + char let = 'a'; + for(eLand l: race_lands) { + auto& gh = race_ghosts[{track_code, modecode()}] [l]; + const int LOST = 3600000; + int best = LOST; + for(auto& gc: gh) best = min(best, gc.result); + string s = (best == LOST) ? "" : racetimeformat(best); + dialog::addSelItem(XLAT1(linf[l].name), s, let++); + dialog::add_action([l] () { + stop_game(); + specialland = l; + racing::on = true; + shmup::on = true; + start_game(); + popScreenAll(); + }); + } + + dialog::addBack(); + dialog::display(); + } + struct race_configurer { int playercfg; @@ -521,6 +592,9 @@ struct race_configurer { } else dialog::addBreak(100); + dialog::addItem(XLAT("select the track and start!"), 's'); + dialog::add_action([] () { pushScreen(track_chooser); }); + dialog::addBack(); dialog::display(); @@ -556,6 +630,41 @@ void prepare_subscreens() { } } +void race_won() { + if(!race_finish_tick[current_player]) { + race_finish_tick[current_player] = ticks; + charstyle gcs = getcs(); + for(color_t *x: {&gcs.skincolor, &gcs.haircolor, &gcs.dresscolor, &gcs.swordcolor, &gcs.dresscolor2}) + part(*x, 0) >>= 1; + + race_ghosts[{track_code, modecode()}] [specialland].emplace_back(ghost{gcs, ticks - race_start_tick, current_history[current_player]}); + } + } + +void markers() { + if(racing::player_relative) { + using namespace racing; + cell *goal = NULL; + for(cell *c: track) if(inscreenrange(c)) goal = c; + hyperpoint H = tC0(ggmatrix(goal)); + queuechr(H, 2*vid.fsize, 'X', 0x10100 * int(128 + 100 * sintick(150))); + queuestr(H, vid.fsize, its(celldistance(cwt.at, track.back())), 0x10101 * int(128 - 100 * sintick(150))); + addauraspecial(H, 0x10100, 0); + } + for(auto& ghost: race_ghosts[{track_code, modecode()}][specialland]) { + auto p = std::find_if(ghost.history.begin(), ghost.history.end(), [] (const ghostmoment gm) { return gm.step > ticks - race_start_tick;} ); + if(p == ghost.history.end()) p--; + cell *w = rti[p->where_id].c; + if(!gmatrix.count(w)) continue; + dynamicval x(getcs(), ghost.cs); + drawMonsterType(moPlayer, w, gmatrix[w] * p->T, 0, p->footphase); + } + } + +int get_percentage(int i) { + return min(get_info(shmup::pc[i]->base).completion * 100 / (isize(track) - DROP), 100); + } + } bool subscreen_split(reaction_t what) { diff --git a/shmup.cpp b/shmup.cpp index 2f992aac..089e752c 100644 --- a/shmup.cpp +++ b/shmup.cpp @@ -1792,8 +1792,9 @@ void movePlayer(monster *m, int delta) { mirror::createHere(cw, cpid); mirror::breakMirror(cw, cpid); awakenMimics(m, c2); - if(racing::on && !racing::race_finish_tick[racing::current_player]) - racing::race_finish_tick[racing::current_player] = ticks; + #if CAP_RACING + if(racing::on) racing::race_won(); + #endif } if(c2->wall == waGlass && items[itOrbAether]) { items[itOrbAether] = 0; @@ -3225,6 +3226,7 @@ void init() { else pc[i]->at = spin(2*M_PI*i/players) * xpush(firstland == laMotion ? .5 : .3) * Id; pc[i]->base = cwt.at; + pc[i]->vel = 0; pc[i]->inBoat = (firstland == laCaribbean || firstland == laOcean || firstland == laLivefjord || firstland == laWhirlpool); pc[i]->store(); diff --git a/system.cpp b/system.cpp index 49c647c3..ddb9571e 100644 --- a/system.cpp +++ b/system.cpp @@ -35,7 +35,7 @@ bool verless(string v, string cmp) { bool do_use_special_land() { return !safety && - (peace::on || tactic::on || geometry || NONSTDVAR || randomPatternsMode || yendor::on); + (peace::on || tactic::on || geometry || NONSTDVAR || randomPatternsMode || yendor::on || racing::on); } hookset *hooks_welcome_message; @@ -113,6 +113,8 @@ void initgame() { firstland = safetyland; } + if(racing::on) racing::apply_seed(); + bool use_special_land = do_use_special_land(); if(use_special_land) firstland = specialland;