Spaces:
Sleeping
Sleeping
| import { Character } from './Character.tsx'; | |
| import { orientationDegrees } from '../../convex/util/geometry.ts'; | |
| import { characters } from '../../data/characters.ts'; | |
| import { toast } from 'react-toastify'; | |
| import { Player as ServerPlayer } from '../../convex/aiTown/player.ts'; | |
| import { GameId } from '../../convex/aiTown/ids.ts'; | |
| import { Id } from '../../convex/_generated/dataModel'; | |
| import { Location, locationFields, playerLocation } from '../../convex/aiTown/location.ts'; | |
| import { useHistoricalValue } from '../hooks/useHistoricalValue.ts'; | |
| import { PlayerDescription } from '../../convex/aiTown/playerDescription.ts'; | |
| import { WorldMap } from '../../convex/aiTown/worldMap.ts'; | |
| import { ServerGame } from '../hooks/serverGame.ts'; | |
| export type SelectElement = (element?: { kind: 'player'; id: GameId<'players'> }) => void; | |
| const logged = new Set<string>(); | |
| export const Player = ({ | |
| game, | |
| isViewer, | |
| player, | |
| onClick, | |
| historicalTime, | |
| }: { | |
| game: ServerGame; | |
| isViewer: boolean; | |
| player: ServerPlayer; | |
| onClick: SelectElement; | |
| historicalTime?: number; | |
| }) => { | |
| const playerCharacter = game.playerDescriptions.get(player.id)?.character; | |
| if (!playerCharacter) { | |
| throw new Error(`Player ${player.id} has no character`); | |
| } | |
| let character = characters.find((c) => c.name === playerCharacter); | |
| // If it's night, use the night version of the character | |
| if (game.world.gameCycle.cycleState === 'Night' && game.playerDescriptions.get(player.id)?.type === 'werewolf') { | |
| character = characters.find((c) => c.name === 'c1'); | |
| } | |
| const locationBuffer = game.world.historicalLocations?.get(player.id); | |
| const historicalLocation = useHistoricalValue<Location>( | |
| locationFields, | |
| historicalTime, | |
| playerLocation(player), | |
| locationBuffer, | |
| ); | |
| if (!character) { | |
| if (!logged.has(playerCharacter)) { | |
| logged.add(playerCharacter); | |
| toast.error(`Unknown character ${playerCharacter}`); | |
| } | |
| return null; | |
| } | |
| if (!historicalLocation) { | |
| return null; | |
| } | |
| const isSpeaking = !![...game.world.conversations.values()].find( | |
| (c) => c.isTyping?.playerId === player.id, | |
| ); | |
| const isThinking = | |
| !isSpeaking && | |
| !![...game.world.agents.values()].find( | |
| (a) => a.playerId === player.id && !!a.inProgressOperation, | |
| ); | |
| const tileDim = game.worldMap.tileDim; | |
| const historicalFacing = { dx: historicalLocation.dx, dy: historicalLocation.dy }; | |
| return ( | |
| <> | |
| <Character | |
| x={historicalLocation.x * tileDim + tileDim / 2} | |
| y={historicalLocation.y * tileDim + tileDim / 2} | |
| orientation={orientationDegrees(historicalFacing)} | |
| isMoving={historicalLocation.speed > 0} | |
| isThinking={isThinking} | |
| isSpeaking={isSpeaking} | |
| emoji={ | |
| player.activity && player.activity.until > (historicalTime ?? Date.now()) | |
| ? player.activity?.emoji | |
| : undefined | |
| } | |
| isViewer={isViewer} | |
| textureUrl={character.textureUrl} | |
| spritesheetData={character.spritesheetData} | |
| speed={character.speed} | |
| onClick={() => { | |
| onClick({ kind: 'player', id: player.id }); | |
| }} | |
| /> | |
| </> | |
| ); | |
| }; | |