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));
+}