diff --git a/packages/ecs-browser/src/StyledComponents.ts b/packages/ecs-browser/src/StyledComponents.ts index 34350b6738..7919bd032c 100644 --- a/packages/ecs-browser/src/StyledComponents.ts +++ b/packages/ecs-browser/src/StyledComponents.ts @@ -68,18 +68,11 @@ export const EntityEditorContainer = styled.div` `; export const BrowserContainer = styled.div` - grid-column: 3; - grid-row-start: 1; - grid-row-end: 4; - pointer-events: all; - max-height: calc(100vh - 100px); overflow: auto; - - width: 300px; - justify-self: end; - background-color: rgba(27, 28, 32, 1); color: #8c91a0; + height: 100%; + pointer-events: all; `; export const QueryBuilderForm = styled.form` diff --git a/packages/phaserx/src/createCamera.ts b/packages/phaserx/src/createCamera.ts index 53b9620837..f2819e5567 100644 --- a/packages/phaserx/src/createCamera.ts +++ b/packages/phaserx/src/createCamera.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Gesture } from "@use-gesture/vanilla"; import { BehaviorSubject, map, sampleTime, scan, Subject, throttleTime } from "rxjs"; -import { Camera, CameraConfig, GestureState, ObjectPool } from "./types"; +import { tileCoordToPixelCoord } from "./utils"; +import { Camera, CameraConfig, Coord, GestureState, ObjectPool } from "./types"; export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, options: CameraConfig): Camera { const phaserGame = document.getElementById(options.phaserSelector); @@ -61,6 +62,12 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option objectPool.ignoreCamera(phaserCamera.id, ignore); } + function centerCameraOnCoord(coord: Coord, tileWidth: number, tileHeight: number) { + const worldCoordinate = tileCoordToPixelCoord(coord, tileWidth, tileHeight); + phaserCamera.centerOn(worldCoordinate.x, worldCoordinate.y); + requestAnimationFrame(() => worldView$.next(phaserCamera.worldView)); + } + return { phaserCamera, worldView$, @@ -71,5 +78,6 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option wheelSub.unsubscribe(); gesture.destroy(); }, + centerCameraOnCoord, }; } diff --git a/packages/phaserx/src/index.ts b/packages/phaserx/src/index.ts index 70a5533143..11b8d1cb2e 100644 --- a/packages/phaserx/src/index.ts +++ b/packages/phaserx/src/index.ts @@ -17,7 +17,7 @@ export { createCamera } from "./createCamera"; export { createChunks } from "./createChunks"; export { createDebugger } from "./createDebugger"; export { createCulling } from "./createCulling"; -export type { Asset } from "./types"; +export type { Asset, Camera } from "./types"; export { AssetType } from "./constants"; export { defineAssetsConfig, diff --git a/packages/phaserx/src/types.ts b/packages/phaserx/src/types.ts index 05ed45bf69..433e97f4d3 100644 --- a/packages/phaserx/src/types.ts +++ b/packages/phaserx/src/types.ts @@ -16,6 +16,7 @@ export type Camera = { zoom$: Observable; ignore: (objectPool: ObjectPool, ignore: boolean) => void; dispose: () => void; + centerCameraOnCoord: (coord: Coord, tileWidth: number, tileHeight: number) => void; }; export type GameObjectTypes = typeof GameObjectClasses; diff --git a/packages/ri/client/src/layers/Local/createLocalLayer.ts b/packages/ri/client/src/layers/Local/createLocalLayer.ts index 5c0342fd16..4d30ad8f36 100644 --- a/packages/ri/client/src/layers/Local/createLocalLayer.ts +++ b/packages/ri/client/src/layers/Local/createLocalLayer.ts @@ -17,7 +17,6 @@ import { createPathSystem, createSyncSystem, createPositionSystem, - createImpSystem, createSelectionSystem, } from "./systems"; import { DEFAULT_MOVE_SPEED } from "./constants"; @@ -89,8 +88,7 @@ export async function createLocalLayer(headless: HeadlessLayer) { }; // Systems - createSelectionSystem(layer); // Enable selection system - createImpSystem(layer); // Enable imps + createSelectionSystem(layer); createSyncSystem(layer); createPositionSystem(layer); createDestinationSystem(layer); diff --git a/packages/ri/client/src/layers/Local/systems/ImpSystem/createImpSystem.ts b/packages/ri/client/src/layers/Local/systems/ImpSystem/createImpSystem.ts deleted file mode 100644 index d2827f3e89..0000000000 --- a/packages/ri/client/src/layers/Local/systems/ImpSystem/createImpSystem.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createEntity, runQuery, withValue, HasValue } from "@latticexyz/recs"; -import { random } from "@latticexyz/utils"; -import { Time } from "../../../../utils/time"; -import { LocalEntityTypes, LocalLayer } from "../../types"; - -/** - * The Imp system handles spawning imps once a second until the max population is reached. - */ -export function createImpSystem(layer: LocalLayer) { - const { - world, - components: { Strolling, LocalEntityType, LocalPosition, MoveSpeed, Selectable }, - } = layer; - - const spawnImp = () => { - const getNumImps = () => runQuery([HasValue(LocalEntityType, { entityType: LocalEntityTypes.Imp })]).size; - - if (getNumImps() > 5) return; - const coord = { - x: random(7), - y: random(7), - }; - - createEntity( - world, - [ - withValue(LocalEntityType, { entityType: LocalEntityTypes.Imp }), - withValue(Strolling, { value: true }), - withValue(LocalPosition, coord), - withValue(MoveSpeed, { default: 500, current: 500 }), - withValue(Selectable, { value: true }), // Remove this later, imps are not selectable - ], - { idSuffix: "imp" } - ); - }; - - const dispose = Time.time.setInterval(spawnImp, 1000); - world.registerDisposer(dispose); -} diff --git a/packages/ri/client/src/layers/Local/systems/ImpSystem/index.ts b/packages/ri/client/src/layers/Local/systems/ImpSystem/index.ts deleted file mode 100644 index acaa46695e..0000000000 --- a/packages/ri/client/src/layers/Local/systems/ImpSystem/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { createImpSystem } from "./createImpSystem"; diff --git a/packages/ri/client/src/layers/Local/systems/index.ts b/packages/ri/client/src/layers/Local/systems/index.ts index b54c1f1e2b..902cb9c568 100644 --- a/packages/ri/client/src/layers/Local/systems/index.ts +++ b/packages/ri/client/src/layers/Local/systems/index.ts @@ -1,5 +1,4 @@ export { createSyncSystem } from "./SyncSystem"; -export { createImpSystem } from "./ImpSystem"; export { createPathSystem } from "./PathSystem"; export { createDestinationSystem } from "./DestinationSystem"; export { createPositionSystem } from "./PositionSystem"; diff --git a/packages/ri/client/src/layers/Renderer/Phaser/createPhaserLayer.ts b/packages/ri/client/src/layers/Renderer/Phaser/createPhaserLayer.ts index 26a4e686f1..381b9d3245 100644 --- a/packages/ri/client/src/layers/Renderer/Phaser/createPhaserLayer.ts +++ b/packages/ri/client/src/layers/Renderer/Phaser/createPhaserLayer.ts @@ -27,6 +27,7 @@ import { curry } from "lodash"; import { WorldCoord } from "@latticexyz/phaserx/src/types"; import { createDrawHighlightCoordSystem } from "./systems/DrawHighlightCoordSystem"; import { createDrawPotentialPathSystem } from "./systems/DrawPotentialPathSystem/createDrawPotentialPathSystem"; +import { createPlayerSpawnSystem } from "./systems/PlayerSpawnSystem"; /** * The Phaser layer extends the Local layer. @@ -90,12 +91,12 @@ export async function createPhaserLayer(local: LocalLayer) { createOutlineSystem(layer); createHueTintSystem(layer); createSelectionSystem(layer); - // createSelectionOutlineSystem(layer); createDrawDevHighlightSystem(layer); createInputSystem(layer); createDrawStaminaSystem(layer); createDrawHighlightCoordSystem(layer); createDrawPotentialPathSystem(layer); + createPlayerSpawnSystem(layer); return layer; } diff --git a/packages/ri/client/src/layers/Renderer/Phaser/systems/InputSystem/createInputSystem.ts b/packages/ri/client/src/layers/Renderer/Phaser/systems/InputSystem/createInputSystem.ts index bb1a2ed43c..c75eb8d3ff 100644 --- a/packages/ri/client/src/layers/Renderer/Phaser/systems/InputSystem/createInputSystem.ts +++ b/packages/ri/client/src/layers/Renderer/Phaser/systems/InputSystem/createInputSystem.ts @@ -11,7 +11,7 @@ export function createInputSystem(layer: PhaserLayer) { api: { highlightCoord }, parentLayers: { headless: { - api: { moveEntity, joinGame }, + api: { moveEntity }, }, local: { components: { Selected }, @@ -53,13 +53,4 @@ export function createInputSystem(layer: PhaserLayer) { .subscribe((coord) => { highlightCoord(coord); }); - - input.click$ - .pipe( - map((pointer) => ({ x: pointer.worldX, y: pointer.worldY })), // Map pointer to pointer pixel cood - map((pixel) => pixelToWorldCoord(maps.Main, pixel)) // Map pixel coord to tile coord - ) - .subscribe((coord) => { - joinGame(coord); - }); } diff --git a/packages/ri/client/src/layers/Renderer/Phaser/systems/PlayerSpawnSystem/createPlayerSpawnSystem.ts b/packages/ri/client/src/layers/Renderer/Phaser/systems/PlayerSpawnSystem/createPlayerSpawnSystem.ts new file mode 100644 index 0000000000..0fca746d48 --- /dev/null +++ b/packages/ri/client/src/layers/Renderer/Phaser/systems/PlayerSpawnSystem/createPlayerSpawnSystem.ts @@ -0,0 +1,41 @@ +import { defineEnterSystem, getComponentValue, Has } from "@latticexyz/recs"; +import { getPlayerEntity } from "@latticexyz/std-client"; +import { PhaserLayer } from "../../types"; + +export function createPlayerSpawnSystem(layer: PhaserLayer) { + const { + world, + parentLayers: { + network: { + personaId, + components: { OwnedBy, Persona, Position }, + }, + }, + scenes: { + Main: { + camera, + maps: { + Main: { tileWidth, tileHeight }, + }, + }, + }, + } = layer; + + let alreadyZoomed = false; + + defineEnterSystem(world, [Has(OwnedBy), Has(Position)], ({ entity }) => { + if (alreadyZoomed) return; + if (!personaId) return; + + const playerEntity = getPlayerEntity(Persona, personaId); + const ownedBy = getComponentValue(OwnedBy, entity)?.value; + + if (ownedBy === world.entities[playerEntity]) { + const position = getComponentValue(Position, entity); + if (!position) return; + + camera.centerCameraOnCoord(position, tileWidth, tileHeight); + alreadyZoomed = true; + } + }); +} diff --git a/packages/ri/client/src/layers/Renderer/Phaser/systems/PlayerSpawnSystem/index.ts b/packages/ri/client/src/layers/Renderer/Phaser/systems/PlayerSpawnSystem/index.ts new file mode 100644 index 0000000000..65127f3a27 --- /dev/null +++ b/packages/ri/client/src/layers/Renderer/Phaser/systems/PlayerSpawnSystem/index.ts @@ -0,0 +1 @@ +export * from "./createPlayerSpawnSystem"; diff --git a/packages/ri/client/src/layers/Renderer/Phaser/systems/SyncSystem/createSyncSystem.ts b/packages/ri/client/src/layers/Renderer/Phaser/systems/SyncSystem/createSyncSystem.ts index 53ba549861..8e06bf9c32 100644 --- a/packages/ri/client/src/layers/Renderer/Phaser/systems/SyncSystem/createSyncSystem.ts +++ b/packages/ri/client/src/layers/Renderer/Phaser/systems/SyncSystem/createSyncSystem.ts @@ -1,8 +1,9 @@ -import { HasValue, Has, defineSyncSystem } from "@latticexyz/recs"; +import { HasValue, Has, defineSyncSystem, getComponentValue } from "@latticexyz/recs"; import { PhaserLayer } from "../../types"; import { LocalEntityTypes } from "../../../../Local/types"; import { EntityTypes } from "../../../../Network/types"; import { Animations, Assets } from "../../constants"; +import { getPersonaColor } from "@latticexyz/std-client"; /** * The Sync system handles adding Phaser layer components to entites based on components they have on parent layers @@ -12,7 +13,7 @@ export function createSyncSystem(layer: PhaserLayer) { world, parentLayers: { network: { - components: { EntityType }, + components: { EntityType, Persona, OwnedBy }, }, local: { components: { LocalEntityType, Selected }, @@ -50,9 +51,23 @@ export function createSyncSystem(layer: PhaserLayer) { defineSyncSystem( world, - [HasValue(LocalEntityType, { entityType: LocalEntityTypes.Hero })], + [Has(OwnedBy)], () => HueTint, - () => ({ value: 0xff0000 }) + (entity) => { + console.log("hue tint sync"); + + const ownedBy = getComponentValue(OwnedBy, entity)?.value; + if (!ownedBy) return { value: 0xff0000 }; + + const ownedByIndex = world.entityToIndex.get(ownedBy); + if (!ownedByIndex) return { value: 0xff0000 }; + + const ownerPersonaId = getComponentValue(Persona, ownedByIndex)?.value; + if (!ownerPersonaId) return { value: 0xff0000 }; + + const personaColor = getPersonaColor(ownerPersonaId); + return { value: personaColor }; + } ); defineSyncSystem( diff --git a/packages/ri/client/src/layers/Renderer/React/components/Appearance.tsx b/packages/ri/client/src/layers/Renderer/React/components/Appearance.tsx deleted file mode 100644 index caaa95250a..0000000000 --- a/packages/ri/client/src/layers/Renderer/React/components/Appearance.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { getComponentValue } from "@latticexyz/recs"; -import { registerUIComponent, Spritesheet } from "../engine"; -import imp from "../../assets/imp.png"; - -const sprites: { [key: string]: { spriteWidth: number; imgPath: string; nFrames: number } | undefined } = { - Imp: { spriteWidth: 24, imgPath: imp, nFrames: 4 }, -}; - -export function registerAppearance() { - registerUIComponent( - "Appearance", - (layers, selectedEntities) => { - if (selectedEntities.size !== 1) return; - const selectedEntity = [...selectedEntities][0]; - const { Appearance } = layers.phaser.components; - const appearance = getComponentValue(Appearance, selectedEntity); - if (!appearance) return; - return sprites[appearance.value]; - }, - (sprite) => { - return ( - <> - Entity: - - ); - } - ); -} diff --git a/packages/ri/client/src/layers/Renderer/React/components/ComponentBrowser.tsx b/packages/ri/client/src/layers/Renderer/React/components/ComponentBrowser.tsx index 8b4030f9a0..24223f1816 100644 --- a/packages/ri/client/src/layers/Renderer/React/components/ComponentBrowser.tsx +++ b/packages/ri/client/src/layers/Renderer/React/components/ComponentBrowser.tsx @@ -6,6 +6,12 @@ import { Component, Entity, hasComponent } from "@latticexyz/recs"; export function registerComponentBrowser() { registerUIComponent( "ComponentBrowser", + { + rowStart: 1, + rowEnd: 5, + colStart: 6, + colEnd: 6 + }, (layers) => { const numEntities = layers.network.world.entities.length; if (numEntities > 1000) return numEntities; diff --git a/packages/ri/client/src/layers/Renderer/React/components/JoinGame.tsx b/packages/ri/client/src/layers/Renderer/React/components/JoinGame.tsx new file mode 100644 index 0000000000..5953f25e8d --- /dev/null +++ b/packages/ri/client/src/layers/Renderer/React/components/JoinGame.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { registerUIComponent } from "../engine"; +import { getPersonaColor, getPlayerEntity } from "@latticexyz/std-client"; +import { getComponentValue } from "@latticexyz/recs"; +import { BigNumber } from "ethers"; + +export function registerJoinGame() { + registerUIComponent( + "JoinGameWindow", + { + colStart: 3, + colEnd: 4, + rowStart: 1, + rowEnd: 1, + }, + (layers) => { + const { + headless: { + api: { joinGame }, + }, + network: { + personaId, + components: { Persona }, + }, + local: { + singletonEntity, + components: { Selection }, + }, + } = layers; + const playerEntity = personaId ? getPlayerEntity(Persona, personaId) : undefined; + const selection = getComponentValue(Selection, singletonEntity); + + return { + joinGame, + personaId, + playerEntity, + selection, + }; + }, + ({ joinGame, personaId, playerEntity, selection }) => { + const joined = !!playerEntity; + const playerColor = getPersonaColor(BigNumber.from(personaId).toHexString()); + + return ( +
+

Join Game

+

PersonaId: {personaId}

+

Joined? {joined ? "yes" : "no"}

+ {!joined && ( + + )} +
+ ); + } + ); +} diff --git a/packages/ri/client/src/layers/Renderer/React/components/SelectedCoords.tsx b/packages/ri/client/src/layers/Renderer/React/components/SelectedCoords.tsx deleted file mode 100644 index aa2c589269..0000000000 --- a/packages/ri/client/src/layers/Renderer/React/components/SelectedCoords.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; -import { registerUIComponent } from "../engine"; -import { getComponentValue } from "@latticexyz/recs"; - -export function registerSelectedCoords() { - registerUIComponent( - "SelectedCoords", - (layers) => { - const { - components: { Selection }, - singletonEntity, - } = layers.local; - const selection = getComponentValue(Selection, singletonEntity); - if (selection && selection.height > 0 && selection.width > 0) return selection; - }, - (selection) => { - return <>Selected coords: {JSON.stringify(selection)}; - } - ); -} diff --git a/packages/ri/client/src/layers/Renderer/React/components/Selection.tsx b/packages/ri/client/src/layers/Renderer/React/components/Selection.tsx index 13d73c6070..1dd141cfb2 100644 --- a/packages/ri/client/src/layers/Renderer/React/components/Selection.tsx +++ b/packages/ri/client/src/layers/Renderer/React/components/Selection.tsx @@ -1,31 +1,60 @@ import React from "react"; import { registerUIComponent } from "../engine"; -import { setPreviewEntity } from "../engine/store"; +import { getComponentValue, Has, runQuery } from "@latticexyz/recs"; +import { getPersonaColor } from "@latticexyz/std-client"; export function registerSelection() { registerUIComponent( - "Selection", - (layers, selectedEntities) => (selectedEntities.size > 1 ? { layers, selectedEntities } : null), - ({ layers, selectedEntities }) => { - const { selectEntity, resetSelection } = layers.local.api; + "SelectedCoords", + { + rowStart: 4, + rowEnd: 4, + colStart: 1, + colEnd: 1, + }, + (layers) => { + const { + local: { + components: { Selection, Selected }, + singletonEntity, + }, + network: { + components: { OwnedBy, Persona }, + }, + } = layers; + const selection = getComponentValue(Selection, singletonEntity); + const getPersonaOfOwner = (selectedEntity: number) => { + const ownedBy = getComponentValue(OwnedBy, selectedEntity)?.value; + if (!ownedBy) return null; + const ownerEntityIndex = layers.network.world.entityToIndex.get(ownedBy); + if (!ownerEntityIndex) return null; + + return getComponentValue(Persona, ownerEntityIndex)?.value; + }; + + const selectedEntity = [...runQuery([Has(Selected)])][0]; + + return { + selection, + selectedEntity: { + ownerPersonaId: getPersonaOfOwner(selectedEntity), + }, + }; + }, + ({ selection, selectedEntity }) => { return ( <> - Selection:{" "} - {[...selectedEntities].map((entity) => ( + x: {selection?.x} +
+ y: {selection?.y} +
+ {selectedEntity.ownerPersonaId && (

{ - resetSelection(); - selectEntity(entity); - setPreviewEntity(undefined); - }} - onMouseEnter={() => setPreviewEntity(entity)} - onMouseLeave={() => setPreviewEntity(undefined)} - > - {entity} -

- ))} + style={{ color: getPersonaColor(selectedEntity.ownerPersonaId).toString(16) }} + >{`owner persona: ${selectedEntity.ownerPersonaId}`}

+ )} +
); } diff --git a/packages/ri/client/src/layers/Renderer/React/components/TurnTimer.css b/packages/ri/client/src/layers/Renderer/React/components/TurnTimer.css new file mode 100644 index 0000000000..d3005cfbe1 --- /dev/null +++ b/packages/ri/client/src/layers/Renderer/React/components/TurnTimer.css @@ -0,0 +1,52 @@ +.base-timer { + position: relative; + width: 300px; + height: 300px; +} + +.base-timer__svg { + transform: scaleX(-1); +} + +.base-timer__circle { + fill: none; + stroke: none; +} + +.base-timer__path-elapsed { + stroke-width: 7px; + stroke: grey; +} + +.base-timer__path-remaining { + stroke-width: 7px; + stroke-linecap: round; + transform: rotate(90deg); + transform-origin: center; + transition: 1s linear all; + fill-rule: nonzero; + stroke: currentColor; +} + +.base-timer__path-remaining.green { + color: rgb(65, 184, 131); +} + +.base-timer__path-remaining.orange { + color: orange; +} + +.base-timer__path-remaining.red { + color: red; +} + +.base-timer__label { + position: absolute; + width: 300px; + height: 300px; + top: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 48px; +} diff --git a/packages/ri/client/src/layers/Renderer/React/components/TurnTimer.tsx b/packages/ri/client/src/layers/Renderer/React/components/TurnTimer.tsx index f3a7e0be4e..db26e20e8b 100644 --- a/packages/ri/client/src/layers/Renderer/React/components/TurnTimer.tsx +++ b/packages/ri/client/src/layers/Renderer/React/components/TurnTimer.tsx @@ -1,39 +1,102 @@ import React from "react"; import { registerUIComponent } from "../engine"; -import { getCurrentTurn, getGameConfig } from "@latticexyz/std-client"; +import { getGameConfig } from "@latticexyz/std-client"; +import "./TurnTimer.css"; + +function calculateTimeFraction(timeLeft: number) { + const rawTimeFraction = timeLeft / 20; + return rawTimeFraction - (1 / 20) * (1 - rawTimeFraction); +} + +const FULL_DASH_ARRAY = 283; +function calculateCircleDashArray(timeLeft: number) { + return `${(calculateTimeFraction(timeLeft) * FULL_DASH_ARRAY).toFixed(0)} 283`; +} + +function remainingPathColor(timeLeft: number) { + const { alert, warning, info } = COLOR_CODES; + if (timeLeft <= alert.threshold) { + return alert.color; + } else if (timeLeft <= warning.threshold) { + return warning.color; + } + + return info.color; +} + +const WARNING_THRESHOLD = 10; +const ALERT_THRESHOLD = 5; + +const COLOR_CODES = { + info: { + color: "green" + }, + warning: { + color: "orange", + threshold: WARNING_THRESHOLD + }, + alert: { + color: "red", + threshold: ALERT_THRESHOLD + } +}; + +const TurnCountdown: React.FC<{ secondsLeft: number }> = ({ secondsLeft }) => { + return ( +
+ + + + + + + + {secondsLeft} + +
+ ); +}; export function registerTurnTimer() { registerUIComponent( "TurnTimer", + { + rowStart: 1, + rowEnd: 1, + colStart: 1, + colEnd: 1, + }, (layers) => { const { world, - network: { - clock - }, - components: { - GameConfig - } + network: { clock }, + components: { GameConfig }, } = layers.network; const gameConfig = getGameConfig(world, GameConfig); - if(!gameConfig) return; + if (!gameConfig) return; const gameStartTime = parseInt(gameConfig.startTime); const turnLength = parseInt(gameConfig.turnLength); const currentTime = clock.currentTime / 1000; const timeElapsed = currentTime - gameStartTime; - const secondsUntilNextTurn = turnLength - timeElapsed % turnLength; + const secondsUntilNextTurn = turnLength - (timeElapsed % turnLength); return { - currentTurn: getCurrentTurn(world, GameConfig, clock), - secondsUntilNextTurn + secondsUntilNextTurn, }; }, - ({ currentTurn, secondsUntilNextTurn }) => { - - return ( -

{currentTurn}: {secondsUntilNextTurn}

- ); + ({ secondsUntilNextTurn }) => { + return ; } ); } diff --git a/packages/ri/client/src/layers/Renderer/React/components/index.ts b/packages/ri/client/src/layers/Renderer/React/components/index.ts index e6cba4e699..d907325efc 100644 --- a/packages/ri/client/src/layers/Renderer/React/components/index.ts +++ b/packages/ri/client/src/layers/Renderer/React/components/index.ts @@ -1,13 +1,11 @@ -import { registerAppearance } from "./Appearance"; import { registerComponentBrowser } from "./ComponentBrowser"; -import { registerSelectedCoords } from "./SelectedCoords"; +import { registerJoinGame } from "./JoinGame"; import { registerSelection } from "./Selection"; import { registerTurnTimer } from "./TurnTimer"; export function registerUIComponents() { registerSelection(); - // registerAppearance(); - registerSelectedCoords(); registerTurnTimer(); registerComponentBrowser(); + registerJoinGame(); } diff --git a/packages/ri/client/src/layers/Renderer/React/engine/Engine.tsx b/packages/ri/client/src/layers/Renderer/React/engine/Engine.tsx index 9ba26a96ac..eea2e98065 100644 --- a/packages/ri/client/src/layers/Renderer/React/engine/Engine.tsx +++ b/packages/ri/client/src/layers/Renderer/React/engine/Engine.tsx @@ -3,31 +3,15 @@ import styled from "styled-components"; import { Layers } from "./types"; import { LayerContext, EngineContext } from "./context"; import { EngineStore } from "./store"; -import { MainWindow, PreviewWindow } from "./components"; +import { MainWindow } from "./components"; import { observer } from "mobx-react-lite"; export const Engine: React.FC<{ layers: Layers }> = observer(({ layers }) => { return ( - - - - + ); }); - -const Container = styled.div` - position: absolute; - left: 0; - top: 0; - height: 100vh; - width: 100vw; - pointer-events: none; - display: grid; - justify-content: start; - align-items: start; - grid-auto-flow: column; -`; diff --git a/packages/ri/client/src/layers/Renderer/React/engine/components/ComponentRenderer.tsx b/packages/ri/client/src/layers/Renderer/React/engine/components/ComponentRenderer.tsx index 394d9c8c7f..21e4a86772 100644 --- a/packages/ri/client/src/layers/Renderer/React/engine/components/ComponentRenderer.tsx +++ b/packages/ri/client/src/layers/Renderer/React/engine/components/ComponentRenderer.tsx @@ -6,6 +6,38 @@ import { Component, Entity } from "@latticexyz/recs"; import { useState } from "react"; import { useEffect } from "react"; import { merge, throttleTime } from "rxjs"; +import { Window } from "./Window"; +import styled from "styled-components"; +import { GridConfiguration } from "../types"; + +const UIGrid = styled.div` + display: grid; + grid-template-columns: repeat(6, 16.6%); + grid-template-rows: repeat(4, 25%); + position: absolute; + left: 0; + top: 0; + height: 100vh; + width: 100vw; + pointer-events: none; +`; + +const UIComponentContainer: React.FC<{ gridConfig: GridConfiguration }> = ({ children, gridConfig }) => { + const { colStart, colEnd, rowStart, rowEnd } = gridConfig; + + return ( + + {children} + + ); +}; export const ComponentRenderer: React.FC<{ selectedEntities: Set; @@ -26,15 +58,20 @@ export const ComponentRenderer: React.FC<{ return () => subscription?.unsubscribe(); }); return ( - <> + {filterNullishValues( // Iterate through all registered UIComponents // and return those whose requirements are fulfilled [...UIComponents.entries()].map(([key, UIComponent]) => { const data = UIComponent.requirement(layers, selectedEntities); - if (data) return
{UIComponent.render(data)}
; + if (data) + return ( + + {UIComponent.render(data)} + + ); }) )} - +
); }); diff --git a/packages/ri/client/src/layers/Renderer/React/engine/components/MainWindow.tsx b/packages/ri/client/src/layers/Renderer/React/engine/components/MainWindow.tsx index 5d99c8f1c1..1690d8559a 100644 --- a/packages/ri/client/src/layers/Renderer/React/engine/components/MainWindow.tsx +++ b/packages/ri/client/src/layers/Renderer/React/engine/components/MainWindow.tsx @@ -2,13 +2,10 @@ import React from "react"; import { observer } from "mobx-react-lite"; import { useSelectedEntities } from "../hooks"; import { ComponentRenderer } from "./ComponentRenderer"; -import { Window } from "./Window"; export const MainWindow: React.FC = observer(() => { const selectedEntities = useSelectedEntities(); return ( - - - + ); }); diff --git a/packages/ri/client/src/layers/Renderer/React/engine/components/PreviewWindow.tsx b/packages/ri/client/src/layers/Renderer/React/engine/components/PreviewWindow.tsx deleted file mode 100644 index d47d7e5b44..0000000000 --- a/packages/ri/client/src/layers/Renderer/React/engine/components/PreviewWindow.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; -import { ComponentRenderer } from "./ComponentRenderer"; -import { Window } from "./Window"; -import { useEngineStore } from "../hooks"; -import { observer } from "mobx-react-lite"; - -export const PreviewWindow: React.FC = observer(() => { - const { previewEntity } = useEngineStore(); - return previewEntity ? ( - - - - ) : null; -}); diff --git a/packages/ri/client/src/layers/Renderer/React/engine/components/Spritesheet.tsx b/packages/ri/client/src/layers/Renderer/React/engine/components/Spritesheet.tsx deleted file mode 100644 index cbc6f29092..0000000000 --- a/packages/ri/client/src/layers/Renderer/React/engine/components/Spritesheet.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -interface Props { - spriteWidth: number; - imgPath: string; - nFrames: number; - timePerFrame?: string; - spriteHeight?: number; - scale?: number; -} - -export const Spritesheet: React.FC = ({ spriteHeight, spriteWidth, imgPath, nFrames, timePerFrame, scale }) => { - return ( - - ); -}; - -const AnimatedImg = styled.div` - height: ${(p) => p.spriteHeight || p.spriteWidth}px; - width: ${(p) => p.spriteWidth}px; - transform: scale(${(p) => (p.scale ? p.scale : 2)}); - image-rendering: pixelated; - background: url(${(p) => p.imgPath}); - animation: play-${(p) => p.nFrames}-${(p) => p.spriteWidth} ${(p) => p.timePerFrame || "0.4s"} steps(${(p) => - p.nFrames}) infinite; - } - @keyframes play-${(p) => p.nFrames}-${(p) => p.spriteWidth} { - 100% { - background-position: -${(p) => p.nFrames * p.spriteWidth}px; - } -`; diff --git a/packages/ri/client/src/layers/Renderer/React/engine/components/Window.tsx b/packages/ri/client/src/layers/Renderer/React/engine/components/Window.tsx index 442c1e9919..e7c018992e 100644 --- a/packages/ri/client/src/layers/Renderer/React/engine/components/Window.tsx +++ b/packages/ri/client/src/layers/Renderer/React/engine/components/Window.tsx @@ -6,7 +6,7 @@ import { filter, fromEvent } from "rxjs"; const WINDOW_CLASSNAME = "react-ui-window"; -export const Window: React.FC = observer(({ children }) => { +export const Window: React.FC<{ style: React.CSSProperties }> = observer(({ children, style }) => { const { phaser: { scenes: { @@ -28,18 +28,20 @@ export const Window: React.FC = observer(({ children }) => { }, []); return ( - + {children} ); }); const Container = styled.div` - background-color: rgb(0, 0, 0, 0.5); - backdrop-filter: blur(10px); - max-width: 500px; - max-height: 100%; + width: 100%; + height: 100%; pointer-events: all; color: #fff; - margin: 10px; `; diff --git a/packages/ri/client/src/layers/Renderer/React/engine/components/index.ts b/packages/ri/client/src/layers/Renderer/React/engine/components/index.ts index be19682611..df5049a39a 100644 --- a/packages/ri/client/src/layers/Renderer/React/engine/components/index.ts +++ b/packages/ri/client/src/layers/Renderer/React/engine/components/index.ts @@ -1,5 +1,3 @@ export { Window } from "./Window"; export { ComponentRenderer } from "./ComponentRenderer"; -export { Spritesheet } from "./Spritesheet"; export { MainWindow } from "./MainWindow"; -export { PreviewWindow } from "./PreviewWindow"; diff --git a/packages/ri/client/src/layers/Renderer/React/engine/index.ts b/packages/ri/client/src/layers/Renderer/React/engine/index.ts index 26ef2a1b11..d9bcfc68fc 100644 --- a/packages/ri/client/src/layers/Renderer/React/engine/index.ts +++ b/packages/ri/client/src/layers/Renderer/React/engine/index.ts @@ -1,4 +1,3 @@ export { Engine } from "./Engine"; export { registerUIComponent } from "./store"; export { useLayers } from "./hooks"; -export { Spritesheet } from "./components"; diff --git a/packages/ri/client/src/layers/Renderer/React/engine/store.ts b/packages/ri/client/src/layers/Renderer/React/engine/store.ts index a37c3ed6db..5bb696da22 100644 --- a/packages/ri/client/src/layers/Renderer/React/engine/store.ts +++ b/packages/ri/client/src/layers/Renderer/React/engine/store.ts @@ -1,5 +1,5 @@ import { observable, action } from "mobx"; -import { UIComponent } from "./types"; +import { GridConfiguration, UIComponent } from "./types"; import { Entity } from "@latticexyz/recs"; export const EngineStore = observable({ @@ -8,8 +8,13 @@ export const EngineStore = observable({ }); export const registerUIComponent = action( - (id: string, requirement: UIComponent["requirement"], render: UIComponent["render"]) => { - EngineStore.UIComponents.set(id, { requirement, render }); + ( + id: string, + gridConfig: GridConfiguration, + requirement: UIComponent["requirement"], + render: UIComponent["render"] + ) => { + EngineStore.UIComponents.set(id, { requirement, render, gridConfig }); } ); diff --git a/packages/ri/client/src/layers/Renderer/React/engine/types.ts b/packages/ri/client/src/layers/Renderer/React/engine/types.ts index 44a1262b2e..9589f589d5 100644 --- a/packages/ri/client/src/layers/Renderer/React/engine/types.ts +++ b/packages/ri/client/src/layers/Renderer/React/engine/types.ts @@ -12,7 +12,10 @@ export type Layers = { phaser: PhaserLayer; }; +export type GridConfiguration = { colStart: number; colEnd: number; rowStart: number; rowEnd: number }; + export interface UIComponent { + gridConfig: GridConfiguration; requirement(layers: Layers, selectedEntities: Set): T | null | undefined; render(props: NonNullable): ReactElement | null; } diff --git a/packages/std-client/src/utils.ts b/packages/std-client/src/utils.ts index 7b0df6661a..cc4d76e0e2 100644 --- a/packages/std-client/src/utils.ts +++ b/packages/std-client/src/utils.ts @@ -1,3 +1,4 @@ +import Phaser from "phaser"; import { Component, getComponentValue, getEntitiesWithValue, Schema, Type, World } from "@latticexyz/recs"; import { keccak256 } from "@latticexyz/utils"; import { BigNumber, ethers } from "ethers"; @@ -41,3 +42,45 @@ export function getGameConfig( return gameConfig; } + +export function randomColor(id: string): number { + const randSeed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values + function seedRand(seed: string) { + for (let i = 0; i < randSeed.length; i++) { + randSeed[i] = 0; + } + for (let i = 0; i < seed.length; i++) { + randSeed[i % 4] = (randSeed[i % 4] << 5) - randSeed[i % 4] + seed.charCodeAt(i); + } + } + + function rand() { + const t = randSeed[0] ^ (randSeed[0] << 11); + randSeed[0] = randSeed[1]; + randSeed[1] = randSeed[2]; + randSeed[2] = randSeed[3]; + randSeed[3] = randSeed[3] ^ (randSeed[3] >> 19) ^ t ^ (t >> 8); + return (randSeed[3] >>> 0) / ((1 << 31) >>> 0); + } + + function createColor() { + // hue is the whole color spectrum + const h = Math.floor(rand() * 360) / 360; + //saturation goes from 40 to 100, it avoids greyish colors + // --> Multiply by 0.75 to limit saturation + // const s = ((rand() * 60 + 40) / 100) * 0.75; + const s = 80 / 100; + // lightness can be anything from 0 to 100, but probabilities are a bell curve around 50% + // --> Multiply by 0.65 to shift + // const l = (((rand() + rand() + rand() + rand()) * 25) / 100) * 0.65; + const l = 70 / 100; + return { h, s, l }; + } + seedRand(id); + const { h, s, l } = createColor(); + return Phaser.Display.Color.HSLToColor(h, s, l).color; +} + +export function getPersonaColor(personaId: string) { + return randomColor(keccak256(personaId).substring(3)); +}