So far we've been using the player's wallet address in the OwnedBy component to represent ownership. Now let's integrate with Persona instead.
All we need to do is to switch OwnedBy from a AddressComponent to a UintComponent and replace most occurences of msg.sender with getPersonaId() in the contracts and all occurences of signer.address with personaId on the client.
That's all! With this tiny change our game is now fully integrated with Persona.
|
@@ -10,13 +10,11 @@ import {
|
|
|
10
10
|
import { createMapping, loadEvents, setupContracts, setupMappings } from "../packages/lattice-eth-middleware";
|
|
11
11
|
import { setupPhaser } from "@latticexyz/phaser-middleware";
|
|
12
12
|
import {
|
|
13
|
-
createAddressComponent,
|
|
14
13
|
createBoolComponent,
|
|
15
14
|
createCoordComponent,
|
|
16
15
|
createStringComponent,
|
|
17
16
|
createTupleComponent,
|
|
18
17
|
createUintComponent,
|
|
19
|
-
decodeAddressComponent,
|
|
20
18
|
decodeBoolComponent,
|
|
21
19
|
decodeCoordComponent,
|
|
22
20
|
decodeStringComponent,
|
|
@@ -56,7 +54,7 @@ export async function createGame(contractAddress: string, privateKey: string, ch
|
|
|
56
54
|
const Position = createCoordComponent(world, "Position");
|
|
57
55
|
const Texture = createStringComponent(world, "Texture");
|
|
58
56
|
const Appearance = createUintComponent(world, "Appearance");
|
|
59
|
-
const OwnedBy =
|
|
57
|
+
const OwnedBy = createUintComponent(world, "OwnedBy");
|
|
60
58
|
const Movable = createBoolComponent(world, "Movable");
|
|
61
59
|
const Miner = createBoolComponent(world, "Miner");
|
|
62
60
|
const Mined = createBoolComponent(world, "Mined");
|
|
@@ -88,7 +86,7 @@ export async function createGame(contractAddress: string, privateKey: string, ch
|
|
|
88
86
|
...createMapping(componentAddresses.position, Position, decodeCoordComponent),
|
|
89
87
|
...createMapping(componentAddresses.texture, Texture, decodeStringComponent),
|
|
90
88
|
...createMapping(componentAddresses.appearance, Appearance, decodeUintComponent),
|
|
91
|
-
...createMapping(componentAddresses.ownedBy, OwnedBy,
|
|
89
|
+
...createMapping(componentAddresses.ownedBy, OwnedBy, decodeUintComponent),
|
|
92
90
|
...createMapping(componentAddresses.movable, Movable, decodeBoolComponent),
|
|
93
91
|
...createMapping(componentAddresses.miner, Miner, decodeBoolComponent),
|
|
94
92
|
...createMapping(componentAddresses.mined, Mined, decodeBoolComponent),
|
|
@@ -14,17 +14,14 @@ export const App: React.FC<{ context: Context }> = observer(({ context }) => {
|
|
|
14
14
|
world,
|
|
15
15
|
components: { OwnedBy, Heart },
|
|
16
16
|
} = context;
|
|
17
|
-
const lostEntityQuery = defineExitQuery(world, [HasValue(OwnedBy, { value: context.
|
|
18
|
-
const ownedByQuery = defineQuery([HasValue(OwnedBy, { value: context.
|
|
17
|
+
const lostEntityQuery = defineExitQuery(world, [HasValue(OwnedBy, { value: context.personaId })]);
|
|
18
|
+
const ownedByQuery = defineQuery([HasValue(OwnedBy, { value: context.personaId })]);
|
|
19
19
|
return reaction(
|
|
20
20
|
() => lostEntityQuery.get(),
|
|
21
21
|
(lostEntities) => {
|
|
22
22
|
if (lostEntities.size == 0) return;
|
|
23
23
|
// If the player doesn't own a heart anymore, the game is lost
|
|
24
|
-
if (
|
|
25
|
-
ownedByQuery.get().size > 0 &&
|
|
26
|
-
!exists([Has(Heart), HasValue(OwnedBy, { value: context.signer.address })])
|
|
27
|
-
) {
|
|
24
|
+
if (ownedByQuery.get().size > 0 && !exists([Has(Heart), HasValue(OwnedBy, { value: context.personaId })])) {
|
|
28
25
|
setModalText("Game over");
|
|
29
26
|
} else {
|
|
30
27
|
// Flash a quick modal if the player lost an entity
|
|
@@ -8,7 +8,7 @@ export function createInputSystem(context: Context) {
|
|
|
8
8
|
components: { OwnedBy, Selected, Position },
|
|
9
9
|
phaser: { input, map: tilemap },
|
|
10
10
|
api: { spawn, actionDirection },
|
|
11
|
-
|
|
11
|
+
personaId,
|
|
12
12
|
} = context;
|
|
13
13
|
|
|
14
14
|
input.onKeyPress(
|
|
@@ -38,7 +38,7 @@ export function createInputSystem(context: Context) {
|
|
|
38
38
|
filter((coord) => coord.x >= 0 && coord.y >= 0 && coord.x < tilemap.width && coord.y < tilemap.height) // Filter clicks outside the map
|
|
39
39
|
)
|
|
40
40
|
.subscribe((coord) => {
|
|
41
|
-
if (exists([HasValue(OwnedBy, { value:
|
|
41
|
+
if (exists([HasValue(OwnedBy, { value: personaId })]) == undefined) {
|
|
42
42
|
// If not spawned, spawn
|
|
43
43
|
console.log("Spawning at", coord);
|
|
44
44
|
spawn(coord);
|
|
@@ -50,7 +50,7 @@ export function createInputSystem(context: Context) {
|
|
|
50
50
|
if (selectedEntity != undefined) removeComponent(Selected, selectedEntity);
|
|
51
51
|
|
|
52
52
|
// Add the Selected component to the entity below the cursor
|
|
53
|
-
const entityAtPos = exists([HasValue(Position, coord), HasValue(OwnedBy, { value:
|
|
53
|
+
const entityAtPos = exists([HasValue(Position, coord), HasValue(OwnedBy, { value: personaId })]);
|
|
54
54
|
if (entityAtPos) {
|
|
55
55
|
console.log("Selected entity", entityAtPos, "at", coord);
|
|
56
56
|
setComponent(Selected, entityAtPos, {});
|
|
@@ -33,7 +33,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
|
|
|
33
33
|
from: deployer,
|
|
34
34
|
log: true,
|
|
35
35
|
autoMine: true,
|
|
36
|
-
args: [world.address
|
|
36
|
+
args: [world.address, personaMirrorAddress],
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
const gameContract = await hre.ethers.getContract('Game', deployer);
|
|
@@ -2,7 +2,7 @@ export const componentConfig = {
|
|
|
2
2
|
position: 'CoordComponent',
|
|
3
3
|
texture: 'StringComponent',
|
|
4
4
|
appearance: 'UintComponent',
|
|
5
|
-
ownedBy: '
|
|
5
|
+
ownedBy: 'UintComponent',
|
|
6
6
|
movable: 'BoolComponent',
|
|
7
7
|
miner: 'BoolComponent',
|
|
8
8
|
mined: 'BoolComponent',
|
|
@@ -8,16 +8,16 @@ import { QueryFragment, QueryType, LibQuery } from 'lattice-ecs/LibQuery.sol';
|
|
|
8
8
|
import { CoordComponent, Coord } from './components/CoordComponent.sol';
|
|
9
9
|
import { UintComponent } from './components/UintComponent.sol';
|
|
10
10
|
import { StringComponent } from './components/StringComponent.sol';
|
|
11
|
-
import { AddressComponent } from './components/AddressComponent.sol';
|
|
12
11
|
import { BoolComponent } from './components/BoolComponent.sol';
|
|
13
12
|
import { TupleComponent } from './components/TupleComponent.sol';
|
|
14
13
|
import { manhattan } from './utils.sol';
|
|
14
|
+
import { PersonaMirror } from 'persona/L2/PersonaMirror.sol';
|
|
15
15
|
|
|
16
16
|
struct Components {
|
|
17
17
|
CoordComponent position;
|
|
18
18
|
StringComponent texture;
|
|
19
19
|
UintComponent appearance;
|
|
20
|
-
|
|
20
|
+
UintComponent ownedBy;
|
|
21
21
|
BoolComponent movable;
|
|
22
22
|
BoolComponent miner;
|
|
23
23
|
BoolComponent mined;
|
|
@@ -33,6 +33,7 @@ enum Texture {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
contract Game {
|
|
36
|
+
PersonaMirror public personaMirror;
|
|
36
37
|
address public world;
|
|
37
38
|
address public owner;
|
|
38
39
|
uint256 public width = 32;
|
|
@@ -45,7 +46,7 @@ contract Game {
|
|
|
45
46
|
_;
|
|
46
47
|
}
|
|
47
48
|
modifier onlyEntityOwner(uint256 entity) {
|
|
48
|
-
require(c.ownedBy.getValue(entity) ==
|
|
49
|
+
require(c.ownedBy.getValue(entity) == getPersona(), 'invalid owner');
|
|
49
50
|
_;
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -59,9 +60,10 @@ contract Game {
|
|
|
59
60
|
_;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
constructor(address _world) {
|
|
63
|
+
constructor(address _world, address _personaMirror) {
|
|
63
64
|
owner = msg.sender;
|
|
64
65
|
world = _world;
|
|
66
|
+
personaMirror = PersonaMirror(_personaMirror);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
function registerComponents(Components memory _components, address[] memory _componentList) public onlyContractOwner {
|
|
@@ -82,6 +84,12 @@ contract Game {
|
|
|
82
84
|
c.texture.set(uint256(Texture.Ground), ground);
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
function getPersona() internal view returns (uint256) {
|
|
88
|
+
uint256 personaId = personaMirror.getActivePersona(msg.sender, address(this));
|
|
89
|
+
require(personaMirror.isAuthorized(personaId, msg.sender, address(this), msg.sig), 'persona not authorized');
|
|
90
|
+
return personaId;
|
|
91
|
+
}
|
|
92
|
+
|
|
85
93
|
/**
|
|
86
94
|
* Remove all components from the given entity
|
|
87
95
|
*/
|
|
@@ -125,11 +133,11 @@ contract Game {
|
|
|
125
133
|
|
|
126
134
|
function spawn(Coord memory center) public inBounds(center) {
|
|
127
135
|
// Check player is not spawned yet
|
|
128
|
-
require(c.ownedBy.getEntitiesWithValue(
|
|
136
|
+
require(c.ownedBy.getEntitiesWithValue(getPersona()).length == 0, 'already spawned');
|
|
129
137
|
|
|
130
138
|
// Create player entity (to indicate spawn)
|
|
131
139
|
uint256 playerEntity = World(world).getNumEntities();
|
|
132
|
-
c.ownedBy.set(playerEntity,
|
|
140
|
+
c.ownedBy.set(playerEntity, getPersona());
|
|
133
141
|
|
|
134
142
|
// Check spawn area is empty, then mine
|
|
135
143
|
for (uint256 dx; dx < 3; dx++) {
|
|
@@ -141,7 +149,7 @@ contract Game {
|
|
|
141
149
|
// Spawn entities
|
|
142
150
|
uint256 entity = World(world).getNumEntities();
|
|
143
151
|
c.position.set(entity, coord);
|
|
144
|
-
c.ownedBy.set(entity,
|
|
152
|
+
c.ownedBy.set(entity, getPersona());
|
|
145
153
|
|
|
146
154
|
// Put heart at the center
|
|
147
155
|
if (dx == 1 && dy == 1) {
|