Skip to content

Commit 1148a5f

Browse files
authored
feat(client): chunithm graphs, song durations (#1284)
* feat: chunithm score graph * feat: add life * feat: add song duration data for all songs * fix: make chunithm graphs non-nullable * fix: test data * fix: potrząsanie kanalizacją * fix: misleading old function name * fix: make duration optional for chunithm * fix: add the missing duration
1 parent ab9474a commit 1148a5f

File tree

15 files changed

+3192
-66
lines changed

15 files changed

+3192
-66
lines changed

client/src/components/charts/OngekiScoreChart.tsx renamed to client/src/components/charts/GekichuScoreChart.tsx

Lines changed: 123 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
PointTooltipProps,
1212
LineSvgProps,
1313
} from "@nivo/line";
14-
import { COLOUR_SET, Difficulties } from "tachi-common";
14+
import { COLOUR_SET, Difficulties, Game } from "tachi-common";
1515
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
1616
import ChartTooltip from "./ChartTooltip";
1717

@@ -22,21 +22,39 @@ const formatTime = (s: DatumValue) =>
2222
.toString()
2323
.padStart(2, "0")}`;
2424

25-
const scoreToLamp = (s: number) => {
26-
switch (s) {
27-
case 970000:
28-
return "S";
29-
case 990000:
30-
return "SS";
31-
case 1000000:
32-
return "SSS";
33-
case 1007500:
34-
return "SSS+";
25+
const getScoreYAxisNotch = (game: Game) => (s: number) => {
26+
if (game === "ongeki") {
27+
switch (s) {
28+
case 970_000:
29+
return "S";
30+
case 990_000:
31+
return "SS";
32+
case 1000_000:
33+
return "SSS";
34+
case 1007_500:
35+
return "SSS+";
36+
}
37+
}
38+
if (game === "chunithm") {
39+
switch (s) {
40+
case 990_000:
41+
return "S+";
42+
case 1000_000:
43+
return "SS";
44+
case 1005_000:
45+
return "SS+";
46+
case 1007_500:
47+
return "SSS";
48+
case 1009_000:
49+
return "SSS+";
50+
}
3551
}
3652
return "";
3753
};
3854

39-
const strokeColor = (type: Difficulties["ongeki:Single"] | "BELLS") => {
55+
const strokeColor = (
56+
type: Difficulties["ongeki:Single"] | Difficulties["chunithm:Single"] | "BELLS"
57+
) => {
4058
const isLight = getTheme() === "light";
4159
switch (type) {
4260
case "BASIC":
@@ -49,21 +67,26 @@ const strokeColor = (type: Difficulties["ongeki:Single"] | "BELLS") => {
4967
return `hsl(280, 60%, ${isLight ? 35 : 67}%)`;
5068
case "BELLS":
5169
return `hsl(55, 90%, ${isLight ? 35 : 42}%)`;
70+
case "ULTIMA":
71+
return `hsl(360, 50%, ${isLight ? 35 : 67}%)`;
5272
default:
5373
return `hsl(0, 0%, ${isLight ? 35 : 67}%)`;
5474
}
5575
};
5676

57-
const limitScoreGraph = (data: Serie[]) => {
77+
const limitScoreGraph = (game: Game, data: Serie[]) => {
5878
for (const val of data[0].data) {
59-
if (val.y === null || val.y === undefined) {
79+
if (typeof val.y !== "number") {
6080
break;
6181
}
62-
if (val.y < 970000) {
82+
if (game === "ongeki" && val.y < 970_000) {
6383
// 969999 will be used to represent values below S
6484
// Without this, the line would cross the bottom axis
6585
// which looks very bad
66-
val.y = 969999;
86+
val.y = 969_999;
87+
}
88+
if (game === "chunithm" && val.y < 990_000) {
89+
val.y = 989_999;
6790
}
6891
}
6992
return data;
@@ -77,7 +100,7 @@ const bellFloor = (data: Datum[], totalBellCount: number) => {
77100
: clamp(-totalBellCount, Math.floor(lowestValue * 1.333), -1);
78101
};
79102

80-
export default function OngekiScoreChart({
103+
export default function GekichuScoreChart({
81104
width = "100%",
82105
height = "100%",
83106
mobileHeight = "100%",
@@ -86,28 +109,46 @@ export default function OngekiScoreChart({
86109
difficulty,
87110
totalBellCount,
88111
data,
112+
game,
113+
duration,
89114
}: {
90115
mobileHeight?: number | string;
91116
mobileWidth?: number | string;
92117
width?: number | string;
93118
height?: number | string;
94119
type: "Score" | "Bells" | "Life";
95-
difficulty: Difficulties["ongeki:Single"];
96-
totalBellCount: number;
120+
difficulty: Difficulties["ongeki:Single"] | Difficulties["chunithm:Single"];
121+
totalBellCount?: number;
97122
data: Serie[];
123+
game: Game;
124+
duration: number;
98125
} & ResponsiveLine["props"]) {
99-
const color =
100-
type === "Score"
101-
? GPT_CLIENT_IMPLEMENTATIONS["ongeki:Single"].difficultyColours[difficulty]
102-
: type === "Bells"
103-
? COLOUR_SET.vibrantYellow
104-
: COLOUR_SET.vibrantGreen;
126+
let color = COLOUR_SET.gray;
127+
128+
if (type === "Score") {
129+
if (game === "chunithm") {
130+
color =
131+
GPT_CLIENT_IMPLEMENTATIONS["chunithm:Single"].difficultyColours[
132+
difficulty as Difficulties["chunithm:Single"]
133+
];
134+
} else if (game === "ongeki") {
135+
color =
136+
GPT_CLIENT_IMPLEMENTATIONS["ongeki:Single"].difficultyColours[
137+
difficulty as Difficulties["ongeki:Single"]
138+
];
139+
}
140+
} else if (type === "Bells") {
141+
color = COLOUR_SET.vibrantYellow;
142+
} else {
143+
color = COLOUR_SET.vibrantGreen;
144+
}
145+
105146
const gradientId = type === "Score" ? difficulty : type;
106147

107148
const commonProps: Omit<LineSvgProps, "data"> = {
108149
margin: { top: 30, bottom: 50, left: 50, right: 50 },
109150
enableGridX: false,
110-
xScale: { type: "linear", min: 0, max: data[0].data.length - 1 },
151+
xScale: { type: "linear", min: 0, max: duration },
111152
axisBottom: { format: (d: number) => formatTime(d) },
112153
motionConfig: "stiff",
113154
crosshairType: "x",
@@ -134,43 +175,68 @@ export default function OngekiScoreChart({
134175

135176
let component;
136177
if (type === "Score") {
137-
component = (
138-
<ResponsiveLine
139-
{...commonProps}
140-
data={limitScoreGraph(data)}
141-
yScale={{ type: "linear", min: 970000, max: 1010000 }}
142-
yFormat={">-,.0f"}
143-
axisLeft={{
144-
tickValues: [970000, 990000, 1000000, 1007500, 1010000],
145-
format: scoreToLamp,
146-
}}
147-
gridYValues={[970000, 980000, 990000, 1000000, 1007500, 1010000]}
148-
enableGridY={true}
149-
colors={strokeColor(difficulty)}
150-
areaBaselineValue={970000}
151-
tooltip={(d: PointTooltipProps) => (
152-
<ChartTooltip>
153-
{d.point.data.y === 969999 ? "< 970,000 " : d.point.data.yFormatted}@{" "}
154-
{formatTime(d.point.data.x)}
155-
</ChartTooltip>
156-
)}
157-
/>
158-
);
178+
if (game === "ongeki") {
179+
component = (
180+
<ResponsiveLine
181+
{...commonProps}
182+
data={limitScoreGraph(game, data)}
183+
yScale={{ type: "linear", min: 970000, max: 1010000 }}
184+
yFormat={">-,.0f"}
185+
axisLeft={{
186+
tickValues: [970000, 990000, 1000000, 1007500, 1010000],
187+
format: getScoreYAxisNotch(game),
188+
}}
189+
gridYValues={[970000, 980000, 990000, 1000000, 1007500, 1010000]}
190+
enableGridY={true}
191+
colors={strokeColor(difficulty)}
192+
areaBaselineValue={970000}
193+
tooltip={(d: PointTooltipProps) => (
194+
<ChartTooltip>
195+
{d.point.data.y === 969999 ? "< 970,000 " : d.point.data.yFormatted}@{" "}
196+
{formatTime(d.point.data.x)}
197+
</ChartTooltip>
198+
)}
199+
/>
200+
);
201+
} else if (game === "chunithm") {
202+
component = (
203+
<ResponsiveLine
204+
{...commonProps}
205+
data={limitScoreGraph(game, data)}
206+
yScale={{ type: "linear", min: 990_000, max: 1010_000 }}
207+
yFormat={">-,.0f"}
208+
axisLeft={{
209+
tickValues: [990_000, 1000_000, 1005_000, 1007_500, 1009_000, 1010_000],
210+
format: getScoreYAxisNotch(game),
211+
}}
212+
gridYValues={[990_000, 1000_000, 1005_000, 1007_500, 1009_000, 1010_000]}
213+
enableGridY={true}
214+
colors={strokeColor(difficulty)}
215+
areaBaselineValue={990000}
216+
tooltip={(d: PointTooltipProps) => (
217+
<ChartTooltip>
218+
{d.point.data.y === 989_999 ? "< 990,000 " : d.point.data.yFormatted}@{" "}
219+
{formatTime(d.point.data.x)}
220+
</ChartTooltip>
221+
)}
222+
/>
223+
);
224+
}
159225
} else if (type === "Bells") {
160226
component = (
161227
<ResponsiveLine
162228
{...commonProps}
163229
data={data}
164230
yScale={{
165231
type: "linear",
166-
min: bellFloor(data[0].data, totalBellCount),
232+
min: bellFloor(data[0].data, totalBellCount!),
167233
max: 0,
168234
stacked: false,
169235
}}
170236
enableGridY={false}
171237
axisLeft={{ format: (e: number) => Math.floor(e) === e && e }}
172238
colors={strokeColor("BELLS")}
173-
areaBaselineValue={bellFloor(data[0].data, totalBellCount)}
239+
areaBaselineValue={bellFloor(data[0].data, totalBellCount!)}
174240
tooltip={(d: PointTooltipProps) => (
175241
<ChartTooltip>
176242
MAX{d.point.data.y === 0 ? "" : d.point.data.y} @{" "}
@@ -179,19 +245,22 @@ export default function OngekiScoreChart({
179245
)}
180246
/>
181247
);
182-
} else {
248+
} else if (type === "Life") {
249+
const max = game === "ongeki" ? 100 : (data[0].data[0].y as number);
250+
const suffix = game === "ongeki" ? "%" : "";
183251
component = (
184252
<ResponsiveLine
185253
{...commonProps}
186254
data={data}
187-
yScale={{ type: "linear", min: 0, max: 100 }}
255+
yScale={{ type: "linear", min: 0, max }}
188256
enableGridY={false}
189-
axisLeft={{ format: (d: number) => `${d}%` }}
257+
axisLeft={{ format: (d: number) => `${d}${suffix}` }}
190258
colors={strokeColor("BASIC")}
191259
areaBaselineValue={0}
192260
tooltip={(d: PointTooltipProps) => (
193261
<ChartTooltip>
194-
{d.point.data.y}% @ {formatTime(d.point.data.x)}
262+
{d.point.data.y}
263+
{suffix} @ {formatTime(d.point.data.x)}
195264
</ChartTooltip>
196265
)}
197266
/>

client/src/components/tables/dropdowns/GPTDropdownSettings.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { IIDXGraphsComponent } from "./components/IIDXScoreDropdownParts";
44
import { ITGGraphsComponent } from "./components/ITGScoreDropdownParts";
55
import { JubeatGraphsComponent } from "./components/JubeatScoreDropdownParts";
66
import { OngekiGraphsComponent } from "./components/OngekiScoreDropdownParts";
7+
import { ChunithmGraphsComponent } from "./components/ChunithmScoreDropdownParts";
78

89
export function GPTDropdownSettings(game: Game, playtype: Playtype): any {
910
if (game === "iidx") {
@@ -33,6 +34,11 @@ export function GPTDropdownSettings(game: Game, playtype: Playtype): any {
3334
renderScoreInfo: true,
3435
GraphComponent: OngekiGraphsComponent as any,
3536
};
37+
} else if (game === "chunithm") {
38+
return {
39+
renderScoreInfo: true,
40+
GraphComponent: ChunithmGraphsComponent as any,
41+
};
3642
}
3743

3844
return {};

client/src/components/tables/dropdowns/PBDropdown.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export default function PBDropdown({
148148
pbData={data}
149149
scoreState={scoreState}
150150
chart={chart}
151+
song={song}
151152
/>
152153
);
153154
}

client/src/components/tables/dropdowns/ScoreDropdown.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,11 @@ export default function ScoreDropdown({
120120
onScoreUpdate={onScoreUpdate}
121121
pbData={data}
122122
chart={chart}
123+
song={song}
123124
/>
124125
);
125126
} else if (view === "vsPB") {
126-
body = <PBCompare data={data} DocComponent={DocComponent} scoreState={scoreState} />;
127+
body = <PBCompare data={data} DocComponent={DocComponent as any} scoreState={scoreState} />;
127128
} else if (view === "manage") {
128129
body = <DeleteScoreBtn score={thisScore} />;
129130
} else if (view === "targets") {

0 commit comments

Comments
 (0)