mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-10 16:28:17 +03:00
fix: backport all features from v2 that came in after merge
This commit is contained in:
@@ -325,6 +325,13 @@ function ImportInfoDisplayer({ game }: { game: GameGroup }) {
|
||||
);
|
||||
} else if (game === "maimaidx") {
|
||||
Content.unshift(
|
||||
<ImportInfoCard
|
||||
desc="Automatically import scores, whenever you get them."
|
||||
href="rizu"
|
||||
key="Rizu"
|
||||
moreInfo="This is the recommended way to import maimai DX scores; data is submitted at the end of each play."
|
||||
name="Rizu"
|
||||
/>,
|
||||
<ImportInfoCard
|
||||
desc="Use your data from maimai DX NET."
|
||||
href="kt-maimaidx-site-importer"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import useSetSubheader from "#components/layout/header/useSetSubheader";
|
||||
import Divider from "#components/util/Divider";
|
||||
import ExternalLink from "#components/util/ExternalLink";
|
||||
import { TachiConfig } from "#lib/config";
|
||||
import React from "react";
|
||||
import Alert from "react-bootstrap/Alert";
|
||||
|
||||
export default function RizuPage() {
|
||||
useSetSubheader(["Import Scores", "Rizu"]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-center mb-4">What is Rizu?</h1>
|
||||
<div>
|
||||
Rizu automatically sends maimai DX scores to a server. {TachiConfig.NAME} is
|
||||
compatible with what Rizu sends, so you can use it to submit scores!
|
||||
</div>
|
||||
<Divider />
|
||||
<h1 className="text-center my-4">Setup Instructions</h1>
|
||||
<ol className="instructions-list">
|
||||
<li>
|
||||
Download the latest version of <code>Rizu-MelonLoader-vVERSION.zip</code>{" "}
|
||||
<ExternalLink href="https://gitea.tendokyu.moe/beerpsi/Rizu/releases/latest">
|
||||
here
|
||||
</ExternalLink>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
Download your <code>Rizu.cfg</code> config file{" "}
|
||||
<ExternalLink href="/client-file-flow/CXRizu">here</ExternalLink>
|
||||
. <br />
|
||||
<Alert className="mt-2" variant="warning">
|
||||
This file contains an API Key, which is meant to be kept secret!
|
||||
</Alert>
|
||||
</li>
|
||||
<li>
|
||||
Follow the installation instructions on{" "}
|
||||
<ExternalLink href="https://gitea.tendokyu.moe/beerpsi/Rizu#melonloader">
|
||||
Tendokyu
|
||||
</ExternalLink>
|
||||
.
|
||||
</li>
|
||||
<li>Your scores are now automatically uploaded to {TachiConfig.NAME}!</li>
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import useSetSubheader from "#components/layout/header/useSetSubheader";
|
||||
import Divider from "#components/util/Divider";
|
||||
import { UserContext } from "#context/UserContext";
|
||||
import { TachiConfig } from "#lib/config";
|
||||
import { Contributors } from "#util/constants/contributors";
|
||||
import { RFA } from "#util/misc";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function CreditsPage() {
|
||||
useSetSubheader("Credits");
|
||||
|
||||
const [alt, setAlt] = useState(false);
|
||||
const [whatTheHell, setWhatTheHell] = useState(0);
|
||||
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
function a(normal: string | JSX.Element, ...altStr: (string | JSX.Element)[]) {
|
||||
if (!alt) {
|
||||
return normal;
|
||||
} else if (whatTheHell < 0) {
|
||||
return normal;
|
||||
}
|
||||
|
||||
return RFA(altStr);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ fontSize: "1.15rem" }}>
|
||||
<p>
|
||||
{a(
|
||||
<>
|
||||
{TachiConfig.NAME} has been the work of many people, and many more
|
||||
contributors. None of this would be possible without these people.
|
||||
</>,
|
||||
"Funding for this program was made possible by by by by by-",
|
||||
`${TachiConfig.NAME} actually just appeared here one day, and we're not sure how.`,
|
||||
`Actually, we just ran npm install ${TachiConfig.NAME}.`,
|
||||
"To be honest, we had a surplus of monkeys and typewriters.",
|
||||
)}
|
||||
</p>
|
||||
<Divider />
|
||||
<p>
|
||||
If you want to support {TachiConfig.NAME} development, see{" "}
|
||||
<Link to="/support">Support</Link>.
|
||||
</p>
|
||||
<Divider />
|
||||
|
||||
<div className="mt-4">
|
||||
<h1 className="user-select-none">
|
||||
De
|
||||
<span
|
||||
className="credits-easter-egg"
|
||||
onClick={() => {
|
||||
if (alt) {
|
||||
// Force a different RFA
|
||||
// by incrementing a random
|
||||
// variable
|
||||
setWhatTheHell(whatTheHell + 1);
|
||||
} else {
|
||||
setAlt(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
v
|
||||
</span>{" "}
|
||||
Team
|
||||
</h1>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
{a(
|
||||
"Lead Dev",
|
||||
"Rules Of Hooks Violator",
|
||||
"ABBA Enthusiast",
|
||||
"Nutcase",
|
||||
"IIDX 'player'",
|
||||
)}
|
||||
: <strong>zk</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a(
|
||||
"Infrastructure & Dev/Ops",
|
||||
"Professional YAML Editor",
|
||||
"Professional nginx.conf Editor",
|
||||
"'What do you mean you rm -rf'd that folder?'",
|
||||
"WintOr",
|
||||
"Frog Lover",
|
||||
)}
|
||||
: <strong>winter</strong>
|
||||
<br />
|
||||
<small>Also the reason I even started learning how to program.</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<h1>Significant Contributors</h1>
|
||||
|
||||
<p>
|
||||
These people have contributed a significant amount of their time to{" "}
|
||||
{TachiConfig.NAME}.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
{a(
|
||||
"Tachi Discord Bot Work + Homepage",
|
||||
"oooOoooo you want to use redux oooo",
|
||||
"Java Skirt Boy",
|
||||
"Urbit Enjoyer",
|
||||
)}
|
||||
: <strong>pfych</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a("Mikado", "SDVX Pocky")}: <strong>adamaq01</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a("Fervidex", "IIDX Black Magic")}: <strong>aixxe</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a("Barbatos", "SDVX Black Magic")}: <strong>Arm1stice</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a(
|
||||
"Graphic Design",
|
||||
"Adobe Suite Purchaser",
|
||||
"Squiggly Line Drawer",
|
||||
"Straight Line Drawer",
|
||||
"Ratboy Genius",
|
||||
"Large Headded Individual",
|
||||
"The Ace Of Craigs",
|
||||
)}
|
||||
: <strong>Craig</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a(
|
||||
"Database Management, Various features, generally just a hell of a lot of work",
|
||||
"This guy put emojis in my codebase",
|
||||
"Emacs Evangelist",
|
||||
)}
|
||||
: <strong>cg505</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a("KsHook", "Konaste Black Magic")}: <strong>Emma</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a("APIs, Databasing", "All Black Magic")}: <strong>Felix</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a("APIs, Massive Dev Support", "Have you tried Elixir?")}:{" "}
|
||||
<strong>haste</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a("Chunitachi", "CHUNITHM Black Magic")}: <strong>tomatosoup</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a(
|
||||
"Dev, Maths Consultation",
|
||||
"Functional Programming Evangelist",
|
||||
"Born On The Cob",
|
||||
"Crunchy Nut Enjoyer",
|
||||
"Has An Afro",
|
||||
)}
|
||||
: <strong>Percyqaz</strong>
|
||||
</li>
|
||||
<li>
|
||||
{a("Server", "Ethereum Mining Solutions")}: <strong>viddy</strong>
|
||||
<br />
|
||||
<small>Yes, the entire server, networking costs, everything.</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<h1>Contributors</h1>
|
||||
|
||||
<p>These people have contributed to {TachiConfig.NAME}.</p>
|
||||
|
||||
<ul>
|
||||
{Contributors.slice(0)
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((e) => (
|
||||
<li key={e}>{e}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<h1>And...</h1>
|
||||
|
||||
<p>
|
||||
Everyone who was part of the beta.
|
||||
{user?.badges.some((x) => x === "beta" || x === "alpha")
|
||||
? " (That's you!)"
|
||||
: ""}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Everyone in the <code>#dev</code> chat for the constant advice and comedy gold.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-body-secondary" style={{ fontSize: "0.2rem" }}>
|
||||
Click the V in Dev Team.
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,8 @@
|
||||
import { TachiConfig } from "#lib/config";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import { type UserDocument } from "tachi-common";
|
||||
|
||||
export default function SupportBanner({ user }: { user: UserDocument }) {
|
||||
// const { data, error } = useApiQuery<{ scores: integer; sessions: integer }>(
|
||||
// `/users/${user.id}/stats`,
|
||||
// );
|
||||
|
||||
const [show, _setShow] = useState(localStorage.getItem("SHOW_SUPPORT_TACHI") === "true");
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("SHOW_SUPPORT_TACHI", `${show}`);
|
||||
}, [show]);
|
||||
|
||||
// thank you
|
||||
if (user.isSupporter) {
|
||||
return (
|
||||
@@ -23,69 +13,4 @@ export default function SupportBanner({ user }: { user: UserDocument }) {
|
||||
}
|
||||
|
||||
return <></>;
|
||||
|
||||
// if (!data) {
|
||||
// return <></>;
|
||||
// }
|
||||
|
||||
// if (error) {
|
||||
// return <></>;
|
||||
// }
|
||||
|
||||
// if (data.scores < 1_000) {
|
||||
// return <></>;
|
||||
// }
|
||||
|
||||
// // too soon to bother
|
||||
// if (Date.now() - user.joinDate < ONE_DAY * 7) {
|
||||
// return <></>;
|
||||
// }
|
||||
|
||||
// if (!show) {
|
||||
// return (
|
||||
// <div
|
||||
// className="d-flex w-100 justify-content-center flex-column align-items-center"
|
||||
// onClick={() => setShow(true)}
|
||||
// >
|
||||
// <div>Support us? ❤️</div>
|
||||
// <div>
|
||||
// <Icon type="chevron-down" />
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <Card
|
||||
// className="mb-8"
|
||||
// footer={
|
||||
// <div className="d-flex w-100 justify-content-center" onClick={() => setShow(false)}>
|
||||
// <Icon type="chevron-up" />
|
||||
// </div>
|
||||
// }
|
||||
// header={
|
||||
// <span style={{ fontSize: "1.55rem" }}>
|
||||
// You've set {data.scores} scores and had {data.sessions} sessions, wew!
|
||||
// </span>
|
||||
// }
|
||||
// >
|
||||
// <span style={{ fontSize: "1.15rem" }}>
|
||||
// {TachiConfig.NAME} is a passion project, and is primarily developed by one person.
|
||||
// <br />
|
||||
// Since you're enjoying the site so much, maybe you'd want to support me? :3
|
||||
// <br />
|
||||
// <br />
|
||||
// If you want to support development, you can donate to my{" "}
|
||||
// <ExternalLink href="https://ko-fi.com/zkrising">Ko-Fi</ExternalLink>, if you
|
||||
// indicate your account name in the donation, you'll get a shiny name on the site!
|
||||
// <br />
|
||||
// I'm working on some new projects in secret. Maybe I'll post some updates on there :P
|
||||
// <br />
|
||||
// <br />
|
||||
// Alternatively, you can star or contribute to the fully-open-source{" "}
|
||||
// <ExternalLink href="https://github.com/zkldi/Tachi">GitHub Repo</ExternalLink>. This
|
||||
// makes me look cool to employers!
|
||||
// </span>
|
||||
// </Card>
|
||||
// );
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function SupportMePage() {
|
||||
useSetSubheader("Support / Ko-fi");
|
||||
useSetSubheader("Support");
|
||||
|
||||
return (
|
||||
<div style={{ fontSize: "1.15rem" }}>
|
||||
@@ -14,11 +14,7 @@ export default function SupportMePage() {
|
||||
<Link to="/credits">people like you</Link>.
|
||||
</p>
|
||||
<p>
|
||||
If you want to support {TachiConfig.NAME} development, you can donate to my{" "}
|
||||
<ExternalLink href="https://ko-fi.com/zkrising">Ko-fi</ExternalLink>.
|
||||
</p>
|
||||
<p>
|
||||
Alternatively, you can star the{" "}
|
||||
You can star the{" "}
|
||||
<ExternalLink href="https://github.com/zkldi/Tachi">GitHub Repo</ExternalLink>. This
|
||||
makes me look cool to employers!
|
||||
</p>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import CreditsPage from "#app/pages/dashboard/misc/CreditsPage";
|
||||
import MyProposalsPage from "#app/pages/dashboard/proposals/MyProposalsPage";
|
||||
import ProposalsPage from "#app/pages/dashboard/proposals/ProposalsPage";
|
||||
import SearchPage from "#app/pages/dashboard/search/SearchPage";
|
||||
@@ -95,6 +96,10 @@ export default function DashboardRoutes() {
|
||||
<PrivacyPolicyPage />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/credits">
|
||||
<CreditsPage />
|
||||
</Route>
|
||||
|
||||
{/* <Route exact path="/support">
|
||||
<SupportMePage />
|
||||
</Route> */}
|
||||
|
||||
@@ -19,6 +19,7 @@ import MikadoPage from "#app/pages/dashboard/import/MikadoPage";
|
||||
import OngekiArtemisExportPage from "#app/pages/dashboard/import/OngekiArtemisExportPage";
|
||||
import OngekiInoharaPage from "#app/pages/dashboard/import/OngekiInoharaPage";
|
||||
import OngekiSiteImportPage from "#app/pages/dashboard/import/OngekiSiteImportPage";
|
||||
import RizuPage from "#app/pages/dashboard/import/RizuPage";
|
||||
import SaekawaPage from "#app/pages/dashboard/import/SaekawaPage";
|
||||
import SDVXEamCSVPage from "#app/pages/dashboard/import/SDVXEamCSVPage";
|
||||
import SilentHookPage from "#app/pages/dashboard/import/SilentHookPage";
|
||||
@@ -255,6 +256,10 @@ export default function ImportRoutes() {
|
||||
<WACCAMyPageScraperPage />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/import/rizu">
|
||||
<RizuPage />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/import/kt-maimaidx-site-importer">
|
||||
<MaimaiDXSiteImportPage />
|
||||
</Route>
|
||||
|
||||
@@ -45,8 +45,10 @@ export default function ClassBadge<GPT extends V3Game = V3Game>({
|
||||
</Badge>
|
||||
);
|
||||
} else {
|
||||
const styleWithMaybeShine = classStyle as { shine?: boolean } & React.CSSProperties;
|
||||
const { shine, ...badgeStyle } = styleWithMaybeShine;
|
||||
badgeComponent = (
|
||||
<Badge bg={""} className="mx-2" style={classStyle}>
|
||||
<Badge bg={""} className={`mx-2${shine ? " shine" : ""}`} style={badgeStyle}>
|
||||
{data.display}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -185,7 +185,9 @@ function ChartInfoMiddle({
|
||||
const diff = FormatDifficultySearch(chart);
|
||||
const gameName =
|
||||
game === "ongeki" ? "オンゲキ" : game === "maimaidx" ? "maimai" : gameGroupConfig.name;
|
||||
const formattedTitle = song.title.replace(/-/gu, " ");
|
||||
const formattedTitle = song.title
|
||||
.replace(/([!?,]){4,}/gu, (m) => m[0].repeat(3))
|
||||
.replace(/-/gu, " ");
|
||||
|
||||
let search = `${gameName} ${formattedTitle}`;
|
||||
|
||||
|
||||
@@ -16,9 +16,14 @@ export default function IIDXStyleSongChartInfoFormat({
|
||||
game: V3Game;
|
||||
song: SongDocument<"bms" | "chunithm" | "iidx" | "maimaidx" | "ongeki" | "pms" | "popn">;
|
||||
}) {
|
||||
const genre =
|
||||
game === "ongeki"
|
||||
? ((song as SongDocument<"ongeki">).data.flavorGenre ?? song.data.genre)
|
||||
: song.data.genre;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4>{song.data.genre}</h4>
|
||||
<h4>{genre}</h4>
|
||||
<h4 style={{ fontSize: "2.5rem", fontWeight: "bold" }}>{song.title}</h4>
|
||||
<h4>{song.artist}</h4>
|
||||
{chart && <h5>({LevelText(chart)})</h5>}
|
||||
|
||||
@@ -63,6 +63,14 @@ export function Footer() {
|
||||
>
|
||||
GDPR
|
||||
</Nav.Link>
|
||||
<Nav.Link
|
||||
as={Link}
|
||||
className={linkClassNames}
|
||||
onClick={() => window.scrollTo(0, 0)}
|
||||
to="/credits"
|
||||
>
|
||||
Credits
|
||||
</Nav.Link>
|
||||
{import.meta.env.VITE_DISCORD && (
|
||||
<Nav.Link
|
||||
as={ExternalLink}
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ function GraphComponent({
|
||||
},
|
||||
]}
|
||||
difficulty={chart.difficulty}
|
||||
duration={song.data.duration}
|
||||
duration={song.data.duration ?? 240}
|
||||
game="ongeki"
|
||||
height="360px"
|
||||
maximumAbsoluteValue={
|
||||
|
||||
@@ -25,7 +25,7 @@ import { NumericSOV } from "#util/sorts";
|
||||
import React from "react";
|
||||
import { COLOUR_SET, type PBScoreDocument, type ScoreDocument, type V3Game } from "tachi-common";
|
||||
|
||||
import { bgc, RAINBOW_EX_GRADIENT, RAINBOW_GRADIENT, RAINBOW_SHINY_GRADIENT } from "./games/_util";
|
||||
import { bgc, RAINBOW_EX_GRADIENT, RAINBOW_GRADIENT } from "./games/_util";
|
||||
import { ARCAEA_TOUCH_IMPL } from "./games/arcaea";
|
||||
import { BMS_7K_IMPL, BMS_14K_IMPL, PMS_IMPL } from "./games/bms-pms";
|
||||
import { DDR_DP_IMPL, DDR_SP_IMPL } from "./games/ddr";
|
||||
@@ -785,9 +785,22 @@ export const GPT_CLIENT_IMPLEMENTATIONS: GPTClientImplementations = {
|
||||
SILVER: bgc("gray", "var(--bs-light)"),
|
||||
GOLD: bgc("var(--bs-warning)", "var(--bs-dark)"),
|
||||
PLATINUM: bgc("silver", "var(--bs-dark)"),
|
||||
RAINBOW: RAINBOW_GRADIENT,
|
||||
RAINBOW_SHINY: RAINBOW_SHINY_GRADIENT,
|
||||
RAINBOW_EX: RAINBOW_EX_GRADIENT,
|
||||
RAINBOW: {
|
||||
background: "linear-gradient(-45deg, #ff73d6 20%, #00ffff 50%, #ebfb2a 80%)",
|
||||
color: "var(--bs-dark)",
|
||||
},
|
||||
RAINBOW_SHINY: {
|
||||
background: "linear-gradient(-45deg, #ff73d6 20%, #00ffff 50%, #ebfb2a 80%)",
|
||||
color: "var(--bs-dark)",
|
||||
shine: true,
|
||||
},
|
||||
RAINBOW_EX: {
|
||||
background:
|
||||
"linear-gradient(-45deg, #df7e85 20%, #efef51 40%, #4bee83 60%, #70bdd7 80%)",
|
||||
color: "var(--bs-dark)",
|
||||
shine: true,
|
||||
},
|
||||
RAINBOW_EX_TRUE: { ...RAINBOW_EX_GRADIENT, shine: true },
|
||||
},
|
||||
},
|
||||
enumColours: {
|
||||
@@ -845,7 +858,14 @@ export const GPT_CLIENT_IMPLEMENTATIONS: GPTClientImplementations = {
|
||||
[
|
||||
"Platinum Score",
|
||||
"P-Score",
|
||||
NumericSOV((x) => x.scoreData.platinumStars * 100000 + x.scoreData.platinumScore),
|
||||
// __related is attached when rendering table rows with chart context
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- NumericSOV row shape
|
||||
NumericSOV((x: any) => {
|
||||
if (x.__related?.chart?.data !== undefined) {
|
||||
return x.scoreData.platinumScore / x.__related.chart.data.maxPlatScore;
|
||||
}
|
||||
return x.scoreData.platinumStars * 100000 + x.scoreData.platinumScore;
|
||||
}),
|
||||
],
|
||||
["Judgements", "Notes", NumericSOV((x) => x.scoreData.judgements.cbreak ?? 0)],
|
||||
[
|
||||
|
||||
@@ -36,12 +36,6 @@ export const RAINBOW_GRADIENT = {
|
||||
color: "var(--bs-dark)",
|
||||
} as const;
|
||||
|
||||
export const RAINBOW_SHINY_GRADIENT = {
|
||||
background:
|
||||
"linear-gradient(-45deg, #ff8697, #ff9dbf, #9e81d0, #87caff, #7dae80, #ffffa7, #ffd88a, #ffab8d, #ff8697)",
|
||||
color: "var(--bs-dark)",
|
||||
} as const;
|
||||
|
||||
export const RAINBOW_EX_GRADIENT = {
|
||||
background: "linear-gradient(-45deg, #0fa091, #0f98d5, #67087f, #d9007e, #f56e06)",
|
||||
color: "var(--bs-light)",
|
||||
|
||||
@@ -65,13 +65,13 @@ export type GPTClassColours<GPT extends V3Game> = {
|
||||
// css properties => css
|
||||
// null -> no styling
|
||||
[V in ClassConfigs[GPT][C]["values"][number]["id"]]:
|
||||
| ({ shine?: boolean } & CSSProperties)
|
||||
| "danger"
|
||||
| "info"
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "success"
|
||||
| "warning"
|
||||
| CSSProperties
|
||||
| null;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,3 +6,35 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
0% {
|
||||
top: -150%;
|
||||
left: -150%;
|
||||
}
|
||||
100% {
|
||||
top: 150%;
|
||||
left: 150%;
|
||||
}
|
||||
}
|
||||
|
||||
.shine {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.shine::before {
|
||||
background: linear-gradient(
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.5) 50%,
|
||||
transparent
|
||||
);
|
||||
content: "";
|
||||
display: block;
|
||||
width: 150%;
|
||||
height: 2rem;
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
animation: shine 4s infinite ease-in-out;
|
||||
z-index: 100;
|
||||
}
|
||||
@@ -23,7 +23,8 @@ export const GAME_GROUP_ONGEKI_CONF = {
|
||||
"LUNATIC",
|
||||
"ボーナストラック",
|
||||
]),
|
||||
duration: z.number(),
|
||||
duration: z.number().nullable(),
|
||||
flavorGenre: z.string().optional(),
|
||||
}),
|
||||
} as const satisfies INTERNAL_GAME_GROUP_CONFIG;
|
||||
|
||||
@@ -39,7 +40,8 @@ export const OngekiColours = [
|
||||
ClassValue("PLATINUM", "鉑", "Platinum: 18.000~18.999 Rating"),
|
||||
ClassValue("RAINBOW", "虹", "Rainbow: 19.000~19.999 Rating"),
|
||||
ClassValue("RAINBOW_SHINY", "虹(光)", "Rainbow Shiny: 20.000~20.999 Rating"),
|
||||
ClassValue("RAINBOW_EX", "虹(極)", "Rainbow Extreme: 21.000~ Rating"),
|
||||
ClassValue("RAINBOW_EX", "虹(極)", "Rainbow Extreme: 21.000~21.999 Rating"),
|
||||
ClassValue("RAINBOW_EX_TRUE", "虹(極)・真", "Rainbow Extreme (True): 22.000~ Rating"),
|
||||
];
|
||||
|
||||
export const GAME_ONGEKI_CONF = {
|
||||
@@ -228,11 +230,11 @@ export const GAME_ONGEKI_CONF = {
|
||||
"オンゲキ bright MEMORY Act.3",
|
||||
"オンゲキ Re:Fresh",
|
||||
]),
|
||||
isBonusTrack: z.boolean(),
|
||||
isReMaster: z.boolean().optional(),
|
||||
maxPlatScore: z.number().int(),
|
||||
inGameID: z.number().int(),
|
||||
maxPlatScore: z.number().int().nonnegative(),
|
||||
inGameID: z.number().int().nonnegative().nullable(),
|
||||
chartViewURL: z.string().optional(),
|
||||
isBonusTrack: z.boolean().optional(),
|
||||
}),
|
||||
|
||||
preferences: z.strictObject({}),
|
||||
|
||||
@@ -612,6 +612,7 @@ export enum ONGEKI_COLOURS {
|
||||
PURPLE = 4,
|
||||
RAINBOW = 9,
|
||||
RAINBOW_EX = 11,
|
||||
RAINBOW_EX_TRUE = 12,
|
||||
RAINBOW_SHINY = 10,
|
||||
RED = 3,
|
||||
SILVER = 6,
|
||||
|
||||
@@ -106,7 +106,7 @@ export async function GetBestJubilityOnSongs(
|
||||
return Promise.all(rows.rows.map((row) => ToPbScoreDocument(row)));
|
||||
}
|
||||
|
||||
const CURRENT_JUBEAT_HOT_VERSION: Versions["jubeat"] = "ave";
|
||||
const CURRENT_JUBEAT_HOT_VERSION: Versions["jubeat"] = "beyond";
|
||||
|
||||
export async function GetPBsForJubility(userID: integer) {
|
||||
const hotSongRows = await DB.selectFrom("song")
|
||||
|
||||
@@ -293,6 +293,8 @@ describe("ONGEKI_IMPL", () => {
|
||||
c(19, "RAINBOW");
|
||||
c(20, "RAINBOW_SHINY");
|
||||
c(21, "RAINBOW_EX");
|
||||
c(22, "RAINBOW_EX_TRUE");
|
||||
c(23, "RAINBOW_EX_TRUE");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -16,8 +16,13 @@ import {
|
||||
|
||||
import { GoalFmtScore, GoalOutOfFmtScore, GradeGoalFormatter } from "./_common";
|
||||
|
||||
const isUnranked = (chart: ChartDocument<"ongeki">) =>
|
||||
(chart.data.inGameID >= 7000 && chart.data.inGameID < 8000) || chart.levelNum === 0.0;
|
||||
const isUnranked = (chart: ChartDocument<"ongeki">) => {
|
||||
const ig = chart.data.inGameID;
|
||||
return (
|
||||
(ig !== null && ig >= 7000 && ig < 8000) ||
|
||||
chart.levelNum === 0.0
|
||||
);
|
||||
};
|
||||
|
||||
const starCount = (platinumScore: number, maxPlatinumScore: number) => {
|
||||
const pct = Math.floor((platinumScore / maxPlatinumScore) * 100);
|
||||
@@ -113,7 +118,9 @@ export const ONGEKI_IMPL: GameImplementation<"ongeki"> = {
|
||||
return { colour: null };
|
||||
}
|
||||
|
||||
if (rating >= 21) {
|
||||
if (rating >= 22) {
|
||||
return { colour: "RAINBOW_EX_TRUE" };
|
||||
} else if (rating >= 21) {
|
||||
return { colour: "RAINBOW_EX" };
|
||||
} else if (rating >= 20) {
|
||||
return { colour: "RAINBOW_SHINY" };
|
||||
@@ -230,9 +237,11 @@ export const ONGEKI_IMPL: GameImplementation<"ongeki"> = {
|
||||
}
|
||||
}
|
||||
|
||||
if (s.scoreData.bellLamp === "FULL BELL" && s.scoreData.noteLamp === "LOSS") {
|
||||
return "Cannot have a LOSS with a FULL BELL.";
|
||||
}
|
||||
// LMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO
|
||||
// if (s.scoreData.bellLamp === "FULL BELL" && s.scoreData.noteLamp === "LOSS") {
|
||||
// return "Cannot have a LOSS with a FULL BELL.";
|
||||
// }
|
||||
// LMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO
|
||||
|
||||
if (s.scoreData.platinumScore > chart.data.maxPlatScore) {
|
||||
return `Cannot have ${s.scoreData.platinumScore}/${chart.data.maxPlatScore} Platinum Score.`;
|
||||
|
||||
@@ -240,6 +240,47 @@ EnableGraphs = true
|
||||
#
|
||||
# If present, an * matches everyone else`,
|
||||
},
|
||||
{
|
||||
clientID: "CXRizu",
|
||||
requestedPermissions: ["submit_score"],
|
||||
name: "Rizu",
|
||||
redirectUri: null,
|
||||
webhookUri: null,
|
||||
apiKeyFilename: "Rizu.cfg",
|
||||
apiKeyTemplate: `[General]
|
||||
|
||||
## Whether to enable score submissions
|
||||
# Setting type: Boolean
|
||||
# Default value: true
|
||||
Enable = true
|
||||
|
||||
## Timeout for score submission in seconds
|
||||
# Setting type: Int32
|
||||
# Default value: 30
|
||||
NetworkTimeout = 30
|
||||
|
||||
## Folder for storing imports that failed due to network errors.
|
||||
## Leave empty to disable
|
||||
# Setting type: String
|
||||
# Default value: UserData/Rizu/FailedImports
|
||||
FailedImportsFolder = UserData/Rizu/FailedImports
|
||||
|
||||
[Keys]
|
||||
|
||||
## Kamaitachi API keys to use for score submissions, in the format
|
||||
## of \`<access-code> = <api-key>\`. The \`default\` key is used as fallback
|
||||
## if an access code does not have an API key set, and can be removed.
|
||||
# Setting type: String
|
||||
# Default value:
|
||||
default = %%TACHI_KEY%%
|
||||
|
||||
[Tachi]
|
||||
|
||||
## Tachi instance base URL
|
||||
# Setting type: String
|
||||
# Default value: https://kamai.tachi.ac
|
||||
BaseUrl = ${ServerConfig.OUR_URL}`,
|
||||
},
|
||||
];
|
||||
|
||||
const BokuDefaultClients: DefaultClients = [
|
||||
|
||||
@@ -119,6 +119,8 @@ function ConvertVersion(ver: number): Versions["jubeat"] {
|
||||
return "festo";
|
||||
case 11:
|
||||
return "ave";
|
||||
case 12:
|
||||
return "beyond";
|
||||
}
|
||||
|
||||
throw new InvalidScoreFailure(`Unknown Game Version ${ver}.`);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type SEEDS_QuestlineDocument,
|
||||
type SEEDS_SongDocument,
|
||||
type SEEDS_TableDocument,
|
||||
type V3Game,
|
||||
} from "tachi-common";
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import type {
|
||||
|
||||
@@ -719,8 +719,9 @@ export const CHUNITHMBBKKSong: SongDocument<"chunithm"> = {
|
||||
altTitles: [],
|
||||
artist: "nora2r",
|
||||
data: {
|
||||
duration: 128.541,
|
||||
genre: "VARIETY",
|
||||
duration: 128.541,
|
||||
displayVersion: "CHUNITHM",
|
||||
},
|
||||
id: "S19d35e0b935765ded25",
|
||||
searchTerms: [],
|
||||
@@ -736,7 +737,6 @@ export const CHUNITHMBBKKChart: ChartDocument<"chunithm"> = {
|
||||
level: "3",
|
||||
data: {
|
||||
inGameID: 3,
|
||||
displayVersion: "CHUNITHM",
|
||||
},
|
||||
isPrimary: true,
|
||||
versions: ["paradiselost"],
|
||||
@@ -1485,6 +1485,7 @@ export const TestingOngekiChart: ChartDocument<"ongeki"> = {
|
||||
displayVersion: "オンゲキ bright MEMORY Act.3",
|
||||
inGameID: 2137,
|
||||
maxPlatScore: 1000,
|
||||
isBonusTrack: false,
|
||||
},
|
||||
difficulty: "MASTER",
|
||||
isPrimary: true,
|
||||
@@ -1537,6 +1538,7 @@ export const TestingOngekiChartConverter: ChartDocument<"ongeki"> = {
|
||||
displayVersion: "オンゲキ bright",
|
||||
inGameID: 678,
|
||||
maxPlatScore: 2768,
|
||||
isBonusTrack: false,
|
||||
},
|
||||
difficulty: "MASTER",
|
||||
isPrimary: true,
|
||||
@@ -1552,6 +1554,7 @@ export const TestingChunithmSongConverter: SongDocument<"chunithm"> = {
|
||||
data: {
|
||||
genre: "POPS & ANIME",
|
||||
duration: 120,
|
||||
displayVersion: "CHUNITHM CRYSTAL PLUS",
|
||||
},
|
||||
id: "s956",
|
||||
searchTerms: [],
|
||||
@@ -1562,7 +1565,6 @@ export const TestingChunithmChartConverter: ChartDocument<"chunithm"> = {
|
||||
game: "chunithm",
|
||||
chartID: "fbba26a6a223b1dd0bf8ef139cfee83114b91086",
|
||||
data: {
|
||||
displayVersion: "CHUNITHM CRYSTAL PLUS",
|
||||
inGameID: 956,
|
||||
},
|
||||
difficulty: "MASTER",
|
||||
@@ -1579,6 +1581,7 @@ export const TestingChunithmWESongConverter: SongDocument<"chunithm"> = {
|
||||
data: {
|
||||
duration: 135.275,
|
||||
genre: "VARIETY",
|
||||
displayVersion: "CHUNITHM NEW PLUS",
|
||||
},
|
||||
id: "S19d35e0c127c7e441ce",
|
||||
searchTerms: ["xl techno more dance"],
|
||||
@@ -1588,7 +1591,6 @@ export const TestingChunithmWESongConverter: SongDocument<"chunithm"> = {
|
||||
export const TestingChunithmWEChartConverter: ChartDocument<"chunithm"> = {
|
||||
game: "chunithm",
|
||||
data: {
|
||||
displayVersion: "CHUNITHM NEW PLUS",
|
||||
inGameID: 8235,
|
||||
},
|
||||
difficulty: "割☆4",
|
||||
|
||||
Reference in New Issue
Block a user