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

feat: spawn points #74

Merged
merged 3 commits into from
Jul 12, 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
7 changes: 3 additions & 4 deletions packages/ri/client/src/layers/Headless/api/joinGame.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { WorldCoord } from "@latticexyz/phaserx/src/types";
import { EntityID, hasComponent } from "@latticexyz/recs";
import { EntityID, EntityIndex, hasComponent } from "@latticexyz/recs";
import { NetworkLayer } from "../../Network";
import { ActionSystem } from "../systems";

export function joinGame(network: NetworkLayer, actions: ActionSystem, targetPosition: WorldCoord) {
export function joinGame(network: NetworkLayer, actions: ActionSystem, spawnEntity: EntityIndex) {
const {
components: { Player },
network: { connectedAddress },
Expand Down Expand Up @@ -35,7 +34,7 @@ export function joinGame(network: NetworkLayer, actions: ActionSystem, targetPos
updates: () => [],
execute: () => {
console.log("spawning");
network.api.joinGame(targetPosition);
network.api.joinGame(world.entities[spawnEntity]);
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,12 @@ export function createSyncSystem(layer: LocalLayer) {
() => Selectable,
() => ({ value: true })
);

// Add Selectable to Settlements
defineSyncSystem(
world,
[HasValue(EntityType, { value: EntityTypes.Settlement })],
() => Selectable,
() => ({ value: true })
);
}
12 changes: 9 additions & 3 deletions packages/ri/client/src/layers/Network/createNetworkLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ export async function createNetworkLayer(config: NetworkLayerConfig) {
{ value: Type.Boolean },
{ id: "Capturable", metadata: { contractId: keccak256("ember.component.capturable") } }
),
SpawnPoint: defineComponent(
world,
{ value: Type.Boolean },
{ id: "SpawnPoint", metadata: { contractId: keccak256("ember.component.spawnPoint") } }
),
};

// Define mappings between contract and client components
Expand All @@ -132,6 +137,7 @@ export async function createNetworkLayer(config: NetworkLayerConfig) {
[keccak256("ember.component.prototypeCopy")]: "PrototypeCopy",
[keccak256("ember.component.factoryComponent")]: "Factory",
[keccak256("ember.component.capturable")]: "Capturable",
[keccak256("ember.component.spawnPoint")]: "SpawnPoint",
};

const contractConfig: SetupContractConfig = {
Expand Down Expand Up @@ -189,9 +195,9 @@ export async function createNetworkLayer(config: NetworkLayerConfig) {
);
}

async function joinGame(position: WorldCoord) {
console.log(`Joining game at position ${JSON.stringify(position)}`);
return systems["ember.system.playerJoin"].executeTyped(position);
async function joinGame(spawnEntity: EntityID) {
console.log(`Joining game at position ${spawnEntity}`);
return systems["ember.system.playerJoin"].executeTyped(BigNumber.from(spawnEntity));
}

async function moveEntity(entity: string, path: WorldCoord[]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HasValue, Has, defineSyncSystem, getComponentValue, defineSystem, setComponent } from "@latticexyz/recs";
import { HasValue, Has, defineSyncSystem } from "@latticexyz/recs";
import { PhaserLayer } from "../../types";
import { EntityTypes } from "../../../../Network/types";
import { Animations, Assets } from "../../constants";
Expand All @@ -11,18 +11,18 @@ export function createSyncSystem(layer: PhaserLayer) {
world,
parentLayers: {
network: {
components: { EntityType, OwnedBy },
components: { EntityType },
},
local: {
components: { Selected, LocalPosition },
},
},
components: { Appearance, SpriteAnimation, Outline, HueTint },
components: { Appearance, SpriteAnimation, Outline },
} = layer;

defineSyncSystem(
world,
[Has(Selected), Has(OwnedBy)],
[Has(Selected)],
() => Outline,
() => ({ color: 0xfff000 })
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import { registerUIComponent } from "../engine";
import { getAddressColor } from "@latticexyz/std-client";
import { defineQuery, EntityID, getComponentValueStrict, HasValue } from "@latticexyz/recs";
import { defineQuery, EntityID, getComponentValue, getComponentValueStrict, Has, HasValue, runQuery } from "@latticexyz/recs";
import { map, merge } from "rxjs";
import { computedToStream } from "@latticexyz/utils";

Expand All @@ -25,26 +25,29 @@ export function registerJoinGame() {
world,
},
local: {
singletonEntity,
components: { Selection },
components: { Selected, LocalPosition },
},
} = layers;

return merge(computedToStream(connectedAddress), Selection.update$, Player.update$).pipe(
return merge(computedToStream(connectedAddress), Selected.update$, Player.update$).pipe(
map(() => connectedAddress.get()),
map((address) => {
const playerEntity = world.entityToIndex.get(address as EntityID);
const selection = getComponentValueStrict(Selection, singletonEntity);

const selectedEntity = [...runQuery([Has(Selected)])][0];
Copy link
Member

@alvrs alvrs Jul 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't this throw an index out of bounds error if there is no entity with Selected component?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe it will return undefined and then fail to find a position, but that is okay

const position = getComponentValue(LocalPosition, selectedEntity);

return {
joinGame,
selection,
selectedEntity,
position,
playerEntity,
address,
};
})
);
},
({ joinGame, playerEntity, selection, address }) => {
({ joinGame, playerEntity, selectedEntity, position, address }) => {
const joined = playerEntity != undefined;
const playerColor = getAddressColor(address || "");

Expand All @@ -53,15 +56,13 @@ export function registerJoinGame() {
{!joined && <h1>Join Game</h1>}
<p style={{ color: playerColor.toString(16) }}>Address: {address}</p>
<p>Joined? {joined ? "yes" : "no"}</p>
{!joined && (
{!joined && selectedEntity != null && (
<button
disabled={!selection}
onClick={() => {
if (!selection) return;
joinGame({ x: selection.x, y: selection.y });
joinGame(selectedEntity);
}}
>
Join at Position ({selection?.x},{selection?.y})
Join at Position ({position?.x},{position?.y})
</button>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/ri/contracts/deploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"MineableComponent",
"MovableComponent",
"OwnedByComponent",
"SpawnPointComponent",
"StaminaComponent",
"UntraversableComponent",
"PositionComponent",
Expand Down
9 changes: 9 additions & 0 deletions packages/ri/contracts/src/components/SpawnPointComponent.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.0;
import "std-contracts/components/BoolComponent.sol";

uint256 constant ID = uint256(keccak256("ember.component.spawnPoint"));

contract SpawnPointComponent is BoolComponent {
constructor(address world) BoolComponent(world, ID) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { HealthComponent, Health, ID as HealthComponentID } from "../components/
import { AttackComponent, Attack, ID as AttackComponentID } from "../components/AttackComponent.sol";
import { FactoryComponent, Factory, ID as FactoryComponentID } from "../components/FactoryComponent.sol";
import { CapturableComponent, ID as CapturableComponentID } from "../components/CapturableComponent.sol";

import { ID as SoldierID } from "./SoldierPrototype.sol";

uint256 constant ID = uint256(keccak256("ember.prototype.settlement"));
Expand Down
27 changes: 26 additions & 1 deletion packages/ri/contracts/src/systems/InitSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import { ISystem } from "solecs/interfaces/ISystem.sol";
import { IWorld } from "solecs/interfaces/IWorld.sol";
import { IUint256Component } from "solecs/interfaces/IUint256Component.sol";

import { getAddressById, getComponentById, addressToEntity, getSystemAddressById } from "solecs/utils.sol";

import { LibPrototype } from "../libraries/LibPrototype.sol";

import { PositionComponent, ID as PositionComponentID, Coord } from "../components/PositionComponent.sol";
import { SpawnPointComponent, ID as SpawnPointComponentID } from "../components/SpawnPointComponent.sol";

import { SoldierPrototype } from "../prototypes/SoldierPrototype.sol";
import { SettlementPrototype } from "../prototypes/SettlementPrototype.sol";
import { SettlementPrototype, ID as SettlementID } from "../prototypes/SettlementPrototype.sol";

uint256 constant ID = uint256(keccak256("ember.system.init"));

Expand Down Expand Up @@ -33,5 +40,23 @@ contract InitSystem is ISystem {
// Initialize Prototypes
SoldierPrototype(components);
SettlementPrototype(components);

uint256 spawnPoint = world.getUniqueEntityId();
LibPrototype.copyPrototype(components, SettlementID, spawnPoint);
Comment on lines +44 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the choice of making spawn points ownerless settlements!


SpawnPointComponent(getAddressById(components, SpawnPointComponentID)).set(spawnPoint);
PositionComponent(getAddressById(components, PositionComponentID)).set(spawnPoint, Coord(0, 0));

spawnPoint = world.getUniqueEntityId();
LibPrototype.copyPrototype(components, SettlementID, spawnPoint);

SpawnPointComponent(getAddressById(components, SpawnPointComponentID)).set(spawnPoint);
PositionComponent(getAddressById(components, PositionComponentID)).set(spawnPoint, Coord(20, 10));

spawnPoint = world.getUniqueEntityId();
LibPrototype.copyPrototype(components, SettlementID, spawnPoint);

SpawnPointComponent(getAddressById(components, SpawnPointComponentID)).set(spawnPoint);
PositionComponent(getAddressById(components, PositionComponentID)).set(spawnPoint, Coord(5, 20));
}
}
75 changes: 33 additions & 42 deletions packages/ri/contracts/src/systems/PlayerJoinSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { StaminaComponent, Stamina, ID as StaminaComponentID } from "../componen
import { LastActionTurnComponent, ID as LastActionTurnComponentID } from "../components/LastActionTurnComponent.sol";
import { HealthComponent, Health, ID as HealthComponentID } from "../components/HealthComponent.sol";
import { AttackComponent, Attack, ID as AttackComponentID } from "../components/AttackComponent.sol";
import { SpawnPointComponent, ID as SpawnPointComponentID } from "../components/SpawnPointComponent.sol";
import { LastActionTurnComponent, ID as LastActionTurnComponentID } from "../components/LastActionTurnComponent.sol";

import { ID as SoldierID } from "../prototypes/SoldierPrototype.sol";
import { ID as SettlementID } from "../prototypes/SettlementPrototype.sol";
Expand All @@ -36,51 +38,55 @@ contract PlayerJoinSystem is ISystem {
}

function requirement(bytes memory arguments) public view returns (bytes memory) {
IComponent playerComponent = getComponentById(components, PlayerComponentID);
PlayerComponent playerComponent = PlayerComponent(getAddressById(components, PlayerComponentID));
require(!playerComponent.has(addressToEntity(msg.sender)), "player already spawned");

Coord memory position = abi.decode(arguments, (Coord));
Coord[] memory spawnPositions = new Coord[](5);

spawnPositions[0] = position;
spawnPositions[1] = Coord(position.x + 1, position.y);
spawnPositions[2] = Coord(position.x - 1, position.y);
spawnPositions[3] = Coord(position.x, position.y + 1);
spawnPositions[4] = Coord(position.x, position.y - 1);
uint256 spawnEntity = abi.decode(arguments, (uint256));
require(
SpawnPointComponent(getAddressById(components, SpawnPointComponentID)).has(spawnEntity),
"that is not a spawn point"
);

PositionComponent positionComponent = PositionComponent(getAddressById(components, PositionComponentID));
for (uint256 i; i < spawnPositions.length; i++) {
require(positionComponent.getEntitiesWithValue(spawnPositions[i]).length == 0, "spot taken");
}
require(positionComponent.has(spawnEntity), "spawn has no location");
Coord memory spawnPosition = positionComponent.getValue(spawnEntity);

Coord[] memory unitPositions = new Coord[](4);

unitPositions[0] = Coord(spawnPosition.x + 1, spawnPosition.y);
unitPositions[1] = Coord(spawnPosition.x - 1, spawnPosition.y);
unitPositions[2] = Coord(spawnPosition.x, spawnPosition.y + 1);
unitPositions[3] = Coord(spawnPosition.x, spawnPosition.y - 1);

return abi.encode(playerComponent, spawnPositions);
return abi.encode(spawnEntity, unitPositions, playerComponent);
}

function execute(bytes memory arguments) public returns (bytes memory) {
(IComponent playerComponent, Coord[] memory spawnPositions) = abi.decode(
(uint256 spawnEntity, Coord[] memory unitPositions, PlayerComponent playerComponent) = abi.decode(
requirement(arguments),
(IComponent, Coord[])
(uint256, Coord[], PlayerComponent)
);

// Create player entity
uint256 playerEntity = addressToEntity(msg.sender);
PlayerComponent(address(playerComponent)).set(playerEntity);

for (uint256 i; i < spawnPositions.length; i++) {
if (i == 0) {
spawnSettlement(playerEntity, spawnPositions[i]);
} else {
spawnSoldier(playerEntity, spawnPositions[i]);
}
playerComponent.set(playerEntity);
OwnedByComponent(getAddressById(components, OwnedByComponentID)).set(spawnEntity, playerEntity);
LastActionTurnComponent(getAddressById(components, LastActionTurnComponentID)).set(
spawnEntity,
LibStamina.getCurrentTurn(components)
);

for (uint256 i; i < unitPositions.length; i++) {
spawnSoldier(playerEntity, unitPositions[i]);
}
}

function requirementTyped(Coord memory targetPosition) public view returns (bytes memory) {
return requirement(abi.encode(targetPosition));
function requirementTyped(uint256 spawnEntity) public view returns (bytes memory) {
return requirement(abi.encode(spawnEntity));
}

function executeTyped(Coord memory targetPosition) public returns (bytes memory) {
return execute(abi.encode(targetPosition));
function executeTyped(uint256 spawnEntity) public returns (bytes memory) {
return execute(abi.encode(spawnEntity));
}

// ------------------------
Expand All @@ -101,19 +107,4 @@ contract PlayerJoinSystem is ISystem {
LibStamina.getCurrentTurn(components)
);
}

function spawnSettlement(uint256 ownerId, Coord memory position) private {
uint256 entity = world.getUniqueEntityId();

LibPrototype.copyPrototype(components, SettlementID, entity);

OwnedByComponent(getAddressById(components, OwnedByComponentID)).set(entity, ownerId);

PositionComponent(getAddressById(components, PositionComponentID)).set(entity, position);

LastActionTurnComponent(getAddressById(components, LastActionTurnComponentID)).set(
entity,
LibStamina.getCurrentTurn(components) - 5
);
}
}