feat(minigames): add UnlockNotice and wire into EOD and radio arenas
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
f76966ebc3
commit
455d705eb9
3 changed files with 66 additions and 0 deletions
|
|
@ -4,8 +4,10 @@ import { useCallback, useEffect, useState } from "react";
|
|||
import Link from "next/link";
|
||||
import { Bomb } from "lucide-react";
|
||||
import type { EodDifficulty, EodStats } from "@/app/(frontend)/eod/actions";
|
||||
import { isEodUnlocked } from "@/lib/custom-minigames/eod";
|
||||
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
||||
import { useSidebar } from "@/components/ui/sidebar";
|
||||
import { UnlockNotice } from "@/components/frontend/minigames/UnlockNotice";
|
||||
import { EodLeaderboard } from "./EodLeaderboard";
|
||||
import { MinesweeperBoard } from "./MinesweeperBoard";
|
||||
|
||||
|
|
@ -25,6 +27,17 @@ export function EodArena({
|
|||
setPlaying(phase === "playing");
|
||||
}, []);
|
||||
|
||||
const customUnlocked = isEodUnlocked({
|
||||
bestTrainingTime: initialStats.bestTrainingTime,
|
||||
bestPerimeterTime: initialStats.bestPerimeterTime,
|
||||
bestSupplyRoadTime: initialStats.bestSupplyRoadTime,
|
||||
});
|
||||
const clearedSectors = [
|
||||
initialStats.bestTrainingTime,
|
||||
initialStats.bestPerimeterTime,
|
||||
initialStats.bestSupplyRoadTime,
|
||||
].filter((time) => time > 0).length;
|
||||
|
||||
useEffect(() => {
|
||||
if (playing && isMobile) setOpenMobile(false);
|
||||
}, [isMobile, playing, setOpenMobile]);
|
||||
|
|
@ -38,6 +51,13 @@ export function EodArena({
|
|||
<Bomb className="size-4" />
|
||||
Custom minefields
|
||||
</Link>
|
||||
{!customUnlocked && (
|
||||
<UnlockNotice
|
||||
title="custom minefields"
|
||||
requirement="you have cleared Training Pad, FOB Perimeter, and Main Supply Road"
|
||||
progress={`${clearedSectors} of 3 sectors cleared`}
|
||||
/>
|
||||
)}
|
||||
<MinesweeperBoard
|
||||
initialStats={initialStats}
|
||||
onGameRecorded={() => setRefreshKey((key) => key + 1)}
|
||||
|
|
|
|||
26
src/components/frontend/minigames/UnlockNotice.tsx
Normal file
26
src/components/frontend/minigames/UnlockNotice.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Lock } from "lucide-react";
|
||||
|
||||
/**
|
||||
* Locked-state hint shown beside a minigame's custom-content link: tells the
|
||||
* player exactly which milestones unlock authoring user-generated content and
|
||||
* how close they are. Unlocked players see nothing (the link speaks for itself).
|
||||
*/
|
||||
export function UnlockNotice({
|
||||
title,
|
||||
requirement,
|
||||
progress,
|
||||
}: {
|
||||
title: string;
|
||||
requirement: string;
|
||||
progress: string;
|
||||
}) {
|
||||
return (
|
||||
<p className="flex max-w-xl items-start gap-2 rounded-lg border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs leading-relaxed text-amber-300">
|
||||
<Lock className="mt-0.5 size-3.5 shrink-0" />
|
||||
<span>
|
||||
<span className="font-medium">Author your own {title}</span> once {requirement}{" "}
|
||||
<span className="text-amber-200/60">({progress})</span>
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,7 +4,13 @@ import { useCallback, useEffect, useState } from "react";
|
|||
import Link from "next/link";
|
||||
import { Radio } from "lucide-react";
|
||||
import type { RadioStats } from "@/app/(frontend)/radio/actions";
|
||||
import {
|
||||
isRadioUnlocked,
|
||||
SPRINT_ACCURACY_REQUIREMENT,
|
||||
WATCH_ACCURACY_REQUIREMENT,
|
||||
} from "@/lib/custom-minigames/radio";
|
||||
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
|
||||
import { UnlockNotice } from "@/components/frontend/minigames/UnlockNotice";
|
||||
import { RadioLeaderboard } from "./RadioLeaderboard";
|
||||
import { RadioTraffic } from "./RadioTraffic";
|
||||
|
||||
|
|
@ -24,6 +30,13 @@ export function RadioArena({
|
|||
setPlaying(phase === "playing");
|
||||
}, []);
|
||||
|
||||
const customUnlocked = isRadioUnlocked({
|
||||
sprintBestAccuracy: initialStats.sprintBestAccuracy,
|
||||
watchBestAccuracy: initialStats.watchBestAccuracy,
|
||||
});
|
||||
const sprintPercent = Math.round(initialStats.sprintBestAccuracy * 100);
|
||||
const watchPercent = Math.round(initialStats.watchBestAccuracy * 100);
|
||||
|
||||
useEffect(() => {
|
||||
const savedMode = window.localStorage.getItem("radio-traffic-mode");
|
||||
if (savedMode === "sprint" || savedMode === "watch") setMode(savedMode);
|
||||
|
|
@ -43,6 +56,13 @@ export function RadioArena({
|
|||
<Radio className="size-4" />
|
||||
Custom radio tests
|
||||
</Link>
|
||||
{!customUnlocked && (
|
||||
<UnlockNotice
|
||||
title="custom radio tests"
|
||||
requirement={`your best accuracy reaches ${Math.round(SPRINT_ACCURACY_REQUIREMENT * 100)}% in sprint and ${Math.round(WATCH_ACCURACY_REQUIREMENT * 100)}% in watch`}
|
||||
progress={`Sprint ${sprintPercent}%, Watch ${watchPercent}%`}
|
||||
/>
|
||||
)}
|
||||
<RadioTraffic
|
||||
initialStats={initialStats}
|
||||
mode={mode}
|
||||
|
|
|
|||
Loading…
Reference in a new issue