import * as PIXI from 'pixi.js'; import { useApp } from '@pixi/react'; import { Player, SelectElement } from './Player.tsx'; import { useEffect, useRef, useState } from 'react'; import { PixiStaticMap } from './PixiStaticMap.tsx'; import PixiViewport from './PixiViewport.tsx'; import { Viewport } from 'pixi-viewport'; import { Id } from '../../convex/_generated/dataModel'; import { useQuery } from 'convex/react'; import { api } from '../../convex/_generated/api.js'; import { useSendInput } from '../hooks/sendInput.ts'; import { toastOnError } from '../toasts.ts'; import { DebugPath } from './DebugPath.tsx'; import { PositionIndicator } from './PositionIndicator.tsx'; import { SHOW_DEBUG_UI } from './Game.tsx'; import { ServerGame } from '../hooks/serverGame.ts'; export const PixiGame = (props: { worldId: Id<'worlds'>; engineId: Id<'engines'>; game: ServerGame; historicalTime: number | undefined; width: number; height: number; setSelectedElement: SelectElement; }) => { // PIXI setup. const pixiApp = useApp(); const viewportRef = useRef(); const oauth = JSON.parse(localStorage.getItem('oauth')); const oauthToken = oauth ? oauth.userInfo.fullname : undefined; const humanTokenIdentifier = useQuery(api.world.userStatus, { worldId: props.worldId, oauthToken }) ?? null; const humanPlayerId = [...props.game.world.players.values()].find( (p) => p.human === humanTokenIdentifier, )?.id; const moveTo = useSendInput(props.engineId, 'moveTo'); // Interaction for clicking on the world to navigate. const dragStart = useRef<{ screenX: number; screenY: number } | null>(null); const onMapPointerDown = (e: any) => { // https://pixijs.download/dev/docs/PIXI.FederatedPointerEvent.html dragStart.current = { screenX: e.screenX, screenY: e.screenY }; }; const [lastDestination, setLastDestination] = useState<{ x: number; y: number; t: number; } | null>(null); const onMapPointerUp = async (e: any) => { if (dragStart.current) { const { screenX, screenY } = dragStart.current; dragStart.current = null; const [dx, dy] = [screenX - e.screenX, screenY - e.screenY]; const dist = Math.sqrt(dx * dx + dy * dy); if (dist > 10) { console.log(`Skipping navigation on drag event (${dist}px)`); return; } } if (!humanPlayerId) { return; } const viewport = viewportRef.current; if (!viewport) { return; } const gameSpacePx = viewport.toWorld(e.screenX, e.screenY); const tileDim = props.game.worldMap.tileDim; const gameSpaceTiles = { x: gameSpacePx.x / tileDim, y: gameSpacePx.y / tileDim, }; setLastDestination({ t: Date.now(), ...gameSpaceTiles }); const roundedTiles = { x: Math.floor(gameSpaceTiles.x), y: Math.floor(gameSpaceTiles.y), }; console.log(`Moving to ${JSON.stringify(roundedTiles)}`); await toastOnError(moveTo({ playerId: humanPlayerId, destination: roundedTiles })); }; const { width, height, tileDim } = props.game.worldMap; const players = [...props.game.world.players.values()]; // Zoom on the user’s avatar when it is created useEffect(() => { if (!viewportRef.current || humanPlayerId === undefined) return; const humanPlayer = props.game.world.players.get(humanPlayerId)!; viewportRef.current.animate({ position: new PIXI.Point(humanPlayer.position.x * tileDim, humanPlayer.position.y * tileDim), scale: 1.5, }); }, [humanPlayerId]); return ( {players.map( (p) => // Only show the path for the human player in non-debug mode. (SHOW_DEBUG_UI || p.id === humanPlayerId) && ( ), )} {lastDestination && } {players.map((p) => ( ))} ); }; export default PixiGame;