feat(players): NPC disposition types and launch-with-players session tokens

Add Hostile/Neutral/Friendly ring types with session-only inactive overrides on control, plus launch-with-players flow and live player tokens on the map.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-30 17:11:01 +08:00
parent 101f595bac
commit cc50e64e21
38 changed files with 1803 additions and 59 deletions
+15
View File
@@ -16,7 +16,10 @@ import { useMaterialsOverlayState } from './materials/useMaterialsOverlayState';
import { NpcsSceneOverlay } from './npcs/NpcsSceneOverlay';
import { useNpcsOverlayState } from './npcs/useNpcsOverlayState';
import { SceneNpcTokensOverlay } from './playerToken/SceneNpcTokensOverlay';
import { ScenePlayerTokensOverlay } from './playerToken/ScenePlayerTokensOverlay';
import { useAppPlayers } from './playerToken/useAppPlayers';
import { useSceneNpcTokensSession } from './playerToken/useSceneNpcTokensSession';
import { useScenePlayerTokensSession } from './playerToken/useScenePlayerTokensSession';
import { SceneOverlayHost } from './sceneOverlay/SceneOverlayHost';
import { useSceneViewState } from './sceneView/useSceneViewState';
import { SceneTokensOverlay } from './tokens/SceneTokensOverlay';
@@ -28,6 +31,7 @@ import styles from './PresentationView.module.css';
import { RotatedImage } from './RotatedImage';
import { useAssetUrl } from './useAssetImageUrl';
import { useVideoPlaybackState } from './video/useVideoPlaybackState';
import { DEFAULT_NPC_TOKEN_SESSION_SCALE } from '../../shared/types/appPlayers';
export type PresentationViewProps = {
session: SessionState | null;
@@ -50,8 +54,10 @@ export function PresentationView({
const [materialsOverlay] = useMaterialsOverlayState();
const [npcsOverlay] = useNpcsOverlayState();
const appTokens = useAppTokens();
const { players: appPlayers } = useAppPlayers();
const [sceneTokensSession] = useSceneTokensSession();
const [sceneNpcTokensSession] = useSceneNpcTokensSession();
const [scenePlayerTokensSession] = useScenePlayerTokensSession();
const [vp] = useVideoPlaybackState();
const videoElRef = useRef<HTMLVideoElement | null>(null);
const [contentRect, setContentRect] = React.useState<{ x: number; y: number; w: number; h: number } | null>(
@@ -189,6 +195,15 @@ export function PresentationView({
grid={scene.grid}
/>
) : null}
{scene?.previewAssetType === 'image' && contentRect ? (
<ScenePlayerTokensOverlay
library={appPlayers}
session={scenePlayerTokensSession}
displayScale={sceneNpcTokensSession.scale ?? DEFAULT_NPC_TOKEN_SESSION_SCALE}
viewport={contentRect}
grid={scene.grid}
/>
) : null}
{scene?.previewAssetType === 'image' && contentRect ? (
<SceneTrapsOverlay
traps={scene.traps ?? []}