Spaces:
Sleeping
Sleeping
File size: 5,707 Bytes
90cbf22 0f725c2 90cbf22 df2ef4f 90cbf22 df2ef4f 90cbf22 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
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<Viewport | undefined>();
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()];
// State to manage the current tileSet
const [currentTileSet, setCurrentTileSet] = useState({
background: props.game.worldMap.bgTiles,
objectMap: props.game.worldMap.objectTiles,
decor: props.game.worldMap.decorTiles,
});
// Effect hook to change the tileSet based on a day/night condition
useEffect(() => {
const { cycleState } = props.game.world.gameCycle;
const tileSet = (cycleState === 'Night' || cycleState === 'PlayerKillVoting')
? {
background: props.game.worldMap.bgTilesN,
objectMap: props.game.worldMap.objectTilesN,
decor: props.game.worldMap.decorTilesN,
}
: {
background: props.game.worldMap.bgTiles,
objectMap: props.game.worldMap.objectTiles,
decor: props.game.worldMap.decorTiles,
};
setCurrentTileSet(tileSet);
}, [props.game.world.gameCycle.cycleState]);
// 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 (
<PixiViewport
app={pixiApp}
screenWidth={props.width}
screenHeight={props.height}
worldWidth={width * tileDim}
worldHeight={height * tileDim}
viewportRef={viewportRef}
>
<PixiStaticMap
map={{
...props.game.worldMap,
bgTiles: currentTileSet.background,
objectTiles: currentTileSet.objectMap,
decorTiles: currentTileSet.decor,
serialize: props.game.worldMap.serialize
}}
onpointerup={onMapPointerUp}
onpointerdown={onMapPointerDown}
/>
{players.map(
(p) =>
// Only show the path for the human player in non-debug mode.
(SHOW_DEBUG_UI || p.id === humanPlayerId) && (
<DebugPath key={`path-${p.id}`} player={p} tileDim={tileDim} />
),
)}
{lastDestination && <PositionIndicator destination={lastDestination} tileDim={tileDim} />}
{players.map((p) => (
<Player
key={`player-${p.id}`}
game={props.game}
player={p}
isViewer={p.id === humanPlayerId}
onClick={props.setSelectedElement}
historicalTime={props.historicalTime}
/>
))}
</PixiViewport>
);
};
export default PixiGame;
|