refactor(minigames): move routes, components, and libs under minigames parents
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
76fbaba293
commit
25975db23f
63 changed files with 250 additions and 92 deletions
85
playwright-report/index.html
Normal file
85
playwright-report/index.html
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -5,7 +5,7 @@ import { headers as nextHeaders } from "next/headers";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ArrowLeft, Bomb } from "lucide-react";
|
import { ArrowLeft, Bomb } from "lucide-react";
|
||||||
import { CustomMinefieldArena } from "@/components/frontend/eod/CustomMinefieldArena";
|
import { CustomMinefieldArena } from "@/components/frontend/minigames/eod/CustomMinefieldArena";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Play Custom Minefield (Polaris Task Force)",
|
title: "Play Custom Minefield (Polaris Task Force)",
|
||||||
|
|
@ -51,7 +51,7 @@ export default async function CustomMinefieldPlayPage({
|
||||||
<div className="flex min-w-0 flex-col gap-6 p-5">
|
<div className="flex min-w-0 flex-col gap-6 p-5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
href="/eod/custom"
|
href="/minigames/eod/custom"
|
||||||
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
|
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="size-4" /> All custom minefields
|
<ArrowLeft className="size-4" /> All custom minefields
|
||||||
|
|
@ -6,7 +6,7 @@ import { emitGameEvent } from "@/utils/event-log/emit";
|
||||||
import { EventTypes } from "@/utils/event-log/eventTypes";
|
import { EventTypes } from "@/utils/event-log/eventTypes";
|
||||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||||
import { broadcastToUser } from "@/lib/realtime/bus";
|
import { broadcastToUser } from "@/lib/realtime/bus";
|
||||||
import { creditEodXp } from "@/lib/custom-minigames/payouts";
|
import { creditEodXp } from "@/lib/minigames/custom/payouts";
|
||||||
import {
|
import {
|
||||||
applyCreatorBank,
|
applyCreatorBank,
|
||||||
customPlayerXp,
|
customPlayerXp,
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
validateMinefieldName,
|
validateMinefieldName,
|
||||||
validateMinefieldScore,
|
validateMinefieldScore,
|
||||||
validateMinePositions,
|
validateMinePositions,
|
||||||
} from "@/lib/custom-minigames/eod";
|
} from "@/lib/minigames/custom/eod";
|
||||||
|
|
||||||
interface ActionResult<T = undefined> {
|
interface ActionResult<T = undefined> {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|
@ -3,8 +3,8 @@ import config from "@payload-config";
|
||||||
import { getPayload } from "payload";
|
import { getPayload } from "payload";
|
||||||
import { headers as nextHeaders } from "next/headers";
|
import { headers as nextHeaders } from "next/headers";
|
||||||
import { Bomb } from "lucide-react";
|
import { Bomb } from "lucide-react";
|
||||||
import { CustomMinefieldBrowser } from "@/components/frontend/eod/CustomMinefieldBrowser";
|
import { CustomMinefieldBrowser } from "@/components/frontend/minigames/eod/CustomMinefieldBrowser";
|
||||||
import { isEodUnlocked } from "@/lib/custom-minigames/eod";
|
import { isEodUnlocked } from "@/lib/minigames/custom/eod";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Custom Minefields (Polaris Task Force)",
|
title: "Custom Minefields (Polaris Task Force)",
|
||||||
|
|
@ -4,7 +4,7 @@ import { getPayload } from "payload";
|
||||||
import { headers as nextHeaders } from "next/headers";
|
import { headers as nextHeaders } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Bomb } from "lucide-react";
|
import { Bomb } from "lucide-react";
|
||||||
import { EodArena } from "@/components/frontend/eod/EodArena";
|
import { EodArena } from "@/components/frontend/minigames/eod/EodArena";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Minefield Clearance (Polaris Task Force)",
|
title: "Minefield Clearance (Polaris Task Force)",
|
||||||
|
|
@ -18,7 +18,7 @@ export default async function EodPage() {
|
||||||
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(`/login?returnTo=${encodeURIComponent("/eod")}`);
|
redirect(`/login?returnTo=${encodeURIComponent("/minigames/eod")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileRes = await payload.find({
|
const profileRes = await payload.find({
|
||||||
|
|
@ -4,7 +4,7 @@ import { getPayload } from "payload";
|
||||||
import { headers as nextHeaders } from "next/headers";
|
import { headers as nextHeaders } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Bird } from "lucide-react";
|
import { Bird } from "lucide-react";
|
||||||
import { FlappyArena } from "@/components/frontend/flappy/FlappyArena";
|
import { FlappyArena } from "@/components/frontend/minigames/flappy/FlappyArena";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Flight Simulator: Polaris Task Force",
|
title: "Flight Simulator: Polaris Task Force",
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
isOpordDifficulty,
|
isOpordDifficulty,
|
||||||
minOpordRunTimeMs,
|
minOpordRunTimeMs,
|
||||||
type OpordDifficulty,
|
type OpordDifficulty,
|
||||||
} from "@/lib/opord-recall";
|
} from "@/lib/minigames/opord-recall";
|
||||||
import { emitGameEvent } from "@/utils/event-log/emit";
|
import { emitGameEvent } from "@/utils/event-log/emit";
|
||||||
import { EventTypes } from "@/utils/event-log/eventTypes";
|
import { EventTypes } from "@/utils/event-log/eventTypes";
|
||||||
import { broadcastToUser } from "@/lib/realtime/bus";
|
import { broadcastToUser } from "@/lib/realtime/bus";
|
||||||
|
|
@ -4,7 +4,7 @@ import { getPayload } from "payload";
|
||||||
import { headers as nextHeaders } from "next/headers";
|
import { headers as nextHeaders } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { ScrollText } from "lucide-react";
|
import { ScrollText } from "lucide-react";
|
||||||
import { OpordArena } from "@/components/frontend/opord/OpordArena";
|
import { OpordArena } from "@/components/frontend/minigames/opord/OpordArena";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "OPORD Recall (Polaris Task Force)",
|
title: "OPORD Recall (Polaris Task Force)",
|
||||||
|
|
@ -18,7 +18,7 @@ export default async function OpordPage() {
|
||||||
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(`/login?returnTo=${encodeURIComponent("/opord")}`);
|
redirect(`/login?returnTo=${encodeURIComponent("/minigames/opord")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileRes = await payload.find({
|
const profileRes = await payload.find({
|
||||||
|
|
@ -14,7 +14,7 @@ import {
|
||||||
MAX_HITS,
|
MAX_HITS,
|
||||||
MAX_DESPAWNED_TARGETS,
|
MAX_DESPAWNED_TARGETS,
|
||||||
MAX_CIVILIAN_HITS,
|
MAX_CIVILIAN_HITS,
|
||||||
} from "@/lib/aim-trainer";
|
} from "@/lib/minigames/aim-trainer";
|
||||||
|
|
||||||
interface ActionResult<T = undefined> {
|
interface ActionResult<T = undefined> {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|
@ -5,8 +5,8 @@ import { headers as nextHeaders } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Crosshair } from "lucide-react";
|
import { Crosshair } from "lucide-react";
|
||||||
|
|
||||||
import type { AimTrainerStats } from "@/app/(frontend)/qualification/actions";
|
import type { AimTrainerStats } from "@/app/(frontend)/minigames/qualification/actions";
|
||||||
import { AimTrainerArena } from "@/components/frontend/qualification/AimTrainerArena";
|
import { AimTrainerArena } from "@/components/frontend/minigames/qualification/AimTrainerArena";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Qualification Course: Polaris Task Force",
|
title: "Qualification Course: Polaris Task Force",
|
||||||
|
|
@ -20,7 +20,7 @@ export default async function QualificationPage() {
|
||||||
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(`/login?returnTo=${encodeURIComponent("/qualification")}`);
|
redirect(`/login?returnTo=${encodeURIComponent("/minigames/qualification")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileRes = await payload.find({
|
const profileRes = await payload.find({
|
||||||
|
|
@ -5,7 +5,7 @@ import { headers as nextHeaders } from "next/headers";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ArrowLeft, Radio } from "lucide-react";
|
import { ArrowLeft, Radio } from "lucide-react";
|
||||||
import { CustomRadioArena } from "@/components/frontend/radio/CustomRadioArena";
|
import { CustomRadioArena } from "@/components/frontend/minigames/radio/CustomRadioArena";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Play Custom Radio Test (Polaris Task Force)",
|
title: "Play Custom Radio Test (Polaris Task Force)",
|
||||||
|
|
@ -45,7 +45,7 @@ export default async function CustomRadioTestPlayPage({
|
||||||
<div className="flex min-w-0 flex-col gap-6 p-5">
|
<div className="flex min-w-0 flex-col gap-6 p-5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
href="/radio/custom"
|
href="/minigames/radio/custom"
|
||||||
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
|
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="size-4" /> All custom radio tests
|
<ArrowLeft className="size-4" /> All custom radio tests
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
validateMessages,
|
validateMessages,
|
||||||
validateRadioScore,
|
validateRadioScore,
|
||||||
validateTestName,
|
validateTestName,
|
||||||
} from "@/lib/custom-minigames/radio";
|
} from "@/lib/minigames/custom/radio";
|
||||||
|
|
||||||
interface ActionResult<T = undefined> {
|
interface ActionResult<T = undefined> {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|
@ -3,8 +3,8 @@ import config from "@payload-config";
|
||||||
import { getPayload } from "payload";
|
import { getPayload } from "payload";
|
||||||
import { headers as nextHeaders } from "next/headers";
|
import { headers as nextHeaders } from "next/headers";
|
||||||
import { Radio } from "lucide-react";
|
import { Radio } from "lucide-react";
|
||||||
import { CustomTestBrowser } from "@/components/frontend/radio/CustomTestBrowser";
|
import { CustomTestBrowser } from "@/components/frontend/minigames/radio/CustomTestBrowser";
|
||||||
import { isRadioUnlocked } from "@/lib/custom-minigames/radio";
|
import { isRadioUnlocked } from "@/lib/minigames/custom/radio";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Custom Radio Tests (Polaris Task Force)",
|
title: "Custom Radio Tests (Polaris Task Force)",
|
||||||
|
|
@ -4,7 +4,7 @@ import { getPayload } from "payload";
|
||||||
import { headers as nextHeaders } from "next/headers";
|
import { headers as nextHeaders } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Radio } from "lucide-react";
|
import { Radio } from "lucide-react";
|
||||||
import { RadioArena } from "@/components/frontend/radio/RadioArena";
|
import { RadioArena } from "@/components/frontend/minigames/radio/RadioArena";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Radio Traffic (Polaris Task Force)",
|
title: "Radio Traffic (Polaris Task Force)",
|
||||||
|
|
@ -18,7 +18,7 @@ export default async function RadioPage() {
|
||||||
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
const { user } = await payload.auth({ headers, canSetHeaders: false });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(`/login?returnTo=${encodeURIComponent("/radio")}`);
|
redirect(`/login?returnTo=${encodeURIComponent("/minigames/radio")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileRes = await payload.find({
|
const profileRes = await payload.find({
|
||||||
|
|
@ -224,35 +224,35 @@ export const navigationCommands: NavigationCommand[] = [
|
||||||
id: "nav:qualification-course",
|
id: "nav:qualification-course",
|
||||||
label: "Qualification Course",
|
label: "Qualification Course",
|
||||||
icon: Crosshair,
|
icon: Crosshair,
|
||||||
href: "/qualification",
|
href: "/minigames/qualification",
|
||||||
keywords: ["aim trainer", "minigame", "shooting", "practice"],
|
keywords: ["aim trainer", "minigame", "shooting", "practice"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "nav:radio-traffic",
|
id: "nav:radio-traffic",
|
||||||
label: "Radio Traffic",
|
label: "Radio Traffic",
|
||||||
icon: Radio,
|
icon: Radio,
|
||||||
href: "/radio",
|
href: "/minigames/radio",
|
||||||
keywords: ["minigame", "comms", "messages", "training"],
|
keywords: ["minigame", "comms", "messages", "training"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "nav:flight-simulator",
|
id: "nav:flight-simulator",
|
||||||
label: "Flight Simulator",
|
label: "Flight Simulator",
|
||||||
icon: PlaneTakeoff,
|
icon: PlaneTakeoff,
|
||||||
href: "/flappy",
|
href: "/minigames/flappy",
|
||||||
keywords: ["minigame", "flappy", "fly", "training"],
|
keywords: ["minigame", "flappy", "fly", "training"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "nav:minefield-clearance",
|
id: "nav:minefield-clearance",
|
||||||
label: "Minefield Clearance",
|
label: "Minefield Clearance",
|
||||||
icon: Bomb,
|
icon: Bomb,
|
||||||
href: "/eod",
|
href: "/minigames/eod",
|
||||||
keywords: ["minigame", "eod", "mines", "training"],
|
keywords: ["minigame", "eod", "mines", "training"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "nav:opord-recall",
|
id: "nav:opord-recall",
|
||||||
label: "OPORD Recall",
|
label: "OPORD Recall",
|
||||||
icon: ScrollText,
|
icon: ScrollText,
|
||||||
href: "/opord",
|
href: "/minigames/opord",
|
||||||
keywords: ["minigame", "briefing", "memory", "operations order", "training"],
|
keywords: ["minigame", "briefing", "memory", "operations order", "training"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -144,31 +144,31 @@ export function NavUser({
|
||||||
</DropdownMenuSubTrigger>
|
</DropdownMenuSubTrigger>
|
||||||
<DropdownMenuSubContent>
|
<DropdownMenuSubContent>
|
||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem asChild>
|
||||||
<Link href="/flappy">
|
<Link href="/minigames/flappy">
|
||||||
<Bird />
|
<Bird />
|
||||||
Flight Simulator
|
Flight Simulator
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem asChild>
|
||||||
<Link href="/eod">
|
<Link href="/minigames/eod">
|
||||||
<Bomb />
|
<Bomb />
|
||||||
Minefield Clearance
|
Minefield Clearance
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem asChild>
|
||||||
<Link href="/radio">
|
<Link href="/minigames/radio">
|
||||||
<Radio />
|
<Radio />
|
||||||
Radio Traffic
|
Radio Traffic
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem asChild>
|
||||||
<Link href="/qualification">
|
<Link href="/minigames/qualification">
|
||||||
<Crosshair />
|
<Crosshair />
|
||||||
Qualification Course
|
Qualification Course
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem asChild>
|
||||||
<Link href="/opord">
|
<Link href="/minigames/opord">
|
||||||
<FileTextIcon />
|
<FileTextIcon />
|
||||||
OPORD Recall
|
OPORD Recall
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
51
src/components/frontend/minigames/UgcLinkButton.tsx
Normal file
51
src/components/frontend/minigames/UgcLinkButton.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
import { ExternalLink, Wrench } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully-featured nav button for a minigame's custom (UGC) area: an eyebrow
|
||||||
|
* status line, a title, and a pitch subtitle. Neutral outline styling, and the
|
||||||
|
* eyebrow flips between the unlocked celebration and the locked state.
|
||||||
|
*/
|
||||||
|
export function UgcLinkButton({
|
||||||
|
href,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
unlocked,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
unlocked: boolean;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
className={cn("relative h-auto w-full gap-4 whitespace-normal px-4 py-4 text-left", className)}
|
||||||
|
>
|
||||||
|
<Link href={href}>
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className="pointer-events-none absolute inset-0 animate-pulse rounded-md border-2 border-white/60"
|
||||||
|
/>
|
||||||
|
<span className="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted">
|
||||||
|
<Wrench className="size-5" />
|
||||||
|
</span>
|
||||||
|
<span className="flex min-w-0 flex-col items-start gap-1">
|
||||||
|
<span className="text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
|
||||||
|
{unlocked ? "You've unlocked" : "Locked"}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-semibold text-foreground">{title}</span>
|
||||||
|
<span className="text-xs font-normal leading-relaxed text-muted-foreground">
|
||||||
|
{subtitle}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<ExternalLink className="ml-auto shrink-0 text-muted-foreground" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { createMinefield } from "@/app/(frontend)/eod/custom/actions";
|
import { createMinefield } from "@/app/(frontend)/minigames/eod/custom/actions";
|
||||||
|
|
||||||
const MIN_SIZE = 9;
|
const MIN_SIZE = 9;
|
||||||
const MAX_SIZE = 500;
|
const MAX_SIZE = 500;
|
||||||
|
|
@ -127,7 +127,7 @@ export function CustomMinefieldBrowser({
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button asChild size="sm">
|
<Button asChild size="sm">
|
||||||
<Link href={`/eod/custom/${row.id}`}>
|
<Link href={`/minigames/eod/custom/${row.id}`}>
|
||||||
<Play /> Play
|
<Play /> Play
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
import { useCallback, useEffect, useRef, useState, type CSSProperties, type PointerEvent } from "react";
|
import { useCallback, useEffect, useRef, useState, type CSSProperties, type PointerEvent } from "react";
|
||||||
import { Bomb, CircleHelp, Flag, RotateCcw, ShieldCheck, Timer, X } from "lucide-react";
|
import { Bomb, CircleHelp, Flag, RotateCcw, ShieldCheck, Timer, X } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { submitCustomMinefieldScore } from "@/app/(frontend)/eod/custom/actions";
|
import { submitCustomMinefieldScore } from "@/app/(frontend)/minigames/eod/custom/actions";
|
||||||
import { useSessionExpired } from "@/hooks/useSessionExpired";
|
import { useSessionExpired } from "@/hooks/useSessionExpired";
|
||||||
import { edgeMineIndices } from "@/lib/custom-minigames/eod";
|
import { edgeMineIndices } from "@/lib/minigames/custom/eod";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { playClear, playExplosion, playFlag, playReveal } from "./sounds";
|
import { playClear, playExplosion, playFlag, playReveal } from "./sounds";
|
||||||
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import Link from "next/link";
|
import type { EodDifficulty, EodStats } from "@/app/(frontend)/minigames/eod/actions";
|
||||||
import { Bomb } from "lucide-react";
|
import { isEodUnlocked } from "@/lib/minigames/custom/eod";
|
||||||
import type { EodDifficulty, EodStats } from "@/app/(frontend)/eod/actions";
|
|
||||||
import { isEodUnlocked } from "@/lib/custom-minigames/eod";
|
|
||||||
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
||||||
import { useSidebar } from "@/components/ui/sidebar";
|
import { useSidebar } from "@/components/ui/sidebar";
|
||||||
import { UnlockNotice } from "@/components/frontend/minigames/UnlockNotice";
|
import { UnlockNotice } from "@/components/frontend/minigames/UnlockNotice";
|
||||||
|
import { UgcLinkButton } from "@/components/frontend/minigames/UgcLinkButton";
|
||||||
import { EodLeaderboard } from "./EodLeaderboard";
|
import { EodLeaderboard } from "./EodLeaderboard";
|
||||||
import { MinesweeperBoard } from "./MinesweeperBoard";
|
import { MinesweeperBoard } from "./MinesweeperBoard";
|
||||||
|
|
||||||
|
|
@ -44,13 +43,13 @@ export function EodArena({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-6">
|
<div className="flex flex-col items-center gap-6">
|
||||||
<Link
|
<UgcLinkButton
|
||||||
href="/eod/custom"
|
href="/minigames/eod/custom"
|
||||||
className="flex items-center gap-1.5 rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-1.5 text-sm text-amber-300 transition-colors hover:bg-amber-500/20"
|
title="UGC Minefields"
|
||||||
>
|
subtitle="Play, compete for, and create custom minefields you can share with other unit members! You can even earn XP for playing maps, and as an author, every payout on your map nets you some XP, too."
|
||||||
<Bomb className="size-4" />
|
unlocked={customUnlocked}
|
||||||
Custom minefields
|
className="max-w-5xl"
|
||||||
</Link>
|
/>
|
||||||
{!customUnlocked && (
|
{!customUnlocked && (
|
||||||
<UnlockNotice
|
<UnlockNotice
|
||||||
title="custom minefields"
|
title="custom minefields"
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { EodDifficulty } from "@/app/(frontend)/eod/actions";
|
import type { EodDifficulty } from "@/app/(frontend)/minigames/eod/actions";
|
||||||
import type { PresenceParticipant } from "@/hooks/useRealtimePresence";
|
import type { PresenceParticipant } from "@/hooks/useRealtimePresence";
|
||||||
|
|
||||||
interface ApiUser {
|
interface ApiUser {
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { updateMinefield } from "@/app/(frontend)/eod/custom/actions";
|
import { updateMinefield } from "@/app/(frontend)/minigames/eod/custom/actions";
|
||||||
|
|
||||||
export function ExposureRuleToggle({
|
export function ExposureRuleToggle({
|
||||||
minefieldId,
|
minefieldId,
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { submitEodResult, type EodDifficulty, type EodStats } from "@/app/(frontend)/eod/actions";
|
import { submitEodResult, type EodDifficulty, type EodStats } from "@/app/(frontend)/minigames/eod/actions";
|
||||||
import { PROFILE_UPDATE_EVENT } from "@/hooks/useGameTick";
|
import { PROFILE_UPDATE_EVENT } from "@/hooks/useGameTick";
|
||||||
import { useSessionExpired } from "@/hooks/useSessionExpired";
|
import { useSessionExpired } from "@/hooks/useSessionExpired";
|
||||||
import { LoginLink } from "@/components/frontend/auth/LoginLink";
|
import { LoginLink } from "@/components/frontend/auth/LoginLink";
|
||||||
|
|
@ -662,7 +662,7 @@ export function MinesweeperBoard({
|
||||||
phase === "playing" && "pointer-events-none opacity-40",
|
phase === "playing" && "pointer-events-none opacity-40",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="grid gap-2 md:grid-cols-3">
|
<div className={cn("grid gap-2", customUnlocked ? "md:grid-cols-4" : "md:grid-cols-3")}>
|
||||||
{DIFFICULTY_KEYS.map((key) => {
|
{DIFFICULTY_KEYS.map((key) => {
|
||||||
const option = DIFFICULTIES[key];
|
const option = DIFFICULTIES[key];
|
||||||
return (
|
return (
|
||||||
|
|
@ -684,8 +684,26 @@ export function MinesweeperBoard({
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{customUnlocked && (
|
||||||
|
<Button
|
||||||
|
key="custom"
|
||||||
|
type="button"
|
||||||
|
variant={difficulty === "custom" ? "default" : "outline"}
|
||||||
|
className="h-auto justify-start px-3 py-2 text-left"
|
||||||
|
disabled={submitting || expired}
|
||||||
|
aria-pressed={difficulty === "custom"}
|
||||||
|
onClick={() => reset("custom")}
|
||||||
|
>
|
||||||
|
<span className="flex flex-col items-start gap-0.5">
|
||||||
|
<span>Custom</span>
|
||||||
|
<span className="text-[10px] font-normal opacity-75">
|
||||||
|
{customConfig.rows}×{customConfig.cols} grid · {customConfig.mines} charges
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{customUnlocked ? (
|
{customUnlocked && difficulty === "custom" ? (
|
||||||
<div className="rounded-lg border border-emerald-500/40 bg-emerald-500/5 p-3">
|
<div className="rounded-lg border border-emerald-500/40 bg-emerald-500/5 p-3">
|
||||||
<div className="flex flex-wrap items-end gap-3">
|
<div className="flex flex-wrap items-end gap-3">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
|
|
@ -792,11 +810,11 @@ export function MinesweeperBoard({
|
||||||
{CUSTOM_MAX_SIZE}×{CUSTOM_MAX_SIZE} field.
|
{CUSTOM_MAX_SIZE}×{CUSTOM_MAX_SIZE} field.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : !customUnlocked ? (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Clear all three standard locations to unlock custom minefields.
|
Clear all three standard locations to unlock custom minefields.
|
||||||
</p>
|
</p>
|
||||||
)}
|
) : null}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import type { FlappyStats } from "@/app/(frontend)/flappy/actions";
|
import type { FlappyStats } from "@/app/(frontend)/minigames/flappy/actions";
|
||||||
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
||||||
import { FlappyBird } from "./FlappyBird";
|
import { FlappyBird } from "./FlappyBird";
|
||||||
import { FlappyLeaderboard } from "./FlappyLeaderboard";
|
import { FlappyLeaderboard } from "./FlappyLeaderboard";
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { type FlappyStats, submitFlappyScore } from "@/app/(frontend)/flappy/actions";
|
import { type FlappyStats, submitFlappyScore } from "@/app/(frontend)/minigames/flappy/actions";
|
||||||
import { PROFILE_UPDATE_EVENT } from "@/hooks/useGameTick";
|
import { PROFILE_UPDATE_EVENT } from "@/hooks/useGameTick";
|
||||||
import { useSessionExpired } from "@/hooks/useSessionExpired";
|
import { useSessionExpired } from "@/hooks/useSessionExpired";
|
||||||
import { LoginLink } from "@/components/frontend/auth/LoginLink";
|
import { LoginLink } from "@/components/frontend/auth/LoginLink";
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { OPORD_DIFFICULTIES, type OpordDifficulty } from "@/lib/opord-recall";
|
import { OPORD_DIFFICULTIES, type OpordDifficulty } from "@/lib/minigames/opord-recall";
|
||||||
import type { OpordStats } from "@/app/(frontend)/opord/actions";
|
import type { OpordStats } from "@/app/(frontend)/minigames/opord/actions";
|
||||||
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
||||||
import { useSidebar } from "@/components/ui/sidebar";
|
import { useSidebar } from "@/components/ui/sidebar";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
@ -15,9 +15,9 @@ import {
|
||||||
type OpordDifficulty,
|
type OpordDifficulty,
|
||||||
type OpordQuestion,
|
type OpordQuestion,
|
||||||
type OpordScenario,
|
type OpordScenario,
|
||||||
} from "@/lib/opord-recall";
|
} from "@/lib/minigames/opord-recall";
|
||||||
import type { OpordStats } from "@/app/(frontend)/opord/actions";
|
import type { OpordStats } from "@/app/(frontend)/minigames/opord/actions";
|
||||||
import { submitOpordRecallResult } from "@/app/(frontend)/opord/actions";
|
import { submitOpordRecallResult } from "@/app/(frontend)/minigames/opord/actions";
|
||||||
import { useSessionExpired } from "@/hooks/useSessionExpired";
|
import { useSessionExpired } from "@/hooks/useSessionExpired";
|
||||||
import { PROFILE_UPDATE_EVENT } from "@/hooks/useGameTick";
|
import { PROFILE_UPDATE_EVENT } from "@/hooks/useGameTick";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Trophy } from "lucide-react";
|
import { Trophy } from "lucide-react";
|
||||||
|
|
||||||
import type { OpordDifficulty } from "@/lib/opord-recall";
|
import type { OpordDifficulty } from "@/lib/minigames/opord-recall";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { PresenceParticipant } from "@/hooks/useRealtimePresence";
|
import type { PresenceParticipant } from "@/hooks/useRealtimePresence";
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { OpordDifficulty } from "@/lib/opord-recall";
|
import type { OpordDifficulty } from "@/lib/minigames/opord-recall";
|
||||||
|
|
||||||
export type OpordLeaderboardFields = {
|
export type OpordLeaderboardFields = {
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
|
|
@ -6,8 +6,8 @@ import {
|
||||||
AIM_TRAINER_DIFFICULTIES,
|
AIM_TRAINER_DIFFICULTIES,
|
||||||
isAimTrainerDifficulty,
|
isAimTrainerDifficulty,
|
||||||
type AimTrainerDifficulty,
|
type AimTrainerDifficulty,
|
||||||
} from "@/lib/aim-trainer";
|
} from "@/lib/minigames/aim-trainer";
|
||||||
import type { AimTrainerStats } from "@/app/(frontend)/qualification/actions";
|
import type { AimTrainerStats } from "@/app/(frontend)/minigames/qualification/actions";
|
||||||
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
||||||
import { useSidebar } from "@/components/ui/sidebar";
|
import { useSidebar } from "@/components/ui/sidebar";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
@ -3,7 +3,7 @@ import {
|
||||||
calculateAimTrainerScore,
|
calculateAimTrainerScore,
|
||||||
COURSE_DURATION_MS,
|
COURSE_DURATION_MS,
|
||||||
type AimTrainerTarget,
|
type AimTrainerTarget,
|
||||||
} from "@/lib/aim-trainer";
|
} from "@/lib/minigames/aim-trainer";
|
||||||
|
|
||||||
export const AIM_TRAINER_CANVAS_WIDTH = 720;
|
export const AIM_TRAINER_CANVAS_WIDTH = 720;
|
||||||
export const AIM_TRAINER_CANVAS_HEIGHT = 480;
|
export const AIM_TRAINER_CANVAS_HEIGHT = 480;
|
||||||
|
|
@ -19,12 +19,12 @@ import {
|
||||||
stepAimTrainerTargets,
|
stepAimTrainerTargets,
|
||||||
TORSO_POINTS,
|
TORSO_POINTS,
|
||||||
type AimTrainerDifficulty,
|
type AimTrainerDifficulty,
|
||||||
} from "@/lib/aim-trainer";
|
} from "@/lib/minigames/aim-trainer";
|
||||||
import {
|
import {
|
||||||
type AimTrainerResultInput,
|
type AimTrainerResultInput,
|
||||||
type AimTrainerStats,
|
type AimTrainerStats,
|
||||||
submitAimTrainerResult,
|
submitAimTrainerResult,
|
||||||
} from "@/app/(frontend)/qualification/actions";
|
} from "@/app/(frontend)/minigames/qualification/actions";
|
||||||
import { LoginLink } from "@/components/frontend/auth/LoginLink";
|
import { LoginLink } from "@/components/frontend/auth/LoginLink";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Trophy } from "lucide-react";
|
import { Trophy } from "lucide-react";
|
||||||
|
|
||||||
import type { AimTrainerDifficulty } from "@/lib/aim-trainer";
|
import type { AimTrainerDifficulty } from "@/lib/minigames/aim-trainer";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { PresenceParticipant } from "@/hooks/useRealtimePresence";
|
import type { PresenceParticipant } from "@/hooks/useRealtimePresence";
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { AimTrainerDifficulty } from "@/lib/aim-trainer";
|
import type { AimTrainerDifficulty } from "@/lib/minigames/aim-trainer";
|
||||||
|
|
||||||
export type AimTrainerLeaderboardFields = {
|
export type AimTrainerLeaderboardFields = {
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
|
|
@ -21,7 +21,7 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { createTest } from "@/app/(frontend)/radio/custom/actions";
|
import { createTest } from "@/app/(frontend)/minigames/radio/custom/actions";
|
||||||
|
|
||||||
const MAX_MESSAGES = 100;
|
const MAX_MESSAGES = 100;
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ export function CustomTestBrowser({
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button asChild size="sm">
|
<Button asChild size="sm">
|
||||||
<Link href={`/radio/custom/${row.id}`}>
|
<Link href={`/minigames/radio/custom/${row.id}`}>
|
||||||
<Play /> Play
|
<Play /> Play
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import Link from "next/link";
|
import type { RadioStats } from "@/app/(frontend)/minigames/radio/actions";
|
||||||
import { Radio } from "lucide-react";
|
|
||||||
import type { RadioStats } from "@/app/(frontend)/radio/actions";
|
|
||||||
import {
|
import {
|
||||||
isRadioUnlocked,
|
isRadioUnlocked,
|
||||||
SPRINT_ACCURACY_REQUIREMENT,
|
SPRINT_ACCURACY_REQUIREMENT,
|
||||||
WATCH_ACCURACY_REQUIREMENT,
|
WATCH_ACCURACY_REQUIREMENT,
|
||||||
} from "@/lib/custom-minigames/radio";
|
} from "@/lib/minigames/custom/radio";
|
||||||
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
||||||
import { UnlockNotice } from "@/components/frontend/minigames/UnlockNotice";
|
import { UnlockNotice } from "@/components/frontend/minigames/UnlockNotice";
|
||||||
|
import { UgcLinkButton } from "@/components/frontend/minigames/UgcLinkButton";
|
||||||
import { RadioLeaderboard } from "./RadioLeaderboard";
|
import { RadioLeaderboard } from "./RadioLeaderboard";
|
||||||
import { RadioTraffic } from "./RadioTraffic";
|
import { RadioTraffic } from "./RadioTraffic";
|
||||||
|
|
||||||
|
|
@ -49,13 +48,13 @@ export function RadioArena({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-6">
|
<div className="flex flex-col items-center gap-6">
|
||||||
<Link
|
<UgcLinkButton
|
||||||
href="/radio/custom"
|
href="/minigames/radio/custom"
|
||||||
className="flex items-center gap-1.5 rounded-lg border border-emerald-500/40 bg-emerald-500/10 px-3 py-1.5 text-sm text-emerald-300 transition-colors hover:bg-emerald-500/20"
|
title="UGC Radio Tests"
|
||||||
>
|
subtitle="Play and create custom radio tests you can share with other unit members! Runs are unranked, so it is a great place to practice before an official transmission."
|
||||||
<Radio className="size-4" />
|
unlocked={customUnlocked}
|
||||||
Custom radio tests
|
className="max-w-[520px]"
|
||||||
</Link>
|
/>
|
||||||
{!customUnlocked && (
|
{!customUnlocked && (
|
||||||
<UnlockNotice
|
<UnlockNotice
|
||||||
title="custom radio tests"
|
title="custom radio tests"
|
||||||
|
|
@ -5,8 +5,8 @@ import type { FormEvent, KeyboardEvent } from "react";
|
||||||
import { Check, Clock3, Radio, Target, Volume2 } from "lucide-react";
|
import { Check, Clock3, Radio, Target, Volume2 } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { type RadioStats, submitRadioResult } from "@/app/(frontend)/radio/actions";
|
import { type RadioStats, submitRadioResult } from "@/app/(frontend)/minigames/radio/actions";
|
||||||
import { submitCustomTestScore } from "@/app/(frontend)/radio/custom/actions";
|
import { submitCustomTestScore } from "@/app/(frontend)/minigames/radio/custom/actions";
|
||||||
import { LoginLink } from "@/components/frontend/auth/LoginLink";
|
import { LoginLink } from "@/components/frontend/auth/LoginLink";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
6
test-results/.last-run.json
Normal file
6
test-results/.last-run.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"status": "failed",
|
||||||
|
"failedTests": [
|
||||||
|
"e5c04ca02ae11ff5ff00-7644b2c9657ee2dfb45d"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect, test, type Page } from "@playwright/test";
|
import { expect, test, type Page } from "@playwright/test";
|
||||||
|
|
||||||
import { formatOpordGrid, generateOpordScenario } from "../../src/lib/opord-recall";
|
import { formatOpordGrid, generateOpordScenario } from "../../src/lib/minigames/opord-recall";
|
||||||
|
|
||||||
const FIXED_SEED = 424242;
|
const FIXED_SEED = 424242;
|
||||||
const QUIZ_OPTION_BUTTON = "button.font-mono";
|
const QUIZ_OPTION_BUTTON = "button.font-mono";
|
||||||
|
|
@ -14,7 +14,7 @@ async function login(page: Page): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function playOpordRound(page: Page): Promise<void> {
|
async function playOpordRound(page: Page): Promise<void> {
|
||||||
await page.goto(`/opord?seed=${FIXED_SEED}`);
|
await page.goto(`/minigames/opord?seed=${FIXED_SEED}`);
|
||||||
await expect(page.getByRole("heading", { name: "OPORD Recall" })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "OPORD Recall" })).toBeVisible();
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Receive briefing" }).click();
|
await page.getByRole("button", { name: "Receive briefing" }).click();
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import {
|
||||||
resolveAimTrainerTargetHit,
|
resolveAimTrainerTargetHit,
|
||||||
stepAimTrainerTarget,
|
stepAimTrainerTarget,
|
||||||
stepAimTrainerTargets,
|
stepAimTrainerTargets,
|
||||||
} from "@/lib/aim-trainer";
|
} from "@/lib/minigames/aim-trainer";
|
||||||
|
|
||||||
/** Deterministic LCG-style RNG so target creation is reproducible across runs. */
|
/** Deterministic LCG-style RNG so target creation is reproducible across runs. */
|
||||||
function seededRng(seed: number): () => number {
|
function seededRng(seed: number): () => number {
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ import {
|
||||||
validateMinefieldName,
|
validateMinefieldName,
|
||||||
validateMinefieldScore,
|
validateMinefieldScore,
|
||||||
validateMinePositions,
|
validateMinePositions,
|
||||||
} from "@/lib/custom-minigames/eod";
|
} from "@/lib/minigames/custom/eod";
|
||||||
import { creditEodXp } from "@/lib/custom-minigames/payouts";
|
import { creditEodXp } from "@/lib/minigames/custom/payouts";
|
||||||
|
|
||||||
let payload: Payload;
|
let payload: Payload;
|
||||||
const RUN = `cmf-${Date.now().toString(36)}`;
|
const RUN = `cmf-${Date.now().toString(36)}`;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
validateMessages,
|
validateMessages,
|
||||||
validateRadioScore,
|
validateRadioScore,
|
||||||
validateTestName,
|
validateTestName,
|
||||||
} from "@/lib/custom-minigames/radio";
|
} from "@/lib/minigames/custom/radio";
|
||||||
|
|
||||||
let payload: Payload;
|
let payload: Payload;
|
||||||
const RUN = `crt-${Date.now().toString(36)}`;
|
const RUN = `crt-${Date.now().toString(36)}`;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue