Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to select and move multiple units #27

Merged
merged 2 commits into from
Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class HueTintAndOutlineFXPipeline extends Phaser.Renderer.WebGL.Pipelines
float rightAlpha = texture2D(uMainSampler, outTexCoord + vec2(distance.x, 0.0)).a;
if (srcColor.a == 0.0 && max(max(upAlpha, downAlpha), max(leftAlpha, rightAlpha)) == 1.0)
{
outColor = vec4(0.0, 0.0, 0.0, 1.0);
outColor = vec4(0.0, 0.0, 255.0, 0.5);
}
}
gl_FragColor = outColor;
Expand Down
2 changes: 1 addition & 1 deletion packages/ri/client/src/boot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async function bootLayers() {
}

function disposeLayer(layer: keyof typeof layers) {
layers[layer]?.world.disposeAll();
layers[layer]?.world.dispose();
layers[layer] = undefined;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/ri/client/src/layers/Headless/api/joinGame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function joinGame(network: NetworkLayer, actions: ActionSystem, targetPos
updates: () => [],
execute: () => {
console.log(`spawning entityType: ${EntityTypes.Creature} at ${JSON.stringify(targetPosition)}`);
network.api.joinGame(targetPosition, EntityTypes.Creature);
network.api.joinGame(targetPosition);
},
});
}
54 changes: 29 additions & 25 deletions packages/ri/client/src/layers/Headless/api/moveEntity.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { getPlayerEntity } from "@latticexyz/std-client";
import { getComponentValueStrict, Has, hasComponent, HasValue, setComponent, Type, runQuery } from "@latticexyz/recs";
import {
getComponentValueStrict,
hasComponent,
HasValue,
setComponent,
Type,
runQuery,
getComponentValue,
} from "@latticexyz/recs";
import { ActionSystem, HeadlessLayer } from "../types";

const Directions: { [key: string]: { x: number; y: number } } = {
export const Directions = {
Up: { x: 0, y: -1 },
Right: { x: 1, y: 0 },
Down: { x: 0, y: 1 },
Expand All @@ -12,11 +20,14 @@ const Directions: { [key: string]: { x: number; y: number } } = {
interface MoveData {
action: string;
targetPosition: { x: Type.Number; y: Type.Number };
netStamina: number;
targetEntity?: string;
}

export function moveEntity(layer: HeadlessLayer, actions: ActionSystem, direction: string) {
export function moveEntity(
layer: HeadlessLayer,
actions: ActionSystem,
entity: number,
direction: keyof typeof Directions
) {
const networkLayer = layer.parentLayers.network;
if (!networkLayer.personaId) {
console.warn("Persona ID not found.");
Expand All @@ -29,13 +40,17 @@ export function moveEntity(layer: HeadlessLayer, actions: ActionSystem, directio
networkLayer.components;
const { LocalCurrentStamina } = layer.components;

const playerEntity = getPlayerEntity(Persona, networkLayer.personaId);
const playerEntityIndex = getPlayerEntity(Persona, networkLayer.personaId);
const delta = Directions[direction];
const playerCharacter = runQuery([HasValue(OwnedBy, { value: world.entities[playerEntity] }), Has(Movable)]);
if (playerCharacter.size === 0) throw new Error("Player not found");
if (playerCharacter.size > 1) throw new Error("More than one player character found. Something is very wrong.");

const character = [...playerCharacter][0];
const entityCanMove = getComponentValue(Movable, entity)?.value;
if (!entityCanMove) return;

const movingEntityOwner = getComponentValue(OwnedBy, entity)?.value;
if (movingEntityOwner !== world.entities[playerEntityIndex]) return;

const netStamina = getComponentValueStrict(LocalCurrentStamina, entity).value - 1;
if (netStamina < 0) return;

const actionID = `move ${Math.random()}`;

Expand All @@ -50,14 +65,8 @@ export function moveEntity(layer: HeadlessLayer, actions: ActionSystem, directio
LocalCurrentStamina,
},
requirement: ({ Position, Untraversable }) => {
const currentPosition = getComponentValueStrict(Position, character);
const currentPosition = getComponentValueStrict(Position, entity);
const targetPosition = { x: currentPosition.x + delta.x, y: currentPosition.y + delta.y };
const netStamina = getComponentValueStrict(LocalCurrentStamina, character).value - 1;

if (netStamina < 0) {
actions.cancel(actionID);
return null;
}

const entities = runQuery([HasValue(Position, targetPosition)]);
for (const entity of entities) {
Expand All @@ -72,23 +81,18 @@ export function moveEntity(layer: HeadlessLayer, actions: ActionSystem, directio
updates: (_, data: MoveData) => [
{
component: "Position",
entity: character,
entity: entity,
value: { x: data.targetPosition.x, y: data.targetPosition.y },
},
{
component: "LocalCurrentStamina",
entity: character,
value: { value: data.netStamina },
},
],
execute: async (data: MoveData) => {
console.log("Execute action");
const tx = await layer.parentLayers.network.api.moveEntity(world.entities[character], {
const tx = await layer.parentLayers.network.api.moveEntity(world.entities[entity], {
x: data.targetPosition.x,
y: data.targetPosition.y,
});
await tx.wait();
setComponent(LocalCurrentStamina, character, { value: data.netStamina });
setComponent(LocalCurrentStamina, entity, { value: netStamina });
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function createHeadlessLayer(network: NetworkLayer) {
api: {
joinGame: curry(joinGame)(network, actions),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
moveEntity: (_: string) => {
moveEntity: (entity: number, direction: string) => {
"no-op for types";
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import {
setComponent,
} from "@latticexyz/recs";
import { getCurrentTurn } from "@latticexyz/std-client";
import { BigNumber } from "ethers";
import { HeadlessLayer } from "../..";

export function createCurrentStaminaSystem(layer: HeadlessLayer) {
const blockNumber$ = layer.parentLayers.network.network.blockNumber$;
const { CurrentStamina, LastActionTurn, StaminaRegeneration, MaxStamina, GameConfig } =
layer.parentLayers.network.components;
const { LocalCurrentStamina } = layer.components;
const {
parentLayers: {
network: {
network: { clock },
components: { CurrentStamina, LastActionTurn, StaminaRegeneration, MaxStamina, GameConfig },
},
},
components: { LocalCurrentStamina },
} = layer;

defineUpdateSystem(layer.world, [Has(CurrentStamina), Has(LocalCurrentStamina)], ({ entity, value, component }) => {
if (component !== CurrentStamina) return;
Expand All @@ -29,13 +33,9 @@ export function createCurrentStaminaSystem(layer: HeadlessLayer) {
setComponent(LocalCurrentStamina, entity, { value: updatedValue.value as number });
});

blockNumber$.forEach(() => {
clock.time$.forEach(() => {
const entities = runQuery([Has(CurrentStamina), Has(LastActionTurn), Has(StaminaRegeneration), Has(MaxStamina)]);
const currentTurn = getCurrentTurn(
layer.world,
GameConfig,
BigNumber.from(layer.parentLayers.network.network.clock.currentTime / 1000)
);
const currentTurn = getCurrentTurn(layer.world, GameConfig, clock);

for (const entity of entities) {
const contractStamina = getComponentValueStrict(CurrentStamina, entity).value;
Expand Down
6 changes: 5 additions & 1 deletion packages/ri/client/src/layers/Local/createLocalLayer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createEntity, setComponent, Entity, getComponentValue } from "@latticexyz/recs";
import { createEntity, setComponent, Entity, getComponentValue, defineComponent, Type } from "@latticexyz/recs";
import { HeadlessLayer } from "../Headless";
import {
defineStrollingComponent,
Expand All @@ -22,6 +22,7 @@ import {
} from "./systems";
import { DEFAULT_MOVE_SPEED } from "./constants";
import { Area } from "@latticexyz/utils";
import { createPotentialPathSystem } from "./systems/PotentialPathSystem";

/**
* The Local layer is the thrid layer in the client architecture and extends the Headless layer.
Expand All @@ -41,6 +42,7 @@ export async function createLocalLayer(headless: HeadlessLayer) {
const Selected = defineSelectedComponent(world);
const Selectable = defineSelectableComponent(world);
const RockWall = defineRockWallComponent(world);
const PotentialPath = defineComponent(world, { x: Type.NumberArray, y: Type.NumberArray }, { id: "PotentialPath" });

const components = {
LocalPosition,
Expand All @@ -53,6 +55,7 @@ export async function createLocalLayer(headless: HeadlessLayer) {
Selected,
Selectable,
RockWall,
PotentialPath,
};

// Constants
Expand Down Expand Up @@ -92,6 +95,7 @@ export async function createLocalLayer(headless: HeadlessLayer) {
createPositionSystem(layer);
createDestinationSystem(layer);
createPathSystem(layer);
createPotentialPathSystem(layer);

return layer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { defineSystem, getComponentValue, Has, removeComponent, setComponent, UpdateType } from "@latticexyz/recs";
import { LocalLayer } from "../..";

export function createPotentialPathSystem(layer: LocalLayer) {
const {
world,
components: { Selected, PotentialPath, LocalPosition },
parentLayers: {
headless: {
components: { LocalCurrentStamina },
},
},
} = layer;

defineSystem(world, [Has(Selected), Has(LocalPosition), Has(LocalCurrentStamina)], ({ type, entity }) => {
const currentStamina = getComponentValue(LocalCurrentStamina, entity);

if (type === UpdateType.Exit || currentStamina?.value === 0) {
removeComponent(PotentialPath, entity);
} else if ([UpdateType.Enter, UpdateType.Update].includes(type)) {
const position = getComponentValue(LocalPosition, entity);
if (!position) return;

// TODO replace with dynamic move distance and pathfinding
const potentialPaths = {
x: [position.x + 1, position.x - 1, position.x, position.x],
y: [position.y, position.y, position.y + 1, position.y - 1],
};
setComponent(PotentialPath, entity, potentialPaths);
}
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./createPotentialPathSystem";
6 changes: 3 additions & 3 deletions packages/ri/client/src/layers/Network/createNetworkLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ export async function createNetworkLayer(config?: NetworkLayerConfig) {
await txQueue.Game.addComponentToEntityExternally(BigNumber.from(entity), component.metadata.contractId, data);
}

async function joinGame(position: WorldCoord, entityType: number) {
console.log(`Spawning creature at position ${JSON.stringify(position)}`);
return txQueue.Game.joinGame(position, entityType);
async function joinGame(position: WorldCoord) {
console.log(`Joining game at position ${JSON.stringify(position)}`);
return txQueue.Game.joinGame(position);
}

async function moveEntity(entity: string, targetPosition: WorldCoord) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { WorldCoord } from "@latticexyz/phaserx/src/types";
import { setComponent } from "@latticexyz/recs";
import { PhaserLayer } from "../types";

export function highlightCoord(layer: PhaserLayer, coord: WorldCoord) {
const {
components: { HoverHighlight },
parentLayers: {
local: { singletonEntity },
},
} = layer;

setComponent(HoverHighlight, singletonEntity, { color: 0xb00b1e, ...coord });
}
1 change: 1 addition & 0 deletions packages/ri/client/src/layers/Renderer/Phaser/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { highlightCoord } from "./highlightCoord";
26 changes: 22 additions & 4 deletions packages/ri/client/src/layers/Renderer/Phaser/createPhaserLayer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { createWorld } from "@latticexyz/recs";
import { LocalLayer } from "../../Local";
import {
createMapSystem,
Expand All @@ -21,8 +20,13 @@ import {
defineHueTintComponent,
} from "./components";
import { config } from "./config";
import { createSelectionOutlineSystem } from "./systems/SelectionOutlineSystem";
import { defineDevHighlightComponent } from "@latticexyz/std-client";
import { defineComponent, Type } from "@latticexyz/recs";
import { highlightCoord as highlightCoordApi } from "./api";
import { curry } from "lodash";
import { WorldCoord } from "@latticexyz/phaserx/src/types";
import { createDrawHighlightCoordSystem } from "./systems/DrawHighlightCoordSystem";
import { createDrawPotentialPathSystem } from "./systems/DrawPotentialPathSystem/createDrawPotentialPathSystem";

/**
* The Phaser layer extends the Local layer.
Expand All @@ -38,7 +42,12 @@ export async function createPhaserLayer(local: LocalLayer) {
const Outline = defineOutlineComponent(world);
const HueTint = defineHueTintComponent(world);
const DevHighlight = defineDevHighlightComponent(world);
const components = { Appearance, SpriteAnimation, Outline, HueTint, DevHighlight };
const HoverHighlight = defineComponent(
world,
{ color: Type.OptionalNumber, x: Type.OptionalNumber, y: Type.OptionalNumber },
{ id: "HoverHighlight" }
);
const components = { Appearance, SpriteAnimation, Outline, HueTint, DevHighlight, HoverHighlight };

// Create phaser engine
const { game, scenes, dispose: disposePhaser } = await createPhaserEngine(config);
Expand All @@ -54,7 +63,14 @@ export async function createPhaserLayer(local: LocalLayer) {
},
game,
scenes,
api: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
highlightCoord: (coord: WorldCoord) => {
"no-op for types";
},
},
};
layer.api.highlightCoord = curry(highlightCoordApi)(layer);

// Debugger
// createDebugger(
Expand All @@ -74,10 +90,12 @@ export async function createPhaserLayer(local: LocalLayer) {
createOutlineSystem(layer);
createHueTintSystem(layer);
createSelectionSystem(layer);
createSelectionOutlineSystem(layer);
// createSelectionOutlineSystem(layer);
createDrawDevHighlightSystem(layer);
createInputSystem(layer);
createDrawStaminaSystem(layer);
createDrawHighlightCoordSystem(layer);
createDrawPotentialPathSystem(layer);

return layer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { tileCoordToPixelCoord } from "@latticexyz/phaserx";
import { Has, getComponentValueStrict, defineUpdateSystem } from "@latticexyz/recs";
import { PhaserLayer } from "../../types";

export function createDrawHighlightCoordSystem(layer: PhaserLayer) {
const {
world,
parentLayers: {
local: { singletonEntity },
},
scenes: {
Main: {
objectPool,
maps: {
Main: { tileWidth, tileHeight },
},
},
},
components: { HoverHighlight },
} = layer;

defineUpdateSystem(world, [Has(HoverHighlight)], ({ entity }) => {
const hoverHighlght = getComponentValueStrict(HoverHighlight, singletonEntity);
const highlight = objectPool.get(`${entity}-hover-highlight`, "Rectangle");
if (!hoverHighlght.x || !hoverHighlght.y) return;
const position = { x: hoverHighlght.x, y: hoverHighlght.y };

highlight.setComponent({
id: `highlight`,
once: (box) => {
const pixelCoord = tileCoordToPixelCoord(position, tileWidth, tileHeight);

// box.setVisible(true);
box.setStrokeStyle(3, hoverHighlght.color ?? 0xf0e71d, 0.5);
box.setSize(tileWidth, tileHeight);
box.setPosition(pixelCoord.x + tileWidth / 2, pixelCoord.y + tileHeight / 2);
},
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { createDrawHighlightCoordSystem } from "./createDrawHighlightCoordSystem";
Loading