Track best score/clock/time separately; add a summary row to the level browser
This commit is contained in:
parent
22f78f171c
commit
a91e1a831e
@ -2235,6 +2235,7 @@ export class Level extends LevelInterface {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME should probably remember tics here, not just seconds?
|
||||||
let time = Math.ceil((this.time_remaining ?? 0) / TICS_PER_SECOND);
|
let time = Math.ceil((this.time_remaining ?? 0) / TICS_PER_SECOND);
|
||||||
return {
|
return {
|
||||||
time: time,
|
time: time,
|
||||||
|
|||||||
149
js/main.js
149
js/main.js
@ -1656,35 +1656,54 @@ class Player extends PrimaryView {
|
|||||||
let level_index = level_number - 1;
|
let level_index = level_number - 1;
|
||||||
let scorecard = this.level.get_scorecard();
|
let scorecard = this.level.get_scorecard();
|
||||||
let savefile = this.conductor.current_pack_savefile;
|
let savefile = this.conductor.current_pack_savefile;
|
||||||
let old_scorecard;
|
let old_scorecard = savefile.scorecards[level_index];
|
||||||
if (! this.debug.enabled) {
|
if (! this.debug.enabled) {
|
||||||
if (! savefile.scorecards[level_index] ||
|
// Merge any improved stats into the old scorecard, and update the totals. All
|
||||||
savefile.scorecards[level_index].score < scorecard.score ||
|
// four of these stats are tracked independently: least aid, best score, highest
|
||||||
(savefile.scorecards[level_index].score === scorecard.score &&
|
// clock, lowest real time
|
||||||
savefile.scorecards[level_index].aid > scorecard.aid))
|
let new_scorecard = old_scorecard ? { ...old_scorecard } : {};
|
||||||
{
|
|
||||||
old_scorecard = savefile.scorecards[level_index];
|
|
||||||
|
|
||||||
// Adjust the total score
|
if (! old_scorecard) {
|
||||||
|
savefile.cleared_levels = (savefile.cleared_levels ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aid
|
||||||
|
if (! old_scorecard || scorecard.aid < old_scorecard.aid) {
|
||||||
|
new_scorecard.aid = scorecard.aid;
|
||||||
|
if (scorecard.aid === 0) {
|
||||||
|
savefile.aidless_levels = (savefile.aidless_levels ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score
|
||||||
|
if (! old_scorecard || scorecard.score > old_scorecard.score) {
|
||||||
|
new_scorecard.score = scorecard.score;
|
||||||
savefile.total_score = savefile.total_score ?? 0;
|
savefile.total_score = savefile.total_score ?? 0;
|
||||||
if (old_scorecard) {
|
if (old_scorecard) {
|
||||||
savefile.total_score -= old_scorecard.score;
|
savefile.total_score -= old_scorecard.score;
|
||||||
savefile.total_abstime -= old_scorecard.abstime;
|
|
||||||
}
|
}
|
||||||
savefile.total_score += scorecard.score;
|
savefile.total_score += scorecard.score;
|
||||||
savefile.total_abstime += scorecard.abstime;
|
|
||||||
|
|
||||||
if (! old_scorecard) {
|
|
||||||
savefile.cleared_levels = (savefile.cleared_levels ?? 0) + 1;
|
|
||||||
}
|
|
||||||
if ((! old_scorecard || old_scorecard.aid > 0) && scorecard.aid === 0) {
|
|
||||||
savefile.aidless_levels = (savefile.aidless_levels ?? 0) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
savefile.total_levels = this.conductor.stored_game.level_metadata.length;
|
|
||||||
savefile.scorecards[level_index] = scorecard;
|
|
||||||
this.conductor.save_savefile();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Real time
|
||||||
|
if (! old_scorecard || scorecard.abstime < old_scorecard.abstime) {
|
||||||
|
new_scorecard.abstime = scorecard.abstime;
|
||||||
|
savefile.total_abstime = savefile.total_abstime ?? 0;
|
||||||
|
if (old_scorecard) {
|
||||||
|
savefile.total_abstime -= old_scorecard.abstime;
|
||||||
|
}
|
||||||
|
savefile.total_abstime += scorecard.abstime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock time
|
||||||
|
if (! old_scorecard || scorecard.time > old_scorecard.time) {
|
||||||
|
new_scorecard.time = scorecard.time;
|
||||||
|
// There's no running total of clock times
|
||||||
|
}
|
||||||
|
|
||||||
|
savefile.total_levels = this.conductor.stored_game.level_metadata.length;
|
||||||
|
savefile.scorecards[level_index] = new_scorecard;
|
||||||
|
this.conductor.save_savefile();
|
||||||
}
|
}
|
||||||
|
|
||||||
overlay_reason = 'success';
|
overlay_reason = 'success';
|
||||||
@ -1733,9 +1752,9 @@ class Player extends PrimaryView {
|
|||||||
|
|
||||||
overlay_middle = mk('dl.score-chart',
|
overlay_middle = mk('dl.score-chart',
|
||||||
mk('dt', "base score"),
|
mk('dt', "base score"),
|
||||||
mk('dd', base),
|
mk('dd', base.toLocaleString()),
|
||||||
mk('dt', "time bonus"),
|
mk('dt', "time bonus"),
|
||||||
mk('dd', `+ ${time}`),
|
mk('dd', `+ ${time.toLocaleString()}`),
|
||||||
);
|
);
|
||||||
// It should be impossible to ever have a bonus and then drop back to 0 with CC2
|
// It should be impossible to ever have a bonus and then drop back to 0 with CC2
|
||||||
// rules; thieves can halve it, but the amount taken is rounded down.
|
// rules; thieves can halve it, but the amount taken is rounded down.
|
||||||
@ -1743,7 +1762,7 @@ class Player extends PrimaryView {
|
|||||||
if (this.level.bonus_points) {
|
if (this.level.bonus_points) {
|
||||||
overlay_middle.append(
|
overlay_middle.append(
|
||||||
mk('dt', "score bonus"),
|
mk('dt', "score bonus"),
|
||||||
mk('dd', `+ ${this.level.bonus_points}`),
|
mk('dd', `+ ${this.level.bonus_points.toLocaleString()}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1753,13 +1772,13 @@ class Player extends PrimaryView {
|
|||||||
// TODO show your time, bold time...?
|
// TODO show your time, bold time...?
|
||||||
overlay_middle.append(
|
overlay_middle.append(
|
||||||
mk('dt.-sum', "level score"),
|
mk('dt.-sum', "level score"),
|
||||||
mk('dd.-sum', `${scorecard.score} ${scorecard.aid === 0 ? '★' : ''}`),
|
mk('dd.-sum', `${scorecard.score.toLocaleString()} ${scorecard.aid === 0 ? '★' : ''}`),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (old_scorecard) {
|
if (old_scorecard && old_scorecard.score < scorecard.score) {
|
||||||
overlay_middle.append(
|
overlay_middle.append(
|
||||||
mk('dt', "improvement"),
|
mk('dt', "improvement"),
|
||||||
mk('dd', `+ ${scorecard.score - old_scorecard.score}`),
|
mk('dd', `+ ${(scorecard.score - old_scorecard.score).toLocaleString()}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1768,7 +1787,7 @@ class Player extends PrimaryView {
|
|||||||
|
|
||||||
overlay_middle.append(
|
overlay_middle.append(
|
||||||
mk('dt', "total score"),
|
mk('dt', "total score"),
|
||||||
mk('dd', savefile.total_score),
|
mk('dd', savefile.total_score.toLocaleString()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3269,37 +3288,67 @@ class LevelBrowserOverlay extends DialogOverlay {
|
|||||||
let thead = mk('thead', mk('tr',
|
let thead = mk('thead', mk('tr',
|
||||||
mk('th', ""),
|
mk('th', ""),
|
||||||
mk('th', "Level"),
|
mk('th', "Level"),
|
||||||
mk('th', "Your time"),
|
mk('th.-time', mk('abbr', {
|
||||||
mk('th', mk('abbr', {
|
title: "Time left on the clock when you finished; doesn't exit for untimed levels",
|
||||||
|
}, "Best clock")),
|
||||||
|
mk('th.-time', mk('abbr', {
|
||||||
title: "Actual time it took you to play the level, even on untimed levels, and ignoring any CC2 clock altering effects",
|
title: "Actual time it took you to play the level, even on untimed levels, and ignoring any CC2 clock altering effects",
|
||||||
}, "Real time")),
|
}, "Best real time")),
|
||||||
mk('th', "Your score"),
|
mk('th.-score', "Best score"),
|
||||||
|
mk('th'),
|
||||||
|
mk('th'),
|
||||||
));
|
));
|
||||||
let tbody = mk('tbody');
|
let tbody = mk('tbody');
|
||||||
let table = mk('table.level-browser', thead, tbody);
|
let table = mk('table.level-browser', thead, tbody);
|
||||||
this.main.append(table);
|
this.main.append(table);
|
||||||
let savefile = conductor.current_pack_savefile;
|
let savefile = conductor.current_pack_savefile;
|
||||||
// TODO if i stop eagerloading everything in a .DAT then this will not make sense any more
|
let total_abstime = 0, total_score = 0;
|
||||||
for (let [i, meta] of conductor.stored_game.level_metadata.entries()) {
|
for (let [i, meta] of conductor.stored_game.level_metadata.entries()) {
|
||||||
let scorecard = savefile.scorecards[i];
|
let scorecard = savefile.scorecards[i];
|
||||||
let score = "—", time = "—", abstime = "—";
|
let score = "—", time = "—", abstime = "—", aid = "";
|
||||||
|
let button;
|
||||||
if (scorecard) {
|
if (scorecard) {
|
||||||
score = scorecard.score.toLocaleString();
|
score = scorecard.score.toLocaleString();
|
||||||
if (scorecard.aid === 0) {
|
if (scorecard.aid === 0) {
|
||||||
score += '★';
|
aid = '★';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scorecard.time === 0) {
|
// 0 means untimed level
|
||||||
// This level is untimed
|
if (scorecard.time !== 0) {
|
||||||
time = "n/a";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
time = String(scorecard.time);
|
time = String(scorecard.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Express absolute time as mm:ss, with two decimals on the seconds (which should be
|
|
||||||
// able to exactly count a number of tics)
|
|
||||||
abstime = util.format_duration(scorecard.abstime / TICS_PER_SECOND, 2);
|
abstime = util.format_duration(scorecard.abstime / TICS_PER_SECOND, 2);
|
||||||
|
|
||||||
|
total_abstime += scorecard.abstime;
|
||||||
|
total_score += scorecard.score;
|
||||||
|
|
||||||
|
button = util.mk_button('forget', ev => {
|
||||||
|
new ConfirmOverlay(this.conductor, "Erase these records? This cannot be undone!", () => {
|
||||||
|
let savefile = this.conductor.current_pack_savefile;
|
||||||
|
let scorecard = savefile.scorecards[i];
|
||||||
|
if (! scorecard)
|
||||||
|
return;
|
||||||
|
|
||||||
|
savefile.total_abstime -= scorecard.abstime;
|
||||||
|
savefile.total_score -= scorecard.score;
|
||||||
|
savefile.cleared_levels -= 1;
|
||||||
|
if (savefile.aid === 0) {
|
||||||
|
savefile.aidless_levels -= 1;
|
||||||
|
}
|
||||||
|
savefile.scorecards[i] = null;
|
||||||
|
this.conductor.save_savefile();
|
||||||
|
|
||||||
|
let tr = ev.target.closest('table.level-browser tr');
|
||||||
|
for (let td of tr.querySelectorAll('td.-time, td.-score')) {
|
||||||
|
td.textContent = "—";
|
||||||
|
}
|
||||||
|
tr.querySelector('td.-aid').textContent = "";
|
||||||
|
tr.querySelector('td.-button').textContent = "";
|
||||||
|
// TODO update totals row? ugh
|
||||||
|
}).open();
|
||||||
|
ev.stopPropagation(); // don't trigger row click handler
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = meta.title;
|
let title = meta.title;
|
||||||
@ -3317,6 +3366,8 @@ class LevelBrowserOverlay extends DialogOverlay {
|
|||||||
mk('td.-time', time),
|
mk('td.-time', time),
|
||||||
mk('td.-time', abstime),
|
mk('td.-time', abstime),
|
||||||
mk('td.-score', score),
|
mk('td.-score', score),
|
||||||
|
mk('td.-aid', aid),
|
||||||
|
mk('td.-button', button ?? ''),
|
||||||
// TODO show your time? include 999 times for untimed levels (which i don't know at
|
// TODO show your time? include 999 times for untimed levels (which i don't know at
|
||||||
// this point whoops but i guess if the time is zero then that answers that)? show
|
// this point whoops but i guess if the time is zero then that answers that)? show
|
||||||
// your wallclock time also?
|
// your wallclock time also?
|
||||||
@ -3351,6 +3402,16 @@ class LevelBrowserOverlay extends DialogOverlay {
|
|||||||
|
|
||||||
this.tbody = tbody;
|
this.tbody = tbody;
|
||||||
|
|
||||||
|
table.append(mk('tfoot', mk('tr',
|
||||||
|
mk('th'),
|
||||||
|
mk('th', "Total"),
|
||||||
|
mk('th'),
|
||||||
|
mk('th.-time', util.format_duration(total_abstime / TICS_PER_SECOND, 2)),
|
||||||
|
mk('th.-score', total_score.toLocaleString()),
|
||||||
|
mk('th'),
|
||||||
|
mk('th'),
|
||||||
|
)));
|
||||||
|
|
||||||
this.add_button("nevermind", ev => {
|
this.add_button("nevermind", ev => {
|
||||||
this.close();
|
this.close();
|
||||||
});
|
});
|
||||||
@ -3633,10 +3694,10 @@ class Conductor {
|
|||||||
this.current_pack_savefile.aidless_levels += 1;
|
this.current_pack_savefile.aidless_levels += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.current_pack_savefile.__version__ = 1;
|
this.current_pack_savefile.__version__ = 2;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
else if (this.current_pack_savefile.__version__ === 1) {
|
if (this.current_pack_savefile.__version__ <= 1) {
|
||||||
// I forgot to count a level as aidless on your first playthrough. Also,
|
// I forgot to count a level as aidless on your first playthrough. Also,
|
||||||
// total_time is not a useful field, since 'time' is just where the clock was
|
// total_time is not a useful field, since 'time' is just where the clock was
|
||||||
delete this.current_pack_savefile.total_time;
|
delete this.current_pack_savefile.total_time;
|
||||||
|
|||||||
18
style.css
18
style.css
@ -323,6 +323,15 @@ table.level-browser thead {
|
|||||||
table.level-browser thead tr th {
|
table.level-browser thead tr th {
|
||||||
border-bottom: 2px solid hsl(225, 20%, 60%);
|
border-bottom: 2px solid hsl(225, 20%, 60%);
|
||||||
}
|
}
|
||||||
|
table.level-browser tfoot {
|
||||||
|
position: sticky;
|
||||||
|
bottom: -1em;
|
||||||
|
background: #f4f4f4; /* match dialog background */
|
||||||
|
}
|
||||||
|
table.level-browser tfoot tr th {
|
||||||
|
border-top: 2px solid hsl(225, 20%, 60%);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
table.level-browser td {
|
table.level-browser td {
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
}
|
}
|
||||||
@ -330,12 +339,15 @@ table.level-browser td.-number {
|
|||||||
color: #404040;
|
color: #404040;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
table.level-browser td.-time {
|
table.level-browser th.-time,
|
||||||
text-align: right;
|
table.level-browser th.-score,
|
||||||
}
|
table.level-browser td.-time,
|
||||||
table.level-browser td.-score {
|
table.level-browser td.-score {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
table.level-browser button {
|
||||||
|
font-size: 0.833em;
|
||||||
|
}
|
||||||
table.level-browser tr.--current {
|
table.level-browser tr.--current {
|
||||||
background: var(--generic-bg-selected-on-white);
|
background: var(--generic-bg-selected-on-white);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user