Skip to content

Commit 53bc3cc

Browse files
Kooshabaalvrs
andauthored
feat: various tweaks for mudwar (#151)
* feat(ecs-browser): view selected entity, only show entity id in entity title * feat(phaserx): allow centering and scrolling in camera api * fix(phaserx): increase default chunk size * feat(phaserx): add hasComponent to embodied entities * feat(phaserx): disable keyboard input when all input is disabled * refactor(std-client): rename getAddressColor -> getStringColor * feat(ecs-browser): allow prototype spawning by name * feat(phaserx): add setZoom function to exported camera * fix(phaserx): hue tint shader now shades pixels with alpha < 1 * fix(phaserx): revert off screen padding increase (was causing performance issues) * fix(phaserx): remove unnecessary color conversion in hue tint shader * feat(std-client): add new TxReduced action state that happens before Complete * fix: allow skipping awaiting tx confirmation while still handling errors Co-authored-by: alvrs <alvarius@lattice.xyz>
1 parent dbdeade commit 53bc3cc

File tree

14 files changed

+82
-32
lines changed

14 files changed

+82
-32
lines changed

packages/ecs-browser/src/Browser.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const Browser = observer(
2020
devHighlightComponent,
2121
hoverHighlightComponent,
2222
prototypeComponent,
23+
nameComponent,
2324
spawnPrototypeAt,
2425
world,
2526
}: {
@@ -29,7 +30,8 @@ export const Browser = observer(
2930
devHighlightComponent: Component<{ value: Type.OptionalNumber }>;
3031
hoverHighlightComponent: Component<{ x: Type.OptionalNumber; y: Type.OptionalNumber }>;
3132
prototypeComponent?: Component<{ value: Type.StringArray }>;
32-
spawnPrototypeAt?: (prototypeId: EntityID, position: Coord) => void;
33+
nameComponent: Component<{ value: Type.String }>;
34+
spawnPrototypeAt: (prototypeId: EntityID, position: Coord) => void;
3335
world: World;
3436
}) => {
3537
const [filteredEntities, setFilteredEntities] = useState<EntityID[]>([]);
@@ -48,11 +50,12 @@ export const Browser = observer(
4850
clearDevHighlights={clearDevHighlights}
4951
setOverflow={setOverflow}
5052
/>
51-
{hoverHighlightComponent && prototypeComponent && spawnPrototypeAt && (
53+
{hoverHighlightComponent && prototypeComponent && spawnPrototypeAt && nameComponent && (
5254
<PrototypeCreator
5355
layers={layers}
5456
hoverHighlightComponent={hoverHighlightComponent}
5557
prototypeComponent={prototypeComponent}
58+
nameComponent={nameComponent}
5659
spawnPrototypeAt={spawnPrototypeAt}
5760
/>
5861
)}

packages/ecs-browser/src/EntityEditor.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,15 @@ export const EntityEditor = observer(
5555
onMouseLeave={() => clearDevHighlights()}
5656
>
5757
<div onClick={() => setOpened(!opened)} style={{ cursor: "pointer" }}>
58-
<h3 style={{ color: "white" }}>{world.entities[entity]}</h3>
58+
<div style={{ display: "flex", flexDirection: "row", justifyContent: 'space-between' }}>
59+
<h3 style={{ color: "white" }}>Entity {entity}</h3>
60+
<ComponentBrowserButton onClick={(e) => {
61+
e.stopPropagation();
62+
navigator.clipboard.writeText(world.entities[entity]);
63+
}}>
64+
Click to copy Entity ID
65+
</ComponentBrowserButton>
66+
</div>
5967
<ComponentBrowserButton onClick={() => setOpened(!opened)}>
6068
{opened ? <>&#9660;</> : <>&#9654;</>}
6169
</ComponentBrowserButton>

packages/ecs-browser/src/PrototypeCreator/PrototypeCreator.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, EntityID, Has, Layers, Type } from "@latticexyz/recs";
1+
import { Component, EntityID, getComponentValue, Has, Layers, Type } from "@latticexyz/recs";
22
import React, { useEffect, useState } from "react";
33
import { ComponentBrowserButton, ComponentBrowserSelect } from "../StyledComponents";
44
import { Coord } from "@latticexyz/phaserx";
@@ -8,8 +8,9 @@ export const PrototypeCreator: React.FC<{
88
layers: Layers;
99
spawnPrototypeAt: (prototypeId: EntityID, position: Coord) => void;
1010
prototypeComponent: Component<{ value: Type.StringArray }>;
11+
nameComponent: Component<{ value: Type.String }>;
1112
hoverHighlightComponent: Component<{ x: Type.OptionalNumber; y: Type.OptionalNumber }>;
12-
}> = ({ layers, prototypeComponent, hoverHighlightComponent, spawnPrototypeAt }) => {
13+
}> = ({ layers, prototypeComponent, nameComponent, hoverHighlightComponent, spawnPrototypeAt }) => {
1314
const [selectingPosition, setSelectingPosition] = useState(false);
1415
const [selectedPrototype, setSelectedPrototype] = useState<string | undefined>(undefined);
1516
const hoverHighlight = useComponentValueStream(hoverHighlightComponent);
@@ -52,9 +53,10 @@ export const PrototypeCreator: React.FC<{
5253
<option value="">None</option>
5354
{[...prototypes].map((entityIndex) => {
5455
const entityId = layers.network.world.entities[entityIndex];
56+
const name = getComponentValue(nameComponent, entityIndex)?.value;
5557
return (
5658
<option key={entityId} value={entityId}>
57-
{entityId}
59+
{name || entityId}
5860
</option>
5961
);
6062
})}

packages/ecs-browser/src/QueryBuilder/PositionFilterButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const PositionFilterButton: React.FC<{
3838
setSelectingPosition(true);
3939
}}
4040
>
41-
{selectingPosition ? `Click on tile to select position` : "Select Entities at a position"}
41+
{selectingPosition ? `Click on tile to select position` : "View Entities at a position"}
4242
</ComponentBrowserButton>
4343
);
4444
};

packages/ecs-browser/src/QueryBuilder/QueryBuilder.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ export const QueryBuilder = ({
204204
queryInputRef={queryInputRef}
205205
input={(layers.phaser as any)?.scenes.Main.phaserScene.input}
206206
/>
207+
<ComponentBrowserButton onClick={() => {
208+
queryInputRef.current?.focus();
209+
editQuery('[Has(Selected)]');
210+
}}>
211+
View Selected Entity
212+
</ComponentBrowserButton>
207213
<h3>Filter by Component</h3>
208214
<QueryShortcutContainer style={{ margin: "8px auto" }}>
209215
{orderBy(allComponents, (c) => c.id)

packages/phaserx/src/createCamera.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option
3131
// return Math.pow(2, Math.floor(Math.log(currentZoom * 2) / Math.log(2))) / 2;
3232
// }
3333

34+
function setZoom(zoom: number) {
35+
phaserCamera.setZoom(zoom);
36+
worldView$.next(phaserCamera.worldView);
37+
zoom$.next(zoom);
38+
}
39+
3440
const pinchSub = pinchStream$
3541
.pipe(
3642
throttleTime(10),
@@ -41,9 +47,7 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option
4147
.subscribe(([, zoom]) => {
4248
// Set the gesture zoom state to the current zoom value to avoid zooming beyond the max values
4349
if (gesture._ctrl.state.pinch) gesture._ctrl.state.pinch.offset[0] = zoom;
44-
phaserCamera.setZoom(zoom);
45-
worldView$.next(phaserCamera.worldView);
46-
zoom$.next(zoom);
50+
setZoom(zoom);
4751
});
4852

4953
const wheelSub = wheelStream$
@@ -62,9 +66,18 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option
6266
objectPool.ignoreCamera(phaserCamera.id, ignore);
6367
}
6468

65-
function centerCameraOnCoord(tileCoord: Coord, tileWidth: number, tileHeight: number) {
69+
function centerOnCoord(tileCoord: Coord, tileWidth: number, tileHeight: number) {
6670
const pixelCoord = tileCoordToPixelCoord(tileCoord, tileWidth, tileHeight);
67-
phaserCamera.centerOn(pixelCoord.x, pixelCoord.y);
71+
centerOn(pixelCoord.x, pixelCoord.y);
72+
}
73+
74+
function centerOn(x: number, y: number) {
75+
phaserCamera.centerOn(x, y);
76+
requestAnimationFrame(() => worldView$.next(phaserCamera.worldView));
77+
}
78+
79+
function setScroll(x: number, y: number) {
80+
phaserCamera.setScroll(x, y);
6881
requestAnimationFrame(() => worldView$.next(phaserCamera.worldView));
6982
}
7083

@@ -78,6 +91,9 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option
7891
wheelSub.unsubscribe();
7992
gesture.destroy();
8093
},
81-
centerCameraOnCoord,
94+
centerOnCoord,
95+
centerOn,
96+
setScroll,
97+
setZoom,
8298
};
8399
}

packages/phaserx/src/createEmbodiedEntity.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ export function createEmbodiedEntity<Type extends keyof GameObjectTypes>(
6363
if (activeGameObject && once) once(activeGameObject);
6464
}
6565

66+
function hasComponent(id: string): boolean {
67+
return onOnce.has(id) || onUpdate.has(id);
68+
}
69+
6670
function removeComponent(id: string, stop?: boolean) {
6771
onOnce.delete(id);
6872
onUpdate.delete(id);
@@ -85,6 +89,7 @@ export function createEmbodiedEntity<Type extends keyof GameObjectTypes>(
8589
gameObject.setScale(1, 1);
8690
gameObject.setOrigin(0, 0);
8791
gameObject.setAlpha(1);
92+
gameObject.setScrollFactor(1);
8893
if (isSprite(gameObject, type)) {
8994
gameObject.clearTint();
9095
gameObject.setTexture("");
@@ -123,7 +128,7 @@ export function createEmbodiedEntity<Type extends keyof GameObjectTypes>(
123128
activeGameObject = undefined;
124129
}
125130

126-
return { setComponent, removeComponent, spawn, despawn, position, id, setCameraFilter, type };
131+
return { setComponent, hasComponent, removeComponent, spawn, despawn, position, id, setCameraFilter, type };
127132
}
128133

129134
function executeGameObjectFunctions<Type extends keyof GameObjectTypes>(

packages/phaserx/src/createInput.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export function createInput(inputPlugin: Phaser.Input.InputPlugin) {
118118
for (const key of Object.keys(Phaser.Input.Keyboard.KeyCodes)) addKey(key);
119119

120120
// Subscriptions
121-
const keySub = keyboard$.subscribe((key) => {
121+
const keySub = keyboard$.pipe(filter(() => enabled.current)).subscribe((key) => {
122122
const keyName = codeToKey.get(key.keyCode);
123123
if (!keyName) return;
124124
runInAction(() => {

packages/phaserx/src/pipelines/HueTintAndOutlineFXPipeline.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,7 @@ export class HueTintAndOutlineFXPipeline extends SpritePipeline {
3030
float e = 1.0e-10;
3131
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
3232
}
33-
vec3 hsv2rgb(vec3 c)
34-
{
35-
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
36-
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
37-
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
38-
}
33+
3934
void main(void)
4035
{
4136
vec4 srcColor;
@@ -44,12 +39,11 @@ export class HueTintAndOutlineFXPipeline extends SpritePipeline {
4439
vec3 rgbColor;
4540
srcColor = texture2D(uMainSampler, outTexCoord);
4641
hsvColor = rgb2hsv(srcColor.rgb);
47-
if (hsvColor.g == 0.0 && srcColor.a == 1. && !(tintColor.r == 0.0 && tintColor.g == 0.0 && tintColor.b == 0.0))
42+
if (hsvColor.g == 0.0 && !(tintColor.r == 0.0 && tintColor.g == 0.0 && tintColor.b == 0.0))
4843
{
49-
vec3 color = hsv2rgb(hsvColor);
50-
rgbColor = color * tintColor;
44+
rgbColor = srcColor.rgb * tintColor;
5145
} else {
52-
rgbColor = hsv2rgb(hsvColor);
46+
rgbColor = srcColor.rgb;
5347
}
5448
outColor = vec4(rgbColor.r, rgbColor.g, rgbColor.b, srcColor.a);
5549
if(outline == 1) {

packages/phaserx/src/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ export type Camera = {
1616
zoom$: Observable<number>;
1717
ignore: (objectPool: ObjectPool, ignore: boolean) => void;
1818
dispose: () => void;
19-
centerCameraOnCoord: (tileCoord: Coord, tileWidth: number, tileHeight: number) => void;
19+
centerOnCoord: (tileCoord: Coord, tileWidth: number, tileHeight: number) => void;
20+
centerOn: (x: number, y: number) => void;
21+
setScroll: (x: number, y: number) => void;
22+
setZoom: (zoom: number) => void;
2023
};
2124

2225
export type GameObjectTypes = typeof GameObjectClasses;
@@ -64,6 +67,7 @@ export type Input = ReturnType<typeof createInput>;
6467

6568
export type EmbodiedEntity<Type extends keyof GameObjectTypes> = {
6669
setComponent: (component: GameObjectComponent<Type>) => void;
70+
hasComponent: (id: string) => boolean;
6771
removeComponent: (id: string, stop?: boolean) => void;
6872
spawn: () => void;
6973
despawn: () => void;

packages/std-client/src/systems/ActionSystem/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ export enum ActionState {
55
Complete,
66
Failed,
77
Cancelled,
8+
TxReduced,
89
}
910

1011
export const ActionStateString = {
12+
[ActionState.TxReduced]: "TxReduced",
1113
[ActionState.Requested]: "Requested",
1214
[ActionState.Executing]: "Executing",
1315
[ActionState.WaitingForTxEvents]: "WaitingForTxEvents",

packages/std-client/src/systems/ActionSystem/createActionSystem.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function createActionSystem(world: World, txReduced$: Observable<string>)
4141

4242
/**
4343
* Maps all components in a given components map to the respective components including pending updates
44-
* @param components Components to be mapped to components including pending updates
44+
* @param component Component to be mapped to components including pending updates
4545
* @returns Components including pending updates
4646
*/
4747
function withOptimisticUpdates<C extends Component>(component: C): C {
@@ -110,7 +110,7 @@ export function createActionSystem(world: World, txReduced$: Observable<string>)
110110

111111
/**
112112
* Checks the requirement of a given action and executes the action if the requirement is fulfilled
113-
* @param actionId ID of the action to check the requirement of
113+
* @param action Action to check the requirement of
114114
* @returns void
115115
*/
116116
function checkRequirement(action: ActionData) {
@@ -153,19 +153,26 @@ export function createActionSystem(world: World, txReduced$: Observable<string>)
153153
if (tx) {
154154
// Wait for all tx events to be reduced
155155
updateComponent(Action, action.entityIndex, { state: ActionState.WaitingForTxEvents });
156-
const txReduced = awaitStreamValue(txReduced$, (v) => v === tx.hash);
157-
await Promise.all([tx.wait(), txReduced]);
156+
const txConfirmed = tx.wait().catch(() => handleError(action)); // Also catch the error if not awaiting
157+
await awaitStreamValue(txReduced$, (v) => v === tx.hash);
158+
updateComponent(Action, action.entityIndex, { state: ActionState.TxReduced });
159+
if (action.awaitConfirmation) await txConfirmed;
158160
}
159161

160162
updateComponent(Action, action.entityIndex, { state: ActionState.Complete });
161163
} catch (e) {
162-
updateComponent(Action, action.entityIndex, { state: ActionState.Failed });
164+
handleError(action);
163165
}
164166

165167
// After the action is done executing (failed or completed), remove its actionData and remove the Action component
166168
remove(action.id);
167169
}
168170

171+
// Set the action's state to ActionState.Failed
172+
function handleError(action: ActionData) {
173+
updateComponent(Action, action.entityIndex, { state: ActionState.Failed });
174+
}
175+
169176
/**
170177
* Cancels the action with the given ID if it is in the "Requested" state.
171178
* @param actionId ID of the action to be cancelled

packages/std-client/src/systems/ActionSystem/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export interface ActionRequest<C extends Components, T> {
3232
// If txHashes are returned from the txQueue, the action will only be completed (and pending updates removed)
3333
// once all events from the given txHashes have been received and reduced.
3434
execute: (data: T) => Promise<ContractTransaction> | Promise<void> | void | undefined;
35+
36+
// Flag to set if the queue should wait for the underlying transaction to be confirmed (in addition to being reduced)
37+
awaitConfirmation?: boolean;
3538
}
3639

3740
export type ActionData = ActionRequest<Components, unknown> & {

packages/std-client/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export function randomColor(id: string): number {
167167
return Phaser.Display.Color.HSLToColor(h, s, l).color;
168168
}
169169

170-
export function getAddressColor(address: string) {
170+
export function getStringColor(address: string) {
171171
return randomColor(keccak256(address).substring(2));
172172
}
173173

0 commit comments

Comments
 (0)