From bcdd60b3d0b44eea34791f5458d9cdfc399817c1 Mon Sep 17 00:00:00 2001 From: Marco Hauptmann Date: Thu, 18 Jan 2024 16:01:48 +0100 Subject: [PATCH 1/2] stuff --- .../src/example-plugins/BaseBlue/BaseBlue.js | 141 +++++++++++++++++ .../src/example-plugins/BaseBlue/BaseBlue.sol | 145 ++++++++++++++++++ .../example-plugins/BaseBlue/BaseBlue.yaml | 34 ++++ .../src/example-plugins/BaseRed/BaseRed.js | 141 +++++++++++++++++ .../src/example-plugins/BaseRed/BaseRed.sol | 145 ++++++++++++++++++ .../src/example-plugins/BaseRed/BaseRed.yaml | 34 ++++ 6 files changed, 640 insertions(+) create mode 100644 contracts/src/example-plugins/BaseBlue/BaseBlue.js create mode 100644 contracts/src/example-plugins/BaseBlue/BaseBlue.sol create mode 100644 contracts/src/example-plugins/BaseBlue/BaseBlue.yaml create mode 100644 contracts/src/example-plugins/BaseRed/BaseRed.js create mode 100644 contracts/src/example-plugins/BaseRed/BaseRed.sol create mode 100644 contracts/src/example-plugins/BaseRed/BaseRed.yaml diff --git a/contracts/src/example-plugins/BaseBlue/BaseBlue.js b/contracts/src/example-plugins/BaseBlue/BaseBlue.js new file mode 100644 index 000000000..041427fc6 --- /dev/null +++ b/contracts/src/example-plugins/BaseBlue/BaseBlue.js @@ -0,0 +1,141 @@ +import ds from 'downstream'; + +export default async function update(state) { + // uncomment this to browse the state object in browser console + // this will be logged when selecting a unit and then selecting an instance of this building + //logState(state); + + const selectedTile = getSelectedTile(state); + const selectedBuilding = selectedTile && getBuildingOnTile(state, selectedTile); + const canCraft = selectedBuilding && inputsAreCorrect(state, selectedBuilding) + // uncomment this to be restrictve about which units can craft + // this is a client only check - to enforce it in contracts make + // similar changes in BasicFactory.sol + // && unitIsFriendly(state, selectedBuilding) + ; + + const craft = () => { + const mobileUnit = getMobileUnit(state); + + if (!mobileUnit) { + console.log('no selected unit'); + return; + } + + ds.dispatch({ + name: 'BUILDING_USE', + args: [selectedBuilding.id, mobileUnit.id, []], + }); + + console.log('Craft dispatched'); + }; + + return { + version: 1, + components: [ + { + id: 'basic-factory', + type: 'building', + content: [ + { + id: 'default', + type: 'inline', + html: '

Fill the input slots to enable crafing

', + buttons: [ + { + text: 'Craft', + type: 'action', + action: craft, + disabled: !canCraft, + }, + ], + }, + ], + }, + ], + }; +} + +function getMobileUnit(state) { + return state?.selected?.mobileUnit; +} + +function getSelectedTile(state) { + const tiles = state?.selected?.tiles || {}; + return tiles && tiles.length === 1 ? tiles[0] : undefined; +} + +function getBuildingOnTile(state, tile) { + return (state?.world?.buildings || []).find((b) => tile && b.location?.tile?.id === tile.id); +} + +// returns an array of items the building expects as input +function getRequiredInputItems(building) { + return building?.kind?.inputs || []; +} + +// search through all the bags in the world to find those belonging to this building +function getBuildingBags(state, building) { + return building ? (state?.world?.bags || []).filter((bag) => bag.equipee?.node.id === building.id) : []; +} + +// get building input slots +function getInputSlots(state, building) { + // inputs are the bag with key 0 owned by the building + const buildingBags = getBuildingBags(state, building); + const inputBag = buildingBags.find((bag) => bag.equipee.key === 0); + + // slots used for crafting have sequential keys startng with 0 + return inputBag && inputBag.slots.sort((a, b) => a.key - b.key); +} + +// are the required craft input items in the input slots? +function inputsAreCorrect(state, building) { + const requiredInputItems = getRequiredInputItems(building); + const inputSlots = getInputSlots(state, building); + + return ( + inputSlots && + inputSlots.length >= requiredInputItems.length && + requiredInputItems.every( + (requiredItem) => + inputSlots[requiredItem.key].item.id == requiredItem.item.id && + inputSlots[requiredItem.key].balance == requiredItem.balance + ) + ); +} + +function logState(state) { + console.log('State sent to pluging:', state); +} + +const friendlyPlayerAddresses = [ + // 0x402462EefC217bf2cf4E6814395E1b61EA4c43F7 +]; + +function unitIsFriendly(state, selectedBuilding) { + const mobileUnit = getMobileUnit(state); + return ( + unitIsBuildingOwner(mobileUnit, selectedBuilding) || + unitIsBuildingAuthor(mobileUnit, selectedBuilding) || + friendlyPlayerAddresses.some((addr) => unitOwnerConnectedToWallet(state, mobileUnit, addr)) + ); +} + +function unitIsBuildingOwner(mobileUnit, selectedBuilding) { + //console.log('unit owner id:', mobileUnit?.owner?.id, 'building owner id:', selectedBuilding?.owner?.id); + return mobileUnit?.owner?.id && mobileUnit?.owner?.id === selectedBuilding?.owner?.id; +} + +function unitIsBuildingAuthor(mobileUnit, selectedBuilding) { + //console.log('unit owner id:', mobileUnit?.owner?.id, 'building author id:', selectedBuilding?.kind?.owner?.id); + return mobileUnit?.owner?.id && mobileUnit?.owner?.id === selectedBuilding?.kind?.owner?.id; +} + +function unitOwnerConnectedToWallet(state, mobileUnit, walletAddress) { + //console.log('Checking player:', state?.player, 'controls unit', mobileUnit, walletAddress); + return mobileUnit?.owner?.id == state?.player?.id && state?.player?.addr == walletAddress; +} + +// the source for this code is on github where you can find other example buildings: +// https://github.com/playmint/ds/tree/main/contracts/src/example-plugins diff --git a/contracts/src/example-plugins/BaseBlue/BaseBlue.sol b/contracts/src/example-plugins/BaseBlue/BaseBlue.sol new file mode 100644 index 000000000..6c510b9ea --- /dev/null +++ b/contracts/src/example-plugins/BaseBlue/BaseBlue.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Game} from "cog/IGame.sol"; +import {State} from "cog/IState.sol"; +import {Schema} from "@ds/schema/Schema.sol"; +import {Actions} from "@ds/actions/Actions.sol"; +import {BuildingKind} from "@ds/ext/BuildingKind.sol"; + +using Schema for State; + +contract BaseBlue is BuildingKind { + function use( + Game ds, + bytes24 buildingInstance, + bytes24, + /*actor*/ bytes memory /*payload*/ + ) public { + ds.getDispatcher().dispatch( + abi.encodeCall(Actions.CRAFT, (buildingInstance)) + ); + } + + // version of use that restricts crafting to building owner, author or allow list + // these restrictions will not be reflected in the UI unless you make + // similar changes in BasicFactory.js + /*function use(Game ds, bytes24 buildingInstance, bytes24 actor, bytes memory ) public { + State state = GetState(ds); + CheckIsFriendlyUnit(state, actor, buildingInstance); + + ds.getDispatcher().dispatch(abi.encodeCall(Actions.CRAFT, (buildingInstance))); + }*/ + + // version of use that restricts crafting to units carrying a certain item + /*function use(Game ds, bytes24 buildingInstance, bytes24 actor, bytes memory ) public { + // require carrying an idCard + // you can change idCardItemId to another item id + CheckIsCarryingItem(state, actor, idCardItemId); + + ds.getDispatcher().dispatch(abi.encodeCall(Actions.CRAFT, (buildingInstance))); + }*/ + + function GetState(Game ds) internal returns (State) { + return ds.getState(); + } + + function GetBuildingOwner( + State state, + bytes24 buildingInstance + ) internal view returns (bytes24) { + return state.getOwner(buildingInstance); + } + + function GetBuildingAuthor( + State state, + bytes24 buildingInstance + ) internal view returns (bytes24) { + bytes24 buildingKind = state.getBuildingKind(buildingInstance); + return state.getOwner(buildingKind); + } + + function CheckIsFriendlyUnit( + State state, + bytes24 actor, + bytes24 buildingInstance + ) internal view { + require( + UnitOwnsBuilding(state, actor, buildingInstance) || + UnitAuthoredBuilding(state, actor, buildingInstance) || + UnitOwnedByFriendlyPlayer(state, actor), + "Unit does not have permission to use this building" + ); + } + + function UnitOwnsBuilding( + State state, + bytes24 actor, + bytes24 buildingInstance + ) internal view returns (bool) { + return + state.getOwner(actor) == GetBuildingOwner(state, buildingInstance); + } + + function UnitAuthoredBuilding( + State state, + bytes24 actor, + bytes24 buildingInstance + ) internal view returns (bool) { + return + state.getOwner(actor) == GetBuildingAuthor(state, buildingInstance); + } + + address[] private friendlyPlayerAddresses = [ + 0x402462EefC217bf2cf4E6814395E1b61EA4c43F7 + ]; + + function UnitOwnedByFriendlyPlayer( + State state, + bytes24 actor + ) internal view returns (bool) { + address ownerAddress = state.getOwnerAddress(actor); + for (uint256 i = 0; i < friendlyPlayerAddresses.length; i++) { + if (friendlyPlayerAddresses[i] == ownerAddress) { + return true; + } + } + return false; + } + + // use cli command 'ds get items' for all current possible ids. + bytes24 idCardItemId = 0x6a7a67f0b29554460000000100000064000000640000004c; + + function CheckIsCarryingItem( + State state, + bytes24 actor, + bytes24 item + ) internal view { + require( + (UnitIsCarryingItem(state, actor, item)), + "Unit must be carrying specified item" + ); + } + + function UnitIsCarryingItem( + State state, + bytes24 actor, + bytes24 item + ) internal view returns (bool) { + for (uint8 bagIndex = 0; bagIndex < 2; bagIndex++) { + bytes24 bag = state.getEquipSlot(actor, bagIndex); + if (bag != 0) { + for (uint8 slot = 0; slot < 4; slot++) { + (bytes24 resource /*uint64 balance*/, ) = state.getItemSlot( + bag, + slot + ); + if (resource == item) { + return true; + } + } + } + } + return false; + } +} diff --git a/contracts/src/example-plugins/BaseBlue/BaseBlue.yaml b/contracts/src/example-plugins/BaseBlue/BaseBlue.yaml new file mode 100644 index 000000000..5363f2dcb --- /dev/null +++ b/contracts/src/example-plugins/BaseBlue/BaseBlue.yaml @@ -0,0 +1,34 @@ +--- +kind: BuildingKind +spec: + category: factory + name: Red Base + description: This is the Base of Team Red + model: 14-14 + color: 0 + contract: + file: ./BaseRed.sol + plugin: + file: ./BaseRed.js + materials: + - name: Red Goo + quantity: 100 + - name: Green Goo + quantity: 100 + - name: Blue Goo + quantity: 100 + inputs: [] + outputs: + - name: Nothing + quantity: 1 + +--- +kind: Item +spec: + name: Nothing + icon: xx-01 + goo: + red: 0 + green: 0 + blue: 0 + stackable: false diff --git a/contracts/src/example-plugins/BaseRed/BaseRed.js b/contracts/src/example-plugins/BaseRed/BaseRed.js new file mode 100644 index 000000000..041427fc6 --- /dev/null +++ b/contracts/src/example-plugins/BaseRed/BaseRed.js @@ -0,0 +1,141 @@ +import ds from 'downstream'; + +export default async function update(state) { + // uncomment this to browse the state object in browser console + // this will be logged when selecting a unit and then selecting an instance of this building + //logState(state); + + const selectedTile = getSelectedTile(state); + const selectedBuilding = selectedTile && getBuildingOnTile(state, selectedTile); + const canCraft = selectedBuilding && inputsAreCorrect(state, selectedBuilding) + // uncomment this to be restrictve about which units can craft + // this is a client only check - to enforce it in contracts make + // similar changes in BasicFactory.sol + // && unitIsFriendly(state, selectedBuilding) + ; + + const craft = () => { + const mobileUnit = getMobileUnit(state); + + if (!mobileUnit) { + console.log('no selected unit'); + return; + } + + ds.dispatch({ + name: 'BUILDING_USE', + args: [selectedBuilding.id, mobileUnit.id, []], + }); + + console.log('Craft dispatched'); + }; + + return { + version: 1, + components: [ + { + id: 'basic-factory', + type: 'building', + content: [ + { + id: 'default', + type: 'inline', + html: '

Fill the input slots to enable crafing

', + buttons: [ + { + text: 'Craft', + type: 'action', + action: craft, + disabled: !canCraft, + }, + ], + }, + ], + }, + ], + }; +} + +function getMobileUnit(state) { + return state?.selected?.mobileUnit; +} + +function getSelectedTile(state) { + const tiles = state?.selected?.tiles || {}; + return tiles && tiles.length === 1 ? tiles[0] : undefined; +} + +function getBuildingOnTile(state, tile) { + return (state?.world?.buildings || []).find((b) => tile && b.location?.tile?.id === tile.id); +} + +// returns an array of items the building expects as input +function getRequiredInputItems(building) { + return building?.kind?.inputs || []; +} + +// search through all the bags in the world to find those belonging to this building +function getBuildingBags(state, building) { + return building ? (state?.world?.bags || []).filter((bag) => bag.equipee?.node.id === building.id) : []; +} + +// get building input slots +function getInputSlots(state, building) { + // inputs are the bag with key 0 owned by the building + const buildingBags = getBuildingBags(state, building); + const inputBag = buildingBags.find((bag) => bag.equipee.key === 0); + + // slots used for crafting have sequential keys startng with 0 + return inputBag && inputBag.slots.sort((a, b) => a.key - b.key); +} + +// are the required craft input items in the input slots? +function inputsAreCorrect(state, building) { + const requiredInputItems = getRequiredInputItems(building); + const inputSlots = getInputSlots(state, building); + + return ( + inputSlots && + inputSlots.length >= requiredInputItems.length && + requiredInputItems.every( + (requiredItem) => + inputSlots[requiredItem.key].item.id == requiredItem.item.id && + inputSlots[requiredItem.key].balance == requiredItem.balance + ) + ); +} + +function logState(state) { + console.log('State sent to pluging:', state); +} + +const friendlyPlayerAddresses = [ + // 0x402462EefC217bf2cf4E6814395E1b61EA4c43F7 +]; + +function unitIsFriendly(state, selectedBuilding) { + const mobileUnit = getMobileUnit(state); + return ( + unitIsBuildingOwner(mobileUnit, selectedBuilding) || + unitIsBuildingAuthor(mobileUnit, selectedBuilding) || + friendlyPlayerAddresses.some((addr) => unitOwnerConnectedToWallet(state, mobileUnit, addr)) + ); +} + +function unitIsBuildingOwner(mobileUnit, selectedBuilding) { + //console.log('unit owner id:', mobileUnit?.owner?.id, 'building owner id:', selectedBuilding?.owner?.id); + return mobileUnit?.owner?.id && mobileUnit?.owner?.id === selectedBuilding?.owner?.id; +} + +function unitIsBuildingAuthor(mobileUnit, selectedBuilding) { + //console.log('unit owner id:', mobileUnit?.owner?.id, 'building author id:', selectedBuilding?.kind?.owner?.id); + return mobileUnit?.owner?.id && mobileUnit?.owner?.id === selectedBuilding?.kind?.owner?.id; +} + +function unitOwnerConnectedToWallet(state, mobileUnit, walletAddress) { + //console.log('Checking player:', state?.player, 'controls unit', mobileUnit, walletAddress); + return mobileUnit?.owner?.id == state?.player?.id && state?.player?.addr == walletAddress; +} + +// the source for this code is on github where you can find other example buildings: +// https://github.com/playmint/ds/tree/main/contracts/src/example-plugins diff --git a/contracts/src/example-plugins/BaseRed/BaseRed.sol b/contracts/src/example-plugins/BaseRed/BaseRed.sol new file mode 100644 index 000000000..d5e06f1e2 --- /dev/null +++ b/contracts/src/example-plugins/BaseRed/BaseRed.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Game} from "cog/IGame.sol"; +import {State} from "cog/IState.sol"; +import {Schema} from "@ds/schema/Schema.sol"; +import {Actions} from "@ds/actions/Actions.sol"; +import {BuildingKind} from "@ds/ext/BuildingKind.sol"; + +using Schema for State; + +contract BaseRed is BuildingKind { + function use( + Game ds, + bytes24 buildingInstance, + bytes24, + /*actor*/ bytes memory /*payload*/ + ) public { + ds.getDispatcher().dispatch( + abi.encodeCall(Actions.CRAFT, (buildingInstance)) + ); + } + + // version of use that restricts crafting to building owner, author or allow list + // these restrictions will not be reflected in the UI unless you make + // similar changes in BasicFactory.js + /*function use(Game ds, bytes24 buildingInstance, bytes24 actor, bytes memory ) public { + State state = GetState(ds); + CheckIsFriendlyUnit(state, actor, buildingInstance); + + ds.getDispatcher().dispatch(abi.encodeCall(Actions.CRAFT, (buildingInstance))); + }*/ + + // version of use that restricts crafting to units carrying a certain item + /*function use(Game ds, bytes24 buildingInstance, bytes24 actor, bytes memory ) public { + // require carrying an idCard + // you can change idCardItemId to another item id + CheckIsCarryingItem(state, actor, idCardItemId); + + ds.getDispatcher().dispatch(abi.encodeCall(Actions.CRAFT, (buildingInstance))); + }*/ + + function GetState(Game ds) internal returns (State) { + return ds.getState(); + } + + function GetBuildingOwner( + State state, + bytes24 buildingInstance + ) internal view returns (bytes24) { + return state.getOwner(buildingInstance); + } + + function GetBuildingAuthor( + State state, + bytes24 buildingInstance + ) internal view returns (bytes24) { + bytes24 buildingKind = state.getBuildingKind(buildingInstance); + return state.getOwner(buildingKind); + } + + function CheckIsFriendlyUnit( + State state, + bytes24 actor, + bytes24 buildingInstance + ) internal view { + require( + UnitOwnsBuilding(state, actor, buildingInstance) || + UnitAuthoredBuilding(state, actor, buildingInstance) || + UnitOwnedByFriendlyPlayer(state, actor), + "Unit does not have permission to use this building" + ); + } + + function UnitOwnsBuilding( + State state, + bytes24 actor, + bytes24 buildingInstance + ) internal view returns (bool) { + return + state.getOwner(actor) == GetBuildingOwner(state, buildingInstance); + } + + function UnitAuthoredBuilding( + State state, + bytes24 actor, + bytes24 buildingInstance + ) internal view returns (bool) { + return + state.getOwner(actor) == GetBuildingAuthor(state, buildingInstance); + } + + address[] private friendlyPlayerAddresses = [ + 0x402462EefC217bf2cf4E6814395E1b61EA4c43F7 + ]; + + function UnitOwnedByFriendlyPlayer( + State state, + bytes24 actor + ) internal view returns (bool) { + address ownerAddress = state.getOwnerAddress(actor); + for (uint256 i = 0; i < friendlyPlayerAddresses.length; i++) { + if (friendlyPlayerAddresses[i] == ownerAddress) { + return true; + } + } + return false; + } + + // use cli command 'ds get items' for all current possible ids. + bytes24 idCardItemId = 0x6a7a67f0b29554460000000100000064000000640000004c; + + function CheckIsCarryingItem( + State state, + bytes24 actor, + bytes24 item + ) internal view { + require( + (UnitIsCarryingItem(state, actor, item)), + "Unit must be carrying specified item" + ); + } + + function UnitIsCarryingItem( + State state, + bytes24 actor, + bytes24 item + ) internal view returns (bool) { + for (uint8 bagIndex = 0; bagIndex < 2; bagIndex++) { + bytes24 bag = state.getEquipSlot(actor, bagIndex); + if (bag != 0) { + for (uint8 slot = 0; slot < 4; slot++) { + (bytes24 resource /*uint64 balance*/, ) = state.getItemSlot( + bag, + slot + ); + if (resource == item) { + return true; + } + } + } + } + return false; + } +} diff --git a/contracts/src/example-plugins/BaseRed/BaseRed.yaml b/contracts/src/example-plugins/BaseRed/BaseRed.yaml new file mode 100644 index 000000000..7e7a070f5 --- /dev/null +++ b/contracts/src/example-plugins/BaseRed/BaseRed.yaml @@ -0,0 +1,34 @@ +--- +kind: BuildingKind +spec: + category: factory + name: Red Base + description: This is the Base of Team Red + model: 14-14 + color: 4 + contract: + file: ./BaseRed.sol + plugin: + file: ./BaseRed.js + materials: + - name: Red Goo + quantity: 100 + - name: Green Goo + quantity: 100 + - name: Blue Goo + quantity: 100 + inputs: [] + outputs: + - name: Nothing + quantity: 1 + +--- +kind: Item +spec: + name: Nothing + icon: xx-01 + goo: + red: 0 + green: 0 + blue: 0 + stackable: false From ac319030c54287ea7b743802a6e02684376c3182 Mon Sep 17 00:00:00 2001 From: Marco Hauptmann Date: Thu, 18 Jan 2024 16:19:59 +0100 Subject: [PATCH 2/2] bases --- contracts/src/example-plugins/BaseBlue/BaseBlue.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/src/example-plugins/BaseBlue/BaseBlue.yaml b/contracts/src/example-plugins/BaseBlue/BaseBlue.yaml index 5363f2dcb..e03110688 100644 --- a/contracts/src/example-plugins/BaseBlue/BaseBlue.yaml +++ b/contracts/src/example-plugins/BaseBlue/BaseBlue.yaml @@ -2,14 +2,14 @@ kind: BuildingKind spec: category: factory - name: Red Base - description: This is the Base of Team Red + name: Blue Base + description: This is the Base of Team Blue model: 14-14 color: 0 contract: - file: ./BaseRed.sol + file: ./BaseBlue.sol plugin: - file: ./BaseRed.js + file: ./BaseBlue.js materials: - name: Red Goo quantity: 100