mirror of
https://github.com/PretendoNetwork/mario_kart_8_website.git
synced 2024-05-18 12:50:58 -04:00
Add time trial rankings to mk8 website
This commit is contained in:
parent
d38f258594
commit
89026ac40f
7
.prettierrc.json
Normal file
7
.prettierrc.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"useTabs": true,
|
||||
"printWidth": 150,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": false
|
||||
}
|
33
app/api/rankings/[id]/route.ts
Normal file
33
app/api/rankings/[id]/route.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import app_config from "@/app.config";
|
||||
import { amkj_grpc_client } from "@/helpers/grpc";
|
||||
import { type NextRequest } from 'next/server'
|
||||
import { NextResponse } from "next/server";
|
||||
import { Metadata } from "nice-grpc";
|
||||
|
||||
|
||||
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
|
||||
request.url; // https://nextjs.org/docs/app/building-your-application/routing/router-handlers#dynamic-route-handlers
|
||||
|
||||
try {
|
||||
const trackId = parseInt(params.id);
|
||||
if (isNaN(trackId)) {
|
||||
throw new Error("Invalid track ID");
|
||||
}
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
let ascFilter = true;
|
||||
if (searchParams.has("desc")) {
|
||||
ascFilter = false;
|
||||
}
|
||||
|
||||
const response = await amkj_grpc_client.getTimeTrialRanking({ track: trackId, limit: 10, asc: ascFilter }, {
|
||||
metadata: Metadata({
|
||||
"X-API-Key": app_config.grpc_api_key
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
return NextResponse.json(response, { status: 200 });
|
||||
} catch (err) {
|
||||
return new NextResponse("{}", { status: 500 });
|
||||
}
|
||||
}
|
77
app/page.tsx
77
app/page.tsx
|
@ -1,15 +1,59 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Alert, Badge, Carousel, ListGroup, Tab, Tabs } from "react-bootstrap";
|
||||
|
||||
const HomePage = () => {
|
||||
function getTimeLeftUntilEnd(): string | null {
|
||||
const currentDate = new Date();
|
||||
const currentUTCDate = new Date(currentDate.getTime() + currentDate.getTimezoneOffset() * 60000);
|
||||
|
||||
// Define the target date (8th April 4 PM PDT)
|
||||
const targetDate = new Date("2024-04-08T16:00:00-07:00");
|
||||
if (currentUTCDate > targetDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const differenceMs = targetDate.getTime() - currentUTCDate.getTime();
|
||||
const hours = Math.floor(differenceMs / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((differenceMs % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((differenceMs % (1000 * 60)) / 1000);
|
||||
|
||||
const formattedTime = `${hours} hours, ${minutes} minutes, ${seconds} seconds`;
|
||||
|
||||
return formattedTime;
|
||||
}
|
||||
|
||||
const [timeLeft, setTimeLeft] = useState(getTimeLeftUntilEnd());
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTimeLeft(getTimeLeftUntilEnd());
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="container-fluid mt-5 mb-3 h-100 p-0 d-flex justify-content-center align-items-center">
|
||||
<form className="text-center w-100 p-5 bg-light shadow-lg">
|
||||
<h1 className="mb-3">Welcome to the Pretendo Mario Kart 8 website</h1>
|
||||
<h6><small className="text-muted">A full game server replacement for MK8</small></h6>
|
||||
<h6>
|
||||
<small className="text-muted">A full game server replacement for MK8</small>
|
||||
</h6>
|
||||
{
|
||||
!timeLeft ? (
|
||||
<Alert variant="primary" className="mt-3">
|
||||
The official server will be closed in: <strong>{timeLeft}</strong>
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert variant="danger" className="mt-3">
|
||||
<strong>The official Nintendo server closed on the 8th of April at 4pm EDT</strong>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
<Tabs defaultActiveKey="home" className="mb-3">
|
||||
<Tab eventKey="home" title="Overview">
|
||||
<Carousel className="mb-3" variant="dark">
|
||||
|
@ -50,7 +94,9 @@ const HomePage = () => {
|
|||
<Alert>
|
||||
<Alert.Heading>How to play on this server?</Alert.Heading>
|
||||
<hr />
|
||||
<p>• Follow the steps to join the Pretendo Network on <Link href="https://pretendo.network">our website</Link>!</p>
|
||||
<p>
|
||||
• Follow the steps to join the Pretendo Network on <Link href="https://pretendo.network">our website</Link>!
|
||||
</p>
|
||||
<p>• Create / log on your PNID account and simply start Mario Kart 8</p>
|
||||
</Alert>
|
||||
<Alert variant="success">
|
||||
|
@ -63,7 +109,8 @@ const HomePage = () => {
|
|||
<p>• Have common sense, we may issue a ban even for something that is not on list</p>
|
||||
<hr />
|
||||
<p className="mb-0">
|
||||
<strong>Breaching the rules can get your account or console temporarily/permanently banned from our services</strong> (🤓)
|
||||
<strong>Breaching the rules can get your account or console temporarily/permanently banned from our services</strong>{" "}
|
||||
(🤓)
|
||||
</p>
|
||||
</Alert>
|
||||
</Tab>
|
||||
|
@ -74,7 +121,9 @@ const HomePage = () => {
|
|||
<ListGroup className="mb-3">
|
||||
<strong>
|
||||
<ListGroup.Item variant="primary">Works for everyone</ListGroup.Item>
|
||||
<ListGroup.Item variant="info">Fully/partially a Miiverse related feature, might no be available on Cemu, might require a Tester account.</ListGroup.Item>
|
||||
<ListGroup.Item variant="info">
|
||||
Fully/partially a Miiverse related feature, might no be available on Cemu, might require a Tester account.
|
||||
</ListGroup.Item>
|
||||
<ListGroup.Item variant="danger">Does NOT work</ListGroup.Item>
|
||||
</strong>
|
||||
</ListGroup>
|
||||
|
@ -130,15 +179,21 @@ const HomePage = () => {
|
|||
<p>• Use CEMU 2.0-43 experimental or higher</p>
|
||||
<p>• {"We don't support piracy and we recommend dumping files from your own console."}</p>
|
||||
<p>• {"Files downloaded from the website aren't supported anymore"}</p>
|
||||
<p>• {"If you have unstable frames, plesase don't go online, it ruins the experience for everyone, if it happens too much you will be banned."}</p>
|
||||
<p>
|
||||
•{" "}
|
||||
{
|
||||
"If you have unstable frames, plesase don't go online, it ruins the experience for everyone, if it happens too much you will be banned."
|
||||
}
|
||||
</p>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<p>The server is available on console and CEMU, follow the tutorial on <Link href="https://pretendo.network">the website</Link> to get Pretendo Network on your device.</p>
|
||||
<p>
|
||||
The server is available on console and CEMU, follow the tutorial on{" "}
|
||||
<Link href="https://pretendo.network">the website</Link> to get Pretendo Network on your device.
|
||||
</p>
|
||||
</Alert>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
@ -150,6 +205,6 @@ const HomePage = () => {
|
|||
Mario Kart TV highlight download/upload
|
||||
Mario Kart TV highlight post reply upload/download
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
export default HomePage;
|
||||
|
|
157
app/rankings/[id]/page.tsx
Normal file
157
app/rankings/[id]/page.tsx
Normal file
|
@ -0,0 +1,157 @@
|
|||
"use client";
|
||||
|
||||
import { GetTimeTrialRankingResponse, TimeTrialRanking } from "@/helpers/proto/amkj_service";
|
||||
import TrackList from "@/helpers/types/TrackList";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Alert, Form, Spinner, Table } from "react-bootstrap";
|
||||
|
||||
export default function TrackRankingPage({ params }: { params: { id: string } }) {
|
||||
|
||||
const [rankingResponse, setRankingResponse] = useState<Response | null>(null);
|
||||
|
||||
const [worstRankings, setWorstRankings] = useState<TimeTrialRanking[]>([]);
|
||||
const [worstLastFetch, setWorstLastFetch] = useState<Date | null>(null);
|
||||
|
||||
const [bestRankings, setBestRankings] = useState<TimeTrialRanking[]>([]);
|
||||
const [bestLastFetch, setBestLastFetch] = useState<Date | null>(null);
|
||||
|
||||
const [filterAsc, setFilterAsc] = useState<boolean>(true);
|
||||
|
||||
async function fetchRankings(asc: boolean) {
|
||||
if (asc) {
|
||||
if (bestLastFetch && new Date().getTime() - bestLastFetch.getTime() < 15000) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (worstLastFetch && new Date().getTime() - worstLastFetch.getTime() < 15000) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/rankings/${params.id}?${asc ? "" : "desc"}`, { cache: "no-store" });
|
||||
setRankingResponse(response);
|
||||
const data: GetTimeTrialRankingResponse = await response.json();
|
||||
|
||||
if (asc) {
|
||||
setBestRankings(data.rankings);
|
||||
setBestLastFetch(new Date());
|
||||
} else {
|
||||
setWorstRankings(data.rankings);
|
||||
setWorstLastFetch(new Date());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch rankings:', error);
|
||||
setRankingResponse(null);
|
||||
}
|
||||
}
|
||||
|
||||
function scoreToTime(score: number): string {
|
||||
const milliseconds = score % 1000;
|
||||
const seconds = Math.floor(score / 1000) % 60;
|
||||
const minutes = Math.floor(Math.floor(score / 1000) / 60);
|
||||
return `${minutes}:${seconds.toString().padStart(2, "0")}.${milliseconds.toString().padStart(3, "0")}`;
|
||||
}
|
||||
|
||||
function commonDataToMiiName(data: { data: number[] }): string {
|
||||
const str = data.data;
|
||||
const account_related_data = str.slice(0x14, 0x74);
|
||||
const mii_name = account_related_data.slice(0x1a, 0x2e);
|
||||
|
||||
const u16Array = new Uint16Array(mii_name);
|
||||
let name = "";
|
||||
for (let i = 0; i < u16Array.length; i++) {
|
||||
name += String.fromCharCode(u16Array[i]);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchRankings(filterAsc);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filterAsc]);
|
||||
|
||||
const trackId = parseInt(params.id);
|
||||
if (isNaN(trackId)) {
|
||||
return (
|
||||
<div className="container-fluid mt-5 mb-3 h-100 p-0 d-flex justify-content-center align-items-center">
|
||||
<form className="text-center w-100 p-4 bg-light shadow-lg">
|
||||
<Alert variant="danger">Invalid track ID! (Not a number)</Alert>
|
||||
</form>
|
||||
</div>);
|
||||
}
|
||||
|
||||
const track = TrackList.find((cup) => cup.tracks.find((track) => track.id === trackId))?.tracks.find((track) => track.id === trackId);
|
||||
if (!track) {
|
||||
return (
|
||||
<div className="container-fluid mt-5 mb-3 h-100 p-0 d-flex justify-content-center align-items-center">
|
||||
<form className="text-center w-100 p-4 bg-light shadow-lg">
|
||||
<Alert variant="danger">Invalid track ID! (ID does not exist)</Alert>
|
||||
</form>
|
||||
</div>);
|
||||
}
|
||||
|
||||
const rankings = filterAsc ? bestRankings : worstRankings;
|
||||
|
||||
return (
|
||||
<div className="container-fluid mt-5 mb-3 h-100 p-0 d-flex justify-content-center align-items-center">
|
||||
<form className="text-center w-100 p-4 bg-light shadow-lg">
|
||||
<h1 className="mb-3">Rankings: {track.name}</h1>
|
||||
<Form.Select
|
||||
defaultValue={"0"}
|
||||
className="mx-auto"
|
||||
style={{ width: "18rem" }}
|
||||
onChange={(event) => {
|
||||
const value = parseInt(event.target.value);
|
||||
setFilterAsc(value === 0);
|
||||
fetchRankings(value === 0);
|
||||
|
||||
}}>
|
||||
<option value="0">Filter by best time</option>
|
||||
<option value="1">Filter by worst time</option>
|
||||
</Form.Select>
|
||||
<div className="mt-3">
|
||||
{!rankingResponse && <Spinner animation="border" role="status" />}
|
||||
{rankingResponse && rankingResponse.status !== 200 && (
|
||||
<Alert variant="danger">
|
||||
<Alert.Heading>Error fetching rankings</Alert.Heading>
|
||||
<p>
|
||||
This error should only happen if the NEX server is down, try refreshing the page.
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
If this happens again, contact the developers on the <Alert.Link href="https://discord.gg/pretendo">Pretendo Discord</Alert.Link>
|
||||
</p>
|
||||
</Alert>
|
||||
)}
|
||||
{rankingResponse && rankingResponse.status === 200 && rankings && rankings.length === 0 && <Alert variant="info">No rankings for this track yet!</Alert>}
|
||||
{rankingResponse && rankingResponse.status === 200 && rankings && rankings.length !== 0 && (
|
||||
<Table striped bordered hover responsive className="mx-auto" style={{ maxWidth: "48rem" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>PID</th>
|
||||
<th>Mii Name</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rankings.map((ranking, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>{ranking.rank}</td>
|
||||
<td>{ranking.pid}</td>
|
||||
<td>{commonDataToMiiName(ranking.commonData as any)}</td>
|
||||
<td>{scoreToTime(ranking.score)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
|
@ -1,12 +1,52 @@
|
|||
export default async function RankingsPage() {
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import TrackList from "@/helpers/types/TrackList";
|
||||
import { useState } from "react";
|
||||
import { Card } from "react-bootstrap";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function RankingsPage() {
|
||||
|
||||
const [currentCup, setCurrentCup] = useState<number>(0);
|
||||
|
||||
return (
|
||||
<div className="container-fluid mt-5 mb-3 h-100 p-0 d-flex justify-content-center align-items-center">
|
||||
<form className="text-center w-100 p-5 bg-light shadow-lg">
|
||||
<form className="text-center w-100 p-4 bg-light shadow-lg">
|
||||
<h1 className="mb-3">Rankings</h1>
|
||||
<div className="alert alert-info" role="alert">
|
||||
Not implemented on the website yet.. come back later! (They work in the game though)
|
||||
<h6>Select a cup</h6>
|
||||
<div className="d-flex flex-wrap justify-content-around btn-group">
|
||||
{TrackList.map((cup, cupIdx) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={cup.internalName}
|
||||
className={`d-flex flex-column justify-content-between align-items-center btn btn-outline-primary`}
|
||||
style={{ width: '8rem', whiteSpace: "nowrap", backgroundColor: `${currentCup === cupIdx ? "#0D6EFD80" : "inherit"}` }}
|
||||
onClick={() => setCurrentCup(cupIdx)}
|
||||
>
|
||||
<Image className="rounded-circle" src={`/assets/cup/${cup.internalName}.png`} alt={cup.name + " Icon"} width={100} height={100} />
|
||||
<p className="text-dark"><strong>{cup.name}</strong></p>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="d-flex flex-column justify-content-between align-items-center">
|
||||
{TrackList[currentCup].tracks.map((track) => {
|
||||
return (
|
||||
<Link href={`/rankings/${track.id}`} passHref key={track.internalName} style={{ textDecoration: "none" }}>
|
||||
<Card className="mb-3" style={{ width: '20rem' }}>
|
||||
<Card.Img variant="top" src={`/assets/track/${track.internalName}.png`} alt={track.name + " Icon"} />
|
||||
<Card.Header>
|
||||
<strong>{track.name}</strong>
|
||||
</Card.Header>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</form >
|
||||
</div >
|
||||
);
|
||||
}
|
|
@ -25,6 +25,8 @@ service AmkjService {
|
|||
rpc GetAllTournaments(GetAllTournamentsRequest) returns (GetAllTournamentsResponse) {}
|
||||
|
||||
rpc GetUnlocks(GetUnlocksRequest) returns (GetUnlocksResponse) {}
|
||||
|
||||
rpc GetTimeTrialRanking(GetTimeTrialRankingRequest) returns (GetTimeTrialRankingResponse) {}
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
|
@ -185,4 +187,24 @@ message GetUnlocksResponse {
|
|||
repeated uint32 wing_unlocks = 10;
|
||||
repeated uint32 stamp_unlocks = 11;
|
||||
repeated uint32 dlc_unlocks = 12;
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
|
||||
message TimeTrialRanking {
|
||||
uint32 rank = 1;
|
||||
google.protobuf.Timestamp datetime = 2;
|
||||
uint32 score = 3;
|
||||
uint32 pid = 4;
|
||||
bytes common_data = 5;
|
||||
}
|
||||
|
||||
message GetTimeTrialRankingRequest {
|
||||
uint32 track = 1;
|
||||
int32 limit = 2;
|
||||
bool asc = 3;
|
||||
}
|
||||
|
||||
message GetTimeTrialRankingResponse {
|
||||
repeated TimeTrialRanking rankings = 1;
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable */
|
||||
import Long from "long";
|
||||
import _m0 from "protobufjs/minimal";
|
||||
import * as _m0 from "protobufjs/minimal";
|
||||
|
||||
export const protobufPackage = "google.protobuf";
|
||||
|
||||
|
@ -55,6 +55,7 @@ export const protobufPackage = "google.protobuf";
|
|||
* Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
|
||||
* .setNanos((int) ((millis % 1000) * 1000000)).build();
|
||||
*
|
||||
*
|
||||
* Example 5: Compute Timestamp from Java `Instant.now()`.
|
||||
*
|
||||
* Instant now = Instant.now();
|
||||
|
@ -63,6 +64,7 @@ export const protobufPackage = "google.protobuf";
|
|||
* Timestamp.newBuilder().setSeconds(now.getEpochSecond())
|
||||
* .setNanos(now.getNano()).build();
|
||||
*
|
||||
*
|
||||
* Example 6: Compute Timestamp from current time in Python.
|
||||
*
|
||||
* timestamp = Timestamp()
|
||||
|
@ -96,130 +98,116 @@ export const protobufPackage = "google.protobuf";
|
|||
* ) to obtain a formatter capable of generating timestamps in this format.
|
||||
*/
|
||||
export interface Timestamp {
|
||||
/**
|
||||
* Represents seconds of UTC time since Unix epoch
|
||||
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||
* 9999-12-31T23:59:59Z inclusive.
|
||||
*/
|
||||
seconds: number;
|
||||
/**
|
||||
* Non-negative fractions of a second at nanosecond resolution. Negative
|
||||
* second values with fractions must still have non-negative nanos values
|
||||
* that count forward in time. Must be from 0 to 999,999,999
|
||||
* inclusive.
|
||||
*/
|
||||
nanos: number;
|
||||
/**
|
||||
* Represents seconds of UTC time since Unix epoch
|
||||
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||
* 9999-12-31T23:59:59Z inclusive.
|
||||
*/
|
||||
seconds: number;
|
||||
/**
|
||||
* Non-negative fractions of a second at nanosecond resolution. Negative
|
||||
* second values with fractions must still have non-negative nanos values
|
||||
* that count forward in time. Must be from 0 to 999,999,999
|
||||
* inclusive.
|
||||
*/
|
||||
nanos: number;
|
||||
}
|
||||
|
||||
function createBaseTimestamp(): Timestamp {
|
||||
return { seconds: 0, nanos: 0 };
|
||||
return { seconds: 0, nanos: 0 };
|
||||
}
|
||||
|
||||
export const Timestamp = {
|
||||
encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.seconds !== 0) {
|
||||
writer.uint32(8).int64(message.seconds);
|
||||
}
|
||||
if (message.nanos !== 0) {
|
||||
writer.uint32(16).int32(message.nanos);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.seconds !== 0) {
|
||||
writer.uint32(8).int64(message.seconds);
|
||||
}
|
||||
if (message.nanos !== 0) {
|
||||
writer.uint32(16).int32(message.nanos);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): Timestamp {
|
||||
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseTimestamp();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): Timestamp {
|
||||
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseTimestamp();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
message.seconds = longToNumber(reader.int64() as Long);
|
||||
break;
|
||||
case 2:
|
||||
message.nanos = reader.int32();
|
||||
break;
|
||||
default:
|
||||
reader.skipType(tag & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
message.seconds = longToNumber(reader.int64() as Long);
|
||||
continue;
|
||||
case 2:
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
fromJSON(object: any): Timestamp {
|
||||
return {
|
||||
seconds: isSet(object.seconds) ? Number(object.seconds) : 0,
|
||||
nanos: isSet(object.nanos) ? Number(object.nanos) : 0,
|
||||
};
|
||||
},
|
||||
|
||||
message.nanos = reader.int32();
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
toJSON(message: Timestamp): unknown {
|
||||
const obj: any = {};
|
||||
message.seconds !== undefined && (obj.seconds = Math.round(message.seconds));
|
||||
message.nanos !== undefined && (obj.nanos = Math.round(message.nanos));
|
||||
return obj;
|
||||
},
|
||||
|
||||
fromJSON(object: any): Timestamp {
|
||||
return {
|
||||
seconds: isSet(object.seconds) ? Number(object.seconds) : 0,
|
||||
nanos: isSet(object.nanos) ? Number(object.nanos) : 0,
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: Timestamp): unknown {
|
||||
const obj: any = {};
|
||||
message.seconds !== undefined && (obj.seconds = Math.round(message.seconds));
|
||||
message.nanos !== undefined && (obj.nanos = Math.round(message.nanos));
|
||||
return obj;
|
||||
},
|
||||
|
||||
create(base?: DeepPartial<Timestamp>): Timestamp {
|
||||
return Timestamp.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial(object: DeepPartial<Timestamp>): Timestamp {
|
||||
const message = createBaseTimestamp();
|
||||
message.seconds = object.seconds ?? 0;
|
||||
message.nanos = object.nanos ?? 0;
|
||||
return message;
|
||||
},
|
||||
fromPartial(object: DeepPartial<Timestamp>): Timestamp {
|
||||
const message = createBaseTimestamp();
|
||||
message.seconds = object.seconds ?? 0;
|
||||
message.nanos = object.nanos ?? 0;
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
declare var self: any | undefined;
|
||||
declare var window: any | undefined;
|
||||
declare var global: any | undefined;
|
||||
var tsProtoGlobalThis: any = (() => {
|
||||
if (typeof globalThis !== "undefined") {
|
||||
return globalThis;
|
||||
}
|
||||
if (typeof self !== "undefined") {
|
||||
return self;
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
return window;
|
||||
}
|
||||
if (typeof global !== "undefined") {
|
||||
return global;
|
||||
}
|
||||
throw "Unable to locate global object";
|
||||
var globalThis: any = (() => {
|
||||
if (typeof globalThis !== "undefined") return globalThis;
|
||||
if (typeof self !== "undefined") return self;
|
||||
if (typeof window !== "undefined") return window;
|
||||
if (typeof global !== "undefined") return global;
|
||||
throw "Unable to locate global object";
|
||||
})();
|
||||
|
||||
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
|
||||
|
||||
export type DeepPartial<T> = T extends Builtin ? T
|
||||
: T extends Array<infer U> ? Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||
: Partial<T>;
|
||||
export type DeepPartial<T> = T extends Builtin
|
||||
? T
|
||||
: T extends Array<infer U>
|
||||
? Array<DeepPartial<U>>
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends {}
|
||||
? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||
: Partial<T>;
|
||||
|
||||
function longToNumber(long: Long): number {
|
||||
if (long.gt(Number.MAX_SAFE_INTEGER)) {
|
||||
throw new tsProtoGlobalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
|
||||
}
|
||||
return long.toNumber();
|
||||
if (long.gt(Number.MAX_SAFE_INTEGER)) {
|
||||
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
|
||||
}
|
||||
return long.toNumber();
|
||||
}
|
||||
|
||||
// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
|
||||
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
|
||||
if (_m0.util.Long !== Long) {
|
||||
_m0.util.Long = Long as any;
|
||||
_m0.configure();
|
||||
_m0.util.Long = Long as any;
|
||||
_m0.configure();
|
||||
}
|
||||
|
||||
function isSet(value: any): boolean {
|
||||
return value !== null && value !== undefined;
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
|
124
helpers/types/TrackList.ts
Normal file
124
helpers/types/TrackList.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
const TrackList = [
|
||||
{
|
||||
name: "Mushroom Cup",
|
||||
internalName: "Mushroom",
|
||||
tracks: [
|
||||
{ id: 27, name: "Mario Kart Stadium", internalName: "Gu_FirstCircuit" },
|
||||
{ id: 28, name: "Water Park", internalName: "Gu_WaterPark" },
|
||||
{ id: 19, name: "Sweet Sweet Canyon", internalName: "Gu_Cake" },
|
||||
{ id: 17, name: "Thwomp Ruins", internalName: "Gu_DossunIseki" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Flower Cup",
|
||||
internalName: "Flower",
|
||||
tracks: [
|
||||
{ id: 16, name: "Mario Circuit", internalName: "Gu_MarioCircuit" },
|
||||
{ id: 18, name: "Toad Harbour", internalName: "Gu_City" },
|
||||
{ id: 20, name: "Twisted Mansion", internalName: "Gu_HorrorHouse" },
|
||||
{ id: 21, name: "Shy Guy Falls", internalName: "Gu_Expert" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Star Cup",
|
||||
internalName: "Star",
|
||||
tracks: [
|
||||
{ id: 26, name: "Sunshine Airport", internalName: "Gu_Airport" },
|
||||
{ id: 29, name: "Dolphin Shoals", internalName: "Gu_Ocean" },
|
||||
{ id: 25, name: "Electrodrome", internalName: "Gu_Techno" },
|
||||
{ id: 24, name: "Mount Wario", internalName: "Gu_SnowMountain" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Special Cup",
|
||||
internalName: "Special",
|
||||
tracks: [
|
||||
{ id: 23, name: "Cloudtop Cruise", internalName: "Gu_Cloud" },
|
||||
{ id: 22, name: "Bone Dry Dunes", internalName: "Gu_Desert" },
|
||||
{ id: 30, name: "Bowser's Castle", internalName: "Gu_BowserCastle" },
|
||||
{ id: 31, name: "Rainbow Road", internalName: "Gu_RainbowRoad" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Shell Cup",
|
||||
internalName: "Shell",
|
||||
tracks: [
|
||||
{ id: 33, name: "Wii Moo Moo Meadows", internalName: "Gwii_MooMooMeadows" },
|
||||
{ id: 38, name: "GBA Mario Circuit", internalName: "Gagb_MarioCircuit" },
|
||||
{ id: 36, name: "DS Cheep Cheep Beach", internalName: "Gds_PukupukuBeach" },
|
||||
{ id: 35, name: "N64 Toad's Turnpike", internalName: "G64_KinopioHighway" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Banana Cup",
|
||||
internalName: "Banana",
|
||||
tracks: [
|
||||
{ id: 42, name: "GCN Dry Dry Desert", internalName: "Ggc_DryDryDesert" },
|
||||
{ id: 41, name: "SNES Donut Plains 3", internalName: "Gsfc_DonutsPlain3" },
|
||||
{ id: 34, name: "N64 Royal Raceway", internalName: "G64_PeachCircuit" },
|
||||
{ id: 32, name: "3DS DK Jungle", internalName: "G3ds_DKJungle" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Leaf Cup",
|
||||
internalName: "Leaf",
|
||||
tracks: [
|
||||
{ id: 46, name: "DS Wario Stadium", internalName: "Gds_WarioStadium" },
|
||||
{ id: 37, name: "GCN Sherbet Land", internalName: "Ggc_SherbetLand" },
|
||||
{ id: 39, name: "3DS Melody Motorway", internalName: "G3ds_MusicPark" },
|
||||
{ id: 45, name: "N64 Yoshi Valley", internalName: "G64_YoshiValley" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Lightning Cup",
|
||||
internalName: "Thunder",
|
||||
tracks: [
|
||||
{ id: 44, name: "DS Tick-Tock Clock", internalName: "Gds_TickTockClock" },
|
||||
{ id: 43, name: "3DS Piranha Plant Pipeway", internalName: "G3ds_PackunSlider" },
|
||||
{ id: 40, name: "Wii Grumble Volcano", internalName: "Gwii_GrumbleVolcano" },
|
||||
{ id: 47, name: "N64 Rainbow Road", internalName: "G64_RainbowRoad" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Egg Cup",
|
||||
internalName: "DLC02",
|
||||
tracks: [
|
||||
{ id: 56, name: "GCN Yoshi Circuit", internalName: "Dgc_YoshiCircuit" },
|
||||
{ id: 53, name: "Excitebike Arena", internalName: "Du_ExciteBike" },
|
||||
{ id: 50, name: "Dragon Driftway", internalName: "Du_DragonRoad" },
|
||||
{ id: 49, name: "Mute City", internalName: "Du_MuteCity" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Triforce Cup",
|
||||
internalName: "DLC03",
|
||||
tracks: [
|
||||
{ id: 57, name: "Wii Wario's Gold Mine", internalName: "Dwii_WariosMine" },
|
||||
{ id: 58, name: "SNES Rainbow Road", internalName: "Dsfc_RainbowRoad" },
|
||||
{ id: 55, name: "Ice Ice Outpost", internalName: "Du_IcePark" },
|
||||
{ id: 51, name: "Hyrule Circuit", internalName: "Du_Hyrule" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Crossing Cup",
|
||||
internalName: "DLC04",
|
||||
tracks: [
|
||||
{ id: 61, name: "GCN Baby Park", internalName: "Dgc_BabyPark" },
|
||||
{ id: 62, name: "GBA Cheese Land", internalName: "Dagb_CheeseLand" },
|
||||
{ id: 54, name: "Wild Woods", internalName: "Du_Woods" },
|
||||
{ id: 52, name: "Animal Crossing", internalName: "Du_Animal" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Bell Cup",
|
||||
internalName: "DLC05",
|
||||
tracks: [
|
||||
{ id: 60, name: "3DS Koopa City", internalName: "D3ds_NeoBowserCity" },
|
||||
{ id: 59, name: "GBA Ribbon Road", internalName: "Dagb_RibbonRoad" },
|
||||
{ id: 48, name: "Super Bell Subway", internalName: "Du_Metro" },
|
||||
{ id: 63, name: "Big Blue", internalName: "Du_BigBlue" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default TrackList;
|
1765
package-lock.json
generated
1765
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -15,21 +15,21 @@
|
|||
"@types/react-dom": "18.2.6",
|
||||
"bootstrap": "^5.3.0",
|
||||
"eslint": "8.43.0",
|
||||
"eslint-config-next": "13.4.7",
|
||||
"eslint-config-next": "^14.1.4",
|
||||
"jose": "^4.14.4",
|
||||
"long": "^5.2.3",
|
||||
"next": "13.4.7",
|
||||
"nice-grpc": "^2.1.4",
|
||||
"next": "^14.1.4",
|
||||
"nice-grpc": "^2.1.8",
|
||||
"protobufjs": "^7.2.4",
|
||||
"react": "18.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.8.0",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.10.1",
|
||||
"typescript": "5.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grpc-tools": "^1.12.4",
|
||||
"ts-proto": "^1.150.1"
|
||||
"ts-proto": "^1.112.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue