added kamaitachi export

This commit is contained in:
Polaris 2024-09-02 05:55:16 -04:00
parent 0036ed8934
commit 4484bedb86
10 changed files with 287 additions and 3 deletions

View File

@ -0,0 +1,209 @@
"use server";
import { getAuth } from "@/auth/queries/getauth";
import { getSupportedVersionNumber } from "@/lib/api";
import { artemis } from "@/lib/prisma";
import { fromZonedTime, toZonedTime } from "date-fns-tz";
import { parse } from "date-fns";
const TACHI_CLASSES = [
undefined,
"DAN_I",
"DAN_II",
"DAN_III",
"DAN_IV",
"DAN_V",
"DAN_INFINITE",
] as const;
const TACHI_DIFFICULTIES = [
"BASIC",
"ADVANCED",
"EXPERT",
"MASTER",
"ULTIMA",
] as const;
type BatchManualLamp =
| "ALL JUSTICE CRITICAL"
| "ALL JUSTICE"
| "FULL COMBO"
| "CLEAR"
| "FAILED";
interface BatchManualScore {
identifier: string;
matchType: "inGameID";
score: number;
lamp: BatchManualLamp;
difficulty: "BASIC" | "ADVANCED" | "EXPERT" | "MASTER" | "ULTIMA";
timeAchieved?: number;
judgements?: {
jcrit: number;
justice: number;
attack: number;
miss: number;
};
optional?: {
maxCombo: number;
};
}
interface BatchManualImport {
meta: {
game: string;
playtype: string;
service: string;
};
scores: BatchManualScore[];
classes?: {
dan?: string;
emblem?: string;
};
}
export async function getTachiExport() {
const { user } = await getAuth();
const version = await getSupportedVersionNumber();
if (!user || !user.accessCode) {
throw new Error("User is not authenticated or accessCode is missing");
}
const profile = await artemis.chuni_profile_data.findFirst({
where: {
user: user.UserId,
version,
},
select: {
classEmblemBase: true,
classEmblemMedal: true,
},
});
const playlog = await artemis.chuni_score_playlog.findMany({
where: {
user: user.UserId,
},
select: {
romVersion: true,
userPlayDate: true,
musicId: true,
level: true,
score: true,
maxCombo: true,
judgeGuilty: true,
judgeAttack: true,
judgeJustice: true,
judgeCritical: true,
judgeHeaven: true,
isFullCombo: true,
isAllJustice: true,
isClear: true,
},
});
const tachiExport: BatchManualImport = {
meta: {
game: "chunithm",
playtype: "Single",
service: "Cozynet",
},
scores: [],
classes: {
dan: TACHI_CLASSES[profile?.classEmblemBase ?? 0],
emblem: TACHI_CLASSES[profile?.classEmblemMedal ?? 0],
},
};
for (const log of playlog) {
const {
romVersion,
userPlayDate,
musicId,
level,
score,
judgeHeaven,
judgeCritical,
judgeJustice,
judgeAttack,
judgeGuilty,
maxCombo,
isAllJustice,
isFullCombo,
isClear,
} = log;
if (
romVersion === null ||
musicId === null ||
level === null ||
score === null ||
judgeJustice === null ||
isAllJustice === null ||
isFullCombo === null ||
isClear === null
) {
continue;
}
// Filter out WORLD'S END scores
if (romVersion.startsWith("1.") && level === 4) {
continue;
}
if (romVersion.startsWith("2.") && level === 5) {
continue;
}
let lamp: BatchManualLamp = "FAILED";
if (isAllJustice && judgeJustice === 0) {
lamp = "ALL JUSTICE CRITICAL";
} else if (isAllJustice) {
lamp = "ALL JUSTICE";
} else if (isFullCombo) {
lamp = "FULL COMBO";
} else if (isClear) {
lamp = "CLEAR";
}
const tachiScore: BatchManualScore = {
score,
lamp,
identifier: musicId.toString(),
matchType: "inGameID",
difficulty: TACHI_DIFFICULTIES[level],
};
if (userPlayDate !== null) {
tachiScore.timeAchieved = fromZonedTime(
parse(
userPlayDate,
"yyyy-MM-dd HH:mm:ss",
toZonedTime(new Date(), "Asia/Tokyo"),
),
"Asia/Tokyo",
).valueOf();
}
if (
judgeCritical !== null &&
judgeJustice !== null &&
judgeAttack !== null &&
judgeGuilty !== null
) {
tachiScore.judgements = {
jcrit: (judgeHeaven ?? 0) + judgeCritical,
justice: judgeJustice,
attack: judgeAttack,
miss: judgeGuilty,
};
}
if (maxCombo !== null) {
tachiScore.optional = {
maxCombo,
};
}
tachiExport.scores.push(tachiScore);
}
return tachiExport;
}

View File

@ -0,0 +1,62 @@
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useToast } from "@/components/ui/use-toast";
// Server action imported from server-side code
import { getTachiExport } from "./action";
const TachiExport = () => {
const { toast } = useToast();
const handleExport = async () => {
try {
const result = await getTachiExport();
if (result) {
const exportedFile = new Blob([JSON.stringify(result, null, 2)], {
type: "application/json",
});
const url = URL.createObjectURL(exportedFile);
const kamafile = document.createElement("a");
kamafile.href = url;
kamafile.download = "tachi_export.json";
document.body.appendChild(kamafile);
kamafile.click();
document.body.removeChild(kamafile);
toast({
title: "Success",
description: "Data exported successfully!",
});
} else {
toast({
title: "Error",
description: "Failed to export data",
});
}
} catch (error: any) {
toast({
title: "Error",
description: error.message || "An error occurred while exporting data",
});
}
};
return (
<Card x-chunk="aimecard">
<CardHeader>
<CardTitle className="text-2xl">Export to kamaitachi</CardTitle>
</CardHeader>
<CardContent>
<Button onClick={handleExport} className="w-full">
Export
</Button>
</CardContent>
</Card>
);
};
export { TachiExport };

View File

@ -0,0 +1,10 @@
import { TachiExport } from "./kamaitachiexport";
const SecuritySettingsPage = async () => {
return (
<div>
<TachiExport />
</div>
);
};
export default SecuritySettingsPage;

View File

@ -2,7 +2,7 @@ import SecuritySettings from "./security";
const SecuritySettingsPage = async () => {
return (
<div className="flex min-h-screen w-full flex-col">
<div>
<SecuritySettings />
</div>
);

View File

@ -29,7 +29,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { getGameList, updatePlayerGameVersionChuni } from "./actions";
import { getGameList, updatePlayerGameVersionChuni } from "./action";
type ChunithmGameVersionSelectionProps = {
chunithmGameVersionNumber: {

View File

@ -1,5 +1,5 @@
import { PlayerChangableChunithmGameVersionSelection } from "./gameSelection";
import { getGameList } from "./actions";
import { getGameList } from "./action";
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
const getAllGamesChunithm = async () => {

BIN
bun.lockb

Binary file not shown.

View File

@ -6,6 +6,8 @@ import { usePathname } from "next/navigation";
const NAV_ITEMS = [
{ href: "/settings/home", label: "General" },
{ href: "/settings/security", label: "Security" },
{ href: "/settings/kamaitachi", label: "Kamaitachi Export" },
{ href: "/settings/versions", label: "Edit Game Version" },
];

View File

@ -58,6 +58,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3",
"embla-carousel-react": "^8.1.5",
"encoding-japanese": "^2.2.0",
"geist": "^1.3.0",