mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
turns stuff testings
This commit is contained in:
@@ -4,6 +4,7 @@ Context:
|
|||||||
- You are in a chat room with people driving rovers.
|
- You are in a chat room with people driving rovers.
|
||||||
- The rovers enjoy chaos and destruction, not order and precision.
|
- The rovers enjoy chaos and destruction, not order and precision.
|
||||||
- It is your job to monitor them.
|
- It is your job to monitor them.
|
||||||
|
- tsk and !tskip are chat bot commands, ignore them.
|
||||||
|
|
||||||
Character:
|
Character:
|
||||||
- Calm and highly intelligent with dry sarcasm.
|
- Calm and highly intelligent with dry sarcasm.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-CH1QpIkJ.js"></script>
|
<script type="module" crossorigin src="/assets/index-BjZM2uht.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-C8rSwF1O.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CxKDohQn.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -9,10 +9,24 @@ import { useRoverSnapshots } from '../../hooks/useRoverSnapshots.js';
|
|||||||
import { useControlSystem } from '../../controls/index.js';
|
import { useControlSystem } from '../../controls/index.js';
|
||||||
import VideoTile from '../VideoTile/index.jsx';
|
import VideoTile from '../VideoTile/index.jsx';
|
||||||
|
|
||||||
export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
function countEligibleDrivers(users = []) {
|
||||||
|
const unique = new Set();
|
||||||
|
users.forEach((entry) => {
|
||||||
|
const role = String(entry?.role || '');
|
||||||
|
if (role === 'spectator') return;
|
||||||
|
const roverId = String(entry?.roverId || '').trim();
|
||||||
|
const socketId = String(entry?.socketId || '').trim();
|
||||||
|
if (!roverId || !socketId) return;
|
||||||
|
unique.add(socketId);
|
||||||
|
});
|
||||||
|
return unique.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DriverVideoPanel({ layoutFormat = 'desktop' }) {
|
||||||
const mode = useSessionSelector((state) => state.session?.mode || null);
|
const mode = useSessionSelector((state) => state.session?.mode || null);
|
||||||
const roverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
|
const roverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
|
||||||
const roster = useSessionSelector((state) => state.session?.roster ?? []);
|
const roster = useSessionSelector((state) => state.session?.roster ?? []);
|
||||||
|
const users = useSessionSelector((state) => state.session?.users ?? []);
|
||||||
const turnQueues = useSessionSelector((state) => state.session?.turnQueues ?? {});
|
const turnQueues = useSessionSelector((state) => state.session?.turnQueues ?? {});
|
||||||
const socketId = useSessionSelector((state) => state.session?.socketId || null);
|
const socketId = useSessionSelector((state) => state.session?.socketId || null);
|
||||||
const activeDrivers = useSessionSelector((state) => state.session?.activeDrivers ?? {});
|
const activeDrivers = useSessionSelector((state) => state.session?.activeDrivers ?? {});
|
||||||
@@ -23,7 +37,9 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
|||||||
const [now, setNow] = useState(() => Date.now());
|
const [now, setNow] = useState(() => Date.now());
|
||||||
const [turnCueVisible, setTurnCueVisible] = useState(false);
|
const [turnCueVisible, setTurnCueVisible] = useState(false);
|
||||||
const [turnCueStartAt, setTurnCueStartAt] = useState(null);
|
const [turnCueStartAt, setTurnCueStartAt] = useState(null);
|
||||||
|
const [notTurnFlashAt, setNotTurnFlashAt] = useState(0);
|
||||||
const lastTurnRef = useRef({ active: false, roverId: null });
|
const lastTurnRef = useRef({ active: false, roverId: null });
|
||||||
|
const lastIntentRef = useRef(lastControlIntentAt || 0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode !== 'turns') {
|
if (mode !== 'turns') {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -51,22 +67,34 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
|||||||
const idleDeadline = turnInfo?.idleDeadline || null;
|
const idleDeadline = turnInfo?.idleDeadline || null;
|
||||||
const msUntilTurn = deadline ? deadline - now : null;
|
const msUntilTurn = deadline ? deadline - now : null;
|
||||||
const msUntilIdleSkip = idleDeadline ? idleDeadline - now : null;
|
const msUntilIdleSkip = idleDeadline ? idleDeadline - now : null;
|
||||||
const isPreSwitchWindow =
|
const isTurnsMode = mode === 'turns';
|
||||||
mode === 'turns' && isNextDriver && msUntilTurn != null && msUntilTurn <= 5000 && msUntilTurn > 0;
|
const totalRovers = roster.length;
|
||||||
const shouldShowVideo = mode !== 'turns' || isActiveDriver || isPreSwitchWindow;
|
const totalDrivers = useMemo(() => countEligibleDrivers(users), [users]);
|
||||||
|
const shouldUsePreviewByLoad = isTurnsMode && totalDrivers > totalRovers;
|
||||||
|
const isPreSwitchWindow = isTurnsMode && isNextDriver && msUntilTurn != null && msUntilTurn <= 5000 && msUntilTurn > 0;
|
||||||
|
const isNotYourTurn = isTurnsMode && !isActiveDriver;
|
||||||
|
const shouldUsePreview = isNotYourTurn && !isPreSwitchWindow && shouldUsePreviewByLoad;
|
||||||
|
const shouldShowVideo = !shouldUsePreview;
|
||||||
const turnSeconds =
|
const turnSeconds =
|
||||||
msUntilTurn != null && Number.isFinite(msUntilTurn) ? Math.max(0, Math.ceil(msUntilTurn / 1000)) : null;
|
msUntilTurn != null && Number.isFinite(msUntilTurn) ? Math.max(0, Math.ceil(msUntilTurn / 1000)) : null;
|
||||||
const idleSkipSeconds =
|
const idleSkipSeconds =
|
||||||
msUntilIdleSkip != null && Number.isFinite(msUntilIdleSkip)
|
msUntilIdleSkip != null && Number.isFinite(msUntilIdleSkip)
|
||||||
? Math.max(0, Math.ceil(msUntilIdleSkip / 1000))
|
? Math.max(0, Math.ceil(msUntilIdleSkip / 1000))
|
||||||
: null;
|
: null;
|
||||||
const turnTimerText = isActiveDriver
|
const turnTimerText = useMemo(() => {
|
||||||
? turnSeconds != null
|
if (!isTurnsMode) return null;
|
||||||
? `${turnSeconds}s left`
|
if (isActiveDriver) {
|
||||||
: null
|
return turnSeconds != null ? `${turnSeconds}s left` : 'Your turn';
|
||||||
: isNextDriver && turnSeconds != null
|
}
|
||||||
? `Your turn in ${turnSeconds}s`
|
const parts = ['Not your turn'];
|
||||||
: null;
|
if (isNextDriver && turnSeconds != null) {
|
||||||
|
parts.push(`your turn in ${turnSeconds}s`);
|
||||||
|
}
|
||||||
|
if (shouldUsePreview) {
|
||||||
|
parts.push('preview to save upload bandwidth');
|
||||||
|
}
|
||||||
|
return parts.join(' • ');
|
||||||
|
}, [isTurnsMode, isActiveDriver, isNextDriver, turnSeconds, shouldUsePreview]);
|
||||||
const entries = roverId
|
const entries = roverId
|
||||||
? [
|
? [
|
||||||
...(shouldShowVideo ? [{ type: 'rover', id: roverId, key: roverId }] : []),
|
...(shouldShowVideo ? [{ type: 'rover', id: roverId, key: roverId }] : []),
|
||||||
@@ -117,6 +145,16 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
|||||||
}
|
}
|
||||||
}, [lastControlIntentAt, turnCueStartAt, turnCueVisible]);
|
}, [lastControlIntentAt, turnCueStartAt, turnCueVisible]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const lastIntent = Number(lastIntentRef.current) || 0;
|
||||||
|
const nextIntent = Number(lastControlIntentAt) || 0;
|
||||||
|
const advanced = nextIntent > lastIntent;
|
||||||
|
if (advanced && isNotYourTurn) {
|
||||||
|
setNotTurnFlashAt(Date.now());
|
||||||
|
}
|
||||||
|
lastIntentRef.current = nextIntent;
|
||||||
|
}, [isNotYourTurn, lastControlIntentAt]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
{roverId ? (
|
{roverId ? (
|
||||||
@@ -132,12 +170,14 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
|||||||
layoutFormat={layoutFormat}
|
layoutFormat={layoutFormat}
|
||||||
overcurrentLimiter={overcurrentLimiter}
|
overcurrentLimiter={overcurrentLimiter}
|
||||||
songNote={song?.note}
|
songNote={song?.note}
|
||||||
qualityNotice={!shouldShowVideo ? 'Preview feed (low FPS) until your turn.' : null}
|
qualityNotice={null}
|
||||||
showTurnCue={turnCueVisible}
|
showTurnCue={turnCueVisible}
|
||||||
turnTimerText={turnTimerText}
|
turnTimerText={turnTimerText}
|
||||||
turnSeconds={turnSeconds}
|
turnSeconds={turnSeconds}
|
||||||
isActiveDriver={isActiveDriver}
|
isActiveDriver={isActiveDriver}
|
||||||
idleSkipSeconds={idleSkipSeconds}
|
idleSkipSeconds={idleSkipSeconds}
|
||||||
|
showNotTurnNotice={isNotYourTurn}
|
||||||
|
notTurnFlashAt={notTurnFlashAt}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="panel-muted content-center text-center text-sm text-slate-400 aspect-[4/3]">
|
<div className="panel-muted content-center text-center text-sm text-slate-400 aspect-[4/3]">
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ function HudOverlay({
|
|||||||
mobileHud = false,
|
mobileHud = false,
|
||||||
mapPosition = 'top-center',
|
mapPosition = 'top-center',
|
||||||
turnTimerText = null,
|
turnTimerText = null,
|
||||||
|
turnTimerFlashActive = false,
|
||||||
labelScale = 1,
|
labelScale = 1,
|
||||||
}) {
|
}) {
|
||||||
const isMobile = mobileHud;
|
const isMobile = mobileHud;
|
||||||
@@ -147,7 +148,9 @@ function HudOverlay({
|
|||||||
</div>
|
</div>
|
||||||
{turnTimerText ? (
|
{turnTimerText ? (
|
||||||
<div
|
<div
|
||||||
className={`absolute left-1/2 top-0.5 -translate-x-1/2 rounded bg-black/70 text-slate-100 ${timerPadClass} ${timerTextClass}`}
|
className={`absolute left-1/2 top-0.5 -translate-x-1/2 rounded transition-colors ${
|
||||||
|
turnTimerFlashActive ? 'bg-red-900/80 text-red-100' : 'bg-black/70 text-slate-100'
|
||||||
|
} ${timerPadClass} ${timerTextClass}`}
|
||||||
>
|
>
|
||||||
{turnTimerText}
|
{turnTimerText}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ export default function VideoTile({
|
|||||||
turnTimerText = null,
|
turnTimerText = null,
|
||||||
isActiveDriver = false,
|
isActiveDriver = false,
|
||||||
idleSkipSeconds = null,
|
idleSkipSeconds = null,
|
||||||
|
showNotTurnNotice = false,
|
||||||
|
notTurnFlashAt = 0,
|
||||||
}) {
|
}) {
|
||||||
const discordUrl = useSessionSelector((state) => {
|
const discordUrl = useSessionSelector((state) => {
|
||||||
const socials = state.session?.socials || [];
|
const socials = state.session?.socials || [];
|
||||||
@@ -72,6 +74,7 @@ export default function VideoTile({
|
|||||||
const [restartToken, setRestartToken] = useState(0);
|
const [restartToken, setRestartToken] = useState(0);
|
||||||
const [audioRestartToken, setAudioRestartToken] = useState(0);
|
const [audioRestartToken, setAudioRestartToken] = useState(0);
|
||||||
const [muted, setMuted] = useState(true);
|
const [muted, setMuted] = useState(true);
|
||||||
|
const [noticeFlashActive, setNoticeFlashActive] = useState(false);
|
||||||
const hasDedicatedAudio = Boolean(audioSessionInfo?.url);
|
const hasDedicatedAudio = Boolean(audioSessionInfo?.url);
|
||||||
const usingSnapshot = videoMode === 'snapshot';
|
const usingSnapshot = videoMode === 'snapshot';
|
||||||
const sensors = telemetryFrame?.sensors;
|
const sensors = telemetryFrame?.sensors;
|
||||||
@@ -298,6 +301,13 @@ export default function VideoTile({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showNotTurnNotice || !notTurnFlashAt) return undefined;
|
||||||
|
setNoticeFlashActive(true);
|
||||||
|
const timer = setTimeout(() => setNoticeFlashActive(false), 650);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [showNotTurnNotice, notTurnFlashAt]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'playing') {
|
if (status === 'playing') {
|
||||||
attemptUnmute(0);
|
attemptUnmute(0);
|
||||||
@@ -634,6 +644,7 @@ export default function VideoTile({
|
|||||||
mobileHud={mobileHud}
|
mobileHud={mobileHud}
|
||||||
mapPosition={effectiveHudMapPosition}
|
mapPosition={effectiveHudMapPosition}
|
||||||
turnTimerText={turnTimerText}
|
turnTimerText={turnTimerText}
|
||||||
|
turnTimerFlashActive={noticeFlashActive}
|
||||||
labelScale={hudLabelScale}
|
labelScale={hudLabelScale}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
Reference in New Issue
Block a user