From 6c88705f3e83feca5045c8e76543424ae7b4aa08 Mon Sep 17 00:00:00 2001 From: Henry Caron Date: Mon, 3 Oct 2022 16:37:45 +0100 Subject: [PATCH 01/55] feat: button in top bar --- client/src/Frontend/Views/TopBar.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client/src/Frontend/Views/TopBar.tsx b/client/src/Frontend/Views/TopBar.tsx index f4340c61..48fd3e8d 100644 --- a/client/src/Frontend/Views/TopBar.tsx +++ b/client/src/Frontend/Views/TopBar.tsx @@ -5,6 +5,7 @@ import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { CaptureZonesGeneratedEvent } from '../../Backend/GameLogic/CaptureZoneGenerator'; import { Hook } from '../../_types/global/GlobalTypes'; +import { Btn } from '../Components/Btn'; import { AlignCenterHorizontally } from '../Components/CoreUI'; import { AccountLabel } from '../Components/Labels/Labels'; import { Gold, Red, Sub, Text, White } from '../Components/Text'; @@ -41,11 +42,14 @@ function BoardPlacement({ account }: { account: EthAddress | undefined }) { } content = ( - - - score: {formattedScore} - - +
+ + + stockpile: {formattedScore} + + + Extract all +
); } From e919b23dfa75c27f1d1f41c777e34f371328e745 Mon Sep 17 00:00:00 2001 From: Henry Caron Date: Mon, 3 Oct 2022 19:10:42 +0100 Subject: [PATCH 02/55] feat: extract silver button --- .../Frontend/Components/OpenPaneButtons.tsx | 2 +- .../ManagePlanetArtifacts/ManageArtifacts.tsx | 11 +- .../src/Frontend/Panes/PlanetContextPane.tsx | 12 +- .../src/Frontend/Utils/ShortcutConstants.ts | 1 + client/src/Frontend/Views/SendResources.tsx | 147 ++++++++++++------ 5 files changed, 106 insertions(+), 67 deletions(-) diff --git a/client/src/Frontend/Components/OpenPaneButtons.tsx b/client/src/Frontend/Components/OpenPaneButtons.tsx index 2c7bd088..7e8d69f4 100644 --- a/client/src/Frontend/Components/OpenPaneButtons.tsx +++ b/client/src/Frontend/Components/OpenPaneButtons.tsx @@ -133,7 +133,7 @@ export function OpenPlanetInfoButton({ return ( } helpContent={PlanetInfoHelpContent()} diff --git a/client/src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx b/client/src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx index d2db652d..37362798 100644 --- a/client/src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx +++ b/client/src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx @@ -1,4 +1,4 @@ -import { Artifact, LocatablePlanet, PlanetType } from '@dfdao/types'; +import { Artifact, LocatablePlanet } from '@dfdao/types'; import React, { useEffect, useState } from 'react'; import styled, { css } from 'styled-components'; import { Spacer } from '../../Components/CoreUI'; @@ -18,10 +18,7 @@ export function ManageArtifactsPane({ playerAddress: string; modal: ModalHandle; }) { - const isMyTradingPost = - planet.owner === playerAddress && - planet.planetType === PlanetType.TRADING_POST && - !planet.destroyed; + const isMyPlanet = planet.owner === playerAddress && !planet.destroyed; const [viewingDepositList, setViewingDepositList] = useState(false); let action; @@ -62,7 +59,7 @@ export function ManageArtifactsPane({ - {isMyTradingPost && ( + {isMyPlanet && ( - Deposit Artifact + Activate Artifact )} diff --git a/client/src/Frontend/Panes/PlanetContextPane.tsx b/client/src/Frontend/Panes/PlanetContextPane.tsx index b0da183a..906bf429 100644 --- a/client/src/Frontend/Panes/PlanetContextPane.tsx +++ b/client/src/Frontend/Panes/PlanetContextPane.tsx @@ -6,8 +6,6 @@ import { CapturePlanetButton } from '../Components/CapturePlanetButton'; import { VerticalSplit } from '../Components/CoreUI'; import { MineArtifactButton } from '../Components/MineArtifactButton'; import { - OpenBroadcastPaneButton, - OpenHatPaneButton, OpenManagePlanetArtifactsButton, OpenPlanetInfoButton, OpenUpgradeDetailsPaneButton, @@ -53,15 +51,10 @@ function PlanetContextPaneContent({ } let upgradeRow = null; - if (!p?.destroyed && owned) { + if (!p?.destroyed && owned && p?.planetType == PlanetType.PLANET) { upgradeRow = ; } - let hatRow = null; - if (!p?.destroyed && owned) { - hatRow = ; - } - let withdrawRow = null; if (!p?.destroyed && owned && p?.planetType === PlanetType.TRADING_POST) { withdrawRow = ; @@ -79,6 +72,7 @@ function PlanetContextPaneContent({ planetWrapper={planet} onToggleSendForces={onToggleSendForces} onToggleAbandon={onToggleAbandon} + onToggleExtract={() => {}} /> {captureRow} @@ -86,12 +80,10 @@ function PlanetContextPaneContent({ <> {upgradeRow} - <> - {hatRow} {withdrawRow} diff --git a/client/src/Frontend/Utils/ShortcutConstants.ts b/client/src/Frontend/Utils/ShortcutConstants.ts index 1ae5fa8e..b2ed76f8 100644 --- a/client/src/Frontend/Utils/ShortcutConstants.ts +++ b/client/src/Frontend/Utils/ShortcutConstants.ts @@ -14,6 +14,7 @@ export const TOGGLE_TRANSACTIONS_PANE = "'"; export const TOGGLE_PLANET_ARTIFACTS_PANE = 's'; export const TOGGLE_HAT_PANE = 'x'; export const TOGGLE_ABANDON = 'r'; +export const TOGGLE_WITHDRAW = ']'; export const INVADE = 'y'; export const MINE_ARTIFACT = 'f'; export const TOGGLE_BROADCAST_PANE = 'z'; diff --git a/client/src/Frontend/Views/SendResources.tsx b/client/src/Frontend/Views/SendResources.tsx index df09313a..b2b65511 100644 --- a/client/src/Frontend/Views/SendResources.tsx +++ b/client/src/Frontend/Views/SendResources.tsx @@ -17,22 +17,23 @@ import dfstyles from '../Styles/dfstyles'; import { useAccount, usePlanetInactiveArtifacts, useUIManager } from '../Utils/AppHooks'; import { useEmitterValue } from '../Utils/EmitterHooks'; import { useOnUp } from '../Utils/KeyEmitters'; -import { TOGGLE_ABANDON, TOGGLE_SEND } from '../Utils/ShortcutConstants'; +import { TOGGLE_ABANDON, TOGGLE_SEND, TOGGLE_WITHDRAW } from '../Utils/ShortcutConstants'; import { SelectArtifactRow } from './ArtifactRow'; const StyledSendResources = styled.div` display: flex; flex-direction: column; - gap: 8px; + gap: 4px; `; const StyledShowPercent = styled.div` display: inline-block; + min-width: 35px; & > span:first-child { width: 3em; text-align: right; - margin-right: 1em; + margin-right: 0.5em; } & > span:last-child { @@ -52,8 +53,8 @@ const StyledShowPercent = styled.div` function ShowPercent({ value, setValue }: { value: number; setValue: (x: number) => void }) { return ( - {value}% - + {/* {value}% */} + setValue(value - 1)}> @@ -66,6 +67,7 @@ function ShowPercent({ value, setValue }: { value: number; setValue: (x: number) const ResourceRowDetails = styled.div` display: inline-flex; align-items: center; + justify-content: space-between; gap: 4px; `; @@ -93,26 +95,38 @@ function ResourceBar({ return ( <> - - - - {getResource(value)} - {isSilver ? 'silver' : 'energy'} - - - - ) => { - setValue(parseInt(e.target.value, 10)); - }} - /> + {isSilver ? ( + + Extract {getResource(value)} silver + + ) : ( +
+ +
+ + {getResource(value)} + + {' '} + ({value}%) {isSilver ? 'silver' : 'energy'} + +
+ +
+ + ) => { + setValue(parseInt(e.target.value, 10)); + }} + /> +
+ )} ); } @@ -151,6 +165,39 @@ function AbandonButton({ ); } +function ExtractButton({ + planet, + extracting, + toggleExtracting, + disabled, +}: { + planet?: Planet; + extracting: boolean; + toggleExtracting: () => void; + disabled?: boolean; +}) { + const uiManager = useUIManager(); + + if (!planet) return null; + + let silver = planet.silver; + + return ( + + + {extracting ? 'Extracting' : `Extract all (${silver}) silver`} + + + ); +} + function SendRow({ toggleSending, artifact, @@ -197,10 +244,12 @@ export function SendResources({ planetWrapper: p, onToggleSendForces, onToggleAbandon, + onToggleExtract, }: { planetWrapper: Wrapper; onToggleSendForces: () => void; onToggleAbandon: () => void; + onToggleExtract: () => void; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); @@ -210,9 +259,11 @@ export function SendResources({ const isSendingShip = uiManager.isSendingShip(locationId); const isAbandoning = useEmitterValue(uiManager.isAbandoning$, false); + const isExtracting = false; + const isSendingForces = useEmitterValue(uiManager.isSending$, false); const energySending = uiManager.getForcesSending(locationId); - const silverSending = uiManager.getSilverSending(locationId); + const silverSending = 100; const artifactSending = uiManager.getArtifactSending(locationId); const disableSliders = isSendingShip || isAbandoning; @@ -272,20 +323,6 @@ export function SendResources({ }, [uiManager, locationId, updateEnergySending] ); - useOnUp( - '_', - () => { - updateSilverSending(uiManager.getSilverSending(locationId) - 10); - }, - [uiManager, locationId, updateSilverSending] - ); - useOnUp( - '+', - () => { - updateSilverSending(uiManager.getSilverSending(locationId) + 10); - }, - [uiManager, locationId, updateSilverSending] - ); const artifacts = usePlanetInactiveArtifacts(p, uiManager); const spaceshipsYouOwn = artifacts.filter( @@ -329,6 +366,24 @@ export function SendResources({ ); } + let extractRow; + if (p.value && p.value.transactions?.hasTransaction(isUnconfirmedReleaseTx)) { + extractRow = ( + + + + ); + } else if (p.value && !p.value.destroyed) { + extractRow = ( + + ); + } + return ( {owned && !p.value?.destroyed && ( @@ -339,17 +394,9 @@ export function SendResources({ setValue={updateEnergySending} disabled={disableSliders} /> - {p.value && p.value.silver > 0 && ( - - )} )} + {p.value && artifacts.length > 0 && ( 0 || owned ? sendRow : null} + {p.value && p.value.silver > 0 && extractRow} + {uiManager.getSpaceJunkEnabled() && owned ? abandonRow : null} ); From 72907c76c39ba7865d43093a4ee6626048c3ff91 Mon Sep 17 00:00:00 2001 From: Henry Caron Date: Tue, 4 Oct 2022 10:53:27 +0100 Subject: [PATCH 03/55] feat: minor dummy tweaks --- client/src/Backend/GameLogic/GameManager.ts | 3 --- client/src/Frontend/Views/SendResources.tsx | 7 ++++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/client/src/Backend/GameLogic/GameManager.ts b/client/src/Backend/GameLogic/GameManager.ts index 28831c0d..7534ecdb 100644 --- a/client/src/Backend/GameLogic/GameManager.ts +++ b/client/src/Backend/GameLogic/GameManager.ts @@ -2503,9 +2503,6 @@ class GameManager extends EventEmitter { if (!planet) { throw new Error('tried to withdraw silver from an unknown planet'); } - if (planet.planetType !== PlanetType.TRADING_POST) { - throw new Error('can only withdraw silver from spacetime rips'); - } if (planet.owner !== this.account) { throw new Error('can only withdraw silver from a planet you own'); } diff --git a/client/src/Frontend/Views/SendResources.tsx b/client/src/Frontend/Views/SendResources.tsx index b2b65511..be088bf6 100644 --- a/client/src/Frontend/Views/SendResources.tsx +++ b/client/src/Frontend/Views/SendResources.tsx @@ -191,9 +191,9 @@ function ExtractButton({ shortcutText={TOGGLE_WITHDRAW} disabled={planet.isHomePlanet || disabled} > - - {extracting ? 'Extracting' : `Extract all (${silver}) silver`} - + {/* */} + {extracting ? 'Extracting' : `Extract all (${Math.floor(silver)}) silver`} + {/* */} ); } @@ -399,6 +399,7 @@ export function SendResources({ {p.value && artifacts.length > 0 && ( Date: Tue, 20 Sep 2022 17:36:18 +0100 Subject: [PATCH 04/55] test file --- eth/test/DFToken.test.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 eth/test/DFToken.test.ts diff --git a/eth/test/DFToken.test.ts b/eth/test/DFToken.test.ts new file mode 100644 index 00000000..d8a41f9f --- /dev/null +++ b/eth/test/DFToken.test.ts @@ -0,0 +1 @@ +// TODO: Implement ERC-1155 Tests for 721(Artifact, Spaceship) 20(Silver) From c081680a8bd35cc72fe73316bd42406dd376a91d Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 23 Sep 2022 21:57:10 +0100 Subject: [PATCH 05/55] feat: load SolidStateERC1155 --- eth/contracts/facets/DFArtifactFacet.sol | 3 +- eth/package.json | 1 + eth/test/DFERC1155.test.ts | 19 +++++++ package-lock.json | 68 ++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 eth/test/DFERC1155.test.ts diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 35f93bc7..78d89ea8 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; // Contract imports import {SolidStateERC721} from "@solidstate/contracts/token/ERC721/SolidStateERC721.sol"; import {ERC721BaseStorage} from "@solidstate/contracts/token/ERC721/base/ERC721BaseStorage.sol"; +import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; @@ -19,7 +20,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs} from "../DFTypes.sol"; -contract DFArtifactFacet is WithStorage, SolidStateERC721 { +contract DFArtifactFacet is WithStorage, SolidStateERC721, SolidStateERC1155 { using ERC721BaseStorage for ERC721BaseStorage.Layout; event PlanetProspected(address player, uint256 loc); diff --git a/eth/package.json b/eth/package.json index de7bb054..e7b15a1a 100644 --- a/eth/package.json +++ b/eth/package.json @@ -20,6 +20,7 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", + "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts new file mode 100644 index 00000000..37f64756 --- /dev/null +++ b/eth/test/DFERC1155.test.ts @@ -0,0 +1,19 @@ +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { defaultWorldFixture, World } from './utils/TestWorld'; + +describe('ERC1155', function () { + let world: World; + + async function worldFixture() { + const world = await loadFixture(defaultWorldFixture); + return world; + } + + beforeEach('load fixture', async function () { + world = await loadFixture(worldFixture); + }); + it('has correct functions', async function () { + // world.contract.mint(); + console.log(world.contract.address); + }); +}); diff --git a/package-lock.json b/package-lock.json index ce5bd949..d9a23a68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -227,6 +227,7 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", + "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", @@ -7115,6 +7116,24 @@ } } }, + "node_modules/@solidstate/library": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@solidstate/library/-/library-0.0.41.tgz", + "integrity": "sha512-JWn6wkN4QI/054XEjnzZe/ooNg0q2yq9kHe3EZEQyONSWzLYAFINr/c23dhUu4CnuR1gh2HeOpyOIufpIYSRvw==", + "dev": true, + "dependencies": { + "eth-permit": "^0.1.10" + } + }, + "node_modules/@solidstate/spec": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@solidstate/spec/-/spec-0.0.41.tgz", + "integrity": "sha512-yBKDVFr/tymCncdkJ0lpW9mvH3JXRD9VK1jjWg8nVio7CVXL3B5UWL5s/3Z1WQUGuyTJ4QpxT2jgbEohIgz0ZQ==", + "dev": true, + "dependencies": { + "@solidstate/library": "^0.0.41" + } + }, "node_modules/@spectrum-web-components/action-button": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@spectrum-web-components/action-button/-/action-button-0.7.3.tgz", @@ -15441,6 +15460,15 @@ "resolved": "eth", "link": true }, + "node_modules/eth-permit": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/eth-permit/-/eth-permit-0.1.10.tgz", + "integrity": "sha512-cMAjWvGEaCQoWSlKWBqkBMQvhCtXzVlkTCnYBxGRsCxznbBZSiYuOsHpTAb4EerRwQJkRgwd3vPvcZh3OA7Siw==", + "dev": true, + "dependencies": { + "utf8": "^3.0.0" + } + }, "node_modules/ethereum-cryptography": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", @@ -31126,6 +31154,12 @@ "node": ">=6.14.2" } }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -37540,6 +37574,24 @@ } } }, + "@solidstate/library": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@solidstate/library/-/library-0.0.41.tgz", + "integrity": "sha512-JWn6wkN4QI/054XEjnzZe/ooNg0q2yq9kHe3EZEQyONSWzLYAFINr/c23dhUu4CnuR1gh2HeOpyOIufpIYSRvw==", + "dev": true, + "requires": { + "eth-permit": "^0.1.10" + } + }, + "@solidstate/spec": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@solidstate/spec/-/spec-0.0.41.tgz", + "integrity": "sha512-yBKDVFr/tymCncdkJ0lpW9mvH3JXRD9VK1jjWg8nVio7CVXL3B5UWL5s/3Z1WQUGuyTJ4QpxT2jgbEohIgz0ZQ==", + "dev": true, + "requires": { + "@solidstate/library": "^0.0.41" + } + }, "@spectrum-web-components/action-button": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@spectrum-web-components/action-button/-/action-button-0.7.3.tgz", @@ -43768,6 +43820,7 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", + "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", @@ -43898,6 +43951,15 @@ } } }, + "eth-permit": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/eth-permit/-/eth-permit-0.1.10.tgz", + "integrity": "sha512-cMAjWvGEaCQoWSlKWBqkBMQvhCtXzVlkTCnYBxGRsCxznbBZSiYuOsHpTAb4EerRwQJkRgwd3vPvcZh3OA7Siw==", + "dev": true, + "requires": { + "utf8": "^3.0.0" + } + }, "ethereum-cryptography": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", @@ -55419,6 +55481,12 @@ "node-gyp-build": "^4.3.0" } }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", From c0dabba7ece2d38c7bc370b762e93bc9d886ca8b Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 23 Sep 2022 22:36:19 +0100 Subject: [PATCH 06/55] temp: noodling around --- eth/contracts/DFToken.sol | 6 ++++++ eth/contracts/facets/DFArtifactFacet.sol | 3 +-- eth/test/DFERC1155.test.ts | 24 +++++++++++------------- 3 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 eth/contracts/DFToken.sol diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol new file mode 100644 index 00000000..b711cdae --- /dev/null +++ b/eth/contracts/DFToken.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; + +contract DFToken is SolidStateERC1155 {} diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 78d89ea8..35f93bc7 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; // Contract imports import {SolidStateERC721} from "@solidstate/contracts/token/ERC721/SolidStateERC721.sol"; import {ERC721BaseStorage} from "@solidstate/contracts/token/ERC721/base/ERC721BaseStorage.sol"; -import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; @@ -20,7 +19,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs} from "../DFTypes.sol"; -contract DFArtifactFacet is WithStorage, SolidStateERC721, SolidStateERC1155 { +contract DFArtifactFacet is WithStorage, SolidStateERC721 { using ERC721BaseStorage for ERC721BaseStorage.Layout; event PlanetProspected(address player, uint256 loc); diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 37f64756..0b4f7924 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -1,19 +1,17 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { defaultWorldFixture, World } from './utils/TestWorld'; +import { DFToken, DFToken__factory } from '@dfdao/contracts/typechain'; +import { ethers } from 'hardhat'; -describe('ERC1155', function () { - let world: World; +const tokenURI = 'ERC1155Metadata.tokenURI'; - async function worldFixture() { - const world = await loadFixture(defaultWorldFixture); - return world; - } +describe('SolidStateERC1155', function () { + let token: DFToken; - beforeEach('load fixture', async function () { - world = await loadFixture(worldFixture); + beforeEach(async function () { + const [deployer] = await ethers.getSigners(); + token = await new DFToken__factory(deployer).deploy(); }); - it('has correct functions', async function () { - // world.contract.mint(); - console.log(world.contract.address); + + it('mints', async function () { + console.log('not yet!'); }); }); From 45d985e705fc171ada3b2592ef0c1a21675adaec Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sat, 24 Sep 2022 14:43:46 +0100 Subject: [PATCH 07/55] test: working mint and uri tests --- eth/contracts/DFToken.sol | 23 ++++++++++++++++++++++- eth/test/DFERC1155.test.ts | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index b711cdae..28b1e849 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -3,4 +3,25 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; -contract DFToken is SolidStateERC1155 {} +// Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper +// This makes it more obvious when we are using the DFToken functions + +contract DFToken is SolidStateERC1155 { + function mint( + address account, + uint256 id, + uint256 amount, + bytes memory data + ) public { + _mint(account, id, amount, data); + } + + /** + * @notice set per-token metadata URI + * @param tokenId token whose metadata URI to set + * @param tokenURI per-token URI + */ + function setTokenURI(uint256 tokenId, string memory tokenURI) public { + _setTokenURI(tokenId, tokenURI); + } +} diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 0b4f7924..7187dbeb 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -1,17 +1,47 @@ import { DFToken, DFToken__factory } from '@dfdao/contracts/typechain'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { expect } from 'chai'; import { ethers } from 'hardhat'; -const tokenURI = 'ERC1155Metadata.tokenURI'; +/** + * Once again, a friendly reminder that in ERC1155 tokenId refers to a collection of 1 or more + * tokens, NOT guaranteed to be unique instances of tokens. + */ describe('SolidStateERC1155', function () { let token: DFToken; + let deployer: SignerWithAddress; + let user1: SignerWithAddress; + const addressZero = ethers.constants.AddressZero; + const collectionId = ethers.constants.Zero; + const amount = ethers.constants.Two; + const tokenURI = 'ERC1155Metadata.tokenURI/{id}.json'; beforeEach(async function () { - const [deployer] = await ethers.getSigners(); + const signers = await ethers.getSigners(); + deployer = signers[0]; token = await new DFToken__factory(deployer).deploy(); + user1 = signers[1]; }); - it('mints', async function () { - console.log('not yet!'); + it('mints tokens for tokenId zero', async function () { + await expect(token.mint(user1.address, collectionId, amount, ethers.constants.HashZero)) + .to.emit(token, 'TransferSingle') + .withArgs(deployer.address, addressZero, user1.address, collectionId, amount); + + await expect( + token.connect(user1).mint(user1.address, collectionId, amount, ethers.constants.HashZero) + ) + .to.emit(token, 'TransferSingle') + .withArgs(user1.address, addressZero, user1.address, collectionId, amount); + // Each mint created 2 tokens in the same collection. Two mints = 4 tokens. + expect(await token.balanceOf(user1.address, collectionId)).to.equal(amount.mul(2)); + }); + it('sets tokenURI for tokenId zero', async function () { + await expect(token.setTokenURI(collectionId, tokenURI)) + .to.emit(token, 'URI') + .withArgs(tokenURI, collectionId); + + expect(await token.uri(collectionId)).to.equal(tokenURI); }); }); From 7ab7ddd5ab1b712e09adfd49229c4e7baa8eebaa Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sat, 24 Sep 2022 16:57:06 +0100 Subject: [PATCH 08/55] chore: comment --- eth/test/DFERC1155.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 7187dbeb..de609018 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -34,6 +34,7 @@ describe('SolidStateERC1155', function () { ) .to.emit(token, 'TransferSingle') .withArgs(user1.address, addressZero, user1.address, collectionId, amount); + // Each mint created 2 tokens in the same collection. Two mints = 4 tokens. expect(await token.balanceOf(user1.address, collectionId)).to.equal(amount.mul(2)); }); From 6e30e94a0074d97b30819d7330c9d81da118a149 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sun, 25 Sep 2022 15:29:51 +0100 Subject: [PATCH 09/55] feat: sample bit pack --- eth/contracts/DFToken.sol | 57 ++++++++++++++++++++++++++++++++++++++ eth/test/DFERC1155.test.ts | 9 ++++++ 2 files changed, 66 insertions(+) diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index 28b1e849..aa204d75 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -2,11 +2,25 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; +import "hardhat/console.sol"; // Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper // This makes it more obvious when we are using the DFToken functions contract DFToken is SolidStateERC1155 { + enum ArtifactInfo { + Unknown, + Level, + ArtifactType, + Biome + } + + struct Artifact { + uint8 level; + uint8 artifactType; + uint8 biome; + } + function mint( address account, uint256 id, @@ -24,4 +38,47 @@ contract DFToken is SolidStateERC1155 { function setTokenURI(uint256 tokenId, string memory tokenURI) public { _setTokenURI(tokenId, tokenURI); } + + /** + * @notice calculate amount of bits to shift left + * @param index number of 1 byte words to shift from left + * @return shift length of left shift + */ + function calcBitShift(uint8 index) internal pure returns (uint8) { + uint8 maxVal = 32; + + require(index <= maxVal, "shift index is too high"); + require(index > 0, "shift index is too low"); + + uint256 bin = 8; + uint256 shift = 256; + return uint8(shift - (bin * index)); + } + + function encodeArtifact( + uint256 _level, + uint256 _artifactType, + uint256 _biome + ) public pure returns (uint256) { + uint256 level = _level << calcBitShift(uint8(ArtifactInfo.Level)); + uint256 artifactType = _artifactType << calcBitShift(uint8(ArtifactInfo.ArtifactType)); + uint256 biome = _biome << calcBitShift(uint8(ArtifactInfo.Biome)); + return level + artifactType + biome; + } + + function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + bytes memory _b = abi.encodePacked(artifactId); + // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. + + // Note: Bit shifting requires an index greater than zero. This is why the ArtifactInfo has + // Unknown as the zero property, so calcBitShift(ArtifactInfo.Level) is correct. + // As a consequence, we need to + // offset fetching the relevant byte from the artifactId by 1. + // However + uint8 level = uint8(_b[uint8(ArtifactInfo.Level) - 1]); + uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); + return Artifact(level, artifactType, biome); + // return level + artifactType + biome; + } } diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index de609018..38342ce9 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -45,4 +45,13 @@ describe('SolidStateERC1155', function () { expect(await token.uri(collectionId)).to.equal(tokenURI); }); + it.only('logs bits', async function () { + const _level = '0xff'; + const _artifactType = '0x01'; + const _biome = '0xab'; + const res = await token.encodeArtifact(_level, _artifactType, _biome); + console.log(res._hex); + const { level, biome, artifactType } = await token.decodeArtifact(res); + console.log(level, biome, artifactType); + }); }); From 5984695ec943c9f8dc38cdb7bdae20c755973664 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Tue, 27 Sep 2022 10:28:47 +0100 Subject: [PATCH 10/55] feat: not working artifact refactor --- eth/contracts/DFToken.sol | 46 ++-- eth/contracts/DFTypes.sol | 21 ++ eth/contracts/facets/DFArtifactFacet.sol | 61 ++--- eth/contracts/facets/DFGetterFacet.sol | 226 +++++++++---------- eth/contracts/libraries/LibArtifactUtils.sol | 31 +-- eth/contracts/libraries/LibGameUtils.sol | 2 +- eth/test/DFERC1155.test.ts | 15 +- 7 files changed, 220 insertions(+), 182 deletions(-) diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index aa204d75..4224fa0f 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -2,25 +2,12 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; +import {ArtifactProperties, ArtifactInfo, CollectionType, ArtifactType, ArtifactRarity, Biome} from "./DFTypes.sol"; import "hardhat/console.sol"; // Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper // This makes it more obvious when we are using the DFToken functions - contract DFToken is SolidStateERC1155 { - enum ArtifactInfo { - Unknown, - Level, - ArtifactType, - Biome - } - - struct Artifact { - uint8 level; - uint8 artifactType; - uint8 biome; - } - function mint( address account, uint256 id, @@ -55,18 +42,29 @@ contract DFToken is SolidStateERC1155 { return uint8(shift - (bin * index)); } + /** + * @notice Create the collection ID for a given artifact + * @param _collectionType type of artifact + * @param _rarity rarity of artifact + * @param _artifactType of artifact + * @param _biome of artifact + * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. + */ function encodeArtifact( - uint256 _level, + uint256 _collectionType, + uint256 _rarity, uint256 _artifactType, uint256 _biome ) public pure returns (uint256) { - uint256 level = _level << calcBitShift(uint8(ArtifactInfo.Level)); + uint256 collectionType = _collectionType << + calcBitShift(uint8(ArtifactInfo.CollectionType)); + uint256 rarity = _rarity << calcBitShift(uint8(ArtifactInfo.ArtifactRarity)); uint256 artifactType = _artifactType << calcBitShift(uint8(ArtifactInfo.ArtifactType)); uint256 biome = _biome << calcBitShift(uint8(ArtifactInfo.Biome)); - return level + artifactType + biome; + return collectionType + rarity + artifactType + biome; } - function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { bytes memory _b = abi.encodePacked(artifactId); // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. @@ -75,10 +73,16 @@ contract DFToken is SolidStateERC1155 { // As a consequence, we need to // offset fetching the relevant byte from the artifactId by 1. // However - uint8 level = uint8(_b[uint8(ArtifactInfo.Level) - 1]); + uint8 collectionType = uint8(_b[uint8(ArtifactInfo.CollectionType) - 1]); + uint8 rarity = uint8(_b[uint8(ArtifactInfo.ArtifactRarity) - 1]); uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); - return Artifact(level, artifactType, biome); - // return level + artifactType + biome; + return + ArtifactProperties({ + collectionType: CollectionType(collectionType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + biome: Biome(biome) + }); } } diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 34c55522..0fd461b7 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -263,3 +263,24 @@ enum Biome { Lava, Corrupted } + +enum ArtifactInfo { + Unknown, + CollectionType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) + ArtifactRarity, + ArtifactType, + Biome +} + +enum CollectionType { + Unknown, + Artifact, + Spaceship +} + +struct ArtifactProperties { + CollectionType collectionType; + ArtifactRarity rarity; + ArtifactType artifactType; + Biome biome; +} diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 35f93bc7..a3ac8abf 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -2,10 +2,9 @@ pragma solidity ^0.8.0; // Contract imports -import {SolidStateERC721} from "@solidstate/contracts/token/ERC721/SolidStateERC721.sol"; -import {ERC721BaseStorage} from "@solidstate/contracts/token/ERC721/base/ERC721BaseStorage.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; +import {DFToken} from "../DFToken.sol"; // Library Imports import {LibPermissions} from "../libraries/LibPermissions.sol"; @@ -17,11 +16,9 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs} from "../DFTypes.sol"; - -contract DFArtifactFacet is WithStorage, SolidStateERC721 { - using ERC721BaseStorage for ERC721BaseStorage.Layout; +import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +contract DFArtifactFacet is WithStorage, DFToken { event PlanetProspected(address player, uint256 loc); event ArtifactFound(address player, uint256 artifactId, uint256 loc); event ArtifactDeposited(address player, uint256 artifactId, uint256 loc); @@ -71,7 +68,8 @@ contract DFArtifactFacet is WithStorage, SolidStateERC721 { { require(args.tokenId >= 1, "artifact id must be positive"); - _mint(args.owner, args.tokenId); + // Account, Id, Amount, Data + _mint(args.owner, args.tokenId, 1, ""); Artifact memory newArtifact = Artifact( true, @@ -94,44 +92,51 @@ contract DFArtifactFacet is WithStorage, SolidStateERC721 { return newArtifact; } - function getArtifact(uint256 tokenId) public view returns (Artifact memory) { - return gs().artifacts[tokenId]; + function getArtifact(uint256 tokenId) public view returns (ArtifactProperties memory) { + return DFToken.decodeArtifact(tokenId); + //return gs().artifacts[tokenId]; } - function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { - return gs().artifacts[tokenByIndex(idx)]; - } + // function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { + // return gs().artifacts[tokenByIndex(idx)]; + // } - function getPlayerArtifactIds(address playerId) public view returns (uint256[] memory) { - uint256 balance = balanceOf(playerId); - uint256[] memory results = new uint256[](balance); + // function getPlayerArtifactIds(address playerId) public view returns (uint256[] memory) { + // uint256 balance = balanceOf(playerId); + // uint256[] memory results = new uint256[](balance); - for (uint256 idx = 0; idx < balance; idx++) { - results[idx] = tokenOfOwnerByIndex(playerId, idx); - } + // for (uint256 idx = 0; idx < balance; idx++) { + // results[idx] = tokenOfOwnerByIndex(playerId, idx); + // } - return results; - } + // return results; + // } - function transferArtifact(uint256 tokenId, address newOwner) public onlyAdminOrCore { + function transferArtifact( + uint256 tokenId, + address owner, + address newOwner + ) public onlyAdminOrCore { if (newOwner == address(0)) { - _burn(tokenId); + // account, id, amount. + _burn(owner, tokenId, 1); } else { - _transfer(ownerOf(tokenId), newOwner, tokenId); + // operator sender receiver id amount data + _transfer(owner, owner, newOwner, tokenId, 1, ""); } } - function updateArtifact(Artifact memory updatedArtifact) public onlyAdminOrCore { + function updateArtifact(Artifact memory updatedArtifact, address owner) public onlyAdminOrCore { require( - ERC721BaseStorage.layout().exists(updatedArtifact.id), + doesArtifactExist(owner, updatedArtifact.id), "you cannot update an artifact that doesn't exist" ); gs().artifacts[updatedArtifact.id] = updatedArtifact; } - function doesArtifactExist(uint256 tokenId) public view returns (bool) { - return ERC721BaseStorage.layout().exists(tokenId); + function doesArtifactExist(address owner, uint256 tokenId) public view returns (bool) { + return balanceOf(owner, tokenId) > 0; } function findArtifact( @@ -287,7 +292,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC721 { function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { Artifact memory artifact = createArtifact(args); - transferArtifact(artifact.id, address(this)); + transferArtifact(artifact.id, address(this), address(this)); LibGameUtils._putArtifactOnPlanet(artifact.id, args.planetId); emit ArtifactFound(args.owner, artifact.id, args.planetId); diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 93ff1c38..f8188b1e 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -315,91 +315,91 @@ contract DFGetterFacet is WithStorage { return ret; } - function getArtifactById(uint256 artifactId) - public - view - returns (ArtifactWithMetadata memory ret) - { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); - - address owner; - - try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { - owner = addr; - } catch Error(string memory) { - // artifact is probably burned / owned by 0x0, so owner is 0x0 - } catch (bytes memory) { - // this shouldn't happen - } - - ret = ArtifactWithMetadata({ - artifact: artifact, - upgrade: LibGameUtils._getUpgradeForArtifact(artifact), - timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), - owner: owner, - locationId: gs().artifactIdToPlanetId[artifact.id], - voyageId: gs().artifactIdToVoyageId[artifact.id] - }); - } - - function getArtifactsOnPlanet(uint256 locationId) - public - view - returns (ArtifactWithMetadata[] memory ret) - { - uint256[] memory artifactIds = gs().planetArtifacts[locationId]; - ret = new ArtifactWithMetadata[](artifactIds.length); - for (uint256 i = 0; i < artifactIds.length; i++) { - ret[i] = getArtifactById(artifactIds[i]); - } - return ret; - } - - function bulkGetPlanetArtifacts(uint256[] calldata planetIds) - public - view - returns (ArtifactWithMetadata[][] memory) - { - ArtifactWithMetadata[][] memory ret = new ArtifactWithMetadata[][](planetIds.length); - - for (uint256 i = 0; i < planetIds.length; i++) { - uint256[] memory planetOwnedArtifactIds = gs().planetArtifacts[planetIds[i]]; - ret[i] = bulkGetArtifactsByIds(planetOwnedArtifactIds); - } - - return ret; - } - - function bulkGetArtifactsByIds(uint256[] memory ids) - public - view - returns (ArtifactWithMetadata[] memory ret) - { - ret = new ArtifactWithMetadata[](ids.length); - - for (uint256 i = 0; i < ids.length; i++) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(ids[i]); - - address owner; - - try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { - owner = addr; - } catch Error(string memory) { - // artifact is probably burned or owned by 0x0, so owner is 0x0 - } catch (bytes memory) { - // this shouldn't happen - } - - ret[i] = ArtifactWithMetadata({ - artifact: artifact, - upgrade: LibGameUtils._getUpgradeForArtifact(artifact), - timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), - owner: owner, - locationId: gs().artifactIdToPlanetId[artifact.id], - voyageId: gs().artifactIdToVoyageId[artifact.id] - }); - } - } + // function getArtifactById(uint256 artifactId) + // public + // view + // returns (ArtifactWithMetadata memory ret) + // { + // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); + + // address owner; + + // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { + // owner = addr; + // } catch Error(string memory) { + // // artifact is probably burned / owned by 0x0, so owner is 0x0 + // } catch (bytes memory) { + // // this shouldn't happen + // } + + // ret = ArtifactWithMetadata({ + // artifact: artifact, + // upgrade: LibGameUtils._getUpgradeForArtifact(artifact), + // timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), + // owner: owner, + // locationId: gs().artifactIdToPlanetId[artifact.id], + // voyageId: gs().artifactIdToVoyageId[artifact.id] + // }); + // } + + // function getArtifactsOnPlanet(uint256 locationId) + // public + // view + // returns (ArtifactWithMetadata[] memory ret) + // { + // uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + // ret = new ArtifactWithMetadata[](artifactIds.length); + // for (uint256 i = 0; i < artifactIds.length; i++) { + // ret[i] = getArtifactById(artifactIds[i]); + // } + // return ret; + // } + + // function bulkGetPlanetArtifacts(uint256[] calldata planetIds) + // public + // view + // returns (ArtifactWithMetadata[][] memory) + // { + // ArtifactWithMetadata[][] memory ret = new ArtifactWithMetadata[][](planetIds.length); + + // for (uint256 i = 0; i < planetIds.length; i++) { + // uint256[] memory planetOwnedArtifactIds = gs().planetArtifacts[planetIds[i]]; + // ret[i] = bulkGetArtifactsByIds(planetOwnedArtifactIds); + // } + + // return ret; + // } + + // function bulkGetArtifactsByIds(uint256[] memory ids) + // public + // view + // returns (ArtifactWithMetadata[] memory ret) + // { + // ret = new ArtifactWithMetadata[](ids.length); + + // for (uint256 i = 0; i < ids.length; i++) { + // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(ids[i]); + + // address owner; + + // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { + // owner = addr; + // } catch Error(string memory) { + // // artifact is probably burned or owned by 0x0, so owner is 0x0 + // } catch (bytes memory) { + // // this shouldn't happen + // } + + // ret[i] = ArtifactWithMetadata({ + // artifact: artifact, + // upgrade: LibGameUtils._getUpgradeForArtifact(artifact), + // timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), + // owner: owner, + // locationId: gs().artifactIdToPlanetId[artifact.id], + // voyageId: gs().artifactIdToVoyageId[artifact.id] + // }); + // } + // } /** * Get a group or artifacts based on their index, fetch all between startIdx & endIdx. @@ -408,32 +408,32 @@ contract DFGetterFacet is WithStorage { * @param startIdx index of the first element to get * @param endIdx index of the last element to get */ - function bulkGetArtifacts(uint256 startIdx, uint256 endIdx) - public - view - returns (ArtifactWithMetadata[] memory ret) - { - ret = new ArtifactWithMetadata[](endIdx - startIdx); - - for (uint256 i = startIdx; i < endIdx; i++) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifactAtIndex(i); - address owner = address(0); - - try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { - owner = addr; - } catch Error(string memory) { - // artifact is probably burned or owned by 0x0, so owner is 0x0 - } catch (bytes memory) { - // this shouldn't happen - } - ret[i - startIdx] = ArtifactWithMetadata({ - artifact: artifact, - upgrade: LibGameUtils._getUpgradeForArtifact(artifact), - timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), - owner: owner, - locationId: gs().artifactIdToPlanetId[artifact.id], - voyageId: gs().artifactIdToVoyageId[artifact.id] - }); - } - } + // function bulkGetArtifacts(uint256 startIdx, uint256 endIdx) + // public + // view + // returns (ArtifactWithMetadata[] memory ret) + // { + // ret = new ArtifactWithMetadata[](endIdx - startIdx); + + // for (uint256 i = startIdx; i < endIdx; i++) { + // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifactAtIndex(i); + // address owner = address(0); + + // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { + // owner = addr; + // } catch Error(string memory) { + // // artifact is probably burned or owned by 0x0, so owner is 0x0 + // } catch (bytes memory) { + // // this shouldn't happen + // } + // ret[i - startIdx] = ArtifactWithMetadata({ + // artifact: artifact, + // upgrade: LibGameUtils._getUpgradeForArtifact(artifact), + // timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), + // owner: owner, + // locationId: gs().artifactIdToPlanetId[artifact.id], + // voyageId: gs().artifactIdToVoyageId[artifact.id] + // }); + // } + // } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 9df35d15..740016d7 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -11,7 +11,7 @@ import {LibGameUtils} from "./LibGameUtils.sol"; import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, DFPFindArtifactArgs, DFTCreateArtifactArgs} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; library LibArtifactUtils { function gs() internal pure returns (GameStorage storage) { @@ -105,12 +105,12 @@ library LibArtifactUtils { DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( artifactSeed, - msg.sender, + msg.sender, // discoverer args.planetId, LibGameUtils.artifactRarityFromPlanetLevel(levelBonus + planet.planetLevel), biome, artifactType, - args.coreAddress, + args.coreAddress, // owner address(0) ); @@ -252,12 +252,14 @@ library LibArtifactUtils { if (shouldDeactivateAndBurn) { artifact.lastDeactivated = block.timestamp; // immediately deactivate - DFArtifactFacet(address(this)).updateArtifact(artifact); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract + // artifact, owner + DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract emit ArtifactDeactivated(msg.sender, artifactId, locationId); // burn it after use. will be owned by contract but not on a planet anyone can control LibGameUtils._takeArtifactOffPlanet(artifactId, locationId); } else { - DFArtifactFacet(address(this)).updateArtifact(artifact); + // artifact, owner + DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); } // this is fine even tho some artifacts are immediately deactivated, because @@ -282,7 +284,7 @@ library LibArtifactUtils { artifact.lastDeactivated = block.timestamp; artifact.wormholeTo = 0; emit ArtifactDeactivated(msg.sender, artifact.id, locationId); - DFArtifactFacet(address(this)).updateArtifact(artifact); + DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); bool shouldBurn = artifact.artifactType == ArtifactType.PlanetaryShield || artifact.artifactType == ArtifactType.PhotoidCannon; @@ -304,12 +306,12 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.planetType == PlanetType.TRADING_POST, "can only deposit on trading posts"); require( - DFArtifactFacet(address(this)).ownerOf(artifactId) == msg.sender, + DFArtifactFacet(address(this)).balanceOf(msg.sender, artifactId) > 0, "you can only deposit artifacts you own" ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" @@ -319,8 +321,8 @@ library LibArtifactUtils { require(gs().planetArtifacts[locationId].length < 5, "too many artifacts on this planet"); LibGameUtils._putArtifactOnPlanet(artifactId, locationId); - - DFArtifactFacet(address(this)).transferArtifact(artifactId, coreAddress); + // artifactId, curr owner, new owner + DFArtifactFacet(address(this)).transferArtifact(artifactId, msg.sender, coreAddress); } function withdrawArtifact(uint256 locationId, uint256 artifactId) public { @@ -342,7 +344,8 @@ library LibArtifactUtils { require(!isSpaceship(artifact.artifactType), "cannot withdraw spaceships"); LibGameUtils._takeArtifactOffPlanet(artifactId, locationId); - DFArtifactFacet(address(this)).transferArtifact(artifactId, msg.sender); + // artifactId, curr owner, new owner + DFArtifactFacet(address(this)).transferArtifact(artifactId, address(this), msg.sender); } function prospectPlanet(uint256 locationId) public { @@ -364,9 +367,11 @@ library LibArtifactUtils { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactIds[i]); + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact( + artifactIds[i] + ); if ( - artifact.artifactType == ArtifactType.ShipGear && msg.sender == artifact.controller + artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller ) { return true; } diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index ded7d9d3..814f125f 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -10,7 +10,7 @@ import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; -import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactProperties, ArtifactType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; library LibGameUtils { function gs() internal pure returns (GameStorage storage) { diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 38342ce9..5e7d7d3b 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -45,13 +45,16 @@ describe('SolidStateERC1155', function () { expect(await token.uri(collectionId)).to.equal(tokenURI); }); - it.only('logs bits', async function () { - const _level = '0xff'; + it('logs bits', async function () { + const _collectionType = '0x01'; + const _rarity = '0xff'; const _artifactType = '0x01'; const _biome = '0xab'; - const res = await token.encodeArtifact(_level, _artifactType, _biome); - console.log(res._hex); - const { level, biome, artifactType } = await token.decodeArtifact(res); - console.log(level, biome, artifactType); + const res = await token.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); + const { collectionType, rarity, biome, artifactType } = await token.decodeArtifact(res); + expect(collectionType).to.equal(Number(_collectionType)); + expect(rarity).to.equal(Number(_rarity)); + expect(biome).to.equal(Number(_biome)); + expect(artifactType).to.equal(Number(_artifactType)); }); }); From 3fa771e84f641f1e832baad75d23e85ed0cc1a77 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Tue, 27 Sep 2022 16:15:45 +0100 Subject: [PATCH 11/55] temp: working artifact activate and deactivate --- eth/contracts/DFToken.sol | 61 +- eth/contracts/DFTypes.sol | 5 +- eth/contracts/facets/DFAdminFacet.sol | 13 +- eth/contracts/facets/DFArtifactFacet.sol | 14 +- eth/contracts/facets/DFGetterFacet.sol | 36 +- eth/contracts/facets/DFMoveFacet.sol | 118 +-- eth/contracts/libraries/LibArtifactUtils.sol | 147 ++- eth/contracts/libraries/LibGameUtils.sol | 69 +- eth/contracts/libraries/LibPlanet.sol | 3 +- eth/contracts/libraries/LibStorage.sol | 4 +- eth/hardhat.config.ts | 1 + eth/package.json | 1 + eth/tasks/deploy.ts | 12 +- eth/test/DFERC1155.test.ts | 23 +- eth/test/NewDFArtifacts.test.ts | 903 +++++++++++++++++++ eth/test/utils/TestUtils.ts | 21 +- eth/test/utils/WorldConstants.ts | 8 +- packages/types/src/index.ts | 1 + packages/types/src/token.ts | 18 + 19 files changed, 1259 insertions(+), 199 deletions(-) create mode 100644 eth/test/NewDFArtifacts.test.ts create mode 100644 packages/types/src/token.ts diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index 4224fa0f..4b304c67 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; -import {ArtifactProperties, ArtifactInfo, CollectionType, ArtifactType, ArtifactRarity, Biome} from "./DFTypes.sol"; +import {ArtifactProperties, TokenInfo, CollectionType, ArtifactType, ArtifactRarity, Biome} from "./DFTypes.sol"; import "hardhat/console.sol"; // Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper @@ -55,34 +55,51 @@ contract DFToken is SolidStateERC1155 { uint256 _rarity, uint256 _artifactType, uint256 _biome - ) public pure returns (uint256) { - uint256 collectionType = _collectionType << - calcBitShift(uint8(ArtifactInfo.CollectionType)); - uint256 rarity = _rarity << calcBitShift(uint8(ArtifactInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << calcBitShift(uint8(ArtifactInfo.ArtifactType)); - uint256 biome = _biome << calcBitShift(uint8(ArtifactInfo.Biome)); + ) public view returns (uint256) { + uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); + uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); + uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); + uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); return collectionType + rarity + artifactType + biome; } - function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { + // Collection Type should be Spaceship. ArtifactType should be type of ship. + function encodeSpaceship(uint256 _collectionType, uint256 _artifactType) + public + view + returns (uint256) + { + uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); + uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); + return collectionType + artifactType; + } + + function decodeArtifact(uint256 artifactId) public view returns (ArtifactProperties memory) { bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. + // 0 is left most element. 0 is given the property Unknown in TokenInfo. - // Note: Bit shifting requires an index greater than zero. This is why the ArtifactInfo has - // Unknown as the zero property, so calcBitShift(ArtifactInfo.Level) is correct. + // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has + // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. // As a consequence, we need to // offset fetching the relevant byte from the artifactId by 1. // However - uint8 collectionType = uint8(_b[uint8(ArtifactInfo.CollectionType) - 1]); - uint8 rarity = uint8(_b[uint8(ArtifactInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); - return - ArtifactProperties({ - collectionType: CollectionType(collectionType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - biome: Biome(biome) - }); + uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); + uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); + uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); + // console.log("collectionType %s", collectionType); + // console.log("rarity %s", rarity); + // console.log("artifactType %s", artifactType); + // console.log("biome %s", biome); + + ArtifactProperties memory a = ArtifactProperties({ + id: artifactId, + collectionType: CollectionType(collectionType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + planetBiome: Biome(biome) + }); + + return a; } } diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 0fd461b7..9ce896e8 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -264,7 +264,7 @@ enum Biome { Corrupted } -enum ArtifactInfo { +enum TokenInfo { Unknown, CollectionType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) ArtifactRarity, @@ -279,8 +279,9 @@ enum CollectionType { } struct ArtifactProperties { + uint256 id; CollectionType collectionType; ArtifactRarity rarity; ArtifactType artifactType; - Biome biome; + Biome planetBiome; } diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 19efb8fa..971b3d6b 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -10,14 +10,17 @@ import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; +import {DFArtifactFacet} from "./DFArtifactFacet.sol"; + // Type imports -import {SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, Artifact, ArtifactType, Player, Planet} from "../DFTypes.sol"; +import {SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, Artifact, ArtifactType, Player, Planet} from "../DFTypes.sol"; contract DFAdminFacet is WithStorage { event AdminOwnershipChanged(uint256 loc, address newOwner); event AdminPlanetCreated(uint256 loc); event AdminGiveSpaceship(uint256 loc, address owner, ArtifactType artifactType); event PauseStateChanged(bool paused); + event AdminArtifactCreated(address player, uint256 artifactId, uint256 loc); ///////////////////////////// /// Administrative Engine /// @@ -161,4 +164,12 @@ contract DFAdminFacet is WithStorage { function setPlanetTransferEnabled(bool enabled) public onlyAdmin { gameConstants().PLANET_TRANSFER_ENABLED = enabled; } + + function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { + Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); + // TODO: Remove this redundant logic ? + DFArtifactFacet(address(this)).transferArtifact(artifact.id, address(this), address(this)); + LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); + emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); + } } diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index a3ac8abf..0b2bc6ad 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -17,6 +17,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import "hardhat/console.sol"; contract DFArtifactFacet is WithStorage, DFToken { event PlanetProspected(address player, uint256 loc); @@ -162,6 +163,7 @@ contract DFArtifactFacet is WithStorage, DFToken { ); } + console.log("finding artifact..."); uint256 foundArtifactId = LibArtifactUtils.findArtifact( DFPFindArtifactArgs(planetId, biomebase, address(this)) ); @@ -177,7 +179,7 @@ contract DFArtifactFacet is WithStorage, DFToken { LibArtifactUtils.depositArtifact(locationId, artifactId, address(this)); - emit ArtifactDeposited(msg.sender, artifactId, locationId); + emit ArtifactDeposited(msg.sender, locationId, artifactId); } // withdraws the given artifact from the given planet. you must own the planet, @@ -187,7 +189,7 @@ contract DFArtifactFacet is WithStorage, DFToken { LibArtifactUtils.withdrawArtifact(locationId, artifactId); - emit ArtifactWithdrawn(msg.sender, artifactId, locationId); + emit ArtifactWithdrawn(msg.sender, locationId, artifactId); } // activates the given artifact on the given planet. the artifact must have @@ -289,12 +291,4 @@ contract DFArtifactFacet is WithStorage, DFToken { gs().players[msg.sender].claimedShips = true; } - - function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { - Artifact memory artifact = createArtifact(args); - transferArtifact(artifact.id, address(this), address(this)); - LibGameUtils._putArtifactOnPlanet(artifact.id, args.planetId); - - emit ArtifactFound(args.owner, artifact.id, args.planetId); - } } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index f8188b1e..008a36ee 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -12,7 +12,8 @@ import {LibGameUtils} from "../libraries/LibGameUtils.sol"; import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; // Type imports -import {RevealedCoords, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade, Artifact} from "../DFTypes.sol"; +import {RevealedCoords, ArtifactProperties, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade, Artifact} from "../DFTypes.sol"; +import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { // FIRST-LEVEL GETTERS - mirrors the solidity autogenerated toplevel getters, but for GameStorage @@ -342,18 +343,27 @@ contract DFGetterFacet is WithStorage { // }); // } - // function getArtifactsOnPlanet(uint256 locationId) - // public - // view - // returns (ArtifactWithMetadata[] memory ret) - // { - // uint256[] memory artifactIds = gs().planetArtifacts[locationId]; - // ret = new ArtifactWithMetadata[](artifactIds.length); - // for (uint256 i = 0; i < artifactIds.length; i++) { - // ret[i] = getArtifactById(artifactIds[i]); - // } - // return ret; - // } + function getArtifactsOnPlanet(uint256 locationId) + public + view + returns (ArtifactProperties[] memory ret) + { + uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + ret = new ArtifactProperties[](artifactIds.length); + for (uint256 i = 0; i < artifactIds.length; i++) { + ret[i] = DFArtifactFacet(address(this)).decodeArtifact(artifactIds[i]); + } + return ret; + } + + function getActiveArtifactOnPlanet(uint256 locationId) + public + view + returns (ArtifactProperties memory ret) + { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + return DFArtifactFacet(address(this)).getArtifact(artifactId); + } // function bulkGetPlanetArtifacts(uint256[] calldata planetIds) // public diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index f358fb79..042f4239 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -111,18 +111,20 @@ contract DFMoveFacet is WithStorage { ArrivalType arrivalType = ArrivalType.Normal; Upgrade memory temporaryUpgrade = LibGameUtils.defaultUpgrade(); - (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); - if (wormholePresent) { - effectiveDistTimesHundred /= distModifier; - arrivalType = ArrivalType.Wormhole; - } + // TODO: Add back wormhole + // (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); + // if (wormholePresent) { + // effectiveDistTimesHundred /= distModifier; + // arrivalType = ArrivalType.Wormhole; + // } if (!_isSpaceshipMove(args)) { - (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); - if (photoidPresent) { - temporaryUpgrade = newTempUpgrade; - arrivalType = ArrivalType.Photoid; - } + // TODO: Add back photoid + // (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); + // if (photoidPresent) { + // temporaryUpgrade = newTempUpgrade; + // arrivalType = ArrivalType.Photoid; + // } } _removeSpaceshipEffectsFromOriginPlanet(args); @@ -276,59 +278,59 @@ contract DFMoveFacet is WithStorage { return the modified distance between the origin and target planet. */ - function _checkWormhole(DFPMoveArgs memory args) - private - view - returns (bool wormholePresent, uint256 effectiveDistModifier) - { - Artifact memory relevantWormhole; - Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - Artifact memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); - - // TODO: take the greater rarity of these, or disallow wormholes between planets that - // already have a wormhole between them - if ( - activeArtifactFrom.isInitialized && - activeArtifactFrom.artifactType == ArtifactType.Wormhole && - activeArtifactFrom.wormholeTo == args.newLoc - ) { - relevantWormhole = activeArtifactFrom; - } else if ( - activeArtifactTo.isInitialized && - activeArtifactTo.artifactType == ArtifactType.Wormhole && - activeArtifactTo.wormholeTo == args.oldLoc - ) { - relevantWormhole = activeArtifactTo; - } - - if (relevantWormhole.isInitialized) { - wormholePresent = true; - uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; - effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; - } - } + // function _checkWormhole(DFPMoveArgs memory args) + // private + // view + // returns (bool wormholePresent, uint256 effectiveDistModifier) + // { + // Artifact memory relevantWormhole; + // Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + // Artifact memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); + + // // TODO: take the greater rarity of these, or disallow wormholes between planets that + // // already have a wormhole between them + // if ( + // activeArtifactFrom.isInitialized && + // activeArtifactFrom.artifactType == ArtifactType.Wormhole && + // activeArtifactFrom.wormholeTo == args.newLoc + // ) { + // relevantWormhole = activeArtifactFrom; + // } else if ( + // activeArtifactTo.isInitialized && + // activeArtifactTo.artifactType == ArtifactType.Wormhole && + // activeArtifactTo.wormholeTo == args.oldLoc + // ) { + // relevantWormhole = activeArtifactTo; + // } + + // if (relevantWormhole.isInitialized) { + // wormholePresent = true; + // uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; + // effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; + // } + // } /** If an active photoid cannon is present, return the upgrade that should be applied to the origin planet. */ - function _checkPhotoid(DFPMoveArgs memory args) - private - returns (bool photoidPresent, Upgrade memory temporaryUpgrade) - { - Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - if ( - activeArtifactFrom.isInitialized && - activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && - block.timestamp - activeArtifactFrom.lastActivated >= - gameConstants().PHOTOID_ACTIVATION_DELAY - ) { - photoidPresent = true; - LibArtifactUtils.deactivateArtifact(args.oldLoc); - temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); - } - } + // function _checkPhotoid(DFPMoveArgs memory args) + // private + // returns (bool photoidPresent, Upgrade memory temporaryUpgrade) + // { + // Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + // if ( + // activeArtifactFrom.isInitialized && + // activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && + // block.timestamp - activeArtifactFrom.lastActivated >= + // gameConstants().PHOTOID_ACTIVATION_DELAY + // ) { + // photoidPresent = true; + // LibArtifactUtils.deactivateArtifact(args.oldLoc); + // temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); + // } + // } function _abandonPlanet(DFPMoveArgs memory args) private @@ -436,7 +438,7 @@ contract DFMoveFacet is WithStorage { }); if (args.movedArtifactId != 0) { - LibGameUtils._takeArtifactOffPlanet(args.movedArtifactId, args.oldLoc); + LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); gs().artifactIdToVoyageId[args.movedArtifactId] = gs().planetEventsCount; } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 740016d7..b0b77c9d 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -11,7 +11,8 @@ import {LibGameUtils} from "./LibGameUtils.sol"; import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, CollectionType, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import "hardhat/console.sol"; library LibArtifactUtils { function gs() internal pure returns (GameStorage storage) { @@ -63,9 +64,14 @@ library LibArtifactUtils { require(shipType <= ArtifactType.ShipTitan && shipType >= ArtifactType.ShipMothership); uint256 id = uint256(keccak256(abi.encodePacked(planetId, gs().miscNonce++))); - + uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( + uint8(CollectionType.Spaceship), + uint8(ArtifactRarity.Unknown), + uint8(shipType), + uint8(Biome.Unknown) + ); DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - id, + tokenId, msg.sender, planetId, ArtifactRarity.Unknown, @@ -78,7 +84,7 @@ library LibArtifactUtils { Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); - LibGameUtils._putArtifactOnPlanet(foundArtifact.id, planetId); + LibGameUtils._putArtifactOnPlanet(planetId, foundArtifact.id); return id; } @@ -103,11 +109,27 @@ library LibArtifactUtils { (ArtifactType artifactType, uint256 levelBonus) = LibGameUtils ._randomArtifactTypeAndLevelBonus(artifactSeed, biome, planet.spaceType); + ArtifactRarity rarity = LibGameUtils.artifactRarityFromPlanetLevel( + levelBonus + planet.planetLevel + ); + + // console.log("LAU: collectionType %s", uint8(CollectionType.Artifact)); + // console.log("LAU: rarity %s", uint8(rarity)); + // console.log("artifactType %s", uint8(artifactType)); + // console.log("biome %s", uint8(biome)); + + uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( + uint8(CollectionType.Artifact), + uint8(rarity), + uint8(artifactType), + uint8(biome) + ); + DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - artifactSeed, + tokenId, msg.sender, // discoverer args.planetId, - LibGameUtils.artifactRarityFromPlanetLevel(levelBonus + planet.planetLevel), + rarity, biome, artifactType, args.coreAddress, // owner @@ -118,7 +140,7 @@ library LibArtifactUtils { createArtifactArgs ); - LibGameUtils._putArtifactOnPlanet(foundArtifact.id, args.planetId); + LibGameUtils._putArtifactOnPlanet(args.planetId, foundArtifact.id); planet.hasTriedFindingArtifact = true; gs().players[msg.sender].score += gameConstants().ARTIFACT_POINT_VALUES[ @@ -134,7 +156,9 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - Artifact storage artifact = gs().artifacts[artifactId]; + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).decodeArtifact( + artifactId + ); require( LibGameUtils.isArtifactOnPlanet(locationId, artifactId), @@ -142,12 +166,13 @@ library LibArtifactUtils { ); if (isSpaceship(artifact.artifactType)) { - activateSpaceshipArtifact(locationId, artifactId, planet, artifact); + // This breaks Crescent functionality + // activateSpaceshipArtifact(locationId, artifactId, planet, artifact); } else { activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); } - artifact.activations++; + // artifact.activations++; } function activateSpaceshipArtifact( @@ -184,7 +209,7 @@ library LibArtifactUtils { } planet.planetType = PlanetType.SILVER_MINE; - emit ArtifactActivated(msg.sender, artifactId, locationId); + emit ArtifactActivated(msg.sender, locationId, artifactId); } } @@ -193,47 +218,57 @@ library LibArtifactUtils { uint256 artifactId, uint256 wormholeTo, Planet storage planet, - Artifact memory artifact + ArtifactProperties memory artifact ) private { + console.log("activating %s on %s", locationId, artifactId); require( planet.owner == msg.sender, "you must own the planet you are activating an artifact on" ); require( - !LibGameUtils.getActiveArtifact(locationId).isInitialized, + LibGameUtils.getActiveArtifact(locationId).collectionType == CollectionType.Unknown, "there is already an active artifact on this planet" ); require(!planet.destroyed, "planet is destroyed"); - require(artifact.isInitialized, "this artifact is not on this planet"); + uint256 length = gs().planetArtifacts[locationId].length; + console.log("artifacts on %s: %s", locationId, length); + require( + LibGameUtils.getPlanetArtifact(locationId, artifactId).collectionType != + CollectionType.Unknown, + "this artifact is not on this planet" + ); // Unknown is the 0th one, Monolith is the 1st, and so on. // TODO v0.6: consider photoid canon uint256[10] memory artifactCooldownsHours = [uint256(24), 0, 0, 0, 0, 4, 4, 24, 24, 24]; - - require( - artifact.lastDeactivated + - artifactCooldownsHours[uint256(artifact.artifactType)] * - 60 * - 60 < - block.timestamp, - "this artifact is on a cooldown" - ); + // TODO: Cooldown is broken + // require( + // artifact.lastDeactivated + + // artifactCooldownsHours[uint256(artifact.artifactType)] * + // 60 * + // 60 < + // block.timestamp, + // "this artifact is on a cooldown" + // ); bool shouldDeactivateAndBurn = false; - artifact.lastActivated = block.timestamp; - emit ArtifactActivated(msg.sender, artifactId, locationId); - - if (artifact.artifactType == ArtifactType.Wormhole) { - require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); - require( - gs().planets[wormholeTo].owner == msg.sender, - "you can only create a wormhole to a planet you own" - ); - require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); - artifact.wormholeTo = wormholeTo; - } else if (artifact.artifactType == ArtifactType.BloomFilter) { + // artifact.lastActivated = block.timestamp; + gs().planetActiveArtifact[locationId] = artifactId; + emit ArtifactActivated(msg.sender, locationId, artifactId); + + // TODO: Wormhole is broken + + // if (artifact.artifactType == ArtifactType.Wormhole) { + // require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); + // require( + // gs().planets[wormholeTo].owner == msg.sender, + // "you can only create a wormhole to a planet you own" + // ); + // require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); + // artifact.wormholeTo = wormholeTo; + if (artifact.artifactType == ArtifactType.BloomFilter) { require( 2 * uint256(artifact.rarity) >= planet.planetLevel, "artifact is not powerful enough to apply effect to this planet level" @@ -251,17 +286,19 @@ library LibArtifactUtils { } if (shouldDeactivateAndBurn) { - artifact.lastDeactivated = block.timestamp; // immediately deactivate + // artifact.lastDeactivated = block.timestamp; // immediately deactivate // artifact, owner - DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract - emit ArtifactDeactivated(msg.sender, artifactId, locationId); + // TODO: We aren't updating the artifact beacuse there are no properties to change. + // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract + emit ArtifactDeactivated(msg.sender, locationId, artifactId); // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(artifactId, locationId); + LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); } else { + // TODO: We aren't updating the artifact beacuse there are no properties to change. // artifact, owner - DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); + // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); } - + console.log("buffing planet"); // this is fine even tho some artifacts are immediately deactivated, because // those artifacts do not buff the planet. LibGameUtils._buffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); @@ -277,14 +314,19 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); - Artifact memory artifact = LibGameUtils.getActiveArtifact(locationId); + ArtifactProperties memory artifact = LibGameUtils.getActiveArtifact(locationId); - require(artifact.isInitialized, "this artifact is not activated on this planet"); + require( + artifact.collectionType != CollectionType.Unknown, + "this artifact is not activated on this planet" + ); - artifact.lastDeactivated = block.timestamp; - artifact.wormholeTo = 0; + // artifact.lastDeactivated = block.timestamp; + // artifact.wormholeTo = 0; + gs().planetActiveArtifact[locationId] = 0; emit ArtifactDeactivated(msg.sender, artifact.id, locationId); - DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); + // TODO: Figure out update artifact + // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); bool shouldBurn = artifact.artifactType == ArtifactType.PlanetaryShield || artifact.artifactType == ArtifactType.PhotoidCannon; @@ -320,7 +362,7 @@ library LibArtifactUtils { require(gs().planetArtifacts[locationId].length < 5, "too many artifacts on this planet"); - LibGameUtils._putArtifactOnPlanet(artifactId, locationId); + LibGameUtils._putArtifactOnPlanet(locationId, artifactId); // artifactId, curr owner, new owner DFArtifactFacet(address(this)).transferArtifact(artifactId, msg.sender, coreAddress); } @@ -334,15 +376,19 @@ library LibArtifactUtils { ); require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); - Artifact memory artifact = LibGameUtils.getPlanetArtifact(locationId, artifactId); - require(artifact.isInitialized, "this artifact is not on this planet"); + ArtifactProperties memory artifact = LibGameUtils.getPlanetArtifact(locationId, artifactId); + // TODO: Write is initialized function. + require( + artifact.collectionType != CollectionType.Unknown, + "this artifact is not on this planet" + ); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to withdraw this artifact" ); require(!isSpaceship(artifact.artifactType), "cannot withdraw spaceships"); - LibGameUtils._takeArtifactOffPlanet(artifactId, locationId); + LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); // artifactId, curr owner, new owner DFArtifactFacet(address(this)).transferArtifact(artifactId, address(this), msg.sender); @@ -371,6 +417,7 @@ library LibArtifactUtils { artifactIds[i] ); if ( + // TODO: Gear is broken artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller ) { return true; diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 814f125f..081346b3 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -10,7 +10,8 @@ import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; -import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactProperties, ArtifactType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactProperties, ArtifactType, CollectionType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import "hardhat/console.sol"; library LibGameUtils { function gs() internal pure returns (GameStorage storage) { @@ -267,7 +268,11 @@ library LibGameUtils { }); } - function _getUpgradeForArtifact(Artifact memory artifact) public pure returns (Upgrade memory) { + function _getUpgradeForArtifact(ArtifactProperties memory artifact) + public + pure + returns (Upgrade memory) + { if (artifact.artifactType == ArtifactType.PlanetaryShield) { uint256[6] memory defenseMultipliersPerRarity = [uint256(100), 150, 200, 300, 450, 650]; @@ -382,11 +387,14 @@ library LibGameUtils { // planets can have multiple artifacts on them. this function updates all the // internal contract book-keeping to reflect that the given artifact was // put on. note that this function does not transfer the artifact. - function _putArtifactOnPlanet(uint256 artifactId, uint256 locationId) public { - gs().artifactIdToPlanetId[artifactId] = locationId; + function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { + console.log("putting %s on %s", locationId, artifactId); gs().planetArtifacts[locationId].push(artifactId); + uint256 length = gs().planetArtifacts[locationId].length; + console.log("new planet artifact id", gs().planetArtifacts[locationId][length - 1]); } + // TODO: Why not burn ? // planets can have multiple artifacts on them. this function updates all the // internal contract book-keeping to reflect that the given artifact was // taken off the given planet. note that this function does not transfer the @@ -394,18 +402,23 @@ library LibGameUtils { // // if the given artifact is not on the given planet, reverts // if the given artifact is currently activated, reverts - function _takeArtifactOffPlanet(uint256 artifactId, uint256 locationId) public { + + /** + * Should remove artifactId from planet with locationId if artifactId exists AND is not active. + */ + + function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact( + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact( gs().planetArtifacts[locationId][i] ); require( - !isActivated(artifact), + !isActivatedERC1155(locationId, artifactId), "you cannot take an activated artifact off a planet" ); @@ -419,7 +432,6 @@ library LibGameUtils { } require(hadTheArtifact, "this artifact was not present on this planet"); - gs().artifactIdToPlanetId[artifactId] = 0; gs().planetArtifacts[locationId].pop(); } @@ -431,6 +443,10 @@ library LibGameUtils { return artifact.lastDeactivated < artifact.lastActivated; } + function isActivatedERC1155(uint256 locationId, uint256 artifactId) public view returns (bool) { + return (gs().planetActiveArtifact[locationId] > 0); + } + function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) public returns (bool) { for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { @@ -446,31 +462,31 @@ library LibGameUtils { function getPlanetArtifact(uint256 locationId, uint256 artifactId) public view - returns (Artifact memory) + returns (ArtifactProperties memory) { + console.log("searching for %s on %s", locationId, artifactId); + console.log( + "%s artifacts on planet %s", + gs().planetArtifacts[locationId].length, + locationId + ); for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { + console.log("found %s ", gs().planetArtifacts[locationId][i]); if (gs().planetArtifacts[locationId][i] == artifactId) { return DFArtifactFacet(address(this)).getArtifact(artifactId); } } - return _nullArtifact(); + return _nullArtifactProperties(); } // if the given planet has an activated artifact on it, then return the artifact // otherwise, return a 'null artifact' - function getActiveArtifact(uint256 locationId) public view returns (Artifact memory) { - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact( - gs().planetArtifacts[locationId][i] - ); - - if (isActivated(artifact)) { - return artifact; - } - } + function getActiveArtifact(uint256 locationId) public view returns (ArtifactProperties memory) { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + if (artifactId != 0) return DFArtifactFacet(address(this)).decodeArtifact(artifactId); - return _nullArtifact(); + return _nullArtifactProperties(); } // the space junk that a planet starts with @@ -501,6 +517,17 @@ library LibGameUtils { ); } + function _nullArtifactProperties() private pure returns (ArtifactProperties memory) { + return + ArtifactProperties( + 0, + CollectionType.Unknown, + ArtifactRarity.Unknown, + ArtifactType.Unknown, + Biome.Unknown + ); + } + function _buffPlanet(uint256 location, Upgrade memory upgrade) public { Planet storage planet = gs().planets[location]; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index acf3c307..7d503526 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -13,6 +13,7 @@ import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStora // Type imports import {Artifact, ArtifactType, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; +import "hardhat/console.sol"; library LibPlanet { function gs() internal pure returns (GameStorage storage) { @@ -346,7 +347,7 @@ library LibPlanet { for (uint256 i = 0; i < 12; i++) { if (artifactIdsToAddToPlanet[i] != 0) { gs().artifactIdToVoyageId[artifactIdsToAddToPlanet[i]] = 0; - LibGameUtils._putArtifactOnPlanet(artifactIdsToAddToPlanet[i], location); + LibGameUtils._putArtifactOnPlanet(location, artifactIdsToAddToPlanet[i]); } } } diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index fb375c08..67157ca7 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -45,8 +45,10 @@ struct GameStorage { mapping(uint256 => PlanetEventMetadata[]) planetEvents; // maps event id to arrival data mapping(uint256 => ArrivalData) planetArrivals; - mapping(uint256 => uint256[]) planetArtifacts; // Artifact stuff + mapping(uint256 => uint256[]) planetArtifacts; + // TODO: Make this an array + mapping(uint256 => uint256) planetActiveArtifact; mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; diff --git a/eth/hardhat.config.ts b/eth/hardhat.config.ts index fc7dee6e..20a56f9d 100644 --- a/eth/hardhat.config.ts +++ b/eth/hardhat.config.ts @@ -219,6 +219,7 @@ const config: HardhatUserConfig = { // This plugin will combine all ABIs from any Smart Contract with `Facet` in the name or path and output it as `DarkForest.json` name: 'DarkForest', include: ['Facet', 'DFDiamond'], + exclude: ['Old'], // We explicitly set `strict` to `true` because we want to validate our facets don't accidentally provide overlapping functions strict: true, // We use our diamond utils to filter some functions we ignore from the combined ABI diff --git a/eth/package.json b/eth/package.json index e7b15a1a..45969814 100644 --- a/eth/package.json +++ b/eth/package.json @@ -64,6 +64,7 @@ "node": ">=16" }, "scripts": { + "hardhat": "hardhat", "test": "hardhat test && npm run subgraph:template:dev", "lint": "eslint .", "format": "prettier --write .", diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index e7486cc1..f9df2f50 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -309,15 +309,11 @@ export async function deployAndCut( return [diamond, diamondInit, initReceipt] as const; } -export async function deployGetterFacet( - {}, - { LibGameUtils }: Libraries, - hre: HardhatRuntimeEnvironment -) { +export async function deployGetterFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { const factory = await hre.ethers.getContractFactory('DFGetterFacet', { - libraries: { - LibGameUtils, - }, + // libraries: { + // LibGameUtils, + // }, }); const contract = await factory.deploy(); await contract.deployTransaction.wait(); diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index 5e7d7d3b..c23ae107 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -1,4 +1,5 @@ import { DFToken, DFToken__factory } from '@dfdao/contracts/typechain'; +import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; @@ -45,16 +46,26 @@ describe('SolidStateERC1155', function () { expect(await token.uri(collectionId)).to.equal(tokenURI); }); - it('logs bits', async function () { + it.skip('logs bits for artifact', async function () { + // Must be valid options const _collectionType = '0x01'; - const _rarity = '0xff'; - const _artifactType = '0x01'; - const _biome = '0xab'; + const _rarity = ArtifactRarity.Legendary; + const _artifactType = ArtifactType.Colossus; + const _biome = Biome.DESERT; const res = await token.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); - const { collectionType, rarity, biome, artifactType } = await token.decodeArtifact(res); + const { collectionType, rarity, planetBiome, artifactType } = await token.decodeArtifact(res); expect(collectionType).to.equal(Number(_collectionType)); expect(rarity).to.equal(Number(_rarity)); - expect(biome).to.equal(Number(_biome)); + expect(planetBiome).to.equal(Number(_biome)); + expect(artifactType).to.equal(Number(_artifactType)); + }); + it.only('logs bits for spaceship', async function () { + // Must be valid options + const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types + const _artifactType = ArtifactType.ShipGear; + const res = await token.encodeArtifact(_collectionType, 0, _artifactType, 0); + const { collectionType, artifactType } = await token.decodeArtifact(res); + expect(collectionType).to.equal(Number(_collectionType)); expect(artifactType).to.equal(Number(_artifactType)); }); }); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts new file mode 100644 index 00000000..2b21cf9a --- /dev/null +++ b/eth/test/NewDFArtifacts.test.ts @@ -0,0 +1,903 @@ +import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { BigNumberish } from 'ethers'; +import hre from 'hardhat'; +import { TestLocation } from './utils/TestLocation'; +import { + conquerUnownedPlanet, + createArtifactOnPlanet, + getArtifactsOwnedBy, + getCurrentTime, + getStatSum, + hexToBigNumber, + increaseBlockchainTime, + makeFindArtifactArgs, + makeInitArgs, + makeMoveArgs, + prettyPrintToken, + user1MintArtifactPlanet, + ZERO_ADDRESS, +} from './utils/TestUtils'; +import { defaultWorldFixture, World } from './utils/TestWorld'; +import { + ARTIFACT_PLANET_1, + LVL0_PLANET, + LVL0_PLANET_DEAD_SPACE, + LVL3_SPACETIME_1, + LVL3_SPACETIME_2, + LVL3_SPACETIME_3, + LVL3_UNOWNED_NEBULA, + LVL4_UNOWNED_DEEP_SPACE, + LVL6_SPACETIME, + SPACE_PERLIN, + SPAWN_PLANET_1, + SPAWN_PLANET_2, +} from './utils/WorldConstants'; + +describe('DarkForestArtifacts', function () { + let world: World; + + async function worldFixture() { + const world = await loadFixture(defaultWorldFixture); + + // Initialize player + await world.user1Core.initializePlayer(...makeInitArgs(SPAWN_PLANET_1)); + await world.user1Core.giveSpaceShips(SPAWN_PLANET_1.id); + await world.user2Core.initializePlayer(...makeInitArgs(SPAWN_PLANET_2)); + + // Conquer initial planets + //// Player 1 + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, ARTIFACT_PLANET_1); + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + //// Player 2 + await conquerUnownedPlanet(world, world.user2Core, SPAWN_PLANET_2, LVL3_SPACETIME_2); + await increaseBlockchainTime(); + + // Move the Gear ship into position + const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( + (artifact) => artifact.artifactType === ArtifactType.ShipGear + ); + console.log(`gearId`, gearShip?.id._hex); + const gearId = gearShip?.id; + await world.user1Core.move( + ...makeMoveArgs(SPAWN_PLANET_1, ARTIFACT_PLANET_1, 100, 0, 0, gearId) + ); + await increaseBlockchainTime(); + const tx = await world.user1Core.refreshPlanet(ARTIFACT_PLANET_1.id); + await tx.wait(); + + const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + prettyPrintToken(artifactsOnPlanet[0]); + // Expect gear to be on planet. + expect(artifactsOnPlanet.length).to.be.equal(1); + + // Conquer another planet for artifact storage + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET_DEAD_SPACE); + + return world; + } + + beforeEach('load fixture', async function () { + this.timeout(0); + world = await loadFixture(worldFixture); + }); + + async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { + return await world.contract.getArtifactsOnPlanet(locationId); + } + + it.only('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { + const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + const newId = await user1MintArtifactPlanet(world.user1Core); + console.log('new id', newId._hex); + const res = await world.user1Core.decodeArtifact(newId); + prettyPrintToken(res); + const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + // artifact and gear should be on planet. Gear is 0 and Artifact is 1. + const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + expect(artifactsOnPlanet.length).to.be.equal(2); + + // artifact should be owned by contract + artifactsOnPlanet.map(async (a) => { + expect(await world.contract.balanceOf(world.contract.address, a.id)).to.equal(1); + }); + + // expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); + + // let's update the planet to be one of the basic artifacts, so that + // we know it's definitely going to buff the planet in some way. also, + // this prevents the artifact from being one that requires valid parameter + // in order to activate + // const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); + // updatedArtifact.artifactType = 0; + // await world.contract.updateArtifact(updatedArtifact); + + // // planet should be buffed after discovered artifact + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[1].id, 0); + const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); + prettyPrintToken(activeArtifact); + const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + // // planet buff should be removed after artifact deactivated + await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); + const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + expect(statSumAfterActivation).to.not.be.within(statSumInitial - 5, statSumInitial + 5); + expect(statSumAfterActivation).to.not.be.within( + statSumAfterDeactivate - 5, + statSumAfterDeactivate + 5 + ); + expect(statSumAfterDeactivate).to.be.within(statSumInitial - 5, statSumInitial + 5); + expect(statSumAfterFound).to.be.within(statSumInitial - 5, statSumInitial + 5); + }); + + it('cannot prospect multiple times, cannot find artifact more than 256 blocks after prospecting', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + + await expect(world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id)).to.be.revertedWith( + 'this planet has already been prospected' + ); + + for (let i = 0; i < 256; i++) { + await increaseBlockchainTime(); + } + + await expect( + world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) + ).to.be.revertedWith('planet prospect expired'); + }); + + it('should return a correct token uri for a minted artifact', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + await increaseBlockchainTime(); + await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); + + const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); + const tokenUri = await world.contract.tokenURI(artifactsOnPlanet[0]); + + const networkId = hre.network.config.chainId; + const contractAddress = world.contract.address; + + expect(tokenUri).to.eq( + `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + + artifactsOnPlanet[0] + ); + }); + + it("should not be able to deposit an artifact you don't own", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // user1 moves artifact and withdraws + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); + + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + + // user2 should not be able to deposit artifact + await expect( + world.user2Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) + ).to.be.revertedWith('you can only deposit artifacts you own'); + }); + + it('should be able to move an artifact from a planet you own', async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + + // ruins should have artifact, spawn planet should not. + expect(artifactsOnRuins.length).to.eq(1); + expect(artifactsOnSpawn.length).to.eq(0); + + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); + + // move artifact; check that artifact is placed on voyage + const moveTx = await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ); + const moveReceipt = await moveTx.wait(); + const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued + const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); + expect(artifactPreArrival.voyageId).to.eq(voyageId); + expect(artifactPreArrival.locationId).to.eq(0); + + // when moving, both the ruins and the spawn planet should not have artifacts + artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + expect(artifactsOnRuins.length).to.eq(0); + expect(artifactsOnSpawn.length).to.eq(0); + + // fast forward to arrival + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); + + // check artifact is on the new planet + const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); + expect(artifactPostArrival.voyageId).to.eq(0); + expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); + artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + expect(artifactsOnRuins.length).to.eq(0); + expect(artifactsOnSpawn.length).to.eq(1); + }); + + it('should not be able to move more than some max amount of artifacts to a planet', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + const maxArtifactsOnPlanet = 4; + for (let i = 0; i <= maxArtifactsOnPlanet; i++) { + // place an artifact on the trading post + const newTokenId = hexToBigNumber(i + 1 + ''); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, + rarity: 1, + biome: 1, + artifactType: 5, + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + + // wait for the planet to fill up and download its stats + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); + const tradingPost2Planet = await world.user1Core.planets(LVL3_SPACETIME_1.id); + + if (i > maxArtifactsOnPlanet) { + await expect( + world.user1Core.move( + ...makeMoveArgs( + LVL3_SPACETIME_1, + LVL0_PLANET_DEAD_SPACE, + 0, + tradingPost2Planet.population.toNumber() - 1, + 0, + newTokenId + ) + ) + ).to.be.revertedWith( + 'the planet you are moving an artifact to can have at most 5 artifacts on it' + ); + } else { + // move the artifact from the trading post + await world.user1Core.move( + ...makeMoveArgs( + LVL3_SPACETIME_1, + LVL0_PLANET_DEAD_SPACE, + 0, + tradingPost2Planet.population.toNumber() - 1, + 0, + newTokenId + ) + ); + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(LVL0_PLANET_DEAD_SPACE.id); + const artifactsOnPlanet = await getArtifactsOnPlanet(world, LVL0_PLANET_DEAD_SPACE.id); + expect(artifactsOnPlanet.length).to.eq(i + 1); + } + } + }); + + it("should be able to conquer another player's planet and move their artifact", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); + + const artifactPlanetPopCap = ( + await world.contract.planets(ARTIFACT_PLANET_1.id) + ).populationCap.toNumber(); + + await world.user1Core.move( + ...makeMoveArgs( + ARTIFACT_PLANET_1, + SPAWN_PLANET_1, + 10, + Math.floor(artifactPlanetPopCap * 0.999), // if only 0.99 it's still untakeable, bc high def + 0 + ) + ); + + // steal planet + await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, ARTIFACT_PLANET_1, 0, 50000, 0)); + await increaseBlockchainTime(); + + // move artifact + await world.user2Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, newArtifactId) + ); + await increaseBlockchainTime(); + + // verify that artifact was moved + await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); + const artifacts = await getArtifactsOwnedBy(world.contract, world.user2.address); + + expect(artifacts.length).to.be.equal(1); + }); + + it('not be able to prospect for an artifact on planets that are not ruins', async function () { + await expect(world.user1Core.prospectPlanet(SPAWN_PLANET_1.id)).to.be.revertedWith( + "you can't find an artifact on this planet" + ); + }); + + it('should mint randomly', async function () { + // This can take upwards of 90000ms in CI + this.timeout(0); + + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + /* eslint-disable @typescript-eslint/no-explicit-any */ + let artifacts: any; + let prevLocation = SPAWN_PLANET_1; + + for (let i = 0; i < 20; i++) { + // byte #8 is 18_16 = 24_10 so it's a ruins planet + const randomHex = + `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + + (i >= 10 ? i.toString()[0] : 0) + + '' + + (i % 10); + + const planetWithArtifactLoc = new TestLocation({ + hex: randomHex, + perlin: SPACE_PERLIN, + distFromOrigin: 1998, + }); + + await world.contract.adminInitializePlanet( + planetWithArtifactLoc.id, + planetWithArtifactLoc.perlin + ); + + await world.contract.adminGiveSpaceShip( + planetWithArtifactLoc.id, + world.user1.address, + ArtifactType.ShipGear + ); + + await increaseBlockchainTime(); + + await world.user1Core.move(...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0)); // move 80000 from asteroids but 160000 from ruins since ruins are higher level + await increaseBlockchainTime(); + + await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); + await increaseBlockchainTime(); + + await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); + await increaseBlockchainTime(); + + const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); + const artifactId = artifactsOnPlanet[0].id; + + await world.user1Core.move( + ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) + ); + await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); + artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); + + expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra + expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); + expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); + + prevLocation = planetWithArtifactLoc; + } + + const artifactTypeSet = new Set(); + + for (let i = 0; i < artifacts.length; i++) { + artifactTypeSet.add(artifacts[i].artifactType); + } + + expect(artifactTypeSet.size).to.be.greaterThan(1); + }); + + it('should not mint an artifact on the same planet twice', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + await increaseBlockchainTime(); + await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); + await increaseBlockchainTime(); + await expect( + world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) + ).to.be.revertedWith('artifact already minted from this planet'); + }); + + it('should not be able to move an activated artifact', async function () { + const artifactId = await createArtifactOnPlanet( + world.contract, + world.user1.address, + ARTIFACT_PLANET_1, + ArtifactType.Monolith + ); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); + + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, artifactId) + ) + ).to.be.revertedWith('you cannot take an activated artifact off a planet'); + }); + + it("should not be able to move an artifact from a planet it's not on", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); + + // move artifact + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ); + + // try moving artifact again; should fail + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ) + ).to.be.revertedWith('this artifact was not present on this planet'); + + // try moving nonexistent artifact + await expect( + world.user1Core.move(...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345)) + ).to.be.revertedWith('this artifact was not present on this planet'); + }); + + describe('trading post', function () { + it('should be able to withdraw from / deposit onto trading posts you own', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_3); + await increaseBlockchainTime(); + + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // move artifact to LVL3_SPACETIME_1 + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); + await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); + + // artifact should be on LVL3_SPACETIME_1 + let artifact = await world.contract.getArtifactById(newArtifactId); + let artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); + let artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); + await expect(artifact.locationId).to.eq(LVL3_SPACETIME_1.id); + await expect(artifactsOnTP1.length).to.eq(1); + await expect(artifactsOnTP2.length).to.eq(0); + + // withdraw from LVL3_SPACETIME_1 + await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + + // artifact should be on voyage + artifact = await world.contract.getArtifactById(newArtifactId); + artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); + artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); + await expect(artifact.locationId).to.eq(0); + await expect(artifactsOnTP1.length).to.eq(0); + await expect(artifactsOnTP2.length).to.eq(0); + + // deposit onto LVL3_SPACETIME_3 + await world.user1Core.depositArtifact(LVL3_SPACETIME_3.id, newArtifactId); + + // artifact should be on LVL3_SPACETIME_3 + artifact = await world.contract.getArtifactById(newArtifactId); + artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); + artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); + await expect(artifact.locationId).to.eq(LVL3_SPACETIME_3.id); + await expect(artifactsOnTP1.length).to.eq(0); + await expect(artifactsOnTP2.length).to.eq(1); + }); + + it("should not be able to withdraw from / deposit onto trading post you don't own", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // move artifact + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); + + // user2 should not be able to withdraw from LVL3_SPACETIME_1 + await expect( + world.user2Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) + ).to.be.revertedWith('you can only withdraw from a planet you own'); + + // user1 should not be able to deposit onto LVL3_SPACETIME_2 + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + await expect( + world.user1Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) + ).to.be.revertedWith('you can only deposit on a planet you own'); + }); + + it('should not be able to withdraw an artifact from a trading post that is not on the trading post', async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // should not be able to withdraw newArtifactId from LVL3_SPACETIME_1 + await expect( + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) + ).to.be.revertedWith('this artifact is not on this planet'); + }); + + it('should not be able to withdraw/deposit onto a planet that is not a trading post', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET); + await increaseBlockchainTime(); + + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + + // should not be able to withdraw from ruins (which are not trading posts) + await expect( + world.user2Core.withdrawArtifact(ARTIFACT_PLANET_1.id, newArtifactId) + ).to.be.revertedWith('can only withdraw from trading posts'); + + // move artifact and withdraw + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + + // should not be able to deposit onto LVL0_PLANET (which is regular planet and not trading post) + await expect( + world.user1Core.depositArtifact(LVL0_PLANET.id, newArtifactId) + ).to.be.revertedWith('can only deposit on trading posts'); + }); + + it('should not be able to withdraw/deposit a high level artifact onto low level trading post', async function () { + await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); + await increaseBlockchainTime(); // allow planets to fill up energy again + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 4, // rarity + biome: 1, // biome + artifactType: 1, + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + // deposit fails on low level trading post, succeeds on high level trading post + await expect( + world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId) + ).to.be.revertedWith('spacetime rip not high enough level to deposit this artifact'); + world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); + + // withdraw fails on low level trading post + await world.user1Core.move( + ...makeMoveArgs(LVL6_SPACETIME, LVL3_SPACETIME_1, 0, 250000000, 0, newTokenId) + ); + await expect( + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newTokenId) + ).to.be.revertedWith('spacetime rip not high enough level to withdraw this artifact'); + + // withdraw succeeds on high level post + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, LVL6_SPACETIME, 0, 500000, 0, newTokenId) + ); + await world.user1Core.withdrawArtifact(LVL6_SPACETIME.id, newTokenId); + }); + }); + + describe('wormhole', function () { + it('should increase movement speed, in both directions', async function () { + // This can take an upwards of 32000ms + this.timeout(0); + + const from = SPAWN_PLANET_1; + const to = LVL0_PLANET; + await conquerUnownedPlanet(world, world.user1Core, from, LVL3_UNOWNED_NEBULA); + await conquerUnownedPlanet(world, world.user1Core, LVL3_UNOWNED_NEBULA, LVL6_SPACETIME); + await conquerUnownedPlanet(world, world.user1Core, from, LVL0_PLANET); + + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + const artifactRarities = [1, 2, 3, 4, 5]; // 0 is unknown, so we start at 1 + const wormholeSpeedups = [2, 4, 8, 16, 32]; + + for (let i = 0; i < artifactRarities.length; i++) { + const artifactId = await createArtifactOnPlanet( + world.contract, + world.user1.address, + from, + ArtifactType.Wormhole, + { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } + ); + await world.user1Core.activateArtifact(from.id, artifactId, to.id); + + // move from planet with artifact to its wormhole destination + await increaseBlockchainTime(); + await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); + const fromPlanet = await world.contract.planets(from.id); + const planetArrivals = await world.contract.getPlanetArrivals(to.id); + const arrival = planetArrivals[0]; + const expectedTime = Math.floor( + Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanet.speed.toNumber() + ); + + expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); + + // move from the wormhole destination planet back to the planet whose wormhole is pointing at + // it + await increaseBlockchainTime(); + await world.user1Core.move(...makeMoveArgs(to, from, dist, shipsSent, silverSent)); + const fromPlanetInverted = await world.contract.planets(to.id); + const planetArrivalsInverted = await world.contract.getPlanetArrivals(from.id); + const arrivalInverted = planetArrivalsInverted[0]; + const expectedTimeInverted = Math.floor( + Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanetInverted.speed.toNumber() + ); + + expect(arrivalInverted.arrivalTime.sub(arrivalInverted.departureTime)).to.be.equal( + expectedTimeInverted + ); + + await world.user1Core.deactivateArtifact(from.id); + } + }); + + it("shouldn't transfer energy to planets that aren't owned by the sender", async function () { + const from = SPAWN_PLANET_1; + const to = LVL0_PLANET; + + // user 2 takes over a larger planet + await conquerUnownedPlanet(world, world.user2Core, SPAWN_PLANET_2, LVL3_UNOWNED_NEBULA); + + // user 1 takes over the 2nd planet + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, to); + await world.user1Core.refreshPlanet(to.id); + const toPlanet = await world.contract.planets(to.id); + expect(toPlanet.owner).to.eq(world.user1.address); + + // create a wormhole + const newTokenId = hexToBigNumber('5'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, + biome: 1, // biome + artifactType: 5, // wormhole + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + const userArtifacts = await world.contract.getPlayerArtifactIds(world.user1.address); + expect(userArtifacts[0]).to.eq(newTokenId); + + // activate the wormhole to the 2nd planet + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) + ); + await world.user1Core.activateArtifact(from.id, newTokenId, to.id); + + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await increaseBlockchainTime(); + + // user 2 takes over the wormhole's destination + const largePlanet = await world.contract.planets(LVL3_UNOWNED_NEBULA.id); + await world.user2Core.move( + ...makeMoveArgs(LVL3_UNOWNED_NEBULA, to, 10, largePlanet.populationCap.div(2), 0) + ); + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(to.id); + const toPlanetOwnedBySecond = await world.contract.planets(to.id); + expect(toPlanetOwnedBySecond.owner).to.eq(world.user2.address); + + // ok, now for the test: move from the planet with the wormhole to its wormhole target + await increaseBlockchainTime(); + await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); + + // check that the move is sped up + const fromPlanet = await world.contract.planets(from.id); + const planetArrivals = await world.contract.getPlanetArrivals(to.id); + const arrival = planetArrivals[0]; + const expectedTime = Math.floor((Math.floor(dist / 2) * 100) / fromPlanet.speed.toNumber()); + expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); + + // fast forward to the time that the arrival is scheduled to arrive + const currentTime = await getCurrentTime(); + await increaseBlockchainTime(arrival.arrivalTime.toNumber() - currentTime - 5); + await world.user1Core.refreshPlanet(to.id); + const planetPreArrival = await world.contract.planets(to.id); + const arrivalsPreArrival = await world.contract.getPlanetArrivals(to.id); + + await increaseBlockchainTime(6); + await world.user1Core.refreshPlanet(to.id); + const planetPostArrival = await world.contract.planets(to.id); + const arrivalsPostArrival = await world.contract.getPlanetArrivals(to.id); + + // expect that the arrival transfered precisely zero energy. + expect(planetPreArrival.population).to.eq(planetPostArrival.population); + expect(arrivalsPreArrival.length).to.eq(1); + expect(arrivalsPostArrival.length).to.eq(0); + }); + }); + + describe('bloom filter', function () { + it('is burnt after usage, and should fill energy and silver', async function () { + const from = SPAWN_PLANET_1; + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); + + const planetBeforeBloomFilter = await world.user1Core.planets(from.id); + expect(planetBeforeBloomFilter.population.toNumber()).to.be.lessThan( + planetBeforeBloomFilter.populationCap.toNumber() + ); + expect(planetBeforeBloomFilter.silver).to.eq(0); + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, // rarity + biome: 1, // biome + artifactType: 8, // bloom filter + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await increaseBlockchainTime(); // so that trading post can fill up to max energy + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) + ); + await world.user1Core.activateArtifact(from.id, newTokenId, 0); + + const planetAfterBloomFilter = await world.user1Core.planets(from.id); + expect(planetAfterBloomFilter.population).to.eq(planetAfterBloomFilter.populationCap); + expect(planetAfterBloomFilter.silver).to.eq(planetAfterBloomFilter.silverCap); + + const bloomFilterPostActivation = await world.contract.getArtifactById(newTokenId); + + // bloom filter is immediately deactivated after activation + expect(bloomFilterPostActivation.artifact.lastActivated).to.eq( + bloomFilterPostActivation.artifact.lastDeactivated + ); + + // bloom filter is no longer on a planet (is instead owned by contract), and so is effectively burned + expect(bloomFilterPostActivation.locationId.toString()).to.eq('0'); + }); + + it("can't be used on a planet of too high level", async function () { + this.timeout(1000 * 60); + await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE); + const from = SPAWN_PLANET_1; + + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); + + const planetBeforeBloomFilter = await world.user1Core.planets(from.id); + expect(planetBeforeBloomFilter.population.toNumber()).to.be.lessThan( + planetBeforeBloomFilter.populationCap.toNumber() + ); + expect(planetBeforeBloomFilter.silver).to.eq(0); + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, // rarity + biome: 1, // biome + artifactType: 9, // bloom filter + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await increaseBlockchainTime(); // so that trading post can fill up to max energy + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) + ); + await expect( + world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) + ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); + }); + }); + + describe('black domain', function () { + it('is burnt after usage, and prevents moves from being made to it and from it', async function () { + const to = LVL0_PLANET; + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)); + await increaseBlockchainTime(); + + await world.user1Core.refreshPlanet(to.id); + const conqueredSecondPlanet = await world.user1Core.planets(to.id); + expect(conqueredSecondPlanet.owner).to.eq(world.user1.address); + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, // rarity + biome: 1, // biome + artifactType: 9, // black domain + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move(...makeMoveArgs(LVL3_SPACETIME_1, to, 0, 500000, 0, newTokenId)); + await world.user1Core.activateArtifact(to.id, newTokenId, 0); + + // black domain is no longer on a planet (is instead owned by contract), and so is effectively burned + const blackDomainPostActivation = await world.contract.getArtifactById(newTokenId); + expect(blackDomainPostActivation.locationId.toString()).to.eq('0'); + + // check the planet is destroyed + const newPlanet = await world.user1Core.planets(to.id); + expect(newPlanet.destroyed).to.eq(true); + + await increaseBlockchainTime(); + + // moves to destroyed planets don't work + await expect( + world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)) + ).to.be.revertedWith('planet is destroyed'); + + // moves from destroyed planets also don't work + await expect( + world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)) + ).to.be.revertedWith('planet is destroyed'); + }); + + it("can't be used on a planet of too high level", async function () { + this.timeout(1000 * 60); + await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE); + const from = SPAWN_PLANET_1; + const dist = 50; + const shipsSent = 10000; + const silverSent = 0; + + await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); + + const planetBeforeBlackDomain = await world.user1Core.planets(from.id); + expect(planetBeforeBlackDomain.population.toNumber()).to.be.lessThan( + planetBeforeBlackDomain.populationCap.toNumber() + ); + expect(planetBeforeBlackDomain.silver).to.eq(0); + + const newTokenId = hexToBigNumber('1'); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, // planet id + rarity: 1, // rarity + biome: 1, // biome + artifactType: 8, // bloom filter + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await increaseBlockchainTime(); // so that trading post can fill up to max energy + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) + ); + await expect( + world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) + ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); + }); + }); + + // TODO: tests for photoid cannon and planetary shield? +}); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index ac996184..906a654c 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -1,4 +1,5 @@ import type { DarkForest } from '@dfdao/contracts/typechain'; +import { ArtifactPropertiesStructOutput } from '@dfdao/contracts/typechain/contracts/DFToken'; import { modPBigInt } from '@dfdao/hashing'; import { buildContractCallArgs, @@ -7,7 +8,15 @@ import { WhitelistSnarkInput, } from '@dfdao/snarks'; import { whitelistSnarkWasmPath, whitelistSnarkZkeyPath } from '@dfdao/snarks/node'; -import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; +import { + ArtifactRarity, + ArtifactRarityNames, + ArtifactType, + ArtifactTypeNames, + Biome, + BiomeNames, + CollectionTypeNames, +} from '@dfdao/types'; import { bigIntFromKey } from '@dfdao/whitelist'; import { mine, time } from '@nomicfoundation/hardhat-network-helpers'; import bigInt from 'big-integer'; @@ -34,6 +43,14 @@ export function hexToBigNumber(hex: string): BigNumber { return BigNumber.from(`0x${hex}`); } +export function prettyPrintToken(token: ArtifactPropertiesStructOutput) { + console.log( + `~Token~\nCollection: ${CollectionTypeNames[token.collectionType]}\nRarity: ${ + ArtifactRarityNames[token.rarity] + }\nType: ${ArtifactTypeNames[token.artifactType]}\nBiome: ${BiomeNames[token.planetBiome]}` + ); +} + export function makeRevealArgs( planetLoc: TestLocation, x: number, @@ -292,7 +309,7 @@ export async function user1MintArtifactPlanet(user1Core: DarkForest) { const findArtifactReceipt = await findArtifactTx.wait(); // 0th event is erc721 transfer (i think); 1st event is UpdateArtifact, 2nd argument of this event is artifactId const artifactId = findArtifactReceipt.events?.[1].args?.[1]; - return artifactId; + return artifactId as BigNumber; } export async function getArtifactsOwnedBy(contract: DarkForest, addr: string) { diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index 9ae8db2d..f729058c 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -96,10 +96,10 @@ const defaultInitializerValues = { CAPTURE_ZONES_PER_5000_WORLD_RADIUS: 1, SPACESHIPS: { GEAR: true, - MOTHERSHIP: true, - CRESCENT: true, - TITAN: true, - WHALE: true, + MOTHERSHIP: false, + CRESCENT: false, + TITAN: false, + WHALE: false, }, ROUND_END_REWARDS_BY_RANK: [ 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 289d1025..dc2b5e47 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -42,6 +42,7 @@ export * from './plugin'; export * from './renderer'; export * from './reveal'; export * from './setting'; +export * from './token'; export * from './transaction'; export * from './transactions'; export * from './upgrade'; diff --git a/packages/types/src/token.ts b/packages/types/src/token.ts new file mode 100644 index 00000000..258e48da --- /dev/null +++ b/packages/types/src/token.ts @@ -0,0 +1,18 @@ +import { Abstract } from './utility'; + +export type CollectionType = Abstract; + +export const CollectionType = { + Unknown: 0 as CollectionType, + Artifact: 1 as CollectionType, + Spaceship: 2 as CollectionType, +} as const; + +/** + * Mapping from CollectionType to pretty-printed names. + */ +export const CollectionTypeNames = { + [CollectionType.Unknown]: 'Unknown', + [CollectionType.Artifact]: 'Artifact', + [CollectionType.Spaceship]: 'Spaceship', +} as const; From d8a6ca7bc8e3732486f97978c9aadc8aa002e07c Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Tue, 27 Sep 2022 18:53:13 +0100 Subject: [PATCH 12/55] test: some test starting to work: --- eth/contracts/DFInitialize.sol | 8 +++--- eth/test/NewDFArtifacts.test.ts | 44 ++++++++++++++++++--------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/eth/contracts/DFInitialize.sol b/eth/contracts/DFInitialize.sol index 3ce6a411..6392275c 100644 --- a/eth/contracts/DFInitialize.sol +++ b/eth/contracts/DFInitialize.sol @@ -21,7 +21,7 @@ pragma solidity ^0.8.0; // Interface imports // Inherited storage -import {ERC721MetadataStorage} from "@solidstate/contracts/token/ERC721/metadata/ERC721MetadataStorage.sol"; +import {ERC1155MetadataStorage} from "@solidstate/contracts/token/ERC1155/metadata/ERC1155MetadataStorage.sol"; // Library imports import {WithStorage, SpaceshipConstants} from "./libraries/LibStorage.sol"; @@ -99,7 +99,7 @@ struct InitArgs { } contract DFInitialize is WithStorage { - using ERC721MetadataStorage for ERC721MetadataStorage.Layout; + using ERC1155MetadataStorage for ERC1155MetadataStorage.Layout; // You can add parameters to this function in order to pass in // data to set initialize state variables @@ -110,9 +110,7 @@ contract DFInitialize is WithStorage { ) external { // Setup the ERC721 metadata // TODO(#1925): Add name and symbol for the artifact tokens - ERC721MetadataStorage.layout().name = ""; - ERC721MetadataStorage.layout().symbol = ""; - ERC721MetadataStorage.layout().baseURI = artifactBaseURI; + ERC1155MetadataStorage.layout().baseURI = artifactBaseURI; gs().diamondAddress = address(this); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 2b21cf9a..e9b360d4 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -67,11 +67,6 @@ describe('DarkForestArtifacts', function () { const tx = await world.user1Core.refreshPlanet(ARTIFACT_PLANET_1.id); await tx.wait(); - const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - prettyPrintToken(artifactsOnPlanet[0]); - // Expect gear to be on planet. - expect(artifactsOnPlanet.length).to.be.equal(1); - // Conquer another planet for artifact storage await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET_DEAD_SPACE); @@ -83,11 +78,15 @@ describe('DarkForestArtifacts', function () { world = await loadFixture(worldFixture); }); + // Gets Artifacts but not Spaceships async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { - return await world.contract.getArtifactsOnPlanet(locationId); + return (await world.contract.getArtifactsOnPlanet(locationId)).filter( + (artifact) => artifact.artifactType < ArtifactType.ShipMothership + ); } - it.only('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { + // This test will fail if the artifact is special. + it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); const newId = await user1MintArtifactPlanet(world.user1Core); @@ -98,7 +97,7 @@ describe('DarkForestArtifacts', function () { // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - expect(artifactsOnPlanet.length).to.be.equal(2); + expect(artifactsOnPlanet.length).to.be.equal(1); // artifact should be owned by contract artifactsOnPlanet.map(async (a) => { @@ -116,7 +115,7 @@ describe('DarkForestArtifacts', function () { // await world.contract.updateArtifact(updatedArtifact); // // planet should be buffed after discovered artifact - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[1].id, 0); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); prettyPrintToken(activeArtifact); const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); @@ -156,7 +155,7 @@ describe('DarkForestArtifacts', function () { await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); - const tokenUri = await world.contract.tokenURI(artifactsOnPlanet[0]); + const tokenUri = await world.contract.uri(artifactsOnPlanet[0]); const networkId = hre.network.config.chainId; const contractAddress = world.contract.address; @@ -189,8 +188,9 @@ describe('DarkForestArtifacts', function () { let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - // ruins should have artifact, spawn planet should not. + // ruins should have 1 artifact (gear is filtered), spawn planet should not. expect(artifactsOnRuins.length).to.eq(1); + // Might fail w spaceships expect(artifactsOnSpawn.length).to.eq(0); // after finding artifact, planet's popCap might get buffed @@ -203,9 +203,12 @@ describe('DarkForestArtifacts', function () { ); const moveReceipt = await moveTx.wait(); const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued - const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); - expect(artifactPreArrival.voyageId).to.eq(voyageId); - expect(artifactPreArrival.locationId).to.eq(0); + console.log(`voyageId`, voyageId); + // confirming that artifact is on a voyage. Why is that necessary? + // TODO: Figure out how to test on voyage. + // const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); + // expect(artifactPreArrival.voyageId).to.eq(voyageId); + // expect(artifactPreArrival.locationId).to.eq(0); // when moving, both the ruins and the spawn planet should not have artifacts artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); @@ -218,9 +221,10 @@ describe('DarkForestArtifacts', function () { await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); // check artifact is on the new planet - const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); - expect(artifactPostArrival.voyageId).to.eq(0); - expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); + // TODO: Test voyage better + // const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); + // expect(artifactPostArrival.voyageId).to.eq(0); + // expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); expect(artifactsOnRuins.length).to.eq(0); @@ -319,9 +323,9 @@ describe('DarkForestArtifacts', function () { // verify that artifact was moved await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); - const artifacts = await getArtifactsOwnedBy(world.contract, world.user2.address); + const artifacts = await world.user2Core.balanceOf(world.user2.address, newArtifactId); - expect(artifacts.length).to.be.equal(1); + expect(artifacts).to.be.equal(1); }); it('not be able to prospect for an artifact on planets that are not ruins', async function () { @@ -330,7 +334,7 @@ describe('DarkForestArtifacts', function () { ); }); - it('should mint randomly', async function () { + it.skip('should mint randomly', async function () { // This can take upwards of 90000ms in CI this.timeout(0); From b34357299ef7f7b691350bbcf828cf2f7be583db Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 11:56:36 +0100 Subject: [PATCH 13/55] feat: all artifact tests passing (no photoid or planetary shield) --- eth/contracts/facets/DFAdminFacet.sol | 5 +- eth/contracts/facets/DFMoveFacet.sol | 76 +- eth/contracts/libraries/LibArtifactUtils.sol | 30 +- eth/contracts/libraries/LibGameUtils.sol | 2 +- eth/contracts/libraries/LibStorage.sol | 2 + eth/test/DFERC1155.test.ts | 2 +- eth/test/NewDFArtifacts.test.ts | 810 ++++++++++--------- eth/test/utils/TestUtils.ts | 8 +- eth/test/utils/WorldConstants.ts | 6 + 9 files changed, 487 insertions(+), 454 deletions(-) diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 971b3d6b..304f3ac8 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -167,9 +167,8 @@ contract DFAdminFacet is WithStorage { function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); - // TODO: Remove this redundant logic ? - DFArtifactFacet(address(this)).transferArtifact(artifact.id, address(this), address(this)); - LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); + // Don't put artifact on planet if no planetId given. + if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); } } diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 042f4239..bf04964e 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -14,7 +14,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, Artifact, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; contract DFMoveFacet is WithStorage { modifier notPaused() { @@ -112,11 +112,11 @@ contract DFMoveFacet is WithStorage { Upgrade memory temporaryUpgrade = LibGameUtils.defaultUpgrade(); // TODO: Add back wormhole - // (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); - // if (wormholePresent) { - // effectiveDistTimesHundred /= distModifier; - // arrivalType = ArrivalType.Wormhole; - // } + (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); + if (wormholePresent) { + effectiveDistTimesHundred /= distModifier; + arrivalType = ArrivalType.Wormhole; + } if (!_isSpaceshipMove(args)) { // TODO: Add back photoid @@ -274,41 +274,43 @@ contract DFMoveFacet is WithStorage { } /** + TODO: Fix wormhole functionality. If an active wormhole is present on the origin planet, return the modified distance between the origin and target planet. */ - // function _checkWormhole(DFPMoveArgs memory args) - // private - // view - // returns (bool wormholePresent, uint256 effectiveDistModifier) - // { - // Artifact memory relevantWormhole; - // Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - // Artifact memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); - - // // TODO: take the greater rarity of these, or disallow wormholes between planets that - // // already have a wormhole between them - // if ( - // activeArtifactFrom.isInitialized && - // activeArtifactFrom.artifactType == ArtifactType.Wormhole && - // activeArtifactFrom.wormholeTo == args.newLoc - // ) { - // relevantWormhole = activeArtifactFrom; - // } else if ( - // activeArtifactTo.isInitialized && - // activeArtifactTo.artifactType == ArtifactType.Wormhole && - // activeArtifactTo.wormholeTo == args.oldLoc - // ) { - // relevantWormhole = activeArtifactTo; - // } + function _checkWormhole(DFPMoveArgs memory args) + private + view + returns (bool wormholePresent, uint256 effectiveDistModifier) + { + wormholePresent = false; + + // Artifact memory relevantWormhole; + ArtifactProperties memory relevantWormhole; + ArtifactProperties memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + ArtifactProperties memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); + // TODO: take the greater rarity of these, or disallow wormholes between planets that + // already have a wormhole between them + if ( + activeArtifactFrom.artifactType == ArtifactType.Wormhole && + gs().planetWormholes[args.oldLoc] == args.newLoc + ) { + relevantWormhole = activeArtifactFrom; + wormholePresent = true; + } else if ( + activeArtifactTo.artifactType == ArtifactType.Wormhole && + gs().planetWormholes[args.newLoc] == args.oldLoc + ) { + relevantWormhole = activeArtifactTo; + wormholePresent = true; + } - // if (relevantWormhole.isInitialized) { - // wormholePresent = true; - // uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; - // effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; - // } - // } + if (wormholePresent) { + uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; + effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; + } + } /** If an active photoid cannon is present, return @@ -439,7 +441,7 @@ contract DFMoveFacet is WithStorage { if (args.movedArtifactId != 0) { LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); - gs().artifactIdToVoyageId[args.movedArtifactId] = gs().planetEventsCount; + // gs().artifactIdToVoyageId[args.movedArtifactId] = gs().planetEventsCount; } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index b0b77c9d..2b1d92d8 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -166,7 +166,7 @@ library LibArtifactUtils { ); if (isSpaceship(artifact.artifactType)) { - // This breaks Crescent functionality + // TODO: fix Crescent functionality // activateSpaceshipArtifact(locationId, artifactId, planet, artifact); } else { activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); @@ -220,7 +220,7 @@ library LibArtifactUtils { Planet storage planet, ArtifactProperties memory artifact ) private { - console.log("activating %s on %s", locationId, artifactId); + console.log("activating %s on %s", artifactId, locationId); require( planet.owner == msg.sender, "you must own the planet you are activating an artifact on" @@ -260,15 +260,18 @@ library LibArtifactUtils { // TODO: Wormhole is broken - // if (artifact.artifactType == ArtifactType.Wormhole) { - // require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); - // require( - // gs().planets[wormholeTo].owner == msg.sender, - // "you can only create a wormhole to a planet you own" - // ); - // require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); - // artifact.wormholeTo = wormholeTo; - if (artifact.artifactType == ArtifactType.BloomFilter) { + if (artifact.artifactType == ArtifactType.Wormhole) { + require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); + + require( + gs().planets[wormholeTo].owner == msg.sender, + "you can only create a wormhole to a planet you own" + ); + require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); + // TODO: Store some way to remember where a wormhole is. Maybe new data structure. + // artifact.wormholeTo = wormholeTo; + gs().planetWormholes[locationId] = wormholeTo; + } else if (artifact.artifactType == ArtifactType.BloomFilter) { require( 2 * uint256(artifact.rarity) >= planet.planetLevel, "artifact is not powerful enough to apply effect to this planet level" @@ -287,6 +290,8 @@ library LibArtifactUtils { if (shouldDeactivateAndBurn) { // artifact.lastDeactivated = block.timestamp; // immediately deactivate + gs().planetActiveArtifact[locationId] = 0; // immediately deactivate + // artifact, owner // TODO: We aren't updating the artifact beacuse there are no properties to change. // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract @@ -322,7 +327,8 @@ library LibArtifactUtils { ); // artifact.lastDeactivated = block.timestamp; - // artifact.wormholeTo = 0; + // LOL just pretend there is a wormhole. + gs().planetWormholes[locationId] = 0; gs().planetActiveArtifact[locationId] = 0; emit ArtifactDeactivated(msg.sender, artifact.id, locationId); // TODO: Figure out update artifact diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 081346b3..a1e899c9 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -388,7 +388,7 @@ library LibGameUtils { // internal contract book-keeping to reflect that the given artifact was // put on. note that this function does not transfer the artifact. function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { - console.log("putting %s on %s", locationId, artifactId); + console.log("putting %s on %s", artifactId, locationId); gs().planetArtifacts[locationId].push(artifactId); uint256 length = gs().planetArtifacts[locationId].length; console.log("new planet artifact id", gs().planetArtifacts[locationId][length - 1]); diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 67157ca7..a3d60631 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -49,6 +49,8 @@ struct GameStorage { mapping(uint256 => uint256[]) planetArtifacts; // TODO: Make this an array mapping(uint256 => uint256) planetActiveArtifact; + // wormhole from => to. planetWormHoles[from] = to; + mapping(uint256 => uint256) planetWormholes; mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts index c23ae107..d95344cb 100644 --- a/eth/test/DFERC1155.test.ts +++ b/eth/test/DFERC1155.test.ts @@ -59,7 +59,7 @@ describe('SolidStateERC1155', function () { expect(planetBiome).to.equal(Number(_biome)); expect(artifactType).to.equal(Number(_artifactType)); }); - it.only('logs bits for spaceship', async function () { + it('logs bits for spaceship', async function () { // Must be valid options const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types const _artifactType = ArtifactType.ShipGear; diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index e9b360d4..a9f4fdb4 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -1,4 +1,4 @@ -import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; +import { ArtifactRarity, ArtifactType, Biome, CollectionType } from '@dfdao/types'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { BigNumberish } from 'ethers'; @@ -6,7 +6,7 @@ import hre from 'hardhat'; import { TestLocation } from './utils/TestLocation'; import { conquerUnownedPlanet, - createArtifactOnPlanet, + createArtifact, getArtifactsOwnedBy, getCurrentTime, getStatSum, @@ -33,9 +33,10 @@ import { SPACE_PERLIN, SPAWN_PLANET_1, SPAWN_PLANET_2, + ZERO_PLANET, } from './utils/WorldConstants'; -describe('DarkForestArtifacts', function () { +describe.only('DarkForestArtifacts', function () { let world: World; async function worldFixture() { @@ -74,6 +75,7 @@ describe('DarkForestArtifacts', function () { } beforeEach('load fixture', async function () { + console.log(`loading world...`); this.timeout(0); world = await loadFixture(worldFixture); }); @@ -85,179 +87,192 @@ describe('DarkForestArtifacts', function () { ); } - // This test will fail if the artifact is special. - it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { - const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + describe('it tests basic artifact actions', function () { + // This test will fail if the artifact is special. + it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { + const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - const newId = await user1MintArtifactPlanet(world.user1Core); - console.log('new id', newId._hex); - const res = await world.user1Core.decodeArtifact(newId); - prettyPrintToken(res); - const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + const newId = await user1MintArtifactPlanet(world.user1Core); + console.log('new id', newId._hex); + const res = await world.user1Core.decodeArtifact(newId); + prettyPrintToken(res); + const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - // artifact and gear should be on planet. Gear is 0 and Artifact is 1. - const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - expect(artifactsOnPlanet.length).to.be.equal(1); + // artifact and gear should be on planet. Gear is 0 and Artifact is 1. + const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + expect(artifactsOnPlanet.length).to.be.equal(1); - // artifact should be owned by contract - artifactsOnPlanet.map(async (a) => { - expect(await world.contract.balanceOf(world.contract.address, a.id)).to.equal(1); - }); - - // expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); - - // let's update the planet to be one of the basic artifacts, so that - // we know it's definitely going to buff the planet in some way. also, - // this prevents the artifact from being one that requires valid parameter - // in order to activate - // const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); - // updatedArtifact.artifactType = 0; - // await world.contract.updateArtifact(updatedArtifact); - - // // planet should be buffed after discovered artifact - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); - const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); - prettyPrintToken(activeArtifact); - const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - // // planet buff should be removed after artifact deactivated - await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); - const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - expect(statSumAfterActivation).to.not.be.within(statSumInitial - 5, statSumInitial + 5); - expect(statSumAfterActivation).to.not.be.within( - statSumAfterDeactivate - 5, - statSumAfterDeactivate + 5 - ); - expect(statSumAfterDeactivate).to.be.within(statSumInitial - 5, statSumInitial + 5); - expect(statSumAfterFound).to.be.within(statSumInitial - 5, statSumInitial + 5); - }); - - it('cannot prospect multiple times, cannot find artifact more than 256 blocks after prospecting', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + // artifact should be owned by contract + artifactsOnPlanet.map(async (a) => { + expect(await world.contract.balanceOf(world.contract.address, a.id)).to.equal(1); + }); - await expect(world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id)).to.be.revertedWith( - 'this planet has already been prospected' - ); + // expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); + + // let's update the planet to be one of the basic artifacts, so that + // we know it's definitely going to buff the planet in some way. also, + // this prevents the artifact from being one that requires valid parameter + // in order to activate + // const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); + // updatedArtifact.artifactType = 0; + // await world.contract.updateArtifact(updatedArtifact); + + // // planet should be buffed after discovered artifact + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); + const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); + prettyPrintToken(activeArtifact); + const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + // // planet buff should be removed after artifact deactivated + await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); + const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + + expect(statSumAfterActivation).to.not.be.within(statSumInitial - 5, statSumInitial + 5); + expect(statSumAfterActivation).to.not.be.within( + statSumAfterDeactivate - 5, + statSumAfterDeactivate + 5 + ); + expect(statSumAfterDeactivate).to.be.within(statSumInitial - 5, statSumInitial + 5); + expect(statSumAfterFound).to.be.within(statSumInitial - 5, statSumInitial + 5); + }); - for (let i = 0; i < 256; i++) { - await increaseBlockchainTime(); - } + it('cannot prospect multiple times, cannot find artifact more than 256 blocks after prospecting', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await expect( - world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) - ).to.be.revertedWith('planet prospect expired'); - }); - - it('should return a correct token uri for a minted artifact', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); + await expect(world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id)).to.be.revertedWith( + 'this planet has already been prospected' + ); - const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); - const tokenUri = await world.contract.uri(artifactsOnPlanet[0]); + for (let i = 0; i < 256; i++) { + await increaseBlockchainTime(); + } - const networkId = hre.network.config.chainId; - const contractAddress = world.contract.address; + await expect( + world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) + ).to.be.revertedWith('planet prospect expired'); + }); - expect(tokenUri).to.eq( - `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + - artifactsOnPlanet[0] - ); - }); + it('should return a correct token uri for a minted artifact', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + await increaseBlockchainTime(); + await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); - it("should not be able to deposit an artifact you don't own", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); + const tokenUri = await world.contract.uri(artifactsOnPlanet[0]); - // user1 moves artifact and withdraws - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); + const networkId = hre.network.config.chainId; + const contractAddress = world.contract.address; - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); + expect(tokenUri).to.eq( + `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + + artifactsOnPlanet[0] + ); + }); - // user2 should not be able to deposit artifact - await expect( - world.user2Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) - ).to.be.revertedWith('you can only deposit artifacts you own'); - }); + it("should not be able to deposit an artifact you don't own", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - it('should be able to move an artifact from a planet you own', async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + // user1 moves artifact and withdraws + await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) + ); - let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - // ruins should have 1 artifact (gear is filtered), spawn planet should not. - expect(artifactsOnRuins.length).to.eq(1); - // Might fail w spaceships - expect(artifactsOnSpawn.length).to.eq(0); + // user2 should not be able to deposit artifact + await expect( + world.user2Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) + ).to.be.revertedWith('you can only deposit artifacts you own'); + }); - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); + it('should be able to move an artifact from a planet you own', async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - // move artifact; check that artifact is placed on voyage - const moveTx = await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ); - const moveReceipt = await moveTx.wait(); - const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued - console.log(`voyageId`, voyageId); - // confirming that artifact is on a voyage. Why is that necessary? - // TODO: Figure out how to test on voyage. - // const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); - // expect(artifactPreArrival.voyageId).to.eq(voyageId); - // expect(artifactPreArrival.locationId).to.eq(0); - - // when moving, both the ruins and the spawn planet should not have artifacts - artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - expect(artifactsOnRuins.length).to.eq(0); - expect(artifactsOnSpawn.length).to.eq(0); - - // fast forward to arrival - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); - - // check artifact is on the new planet - // TODO: Test voyage better - // const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); - // expect(artifactPostArrival.voyageId).to.eq(0); - // expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); - artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - expect(artifactsOnRuins.length).to.eq(0); - expect(artifactsOnSpawn.length).to.eq(1); - }); + let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - it('should not be able to move more than some max amount of artifacts to a planet', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + // ruins should have 1 artifact (gear is filtered), spawn planet should not. + expect(artifactsOnRuins.length).to.eq(1); + // Might fail w spaceships + expect(artifactsOnSpawn.length).to.eq(0); - const maxArtifactsOnPlanet = 4; - for (let i = 0; i <= maxArtifactsOnPlanet; i++) { - // place an artifact on the trading post - const newTokenId = hexToBigNumber(i + 1 + ''); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, - rarity: 1, - biome: 1, - artifactType: 5, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); - // wait for the planet to fill up and download its stats + // move artifact; check that artifact is placed on voyage + const moveTx = await world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ); + const moveReceipt = await moveTx.wait(); + const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued + const oldLocArtifacts = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + expect(oldLocArtifacts.length).to.equal(0); + // confirming that artifact is on a voyage by checking that its no longer at the old + // destination, and that its id is on the current voyage. + const arrivalData = await world.contract.getPlanetArrival(voyageId); + expect(arrivalData.carriedArtifactId).to.equal(newArtifactId); + + // when moving, both the ruins and the spawn planet should not have artifacts + artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + expect(artifactsOnRuins.length).to.eq(0); + expect(artifactsOnSpawn.length).to.eq(0); + + // fast forward to arrival await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); - const tradingPost2Planet = await world.user1Core.planets(LVL3_SPACETIME_1.id); + await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); + + // check artifact is on the new planet. Hard to test artifact is NOT on a voyage. but if it + // exists on a planet, it is not on a voyage. + artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); + artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); + expect(artifactsOnRuins.length).to.eq(0); + expect(artifactsOnSpawn.length).to.eq(1); + }); - if (i > maxArtifactsOnPlanet) { - await expect( - world.user1Core.move( + it('should not be able to move more than some max amount of artifacts to a planet', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + const maxArtifactsOnPlanet = 4; + for (let i = 0; i <= maxArtifactsOnPlanet; i++) { + // place an artifact on the trading post + const newTokenId = hexToBigNumber(i + 1 + ''); + await world.contract.createArtifact({ + tokenId: newTokenId, + discoverer: world.user1.address, + planetId: 1, + rarity: 1, + biome: 1, + artifactType: 5, + owner: world.user1.address, + controller: ZERO_ADDRESS, + }); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + + // wait for the planet to fill up and download its stats + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); + const tradingPost2Planet = await world.user1Core.planets(LVL3_SPACETIME_1.id); + + if (i > maxArtifactsOnPlanet) { + await expect( + world.user1Core.move( + ...makeMoveArgs( + LVL3_SPACETIME_1, + LVL0_PLANET_DEAD_SPACE, + 0, + tradingPost2Planet.population.toNumber() - 1, + 0, + newTokenId + ) + ) + ).to.be.revertedWith( + 'the planet you are moving an artifact to can have at most 5 artifacts on it' + ); + } else { + // move the artifact from the trading post + await world.user1Core.move( ...makeMoveArgs( LVL3_SPACETIME_1, LVL0_PLANET_DEAD_SPACE, @@ -266,193 +281,183 @@ describe('DarkForestArtifacts', function () { 0, newTokenId ) - ) - ).to.be.revertedWith( - 'the planet you are moving an artifact to can have at most 5 artifacts on it' - ); - } else { - // move the artifact from the trading post - await world.user1Core.move( - ...makeMoveArgs( - LVL3_SPACETIME_1, - LVL0_PLANET_DEAD_SPACE, - 0, - tradingPost2Planet.population.toNumber() - 1, - 0, - newTokenId - ) - ); - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(LVL0_PLANET_DEAD_SPACE.id); - const artifactsOnPlanet = await getArtifactsOnPlanet(world, LVL0_PLANET_DEAD_SPACE.id); - expect(artifactsOnPlanet.length).to.eq(i + 1); + ); + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(LVL0_PLANET_DEAD_SPACE.id); + const artifactsOnPlanet = await getArtifactsOnPlanet(world, LVL0_PLANET_DEAD_SPACE.id); + expect(artifactsOnPlanet.length).to.eq(i + 1); + } } - } - }); - - it("should be able to conquer another player's planet and move their artifact", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); - - const artifactPlanetPopCap = ( - await world.contract.planets(ARTIFACT_PLANET_1.id) - ).populationCap.toNumber(); - - await world.user1Core.move( - ...makeMoveArgs( - ARTIFACT_PLANET_1, - SPAWN_PLANET_1, - 10, - Math.floor(artifactPlanetPopCap * 0.999), // if only 0.99 it's still untakeable, bc high def - 0 - ) - ); + }); - // steal planet - await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, ARTIFACT_PLANET_1, 0, 50000, 0)); - await increaseBlockchainTime(); + it("should be able to conquer another player's planet and move their artifact", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - // move artifact - await world.user2Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, newArtifactId) - ); - await increaseBlockchainTime(); + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); - // verify that artifact was moved - await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); - const artifacts = await world.user2Core.balanceOf(world.user2.address, newArtifactId); + const artifactPlanetPopCap = ( + await world.contract.planets(ARTIFACT_PLANET_1.id) + ).populationCap.toNumber(); - expect(artifacts).to.be.equal(1); - }); + await world.user1Core.move( + ...makeMoveArgs( + ARTIFACT_PLANET_1, + SPAWN_PLANET_1, + 10, + Math.floor(artifactPlanetPopCap * 0.999), // if only 0.99 it's still untakeable, bc high def + 0 + ) + ); - it('not be able to prospect for an artifact on planets that are not ruins', async function () { - await expect(world.user1Core.prospectPlanet(SPAWN_PLANET_1.id)).to.be.revertedWith( - "you can't find an artifact on this planet" - ); - }); + // steal planet + await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, ARTIFACT_PLANET_1, 0, 50000, 0)); + await increaseBlockchainTime(); - it.skip('should mint randomly', async function () { - // This can take upwards of 90000ms in CI - this.timeout(0); + // move artifact + await world.user2Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, newArtifactId) + ); + await increaseBlockchainTime(); - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + // verify that artifact was moved + await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); + const artifacts = await world.user2Core.balanceOf(world.user2.address, newArtifactId); - /* eslint-disable @typescript-eslint/no-explicit-any */ - let artifacts: any; - let prevLocation = SPAWN_PLANET_1; - - for (let i = 0; i < 20; i++) { - // byte #8 is 18_16 = 24_10 so it's a ruins planet - const randomHex = - `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + - (i >= 10 ? i.toString()[0] : 0) + - '' + - (i % 10); - - const planetWithArtifactLoc = new TestLocation({ - hex: randomHex, - perlin: SPACE_PERLIN, - distFromOrigin: 1998, - }); + expect(artifacts).to.be.equal(1); + }); - await world.contract.adminInitializePlanet( - planetWithArtifactLoc.id, - planetWithArtifactLoc.perlin + it('not be able to prospect for an artifact on planets that are not ruins', async function () { + await expect(world.user1Core.prospectPlanet(SPAWN_PLANET_1.id)).to.be.revertedWith( + "you can't find an artifact on this planet" ); + }); + // TODO: Why do we need this test? + it.skip('should mint randomly', async function () { + // This can take upwards of 90000ms in CI + this.timeout(0); - await world.contract.adminGiveSpaceShip( - planetWithArtifactLoc.id, - world.user1.address, - ArtifactType.ShipGear - ); + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + /* eslint-disable @typescript-eslint/no-explicit-any */ + let artifacts: any; + let prevLocation = SPAWN_PLANET_1; + + for (let i = 0; i < 20; i++) { + // byte #8 is 18_16 = 24_10 so it's a ruins planet + const randomHex = + `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + + (i >= 10 ? i.toString()[0] : 0) + + '' + + (i % 10); + + const planetWithArtifactLoc = new TestLocation({ + hex: randomHex, + perlin: SPACE_PERLIN, + distFromOrigin: 1998, + }); + + await world.contract.adminInitializePlanet( + planetWithArtifactLoc.id, + planetWithArtifactLoc.perlin + ); - await increaseBlockchainTime(); + await world.contract.adminGiveSpaceShip( + planetWithArtifactLoc.id, + world.user1.address, + ArtifactType.ShipGear + ); - await world.user1Core.move(...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0)); // move 80000 from asteroids but 160000 from ruins since ruins are higher level - await increaseBlockchainTime(); + await increaseBlockchainTime(); - await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); - await increaseBlockchainTime(); + await world.user1Core.move( + ...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0) + ); // move 80000 from asteroids but 160000 from ruins since ruins are higher level + await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); - await increaseBlockchainTime(); + await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); + await increaseBlockchainTime(); - const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); - const artifactId = artifactsOnPlanet[0].id; + await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); + await increaseBlockchainTime(); - await world.user1Core.move( - ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) - ); - await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); - artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); + const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); + const artifactId = artifactsOnPlanet[0].id; - expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra - expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); - expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); + await world.user1Core.move( + ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) + ); + await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); + artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); - prevLocation = planetWithArtifactLoc; - } + expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra + expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); + expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); - const artifactTypeSet = new Set(); + prevLocation = planetWithArtifactLoc; + } - for (let i = 0; i < artifacts.length; i++) { - artifactTypeSet.add(artifacts[i].artifactType); - } + const artifactTypeSet = new Set(); - expect(artifactTypeSet.size).to.be.greaterThan(1); - }); + for (let i = 0; i < artifacts.length; i++) { + artifactTypeSet.add(artifacts[i].artifactType); + } - it('should not mint an artifact on the same planet twice', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); - await increaseBlockchainTime(); - await expect( - world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) - ).to.be.revertedWith('artifact already minted from this planet'); - }); + expect(artifactTypeSet.size).to.be.greaterThan(1); + }); - it('should not be able to move an activated artifact', async function () { - const artifactId = await createArtifactOnPlanet( - world.contract, - world.user1.address, - ARTIFACT_PLANET_1, - ArtifactType.Monolith - ); - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); + it('should not mint an artifact on the same planet twice', async function () { + await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); + await increaseBlockchainTime(); + await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); + await increaseBlockchainTime(); + await expect( + world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) + ).to.be.revertedWith('artifact already minted from this planet'); + }); - await expect( - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, artifactId) - ) - ).to.be.revertedWith('you cannot take an activated artifact off a planet'); - }); + it('should not be able to move an activated artifact', async function () { + const artifactId = await createArtifact( + world.contract, + world.user1.address, + ARTIFACT_PLANET_1, + ArtifactType.Monolith + ); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - it("should not be able to move an artifact from a planet it's not on", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, artifactId) + ) + ).to.be.revertedWith('you cannot take an activated artifact off a planet'); + }); - // move artifact - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ); + it("should not be able to move an artifact from a planet it's not on", async function () { + const newArtifactId = await user1MintArtifactPlanet(world.user1Core); + // after finding artifact, planet's popCap might get buffed + // so let it fill up again + await increaseBlockchainTime(); - // try moving artifact again; should fail - await expect( + // move artifact world.user1Core.move( ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ) - ).to.be.revertedWith('this artifact was not present on this planet'); + ); + + // try moving artifact again; should fail + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) + ) + ).to.be.revertedWith('this artifact was not present on this planet'); - // try moving nonexistent artifact - await expect( - world.user1Core.move(...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345)) - ).to.be.revertedWith('this artifact was not present on this planet'); + // try moving nonexistent artifact + await expect( + world.user1Core.move( + ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345) + ) + ).to.be.revertedWith('this artifact was not present on this planet'); + }); }); describe('trading post', function () { @@ -469,21 +474,18 @@ describe('DarkForestArtifacts', function () { await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); // artifact should be on LVL3_SPACETIME_1 - let artifact = await world.contract.getArtifactById(newArtifactId); let artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); let artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(LVL3_SPACETIME_1.id); + await expect(artifactsOnTP1.find((a) => a.id === newArtifactId)); await expect(artifactsOnTP1.length).to.eq(1); await expect(artifactsOnTP2.length).to.eq(0); // withdraw from LVL3_SPACETIME_1 await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - // artifact should be on voyage - artifact = await world.contract.getArtifactById(newArtifactId); + // artifact should not be on any planet. artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(0); await expect(artifactsOnTP1.length).to.eq(0); await expect(artifactsOnTP2.length).to.eq(0); @@ -491,10 +493,9 @@ describe('DarkForestArtifacts', function () { await world.user1Core.depositArtifact(LVL3_SPACETIME_3.id, newArtifactId); // artifact should be on LVL3_SPACETIME_3 - artifact = await world.contract.getArtifactById(newArtifactId); artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(LVL3_SPACETIME_3.id); + await expect(artifactsOnTP1.find((a) => a.id === newArtifactId)); await expect(artifactsOnTP1.length).to.eq(0); await expect(artifactsOnTP2.length).to.eq(1); }); @@ -555,17 +556,15 @@ describe('DarkForestArtifacts', function () { await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); await increaseBlockchainTime(); // allow planets to fill up energy again - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 4, // rarity - biome: 1, // biome - artifactType: 1, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.Monolith, + CollectionType.Artifact, + { rarity: ArtifactRarity.Legendary, biome: Biome.OCEAN } + ); + // deposit fails on low level trading post, succeeds on high level trading post await expect( world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId) @@ -589,7 +588,7 @@ describe('DarkForestArtifacts', function () { }); describe('wormhole', function () { - it('should increase movement speed, in both directions', async function () { + it('should increase movement speed, in both directions, and decrease after deactivation', async function () { // This can take an upwards of 32000ms this.timeout(0); @@ -607,14 +606,19 @@ describe('DarkForestArtifacts', function () { const wormholeSpeedups = [2, 4, 8, 16, 32]; for (let i = 0; i < artifactRarities.length; i++) { - const artifactId = await createArtifactOnPlanet( + const artifactId = await createArtifact( world.contract, world.user1.address, from, ArtifactType.Wormhole, + CollectionType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); + prettyPrintToken(await world.contract.decodeArtifact(artifactId)); await world.user1Core.activateArtifact(from.id, artifactId, to.id); + // Confirm womrhole is active + const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(from.id); + expect(activeArtifact.id).to.equal(artifactId); // move from planet with artifact to its wormhole destination await increaseBlockchainTime(); @@ -626,7 +630,7 @@ describe('DarkForestArtifacts', function () { Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanet.speed.toNumber() ); - expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); + expect(arrival.arrivalTime.sub(arrival.departureTime).toNumber()).to.be.equal(expectedTime); // move from the wormhole destination planet back to the planet whose wormhole is pointing at // it @@ -644,6 +648,19 @@ describe('DarkForestArtifacts', function () { ); await world.user1Core.deactivateArtifact(from.id); + + // Move from planet with artifact to destination and expect speed is not boosted. + await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); + const fromPlanetAfterDActivate = await world.contract.planets(from.id); + const planetArrivalsAfterDActivate = await world.contract.getPlanetArrivals(to.id); + const arrivalAfterDActivate = planetArrivalsAfterDActivate[0]; + const expectedTimeAfterDActivate = Math.floor( + Math.floor(dist * 100) / fromPlanetAfterDActivate.speed.toNumber() + ); + + expect( + arrivalAfterDActivate.arrivalTime.sub(arrivalAfterDActivate.departureTime).toNumber() + ).to.be.equal(expectedTimeAfterDActivate); } }); @@ -661,26 +678,24 @@ describe('DarkForestArtifacts', function () { expect(toPlanet.owner).to.eq(world.user1.address); // create a wormhole - const newTokenId = hexToBigNumber('5'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, - biome: 1, // biome - artifactType: 5, // wormhole - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - const userArtifacts = await world.contract.getPlayerArtifactIds(world.user1.address); - expect(userArtifacts[0]).to.eq(newTokenId); + const artifactId = await createArtifact( + world.contract, + world.user1.address, + from, + ArtifactType.Wormhole, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + + const userWormholeBalance = await world.contract.balanceOf(world.user1.address, artifactId); + expect(userWormholeBalance).to.eq(1); // activate the wormhole to the 2nd planet - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, artifactId); await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) + ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, artifactId) ); - await world.user1Core.activateArtifact(from.id, newTokenId, to.id); + await world.user1Core.activateArtifact(from.id, artifactId, to.id); const dist = 50; const shipsSent = 10000; @@ -743,17 +758,16 @@ describe('DarkForestArtifacts', function () { ); expect(planetBeforeBloomFilter.silver).to.eq(0); - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 8, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.BloomFilter, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -765,15 +779,13 @@ describe('DarkForestArtifacts', function () { expect(planetAfterBloomFilter.population).to.eq(planetAfterBloomFilter.populationCap); expect(planetAfterBloomFilter.silver).to.eq(planetAfterBloomFilter.silverCap); - const bloomFilterPostActivation = await world.contract.getArtifactById(newTokenId); + const artifactsOnRipAfterBurn = await world.contract.getArtifactsOnPlanet(SPAWN_PLANET_1.id); // bloom filter is immediately deactivated after activation - expect(bloomFilterPostActivation.artifact.lastActivated).to.eq( - bloomFilterPostActivation.artifact.lastDeactivated - ); + expect(artifactsOnRipAfterBurn.length).to.equal(0); // bloom filter is no longer on a planet (is instead owned by contract), and so is effectively burned - expect(bloomFilterPostActivation.locationId.toString()).to.eq('0'); + // expect(bloomFilterPostActivation.locationId.toString()).to.eq('0'); }); it("can't be used on a planet of too high level", async function () { @@ -793,17 +805,16 @@ describe('DarkForestArtifacts', function () { ); expect(planetBeforeBloomFilter.silver).to.eq(0); - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 9, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.BloomFilter, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -829,24 +840,23 @@ describe('DarkForestArtifacts', function () { const conqueredSecondPlanet = await world.user1Core.planets(to.id); expect(conqueredSecondPlanet.owner).to.eq(world.user1.address); - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 9, // black domain - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.BlackDomain, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move(...makeMoveArgs(LVL3_SPACETIME_1, to, 0, 500000, 0, newTokenId)); await world.user1Core.activateArtifact(to.id, newTokenId, 0); - // black domain is no longer on a planet (is instead owned by contract), and so is effectively burned - const blackDomainPostActivation = await world.contract.getArtifactById(newTokenId); - expect(blackDomainPostActivation.locationId.toString()).to.eq('0'); + // black domain is no longer on a planet (is instead owned by contract), and so is effectively + // burned + const artifactsOnRipAfterBurn = await world.contract.getArtifactsOnPlanet(to.id); + expect(artifactsOnRipAfterBurn.length).to.equal(0); // check the planet is destroyed const newPlanet = await world.user1Core.planets(to.id); @@ -881,17 +891,15 @@ describe('DarkForestArtifacts', function () { ); expect(planetBeforeBlackDomain.silver).to.eq(0); - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 8, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.BlackDomain, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -903,5 +911,13 @@ describe('DarkForestArtifacts', function () { }); }); + describe('planetary shield', function () { + // TODO ... + }); + + describe('photoid cannon', function () { + // TODO ... + }); + // TODO: tests for photoid cannon and planetary shield? }); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 906a654c..2788618d 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -15,6 +15,7 @@ import { ArtifactTypeNames, Biome, BiomeNames, + CollectionType, CollectionTypeNames, } from '@dfdao/types'; import { bigIntFromKey } from '@dfdao/whitelist'; @@ -319,18 +320,19 @@ export async function getArtifactsOwnedBy(contract: DarkForest, addr: string) { ); } -export async function createArtifactOnPlanet( +export async function createArtifact( contract: DarkForest, owner: string, planet: TestLocation, type: ArtifactType, + collectionType = CollectionType.Artifact, { rarity, biome }: { rarity?: ArtifactRarity; biome?: Biome } = {} ) { rarity ||= ArtifactRarity.Common; biome ||= Biome.FOREST; - const tokenId = hexToBigNumber(Math.floor(Math.random() * 10000000000).toString(16)); - + const tokenId = await contract.encodeArtifact(collectionType, rarity, type, biome); + console.log(`tokenId`, tokenId); await contract.adminGiveArtifact({ tokenId, discoverer: owner, diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index f729058c..83174ecd 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -391,6 +391,12 @@ export const ADMIN_PLANET = new TestLocation({ distFromOrigin: 1998, }); +export const ZERO_PLANET = new TestLocation({ + hex: '0000000000000000000000000000000000000000000000000000000000000069', + perlin: NEBULA_PERLIN, + distFromOrigin: 1998, +}); + // not under difficulty threshold export const ADMIN_PLANET_CLOAKED = new TestLocation({ hex: '0100000000000000000000000000000000000000000000000000000000000069', From 0f75db8ae6d82390732b317b5e9a300ecdf0e0cf Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 12:25:39 +0100 Subject: [PATCH 14/55] move tests work almost but need to totally redo spaceships --- eth/contracts/facets/DFMoveFacet.sol | 4 +- eth/contracts/libraries/LibArtifactUtils.sol | 3 +- eth/contracts/libraries/LibStorage.sol | 1 + eth/test/DFArtifacts.test.ts | 889 ------------------- eth/test/DFMove.test.ts | 32 +- eth/test/DFSpaceShips.test.ts | 6 +- eth/test/NewDFArtifacts.test.ts | 2 +- eth/test/utils/WorldConstants.ts | 8 +- 8 files changed, 30 insertions(+), 915 deletions(-) delete mode 100644 eth/test/DFArtifacts.test.ts diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index bf04964e..b8dbe04f 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; // Contract imports import {DFVerifierFacet} from "./DFVerifierFacet.sol"; +import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Library imports import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; @@ -199,7 +200,8 @@ contract DFMoveFacet is WithStorage { require(args.popMoved == 0, "ship moves must move 0 energy"); require(args.silverMoved == 0, "ship moves must move 0 silver"); require( - gs().artifacts[args.movedArtifactId].controller == msg.sender, + // TODO: better name for doesArtifactExist function + DFArtifactFacet(address(this)).doesArtifactExist(msg.sender, args.movedArtifactId), "you can only move your own ships" ); } else { diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 2b1d92d8..ff484c46 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -70,6 +70,7 @@ library LibArtifactUtils { uint8(shipType), uint8(Biome.Unknown) ); + // TODO: Use struct naming convetion for readability DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( tokenId, msg.sender, @@ -77,7 +78,7 @@ library LibArtifactUtils { ArtifactRarity.Unknown, Biome.Unknown, shipType, - address(this), + owner, // Player is owner of new ship owner ); diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index a3d60631..3c7b7a90 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -51,6 +51,7 @@ struct GameStorage { mapping(uint256 => uint256) planetActiveArtifact; // wormhole from => to. planetWormHoles[from] = to; mapping(uint256 => uint256) planetWormholes; + // spaceShip owners uint256 => address or balanceOf(owner,id) mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts deleted file mode 100644 index b07bd8d8..00000000 --- a/eth/test/DFArtifacts.test.ts +++ /dev/null @@ -1,889 +0,0 @@ -import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { expect } from 'chai'; -import { BigNumberish } from 'ethers'; -import hre from 'hardhat'; -import { TestLocation } from './utils/TestLocation'; -import { - conquerUnownedPlanet, - createArtifactOnPlanet, - getArtifactsOwnedBy, - getCurrentTime, - getStatSum, - hexToBigNumber, - increaseBlockchainTime, - makeFindArtifactArgs, - makeInitArgs, - makeMoveArgs, - user1MintArtifactPlanet, - ZERO_ADDRESS, -} from './utils/TestUtils'; -import { defaultWorldFixture, World } from './utils/TestWorld'; -import { - ARTIFACT_PLANET_1, - LVL0_PLANET, - LVL0_PLANET_DEAD_SPACE, - LVL3_SPACETIME_1, - LVL3_SPACETIME_2, - LVL3_SPACETIME_3, - LVL3_UNOWNED_NEBULA, - LVL4_UNOWNED_DEEP_SPACE, - LVL6_SPACETIME, - SPACE_PERLIN, - SPAWN_PLANET_1, - SPAWN_PLANET_2, -} from './utils/WorldConstants'; - -describe('DarkForestArtifacts', function () { - let world: World; - - async function worldFixture() { - const world = await loadFixture(defaultWorldFixture); - - // Initialize player - await world.user1Core.initializePlayer(...makeInitArgs(SPAWN_PLANET_1)); - await world.user1Core.giveSpaceShips(SPAWN_PLANET_1.id); - await world.user2Core.initializePlayer(...makeInitArgs(SPAWN_PLANET_2)); - - // Conquer initial planets - //// Player 1 - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, ARTIFACT_PLANET_1); - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); - //// Player 2 - await conquerUnownedPlanet(world, world.user2Core, SPAWN_PLANET_2, LVL3_SPACETIME_2); - await increaseBlockchainTime(); - - // Move the Gear ship into position - const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (a) => a.artifact.artifactType === ArtifactType.ShipGear - ); - const gearId = gearShip?.artifact.id; - await world.user1Core.move( - ...makeMoveArgs(SPAWN_PLANET_1, ARTIFACT_PLANET_1, 100, 0, 0, gearId) - ); - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(ARTIFACT_PLANET_1.id); - - // Conquer another planet for artifact storage - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET_DEAD_SPACE); - - return world; - } - - beforeEach('load fixture', async function () { - this.timeout(0); - world = await loadFixture(worldFixture); - }); - - async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { - return (await world.contract.getArtifactsOnPlanet(locationId)) - .map((metadata) => metadata.artifact) - .filter((artifact) => artifact.artifactType < ArtifactType.ShipMothership); - } - - it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { - const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - await user1MintArtifactPlanet(world.user1Core); - - const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - // artifact should be on planet - const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - expect(artifactsOnPlanet.length).to.be.equal(1); - - // artifact should be owned by contract - expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); - - // let's update the planet to be one of the basic artifacts, so that - // we know it's definitely going to buff the planet in some way. also, - // this prevents the artifact from being one that requires valid parameter - // in order to activate - const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); - updatedArtifact.artifactType = 0; - await world.contract.updateArtifact(updatedArtifact); - - // planet should be buffed after discovered artifact - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); - const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - // planet buff should be removed after artifact deactivated - await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); - const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - - expect(statSumAfterActivation).to.not.be.within(statSumInitial - 5, statSumInitial + 5); - expect(statSumAfterActivation).to.not.be.within( - statSumAfterDeactivate - 5, - statSumAfterDeactivate + 5 - ); - expect(statSumAfterDeactivate).to.be.within(statSumInitial - 5, statSumInitial + 5); - expect(statSumAfterFound).to.be.within(statSumInitial - 5, statSumInitial + 5); - }); - - it('cannot prospect multiple times, cannot find artifact more than 256 blocks after prospecting', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - - await expect(world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id)).to.be.revertedWith( - 'this planet has already been prospected' - ); - - for (let i = 0; i < 256; i++) { - await increaseBlockchainTime(); - } - - await expect( - world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) - ).to.be.revertedWith('planet prospect expired'); - }); - - it('should return a correct token uri for a minted artifact', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); - - const artifactsOnPlanet = await world.contract.planetArtifacts(ARTIFACT_PLANET_1.id); - const tokenUri = await world.contract.tokenURI(artifactsOnPlanet[0]); - - const networkId = hre.network.config.chainId; - const contractAddress = world.contract.address; - - expect(tokenUri).to.eq( - `https://nft-test.zkga.me/token-uri/artifact/${networkId}-${contractAddress}/` + - artifactsOnPlanet[0] - ); - }); - - it("should not be able to deposit an artifact you don't own", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // user1 moves artifact and withdraws - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - - // user2 should not be able to deposit artifact - await expect( - world.user2Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) - ).to.be.revertedWith('you can only deposit artifacts you own'); - }); - - it('should be able to move an artifact from a planet you own', async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - let artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - let artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - - // ruins should have artifact, spawn planet should not. - expect(artifactsOnRuins.length).to.eq(1); - expect(artifactsOnSpawn.length).to.eq(0); - - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); - - // move artifact; check that artifact is placed on voyage - const moveTx = await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ); - const moveReceipt = await moveTx.wait(); - const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued - const artifactPreArrival = await world.contract.getArtifactById(newArtifactId); - expect(artifactPreArrival.voyageId).to.eq(voyageId); - expect(artifactPreArrival.locationId).to.eq(0); - - // when moving, both the ruins and the spawn planet should not have artifacts - artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - expect(artifactsOnRuins.length).to.eq(0); - expect(artifactsOnSpawn.length).to.eq(0); - - // fast forward to arrival - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(SPAWN_PLANET_1.id); - - // check artifact is on the new planet - const artifactPostArrival = await world.contract.getArtifactById(newArtifactId); - expect(artifactPostArrival.voyageId).to.eq(0); - expect(artifactPostArrival.locationId).to.eq(SPAWN_PLANET_1.id); - artifactsOnRuins = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); - artifactsOnSpawn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); - expect(artifactsOnRuins.length).to.eq(0); - expect(artifactsOnSpawn.length).to.eq(1); - }); - - it('should not be able to move more than some max amount of artifacts to a planet', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); - - const maxArtifactsOnPlanet = 4; - for (let i = 0; i <= maxArtifactsOnPlanet; i++) { - // place an artifact on the trading post - const newTokenId = hexToBigNumber(i + 1 + ''); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, - rarity: 1, - biome: 1, - artifactType: 5, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - - // wait for the planet to fill up and download its stats - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); - const tradingPost2Planet = await world.user1Core.planets(LVL3_SPACETIME_1.id); - - if (i > maxArtifactsOnPlanet) { - await expect( - world.user1Core.move( - ...makeMoveArgs( - LVL3_SPACETIME_1, - LVL0_PLANET_DEAD_SPACE, - 0, - tradingPost2Planet.population.toNumber() - 1, - 0, - newTokenId - ) - ) - ).to.be.revertedWith( - 'the planet you are moving an artifact to can have at most 5 artifacts on it' - ); - } else { - // move the artifact from the trading post - await world.user1Core.move( - ...makeMoveArgs( - LVL3_SPACETIME_1, - LVL0_PLANET_DEAD_SPACE, - 0, - tradingPost2Planet.population.toNumber() - 1, - 0, - newTokenId - ) - ); - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(LVL0_PLANET_DEAD_SPACE.id); - const artifactsOnPlanet = await getArtifactsOnPlanet(world, LVL0_PLANET_DEAD_SPACE.id); - expect(artifactsOnPlanet.length).to.eq(i + 1); - } - } - }); - - it("should be able to conquer another player's planet and move their artifact", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); - - const artifactPlanetPopCap = ( - await world.contract.planets(ARTIFACT_PLANET_1.id) - ).populationCap.toNumber(); - - await world.user1Core.move( - ...makeMoveArgs( - ARTIFACT_PLANET_1, - SPAWN_PLANET_1, - 10, - Math.floor(artifactPlanetPopCap * 0.999), // if only 0.99 it's still untakeable, bc high def - 0 - ) - ); - - // steal planet - await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, ARTIFACT_PLANET_1, 0, 50000, 0)); - await increaseBlockchainTime(); - - // move artifact - await world.user2Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_2, 0, 50000, 0, newArtifactId) - ); - await increaseBlockchainTime(); - - // verify that artifact was moved - await world.user2Core.withdrawArtifact(LVL3_SPACETIME_2.id, newArtifactId); - const artifacts = await getArtifactsOwnedBy(world.contract, world.user2.address); - - expect(artifacts.length).to.be.equal(1); - }); - - it('not be able to prospect for an artifact on planets that are not ruins', async function () { - await expect(world.user1Core.prospectPlanet(SPAWN_PLANET_1.id)).to.be.revertedWith( - "you can't find an artifact on this planet" - ); - }); - - it('should mint randomly', async function () { - // This can take upwards of 90000ms in CI - this.timeout(0); - - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); - - /* eslint-disable @typescript-eslint/no-explicit-any */ - let artifacts: any; - let prevLocation = SPAWN_PLANET_1; - - for (let i = 0; i < 20; i++) { - // byte #8 is 18_16 = 24_10 so it's a ruins planet - const randomHex = - `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + - (i >= 10 ? i.toString()[0] : 0) + - '' + - (i % 10); - - const planetWithArtifactLoc = new TestLocation({ - hex: randomHex, - perlin: SPACE_PERLIN, - distFromOrigin: 1998, - }); - - await world.contract.adminInitializePlanet( - planetWithArtifactLoc.id, - planetWithArtifactLoc.perlin - ); - - await world.contract.adminGiveSpaceShip( - planetWithArtifactLoc.id, - world.user1.address, - ArtifactType.ShipGear - ); - - await increaseBlockchainTime(); - - await world.user1Core.move(...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0)); // move 80000 from asteroids but 160000 from ruins since ruins are higher level - await increaseBlockchainTime(); - - await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); - await increaseBlockchainTime(); - - await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); - await increaseBlockchainTime(); - - const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); - const artifactId = artifactsOnPlanet[0].id; - - await world.user1Core.move( - ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) - ); - await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); - artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); - - expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra - expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); - expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); - - prevLocation = planetWithArtifactLoc; - } - - const artifactTypeSet = new Set(); - - for (let i = 0; i < artifacts.length; i++) { - artifactTypeSet.add(artifacts[i].artifactType); - } - - expect(artifactTypeSet.size).to.be.greaterThan(1); - }); - - it('should not mint an artifact on the same planet twice', async function () { - await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); - await increaseBlockchainTime(); - await world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); - await increaseBlockchainTime(); - await expect( - world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) - ).to.be.revertedWith('artifact already minted from this planet'); - }); - - it('should not be able to move an activated artifact', async function () { - const artifactId = await createArtifactOnPlanet( - world.contract, - world.user1.address, - ARTIFACT_PLANET_1, - ArtifactType.Monolith - ); - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - - await expect( - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, artifactId) - ) - ).to.be.revertedWith('you cannot take an activated artifact off a planet'); - }); - - it("should not be able to move an artifact from a planet it's not on", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - // after finding artifact, planet's popCap might get buffed - // so let it fill up again - await increaseBlockchainTime(); - - // move artifact - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ); - - // try moving artifact again; should fail - await expect( - world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) - ) - ).to.be.revertedWith('this artifact was not present on this planet'); - - // try moving nonexistent artifact - await expect( - world.user1Core.move(...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345)) - ).to.be.revertedWith('this artifact was not present on this planet'); - }); - - describe('trading post', function () { - it('should be able to withdraw from / deposit onto trading posts you own', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_3); - await increaseBlockchainTime(); - - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // move artifact to LVL3_SPACETIME_1 - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - await world.user1Core.refreshPlanet(LVL3_SPACETIME_1.id); - - // artifact should be on LVL3_SPACETIME_1 - let artifact = await world.contract.getArtifactById(newArtifactId); - let artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); - let artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(LVL3_SPACETIME_1.id); - await expect(artifactsOnTP1.length).to.eq(1); - await expect(artifactsOnTP2.length).to.eq(0); - - // withdraw from LVL3_SPACETIME_1 - await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - - // artifact should be on voyage - artifact = await world.contract.getArtifactById(newArtifactId); - artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); - artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(0); - await expect(artifactsOnTP1.length).to.eq(0); - await expect(artifactsOnTP2.length).to.eq(0); - - // deposit onto LVL3_SPACETIME_3 - await world.user1Core.depositArtifact(LVL3_SPACETIME_3.id, newArtifactId); - - // artifact should be on LVL3_SPACETIME_3 - artifact = await world.contract.getArtifactById(newArtifactId); - artifactsOnTP1 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_1.id); - artifactsOnTP2 = await world.contract.getArtifactsOnPlanet(LVL3_SPACETIME_3.id); - await expect(artifact.locationId).to.eq(LVL3_SPACETIME_3.id); - await expect(artifactsOnTP1.length).to.eq(0); - await expect(artifactsOnTP2.length).to.eq(1); - }); - - it("should not be able to withdraw from / deposit onto trading post you don't own", async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // move artifact - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - - // user2 should not be able to withdraw from LVL3_SPACETIME_1 - await expect( - world.user2Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) - ).to.be.revertedWith('you can only withdraw from a planet you own'); - - // user1 should not be able to deposit onto LVL3_SPACETIME_2 - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - await expect( - world.user1Core.depositArtifact(LVL3_SPACETIME_2.id, newArtifactId) - ).to.be.revertedWith('you can only deposit on a planet you own'); - }); - - it('should not be able to withdraw an artifact from a trading post that is not on the trading post', async function () { - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // should not be able to withdraw newArtifactId from LVL3_SPACETIME_1 - await expect( - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) - ).to.be.revertedWith('this artifact is not on this planet'); - }); - - it('should not be able to withdraw/deposit onto a planet that is not a trading post', async function () { - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL0_PLANET); - await increaseBlockchainTime(); - - const newArtifactId = await user1MintArtifactPlanet(world.user1Core); - - // should not be able to withdraw from ruins (which are not trading posts) - await expect( - world.user2Core.withdrawArtifact(ARTIFACT_PLANET_1.id, newArtifactId) - ).to.be.revertedWith('can only withdraw from trading posts'); - - // move artifact and withdraw - await world.user1Core.move( - ...makeMoveArgs(ARTIFACT_PLANET_1, LVL3_SPACETIME_1, 0, 50000, 0, newArtifactId) - ); - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId); - - // should not be able to deposit onto LVL0_PLANET (which is regular planet and not trading post) - await expect( - world.user1Core.depositArtifact(LVL0_PLANET.id, newArtifactId) - ).to.be.revertedWith('can only deposit on trading posts'); - }); - - it('should not be able to withdraw/deposit a high level artifact onto low level trading post', async function () { - await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); - await increaseBlockchainTime(); // allow planets to fill up energy again - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 4, // rarity - biome: 1, // biome - artifactType: 1, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - // deposit fails on low level trading post, succeeds on high level trading post - await expect( - world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId) - ).to.be.revertedWith('spacetime rip not high enough level to deposit this artifact'); - world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); - - // withdraw fails on low level trading post - await world.user1Core.move( - ...makeMoveArgs(LVL6_SPACETIME, LVL3_SPACETIME_1, 0, 250000000, 0, newTokenId) - ); - await expect( - world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newTokenId) - ).to.be.revertedWith('spacetime rip not high enough level to withdraw this artifact'); - - // withdraw succeeds on high level post - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, LVL6_SPACETIME, 0, 500000, 0, newTokenId) - ); - await world.user1Core.withdrawArtifact(LVL6_SPACETIME.id, newTokenId); - }); - }); - - describe('wormhole', function () { - it('should increase movement speed, in both directions', async function () { - // This can take an upwards of 32000ms - this.timeout(0); - - const from = SPAWN_PLANET_1; - const to = LVL0_PLANET; - await conquerUnownedPlanet(world, world.user1Core, from, LVL3_UNOWNED_NEBULA); - await conquerUnownedPlanet(world, world.user1Core, LVL3_UNOWNED_NEBULA, LVL6_SPACETIME); - await conquerUnownedPlanet(world, world.user1Core, from, LVL0_PLANET); - - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - const artifactRarities = [1, 2, 3, 4, 5]; // 0 is unknown, so we start at 1 - const wormholeSpeedups = [2, 4, 8, 16, 32]; - - for (let i = 0; i < artifactRarities.length; i++) { - const artifactId = await createArtifactOnPlanet( - world.contract, - world.user1.address, - from, - ArtifactType.Wormhole, - { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } - ); - await world.user1Core.activateArtifact(from.id, artifactId, to.id); - - // move from planet with artifact to its wormhole destination - await increaseBlockchainTime(); - await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); - const fromPlanet = await world.contract.planets(from.id); - const planetArrivals = await world.contract.getPlanetArrivals(to.id); - const arrival = planetArrivals[0]; - const expectedTime = Math.floor( - Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanet.speed.toNumber() - ); - - expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); - - // move from the wormhole destination planet back to the planet whose wormhole is pointing at - // it - await increaseBlockchainTime(); - await world.user1Core.move(...makeMoveArgs(to, from, dist, shipsSent, silverSent)); - const fromPlanetInverted = await world.contract.planets(to.id); - const planetArrivalsInverted = await world.contract.getPlanetArrivals(from.id); - const arrivalInverted = planetArrivalsInverted[0]; - const expectedTimeInverted = Math.floor( - Math.floor((dist * 100) / wormholeSpeedups[i]) / fromPlanetInverted.speed.toNumber() - ); - - expect(arrivalInverted.arrivalTime.sub(arrivalInverted.departureTime)).to.be.equal( - expectedTimeInverted - ); - - await world.user1Core.deactivateArtifact(from.id); - } - }); - - it("shouldn't transfer energy to planets that aren't owned by the sender", async function () { - const from = SPAWN_PLANET_1; - const to = LVL0_PLANET; - - // user 2 takes over a larger planet - await conquerUnownedPlanet(world, world.user2Core, SPAWN_PLANET_2, LVL3_UNOWNED_NEBULA); - - // user 1 takes over the 2nd planet - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, to); - await world.user1Core.refreshPlanet(to.id); - const toPlanet = await world.contract.planets(to.id); - expect(toPlanet.owner).to.eq(world.user1.address); - - // create a wormhole - const newTokenId = hexToBigNumber('5'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, - biome: 1, // biome - artifactType: 5, // wormhole - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - const userArtifacts = await world.contract.getPlayerArtifactIds(world.user1.address); - expect(userArtifacts[0]).to.eq(newTokenId); - - // activate the wormhole to the 2nd planet - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) - ); - await world.user1Core.activateArtifact(from.id, newTokenId, to.id); - - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await increaseBlockchainTime(); - - // user 2 takes over the wormhole's destination - const largePlanet = await world.contract.planets(LVL3_UNOWNED_NEBULA.id); - await world.user2Core.move( - ...makeMoveArgs(LVL3_UNOWNED_NEBULA, to, 10, largePlanet.populationCap.div(2), 0) - ); - await increaseBlockchainTime(); - await world.user1Core.refreshPlanet(to.id); - const toPlanetOwnedBySecond = await world.contract.planets(to.id); - expect(toPlanetOwnedBySecond.owner).to.eq(world.user2.address); - - // ok, now for the test: move from the planet with the wormhole to its wormhole target - await increaseBlockchainTime(); - await world.user1Core.move(...makeMoveArgs(from, to, dist, shipsSent, silverSent)); - - // check that the move is sped up - const fromPlanet = await world.contract.planets(from.id); - const planetArrivals = await world.contract.getPlanetArrivals(to.id); - const arrival = planetArrivals[0]; - const expectedTime = Math.floor((Math.floor(dist / 2) * 100) / fromPlanet.speed.toNumber()); - expect(arrival.arrivalTime.sub(arrival.departureTime)).to.be.equal(expectedTime); - - // fast forward to the time that the arrival is scheduled to arrive - const currentTime = await getCurrentTime(); - await increaseBlockchainTime(arrival.arrivalTime.toNumber() - currentTime - 5); - await world.user1Core.refreshPlanet(to.id); - const planetPreArrival = await world.contract.planets(to.id); - const arrivalsPreArrival = await world.contract.getPlanetArrivals(to.id); - - await increaseBlockchainTime(6); - await world.user1Core.refreshPlanet(to.id); - const planetPostArrival = await world.contract.planets(to.id); - const arrivalsPostArrival = await world.contract.getPlanetArrivals(to.id); - - // expect that the arrival transfered precisely zero energy. - expect(planetPreArrival.population).to.eq(planetPostArrival.population); - expect(arrivalsPreArrival.length).to.eq(1); - expect(arrivalsPostArrival.length).to.eq(0); - }); - }); - - describe('bloom filter', function () { - it('is burnt after usage, and should fill energy and silver', async function () { - const from = SPAWN_PLANET_1; - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); - - const planetBeforeBloomFilter = await world.user1Core.planets(from.id); - expect(planetBeforeBloomFilter.population.toNumber()).to.be.lessThan( - planetBeforeBloomFilter.populationCap.toNumber() - ); - expect(planetBeforeBloomFilter.silver).to.eq(0); - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 8, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await increaseBlockchainTime(); // so that trading post can fill up to max energy - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, newTokenId) - ); - await world.user1Core.activateArtifact(from.id, newTokenId, 0); - - const planetAfterBloomFilter = await world.user1Core.planets(from.id); - expect(planetAfterBloomFilter.population).to.eq(planetAfterBloomFilter.populationCap); - expect(planetAfterBloomFilter.silver).to.eq(planetAfterBloomFilter.silverCap); - - const bloomFilterPostActivation = await world.contract.getArtifactById(newTokenId); - - // bloom filter is immediately deactivated after activation - expect(bloomFilterPostActivation.artifact.lastActivated).to.eq( - bloomFilterPostActivation.artifact.lastDeactivated - ); - - // bloom filter is no longer on a planet (is instead owned by contract), and so is effectively burned - expect(bloomFilterPostActivation.locationId.toString()).to.eq('0'); - }); - - it("can't be used on a planet of too high level", async function () { - this.timeout(1000 * 60); - await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE); - const from = SPAWN_PLANET_1; - - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); - - const planetBeforeBloomFilter = await world.user1Core.planets(from.id); - expect(planetBeforeBloomFilter.population.toNumber()).to.be.lessThan( - planetBeforeBloomFilter.populationCap.toNumber() - ); - expect(planetBeforeBloomFilter.silver).to.eq(0); - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 9, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await increaseBlockchainTime(); // so that trading post can fill up to max energy - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) - ); - await expect( - world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) - ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); - }); - }); - - describe('black domain', function () { - it('is burnt after usage, and prevents moves from being made to it and from it', async function () { - const to = LVL0_PLANET; - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)); - await increaseBlockchainTime(); - - await world.user1Core.refreshPlanet(to.id); - const conqueredSecondPlanet = await world.user1Core.planets(to.id); - expect(conqueredSecondPlanet.owner).to.eq(world.user1.address); - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 9, // black domain - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move(...makeMoveArgs(LVL3_SPACETIME_1, to, 0, 500000, 0, newTokenId)); - await world.user1Core.activateArtifact(to.id, newTokenId, 0); - - // black domain is no longer on a planet (is instead owned by contract), and so is effectively burned - const blackDomainPostActivation = await world.contract.getArtifactById(newTokenId); - expect(blackDomainPostActivation.locationId.toString()).to.eq('0'); - - // check the planet is destroyed - const newPlanet = await world.user1Core.planets(to.id); - expect(newPlanet.destroyed).to.eq(true); - - await increaseBlockchainTime(); - - // moves to destroyed planets don't work - await expect( - world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)) - ).to.be.revertedWith('planet is destroyed'); - - // moves from destroyed planets also don't work - await expect( - world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, to, dist, shipsSent, silverSent)) - ).to.be.revertedWith('planet is destroyed'); - }); - - it("can't be used on a planet of too high level", async function () { - this.timeout(1000 * 60); - await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE); - const from = SPAWN_PLANET_1; - const dist = 50; - const shipsSent = 10000; - const silverSent = 0; - - await world.user1Core.move(...makeMoveArgs(from, LVL0_PLANET, dist, shipsSent, silverSent)); - - const planetBeforeBlackDomain = await world.user1Core.planets(from.id); - expect(planetBeforeBlackDomain.population.toNumber()).to.be.lessThan( - planetBeforeBlackDomain.populationCap.toNumber() - ); - expect(planetBeforeBlackDomain.silver).to.eq(0); - - const newTokenId = hexToBigNumber('1'); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, // planet id - rarity: 1, // rarity - biome: 1, // biome - artifactType: 8, // bloom filter - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); - await increaseBlockchainTime(); // so that trading post can fill up to max energy - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, LVL4_UNOWNED_DEEP_SPACE, 0, 500000, 0, newTokenId) - ); - await expect( - world.user1Core.activateArtifact(LVL4_UNOWNED_DEEP_SPACE.id, newTokenId, 0) - ).to.be.revertedWith('artifact is not powerful enough to apply effect to this planet level'); - }); - }); - - // TODO: tests for photoid cannon and planetary shield? -}); diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index e6cd9925..becd5421 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers'; import { ethers } from 'hardhat'; import { conquerUnownedPlanet, - createArtifactOnPlanet, + createArtifact, increaseBlockchainTime, makeInitArgs, makeMoveArgs, @@ -28,7 +28,7 @@ import { const { BigNumber: BN } = ethers; -describe('DarkForestMove', function () { +describe.only('DarkForestMove', function () { describe('moving space ships', function () { let world: World; @@ -51,7 +51,7 @@ describe('DarkForestMove', function () { }); it('allows controller to move ships to places they do not own with infinite distance', async function () { - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; const shipId = ship.id; await world.user1Core.move( @@ -68,7 +68,7 @@ describe('DarkForestMove', function () { await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL1_ASTEROID_NEBULA); await increaseBlockchainTime(); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; const shipId = ship.id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_NEBULA, 1000, 0, 0, shipId) @@ -83,23 +83,24 @@ describe('DarkForestMove', function () { }); it('should not allow you to move enemy ships on your own planet', async function () { - const ship = (await world.user2Core.getArtifactsOnPlanet(SPAWN_PLANET_2.id))[0].artifact; - const shipId = ship.id; + const user2shipId = (await world.user2Core.getArtifactsOnPlanet(SPAWN_PLANET_2.id))[0].id; await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL2_PLANET_SPACE); await world.user2Core.move( - ...makeMoveArgs(SPAWN_PLANET_2, LVL2_PLANET_SPACE, 1000, 0, 0, shipId) + ...makeMoveArgs(SPAWN_PLANET_2, LVL2_PLANET_SPACE, 1000, 0, 0, user2shipId) ); await increaseBlockchainTime(); await expect( - world.user1Core.move(...makeMoveArgs(LVL2_PLANET_SPACE, SPAWN_PLANET_2, 1000, 0, 0, shipId)) + world.user1Core.move( + ...makeMoveArgs(LVL2_PLANET_SPACE, SPAWN_PLANET_2, 1000, 0, 0, user2shipId) + ) ).to.be.revertedWith('you can only move your own ships'); }); - it('should not consume a photoid if moving a ship off a planet with one activated', async function () { - const artifactId = await createArtifactOnPlanet( + it.skip('should not consume a photoid if moving a ship off a planet with one activated', async function () { + const artifactId = await createArtifact( world.contract, world.user1.address, SPAWN_PLANET_1, @@ -109,8 +110,7 @@ describe('DarkForestMove', function () { await world.user1Core.activateArtifact(SPAWN_PLANET_1.id, artifactId, 0); await increaseBlockchainTime(); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; - const shipId = ship.id; + const shipId = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, shipId) @@ -118,7 +118,7 @@ describe('DarkForestMove', function () { await world.contract.refreshPlanet(SPAWN_PLANET_1.id); const activePhotoid = (await world.contract.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).filter( - (a) => a.artifact.artifactType === ArtifactType.PhotoidCannon + (a) => a.artifactType === ArtifactType.PhotoidCannon )[0]; // If the photoid is not there, it was used during ship move expect(activePhotoid).to.not.eq(undefined); @@ -921,7 +921,7 @@ describe('move rate limits', function () { ArtifactType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 10, 0, 0, ship.id) @@ -934,7 +934,7 @@ describe('move rate limits', function () { ArtifactType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; await expect( world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 1000, 0, 0, ship.id)) @@ -960,7 +960,7 @@ describe('move rate limits', function () { ArtifactType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].artifact; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 10, 0, 0, ship.id) diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index 3615d698..cfe7e165 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -16,7 +16,7 @@ import { SPAWN_PLANET_2, } from './utils/WorldConstants'; -describe('Space Ships', function () { +describe.only('DarkForestSpaceShips', function () { let world: World; async function worldFixture() { @@ -62,8 +62,8 @@ describe('Space Ships', function () { it('pauses energy regeneration on planets', async function () { const titan = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (a) => a.artifact.artifactType === ArtifactType.ShipTitan - )?.artifact; + (a) => a.artifactType === ArtifactType.ShipTitan + ); // Move Titan to planet await world.user1Core.move( diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index a9f4fdb4..9c4c7d0d 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -36,7 +36,7 @@ import { ZERO_PLANET, } from './utils/WorldConstants'; -describe.only('DarkForestArtifacts', function () { +describe('DarkForestArtifacts', function () { let world: World; async function worldFixture() { diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index 83174ecd..cb70844d 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -96,10 +96,10 @@ const defaultInitializerValues = { CAPTURE_ZONES_PER_5000_WORLD_RADIUS: 1, SPACESHIPS: { GEAR: true, - MOTHERSHIP: false, - CRESCENT: false, - TITAN: false, - WHALE: false, + MOTHERSHIP: true, + CRESCENT: true, + TITAN: true, + WHALE: true, }, ROUND_END_REWARDS_BY_RANK: [ 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, From 8523cc100e1814e1ec7d4092c9552c8586eff879 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 12:32:05 +0100 Subject: [PATCH 15/55] feat: spaceships work --- eth/contracts/libraries/LibArtifactUtils.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index ff484c46..b101595b 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -62,8 +62,8 @@ library LibArtifactUtils { ArtifactType shipType ) public returns (uint256) { require(shipType <= ArtifactType.ShipTitan && shipType >= ArtifactType.ShipMothership); - - uint256 id = uint256(keccak256(abi.encodePacked(planetId, gs().miscNonce++))); + // require(gs().miscNonce < MAX UINT 128) but won't happen. + uint128 id = uint128(gs().miscNonce++); uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( uint8(CollectionType.Spaceship), uint8(ArtifactRarity.Unknown), @@ -72,7 +72,7 @@ library LibArtifactUtils { ); // TODO: Use struct naming convetion for readability DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - tokenId, + tokenId + id, // makes each spaceship unique but keeps generic properties. msg.sender, planetId, ArtifactRarity.Unknown, From 8af4bce166377401a9b11d2b38cea86aeb043484 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 16:15:07 +0100 Subject: [PATCH 16/55] feat: all tests pass + photoid. Need crescent and planetary shield then clean up then clinet --- eth/contracts/facets/DFGetterFacet.sol | 17 +++ eth/contracts/facets/DFMoveFacet.sol | 53 +++++---- eth/contracts/libraries/LibArtifactUtils.sol | 111 ++++++++----------- eth/contracts/libraries/LibGameUtils.sol | 8 +- eth/contracts/libraries/LibStorage.sol | 3 +- eth/test/DFMove.test.ts | 2 +- eth/test/DFSpaceShips.test.ts | 2 +- eth/test/NewDFArtifacts.test.ts | 64 ++++++++++- eth/test/utils/WorldConstants.ts | 2 +- 9 files changed, 166 insertions(+), 96 deletions(-) diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 008a36ee..38f38fc3 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -316,6 +316,10 @@ contract DFGetterFacet is WithStorage { return ret; } + function getArtifactActivationTimeOnPlanet(uint256 locationId) public view returns (uint256) { + return gs().planetArtifactActivationTime[locationId]; + } + // function getArtifactById(uint256 artifactId) // public // view @@ -356,6 +360,19 @@ contract DFGetterFacet is WithStorage { return ret; } + function artifactExistsOnPlanet(uint256 locationId, uint256 artifactId) + public + view + returns (bool) + { + bool hasArtifact = false; + uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + for (uint256 i = 0; i < artifactIds.length; i++) { + if (artifactIds[i] == artifactId) hasArtifact = true; + } + return hasArtifact; + } + function getActiveArtifactOnPlanet(uint256 locationId) public view diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index b8dbe04f..3f1be14d 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -16,6 +16,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {ArrivalData, ArrivalType, Artifact, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { modifier notPaused() { @@ -121,11 +122,12 @@ contract DFMoveFacet is WithStorage { if (!_isSpaceshipMove(args)) { // TODO: Add back photoid - // (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); - // if (photoidPresent) { - // temporaryUpgrade = newTempUpgrade; - // arrivalType = ArrivalType.Photoid; - // } + (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); + if (photoidPresent) { + temporaryUpgrade = newTempUpgrade; + arrivalType = ArrivalType.Photoid; + console.log("doing photoid move"); + } } _removeSpaceshipEffectsFromOriginPlanet(args); @@ -152,6 +154,7 @@ contract DFMoveFacet is WithStorage { _transferPlanetSpaceJunkToPlayer(args); } + // updates oldLoc speed if photoid. LibGameUtils._buffPlanet(args.oldLoc, temporaryUpgrade); uint256 travelTime = effectiveDistTimesHundred / gs().planets[args.oldLoc].speed; @@ -319,22 +322,22 @@ contract DFMoveFacet is WithStorage { the upgrade that should be applied to the origin planet. */ - // function _checkPhotoid(DFPMoveArgs memory args) - // private - // returns (bool photoidPresent, Upgrade memory temporaryUpgrade) - // { - // Artifact memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - // if ( - // activeArtifactFrom.isInitialized && - // activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && - // block.timestamp - activeArtifactFrom.lastActivated >= - // gameConstants().PHOTOID_ACTIVATION_DELAY - // ) { - // photoidPresent = true; - // LibArtifactUtils.deactivateArtifact(args.oldLoc); - // temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); - // } - // } + function _checkPhotoid(DFPMoveArgs memory args) + private + returns (bool photoidPresent, Upgrade memory temporaryUpgrade) + { + ArtifactProperties memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + if ( + activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && + block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= + gameConstants().PHOTOID_ACTIVATION_DELAY + ) { + photoidPresent = true; + console.log("photoid present? %s", LibGameUtils.getActiveArtifact(args.oldLoc).id > 0); + LibArtifactUtils.deactivateArtifact(args.oldLoc); + temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); + } + } function _abandonPlanet(DFPMoveArgs memory args) private @@ -440,10 +443,12 @@ contract DFMoveFacet is WithStorage { carriedArtifactId: args.movedArtifactId, distance: args.actualDist }); - - if (args.movedArtifactId != 0) { + // Photoids are burned _checkPhotoid, so don't remove twice + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).decodeArtifact( + args.movedArtifactId + ); + if (args.movedArtifactId != 0 && artifact.artifactType != ArtifactType.PhotoidCannon) { LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); - // gs().artifactIdToVoyageId[args.movedArtifactId] = gs().planetEventsCount; } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index b101595b..ff2f2ec7 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; // External contract imports import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; +import {DFGetterFacet} from "../facets/DFGetterFacet.sol"; // Library imports import {LibGameUtils} from "./LibGameUtils.sol"; @@ -176,43 +177,43 @@ library LibArtifactUtils { // artifact.activations++; } - function activateSpaceshipArtifact( - uint256 locationId, - uint256 artifactId, - Planet storage planet, - Artifact storage artifact - ) private { - if (artifact.artifactType == ArtifactType.ShipCrescent) { - require(artifact.activations == 0, "crescent cannot be activated more than once"); - - require( - planet.planetType != PlanetType.SILVER_MINE, - "cannot turn a silver mine into a silver mine" - ); - - require(planet.owner == address(0), "can only activate crescent on unowned planets"); - require(planet.planetLevel >= 1, "planet level must be more than one"); - - artifact.lastActivated = block.timestamp; - artifact.lastDeactivated = block.timestamp; - - if (planet.silver == 0) { - planet.silver = 1; - Planet memory defaultPlanet = LibGameUtils._defaultPlanet( - locationId, - planet.planetLevel, - PlanetType.SILVER_MINE, - planet.spaceType, - gameConstants().TIME_FACTOR_HUNDREDTHS - ); - - planet.silverGrowth = defaultPlanet.silverGrowth; - } - - planet.planetType = PlanetType.SILVER_MINE; - emit ArtifactActivated(msg.sender, locationId, artifactId); - } - } + // function activateSpaceshipArtifact( + // uint256 locationId, + // uint256 artifactId, + // Planet storage planet, + // ArtifactProperties memory artifact + // ) private { + // if (artifact.artifactType == ArtifactType.ShipCrescent) { + // require(artifact.activations == 0, "crescent cannot be activated more than once"); + + // require( + // planet.planetType != PlanetType.SILVER_MINE, + // "cannot turn a silver mine into a silver mine" + // ); + + // require(planet.owner == address(0), "can only activate crescent on unowned planets"); + // require(planet.planetLevel >= 1, "planet level must be more than one"); + + // artifact.lastActivated = block.timestamp; + // artifact.lastDeactivated = block.timestamp; + + // if (planet.silver == 0) { + // planet.silver = 1; + // Planet memory defaultPlanet = LibGameUtils._defaultPlanet( + // locationId, + // planet.planetLevel, + // PlanetType.SILVER_MINE, + // planet.spaceType, + // gameConstants().TIME_FACTOR_HUNDREDTHS + // ); + + // planet.silverGrowth = defaultPlanet.silverGrowth; + // } + + // planet.planetType = PlanetType.SILVER_MINE; + // emit ArtifactActivated(msg.sender, locationId, artifactId); + // } + // } function activateNonSpaceshipArtifact( uint256 locationId, @@ -240,22 +241,9 @@ library LibArtifactUtils { "this artifact is not on this planet" ); - // Unknown is the 0th one, Monolith is the 1st, and so on. - // TODO v0.6: consider photoid canon - uint256[10] memory artifactCooldownsHours = [uint256(24), 0, 0, 0, 0, 4, 4, 24, 24, 24]; - // TODO: Cooldown is broken - // require( - // artifact.lastDeactivated + - // artifactCooldownsHours[uint256(artifact.artifactType)] * - // 60 * - // 60 < - // block.timestamp, - // "this artifact is on a cooldown" - // ); - bool shouldDeactivateAndBurn = false; - // artifact.lastActivated = block.timestamp; + gs().planetArtifactActivationTime[locationId] = block.timestamp; gs().planetActiveArtifact[locationId] = artifactId; emit ArtifactActivated(msg.sender, locationId, artifactId); @@ -291,19 +279,13 @@ library LibArtifactUtils { if (shouldDeactivateAndBurn) { // artifact.lastDeactivated = block.timestamp; // immediately deactivate - gs().planetActiveArtifact[locationId] = 0; // immediately deactivate + gs().planetActiveArtifact[locationId] = 0; // immediately remove activate artifact - // artifact, owner - // TODO: We aren't updating the artifact beacuse there are no properties to change. - // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); // save artifact state immediately, because _takeArtifactOffPlanet will access pull it from tokens contract emit ArtifactDeactivated(msg.sender, locationId, artifactId); // burn it after use. will be owned by contract but not on a planet anyone can control LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); - } else { - // TODO: We aren't updating the artifact beacuse there are no properties to change. - // artifact, owner - // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); } + console.log("buffing planet"); // this is fine even tho some artifacts are immediately deactivated, because // those artifacts do not buff the planet. @@ -331,15 +313,20 @@ library LibArtifactUtils { // LOL just pretend there is a wormhole. gs().planetWormholes[locationId] = 0; gs().planetActiveArtifact[locationId] = 0; + gs().planetArtifactActivationTime[locationId] = 0; + emit ArtifactDeactivated(msg.sender, artifact.id, locationId); - // TODO: Figure out update artifact - // DFArtifactFacet(address(this)).updateArtifact(artifact, address(this)); bool shouldBurn = artifact.artifactType == ArtifactType.PlanetaryShield || artifact.artifactType == ArtifactType.PhotoidCannon; if (shouldBurn) { + console.log("burning %s", artifact.id); + console.log( + "artifact is on planet? %s", + DFGetterFacet(address(this)).artifactExistsOnPlanet(locationId, artifact.id) + ); // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(artifact.id, locationId); + LibGameUtils._takeArtifactOffPlanet(locationId, artifact.id); } LibGameUtils._debuffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index a1e899c9..455964b1 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -237,7 +237,11 @@ library LibGameUtils { }); } - function timeDelayUpgrade(Artifact memory artifact) public pure returns (Upgrade memory) { + function timeDelayUpgrade(ArtifactProperties memory artifact) + public + pure + returns (Upgrade memory) + { if (artifact.artifactType == ArtifactType.PhotoidCannon) { uint256[6] memory range = [uint256(100), 200, 200, 200, 200, 200]; uint256[6] memory speedBoosts = [uint256(100), 500, 1000, 1500, 2000, 2500]; @@ -464,7 +468,7 @@ library LibGameUtils { view returns (ArtifactProperties memory) { - console.log("searching for %s on %s", locationId, artifactId); + console.log("searching for %s on %s", artifactId, locationId); console.log( "%s artifacts on planet %s", gs().planetArtifacts[locationId].length, diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 3c7b7a90..f6eeb51e 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -51,7 +51,8 @@ struct GameStorage { mapping(uint256 => uint256) planetActiveArtifact; // wormhole from => to. planetWormHoles[from] = to; mapping(uint256 => uint256) planetWormholes; - // spaceShip owners uint256 => address or balanceOf(owner,id) + // planetId to timestamp. For all artifacts, but only used for photoids. + mapping(uint256 => uint256) planetArtifactActivationTime; mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index becd5421..97b7e7f4 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -28,7 +28,7 @@ import { const { BigNumber: BN } = ethers; -describe.only('DarkForestMove', function () { +describe('DarkForestMove', function () { describe('moving space ships', function () { let world: World; diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index cfe7e165..b40be076 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -16,7 +16,7 @@ import { SPAWN_PLANET_2, } from './utils/WorldConstants'; -describe.only('DarkForestSpaceShips', function () { +describe('DarkForestSpaceShips', function () { let world: World; async function worldFixture() { diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 9c4c7d0d..a6c2df1b 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -915,9 +915,65 @@ describe('DarkForestArtifacts', function () { // TODO ... }); - describe('photoid cannon', function () { - // TODO ... - }); + describe.only('photoid cannon', function () { + it('activates photoid cannon, increases move speed and rage, then burns photoid', async function () { + const to = LVL0_PLANET; + const dist = 50; + const forces = 500000; + + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.PhotoidCannon, + CollectionType.Artifact, + { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ); + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); + + // Confirm photoid cannon is activated. + const activateTx = await world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0); + const activateRct = await activateTx.wait(); + const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( + block.timestamp + ); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal( + newTokenId + ); + await increaseBlockchainTime(); + + // Make a move that uses photoid cannon + await world.user1Core.move( + ...makeMoveArgs(LVL3_SPACETIME_1, to, dist, forces, 0, newTokenId) + ); + const fromPlanet = await world.contract.planets(LVL3_SPACETIME_1.id); + const planetArrivals = await world.contract.getPlanetArrivals(to.id); + const arrival = planetArrivals[0]; + const rangeBoosts = [100, 200, 200, 200, 200, 200]; + // Divided by 100 to reflect effect on travel time. + const speedBoosts = [1, 5, 10, 15, 20, 25]; - // TODO: tests for photoid cannon and planetary shield? + const expectedTime = Math.floor( + Math.floor((dist * 100) / speedBoosts[ArtifactRarity.Common]) / fromPlanet.speed.toNumber() + ); + + expect(arrival.arrivalTime.sub(arrival.departureTime).toNumber()).to.be.equal(expectedTime); + + const range = (fromPlanet.range.toNumber() * rangeBoosts[ArtifactRarity.Common]) / 100; + const popCap = fromPlanet.populationCap.toNumber(); + const decayFactor = Math.pow(2, dist / range); + const approxArriving = forces / decayFactor - 0.05 * popCap; + + expect(planetArrivals[0].popArriving.toNumber()).to.be.above(approxArriving - 1000); + expect(planetArrivals[0].popArriving.toNumber()).to.be.below(approxArriving + 1000); + + // Confirm photoid is burned + expect((await getArtifactsOnPlanet(world, LVL3_SPACETIME_1.id)).length).to.equal(0); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal(0); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( + 0 + ); + }); + }); }); diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index cb70844d..d211bf42 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -26,7 +26,7 @@ const defaultInitializerValues = { PLANET_LEVEL_THRESHOLDS: [16777216, 4194292, 1048561, 262128, 65520, 16368, 4080, 1008, 240, 48], PLANET_RARITY: 16384, PLANET_TRANSFER_ENABLED: true, - PHOTOID_ACTIVATION_DELAY: 60 * 60 * 12, + PHOTOID_ACTIVATION_DELAY: 60 * 60 * 12, // 12 hours SPAWN_RIM_AREA: 7234560000, LOCATION_REVEAL_COOLDOWN: 60 * 60 * 24, PLANET_TYPE_WEIGHTS: [ From 19274096f854b2efbb5d10f1d6e4153bc29f7eed Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 28 Sep 2022 16:34:13 +0100 Subject: [PATCH 17/55] failing test :( --- eth/contracts/facets/DFMoveFacet.sol | 2 +- eth/contracts/libraries/LibGameUtils.sol | 1 + eth/test/NewDFArtifacts.test.ts | 23 ++++++++++++++++++----- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 3f1be14d..f9809433 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -425,7 +425,7 @@ contract DFMoveFacet is WithStorage { planet.populationCap ); bool isSpaceship = LibArtifactUtils.isSpaceship( - gs().artifacts[args.movedArtifactId].artifactType + DFArtifactFacet(address(this)).decodeArtifact(args.movedArtifactId).artifactType ); // space ship moves are implemented as 0-energy moves require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 455964b1..6e902e39 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -413,6 +413,7 @@ library LibGameUtils { function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; + console.log("%s artifacts on %s", artifactsOnThisPlanet, locationId); bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index a6c2df1b..f12ecff3 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -36,7 +36,7 @@ import { ZERO_PLANET, } from './utils/WorldConstants'; -describe('DarkForestArtifacts', function () { +describe.only('DarkForestArtifacts', function () { let world: World; async function worldFixture() { @@ -59,7 +59,6 @@ describe('DarkForestArtifacts', function () { const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( (artifact) => artifact.artifactType === ArtifactType.ShipGear ); - console.log(`gearId`, gearShip?.id._hex); const gearId = gearShip?.id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, ARTIFACT_PLANET_1, 100, 0, 0, gearId) @@ -664,7 +663,7 @@ describe('DarkForestArtifacts', function () { } }); - it("shouldn't transfer energy to planets that aren't owned by the sender", async function () { + it.only("shouldn't transfer energy to planets that aren't owned by the sender", async function () { const from = SPAWN_PLANET_1; const to = LVL0_PLANET; @@ -692,9 +691,23 @@ describe('DarkForestArtifacts', function () { // activate the wormhole to the 2nd planet await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, artifactId); + console.log('her?'); + + // Move gear bc too many artifacts on SPAWN_PLANET_1, so can't receive wormhole. + const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( + (artifact) => artifact.artifactType === ArtifactType.ShipGear + ); + console.log(`gearId`); + await world.user1Core.move( + ...makeMoveArgs(SPAWN_PLANET_1, LVL0_PLANET, 1000, 100, 0, gearShip?.id) + ); + + increaseBlockchainTime(); + await world.user1Core.move( ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, artifactId) ); + await world.user1Core.activateArtifact(from.id, artifactId, to.id); const dist = 50; @@ -779,7 +792,7 @@ describe('DarkForestArtifacts', function () { expect(planetAfterBloomFilter.population).to.eq(planetAfterBloomFilter.populationCap); expect(planetAfterBloomFilter.silver).to.eq(planetAfterBloomFilter.silverCap); - const artifactsOnRipAfterBurn = await world.contract.getArtifactsOnPlanet(SPAWN_PLANET_1.id); + const artifactsOnRipAfterBurn = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); // bloom filter is immediately deactivated after activation expect(artifactsOnRipAfterBurn.length).to.equal(0); @@ -915,7 +928,7 @@ describe('DarkForestArtifacts', function () { // TODO ... }); - describe.only('photoid cannon', function () { + describe('photoid cannon', function () { it('activates photoid cannon, increases move speed and rage, then burns photoid', async function () { const to = LVL0_PLANET; const dist = 50; From d0b6fe7edd346674698da5936e5dee81d220aaa9 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 11:35:29 +0100 Subject: [PATCH 18/55] feat: crescent test --- eth/contracts/facets/DFMoveFacet.sol | 10 ++ eth/contracts/libraries/LibArtifactUtils.sol | 79 +++++---- eth/contracts/libraries/LibGameUtils.sol | 9 +- eth/test/DFMove.test.ts | 18 +- eth/test/DFSpaceShips.test.ts | 44 ++++- eth/test/NewDFArtifacts.test.ts | 173 +++++++++---------- eth/test/utils/TestUtils.ts | 8 +- 7 files changed, 200 insertions(+), 141 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index f9809433..e6075aea 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -418,6 +418,14 @@ contract DFMoveFacet is WithStorage { function _createArrival(DFPCreateArrivalArgs memory args) private { // enter the arrival data for event id Planet memory planet = gs().planets[args.oldLoc]; + // console.log( + // "pop moved: %s dist %s range %s", + // args.popMoved, + // args.effectiveDistTimesHundred, + // uint256(planet.range) + // ); + // console.logUint(planet.populationCap); + uint256 popArriving = _getDecayedPop( args.popMoved, args.effectiveDistTimesHundred, @@ -427,7 +435,9 @@ contract DFMoveFacet is WithStorage { bool isSpaceship = LibArtifactUtils.isSpaceship( DFArtifactFacet(address(this)).decodeArtifact(args.movedArtifactId).artifactType ); + // console.log("pop arriving: %s from %s", popArriving, args.oldLoc); // space ship moves are implemented as 0-energy moves + console.log("isSpaceship: ", isSpaceship); require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); require(isSpaceship ? args.popMoved == 0 : true, "spaceship moves must be 0 energy moves"); gs().planetArrivals[gs().planetEventsCount] = ArrivalData({ diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index ff2f2ec7..6b12f027 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -169,7 +169,7 @@ library LibArtifactUtils { if (isSpaceship(artifact.artifactType)) { // TODO: fix Crescent functionality - // activateSpaceshipArtifact(locationId, artifactId, planet, artifact); + activateSpaceshipArtifact(locationId, artifactId, planet, artifact); } else { activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); } @@ -177,43 +177,46 @@ library LibArtifactUtils { // artifact.activations++; } - // function activateSpaceshipArtifact( - // uint256 locationId, - // uint256 artifactId, - // Planet storage planet, - // ArtifactProperties memory artifact - // ) private { - // if (artifact.artifactType == ArtifactType.ShipCrescent) { - // require(artifact.activations == 0, "crescent cannot be activated more than once"); - - // require( - // planet.planetType != PlanetType.SILVER_MINE, - // "cannot turn a silver mine into a silver mine" - // ); - - // require(planet.owner == address(0), "can only activate crescent on unowned planets"); - // require(planet.planetLevel >= 1, "planet level must be more than one"); - - // artifact.lastActivated = block.timestamp; - // artifact.lastDeactivated = block.timestamp; - - // if (planet.silver == 0) { - // planet.silver = 1; - // Planet memory defaultPlanet = LibGameUtils._defaultPlanet( - // locationId, - // planet.planetLevel, - // PlanetType.SILVER_MINE, - // planet.spaceType, - // gameConstants().TIME_FACTOR_HUNDREDTHS - // ); - - // planet.silverGrowth = defaultPlanet.silverGrowth; - // } - - // planet.planetType = PlanetType.SILVER_MINE; - // emit ArtifactActivated(msg.sender, locationId, artifactId); - // } - // } + function activateSpaceshipArtifact( + uint256 locationId, + uint256 artifactId, + Planet storage planet, + ArtifactProperties memory artifact + ) private { + if (artifact.artifactType == ArtifactType.ShipCrescent) { + // Burn the goddam crescent + // require(artifact.activations == 0, "crescent cannot be activated more than once"); + + require( + planet.planetType != PlanetType.SILVER_MINE, + "cannot turn a silver mine into a silver mine" + ); + + require(planet.owner == address(0), "can only activate crescent on unowned planets"); + require(planet.planetLevel >= 1, "planet level must be more than zero"); + + if (planet.silver == 0) { + planet.silver = 1; + Planet memory defaultPlanet = LibGameUtils._defaultPlanet( + locationId, + planet.planetLevel, + PlanetType.SILVER_MINE, + planet.spaceType, + gameConstants().TIME_FACTOR_HUNDREDTHS + ); + + planet.silverGrowth = defaultPlanet.silverGrowth; + } + + planet.planetType = PlanetType.SILVER_MINE; + emit ArtifactActivated(msg.sender, locationId, artifactId); + + // TODO: Why not actually burn? + // burn it after use. will be owned by contract but not on a planet anyone can control + LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); + emit ArtifactDeactivated(msg.sender, locationId, artifactId); + } + } function activateNonSpaceshipArtifact( uint256 locationId, diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 6e902e39..1cd4259d 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -413,15 +413,12 @@ library LibGameUtils { function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; + console.log("removing %s from %s", artifactId, locationId); console.log("%s artifacts on %s", artifactsOnThisPlanet, locationId); bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact( - gs().planetArtifacts[locationId][i] - ); - require( !isActivatedERC1155(locationId, artifactId), "you cannot take an activated artifact off a planet" @@ -449,10 +446,10 @@ library LibGameUtils { } function isActivatedERC1155(uint256 locationId, uint256 artifactId) public view returns (bool) { - return (gs().planetActiveArtifact[locationId] > 0); + return (gs().planetActiveArtifact[locationId] == artifactId); } - function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) public returns (bool) { + function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) public view returns (bool) { for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { return true; diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index 97b7e7f4..bafc5184 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -6,6 +6,7 @@ import { ethers } from 'hardhat'; import { conquerUnownedPlanet, createArtifact, + getArtifactsOnPlanet, increaseBlockchainTime, makeInitArgs, makeMoveArgs, @@ -99,7 +100,7 @@ describe('DarkForestMove', function () { ).to.be.revertedWith('you can only move your own ships'); }); - it.skip('should not consume a photoid if moving a ship off a planet with one activated', async function () { + it('should not consume a photoid if moving a ship off a planet with one activated', async function () { const artifactId = await createArtifact( world.contract, world.user1.address, @@ -108,18 +109,23 @@ describe('DarkForestMove', function () { ); await world.user1Core.activateArtifact(SPAWN_PLANET_1.id, artifactId, 0); + expect((await world.contract.getActiveArtifactOnPlanet(SPAWN_PLANET_1.id)).id).to.equal( + artifactId + ); + await increaseBlockchainTime(); - const shipId = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0].id; + const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).filter( + (a) => a.artifactType === ArtifactType.ShipGear + )[0]; + console.log(`gear id`, ship?.id); await world.user1Core.move( - ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, shipId) + ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, ship?.id) ); await world.contract.refreshPlanet(SPAWN_PLANET_1.id); - const activePhotoid = (await world.contract.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).filter( - (a) => a.artifactType === ArtifactType.PhotoidCannon - )[0]; + const activePhotoid = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); // If the photoid is not there, it was used during ship move expect(activePhotoid).to.not.eq(undefined); }); diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index b40be076..a38eda61 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -1,4 +1,4 @@ -import { ArtifactType } from '@dfdao/types'; +import { ArtifactType, PlanetType } from '@dfdao/types'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { @@ -6,6 +6,7 @@ import { increaseBlockchainTime, makeInitArgs, makeMoveArgs, + prettyPrintToken, } from './utils/TestUtils'; import { defaultWorldFixture, World } from './utils/TestWorld'; import { @@ -106,6 +107,47 @@ describe('DarkForestSpaceShips', function () { }); }); + describe('using the Crescent', function () { + it.only('turns planet into an asteroid and burns crescent', async function () { + const crescent = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( + (a) => a.artifactType === ArtifactType.ShipCrescent + ); + if (!crescent) throw new Error('crescent not found'); + prettyPrintToken(crescent); + + // Move Crescent to planet + await world.user1Core.move( + ...makeMoveArgs(SPAWN_PLANET_1, LVL1_PLANET_DEEP_SPACE, 1000, 0, 0, crescent.id) + ); + + await increaseBlockchainTime(); + await world.contract.refreshPlanet(LVL1_PLANET_DEEP_SPACE.id); + + const crescentNewLocId = ( + await world.contract.getArtifactsOnPlanet(LVL1_PLANET_DEEP_SPACE.id) + )[0].id; + expect(crescentNewLocId).to.equal(crescent?.id); + + const planetBeforeActivate = await world.contract.planets(LVL1_PLANET_DEEP_SPACE.id); + await world.user1Core.activateArtifact(LVL1_PLANET_DEEP_SPACE.id, crescent.id, 0); + const planetAfterActivate = await world.contract.planets(LVL1_PLANET_DEEP_SPACE.id); + // Silver is higher + expect(planetBeforeActivate.silverGrowth).to.be.lessThan(planetAfterActivate.silverGrowth); + // Crescent is no longer on planet. + expect( + (await world.contract.getArtifactsOnPlanet(LVL1_PLANET_DEEP_SPACE.id)).length + ).to.equal(0); + // Planet was planet + expect(planetBeforeActivate.planetType).to.equal(PlanetType.PLANET); + // Planet is now asteroid. + expect(planetAfterActivate.planetType).to.equal(PlanetType.SILVER_MINE); + // Cannot activate again. + await expect( + world.user1Core.activateArtifact(LVL1_PLANET_DEEP_SPACE.id, crescent.id, 0) + ).to.be.revertedWith("can't active an artifact on a planet it's not on"); + }); + }); + describe('spawning on non-home planet', async function () { let world: World; diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index f12ecff3..83a8cdb8 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -1,12 +1,12 @@ import { ArtifactRarity, ArtifactType, Biome, CollectionType } from '@dfdao/types'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { loadFixture, mine } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; -import { BigNumberish } from 'ethers'; import hre from 'hardhat'; import { TestLocation } from './utils/TestLocation'; import { conquerUnownedPlanet, createArtifact, + getArtifactsOnPlanet, getArtifactsOwnedBy, getCurrentTime, getStatSum, @@ -36,7 +36,7 @@ import { ZERO_PLANET, } from './utils/WorldConstants'; -describe.only('DarkForestArtifacts', function () { +describe('DarkForestArtifacts', function () { let world: World; async function worldFixture() { @@ -79,24 +79,24 @@ describe.only('DarkForestArtifacts', function () { world = await loadFixture(worldFixture); }); - // Gets Artifacts but not Spaceships - async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { - return (await world.contract.getArtifactsOnPlanet(locationId)).filter( - (artifact) => artifact.artifactType < ArtifactType.ShipMothership - ); - } - describe('it tests basic artifact actions', function () { // This test will fail if the artifact is special. it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - const newId = await user1MintArtifactPlanet(world.user1Core); - console.log('new id', newId._hex); - const res = await world.user1Core.decodeArtifact(newId); - prettyPrintToken(res); + const artifactId = await createArtifact( + world.contract, + world.user1.address, + ARTIFACT_PLANET_1, + ArtifactType.Colossus + ); + const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); + await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); + + prettyPrintToken(await world.user1Core.decodeArtifact(artifactId)); + // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); expect(artifactsOnPlanet.length).to.be.equal(1); @@ -106,23 +106,13 @@ describe.only('DarkForestArtifacts', function () { expect(await world.contract.balanceOf(world.contract.address, a.id)).to.equal(1); }); - // expect(artifactsOnPlanet[0].discoverer).to.eq(world.user1.address); - - // let's update the planet to be one of the basic artifacts, so that - // we know it's definitely going to buff the planet in some way. also, - // this prevents the artifact from being one that requires valid parameter - // in order to activate - // const updatedArtifact = Object.assign({}, artifactsOnPlanet[0]); - // updatedArtifact.artifactType = 0; - // await world.contract.updateArtifact(updatedArtifact); - - // // planet should be buffed after discovered artifact - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactsOnPlanet[0].id, 0); + // planet should be buffed after discovered artifact const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(ARTIFACT_PLANET_1.id); prettyPrintToken(activeArtifact); + const statSumAfterActivation = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - // // planet buff should be removed after artifact deactivated + // planet buff should be removed after artifact deactivated await world.user1Core.deactivateArtifact(ARTIFACT_PLANET_1.id); const statSumAfterDeactivate = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); @@ -142,9 +132,7 @@ describe.only('DarkForestArtifacts', function () { 'this planet has already been prospected' ); - for (let i = 0; i < 256; i++) { - await increaseBlockchainTime(); - } + await mine(256); await expect( world.user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)) @@ -663,7 +651,7 @@ describe.only('DarkForestArtifacts', function () { } }); - it.only("shouldn't transfer energy to planets that aren't owned by the sender", async function () { + it("shouldn't transfer energy to planets that aren't owned by the sender", async function () { const from = SPAWN_PLANET_1; const to = LVL0_PLANET; @@ -686,23 +674,20 @@ describe.only('DarkForestArtifacts', function () { { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - const userWormholeBalance = await world.contract.balanceOf(world.user1.address, artifactId); - expect(userWormholeBalance).to.eq(1); - - // activate the wormhole to the 2nd planet - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, artifactId); - console.log('her?'); - // Move gear bc too many artifacts on SPAWN_PLANET_1, so can't receive wormhole. - const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (artifact) => artifact.artifactType === ArtifactType.ShipGear + const crescentShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( + (artifact) => artifact.artifactType === ArtifactType.ShipCrescent ); - console.log(`gearId`); + await world.user1Core.move( - ...makeMoveArgs(SPAWN_PLANET_1, LVL0_PLANET, 1000, 100, 0, gearShip?.id) + ...makeMoveArgs(SPAWN_PLANET_1, LVL0_PLANET, 0, 0, 0, crescentShip?.id) ); - increaseBlockchainTime(); + const userWormholeBalance = await world.contract.balanceOf(world.user1.address, artifactId); + expect(userWormholeBalance).to.eq(1); + + // activate the wormhole to the 2nd planet + await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, artifactId); await world.user1Core.move( ...makeMoveArgs(LVL3_SPACETIME_1, SPAWN_PLANET_1, 0, 500000, 0, artifactId) @@ -930,63 +915,73 @@ describe.only('DarkForestArtifacts', function () { describe('photoid cannon', function () { it('activates photoid cannon, increases move speed and rage, then burns photoid', async function () { + await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); + await increaseBlockchainTime(); + const to = LVL0_PLANET; const dist = 50; - const forces = 500000; + const forces = 40000000; // Has to be big to account for - const newTokenId = await createArtifact( - world.contract, - world.user1.address, - ZERO_PLANET, - ArtifactType.PhotoidCannon, - CollectionType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } - ); - await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); - - // Confirm photoid cannon is activated. - const activateTx = await world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0); - const activateRct = await activateTx.wait(); - const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( - block.timestamp - ); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal( - newTokenId - ); - await increaseBlockchainTime(); - - // Make a move that uses photoid cannon - await world.user1Core.move( - ...makeMoveArgs(LVL3_SPACETIME_1, to, dist, forces, 0, newTokenId) - ); - const fromPlanet = await world.contract.planets(LVL3_SPACETIME_1.id); - const planetArrivals = await world.contract.getPlanetArrivals(to.id); - const arrival = planetArrivals[0]; const rangeBoosts = [100, 200, 200, 200, 200, 200]; // Divided by 100 to reflect effect on travel time. const speedBoosts = [1, 5, 10, 15, 20, 25]; + const artifactRarities = [1, 2, 3, 4, 5]; - const expectedTime = Math.floor( - Math.floor((dist * 100) / speedBoosts[ArtifactRarity.Common]) / fromPlanet.speed.toNumber() - ); + for (let i = 0; i < artifactRarities.length; i++) { + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.PhotoidCannon, + CollectionType.Artifact, + { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } + ); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); - expect(arrival.arrivalTime.sub(arrival.departureTime).toNumber()).to.be.equal(expectedTime); + await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); - const range = (fromPlanet.range.toNumber() * rangeBoosts[ArtifactRarity.Common]) / 100; - const popCap = fromPlanet.populationCap.toNumber(); - const decayFactor = Math.pow(2, dist / range); - const approxArriving = forces / decayFactor - 0.05 * popCap; + // Confirm photoid cannon is activated. + const activateTx = await world.user1Core.activateArtifact(LVL6_SPACETIME.id, newTokenId, 0); + const activateRct = await activateTx.wait(); + const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL6_SPACETIME.id)).to.equal( + block.timestamp + ); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL6_SPACETIME.id)).id).to.equal( + newTokenId + ); - expect(planetArrivals[0].popArriving.toNumber()).to.be.above(approxArriving - 1000); - expect(planetArrivals[0].popArriving.toNumber()).to.be.below(approxArriving + 1000); + await increaseBlockchainTime(); - // Confirm photoid is burned - expect((await getArtifactsOnPlanet(world, LVL3_SPACETIME_1.id)).length).to.equal(0); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal(0); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( - 0 - ); + // Make a move that uses photoid cannon + await world.user1Core.move( + ...makeMoveArgs(LVL6_SPACETIME, to, dist, forces, 0, newTokenId) + ); + const fromPlanet = await world.contract.planets(LVL6_SPACETIME.id); + const planetArrivals = await world.contract.getPlanetArrivals(to.id); + const arrival = planetArrivals[0]; + + const expectedTime = Math.floor( + Math.floor((dist * 100) / speedBoosts[i + 1]) / fromPlanet.speed.toNumber() + ); + + expect(arrival.arrivalTime.sub(arrival.departureTime).toNumber()).to.be.equal(expectedTime); + + const range = (fromPlanet.range.toNumber() * rangeBoosts[i + 1]) / 100; + const popCap = fromPlanet.populationCap.toNumber(); + const decayFactor = Math.pow(2, dist / range); + const approxArriving = forces / decayFactor - 0.05 * popCap; + + expect(planetArrivals[0].popArriving.toNumber()).to.be.above(approxArriving - 1000); + expect(planetArrivals[0].popArriving.toNumber()).to.be.below(approxArriving + 1000); + + // Confirm photoid is burned + expect((await getArtifactsOnPlanet(world, LVL6_SPACETIME.id)).length).to.equal(0); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL6_SPACETIME.id)).id).to.equal(0); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL6_SPACETIME.id)).to.equal( + 0 + ); + } }); }); }); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 2788618d..3b109b1c 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -320,6 +320,13 @@ export async function getArtifactsOwnedBy(contract: DarkForest, addr: string) { ); } +// Gets Artifacts but not Spaceships +export async function getArtifactsOnPlanet(world: World, locationId: BigNumberish) { + return (await world.contract.getArtifactsOnPlanet(locationId)).filter( + (artifact) => artifact.artifactType < ArtifactType.ShipMothership + ); +} + export async function createArtifact( contract: DarkForest, owner: string, @@ -332,7 +339,6 @@ export async function createArtifact( biome ||= Biome.FOREST; const tokenId = await contract.encodeArtifact(collectionType, rarity, type, biome); - console.log(`tokenId`, tokenId); await contract.adminGiveArtifact({ tokenId, discoverer: owner, From 44db267ea3d017756375dc558779d205092a0a74 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 11:48:10 +0100 Subject: [PATCH 19/55] feat: shield test passes --- eth/test/DFSpaceShips.test.ts | 2 +- eth/test/NewDFArtifacts.test.ts | 42 +++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index a38eda61..ccd807bb 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -108,7 +108,7 @@ describe('DarkForestSpaceShips', function () { }); describe('using the Crescent', function () { - it.only('turns planet into an asteroid and burns crescent', async function () { + it('turns planet into an asteroid and burns crescent', async function () { const crescent = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( (a) => a.artifactType === ArtifactType.ShipCrescent ); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 83a8cdb8..85643db3 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -910,11 +910,49 @@ describe('DarkForestArtifacts', function () { }); describe('planetary shield', function () { - // TODO ... + it.only('activates planetary shield, + defense, - range, then burns shield', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); + + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + LVL3_SPACETIME_1, + ArtifactType.PlanetaryShield, + CollectionType.Artifact, + { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } + ); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + + const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); + const activateTx = await world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0); + const activateRct = await activateTx.wait(); + const planetAfterActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); + + const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( + block.timestamp + ); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal( + newTokenId + ); + // Boosts are applied + expect(planetBeforeActivation.defense).to.be.lessThan(planetAfterActivation.defense); + expect(planetBeforeActivation.range).to.be.greaterThan(planetAfterActivation.range); + expect(planetBeforeActivation.speed).to.be.greaterThan(planetAfterActivation.speed); + + // Burned on deactivate + await world.user1Core.deactivateArtifact(LVL3_SPACETIME_1.id); + + expect((await getArtifactsOnPlanet(world, LVL3_SPACETIME_1.id)).length).to.equal(0); + expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal(0); + expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( + 0 + ); + }); }); describe('photoid cannon', function () { - it('activates photoid cannon, increases move speed and rage, then burns photoid', async function () { + it('activates photoid cannon, increases move speed and range, then burns photoid', async function () { await conquerUnownedPlanet(world, world.user1Core, LVL3_SPACETIME_1, LVL6_SPACETIME); await increaseBlockchainTime(); From 1023e7f15bb86e2673bbf66245dadac278a6cafd Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 12:16:52 +0100 Subject: [PATCH 20/55] fix: clean up tests a bit --- eth/contracts/facets/DFMoveFacet.sol | 2 -- eth/test/NewDFArtifacts.test.ts | 37 ++++++---------------------- eth/test/utils/TestUtils.ts | 22 ++++++++++++++++- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index e6075aea..affe1f66 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -435,9 +435,7 @@ contract DFMoveFacet is WithStorage { bool isSpaceship = LibArtifactUtils.isSpaceship( DFArtifactFacet(address(this)).decodeArtifact(args.movedArtifactId).artifactType ); - // console.log("pop arriving: %s from %s", popArriving, args.oldLoc); // space ship moves are implemented as 0-energy moves - console.log("isSpaceship: ", isSpaceship); require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); require(isSpaceship ? args.popMoved == 0 : true, "spaceship moves must be 0 energy moves"); gs().planetArrivals[gs().planetEventsCount] = ArrivalData({ diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 85643db3..98bc74a9 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -4,6 +4,7 @@ import { expect } from 'chai'; import hre from 'hardhat'; import { TestLocation } from './utils/TestLocation'; import { + activateAndConfirm, conquerUnownedPlanet, createArtifact, getArtifactsOnPlanet, @@ -16,6 +17,7 @@ import { makeInitArgs, makeMoveArgs, prettyPrintToken, + testDeactivate, user1MintArtifactPlanet, ZERO_ADDRESS, } from './utils/TestUtils'; @@ -602,10 +604,8 @@ describe('DarkForestArtifacts', function () { { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.decodeArtifact(artifactId)); - await world.user1Core.activateArtifact(from.id, artifactId, to.id); - // Confirm womrhole is active - const activeArtifact = await world.user1Core.getActiveArtifactOnPlanet(from.id); - expect(activeArtifact.id).to.equal(artifactId); + + activateAndConfirm(world.user1Core, from.id, artifactId, to.id); // move from planet with artifact to its wormhole destination await increaseBlockchainTime(); @@ -853,8 +853,7 @@ describe('DarkForestArtifacts', function () { // black domain is no longer on a planet (is instead owned by contract), and so is effectively // burned - const artifactsOnRipAfterBurn = await world.contract.getArtifactsOnPlanet(to.id); - expect(artifactsOnRipAfterBurn.length).to.equal(0); + testDeactivate(world, to.id); // check the planet is destroyed const newPlanet = await world.user1Core.planets(to.id); @@ -924,17 +923,9 @@ describe('DarkForestArtifacts', function () { prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); - const activateTx = await world.user1Core.activateArtifact(LVL3_SPACETIME_1.id, newTokenId, 0); - const activateRct = await activateTx.wait(); + await activateAndConfirm(world.user1Core, LVL3_SPACETIME_1.id, newTokenId); const planetAfterActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); - const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( - block.timestamp - ); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal( - newTokenId - ); // Boosts are applied expect(planetBeforeActivation.defense).to.be.lessThan(planetAfterActivation.defense); expect(planetBeforeActivation.range).to.be.greaterThan(planetAfterActivation.range); @@ -979,15 +970,7 @@ describe('DarkForestArtifacts', function () { await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); // Confirm photoid cannon is activated. - const activateTx = await world.user1Core.activateArtifact(LVL6_SPACETIME.id, newTokenId, 0); - const activateRct = await activateTx.wait(); - const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL6_SPACETIME.id)).to.equal( - block.timestamp - ); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL6_SPACETIME.id)).id).to.equal( - newTokenId - ); + await activateAndConfirm(world.user1Core, LVL6_SPACETIME.id, newTokenId); await increaseBlockchainTime(); @@ -1014,11 +997,7 @@ describe('DarkForestArtifacts', function () { expect(planetArrivals[0].popArriving.toNumber()).to.be.below(approxArriving + 1000); // Confirm photoid is burned - expect((await getArtifactsOnPlanet(world, LVL6_SPACETIME.id)).length).to.equal(0); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL6_SPACETIME.id)).id).to.equal(0); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL6_SPACETIME.id)).to.equal( - 0 - ); + await testDeactivate(world, LVL6_SPACETIME.id); } }); }); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 3b109b1c..d4064da2 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -21,13 +21,14 @@ import { import { bigIntFromKey } from '@dfdao/whitelist'; import { mine, time } from '@nomicfoundation/hardhat-network-helpers'; import bigInt from 'big-integer'; +import { expect } from 'chai'; import { BigNumber, BigNumberish, constants } from 'ethers'; +import hre from 'hardhat'; // @ts-ignore import * as snarkjs from 'snarkjs'; import { TestLocation } from './TestLocation'; import { World } from './TestWorld'; import { ARTIFACT_PLANET_1, initializers, LARGE_INTERVAL } from './WorldConstants'; - const { PLANETHASH_KEY, SPACETYPE_KEY, @@ -352,3 +353,22 @@ export async function createArtifact( return tokenId; } + +export async function testDeactivate(world: World, locationId: BigNumberish) { + expect((await getArtifactsOnPlanet(world, locationId)).length).to.equal(0); + expect((await world.contract.getActiveArtifactOnPlanet(locationId)).id).to.equal(0); + expect(await world.contract.getArtifactActivationTimeOnPlanet(locationId)).to.equal(0); +} + +export async function activateAndConfirm( + contract: DarkForest, + locationId: BigNumber, + tokenId: BigNumberish, + wormHoleTo?: BigNumberish +) { + const activateTx = await contract.activateArtifact(locationId, tokenId, wormHoleTo || 0); + const activateRct = await activateTx.wait(); + const block = await hre.ethers.provider.getBlock(activateRct.blockNumber); + expect(await contract.getArtifactActivationTimeOnPlanet(locationId)).to.equal(block.timestamp); + expect((await contract.getActiveArtifactOnPlanet(locationId)).id).to.equal(tokenId); +} From 15b304163afb410ef5ab7584c4ca6a17290a215f Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 15:23:07 +0100 Subject: [PATCH 21/55] feat: prevent spaceship transfer --- eth/contracts/DFToken.sol | 32 +--- eth/contracts/Tokens.md | 153 +++++++++++++++++++ eth/contracts/facets/DFArtifactFacet.sol | 25 ++- eth/contracts/libraries/LibArtifactUtils.sol | 24 +-- eth/contracts/libraries/LibGameUtils.sol | 6 - eth/test/DFSpaceShips.test.ts | 42 +++++ eth/test/NewDFArtifacts.test.ts | 22 +-- eth/test/utils/TestUtils.ts | 10 ++ 8 files changed, 255 insertions(+), 59 deletions(-) create mode 100644 eth/contracts/Tokens.md diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol index 4b304c67..9cbd5c4c 100644 --- a/eth/contracts/DFToken.sol +++ b/eth/contracts/DFToken.sol @@ -8,15 +8,6 @@ import "hardhat/console.sol"; // Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper // This makes it more obvious when we are using the DFToken functions contract DFToken is SolidStateERC1155 { - function mint( - address account, - uint256 id, - uint256 amount, - bytes memory data - ) public { - _mint(account, id, amount, data); - } - /** * @notice set per-token metadata URI * @param tokenId token whose metadata URI to set @@ -55,7 +46,7 @@ contract DFToken is SolidStateERC1155 { uint256 _rarity, uint256 _artifactType, uint256 _biome - ) public view returns (uint256) { + ) public pure returns (uint256) { uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); @@ -63,18 +54,11 @@ contract DFToken is SolidStateERC1155 { return collectionType + rarity + artifactType + biome; } - // Collection Type should be Spaceship. ArtifactType should be type of ship. - function encodeSpaceship(uint256 _collectionType, uint256 _artifactType) - public - view - returns (uint256) - { - uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); - uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); - return collectionType + artifactType; - } - - function decodeArtifact(uint256 artifactId) public view returns (ArtifactProperties memory) { + /** + * @notice Fetch the ArtifactProperties for the given id + * @param artifactId type of artifact + */ + function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { bytes memory _b = abi.encodePacked(artifactId); // 0 is left most element. 0 is given the property Unknown in TokenInfo. @@ -87,10 +71,6 @@ contract DFToken is SolidStateERC1155 { uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); - // console.log("collectionType %s", collectionType); - // console.log("rarity %s", rarity); - // console.log("artifactType %s", artifactType); - // console.log("biome %s", biome); ArtifactProperties memory a = ArtifactProperties({ id: artifactId, diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md new file mode 100644 index 00000000..d24c15a9 --- /dev/null +++ b/eth/contracts/Tokens.md @@ -0,0 +1,153 @@ +# Dark Forest Tokens (ERC1155): + +Each token in Dark Forest is a collection under the ERC1155 Standard. + +Each collection has a `tokenId` can be fungible (supply > 1) or non-fungible (supply = 1). + +The fundamental data structure in ERC1155 is `mapping(uint256 => mapping(address => uint256)) balances;`. + +`balances[tokenId][myAddress]` = number of tokens I have of a given collection. + +The `uint 256 tokenId`, which identifies a _set_ of tokens, is represented in the following way: + +Each `uint256 tokenId` is broken down into 32 chunks of 8 bits each (32\*8 = 256). + +> | chunk1 | chunk 2 | ... chunk32 |. + +Chunks are used from left to right, so a token that has a value of `0xff` in chunk1 looks like`0xff00000000000000000000000000000000000000000000000000000000000000` + +Another way to visualize the `tokenId` is by highlighting each chunk: `0x**ff**_00_**00**_00_**00**_00_...` + +Each chunk allows for 2^8 (256) unique pieces of information. If you need more than 256 properties of a token, you can use an additional chunk + +This architecture allows us to encode information about a Dark Forest token in the `tokenId` itself, +and, more importantly, it allows to create a copy of a token just by using the same `tokenId`. + +This concept will become clearer as we examine how these rules are used for Artifacts and +Spaceships. + +In `DFTypes.sol`, the `TokenInfo` enum looks like this: + +```js +enum TokenInfo { + Unknown, + CollectionType, + ArtifactRarity, + ArtifactType, + Biome +} +``` + +Each index in `TokenInfo` refers to a chunk in the `tokenId`. + +> | CollectionType | ArtifactRarity | ArtifactType | Biome | chunk5 | ... chunk 32 | + +Each of these chunks has 256 options. So you can have 256 Artifact Rarities, Types, Biomes etc... +This should be plenty, but if you need more you just use another chunk. + +## Artifacts + +In Dark Forest, artifacts are fungible. If I have two Epic Monoliths found in the Ocean biome, I can +sell either one and they have the same buffs for planets. + +Thus, we can represent in this information with the following encoding: + +> | CollectionType.Artifact | ArtifactRarity.Epic | ArtifactType.Monolith | Biome.Ocean | ... | + +In hex: + +> | 0x01 | 0x03| 0x01 | 0x01 | ... + +Under the hood, we simply calculate the value of the given property (Epic Artifact = 3), convert it +to hex (0x03), and place it in the appropriate location (TokenInfo.ArtifactRarity = 2, so place 0x03 +two chunks from the left). + +To decode, we just reverse this process. Given the id `0x010301010000.....`, I know that it +is an Epic Monoliths Artifact from the Ocean Biome. + +You can see the actual encoding and decoding take place in `DFToken.sol/encodeArtifact` and +`DFToken.sol/decode Artifact`. + +### Minting + +In Dark Forest, when Artifacts are minted, they are placed on the planet they were found on, _but +they are still owned by the core game contract_. From the eyes of the game contract, it will just +own bunch of Epic Monoliths. However, we have an additional data structure, +`mapping(uint256 =>uint256[]) planetArtifacts;` in the contract storage that keeps track of which +artifacts are on which planet. + +If you own the planet that contains an Artifact, you can move that Artifact. This means that players +can lose access to their artifacts if the planet it is on gets captured. + +### Withdrawing + +For players to gain ownership over their Aritifacts, they must withdraw them from a Spacetime Rip. + +Under the hood, this decreases the number of Epic Monoliths owned by the core game contract by 1 and +increases the number of Epic Monoliths owned by the player by 1. It also removes _one_ instance of +the Epic Monolith tokenId from the `planetArtifacts` mapping. + +Players can now transfer or burn the Artifact. + +### Transferring + +ERC1155 Transfers function just like ERC721 transfers, but can also specify an amount of tokens to +transfer. + +If I wanted to send _two_ Epic Monoliths to a friend, I would call the following: +`safeTransferFrom(me,myFriend,epicMonolithId, 2, "")` + +## Spaceships + +Spaceships are very similar to Artifacts, in that they give special powers to planets. + +Spaceships are _non-fungible_. If I have two Epic Monoliths on a planet and you capture the +planet, you control the Monoliths. However, if I have my Mothership on your planet and you have your Mothership on +your planet, you cannot control my Mothership. This means that the `planetArtifacts` mapping above +will fail to enforce this primary rule of Spaceships. To fix this, each Spaceship must have a unique +id and be owned by the account that minted it. + +However, we can still use our `tokenId` chunk system because we don't need all 256 bits to uniquely +identify a Spaceship. We limit Spaceships to the first 16 chunks (128 bits) and save 128 for a +unique id. This uses the [Split-Id](https://eips.ethereum.org/EIPS/eip-1155#split-id-bits) method +recommended in the ERC1155 Proposal. + +A Mothership Spaceship is represented like so + +> | CollectionType.Spaceship | ArtifactRarity.Unknown | ArtifactType.ShipMothership | Biome.Unknown +> | ... chunk 16 | uniqueId (16 chunks) | + +In hex: + +> | 0x02 | 0x00 | 0x0a | 0x00 | ... | uniqueId (16 chunks) | + +A Spaceship's tokenId = `uint128 tokenInfo` + `uint128 uniqueId` + +### Minting + +When a user creates or mints a Spaceship, they own it, not the contract. + +Lets say my Mothership has id `<0x02000a00><0x01>`. + +`balances[0x02000a00...01][myAddress] = 1`. + +If Velorum mints their own Mothership, it would have id: `<0x02000a00><0x02>`. + +`balances[0x02000a00...01][myAddress] = 1`. + +Velorum's Mothership has the same TokenInfo, but a unique identifier at the end. This means the +contract stores my Mothership and Velorum's Mothership as completely different collections. +However, because our ships share the same first 128 bits, we can still calculate the information +about the Spaceship (ArtifactType, CollectionType) just by feeding the `decodeArtifact` function the `tokenId`. + +### Transferring + +Right now, Spaceships cannot be transferred. This means players cannot sell their powerful ships. +This is implemented using the `beforeTokenTransfer` hook in ERC1155, which only lets the core +contract transfer Spaceships. This is used when minting a ship. + +We could easily turn off this check if we wanted players to be able to buy and sell Spaceships. + +## Activating + +## Deactiving diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 0b2bc6ad..43b74dfd 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.0; // Contract imports +import {ERC1155BaseInternal} from "@solidstate/contracts/token/ERC1155/base/ERC1155Base.sol"; +import {ERC1155EnumerableInternal} from "@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableInternal.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; import {DFToken} from "../DFToken.sol"; @@ -17,6 +19,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; + import "hardhat/console.sol"; contract DFArtifactFacet is WithStorage, DFToken { @@ -113,6 +116,8 @@ contract DFArtifactFacet is WithStorage, DFToken { // return results; // } + // This calls the low level _transfer call which doesn't check if the msg.sender actually owns + // the tokenId. TODO: See if this is a problem. function transferArtifact( uint256 tokenId, address owner, @@ -122,7 +127,7 @@ contract DFArtifactFacet is WithStorage, DFToken { // account, id, amount. _burn(owner, tokenId, 1); } else { - // operator sender receiver id amount data + // sender receiver id amount data _transfer(owner, owner, newOwner, tokenId, 1, ""); } } @@ -291,4 +296,22 @@ contract DFArtifactFacet is WithStorage, DFToken { gs().players[msg.sender].claimedShips = true; } + + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual override { + uint256 length = ids.length; + for (uint256 i = 0; i < length; i++) { + ArtifactProperties memory artifact = decodeArtifact(ids[i]); + // Only core contract can transfer Spaceships + if (LibArtifactUtils.isSpaceship(artifact.artifactType)) { + require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); + } + } + } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 6b12f027..1d9b1df0 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -71,18 +71,18 @@ library LibArtifactUtils { uint8(shipType), uint8(Biome.Unknown) ); - // TODO: Use struct naming convetion for readability - DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - tokenId + id, // makes each spaceship unique but keeps generic properties. - msg.sender, - planetId, - ArtifactRarity.Unknown, - Biome.Unknown, - shipType, - owner, // Player is owner of new ship - owner - ); - + // TODO: Use struct naming convention for readability + DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs({ + tokenId: tokenId + id, + discoverer: msg.sender, + planetId: planetId, + rarity: ArtifactRarity.Unknown, + biome: Biome.Unknown, + artifactType: shipType, + owner: owner, + // Only used for spaceships + controller: owner + }); Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 1cd4259d..3b762cbe 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -467,13 +467,7 @@ library LibGameUtils { returns (ArtifactProperties memory) { console.log("searching for %s on %s", artifactId, locationId); - console.log( - "%s artifacts on planet %s", - gs().planetArtifacts[locationId].length, - locationId - ); for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - console.log("found %s ", gs().planetArtifacts[locationId][i]); if (gs().planetArtifacts[locationId][i] == artifactId) { return DFArtifactFacet(address(this)).getArtifact(artifactId); } diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index ccd807bb..62b9514d 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -3,6 +3,7 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { conquerUnownedPlanet, + getArtifactOnPlanetByType, increaseBlockchainTime, makeInitArgs, makeMoveArgs, @@ -58,6 +59,47 @@ describe('DarkForestSpaceShips', function () { }); }); + describe.only('ship transfers', function () { + it('cannot transfer your own spaceship', async function () { + const motherShip = await getArtifactOnPlanetByType( + world.contract, + SPAWN_PLANET_1.id, + ArtifactType.ShipMothership + ); + // Player owns ship. + expect(await world.contract.balanceOf(world.user1.address, motherShip.id)).to.equal(1); + await expect( + world.user1Core.safeTransferFrom( + world.user1.address, + world.user2.address, + motherShip.id, + 1, + '0x00' + ) + ).to.be.revertedWith('player cannot transfer a Spaceship'); + }); + it('cannot transfer other players spaceship', async function () { + await world.user2Core.giveSpaceShips(SPAWN_PLANET_2.id); + + const motherShip = await getArtifactOnPlanetByType( + world.contract, + SPAWN_PLANET_2.id, + ArtifactType.ShipMothership + ); + // Other Player owns ship. + expect(await world.contract.balanceOf(world.user2.address, motherShip.id)).to.equal(1); + await expect( + world.user1Core.safeTransferFrom( + world.user2.address, + world.user1.address, + motherShip.id, + 1, + '0x00' + ) + ).to.be.revertedWith('ERC1155: caller is not owner nor approved'); + }); + }); + describe('using the Titan', async function () { this.timeout(0); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 98bc74a9..7117cae6 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -11,7 +11,6 @@ import { getArtifactsOwnedBy, getCurrentTime, getStatSum, - hexToBigNumber, increaseBlockchainTime, makeFindArtifactArgs, makeInitArgs, @@ -19,7 +18,6 @@ import { prettyPrintToken, testDeactivate, user1MintArtifactPlanet, - ZERO_ADDRESS, } from './utils/TestUtils'; import { defaultWorldFixture, World } from './utils/TestWorld'; import { @@ -195,6 +193,7 @@ describe('DarkForestArtifacts', function () { ); const moveReceipt = await moveTx.wait(); const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued + await world.contract.refreshPlanet(ARTIFACT_PLANET_1.id); const oldLocArtifacts = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); expect(oldLocArtifacts.length).to.equal(0); // confirming that artifact is on a voyage by checking that its no longer at the old @@ -226,17 +225,12 @@ describe('DarkForestArtifacts', function () { const maxArtifactsOnPlanet = 4; for (let i = 0; i <= maxArtifactsOnPlanet; i++) { // place an artifact on the trading post - const newTokenId = hexToBigNumber(i + 1 + ''); - await world.contract.createArtifact({ - tokenId: newTokenId, - discoverer: world.user1.address, - planetId: 1, - rarity: 1, - biome: 1, - artifactType: 5, - owner: world.user1.address, - controller: ZERO_ADDRESS, - }); + const newTokenId = await createArtifact( + world.contract, + world.user1.address, + ZERO_PLANET, + ArtifactType.Monolith + ); await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); // wait for the planet to fill up and download its stats @@ -909,7 +903,7 @@ describe('DarkForestArtifacts', function () { }); describe('planetary shield', function () { - it.only('activates planetary shield, + defense, - range, then burns shield', async function () { + it('activates planetary shield, + defense, - range, then burns shield', async function () { await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); const newTokenId = await createArtifact( diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index d4064da2..565df44c 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -372,3 +372,13 @@ export async function activateAndConfirm( expect(await contract.getArtifactActivationTimeOnPlanet(locationId)).to.equal(block.timestamp); expect((await contract.getActiveArtifactOnPlanet(locationId)).id).to.equal(tokenId); } + +export async function getArtifactOnPlanetByType( + contract: DarkForest, + locationId: BigNumber, + artifactType: ArtifactType +) { + return (await contract.getArtifactsOnPlanet(locationId)).filter( + (artifact) => (artifact.artifactType as ArtifactType) === artifactType + )[0]; +} From a2b4bef4dc7f54228b0fa3b32d156e9455020862 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 15:34:51 +0100 Subject: [PATCH 22/55] feat: bye bye DFToken --- eth/contracts/DFToken.sol | 85 ----------------------- eth/contracts/Tokens.md | 9 +++ eth/contracts/facets/DFArtifactFacet.sol | 87 ++++++++++++++++++++++-- eth/test/NewDFArtifacts.test.ts | 30 +++++++- 4 files changed, 118 insertions(+), 93 deletions(-) delete mode 100644 eth/contracts/DFToken.sol diff --git a/eth/contracts/DFToken.sol b/eth/contracts/DFToken.sol deleted file mode 100644 index 9cbd5c4c..00000000 --- a/eth/contracts/DFToken.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.0; - -import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; -import {ArtifactProperties, TokenInfo, CollectionType, ArtifactType, ArtifactRarity, Biome} from "./DFTypes.sol"; -import "hardhat/console.sol"; - -// Note: We can call _mint and _setTokenUri directly in DFArtifactFacet, but I like having a wrapper -// This makes it more obvious when we are using the DFToken functions -contract DFToken is SolidStateERC1155 { - /** - * @notice set per-token metadata URI - * @param tokenId token whose metadata URI to set - * @param tokenURI per-token URI - */ - function setTokenURI(uint256 tokenId, string memory tokenURI) public { - _setTokenURI(tokenId, tokenURI); - } - - /** - * @notice calculate amount of bits to shift left - * @param index number of 1 byte words to shift from left - * @return shift length of left shift - */ - function calcBitShift(uint8 index) internal pure returns (uint8) { - uint8 maxVal = 32; - - require(index <= maxVal, "shift index is too high"); - require(index > 0, "shift index is too low"); - - uint256 bin = 8; - uint256 shift = 256; - return uint8(shift - (bin * index)); - } - - /** - * @notice Create the collection ID for a given artifact - * @param _collectionType type of artifact - * @param _rarity rarity of artifact - * @param _artifactType of artifact - * @param _biome of artifact - * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. - */ - function encodeArtifact( - uint256 _collectionType, - uint256 _rarity, - uint256 _artifactType, - uint256 _biome - ) public pure returns (uint256) { - uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); - uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); - uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); - return collectionType + rarity + artifactType + biome; - } - - /** - * @notice Fetch the ArtifactProperties for the given id - * @param artifactId type of artifact - */ - function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { - bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in TokenInfo. - - // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has - // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. - // As a consequence, we need to - // offset fetching the relevant byte from the artifactId by 1. - // However - uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); - uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); - - ArtifactProperties memory a = ArtifactProperties({ - id: artifactId, - collectionType: CollectionType(collectionType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - planetBiome: Biome(biome) - }); - - return a; - } -} diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index d24c15a9..8dcb27bd 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -150,4 +150,13 @@ We could easily turn off this check if we wanted players to be able to buy and s ## Activating +Every Artifact and one Spaceship (Crescent) must be activated on a planet to be used. + ## Deactiving + +# Next Steps + +## Silver + +We can give silver a `tokenId` as well. This would be useful it silver was a fungible resource that +could be used to buy multiple things in game. Easy enough to make it non-transferrable as well. diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 43b74dfd..b5d789fa 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.0; // Contract imports -import {ERC1155BaseInternal} from "@solidstate/contracts/token/ERC1155/base/ERC1155Base.sol"; -import {ERC1155EnumerableInternal} from "@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableInternal.sol"; +import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; import {DFToken} from "../DFToken.sol"; @@ -18,11 +17,11 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {Artifact, ArtifactType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import {Artifact, ArtifactRarity, ArtifactType, Biome, CollectionType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; import "hardhat/console.sol"; -contract DFArtifactFacet is WithStorage, DFToken { +contract DFArtifactFacet is WithStorage, SolidStateERC1155 { event PlanetProspected(address player, uint256 loc); event ArtifactFound(address player, uint256 artifactId, uint256 loc); event ArtifactDeposited(address player, uint256 artifactId, uint256 loc); @@ -96,9 +95,8 @@ contract DFArtifactFacet is WithStorage, DFToken { return newArtifact; } - function getArtifact(uint256 tokenId) public view returns (ArtifactProperties memory) { - return DFToken.decodeArtifact(tokenId); - //return gs().artifacts[tokenId]; + function getArtifact(uint256 tokenId) public pure returns (ArtifactProperties memory) { + return decodeArtifact(tokenId); } // function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { @@ -314,4 +312,79 @@ contract DFArtifactFacet is WithStorage, DFToken { } } } + + /** + * @notice set per-token metadata URI + * @param tokenId token whose metadata URI to set + * @param tokenURI per-token URI + */ + function setTokenURI(uint256 tokenId, string memory tokenURI) public { + _setTokenURI(tokenId, tokenURI); + } + + /** + * @notice calculate amount of bits to shift left + * @param index number of 1 byte words to shift from left + * @return shift length of left shift + */ + function calcBitShift(uint8 index) internal pure returns (uint8) { + uint8 maxVal = 32; + + require(index <= maxVal, "shift index is too high"); + require(index > 0, "shift index is too low"); + + uint256 bin = 8; + uint256 shift = 256; + return uint8(shift - (bin * index)); + } + + /** + * @notice Create the collection ID for a given artifact + * @param _collectionType type of artifact + * @param _rarity rarity of artifact + * @param _artifactType of artifact + * @param _biome of artifact + * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. + */ + function encodeArtifact( + uint256 _collectionType, + uint256 _rarity, + uint256 _artifactType, + uint256 _biome + ) public pure returns (uint256) { + uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); + uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); + uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); + uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); + return collectionType + rarity + artifactType + biome; + } + + /** + * @notice Fetch the ArtifactProperties for the given id + * @param artifactId type of artifact + */ + function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { + bytes memory _b = abi.encodePacked(artifactId); + // 0 is left most element. 0 is given the property Unknown in TokenInfo. + + // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has + // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. + // As a consequence, we need to + // offset fetching the relevant byte from the artifactId by 1. + // However + uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); + uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); + uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); + + ArtifactProperties memory a = ArtifactProperties({ + id: artifactId, + collectionType: CollectionType(collectionType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + planetBiome: Biome(biome) + }); + + return a; + } } diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/NewDFArtifacts.test.ts index 7117cae6..5d587dd4 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/NewDFArtifacts.test.ts @@ -79,7 +79,35 @@ describe('DarkForestArtifacts', function () { world = await loadFixture(worldFixture); }); - describe('it tests basic artifact actions', function () { + describe.only('it tests basic artifact actions', function () { + it('logs bits for artifact', async function () { + // Must be valid options + const _collectionType = '0x01'; + const _rarity = ArtifactRarity.Legendary; + const _artifactType = ArtifactType.Colossus; + const _biome = Biome.DESERT; + const res = await world.contract.encodeArtifact( + _collectionType, + _rarity, + _artifactType, + _biome + ); + const { collectionType, rarity, planetBiome, artifactType } = + await world.contract.decodeArtifact(res); + expect(collectionType).to.equal(Number(_collectionType)); + expect(rarity).to.equal(Number(_rarity)); + expect(planetBiome).to.equal(Number(_biome)); + expect(artifactType).to.equal(Number(_artifactType)); + }); + it('logs bits for spaceship', async function () { + // Must be valid options + const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types + const _artifactType = ArtifactType.ShipGear; + const res = await world.contract.encodeArtifact(_collectionType, 0, _artifactType, 0); + const { collectionType, artifactType } = await world.contract.decodeArtifact(res); + expect(collectionType).to.equal(Number(_collectionType)); + expect(artifactType).to.equal(Number(_artifactType)); + }); // This test will fail if the artifact is special. it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { const statSumInitial = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); From ce284d82dad93b2d55993e20c4e811ec5794452d Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 15:50:58 +0100 Subject: [PATCH 23/55] feat: Readme --- eth/contracts/Tokens.md | 51 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 8dcb27bd..18f077c3 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -121,7 +121,7 @@ In hex: > | 0x02 | 0x00 | 0x0a | 0x00 | ... | uniqueId (16 chunks) | -A Spaceship's tokenId = `uint128 tokenInfo` + `uint128 uniqueId` +A Spaceship's tokenId = `` ### Minting @@ -152,7 +152,54 @@ We could easily turn off this check if we wanted players to be able to buy and s Every Artifact and one Spaceship (Crescent) must be activated on a planet to be used. -## Deactiving +The fungible nature of Artifacts creates a challenge: How do we associate data with specific artifacts? How do I know when my +Epic Monolith is activated? + +There are new data structures in `LibStorage.sol` to handle this information. Because Artifact +activations are always associated with planets, we can store the needed information on the relevant planets +instead of on the Artifacts themselves. + +```js + mapping(uint256 => uint256[]) planetArtifacts; + mapping(uint256 => uint256) planetActiveArtifact; + mapping(uint256 => uint256) planetWormholes; + mapping(uint256 => uint256) planetArtifactActivationTime; +``` + +Lets say I move my Epic Monolith with id `0xMonolith` to planet A with id `0xA`. +Now `planetArtifacts[0xA]` includes `0xMonolith`. +Now, I activate my Monolith. The following occurs: + +- `planetActiveArtifact[0xA] = 0xMonolith`. +- `planetArtifactActivationTime[0xA] = block.timestamp` + +If I had a Wormhole instead of a Monolith, I would also update `planetWormholes`. Lets say I want a +wormhole from `0xA` to `0xB`. + +- `planetWormholes[0xA] = 0xB` + +## Deactivating + +If I deactivate my artifact from `0xA`, we simply undo these maneuvers: + +- `planetActiveArtifact[0xA] = 0`. +- `planetArtifactActivationTime[0xA] = 0` + +For the wormhole, we do the same: + +- `planetWormholes[0xA] = 0` + +## Simulteanous Activate and Deactivate + +Some Bloom Filters (Artifacts) and Crescents (Spaceships) are burned on use. + +All we do is make sure that we call the Activate and Deactivate functions in the same transaction. + +## Photoid + +For Photoid cannons, we simply apply the Photoid move if the current time is greater than the time +activated + the Photoid activation delay. If someone captures the planet, the activation time +doesn't change. # Next Steps From 4db8a7f6aa75a4f60e24084d4286d9e1567ae73b Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 15:51:23 +0100 Subject: [PATCH 24/55] clean --- eth/test/DFERC1155.test.ts | 71 -------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 eth/test/DFERC1155.test.ts diff --git a/eth/test/DFERC1155.test.ts b/eth/test/DFERC1155.test.ts deleted file mode 100644 index d95344cb..00000000 --- a/eth/test/DFERC1155.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { DFToken, DFToken__factory } from '@dfdao/contracts/typechain'; -import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { expect } from 'chai'; -import { ethers } from 'hardhat'; - -/** - * Once again, a friendly reminder that in ERC1155 tokenId refers to a collection of 1 or more - * tokens, NOT guaranteed to be unique instances of tokens. - */ - -describe('SolidStateERC1155', function () { - let token: DFToken; - let deployer: SignerWithAddress; - let user1: SignerWithAddress; - const addressZero = ethers.constants.AddressZero; - const collectionId = ethers.constants.Zero; - const amount = ethers.constants.Two; - const tokenURI = 'ERC1155Metadata.tokenURI/{id}.json'; - - beforeEach(async function () { - const signers = await ethers.getSigners(); - deployer = signers[0]; - token = await new DFToken__factory(deployer).deploy(); - user1 = signers[1]; - }); - - it('mints tokens for tokenId zero', async function () { - await expect(token.mint(user1.address, collectionId, amount, ethers.constants.HashZero)) - .to.emit(token, 'TransferSingle') - .withArgs(deployer.address, addressZero, user1.address, collectionId, amount); - - await expect( - token.connect(user1).mint(user1.address, collectionId, amount, ethers.constants.HashZero) - ) - .to.emit(token, 'TransferSingle') - .withArgs(user1.address, addressZero, user1.address, collectionId, amount); - - // Each mint created 2 tokens in the same collection. Two mints = 4 tokens. - expect(await token.balanceOf(user1.address, collectionId)).to.equal(amount.mul(2)); - }); - it('sets tokenURI for tokenId zero', async function () { - await expect(token.setTokenURI(collectionId, tokenURI)) - .to.emit(token, 'URI') - .withArgs(tokenURI, collectionId); - - expect(await token.uri(collectionId)).to.equal(tokenURI); - }); - it.skip('logs bits for artifact', async function () { - // Must be valid options - const _collectionType = '0x01'; - const _rarity = ArtifactRarity.Legendary; - const _artifactType = ArtifactType.Colossus; - const _biome = Biome.DESERT; - const res = await token.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); - const { collectionType, rarity, planetBiome, artifactType } = await token.decodeArtifact(res); - expect(collectionType).to.equal(Number(_collectionType)); - expect(rarity).to.equal(Number(_rarity)); - expect(planetBiome).to.equal(Number(_biome)); - expect(artifactType).to.equal(Number(_artifactType)); - }); - it('logs bits for spaceship', async function () { - // Must be valid options - const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types - const _artifactType = ArtifactType.ShipGear; - const res = await token.encodeArtifact(_collectionType, 0, _artifactType, 0); - const { collectionType, artifactType } = await token.decodeArtifact(res); - expect(collectionType).to.equal(Number(_collectionType)); - expect(artifactType).to.equal(Number(_artifactType)); - }); -}); From cf20e4c778258d8848d5f8851c4aa6d2aa8f20c9 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:25:05 +0100 Subject: [PATCH 25/55] the nice nice cleanup --- eth/contracts/DFTypes.sol | 20 +-- eth/contracts/facets/DFAdminFacet.sol | 6 +- eth/contracts/facets/DFArtifactFacet.sol | 115 ++------------ eth/contracts/facets/DFGetterFacet.sol | 13 +- eth/contracts/facets/DFMoveFacet.sol | 41 +++-- eth/contracts/libraries/LibArtifactUtils.sol | 147 ++++++++++++++---- eth/contracts/libraries/LibGameUtils.sol | 72 +-------- eth/contracts/libraries/LibLazyUpdate.sol | 2 +- eth/contracts/libraries/LibPlanet.sol | 9 +- eth/contracts/libraries/LibStorage.sol | 3 +- eth/tasks/deploy.ts | 13 +- ...FArtifacts.test.ts => DFArtifacts.test.ts} | 18 +-- eth/test/DFSpaceShips.test.ts | 2 +- 13 files changed, 192 insertions(+), 269 deletions(-) rename eth/test/{NewDFArtifacts.test.ts => DFArtifacts.test.ts} (98%) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 9ce896e8..37173772 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -222,27 +222,9 @@ enum ArtifactRarity { Mythic } -// for NFTs -struct Artifact { - bool isInitialized; - uint256 id; - uint256 planetDiscoveredOn; - ArtifactRarity rarity; - Biome planetBiome; - uint256 mintedAtTimestamp; - address discoverer; - ArtifactType artifactType; - // an artifact is 'activated' iff lastActivated > lastDeactivated - uint256 activations; - uint256 lastActivated; - uint256 lastDeactivated; - uint256 wormholeTo; // location id - address controller; // space ships can be controlled regardless of which planet they're on -} - // for artifact getters struct ArtifactWithMetadata { - Artifact artifact; + ArtifactProperties artifact; Upgrade upgrade; Upgrade timeDelayedUpgrade; // for photoid canons specifically. address owner; diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 304f3ac8..56dd3807 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -13,7 +13,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Type imports -import {SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, Artifact, ArtifactType, Player, Planet} from "../DFTypes.sol"; +import {ArtifactProperties, SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet} from "../DFTypes.sol"; contract DFAdminFacet is WithStorage { event AdminOwnershipChanged(uint256 loc, address newOwner); @@ -145,7 +145,7 @@ contract DFAdminFacet is WithStorage { require(LibArtifactUtils.isSpaceship(artifactType), "artifact type must be a space ship"); uint256 shipId = LibArtifactUtils.createAndPlaceSpaceship(locationId, owner, artifactType); - Artifact memory artifact = gs().artifacts[shipId]; + ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(shipId); Planet memory planet = gs().planets[locationId]; planet = LibPlanet.applySpaceshipArrive(artifact, planet); @@ -166,7 +166,7 @@ contract DFAdminFacet is WithStorage { } function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { - Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); + ArtifactProperties memory artifact = DFArtifactFacet(address(this)).createArtifact(args); // Don't put artifact on planet if no planetId given. if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index b5d789fa..fb27ed5c 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; -import {DFToken} from "../DFToken.sol"; // Library Imports import {LibPermissions} from "../libraries/LibPermissions.sol"; @@ -17,7 +16,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {Artifact, ArtifactRarity, ArtifactType, Biome, CollectionType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; +import {ArtifactRarity, ArtifactType, Biome, CollectionType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; import "hardhat/console.sol"; @@ -67,42 +66,34 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { function createArtifact(DFTCreateArtifactArgs memory args) public onlyAdminOrCore - returns (Artifact memory) + returns (ArtifactProperties memory) { require(args.tokenId >= 1, "artifact id must be positive"); // Account, Id, Amount, Data _mint(args.owner, args.tokenId, 1, ""); - Artifact memory newArtifact = Artifact( - true, - args.tokenId, - args.planetId, - args.rarity, - args.biome, - block.timestamp, - args.discoverer, - args.artifactType, - 0, - 0, - 0, - 0, - args.controller - ); - - gs().artifacts[args.tokenId] = newArtifact; - - return newArtifact; + return getArtifact(args.tokenId); } function getArtifact(uint256 tokenId) public pure returns (ArtifactProperties memory) { - return decodeArtifact(tokenId); + return LibArtifactUtils.decodeArtifact(tokenId); + } + + function encodeArtifact( + uint256 _collectionType, + uint256 _rarity, + uint256 _artifactType, + uint256 _biome + ) public pure returns (uint256) { + return LibArtifactUtils.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); } // function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { // return gs().artifacts[tokenByIndex(idx)]; // } + // TODO: Add enumerable // function getPlayerArtifactIds(address playerId) public view returns (uint256[] memory) { // uint256 balance = balanceOf(playerId); // uint256[] memory results = new uint256[](balance); @@ -130,15 +121,6 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { } } - function updateArtifact(Artifact memory updatedArtifact, address owner) public onlyAdminOrCore { - require( - doesArtifactExist(owner, updatedArtifact.id), - "you cannot update an artifact that doesn't exist" - ); - - gs().artifacts[updatedArtifact.id] = updatedArtifact; - } - function doesArtifactExist(address owner, uint256 tokenId) public view returns (bool) { return balanceOf(owner, tokenId) > 0; } @@ -166,7 +148,6 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ); } - console.log("finding artifact..."); uint256 foundArtifactId = LibArtifactUtils.findArtifact( DFPFindArtifactArgs(planetId, biomebase, address(this)) ); @@ -305,7 +286,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ) internal virtual override { uint256 length = ids.length; for (uint256 i = 0; i < length; i++) { - ArtifactProperties memory artifact = decodeArtifact(ids[i]); + ArtifactProperties memory artifact = getArtifact(ids[i]); // Only core contract can transfer Spaceships if (LibArtifactUtils.isSpaceship(artifact.artifactType)) { require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); @@ -321,70 +302,4 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { function setTokenURI(uint256 tokenId, string memory tokenURI) public { _setTokenURI(tokenId, tokenURI); } - - /** - * @notice calculate amount of bits to shift left - * @param index number of 1 byte words to shift from left - * @return shift length of left shift - */ - function calcBitShift(uint8 index) internal pure returns (uint8) { - uint8 maxVal = 32; - - require(index <= maxVal, "shift index is too high"); - require(index > 0, "shift index is too low"); - - uint256 bin = 8; - uint256 shift = 256; - return uint8(shift - (bin * index)); - } - - /** - * @notice Create the collection ID for a given artifact - * @param _collectionType type of artifact - * @param _rarity rarity of artifact - * @param _artifactType of artifact - * @param _biome of artifact - * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. - */ - function encodeArtifact( - uint256 _collectionType, - uint256 _rarity, - uint256 _artifactType, - uint256 _biome - ) public pure returns (uint256) { - uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); - uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); - uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); - return collectionType + rarity + artifactType + biome; - } - - /** - * @notice Fetch the ArtifactProperties for the given id - * @param artifactId type of artifact - */ - function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { - bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in TokenInfo. - - // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has - // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. - // As a consequence, we need to - // offset fetching the relevant byte from the artifactId by 1. - // However - uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); - uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); - - ArtifactProperties memory a = ArtifactProperties({ - id: artifactId, - collectionType: CollectionType(collectionType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - planetBiome: Biome(biome) - }); - - return a; - } } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 38f38fc3..7fc87e45 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -7,12 +7,13 @@ import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Library imports import {LibPermissions} from "../libraries/LibPermissions.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; // Storage imports import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; // Type imports -import {RevealedCoords, ArtifactProperties, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade, Artifact} from "../DFTypes.sol"; +import {RevealedCoords, ArtifactProperties, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { @@ -325,7 +326,7 @@ contract DFGetterFacet is WithStorage { // view // returns (ArtifactWithMetadata memory ret) // { - // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); + // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifact(artifactId); // address owner; @@ -355,7 +356,7 @@ contract DFGetterFacet is WithStorage { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; ret = new ArtifactProperties[](artifactIds.length); for (uint256 i = 0; i < artifactIds.length; i++) { - ret[i] = DFArtifactFacet(address(this)).decodeArtifact(artifactIds[i]); + ret[i] = LibArtifactUtils.decodeArtifact(artifactIds[i]); } return ret; } @@ -379,7 +380,7 @@ contract DFGetterFacet is WithStorage { returns (ArtifactProperties memory ret) { uint256 artifactId = gs().planetActiveArtifact[locationId]; - return DFArtifactFacet(address(this)).getArtifact(artifactId); + return LibArtifactUtils.decodeArtifact(artifactId); } // function bulkGetPlanetArtifacts(uint256[] calldata planetIds) @@ -405,7 +406,7 @@ contract DFGetterFacet is WithStorage { // ret = new ArtifactWithMetadata[](ids.length); // for (uint256 i = 0; i < ids.length; i++) { - // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifact(ids[i]); + // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifact(ids[i]); // address owner; @@ -443,7 +444,7 @@ contract DFGetterFacet is WithStorage { // ret = new ArtifactWithMetadata[](endIdx - startIdx); // for (uint256 i = startIdx; i < endIdx; i++) { - // Artifact memory artifact = DFArtifactFacet(address(this)).getArtifactAtIndex(i); + // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifactAtIndex(i); // address owner = address(0); // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index affe1f66..7e0eeee4 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -15,7 +15,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, Artifact, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { @@ -126,7 +126,6 @@ contract DFMoveFacet is WithStorage { if (photoidPresent) { temporaryUpgrade = newTempUpgrade; arrivalType = ArrivalType.Photoid; - console.log("doing photoid move"); } } @@ -236,7 +235,7 @@ contract DFMoveFacet is WithStorage { } } - function applySpaceshipDepart(Artifact memory artifact, Planet memory planet) + function applySpaceshipDepart(ArtifactProperties memory artifact, Planet memory planet) public view returns (Planet memory) @@ -273,7 +272,9 @@ contract DFMoveFacet is WithStorage { Undo the spaceship effects that were applied when the ship arrived on the planet. */ function _removeSpaceshipEffectsFromOriginPlanet(DFPMoveArgs memory args) private { - Artifact memory movedArtifact = gs().artifacts[args.movedArtifactId]; + ArtifactProperties memory movedArtifact = LibArtifactUtils.decodeArtifact( + args.movedArtifactId + ); Planet memory planet = applySpaceshipDepart(movedArtifact, gs().planets[args.oldLoc]); gs().planets[args.oldLoc] = planet; } @@ -291,10 +292,13 @@ contract DFMoveFacet is WithStorage { { wormholePresent = false; - // Artifact memory relevantWormhole; ArtifactProperties memory relevantWormhole; - ArtifactProperties memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); - ArtifactProperties memory activeArtifactTo = LibGameUtils.getActiveArtifact(args.newLoc); + ArtifactProperties memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact( + args.oldLoc + ); + ArtifactProperties memory activeArtifactTo = LibArtifactUtils.getActiveArtifact( + args.newLoc + ); // TODO: take the greater rarity of these, or disallow wormholes between planets that // already have a wormhole between them if ( @@ -326,14 +330,15 @@ contract DFMoveFacet is WithStorage { private returns (bool photoidPresent, Upgrade memory temporaryUpgrade) { - ArtifactProperties memory activeArtifactFrom = LibGameUtils.getActiveArtifact(args.oldLoc); + ArtifactProperties memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact( + args.oldLoc + ); if ( activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= gameConstants().PHOTOID_ACTIVATION_DELAY ) { photoidPresent = true; - console.log("photoid present? %s", LibGameUtils.getActiveArtifact(args.oldLoc).id > 0); LibArtifactUtils.deactivateArtifact(args.oldLoc); temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); } @@ -412,19 +417,15 @@ contract DFMoveFacet is WithStorage { } function _isSpaceshipMove(DFPMoveArgs memory args) private view returns (bool) { - return LibArtifactUtils.isSpaceship(gs().artifacts[args.movedArtifactId].artifactType); + return + LibArtifactUtils.isSpaceship( + LibArtifactUtils.decodeArtifact(args.movedArtifactId).artifactType + ); } function _createArrival(DFPCreateArrivalArgs memory args) private { // enter the arrival data for event id Planet memory planet = gs().planets[args.oldLoc]; - // console.log( - // "pop moved: %s dist %s range %s", - // args.popMoved, - // args.effectiveDistTimesHundred, - // uint256(planet.range) - // ); - // console.logUint(planet.populationCap); uint256 popArriving = _getDecayedPop( args.popMoved, @@ -433,7 +434,7 @@ contract DFMoveFacet is WithStorage { planet.populationCap ); bool isSpaceship = LibArtifactUtils.isSpaceship( - DFArtifactFacet(address(this)).decodeArtifact(args.movedArtifactId).artifactType + LibArtifactUtils.decodeArtifact(args.movedArtifactId).artifactType ); // space ship moves are implemented as 0-energy moves require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); @@ -452,9 +453,7 @@ contract DFMoveFacet is WithStorage { distance: args.actualDist }); // Photoids are burned _checkPhotoid, so don't remove twice - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).decodeArtifact( - args.movedArtifactId - ); + ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); if (args.movedArtifactId != 0 && artifact.artifactType != ArtifactType.PhotoidCannon) { LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 1d9b1df0..bf4b7a01 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -12,7 +12,7 @@ import {LibGameUtils} from "./LibGameUtils.sol"; import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, Artifact, ArtifactType, ArtifactRarity, CollectionType, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, CollectionType, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibArtifactUtils { @@ -65,7 +65,7 @@ library LibArtifactUtils { require(shipType <= ArtifactType.ShipTitan && shipType >= ArtifactType.ShipMothership); // require(gs().miscNonce < MAX UINT 128) but won't happen. uint128 id = uint128(gs().miscNonce++); - uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( + uint256 tokenId = encodeArtifact( uint8(CollectionType.Spaceship), uint8(ArtifactRarity.Unknown), uint8(shipType), @@ -83,12 +83,12 @@ library LibArtifactUtils { // Only used for spaceships controller: owner }); - Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( + ArtifactProperties memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); LibGameUtils._putArtifactOnPlanet(planetId, foundArtifact.id); - return id; + return tokenId; } function findArtifact(DFPFindArtifactArgs memory args) public returns (uint256 artifactId) { @@ -115,12 +115,7 @@ library LibArtifactUtils { levelBonus + planet.planetLevel ); - // console.log("LAU: collectionType %s", uint8(CollectionType.Artifact)); - // console.log("LAU: rarity %s", uint8(rarity)); - // console.log("artifactType %s", uint8(artifactType)); - // console.log("biome %s", uint8(biome)); - - uint256 tokenId = DFArtifactFacet(address(this)).encodeArtifact( + uint256 tokenId = encodeArtifact( uint8(CollectionType.Artifact), uint8(rarity), uint8(artifactType), @@ -138,7 +133,7 @@ library LibArtifactUtils { address(0) ); - Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( + ArtifactProperties memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); @@ -158,9 +153,7 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).decodeArtifact( - artifactId - ); + ArtifactProperties memory artifact = decodeArtifact(artifactId); require( LibGameUtils.isArtifactOnPlanet(locationId, artifactId), @@ -225,22 +218,19 @@ library LibArtifactUtils { Planet storage planet, ArtifactProperties memory artifact ) private { - console.log("activating %s on %s", artifactId, locationId); require( planet.owner == msg.sender, "you must own the planet you are activating an artifact on" ); require( - LibGameUtils.getActiveArtifact(locationId).collectionType == CollectionType.Unknown, + getActiveArtifact(locationId).collectionType == CollectionType.Unknown, "there is already an active artifact on this planet" ); require(!planet.destroyed, "planet is destroyed"); uint256 length = gs().planetArtifacts[locationId].length; - console.log("artifacts on %s: %s", locationId, length); require( - LibGameUtils.getPlanetArtifact(locationId, artifactId).collectionType != - CollectionType.Unknown, + getPlanetArtifact(locationId, artifactId).collectionType != CollectionType.Unknown, "this artifact is not on this planet" ); @@ -288,8 +278,6 @@ library LibArtifactUtils { // burn it after use. will be owned by contract but not on a planet anyone can control LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); } - - console.log("buffing planet"); // this is fine even tho some artifacts are immediately deactivated, because // those artifacts do not buff the planet. LibGameUtils._buffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); @@ -305,7 +293,7 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); - ArtifactProperties memory artifact = LibGameUtils.getActiveArtifact(locationId); + ArtifactProperties memory artifact = getActiveArtifact(locationId); require( artifact.collectionType != CollectionType.Unknown, @@ -323,11 +311,6 @@ library LibArtifactUtils { bool shouldBurn = artifact.artifactType == ArtifactType.PlanetaryShield || artifact.artifactType == ArtifactType.PhotoidCannon; if (shouldBurn) { - console.log("burning %s", artifact.id); - console.log( - "artifact is on planet? %s", - DFGetterFacet(address(this)).artifactExistsOnPlanet(locationId, artifact.id) - ); // burn it after use. will be owned by contract but not on a planet anyone can control LibGameUtils._takeArtifactOffPlanet(locationId, artifact.id); } @@ -350,7 +333,7 @@ library LibArtifactUtils { ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact(artifactId); + ArtifactProperties memory artifact = decodeArtifact(artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" @@ -373,7 +356,7 @@ library LibArtifactUtils { ); require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); - ArtifactProperties memory artifact = LibGameUtils.getPlanetArtifact(locationId, artifactId); + ArtifactProperties memory artifact = getPlanetArtifact(locationId, artifactId); // TODO: Write is initialized function. require( artifact.collectionType != CollectionType.Unknown, @@ -410,9 +393,7 @@ library LibArtifactUtils { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).getArtifact( - artifactIds[i] - ); + ArtifactProperties memory artifact = decodeArtifact(artifactIds[i]); if ( // TODO: Gear is broken artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller @@ -428,4 +409,106 @@ library LibArtifactUtils { return artifactType >= ArtifactType.ShipMothership && artifactType <= ArtifactType.ShipTitan; } + + /** + * @notice calculate amount of bits to shift left + * @param index number of 1 byte words to shift from left + * @return shift length of left shift + */ + function calcBitShift(uint8 index) internal pure returns (uint8) { + uint8 maxVal = 32; + + require(index <= maxVal, "shift index is too high"); + require(index > 0, "shift index is too low"); + + uint256 bin = 8; + uint256 shift = 256; + return uint8(shift - (bin * index)); + } + + /** + * @notice Create the collection ID for a given artifact + * @param _collectionType type of artifact + * @param _rarity rarity of artifact + * @param _artifactType of artifact + * @param _biome of artifact + * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. + */ + function encodeArtifact( + uint256 _collectionType, + uint256 _rarity, + uint256 _artifactType, + uint256 _biome + ) public pure returns (uint256) { + uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); + uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); + uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); + uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); + return collectionType + rarity + artifactType + biome; + } + + /** + * @notice Fetch the ArtifactProperties for the given id + * @param artifactId type of artifact + */ + function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { + bytes memory _b = abi.encodePacked(artifactId); + // 0 is left most element. 0 is given the property Unknown in TokenInfo. + + // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has + // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. + // As a consequence, we need to + // offset fetching the relevant byte from the artifactId by 1. + // However + uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); + uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); + uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); + + ArtifactProperties memory a = ArtifactProperties({ + id: artifactId, + collectionType: CollectionType(collectionType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + planetBiome: Biome(biome) + }); + + return a; + } + + // if the given planet has an activated artifact on it, then return the artifact + // otherwise, return a 'null artifact' + function getActiveArtifact(uint256 locationId) public view returns (ArtifactProperties memory) { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + if (artifactId != 0) return decodeArtifact(artifactId); + + return _nullArtifactProperties(); + } + + function _nullArtifactProperties() private pure returns (ArtifactProperties memory) { + return + ArtifactProperties( + 0, + CollectionType.Unknown, + ArtifactRarity.Unknown, + ArtifactType.Unknown, + Biome.Unknown + ); + } + + // if the given artifact is on the given planet, then return the artifact + // otherwise, return a 'null' artifact + function getPlanetArtifact(uint256 locationId, uint256 artifactId) + public + view + returns (ArtifactProperties memory) + { + for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { + if (gs().planetArtifacts[locationId][i] == artifactId) { + return decodeArtifact(artifactId); + } + } + + return _nullArtifactProperties(); + } } diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 3b762cbe..1189a7df 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -10,7 +10,7 @@ import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; -import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactProperties, ArtifactType, CollectionType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, ArtifactProperties, ArtifactType, CollectionType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibGameUtils { @@ -392,10 +392,8 @@ library LibGameUtils { // internal contract book-keeping to reflect that the given artifact was // put on. note that this function does not transfer the artifact. function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { - console.log("putting %s on %s", artifactId, locationId); gs().planetArtifacts[locationId].push(artifactId); uint256 length = gs().planetArtifacts[locationId].length; - console.log("new planet artifact id", gs().planetArtifacts[locationId][length - 1]); } // TODO: Why not burn ? @@ -413,14 +411,13 @@ library LibGameUtils { function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; - console.log("removing %s from %s", artifactId, locationId); - console.log("%s artifacts on %s", artifactsOnThisPlanet, locationId); + bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { require( - !isActivatedERC1155(locationId, artifactId), + !isActivated(locationId, artifactId), "you cannot take an activated artifact off a planet" ); @@ -441,11 +438,8 @@ library LibGameUtils { // we do not have an `isActive` field on artifact; the times that the // artifact was last activated and deactivated are sufficent to determine // whether or not the artifact is activated. - function isActivated(Artifact memory artifact) public pure returns (bool) { - return artifact.lastDeactivated < artifact.lastActivated; - } - function isActivatedERC1155(uint256 locationId, uint256 artifactId) public view returns (bool) { + function isActivated(uint256 locationId, uint256 artifactId) public view returns (bool) { return (gs().planetActiveArtifact[locationId] == artifactId); } @@ -459,32 +453,6 @@ library LibGameUtils { return false; } - // if the given artifact is on the given planet, then return the artifact - // otherwise, return a 'null' artifact - function getPlanetArtifact(uint256 locationId, uint256 artifactId) - public - view - returns (ArtifactProperties memory) - { - console.log("searching for %s on %s", artifactId, locationId); - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { - return DFArtifactFacet(address(this)).getArtifact(artifactId); - } - } - - return _nullArtifactProperties(); - } - - // if the given planet has an activated artifact on it, then return the artifact - // otherwise, return a 'null artifact' - function getActiveArtifact(uint256 locationId) public view returns (ArtifactProperties memory) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; - if (artifactId != 0) return DFArtifactFacet(address(this)).decodeArtifact(artifactId); - - return _nullArtifactProperties(); - } - // the space junk that a planet starts with function getPlanetDefaultSpaceJunk(Planet memory planet) public view returns (uint256) { if (planet.isHomePlanet) return 0; @@ -492,38 +460,6 @@ library LibGameUtils { return gameConstants().PLANET_LEVEL_JUNK[planet.planetLevel]; } - // constructs a new artifact whose `isInititalized` field is set to `false` - // used to represent the concept of 'no artifact' - function _nullArtifact() private pure returns (Artifact memory) { - return - Artifact( - false, - 0, - 0, - ArtifactRarity(0), - Biome(0), - 0, - address(0), - ArtifactType(0), - 0, - 0, - 0, - 0, - address(0) - ); - } - - function _nullArtifactProperties() private pure returns (ArtifactProperties memory) { - return - ArtifactProperties( - 0, - CollectionType.Unknown, - ArtifactRarity.Unknown, - ArtifactType.Unknown, - Biome.Unknown - ); - } - function _buffPlanet(uint256 location, Upgrade memory upgrade) public { Planet storage planet = gs().planets[location]; diff --git a/eth/contracts/libraries/LibLazyUpdate.sol b/eth/contracts/libraries/LibLazyUpdate.sol index 0c3944f3..fa3daaaf 100644 --- a/eth/contracts/libraries/LibLazyUpdate.sol +++ b/eth/contracts/libraries/LibLazyUpdate.sol @@ -8,7 +8,7 @@ import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; import {LibStorage, GameStorage} from "./LibStorage.sol"; // Type imports -import {Planet, PlanetType, PlanetEventMetadata, PlanetEventType, ArrivalData, ArrivalType, Artifact} from "../DFTypes.sol"; +import {Planet, PlanetType, PlanetEventMetadata, PlanetEventType, ArrivalData, ArrivalType} from "../DFTypes.sol"; library LibLazyUpdate { function gs() internal pure returns (GameStorage storage) { diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 7d503526..ff0319bb 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -3,16 +3,18 @@ pragma solidity ^0.8.0; // Contract imports import {DFVerifierFacet} from "../facets/DFVerifierFacet.sol"; +import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; // Library imports import {LibGameUtils} from "./LibGameUtils.sol"; import {LibLazyUpdate} from "./LibLazyUpdate.sol"; +import {LibArtifactUtils} from "./LibArtifactUtils.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports -import {Artifact, ArtifactType, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; +import {ArtifactType, ArtifactProperties, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibPlanet { @@ -286,7 +288,8 @@ library LibPlanet { } for (uint256 i = 0; i < artifactsToAdd.length; i++) { - Artifact memory artifact = gs().artifacts[artifactsToAdd[i]]; + // artifactsToAdd[i] + ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(artifactsToAdd[i]); planet = applySpaceshipArrive(artifact, planet); } @@ -296,7 +299,7 @@ library LibPlanet { return (planet, eventsToRemove, artifactsToAdd); } - function applySpaceshipArrive(Artifact memory artifact, Planet memory planet) + function applySpaceshipArrive(ArtifactProperties memory artifact, Planet memory planet) public pure returns (Planet memory) diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index f6eeb51e..57d1d3a1 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; // Type imports -import {Planet, PlanetEventMetadata, PlanetDefaultStats, Upgrade, RevealedCoords, Player, ArrivalData, Artifact} from "../DFTypes.sol"; +import {Planet, PlanetEventMetadata, PlanetDefaultStats, Upgrade, RevealedCoords, Player, ArrivalData} from "../DFTypes.sol"; struct WhitelistStorage { bool enabled; @@ -53,7 +53,6 @@ struct GameStorage { mapping(uint256 => uint256) planetWormholes; // planetId to timestamp. For all artifacts, but only used for photoids. mapping(uint256 => uint256) planetArtifactActivationTime; - mapping(uint256 => Artifact) artifacts; // Capture Zones uint256 nextChangeBlock; } diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index f9df2f50..a1c90c51 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -309,11 +309,15 @@ export async function deployAndCut( return [diamond, diamondInit, initReceipt] as const; } -export async function deployGetterFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { +export async function deployGetterFacet( + {}, + { LibArtifactUtils }: Libraries, + hre: HardhatRuntimeEnvironment +) { const factory = await hre.ethers.getContractFactory('DFGetterFacet', { - // libraries: { - // LibGameUtils, - // }, + libraries: { + LibArtifactUtils, + }, }); const contract = await factory.deploy(); await contract.deployTransaction.wait(); @@ -411,6 +415,7 @@ export async function deployLibraries({}, hre: HardhatRuntimeEnvironment) { libraries: { LibGameUtils: LibGameUtils.address, LibLazyUpdate: LibLazyUpdate.address, + LibArtifactUtils: LibArtifactUtils.address, }, }); const LibPlanet = await LibPlanetFactory.deploy(); diff --git a/eth/test/NewDFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts similarity index 98% rename from eth/test/NewDFArtifacts.test.ts rename to eth/test/DFArtifacts.test.ts index 5d587dd4..b7ed4149 100644 --- a/eth/test/NewDFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -79,7 +79,7 @@ describe('DarkForestArtifacts', function () { world = await loadFixture(worldFixture); }); - describe.only('it tests basic artifact actions', function () { + describe('it tests basic artifact actions', function () { it('logs bits for artifact', async function () { // Must be valid options const _collectionType = '0x01'; @@ -93,7 +93,7 @@ describe('DarkForestArtifacts', function () { _biome ); const { collectionType, rarity, planetBiome, artifactType } = - await world.contract.decodeArtifact(res); + await world.contract.getArtifact(res); expect(collectionType).to.equal(Number(_collectionType)); expect(rarity).to.equal(Number(_rarity)); expect(planetBiome).to.equal(Number(_biome)); @@ -104,7 +104,7 @@ describe('DarkForestArtifacts', function () { const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types const _artifactType = ArtifactType.ShipGear; const res = await world.contract.encodeArtifact(_collectionType, 0, _artifactType, 0); - const { collectionType, artifactType } = await world.contract.decodeArtifact(res); + const { collectionType, artifactType } = await world.contract.getArtifact(res); expect(collectionType).to.equal(Number(_collectionType)); expect(artifactType).to.equal(Number(_artifactType)); }); @@ -123,7 +123,7 @@ describe('DarkForestArtifacts', function () { await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - prettyPrintToken(await world.user1Core.decodeArtifact(artifactId)); + prettyPrintToken(await world.user1Core.getArtifact(artifactId)); // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); @@ -625,7 +625,7 @@ describe('DarkForestArtifacts', function () { CollectionType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(artifactId)); + prettyPrintToken(await world.contract.getArtifact(artifactId)); activateAndConfirm(world.user1Core, from.id, artifactId, to.id); @@ -786,7 +786,7 @@ describe('DarkForestArtifacts', function () { CollectionType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); @@ -834,7 +834,7 @@ describe('DarkForestArtifacts', function () { { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -942,7 +942,7 @@ describe('DarkForestArtifacts', function () { CollectionType.Artifact, { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifact(newTokenId)); const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); await activateAndConfirm(world.user1Core, LVL3_SPACETIME_1.id, newTokenId); @@ -987,7 +987,7 @@ describe('DarkForestArtifacts', function () { CollectionType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifact(newTokenId)); await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index 62b9514d..d4192c82 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -59,7 +59,7 @@ describe('DarkForestSpaceShips', function () { }); }); - describe.only('ship transfers', function () { + describe('ship transfers', function () { it('cannot transfer your own spaceship', async function () { const motherShip = await getArtifactOnPlanetByType( world.contract, From 86592f046ac5e25f01056537e95bdbfb590ecc3f Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:26:41 +0100 Subject: [PATCH 26/55] clean --- eth/test/DFToken.test.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 eth/test/DFToken.test.ts diff --git a/eth/test/DFToken.test.ts b/eth/test/DFToken.test.ts deleted file mode 100644 index d8a41f9f..00000000 --- a/eth/test/DFToken.test.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO: Implement ERC-1155 Tests for 721(Artifact, Spaceship) 20(Silver) From 9731bfb58ec484b31272969314fe71f2bd47c4a4 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:31:27 +0100 Subject: [PATCH 27/55] clean --- eth/contracts/DFInitialize.sol | 3 +-- eth/contracts/facets/DFArtifactFacet.sol | 16 +--------------- eth/contracts/facets/DFMoveFacet.sol | 3 --- eth/contracts/libraries/LibArtifactUtils.sol | 11 ----------- eth/contracts/libraries/LibStorage.sol | 1 - 5 files changed, 2 insertions(+), 32 deletions(-) diff --git a/eth/contracts/DFInitialize.sol b/eth/contracts/DFInitialize.sol index 6392275c..b0033910 100644 --- a/eth/contracts/DFInitialize.sol +++ b/eth/contracts/DFInitialize.sol @@ -108,8 +108,7 @@ contract DFInitialize is WithStorage { string memory artifactBaseURI, InitArgs memory initArgs ) external { - // Setup the ERC721 metadata - // TODO(#1925): Add name and symbol for the artifact tokens + // Setup the ERC1155 metadata ERC1155MetadataStorage.layout().baseURI = artifactBaseURI; gs().diamondAddress = address(this); diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index fb27ed5c..bd762b9b 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -89,21 +89,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return LibArtifactUtils.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); } - // function getArtifactAtIndex(uint256 idx) public view returns (Artifact memory) { - // return gs().artifacts[tokenByIndex(idx)]; - // } - - // TODO: Add enumerable - // function getPlayerArtifactIds(address playerId) public view returns (uint256[] memory) { - // uint256 balance = balanceOf(playerId); - // uint256[] memory results = new uint256[](balance); - - // for (uint256 idx = 0; idx < balance; idx++) { - // results[idx] = tokenOfOwnerByIndex(playerId, idx); - // } - - // return results; - // } + // TODO: Add ERC1155 Enumerable Wrappers // This calls the low level _transfer call which doesn't check if the msg.sender actually owns // the tokenId. TODO: See if this is a problem. diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 7e0eeee4..84288977 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -113,7 +113,6 @@ contract DFMoveFacet is WithStorage { ArrivalType arrivalType = ArrivalType.Normal; Upgrade memory temporaryUpgrade = LibGameUtils.defaultUpgrade(); - // TODO: Add back wormhole (bool wormholePresent, uint256 distModifier) = _checkWormhole(args); if (wormholePresent) { effectiveDistTimesHundred /= distModifier; @@ -121,7 +120,6 @@ contract DFMoveFacet is WithStorage { } if (!_isSpaceshipMove(args)) { - // TODO: Add back photoid (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); if (photoidPresent) { temporaryUpgrade = newTempUpgrade; @@ -280,7 +278,6 @@ contract DFMoveFacet is WithStorage { } /** - TODO: Fix wormhole functionality. If an active wormhole is present on the origin planet, return the modified distance between the origin and target planet. diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index bf4b7a01..cd0326ab 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -71,7 +71,6 @@ library LibArtifactUtils { uint8(shipType), uint8(Biome.Unknown) ); - // TODO: Use struct naming convention for readability DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs({ tokenId: tokenId + id, discoverer: msg.sender, @@ -161,13 +160,10 @@ library LibArtifactUtils { ); if (isSpaceship(artifact.artifactType)) { - // TODO: fix Crescent functionality activateSpaceshipArtifact(locationId, artifactId, planet, artifact); } else { activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); } - - // artifact.activations++; } function activateSpaceshipArtifact( @@ -177,9 +173,6 @@ library LibArtifactUtils { ArtifactProperties memory artifact ) private { if (artifact.artifactType == ArtifactType.ShipCrescent) { - // Burn the goddam crescent - // require(artifact.activations == 0, "crescent cannot be activated more than once"); - require( planet.planetType != PlanetType.SILVER_MINE, "cannot turn a silver mine into a silver mine" @@ -240,8 +233,6 @@ library LibArtifactUtils { gs().planetActiveArtifact[locationId] = artifactId; emit ArtifactActivated(msg.sender, locationId, artifactId); - // TODO: Wormhole is broken - if (artifact.artifactType == ArtifactType.Wormhole) { require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); @@ -250,8 +241,6 @@ library LibArtifactUtils { "you can only create a wormhole to a planet you own" ); require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); - // TODO: Store some way to remember where a wormhole is. Maybe new data structure. - // artifact.wormholeTo = wormholeTo; gs().planetWormholes[locationId] = wormholeTo; } else if (artifact.artifactType == ArtifactType.BloomFilter) { require( diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 57d1d3a1..86a863b3 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -47,7 +47,6 @@ struct GameStorage { mapping(uint256 => ArrivalData) planetArrivals; // Artifact stuff mapping(uint256 => uint256[]) planetArtifacts; - // TODO: Make this an array mapping(uint256 => uint256) planetActiveArtifact; // wormhole from => to. planetWormHoles[from] = to; mapping(uint256 => uint256) planetWormholes; From 8cb35902e1df425225de746a6637467b5920df42 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:41:18 +0100 Subject: [PATCH 28/55] remove more data structures --- eth/contracts/facets/DFGetterFacet.sol | 10 +--------- eth/contracts/libraries/LibPlanet.sol | 1 - eth/contracts/libraries/LibStorage.sol | 2 -- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 7fc87e45..9ae192ab 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -78,14 +78,6 @@ contract DFGetterFacet is WithStorage { return gs().revealedCoords[key]; } - function artifactIdToPlanetId(uint256 key) public view returns (uint256) { - return gs().artifactIdToPlanetId[key]; - } - - function artifactIdToVoyageId(uint256 key) public view returns (uint256) { - return gs().artifactIdToVoyageId[key]; - } - function planetEvents(uint256 key) public view returns (PlanetEventMetadata[] memory) { return gs().planetEvents[key]; } @@ -406,7 +398,7 @@ contract DFGetterFacet is WithStorage { // ret = new ArtifactWithMetadata[](ids.length); // for (uint256 i = 0; i < ids.length; i++) { - // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifact(ids[i]); + // ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(ids[i]); // address owner; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index ff0319bb..84aa20a9 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -349,7 +349,6 @@ library LibPlanet { for (uint256 i = 0; i < 12; i++) { if (artifactIdsToAddToPlanet[i] != 0) { - gs().artifactIdToVoyageId[artifactIdsToAddToPlanet[i]] = 0; LibGameUtils._putArtifactOnPlanet(location, artifactIdsToAddToPlanet[i]); } } diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 86a863b3..58ee97a3 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -38,8 +38,6 @@ struct GameStorage { uint256 miscNonce; mapping(uint256 => Planet) planets; mapping(uint256 => RevealedCoords) revealedCoords; - mapping(uint256 => uint256) artifactIdToPlanetId; - mapping(uint256 => uint256) artifactIdToVoyageId; mapping(address => Player) players; // maps location id to planet events array mapping(uint256 => PlanetEventMetadata[]) planetEvents; From a8acca5a2fa3d8ad5961e4b5294d055d8621b76d Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 16:47:59 +0100 Subject: [PATCH 29/55] feat: give artifacts unique ids for getter purposes --- eth/contracts/libraries/LibArtifactUtils.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index cd0326ab..c2ac2c59 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -113,7 +113,7 @@ library LibArtifactUtils { ArtifactRarity rarity = LibGameUtils.artifactRarityFromPlanetLevel( levelBonus + planet.planetLevel ); - + uint128 id = uint128(gs().miscNonce++); uint256 tokenId = encodeArtifact( uint8(CollectionType.Artifact), uint8(rarity), @@ -122,7 +122,7 @@ library LibArtifactUtils { ); DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - tokenId, + tokenId + id, msg.sender, // discoverer args.planetId, rarity, From aca886eb1a0ed83973fa19e27fc22c62674c0c7b Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 29 Sep 2022 22:56:41 +0100 Subject: [PATCH 30/55] feat: encode / decode works, need to thread through --- eth/contracts/DFTypes.sol | 67 ++++++++++---- eth/contracts/Tokens.md | 10 +-- eth/contracts/facets/DFAdminFacet.sol | 6 +- eth/contracts/facets/DFArtifactFacet.sol | 28 +++++- eth/contracts/facets/DFGetterFacet.sol | 14 ++- eth/contracts/facets/DFMoveFacet.sol | 24 ++--- eth/contracts/libraries/LibArtifact.sol | 62 +++++++++++++ eth/contracts/libraries/LibArtifactUtils.sol | 95 ++++++++------------ eth/contracts/libraries/LibGameUtils.sol | 28 ++---- eth/contracts/libraries/LibPlanet.sol | 6 +- eth/contracts/libraries/LibSpaceship.sol | 54 +++++++++++ eth/contracts/libraries/LibUtils.sol | 42 +++++++++ eth/test/DFArtifacts.test.ts | 49 +++++++--- eth/test/utils/TestUtils.ts | 12 +-- 14 files changed, 342 insertions(+), 155 deletions(-) create mode 100644 eth/contracts/libraries/LibArtifact.sol create mode 100644 eth/contracts/libraries/LibSpaceship.sol create mode 100644 eth/contracts/libraries/LibUtils.sol diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 37173772..4262ea14 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -206,6 +206,16 @@ enum ArtifactType { PhotoidCannon, BloomFilter, BlackDomain, + // TODO; remove this + ShipMothership, + ShipCrescent, + ShipWhale, + ShipGear, + ShipTitan +} + +enum SpaceshipType { + Unknown, ShipMothership, ShipCrescent, ShipWhale, @@ -223,14 +233,14 @@ enum ArtifactRarity { } // for artifact getters -struct ArtifactWithMetadata { - ArtifactProperties artifact; - Upgrade upgrade; - Upgrade timeDelayedUpgrade; // for photoid canons specifically. - address owner; - uint256 locationId; // 0 if planet is not deposited into contract or is on a voyage - uint256 voyageId; // 0 is planet is not deposited into contract or is on a planet -} +// struct ArtifactWithMetadata { +// Artifact artifact; +// Upgrade upgrade; +// Upgrade timeDelayedUpgrade; // for photoid canons specifically. +// address owner; +// uint256 locationId; // 0 if planet is not deposited into contract or is on a voyage +// uint256 voyageId; // 0 is planet is not deposited into contract or is on a planet +// } enum Biome { Unknown, @@ -246,24 +256,45 @@ enum Biome { Corrupted } -enum TokenInfo { - Unknown, - CollectionType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) - ArtifactRarity, - ArtifactType, - Biome -} +// enum TokenInfo { +// Unknown, +// TokenType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) +// ArtifactRarity, +// ArtifactType, +// Biome +// } -enum CollectionType { +enum TokenType { Unknown, Artifact, Spaceship } -struct ArtifactProperties { +enum ArtifactInfo { + Unknown, + TokenType, + ArtifactRarity, + ArtifactType, + Biome +} + +struct Artifact { uint256 id; - CollectionType collectionType; + TokenType tokenType; ArtifactRarity rarity; ArtifactType artifactType; Biome planetBiome; } + +// Used for accessing properties of spaceship tokenId +enum SpaceshipInfo { + Unknown, + TokenType, + SpaceshipType +} + +struct Spaceship { + uint256 id; + TokenType tokenType; + SpaceshipType spaceshipType; +} diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 18f077c3..1d9b2bd8 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -31,7 +31,7 @@ In `DFTypes.sol`, the `TokenInfo` enum looks like this: ```js enum TokenInfo { Unknown, - CollectionType, + TokenType, ArtifactRarity, ArtifactType, Biome @@ -40,7 +40,7 @@ enum TokenInfo { Each index in `TokenInfo` refers to a chunk in the `tokenId`. -> | CollectionType | ArtifactRarity | ArtifactType | Biome | chunk5 | ... chunk 32 | +> | TokenType | ArtifactRarity | ArtifactType | Biome | chunk5 | ... chunk 32 | Each of these chunks has 256 options. So you can have 256 Artifact Rarities, Types, Biomes etc... This should be plenty, but if you need more you just use another chunk. @@ -52,7 +52,7 @@ sell either one and they have the same buffs for planets. Thus, we can represent in this information with the following encoding: -> | CollectionType.Artifact | ArtifactRarity.Epic | ArtifactType.Monolith | Biome.Ocean | ... | +> | TokenType.Artifact | ArtifactRarity.Epic | ArtifactType.Monolith | Biome.Ocean | ... | In hex: @@ -114,7 +114,7 @@ recommended in the ERC1155 Proposal. A Mothership Spaceship is represented like so -> | CollectionType.Spaceship | ArtifactRarity.Unknown | ArtifactType.ShipMothership | Biome.Unknown +> | TokenType.Spaceship | ArtifactRarity.Unknown | ArtifactType.ShipMothership | Biome.Unknown > | ... chunk 16 | uniqueId (16 chunks) | In hex: @@ -138,7 +138,7 @@ If Velorum mints their own Mothership, it would have id: `<0x02000a00><0x02>`. Velorum's Mothership has the same TokenInfo, but a unique identifier at the end. This means the contract stores my Mothership and Velorum's Mothership as completely different collections. However, because our ships share the same first 128 bits, we can still calculate the information -about the Spaceship (ArtifactType, CollectionType) just by feeding the `decodeArtifact` function the `tokenId`. +about the Spaceship (ArtifactType, TokenType) just by feeding the `decodeArtifact` function the `tokenId`. ### Transferring diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 56dd3807..33cae68d 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -13,7 +13,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Type imports -import {ArtifactProperties, SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet} from "../DFTypes.sol"; +import {Artifact, SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet} from "../DFTypes.sol"; contract DFAdminFacet is WithStorage { event AdminOwnershipChanged(uint256 loc, address newOwner); @@ -145,7 +145,7 @@ contract DFAdminFacet is WithStorage { require(LibArtifactUtils.isSpaceship(artifactType), "artifact type must be a space ship"); uint256 shipId = LibArtifactUtils.createAndPlaceSpaceship(locationId, owner, artifactType); - ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(shipId); + Artifact memory artifact = LibArtifactUtils.decodeArtifact(shipId); Planet memory planet = gs().planets[locationId]; planet = LibPlanet.applySpaceshipArrive(artifact, planet); @@ -166,7 +166,7 @@ contract DFAdminFacet is WithStorage { } function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { - ArtifactProperties memory artifact = DFArtifactFacet(address(this)).createArtifact(args); + Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); // Don't put artifact on planet if no planetId given. if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index bd762b9b..86543e16 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -10,13 +10,16 @@ import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; import {LibPermissions} from "../libraries/LibPermissions.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; +import {LibArtifact} from "../libraries/LibArtifact.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; + import {LibPlanet} from "../libraries/LibPlanet.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArtifactRarity, ArtifactType, Biome, CollectionType, DFTCreateArtifactArgs, DFPFindArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; +import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship} from "../DFTypes.sol"; import "hardhat/console.sol"; @@ -66,7 +69,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { function createArtifact(DFTCreateArtifactArgs memory args) public onlyAdminOrCore - returns (ArtifactProperties memory) + returns (Artifact memory) { require(args.tokenId >= 1, "artifact id must be positive"); @@ -76,7 +79,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return getArtifact(args.tokenId); } - function getArtifact(uint256 tokenId) public pure returns (ArtifactProperties memory) { + function getArtifact(uint256 tokenId) public pure returns (Artifact memory) { return LibArtifactUtils.decodeArtifact(tokenId); } @@ -89,6 +92,23 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return LibArtifactUtils.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); } + function testEncodeSpaceship(Spaceship memory spaceship) public pure returns (uint256) { + return LibSpaceship.encode(spaceship); + } + + function testDecodeSpaceship(uint256 shipId) public view returns (Spaceship memory) { + return LibSpaceship.decode(shipId); + } + + function testEncodeArtifact(Artifact memory artifact) public view returns (uint256) { + console.log("biome input", uint8(artifact.planetBiome)); + return LibArtifact.encode(artifact); + } + + function testDecodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + return LibArtifact.decode(artifactId); + } + // TODO: Add ERC1155 Enumerable Wrappers // This calls the low level _transfer call which doesn't check if the msg.sender actually owns @@ -272,7 +292,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ) internal virtual override { uint256 length = ids.length; for (uint256 i = 0; i < length; i++) { - ArtifactProperties memory artifact = getArtifact(ids[i]); + Artifact memory artifact = getArtifact(ids[i]); // Only core contract can transfer Spaceships if (LibArtifactUtils.isSpaceship(artifact.artifactType)) { require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 9ae192ab..9663b247 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -13,7 +13,7 @@ import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; // Type imports -import {RevealedCoords, ArtifactProperties, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, ArtifactWithMetadata, Upgrade} from "../DFTypes.sol"; +import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { @@ -340,13 +340,9 @@ contract DFGetterFacet is WithStorage { // }); // } - function getArtifactsOnPlanet(uint256 locationId) - public - view - returns (ArtifactProperties[] memory ret) - { + function getArtifactsOnPlanet(uint256 locationId) public view returns (Artifact[] memory ret) { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; - ret = new ArtifactProperties[](artifactIds.length); + ret = new Artifact[](artifactIds.length); for (uint256 i = 0; i < artifactIds.length; i++) { ret[i] = LibArtifactUtils.decodeArtifact(artifactIds[i]); } @@ -369,7 +365,7 @@ contract DFGetterFacet is WithStorage { function getActiveArtifactOnPlanet(uint256 locationId) public view - returns (ArtifactProperties memory ret) + returns (Artifact memory ret) { uint256 artifactId = gs().planetActiveArtifact[locationId]; return LibArtifactUtils.decodeArtifact(artifactId); @@ -398,7 +394,7 @@ contract DFGetterFacet is WithStorage { // ret = new ArtifactWithMetadata[](ids.length); // for (uint256 i = 0; i < ids.length; i++) { - // ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(ids[i]); + // Artifact memory artifact = LibArtifactUtils.decodeArtifact(ids[i]); // address owner; diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 84288977..5a06ee2b 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -15,7 +15,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, ArtifactProperties, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { @@ -233,7 +233,7 @@ contract DFMoveFacet is WithStorage { } } - function applySpaceshipDepart(ArtifactProperties memory artifact, Planet memory planet) + function applySpaceshipDepart(Artifact memory artifact, Planet memory planet) public view returns (Planet memory) @@ -270,9 +270,7 @@ contract DFMoveFacet is WithStorage { Undo the spaceship effects that were applied when the ship arrived on the planet. */ function _removeSpaceshipEffectsFromOriginPlanet(DFPMoveArgs memory args) private { - ArtifactProperties memory movedArtifact = LibArtifactUtils.decodeArtifact( - args.movedArtifactId - ); + Artifact memory movedArtifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); Planet memory planet = applySpaceshipDepart(movedArtifact, gs().planets[args.oldLoc]); gs().planets[args.oldLoc] = planet; } @@ -289,13 +287,9 @@ contract DFMoveFacet is WithStorage { { wormholePresent = false; - ArtifactProperties memory relevantWormhole; - ArtifactProperties memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact( - args.oldLoc - ); - ArtifactProperties memory activeArtifactTo = LibArtifactUtils.getActiveArtifact( - args.newLoc - ); + Artifact memory relevantWormhole; + Artifact memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact(args.oldLoc); + Artifact memory activeArtifactTo = LibArtifactUtils.getActiveArtifact(args.newLoc); // TODO: take the greater rarity of these, or disallow wormholes between planets that // already have a wormhole between them if ( @@ -327,9 +321,7 @@ contract DFMoveFacet is WithStorage { private returns (bool photoidPresent, Upgrade memory temporaryUpgrade) { - ArtifactProperties memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact( - args.oldLoc - ); + Artifact memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact(args.oldLoc); if ( activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= @@ -450,7 +442,7 @@ contract DFMoveFacet is WithStorage { distance: args.actualDist }); // Photoids are burned _checkPhotoid, so don't remove twice - ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); + Artifact memory artifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); if (args.movedArtifactId != 0 && artifact.artifactType != ArtifactType.PhotoidCannon) { LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); } diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol new file mode 100644 index 00000000..7b9e5b4f --- /dev/null +++ b/eth/contracts/libraries/LibArtifact.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +/** + * Library for all things Artifacts + */ + +// Contract imports +import "hardhat/console.sol"; + +// Library imports +import {LibUtils} from "./LibUtils.sol"; + +// Storage imports + +// Type imports +import {Artifact, ArtifactInfo, ArtifactRarity, ArtifactType, Biome, TokenType} from "../DFTypes.sol"; + +library LibArtifact { + /** + * @notice Create the token ID for a Artifact with the following properties: + * @param artifact Artifact + */ + function encode(Artifact memory artifact) internal view returns (uint256) { + // x << y is equivalent to the mathematical expression x * 2**y + uint256 tokenType = LibUtils.shiftLeft( + uint8(artifact.tokenType), + uint8(ArtifactInfo.TokenType) + ); + uint256 rarity = LibUtils.shiftLeft( + uint8(artifact.rarity), + uint8(ArtifactInfo.ArtifactRarity) + ); + uint256 artifactType = LibUtils.shiftLeft( + uint8(artifact.artifactType), + uint8(ArtifactInfo.ArtifactType) + ); + uint256 biome = LibUtils.shiftLeft(uint8(artifact.planetBiome), uint8(ArtifactInfo.Biome)); + return tokenType + rarity + artifactType + biome; + } + + function decode(uint256 artifactId) internal pure returns (Artifact memory) { + bytes memory _b = abi.encodePacked(artifactId); + uint8 tokenIdx = uint8(ArtifactInfo.TokenType) - 1; // account for Unknown at 0 + uint8 rarityIdx = uint8(ArtifactInfo.ArtifactRarity) - 1; + uint8 typeIdx = uint8(ArtifactInfo.ArtifactType) - 1; + uint8 biomeIdx = uint8(ArtifactInfo.Biome) - 1; + + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + uint8 rarity = uint8(LibUtils.calculateByteUInt(_b, rarityIdx, rarityIdx)); + uint8 artifactType = uint8(LibUtils.calculateByteUInt(_b, typeIdx, typeIdx)); + uint8 biome = uint8(LibUtils.calculateByteUInt(_b, biomeIdx, biomeIdx)); + return + Artifact({ + id: artifactId, + tokenType: TokenType(tokenType), + rarity: ArtifactRarity(rarity), + artifactType: ArtifactType(artifactType), + planetBiome: Biome(biome) + }); + } +} diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index c2ac2c59..996f9c5c 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -7,12 +7,13 @@ import {DFGetterFacet} from "../facets/DFGetterFacet.sol"; // Library imports import {LibGameUtils} from "./LibGameUtils.sol"; +import {LibUtils} from "./LibUtils.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, CollectionType, DFPFindArtifactArgs, DFTCreateArtifactArgs, ArtifactProperties, TokenInfo} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, TokenType, DFPFindArtifactArgs, DFTCreateArtifactArgs, Artifact, ArtifactInfo} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibArtifactUtils { @@ -66,7 +67,7 @@ library LibArtifactUtils { // require(gs().miscNonce < MAX UINT 128) but won't happen. uint128 id = uint128(gs().miscNonce++); uint256 tokenId = encodeArtifact( - uint8(CollectionType.Spaceship), + uint8(TokenType.Spaceship), uint8(ArtifactRarity.Unknown), uint8(shipType), uint8(Biome.Unknown) @@ -82,7 +83,7 @@ library LibArtifactUtils { // Only used for spaceships controller: owner }); - ArtifactProperties memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( + Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); LibGameUtils._putArtifactOnPlanet(planetId, foundArtifact.id); @@ -115,7 +116,7 @@ library LibArtifactUtils { ); uint128 id = uint128(gs().miscNonce++); uint256 tokenId = encodeArtifact( - uint8(CollectionType.Artifact), + uint8(TokenType.Artifact), uint8(rarity), uint8(artifactType), uint8(biome) @@ -132,7 +133,7 @@ library LibArtifactUtils { address(0) ); - ArtifactProperties memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( + Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( createArtifactArgs ); @@ -152,7 +153,7 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - ArtifactProperties memory artifact = decodeArtifact(artifactId); + Artifact memory artifact = decodeArtifact(artifactId); require( LibGameUtils.isArtifactOnPlanet(locationId, artifactId), @@ -170,7 +171,7 @@ library LibArtifactUtils { uint256 locationId, uint256 artifactId, Planet storage planet, - ArtifactProperties memory artifact + Artifact memory artifact ) private { if (artifact.artifactType == ArtifactType.ShipCrescent) { require( @@ -209,21 +210,21 @@ library LibArtifactUtils { uint256 artifactId, uint256 wormholeTo, Planet storage planet, - ArtifactProperties memory artifact + Artifact memory artifact ) private { require( planet.owner == msg.sender, "you must own the planet you are activating an artifact on" ); require( - getActiveArtifact(locationId).collectionType == CollectionType.Unknown, + getActiveArtifact(locationId).tokenType == TokenType.Unknown, "there is already an active artifact on this planet" ); require(!planet.destroyed, "planet is destroyed"); uint256 length = gs().planetArtifacts[locationId].length; require( - getPlanetArtifact(locationId, artifactId).collectionType != CollectionType.Unknown, + getPlanetArtifact(locationId, artifactId).tokenType != TokenType.Unknown, "this artifact is not on this planet" ); @@ -282,10 +283,10 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); - ArtifactProperties memory artifact = getActiveArtifact(locationId); + Artifact memory artifact = getActiveArtifact(locationId); require( - artifact.collectionType != CollectionType.Unknown, + artifact.tokenType != TokenType.Unknown, "this artifact is not activated on this planet" ); @@ -322,7 +323,7 @@ library LibArtifactUtils { ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); - ArtifactProperties memory artifact = decodeArtifact(artifactId); + Artifact memory artifact = decodeArtifact(artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" @@ -345,12 +346,9 @@ library LibArtifactUtils { ); require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); - ArtifactProperties memory artifact = getPlanetArtifact(locationId, artifactId); + Artifact memory artifact = getPlanetArtifact(locationId, artifactId); // TODO: Write is initialized function. - require( - artifact.collectionType != CollectionType.Unknown, - "this artifact is not on this planet" - ); + require(artifact.tokenType != TokenType.Unknown, "this artifact is not on this planet"); require( planet.planetLevel > uint256(artifact.rarity), @@ -382,7 +380,7 @@ library LibArtifactUtils { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - ArtifactProperties memory artifact = decodeArtifact(artifactIds[i]); + Artifact memory artifact = decodeArtifact(artifactIds[i]); if ( // TODO: Gear is broken artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller @@ -399,22 +397,6 @@ library LibArtifactUtils { artifactType >= ArtifactType.ShipMothership && artifactType <= ArtifactType.ShipTitan; } - /** - * @notice calculate amount of bits to shift left - * @param index number of 1 byte words to shift from left - * @return shift length of left shift - */ - function calcBitShift(uint8 index) internal pure returns (uint8) { - uint8 maxVal = 32; - - require(index <= maxVal, "shift index is too high"); - require(index > 0, "shift index is too low"); - - uint256 bin = 8; - uint256 shift = 256; - return uint8(shift - (bin * index)); - } - /** * @notice Create the collection ID for a given artifact * @param _collectionType type of artifact @@ -429,34 +411,35 @@ library LibArtifactUtils { uint256 _artifactType, uint256 _biome ) public pure returns (uint256) { - uint256 collectionType = _collectionType << calcBitShift(uint8(TokenInfo.CollectionType)); - uint256 rarity = _rarity << calcBitShift(uint8(TokenInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << calcBitShift(uint8(TokenInfo.ArtifactType)); - uint256 biome = _biome << calcBitShift(uint8(TokenInfo.Biome)); - return collectionType + rarity + artifactType + biome; + uint256 tokenType = _collectionType << LibUtils.calcBitShift(uint8(ArtifactInfo.TokenType)); + uint256 rarity = _rarity << LibUtils.calcBitShift(uint8(ArtifactInfo.ArtifactRarity)); + uint256 artifactType = _artifactType << + LibUtils.calcBitShift(uint8(ArtifactInfo.ArtifactType)); + uint256 biome = _biome << LibUtils.calcBitShift(uint8(ArtifactInfo.Biome)); + return tokenType + rarity + artifactType + biome; } /** - * @notice Fetch the ArtifactProperties for the given id + * @notice Fetch the Artifact for the given id * @param artifactId type of artifact */ - function decodeArtifact(uint256 artifactId) public pure returns (ArtifactProperties memory) { + function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in TokenInfo. + // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. - // Note: Bit shifting requires an index greater than zero. This is why the TokenInfo has - // Unknown as the zero property, so calcBitShift(TokenInfo.Level) is correct. + // Note: Bit shifting requires an index greater than zero. This is why the ArtifactInfo has + // Unknown as the zero property, so calcBitShift(ArtifactInfo.Level) is correct. // As a consequence, we need to // offset fetching the relevant byte from the artifactId by 1. // However - uint8 collectionType = uint8(_b[uint8(TokenInfo.CollectionType) - 1]); - uint8 rarity = uint8(_b[uint8(TokenInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(TokenInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(TokenInfo.Biome) - 1]); + uint8 tokenType = uint8(_b[uint8(ArtifactInfo.TokenType) - 1]); + uint8 rarity = uint8(_b[uint8(ArtifactInfo.ArtifactRarity) - 1]); + uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); + uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); - ArtifactProperties memory a = ArtifactProperties({ + Artifact memory a = Artifact({ id: artifactId, - collectionType: CollectionType(collectionType), + tokenType: TokenType(tokenType), rarity: ArtifactRarity(rarity), artifactType: ArtifactType(artifactType), planetBiome: Biome(biome) @@ -467,18 +450,18 @@ library LibArtifactUtils { // if the given planet has an activated artifact on it, then return the artifact // otherwise, return a 'null artifact' - function getActiveArtifact(uint256 locationId) public view returns (ArtifactProperties memory) { + function getActiveArtifact(uint256 locationId) public view returns (Artifact memory) { uint256 artifactId = gs().planetActiveArtifact[locationId]; if (artifactId != 0) return decodeArtifact(artifactId); return _nullArtifactProperties(); } - function _nullArtifactProperties() private pure returns (ArtifactProperties memory) { + function _nullArtifactProperties() private pure returns (Artifact memory) { return - ArtifactProperties( + Artifact( 0, - CollectionType.Unknown, + TokenType.Unknown, ArtifactRarity.Unknown, ArtifactType.Unknown, Biome.Unknown @@ -490,7 +473,7 @@ library LibArtifactUtils { function getPlanetArtifact(uint256 locationId, uint256 artifactId) public view - returns (ArtifactProperties memory) + returns (Artifact memory) { for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 1189a7df..2053cdee 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -6,11 +6,12 @@ import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; // Library imports import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; +import {LibUtils} from "./LibUtils.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; -import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, ArtifactProperties, ArtifactType, CollectionType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; +import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactType, TokenType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibGameUtils { @@ -26,17 +27,6 @@ library LibGameUtils { return LibStorage.snarkConstants(); } - // inclusive on both ends - function _calculateByteUInt( - bytes memory _b, - uint256 _startByte, - uint256 _endByte - ) public pure returns (uint256 _byteUInt) { - for (uint256 i = _startByte; i <= _endByte; i++) { - _byteUInt += uint256(uint8(_b[i])) * (256**(_endByte - i)); - } - } - function _locationIdValid(uint256 _loc) public view returns (bool) { return (_loc < (21888242871839275222246405745257275088548364400416034343698204186575808495617 / @@ -88,7 +78,7 @@ library LibGameUtils { bytes memory _b = abi.encodePacked(_location); // get the uint value of byte 4 - 6 - uint256 _planetLevelUInt = _calculateByteUInt(_b, 4, 6); + uint256 _planetLevelUInt = LibUtils.calculateByteUInt(_b, 4, 6); uint256 level; // reverse-iterate thresholds and return planet type accordingly @@ -237,11 +227,7 @@ library LibGameUtils { }); } - function timeDelayUpgrade(ArtifactProperties memory artifact) - public - pure - returns (Upgrade memory) - { + function timeDelayUpgrade(Artifact memory artifact) public pure returns (Upgrade memory) { if (artifact.artifactType == ArtifactType.PhotoidCannon) { uint256[6] memory range = [uint256(100), 200, 200, 200, 200, 200]; uint256[6] memory speedBoosts = [uint256(100), 500, 1000, 1500, 2000, 2500]; @@ -272,11 +258,7 @@ library LibGameUtils { }); } - function _getUpgradeForArtifact(ArtifactProperties memory artifact) - public - pure - returns (Upgrade memory) - { + function _getUpgradeForArtifact(Artifact memory artifact) public pure returns (Upgrade memory) { if (artifact.artifactType == ArtifactType.PlanetaryShield) { uint256[6] memory defenseMultipliersPerRarity = [uint256(100), 150, 200, 300, 450, 650]; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 84aa20a9..934a8c96 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -14,7 +14,7 @@ import {LibArtifactUtils} from "./LibArtifactUtils.sol"; import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports -import {ArtifactType, ArtifactProperties, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; +import {ArtifactType, Artifact, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibPlanet { @@ -289,7 +289,7 @@ library LibPlanet { for (uint256 i = 0; i < artifactsToAdd.length; i++) { // artifactsToAdd[i] - ArtifactProperties memory artifact = LibArtifactUtils.decodeArtifact(artifactsToAdd[i]); + Artifact memory artifact = LibArtifactUtils.decodeArtifact(artifactsToAdd[i]); planet = applySpaceshipArrive(artifact, planet); } @@ -299,7 +299,7 @@ library LibPlanet { return (planet, eventsToRemove, artifactsToAdd); } - function applySpaceshipArrive(ArtifactProperties memory artifact, Planet memory planet) + function applySpaceshipArrive(Artifact memory artifact, Planet memory planet) public pure returns (Planet memory) diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol new file mode 100644 index 00000000..32578b37 --- /dev/null +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +/** + * Library for all things Spaceships + */ + +// Contract imports +import "hardhat/console.sol"; + +// Library imports +import {LibUtils} from "./LibUtils.sol"; + +// Storage imports + +// Type imports +import {Spaceship, SpaceshipInfo, SpaceshipType, TokenType} from "../DFTypes.sol"; + +library LibSpaceship { + /** + * @notice Create the token ID for a Spaceship with the following properties: + * @param spaceship Spaceship + */ + function encode(Spaceship memory spaceship) internal pure returns (uint256) { + // x << y is equivalent to the mathematical expression x * 2**y + uint256 tokenType = LibUtils.shiftLeft( + uint8(spaceship.tokenType), + uint8(SpaceshipInfo.TokenType) + ); + uint256 shipType = LibUtils.shiftLeft( + uint8(spaceship.spaceshipType), + uint8(SpaceshipInfo.SpaceshipType) + ); + return tokenType + shipType; + } + + function decode(uint256 spaceshipId) internal view returns (Spaceship memory) { + bytes memory _b = abi.encodePacked(spaceshipId); + uint8 tokenIdx = uint8(SpaceshipInfo.TokenType) - 1; + uint8 shipInfoIdx = uint8(SpaceshipInfo.SpaceshipType) - 1; + console.log("ship info indx", shipInfoIdx); + + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + uint8 shipType = uint8(LibUtils.calculateByteUInt(_b, shipInfoIdx, shipInfoIdx)); + console.log("tokenType", tokenType); + console.log("shipType", shipType); + return + Spaceship({ + id: spaceshipId, + tokenType: TokenType(tokenType), + spaceshipType: SpaceshipType(shipType) + }); + } +} diff --git a/eth/contracts/libraries/LibUtils.sol b/eth/contracts/libraries/LibUtils.sol new file mode 100644 index 00000000..7363a683 --- /dev/null +++ b/eth/contracts/libraries/LibUtils.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +/** + * General Purpose Utilities. Must be pure functions. + */ + +library LibUtils { + /** + * @notice calculate amount of bits to shift left + * @param index number of 1 byte words to shift from left + * @return shift length of left shift + */ + function calcBitShift(uint8 index) internal pure returns (uint8) { + uint8 maxVal = 32; + + require(index <= maxVal, "shift index is too high"); + require(index > 0, "shift index is too low"); + + uint256 bin = 8; + uint256 shift = 256; + return uint8(shift - (bin * index)); + } + + // inclusive on both ends + function calculateByteUInt( + bytes memory _b, + uint256 _startByte, + uint256 _endByte + ) internal pure returns (uint256 _byteUInt) { + for (uint256 i = _startByte; i <= _endByte; i++) { + _byteUInt += uint256(uint8(_b[i])) * (256**(_endByte - i)); + } + } + + /** + * @notice x << y where y = 2^calcBitShift(index)) + */ + function shiftLeft(uint8 value, uint8 index) internal pure returns (uint256) { + return (value * 2**calcBitShift(index)); + } +} diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index b7ed4149..594faf70 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -1,4 +1,4 @@ -import { ArtifactRarity, ArtifactType, Biome, CollectionType } from '@dfdao/types'; +import { ArtifactRarity, ArtifactType, Biome, TokenType } from '@dfdao/types'; import { loadFixture, mine } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import hre from 'hardhat'; @@ -80,7 +80,7 @@ describe('DarkForestArtifacts', function () { }); describe('it tests basic artifact actions', function () { - it('logs bits for artifact', async function () { + it.only('logs bits for artifact old', async function () { // Must be valid options const _collectionType = '0x01'; const _rarity = ArtifactRarity.Legendary; @@ -92,21 +92,46 @@ describe('DarkForestArtifacts', function () { _artifactType, _biome ); - const { collectionType, rarity, planetBiome, artifactType } = - await world.contract.getArtifact(res); - expect(collectionType).to.equal(Number(_collectionType)); + const { tokenType, rarity, planetBiome, artifactType } = await world.contract.getArtifact( + res + ); + expect(tokenType).to.equal(Number(_collectionType)); expect(rarity).to.equal(Number(_rarity)); expect(planetBiome).to.equal(Number(_biome)); expect(artifactType).to.equal(Number(_artifactType)); }); - it('logs bits for spaceship', async function () { + it('encodes and decodes artifact', async function () { // Must be valid options - const _collectionType = '0x02'; // TODO: add CollectionType to @dfdao/types - const _artifactType = ArtifactType.ShipGear; - const res = await world.contract.encodeArtifact(_collectionType, 0, _artifactType, 0); - const { collectionType, artifactType } = await world.contract.getArtifact(res); - expect(collectionType).to.equal(Number(_collectionType)); - expect(artifactType).to.equal(Number(_artifactType)); + const tokenType = TokenType.Artifact; + const rarity = ArtifactRarity.Legendary; + const artifactType = ArtifactType.Colossus; + const planetBiome = Biome.DESERT; + const res = await world.contract.testEncodeArtifact({ + id: 0, + tokenType, + rarity, + artifactType, + planetBiome, + }); + const a = await world.contract.getArtifact(res); + expect(tokenType).to.equal(Number(a.tokenType)); + expect(rarity).to.equal(Number(a.rarity)); + expect(artifactType).to.equal(Number(a.artifactType)); + expect(planetBiome).to.equal(Number(a.planetBiome)); + }); + it('encodes and decodes spaceship', async function () { + // Must be valid options + const tokenType = TokenType.Spaceship; + const spaceshipType = 2; + const res = await world.contract.testEncodeSpaceship({ + id: 0, + tokenType, + spaceshipType, + }); + const a = await world.contract.testDecodeSpaceship(res); + console.log(a); + expect(tokenType).to.equal(Number(a.tokenType)); + expect(spaceshipType).to.equal(Number(a.spaceshipType)); }); // This test will fail if the artifact is special. it('be able to mint artifact on ruins, activate/buff, deactivate/debuff', async function () { diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 565df44c..d0ce72cd 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -1,5 +1,5 @@ import type { DarkForest } from '@dfdao/contracts/typechain'; -import { ArtifactPropertiesStructOutput } from '@dfdao/contracts/typechain/contracts/DFToken'; +import { ArtifactStructOutput } from '@dfdao/contracts/typechain/hardhat-diamond-abi/HardhatDiamondABI.sol/DarkForest'; import { modPBigInt } from '@dfdao/hashing'; import { buildContractCallArgs, @@ -15,8 +15,8 @@ import { ArtifactTypeNames, Biome, BiomeNames, - CollectionType, - CollectionTypeNames, + TokenType, + TokenTypeNames, } from '@dfdao/types'; import { bigIntFromKey } from '@dfdao/whitelist'; import { mine, time } from '@nomicfoundation/hardhat-network-helpers'; @@ -45,9 +45,9 @@ export function hexToBigNumber(hex: string): BigNumber { return BigNumber.from(`0x${hex}`); } -export function prettyPrintToken(token: ArtifactPropertiesStructOutput) { +export function prettyPrintToken(token: ArtifactStructOutput) { console.log( - `~Token~\nCollection: ${CollectionTypeNames[token.collectionType]}\nRarity: ${ + `~Token~\nCollection: ${TokenTypeNames[token.tokenType]}\nRarity: ${ ArtifactRarityNames[token.rarity] }\nType: ${ArtifactTypeNames[token.artifactType]}\nBiome: ${BiomeNames[token.planetBiome]}` ); @@ -333,7 +333,7 @@ export async function createArtifact( owner: string, planet: TestLocation, type: ArtifactType, - collectionType = CollectionType.Artifact, + collectionType = TokenType.Artifact, { rarity, biome }: { rarity?: ArtifactRarity; biome?: Biome } = {} ) { rarity ||= ArtifactRarity.Common; From 8e96b8798f21675f7254a23efcdf3b9053204293 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 30 Sep 2022 11:40:08 +0100 Subject: [PATCH 31/55] rename collection type --- packages/types/src/token.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/types/src/token.ts b/packages/types/src/token.ts index 258e48da..0096ec49 100644 --- a/packages/types/src/token.ts +++ b/packages/types/src/token.ts @@ -1,18 +1,18 @@ import { Abstract } from './utility'; -export type CollectionType = Abstract; +export type TokenType = Abstract; -export const CollectionType = { - Unknown: 0 as CollectionType, - Artifact: 1 as CollectionType, - Spaceship: 2 as CollectionType, +export const TokenType = { + Unknown: 0 as TokenType, + Artifact: 1 as TokenType, + Spaceship: 2 as TokenType, } as const; /** - * Mapping from CollectionType to pretty-printed names. + * Mapping from TokenType to pretty-printed names. */ -export const CollectionTypeNames = { - [CollectionType.Unknown]: 'Unknown', - [CollectionType.Artifact]: 'Artifact', - [CollectionType.Spaceship]: 'Spaceship', +export const TokenTypeNames = { + [TokenType.Unknown]: 'Unknown', + [TokenType.Artifact]: 'Artifact', + [TokenType.Spaceship]: 'Spaceship', } as const; From 9545d21a7ef68716a64589ea44e3eb8e6dc62d6b Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 30 Sep 2022 17:57:56 +0100 Subject: [PATCH 32/55] feat: tests pass with spaceships as separate entities --- eth/contracts/facets/DFAdminFacet.sol | 36 ++++-- eth/contracts/facets/DFArtifactFacet.sol | 52 +++++--- eth/contracts/facets/DFGetterFacet.sol | 38 ++++-- eth/contracts/facets/DFMoveFacet.sol | 38 +++--- eth/contracts/libraries/LibArtifact.sol | 9 +- eth/contracts/libraries/LibArtifactUtils.sol | 127 +++++++++---------- eth/contracts/libraries/LibGameUtils.sol | 40 +++++- eth/contracts/libraries/LibPlanet.sol | 33 +++-- eth/contracts/libraries/LibSpaceship.sol | 14 +- eth/contracts/libraries/LibStorage.sol | 3 +- eth/tasks/deploy.ts | 2 +- eth/test/DFArtifacts.test.ts | 33 ++--- eth/test/DFMove.test.ts | 41 +++--- eth/test/DFSpaceShips.test.ts | 37 +++--- eth/test/utils/TestUtils.ts | 11 ++ packages/types/src/index.ts | 1 + 16 files changed, 317 insertions(+), 198 deletions(-) diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 33cae68d..dd1877b9 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -2,10 +2,12 @@ pragma solidity ^0.8.0; // Library imports -import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibArtifact} from "../libraries/LibArtifact.sol"; +import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {LibPermissions} from "../libraries/LibPermissions.sol"; import {LibPlanet} from "../libraries/LibPlanet.sol"; -import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; @@ -13,12 +15,12 @@ import {WithStorage} from "../libraries/LibStorage.sol"; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Type imports -import {Artifact, SpaceType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet} from "../DFTypes.sol"; +import {Artifact, SpaceType, Spaceship, SpaceshipType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet, TokenType} from "../DFTypes.sol"; contract DFAdminFacet is WithStorage { event AdminOwnershipChanged(uint256 loc, address newOwner); event AdminPlanetCreated(uint256 loc); - event AdminGiveSpaceship(uint256 loc, address owner, ArtifactType artifactType); + event AdminGiveSpaceship(uint256 loc, address owner, SpaceshipType shipType); event PauseStateChanged(bool paused); event AdminArtifactCreated(address player, uint256 artifactId, uint256 loc); @@ -139,20 +141,19 @@ contract DFAdminFacet is WithStorage { function adminGiveSpaceShip( uint256 locationId, address owner, - ArtifactType artifactType + SpaceshipType shipType ) public onlyAdmin { require(gs().planets[locationId].isInitialized, "planet is not initialized"); - require(LibArtifactUtils.isSpaceship(artifactType), "artifact type must be a space ship"); - uint256 shipId = LibArtifactUtils.createAndPlaceSpaceship(locationId, owner, artifactType); - Artifact memory artifact = LibArtifactUtils.decodeArtifact(shipId); + uint256 shipId = LibArtifactUtils.createAndPlaceSpaceship(locationId, owner, shipType); + Spaceship memory spaceship = LibSpaceship.decode(shipId); Planet memory planet = gs().planets[locationId]; - planet = LibPlanet.applySpaceshipArrive(artifact, planet); + planet = LibPlanet.applySpaceshipArrive(spaceship, planet); gs().planets[locationId] = planet; - emit AdminGiveSpaceship(locationId, owner, artifactType); + emit AdminGiveSpaceship(locationId, owner, shipType); } function adminInitializePlanet(uint256 locationId, uint256 perlin) public onlyAdmin { @@ -166,7 +167,20 @@ contract DFAdminFacet is WithStorage { } function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { - Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact(args); + // Note: calling this in tests should supply Diamond address as args.owner + uint256 tokenId = LibArtifact.encode( + Artifact({ + id: 0, + tokenType: TokenType.Artifact, + rarity: args.rarity, + artifactType: args.artifactType, + planetBiome: args.biome + }) + ); + Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact( + tokenId, + args.owner + ); // Don't put artifact on planet if no planetId given. if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 86543e16..d668bf90 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -19,7 +19,7 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship} from "../DFTypes.sol"; +import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship, SpaceshipType} from "../DFTypes.sol"; import "hardhat/console.sol"; @@ -66,21 +66,39 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { _; } - function createArtifact(DFTCreateArtifactArgs memory args) + function createArtifact(uint256 tokenId, address owner) public onlyAdminOrCore returns (Artifact memory) { - require(args.tokenId >= 1, "artifact id must be positive"); + require(tokenId >= 1, "token id must be positive"); + require(LibArtifact.isArtifact(tokenId), "wrong token type"); + // Account, Id, Amount, Data + _mint(owner, tokenId, 1, ""); + + return getArtifact(tokenId); + } + + function createSpaceship(uint256 tokenId, address owner) + public + onlyAdminOrCore + returns (Spaceship memory) + { + require(tokenId >= 1, "token id must be positive"); + require(LibSpaceship.isShip(tokenId), "wrong token type"); // Account, Id, Amount, Data - _mint(args.owner, args.tokenId, 1, ""); + _mint(owner, tokenId, 1, ""); - return getArtifact(args.tokenId); + return getSpaceship(tokenId); } - function getArtifact(uint256 tokenId) public pure returns (Artifact memory) { - return LibArtifactUtils.decodeArtifact(tokenId); + function getArtifact(uint256 artifactId) public pure returns (Artifact memory) { + return LibArtifact.decode(artifactId); + } + + function getSpaceship(uint256 shipId) public pure returns (Spaceship memory) { + return LibSpaceship.decode(shipId); } function encodeArtifact( @@ -96,12 +114,11 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return LibSpaceship.encode(spaceship); } - function testDecodeSpaceship(uint256 shipId) public view returns (Spaceship memory) { + function testDecodeSpaceship(uint256 shipId) public pure returns (Spaceship memory) { return LibSpaceship.decode(shipId); } - function testEncodeArtifact(Artifact memory artifact) public view returns (uint256) { - console.log("biome input", uint8(artifact.planetBiome)); + function testEncodeArtifact(Artifact memory artifact) public pure returns (uint256) { return LibArtifact.encode(artifact); } @@ -127,7 +144,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { } } - function doesArtifactExist(address owner, uint256 tokenId) public view returns (bool) { + function tokenExists(address owner, uint256 tokenId) public view returns (bool) { return balanceOf(owner, tokenId) > 0; } @@ -237,7 +254,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id1 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); emit ArtifactFound(msg.sender, id1, locationId); } @@ -246,7 +263,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id2 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipCrescent + SpaceshipType.ShipCrescent ); emit ArtifactFound(msg.sender, id2, locationId); } @@ -255,7 +272,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id3 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipWhale + SpaceshipType.ShipWhale ); emit ArtifactFound(msg.sender, id3, locationId); } @@ -264,7 +281,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id4 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipGear + SpaceshipType.ShipGear ); emit ArtifactFound(msg.sender, id4, locationId); } @@ -273,7 +290,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256 id5 = LibArtifactUtils.createAndPlaceSpaceship( locationId, owner, - ArtifactType.ShipTitan + SpaceshipType.ShipTitan ); emit ArtifactFound(msg.sender, id5, locationId); @@ -292,9 +309,8 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ) internal virtual override { uint256 length = ids.length; for (uint256 i = 0; i < length; i++) { - Artifact memory artifact = getArtifact(ids[i]); // Only core contract can transfer Spaceships - if (LibArtifactUtils.isSpaceship(artifact.artifactType)) { + if (LibSpaceship.isShip(ids[i])) { require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); } } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 9663b247..0865c634 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -5,15 +5,17 @@ pragma solidity ^0.8.0; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Library imports -import {LibPermissions} from "../libraries/LibPermissions.sol"; -import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {LibArtifact} from "../libraries/LibArtifact.sol"; import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; +import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; // Type imports -import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade} from "../DFTypes.sol"; +import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade, Spaceship} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { @@ -94,6 +96,10 @@ contract DFGetterFacet is WithStorage { return gs().planetArtifacts[key]; } + function planetSpaceships(uint256 key) public view returns (uint256[] memory) { + return gs().planetSpaceships[key]; + } + // ADDITIONAL UTILITY GETTERS function getNPlanets() public view returns (uint256) { @@ -344,22 +350,36 @@ contract DFGetterFacet is WithStorage { uint256[] memory artifactIds = gs().planetArtifacts[locationId]; ret = new Artifact[](artifactIds.length); for (uint256 i = 0; i < artifactIds.length; i++) { - ret[i] = LibArtifactUtils.decodeArtifact(artifactIds[i]); + ret[i] = LibArtifact.decode(artifactIds[i]); } return ret; } - function artifactExistsOnPlanet(uint256 locationId, uint256 artifactId) + function getSpaceshipsOnPlanet(uint256 locationId) public view - returns (bool) + returns (Spaceship[] memory ret) { - bool hasArtifact = false; + uint256[] memory tokenIds = gs().planetSpaceships[locationId]; + ret = new Spaceship[](tokenIds.length); + for (uint256 i = 0; i < tokenIds.length; i++) { + ret[i] = LibSpaceship.decode(tokenIds[i]); + } + return ret; + } + + // Combo on Ships and Artifacts + function tokenExistsOnPlanet(uint256 locationId, uint256 tokenId) public view returns (bool) { + bool hasToken = false; uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - if (artifactIds[i] == artifactId) hasArtifact = true; + if (artifactIds[i] == tokenId) hasToken = true; + } + uint256[] memory shipIds = gs().planetSpaceships[locationId]; + for (uint256 i = 0; i < shipIds.length; i++) { + if (shipIds[i] == tokenId) hasToken = true; } - return hasArtifact; + return hasToken; } function getActiveArtifactOnPlanet(uint256 locationId) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 5a06ee2b..9a884847 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -6,10 +6,12 @@ import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFArtifactFacet} from "./DFArtifactFacet.sol"; // Library imports +import {LibArtifact} from "../libraries/LibArtifact.sol"; import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; import {LibPlanet} from "../libraries/LibPlanet.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; @@ -200,8 +202,7 @@ contract DFMoveFacet is WithStorage { require(args.popMoved == 0, "ship moves must move 0 energy"); require(args.silverMoved == 0, "ship moves must move 0 silver"); require( - // TODO: better name for doesArtifactExist function - DFArtifactFacet(address(this)).doesArtifactExist(msg.sender, args.movedArtifactId), + DFArtifactFacet(address(this)).tokenExists(msg.sender, args.movedArtifactId), "you can only move your own ships" ); } else { @@ -227,8 +228,10 @@ contract DFMoveFacet is WithStorage { if (args.movedArtifactId != 0) { require( - gs().planetArtifacts[args.newLoc].length < 5, - "too many artifacts on this planet" + gs().planetArtifacts[args.newLoc].length + + gs().planetSpaceships[args.newLoc].length < + 5, + "too many tokens on this planet" ); } } @@ -405,11 +408,8 @@ contract DFMoveFacet is WithStorage { } } - function _isSpaceshipMove(DFPMoveArgs memory args) private view returns (bool) { - return - LibArtifactUtils.isSpaceship( - LibArtifactUtils.decodeArtifact(args.movedArtifactId).artifactType - ); + function _isSpaceshipMove(DFPMoveArgs memory args) private pure returns (bool) { + return LibSpaceship.isShip(args.movedArtifactId); } function _createArrival(DFPCreateArrivalArgs memory args) private { @@ -422,9 +422,9 @@ contract DFMoveFacet is WithStorage { planet.range, planet.populationCap ); - bool isSpaceship = LibArtifactUtils.isSpaceship( - LibArtifactUtils.decodeArtifact(args.movedArtifactId).artifactType - ); + + bool isSpaceship = LibSpaceship.isShip(args.movedArtifactId); + // space ship moves are implemented as 0-energy moves require(popArriving > 0 || isSpaceship, "Not enough forces to make move"); require(isSpaceship ? args.popMoved == 0 : true, "spaceship moves must be 0 energy moves"); @@ -441,10 +441,16 @@ contract DFMoveFacet is WithStorage { carriedArtifactId: args.movedArtifactId, distance: args.actualDist }); - // Photoids are burned _checkPhotoid, so don't remove twice - Artifact memory artifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); - if (args.movedArtifactId != 0 && artifact.artifactType != ArtifactType.PhotoidCannon) { - LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); + // Photoids are burned in _checkPhotoid, so don't remove twice + if (args.movedArtifactId != 0) { + if (isSpaceship) { + LibGameUtils._takeSpaceshipOffPlanet(args.oldLoc, args.movedArtifactId); + } else { + Artifact memory artifact = LibArtifact.decode(args.movedArtifactId); + if (artifact.artifactType != ArtifactType.PhotoidCannon) { + LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); + } + } } } diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 7b9e5b4f..f09a0435 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -21,7 +21,7 @@ library LibArtifact { * @notice Create the token ID for a Artifact with the following properties: * @param artifact Artifact */ - function encode(Artifact memory artifact) internal view returns (uint256) { + function encode(Artifact memory artifact) internal pure returns (uint256) { // x << y is equivalent to the mathematical expression x * 2**y uint256 tokenType = LibUtils.shiftLeft( uint8(artifact.tokenType), @@ -59,4 +59,11 @@ library LibArtifact { planetBiome: Biome(biome) }); } + + function isArtifact(uint256 tokenId) internal pure returns (bool) { + bytes memory _b = abi.encodePacked(tokenId); + uint8 tokenIdx = uint8(ArtifactInfo.TokenType) - 1; + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + return (TokenType(tokenType) == TokenType.Artifact); + } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 996f9c5c..9804d113 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -6,14 +6,16 @@ import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; import {DFGetterFacet} from "../facets/DFGetterFacet.sol"; // Library imports +import {LibArtifact} from "./LibArtifact.sol"; import {LibGameUtils} from "./LibGameUtils.sol"; +import {LibSpaceship} from "./LibSpaceship.sol"; import {LibUtils} from "./LibUtils.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports -import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, TokenType, DFPFindArtifactArgs, DFTCreateArtifactArgs, Artifact, ArtifactInfo} from "../DFTypes.sol"; +import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, TokenType, DFPFindArtifactArgs, DFTCreateArtifactArgs, Artifact, ArtifactInfo, Spaceship, SpaceshipType} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibArtifactUtils { @@ -61,34 +63,22 @@ library LibArtifactUtils { function createAndPlaceSpaceship( uint256 planetId, address owner, - ArtifactType shipType + SpaceshipType shipType ) public returns (uint256) { - require(shipType <= ArtifactType.ShipTitan && shipType >= ArtifactType.ShipMothership); + require(shipType != SpaceshipType.Unknown, "incorrect ship type"); // require(gs().miscNonce < MAX UINT 128) but won't happen. uint128 id = uint128(gs().miscNonce++); - uint256 tokenId = encodeArtifact( - uint8(TokenType.Spaceship), - uint8(ArtifactRarity.Unknown), - uint8(shipType), - uint8(Biome.Unknown) + uint256 tokenId = LibSpaceship.encode( + Spaceship({id: 0, tokenType: TokenType.Spaceship, spaceshipType: shipType}) ); - DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs({ - tokenId: tokenId + id, - discoverer: msg.sender, - planetId: planetId, - rarity: ArtifactRarity.Unknown, - biome: Biome.Unknown, - artifactType: shipType, - owner: owner, - // Only used for spaceships - controller: owner - }); - Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( - createArtifactArgs + + Spaceship memory spaceship = DFArtifactFacet(address(this)).createSpaceship( + tokenId + id, // Make each ship unique + owner ); - LibGameUtils._putArtifactOnPlanet(planetId, foundArtifact.id); + LibGameUtils._putSpaceshipOnPlanet(planetId, spaceship.id); - return tokenId; + return spaceship.id; } function findArtifact(DFPFindArtifactArgs memory args) public returns (uint256 artifactId) { @@ -114,27 +104,19 @@ library LibArtifactUtils { ArtifactRarity rarity = LibGameUtils.artifactRarityFromPlanetLevel( levelBonus + planet.planetLevel ); - uint128 id = uint128(gs().miscNonce++); - uint256 tokenId = encodeArtifact( - uint8(TokenType.Artifact), - uint8(rarity), - uint8(artifactType), - uint8(biome) - ); - - DFTCreateArtifactArgs memory createArtifactArgs = DFTCreateArtifactArgs( - tokenId + id, - msg.sender, // discoverer - args.planetId, - rarity, - biome, - artifactType, - args.coreAddress, // owner - address(0) + uint256 tokenId = LibArtifact.encode( + Artifact({ + id: 0, + tokenType: TokenType.Artifact, + rarity: rarity, + artifactType: artifactType, + planetBiome: biome + }) ); Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( - createArtifactArgs + tokenId, + args.coreAddress ); LibGameUtils._putArtifactOnPlanet(args.planetId, foundArtifact.id); @@ -155,25 +137,28 @@ library LibArtifactUtils { Planet storage planet = gs().planets[locationId]; Artifact memory artifact = decodeArtifact(artifactId); - require( - LibGameUtils.isArtifactOnPlanet(locationId, artifactId), - "can't active an artifact on a planet it's not on" - ); - - if (isSpaceship(artifact.artifactType)) { - activateSpaceshipArtifact(locationId, artifactId, planet, artifact); + if (LibSpaceship.isShip(artifactId)) { + require( + LibGameUtils.isSpaceshipOnPlanet(locationId, artifactId), + "can't activate a ship on a planet it's not on" + ); + activateSpaceshipArtifact(locationId, artifactId, planet); } else { + require( + LibGameUtils.isArtifactOnPlanet(locationId, artifactId), + "can't activate an artifact on a planet it's not on" + ); activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); } } function activateSpaceshipArtifact( uint256 locationId, - uint256 artifactId, - Planet storage planet, - Artifact memory artifact + uint256 shipId, + Planet storage planet ) private { - if (artifact.artifactType == ArtifactType.ShipCrescent) { + Spaceship memory s = LibSpaceship.decode(shipId); + if (s.spaceshipType == SpaceshipType.ShipCrescent) { require( planet.planetType != PlanetType.SILVER_MINE, "cannot turn a silver mine into a silver mine" @@ -196,12 +181,12 @@ library LibArtifactUtils { } planet.planetType = PlanetType.SILVER_MINE; - emit ArtifactActivated(msg.sender, locationId, artifactId); + emit ArtifactActivated(msg.sender, locationId, shipId); // TODO: Why not actually burn? // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); - emit ArtifactDeactivated(msg.sender, locationId, artifactId); + LibGameUtils._takeSpaceshipOffPlanet(locationId, shipId); + emit ArtifactDeactivated(msg.sender, locationId, shipId); } } @@ -222,7 +207,6 @@ library LibArtifactUtils { ); require(!planet.destroyed, "planet is destroyed"); - uint256 length = gs().planetArtifacts[locationId].length; require( getPlanetArtifact(locationId, artifactId).tokenType != TokenType.Unknown, "this artifact is not on this planet" @@ -318,7 +302,7 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.planetType == PlanetType.TRADING_POST, "can only deposit on trading posts"); require( - DFArtifactFacet(address(this)).balanceOf(msg.sender, artifactId) > 0, + DFArtifactFacet(address(this)).tokenExists(msg.sender, artifactId), "you can only deposit artifacts you own" ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); @@ -328,9 +312,12 @@ library LibArtifactUtils { planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" ); - require(!isSpaceship(artifact.artifactType), "cannot deposit spaceships"); + require(!LibSpaceship.isShip(artifactId), "cannot deposit spaceships"); - require(gs().planetArtifacts[locationId].length < 5, "too many artifacts on this planet"); + require( + gs().planetArtifacts[locationId].length + gs().planetSpaceships[locationId].length < 5, + "too many tokens on this planet" + ); LibGameUtils._putArtifactOnPlanet(locationId, artifactId); // artifactId, curr owner, new owner @@ -347,8 +334,6 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); Artifact memory artifact = getPlanetArtifact(locationId, artifactId); - // TODO: Write is initialized function. - require(artifact.tokenType != TokenType.Unknown, "this artifact is not on this planet"); require( planet.planetLevel > uint256(artifact.rarity), @@ -377,13 +362,12 @@ library LibArtifactUtils { } function containsGear(uint256 locationId) public view returns (bool) { - uint256[] memory artifactIds = gs().planetArtifacts[locationId]; - - for (uint256 i = 0; i < artifactIds.length; i++) { - Artifact memory artifact = decodeArtifact(artifactIds[i]); + uint256[] memory tokenIds = gs().planetSpaceships[locationId]; + for (uint256 i = 0; i < tokenIds.length; i++) { + Spaceship memory spaceship = LibSpaceship.decode(tokenIds[i]); if ( - // TODO: Gear is broken - artifact.artifactType == ArtifactType.ShipGear // && msg.sender == artifact.controller + spaceship.spaceshipType == SpaceshipType.ShipGear && + DFArtifactFacet(address(this)).tokenExists(msg.sender, tokenIds[i]) ) { return true; } @@ -473,14 +457,17 @@ library LibArtifactUtils { function getPlanetArtifact(uint256 locationId, uint256 artifactId) public view - returns (Artifact memory) + returns (Artifact memory a) { + bool found = false; for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { if (gs().planetArtifacts[locationId][i] == artifactId) { - return decodeArtifact(artifactId); + a = decodeArtifact(artifactId); + found = true; + return a; } } - return _nullArtifactProperties(); + require(found, "artifact not found"); } } diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 2053cdee..1404ac96 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -375,7 +375,10 @@ library LibGameUtils { // put on. note that this function does not transfer the artifact. function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { gs().planetArtifacts[locationId].push(artifactId); - uint256 length = gs().planetArtifacts[locationId].length; + } + + function _putSpaceshipOnPlanet(uint256 locationId, uint256 spaceshipId) public { + gs().planetSpaceships[locationId].push(spaceshipId); } // TODO: Why not burn ? @@ -416,6 +419,31 @@ library LibGameUtils { gs().planetArtifacts[locationId].pop(); } + function _takeSpaceshipOffPlanet(uint256 locationId, uint256 spaceshipId) public { + uint256 shipsOnThisPlanet = gs().planetSpaceships[locationId].length; + + bool hadTheShip = false; + + for (uint256 i = 0; i < shipsOnThisPlanet; i++) { + if (gs().planetSpaceships[locationId][i] == spaceshipId) { + require( + !isActivated(locationId, spaceshipId), + "you cannot take an activated spaceship off a planet" + ); + + gs().planetSpaceships[locationId][i] = gs().planetSpaceships[locationId][ + shipsOnThisPlanet - 1 + ]; + + hadTheShip = true; + break; + } + } + + require(hadTheShip, "this ship was not present on this planet"); + gs().planetSpaceships[locationId].pop(); + } + // an artifact is only considered 'activated' if this method returns true. // we do not have an `isActive` field on artifact; the times that the // artifact was last activated and deactivated are sufficent to determine @@ -435,6 +463,16 @@ library LibGameUtils { return false; } + function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) public view returns (bool) { + for (uint256 i; i < gs().planetSpaceships[locationId].length; i++) { + if (gs().planetSpaceships[locationId][i] == shipId) { + return true; + } + } + + return false; + } + // the space junk that a planet starts with function getPlanetDefaultSpaceJunk(Planet memory planet) public view returns (uint256) { if (planet.isHomePlanet) return 0; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 934a8c96..8c61543b 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -6,15 +6,17 @@ import {DFVerifierFacet} from "../facets/DFVerifierFacet.sol"; import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; // Library imports +import {LibArtifact} from "./LibArtifact.sol"; +import {LibArtifactUtils} from "./LibArtifactUtils.sol"; import {LibGameUtils} from "./LibGameUtils.sol"; import {LibLazyUpdate} from "./LibLazyUpdate.sol"; -import {LibArtifactUtils} from "./LibArtifactUtils.sol"; +import {LibSpaceship} from "./LibSpaceship.sol"; // Storage imports import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports -import {ArtifactType, Artifact, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; +import {ArtifactType, Artifact, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Spaceship, SpaceshipType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; import "hardhat/console.sol"; library LibPlanet { @@ -288,10 +290,11 @@ library LibPlanet { } for (uint256 i = 0; i < artifactsToAdd.length; i++) { - // artifactsToAdd[i] - Artifact memory artifact = LibArtifactUtils.decodeArtifact(artifactsToAdd[i]); - - planet = applySpaceshipArrive(artifact, planet); + // Only apply Spaceship arrival if ship is a spaceship. + if (LibSpaceship.isShip(artifactsToAdd[i])) { + Spaceship memory spaceship = LibSpaceship.decode(artifactsToAdd[i]); + planet = applySpaceshipArrive(spaceship, planet); + } } planet = LibLazyUpdate.updatePlanet(timestamp, planet); @@ -299,7 +302,7 @@ library LibPlanet { return (planet, eventsToRemove, artifactsToAdd); } - function applySpaceshipArrive(Artifact memory artifact, Planet memory planet) + function applySpaceshipArrive(Spaceship memory spaceship, Planet memory planet) public pure returns (Planet memory) @@ -308,17 +311,17 @@ library LibPlanet { return planet; } - if (artifact.artifactType == ArtifactType.ShipMothership) { + if (spaceship.spaceshipType == SpaceshipType.ShipMothership) { if (planet.energyGroDoublers == 0) { planet.populationGrowth *= 2; } planet.energyGroDoublers++; - } else if (artifact.artifactType == ArtifactType.ShipWhale) { + } else if (spaceship.spaceshipType == SpaceshipType.ShipWhale) { if (planet.silverGroDoublers == 0) { planet.silverGrowth *= 2; } planet.silverGroDoublers++; - } else if (artifact.artifactType == ArtifactType.ShipTitan) { + } else if (spaceship.spaceshipType == SpaceshipType.ShipTitan) { planet.pausers++; } @@ -331,7 +334,7 @@ library LibPlanet { ( Planet memory planet, uint256[12] memory eventsToRemove, - uint256[12] memory artifactIdsToAddToPlanet + uint256[12] memory tokenIdsToAddToPlanet ) = getRefreshedPlanet(location, block.timestamp); gs().planets[location] = planet; @@ -348,8 +351,12 @@ library LibPlanet { } for (uint256 i = 0; i < 12; i++) { - if (artifactIdsToAddToPlanet[i] != 0) { - LibGameUtils._putArtifactOnPlanet(location, artifactIdsToAddToPlanet[i]); + if (tokenIdsToAddToPlanet[i] != 0) { + if (LibSpaceship.isShip(tokenIdsToAddToPlanet[i])) { + LibGameUtils._putSpaceshipOnPlanet(location, tokenIdsToAddToPlanet[i]); + } else if (LibArtifact.isArtifact(tokenIdsToAddToPlanet[i])) { + LibGameUtils._putArtifactOnPlanet(location, tokenIdsToAddToPlanet[i]); + } } } } diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index 32578b37..09bf5683 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -34,16 +34,15 @@ library LibSpaceship { return tokenType + shipType; } - function decode(uint256 spaceshipId) internal view returns (Spaceship memory) { + function decode(uint256 spaceshipId) internal pure returns (Spaceship memory) { bytes memory _b = abi.encodePacked(spaceshipId); + // Idx is subtracted by one because each Info enum has Unknown at the zero location. uint8 tokenIdx = uint8(SpaceshipInfo.TokenType) - 1; uint8 shipInfoIdx = uint8(SpaceshipInfo.SpaceshipType) - 1; - console.log("ship info indx", shipInfoIdx); uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); uint8 shipType = uint8(LibUtils.calculateByteUInt(_b, shipInfoIdx, shipInfoIdx)); - console.log("tokenType", tokenType); - console.log("shipType", shipType); + return Spaceship({ id: spaceshipId, @@ -51,4 +50,11 @@ library LibSpaceship { spaceshipType: SpaceshipType(shipType) }); } + + function isShip(uint256 tokenId) internal pure returns (bool) { + bytes memory _b = abi.encodePacked(tokenId); + uint8 tokenIdx = uint8(SpaceshipInfo.TokenType) - 1; + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + return (TokenType(tokenType) == TokenType.Spaceship); + } } diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 58ee97a3..279f10bd 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -43,8 +43,9 @@ struct GameStorage { mapping(uint256 => PlanetEventMetadata[]) planetEvents; // maps event id to arrival data mapping(uint256 => ArrivalData) planetArrivals; - // Artifact stuff + // Token stuff mapping(uint256 => uint256[]) planetArtifacts; + mapping(uint256 => uint256[]) planetSpaceships; mapping(uint256 => uint256) planetActiveArtifact; // wormhole from => to. planetWormHoles[from] = to; mapping(uint256 => uint256) planetWormholes; diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index a1c90c51..431b5ae0 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -415,7 +415,7 @@ export async function deployLibraries({}, hre: HardhatRuntimeEnvironment) { libraries: { LibGameUtils: LibGameUtils.address, LibLazyUpdate: LibLazyUpdate.address, - LibArtifactUtils: LibArtifactUtils.address, + // LibArtifactUtils: LibArtifactUtils.address, }, }); const LibPlanet = await LibPlanetFactory.deploy(); diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index 594faf70..a7c200bb 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -1,4 +1,4 @@ -import { ArtifactRarity, ArtifactType, Biome, TokenType } from '@dfdao/types'; +import { ArtifactRarity, ArtifactType, Biome, SpaceshipType, TokenType } from '@dfdao/types'; import { loadFixture, mine } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import hre from 'hardhat'; @@ -56,9 +56,10 @@ describe('DarkForestArtifacts', function () { await increaseBlockchainTime(); // Move the Gear ship into position - const gearShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (artifact) => artifact.artifactType === ArtifactType.ShipGear + const gearShip = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).find( + (ship) => ship.spaceshipType === SpaceshipType.ShipGear ); + const gearId = gearShip?.id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, ARTIFACT_PLANET_1, 100, 0, 0, gearId) @@ -80,7 +81,7 @@ describe('DarkForestArtifacts', function () { }); describe('it tests basic artifact actions', function () { - it.only('logs bits for artifact old', async function () { + it('logs bits for artifact old', async function () { // Must be valid options const _collectionType = '0x01'; const _rarity = ArtifactRarity.Legendary; @@ -562,7 +563,7 @@ describe('DarkForestArtifacts', function () { // should not be able to withdraw newArtifactId from LVL3_SPACETIME_1 await expect( world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, newArtifactId) - ).to.be.revertedWith('this artifact is not on this planet'); + ).to.be.revertedWith('artifact not found'); }); it('should not be able to withdraw/deposit onto a planet that is not a trading post', async function () { @@ -597,7 +598,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.Monolith, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Legendary, biome: Biome.OCEAN } ); @@ -647,7 +648,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, from, ArtifactType.Wormhole, - CollectionType.Artifact, + TokenType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.getArtifact(artifactId)); @@ -717,13 +718,13 @@ describe('DarkForestArtifacts', function () { world.user1.address, from, ArtifactType.Wormhole, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); // Move gear bc too many artifacts on SPAWN_PLANET_1, so can't receive wormhole. - const crescentShip = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (artifact) => artifact.artifactType === ArtifactType.ShipCrescent + const crescentShip = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).find( + (ship) => ship.spaceshipType === SpaceshipType.ShipCrescent ); await world.user1Core.move( @@ -808,7 +809,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BloomFilter, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.getArtifact(newTokenId)); @@ -855,7 +856,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BloomFilter, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); @@ -890,7 +891,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BlackDomain, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); @@ -940,7 +941,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BlackDomain, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); @@ -964,7 +965,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, LVL3_SPACETIME_1, ArtifactType.PlanetaryShield, - CollectionType.Artifact, + TokenType.Artifact, { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.getArtifact(newTokenId)); @@ -1009,7 +1010,7 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.PhotoidCannon, - CollectionType.Artifact, + TokenType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); prettyPrintToken(await world.contract.getArtifact(newTokenId)); diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index bafc5184..b452b1f1 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -1,4 +1,4 @@ -import { ArtifactType } from '@dfdao/types'; +import { ArtifactType, SpaceshipType } from '@dfdao/types'; import { loadFixture, time } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { BigNumber } from 'ethers'; @@ -52,7 +52,7 @@ describe('DarkForestMove', function () { }); it('allows controller to move ships to places they do not own with infinite distance', async function () { - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; const shipId = ship.id; await world.user1Core.move( @@ -61,15 +61,17 @@ describe('DarkForestMove', function () { await increaseBlockchainTime(); await world.user1Core.refreshPlanet(LVL2_PLANET_SPACE.id); - expect((await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).length).to.be.eq(4); - expect((await world.user1Core.getArtifactsOnPlanet(LVL2_PLANET_SPACE.id)).length).to.be.eq(1); + expect((await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).length).to.be.eq(4); + expect((await world.user1Core.getSpaceshipsOnPlanet(LVL2_PLANET_SPACE.id)).length).to.be.eq( + 1 + ); }); it('allows controller to move ships between their own planets', async function () { await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL1_ASTEROID_NEBULA); await increaseBlockchainTime(); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; const shipId = ship.id; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_NEBULA, 1000, 0, 0, shipId) @@ -78,13 +80,13 @@ describe('DarkForestMove', function () { await increaseBlockchainTime(); await world.user1Core.refreshPlanet(LVL1_ASTEROID_NEBULA.id); - expect((await world.user1Core.getArtifactsOnPlanet(LVL1_ASTEROID_NEBULA.id)).length).to.be.eq( - 1 - ); + expect( + (await world.user1Core.getSpaceshipsOnPlanet(LVL1_ASTEROID_NEBULA.id)).length + ).to.be.eq(1); }); it('should not allow you to move enemy ships on your own planet', async function () { - const user2shipId = (await world.user2Core.getArtifactsOnPlanet(SPAWN_PLANET_2.id))[0].id; + const user2shipId = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_2.id))[0].id; await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL2_PLANET_SPACE); @@ -92,7 +94,6 @@ describe('DarkForestMove', function () { ...makeMoveArgs(SPAWN_PLANET_2, LVL2_PLANET_SPACE, 1000, 0, 0, user2shipId) ); await increaseBlockchainTime(); - await expect( world.user1Core.move( ...makeMoveArgs(LVL2_PLANET_SPACE, SPAWN_PLANET_2, 1000, 0, 0, user2shipId) @@ -115,8 +116,8 @@ describe('DarkForestMove', function () { await increaseBlockchainTime(); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).filter( - (a) => a.artifactType === ArtifactType.ShipGear + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).filter( + (s) => s.spaceshipType === SpaceshipType.ShipGear )[0]; console.log(`gear id`, ship?.id); @@ -924,10 +925,10 @@ describe('move rate limits', function () { await world.contract.adminGiveSpaceShip( SPAWN_PLANET_1.id, world.user1.address, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 10, 0, 0, ship.id) @@ -937,10 +938,10 @@ describe('move rate limits', function () { await world.contract.adminGiveSpaceShip( SPAWN_PLANET_1.id, world.user1.address, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; await expect( world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 1000, 0, 0, ship.id)) @@ -949,7 +950,7 @@ describe('move rate limits', function () { await increaseBlockchainTime(); await world.user1Core.refreshPlanet(LVL2_PLANET_SPACE.id); - const numShipsOnPlanet = (await world.user1Core.getArtifactsOnPlanet(LVL2_PLANET_SPACE.id)) + const numShipsOnPlanet = (await world.user1Core.getSpaceshipsOnPlanet(LVL2_PLANET_SPACE.id)) .length; expect(numShipsOnPlanet).to.be.eq(6); @@ -963,10 +964,10 @@ describe('move rate limits', function () { await world.contract.adminGiveSpaceShip( SPAWN_PLANET_1.id, world.user1.address, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); - const ship = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id))[0]; + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 10, 0, 0, ship.id) @@ -980,7 +981,7 @@ describe('move rate limits', function () { await increaseBlockchainTime(); await world.user1Core.refreshPlanet(LVL2_PLANET_SPACE.id); - const numShipsOnPlanet = (await world.user1Core.getArtifactsOnPlanet(LVL2_PLANET_SPACE.id)) + const numShipsOnPlanet = (await world.user1Core.getSpaceshipsOnPlanet(LVL2_PLANET_SPACE.id)) .length; expect(numShipsOnPlanet).to.be.eq(6); diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index d4192c82..874f6d38 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -1,13 +1,12 @@ -import { ArtifactType, PlanetType } from '@dfdao/types'; +import { PlanetType, SpaceshipType } from '@dfdao/types'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { conquerUnownedPlanet, - getArtifactOnPlanetByType, + getSpaceshipOnPlanetByType, increaseBlockchainTime, makeInitArgs, makeMoveArgs, - prettyPrintToken, } from './utils/TestUtils'; import { defaultWorldFixture, World } from './utils/TestWorld'; import { @@ -41,7 +40,9 @@ describe('DarkForestSpaceShips', function () { describe('spawning your ships', function () { it('gives you 5 space ships', async function () { - expect((await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).length).to.be.equal(5); + expect((await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).length).to.be.equal( + 5 + ); }); it('can only be done once per player', async function () { @@ -61,10 +62,10 @@ describe('DarkForestSpaceShips', function () { describe('ship transfers', function () { it('cannot transfer your own spaceship', async function () { - const motherShip = await getArtifactOnPlanetByType( + const motherShip = await getSpaceshipOnPlanetByType( world.contract, SPAWN_PLANET_1.id, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); // Player owns ship. expect(await world.contract.balanceOf(world.user1.address, motherShip.id)).to.equal(1); @@ -81,10 +82,10 @@ describe('DarkForestSpaceShips', function () { it('cannot transfer other players spaceship', async function () { await world.user2Core.giveSpaceShips(SPAWN_PLANET_2.id); - const motherShip = await getArtifactOnPlanetByType( + const motherShip = await getSpaceshipOnPlanetByType( world.contract, SPAWN_PLANET_2.id, - ArtifactType.ShipMothership + SpaceshipType.ShipMothership ); // Other Player owns ship. expect(await world.contract.balanceOf(world.user2.address, motherShip.id)).to.equal(1); @@ -104,8 +105,10 @@ describe('DarkForestSpaceShips', function () { this.timeout(0); it('pauses energy regeneration on planets', async function () { - const titan = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (a) => a.artifactType === ArtifactType.ShipTitan + const titan = await getSpaceshipOnPlanetByType( + world.contract, + SPAWN_PLANET_1.id, + SpaceshipType.ShipTitan ); // Move Titan to planet @@ -151,11 +154,11 @@ describe('DarkForestSpaceShips', function () { describe('using the Crescent', function () { it('turns planet into an asteroid and burns crescent', async function () { - const crescent = (await world.user1Core.getArtifactsOnPlanet(SPAWN_PLANET_1.id)).find( - (a) => a.artifactType === ArtifactType.ShipCrescent + const crescent = await getSpaceshipOnPlanetByType( + world.contract, + SPAWN_PLANET_1.id, + SpaceshipType.ShipCrescent ); - if (!crescent) throw new Error('crescent not found'); - prettyPrintToken(crescent); // Move Crescent to planet await world.user1Core.move( @@ -166,7 +169,7 @@ describe('DarkForestSpaceShips', function () { await world.contract.refreshPlanet(LVL1_PLANET_DEEP_SPACE.id); const crescentNewLocId = ( - await world.contract.getArtifactsOnPlanet(LVL1_PLANET_DEEP_SPACE.id) + await world.contract.getSpaceshipsOnPlanet(LVL1_PLANET_DEEP_SPACE.id) )[0].id; expect(crescentNewLocId).to.equal(crescent?.id); @@ -177,7 +180,7 @@ describe('DarkForestSpaceShips', function () { expect(planetBeforeActivate.silverGrowth).to.be.lessThan(planetAfterActivate.silverGrowth); // Crescent is no longer on planet. expect( - (await world.contract.getArtifactsOnPlanet(LVL1_PLANET_DEEP_SPACE.id)).length + (await world.contract.getSpaceshipsOnPlanet(LVL1_PLANET_DEEP_SPACE.id)).length ).to.equal(0); // Planet was planet expect(planetBeforeActivate.planetType).to.equal(PlanetType.PLANET); @@ -186,7 +189,7 @@ describe('DarkForestSpaceShips', function () { // Cannot activate again. await expect( world.user1Core.activateArtifact(LVL1_PLANET_DEEP_SPACE.id, crescent.id, 0) - ).to.be.revertedWith("can't active an artifact on a planet it's not on"); + ).to.be.revertedWith("can't activate a ship on a planet it's not on"); }); }); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index d0ce72cd..31363352 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -15,6 +15,7 @@ import { ArtifactTypeNames, Biome, BiomeNames, + SpaceshipType, TokenType, TokenTypeNames, } from '@dfdao/types'; @@ -382,3 +383,13 @@ export async function getArtifactOnPlanetByType( (artifact) => (artifact.artifactType as ArtifactType) === artifactType )[0]; } + +export async function getSpaceshipOnPlanetByType( + contract: DarkForest, + locationId: BigNumber, + shipType: SpaceshipType +) { + return (await contract.getSpaceshipsOnPlanet(locationId)).filter( + (s) => (s.spaceshipType as SpaceshipType) === shipType + )[0]; +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index dc2b5e47..776e7a94 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -42,6 +42,7 @@ export * from './plugin'; export * from './renderer'; export * from './reveal'; export * from './setting'; +export * from './spaceship'; export * from './token'; export * from './transaction'; export * from './transactions'; From af64dceac29bc3ebe63f7733a01b6d1fbc8e68da Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 30 Sep 2022 17:58:09 +0100 Subject: [PATCH 33/55] spaceship type --- packages/types/src/spaceship.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 packages/types/src/spaceship.ts diff --git a/packages/types/src/spaceship.ts b/packages/types/src/spaceship.ts new file mode 100644 index 00000000..027df98a --- /dev/null +++ b/packages/types/src/spaceship.ts @@ -0,0 +1,32 @@ +import { Abstract } from './utility'; + +/** + * Abstract type representing an artifact type. + */ +export type SpaceshipType = Abstract; + +/** + * Enumeration of artifact types. + */ +export const SpaceshipType = { + Unknown: 0 as SpaceshipType, + ShipMothership: 1 as SpaceshipType, + ShipCrescent: 2 as SpaceshipType, + ShipWhale: 3 as SpaceshipType, + ShipGear: 4 as SpaceshipType, + ShipTitan: 5 as SpaceshipType, + + // Don't forget to update MIN_ARTIFACT_TYPE and/or MAX_ARTIFACT_TYPE in the `constants` package +} as const; + +/** + * Mapping from SpaceshipType to pretty-printed names. + */ +export const SpaceshipTypeNames = { + [SpaceshipType.Unknown]: 'Unknown', + [SpaceshipType.ShipMothership]: 'Mothership', + [SpaceshipType.ShipCrescent]: 'Crescent', + [SpaceshipType.ShipWhale]: 'Whale', + [SpaceshipType.ShipGear]: 'Gear', + [SpaceshipType.ShipTitan]: 'Titan', +} as const; From cbf87ab2682bebcedb934eac1e16ee06d90e2bbd Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Fri, 30 Sep 2022 18:18:28 +0100 Subject: [PATCH 34/55] fix: photoid test --- eth/contracts/DFTypes.sol | 8 +------- eth/contracts/facets/DFArtifactFacet.sol | 2 -- eth/contracts/facets/DFMoveFacet.sol | 15 ++++++++------- eth/contracts/libraries/LibArtifactUtils.sol | 7 +------ eth/test/DFArtifacts.test.ts | 2 -- eth/test/DFMove.test.ts | 1 - 6 files changed, 10 insertions(+), 25 deletions(-) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 4262ea14..475b1bc2 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -205,13 +205,7 @@ enum ArtifactType { PlanetaryShield, PhotoidCannon, BloomFilter, - BlackDomain, - // TODO; remove this - ShipMothership, - ShipCrescent, - ShipWhale, - ShipGear, - ShipTitan + BlackDomain } enum SpaceshipType { diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index d668bf90..cbd2e7fc 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -126,8 +126,6 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return LibArtifact.decode(artifactId); } - // TODO: Add ERC1155 Enumerable Wrappers - // This calls the low level _transfer call which doesn't check if the msg.sender actually owns // the tokenId. TODO: See if this is a problem. function transferArtifact( diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 9a884847..44f1713b 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -17,7 +17,7 @@ import {LibSpaceship} from "../libraries/LibSpaceship.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Spaceship, SpaceshipType, Upgrade} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { @@ -236,7 +236,7 @@ contract DFMoveFacet is WithStorage { } } - function applySpaceshipDepart(Artifact memory artifact, Planet memory planet) + function applySpaceshipDepart(Spaceship memory spaceship, Planet memory planet) public view returns (Planet memory) @@ -245,21 +245,21 @@ contract DFMoveFacet is WithStorage { return planet; } - if (artifact.artifactType == ArtifactType.ShipMothership) { + if (spaceship.spaceshipType == SpaceshipType.ShipMothership) { if (planet.energyGroDoublers == 1) { planet.energyGroDoublers--; planet.populationGrowth /= 2; } else if (planet.energyGroDoublers > 1) { planet.energyGroDoublers--; } - } else if (artifact.artifactType == ArtifactType.ShipWhale) { + } else if (spaceship.spaceshipType == SpaceshipType.ShipWhale) { if (planet.silverGroDoublers == 1) { planet.silverGroDoublers--; planet.silverGrowth /= 2; } else if (planet.silverGroDoublers > 1) { planet.silverGroDoublers--; } - } else if (artifact.artifactType == ArtifactType.ShipTitan) { + } else if (spaceship.spaceshipType == SpaceshipType.ShipTitan) { // so that updating silver/energy starts from the current time, // as opposed to the last time that the planet was updated planet.lastUpdated = block.timestamp; @@ -273,8 +273,9 @@ contract DFMoveFacet is WithStorage { Undo the spaceship effects that were applied when the ship arrived on the planet. */ function _removeSpaceshipEffectsFromOriginPlanet(DFPMoveArgs memory args) private { - Artifact memory movedArtifact = LibArtifactUtils.decodeArtifact(args.movedArtifactId); - Planet memory planet = applySpaceshipDepart(movedArtifact, gs().planets[args.oldLoc]); + if (!LibSpaceship.isShip(args.movedArtifactId)) return; + Spaceship memory spaceship = LibSpaceship.decode(args.movedArtifactId); + Planet memory planet = applySpaceshipDepart(spaceship, gs().planets[args.oldLoc]); gs().planets[args.oldLoc] = planet; } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 9804d113..0d59a8bd 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -339,7 +339,7 @@ library LibArtifactUtils { planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to withdraw this artifact" ); - require(!isSpaceship(artifact.artifactType), "cannot withdraw spaceships"); + require(!LibSpaceship.isShip(artifactId), "cannot withdraw spaceships"); LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); // artifactId, curr owner, new owner @@ -376,11 +376,6 @@ library LibArtifactUtils { return false; } - function isSpaceship(ArtifactType artifactType) public pure returns (bool) { - return - artifactType >= ArtifactType.ShipMothership && artifactType <= ArtifactType.ShipTitan; - } - /** * @notice Create the collection ID for a given artifact * @param _collectionType type of artifact diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index a7c200bb..e25450d5 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -75,7 +75,6 @@ describe('DarkForestArtifacts', function () { } beforeEach('load fixture', async function () { - console.log(`loading world...`); this.timeout(0); world = await loadFixture(worldFixture); }); @@ -130,7 +129,6 @@ describe('DarkForestArtifacts', function () { spaceshipType, }); const a = await world.contract.testDecodeSpaceship(res); - console.log(a); expect(tokenType).to.equal(Number(a.tokenType)); expect(spaceshipType).to.equal(Number(a.spaceshipType)); }); diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index b452b1f1..a3a4850b 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -119,7 +119,6 @@ describe('DarkForestMove', function () { const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).filter( (s) => s.spaceshipType === SpaceshipType.ShipGear )[0]; - console.log(`gear id`, ship?.id); await world.user1Core.move( ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, ship?.id) From 76fec8dd78821144c628f06854eb3b956ce4b7e4 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sat, 1 Oct 2022 14:39:40 +0100 Subject: [PATCH 35/55] update types and readme --- eth/contracts/DFTypes.sol | 8 ----- eth/contracts/Tokens.md | 64 +++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 475b1bc2..893d328e 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -250,14 +250,6 @@ enum Biome { Corrupted } -// enum TokenInfo { -// Unknown, -// TokenType, // Each bin of tokens gets an id (spaceships, artifacts, etc...) -// ArtifactRarity, -// ArtifactType, -// Biome -// } - enum TokenType { Unknown, Artifact, diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 1d9b2bd8..4c4d5b30 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -26,24 +26,36 @@ and, more importantly, it allows to create a copy of a token just by using the s This concept will become clearer as we examine how these rules are used for Artifacts and Spaceships. -In `DFTypes.sol`, the `TokenInfo` enum looks like this: +Each of these chunks has 256 options. So you can have 256 Artifact Rarities, Types, Biomes etc... +This should be plenty, but if you need more you just use another chunk. + +## General Token Info + +Each collection (called a Token) in Dark Forest must have a Library dedicated to it with the naming +convention `Lib.sol`. + +The first byte (from left) of the tokenId **must** correspond to the appropriate value in the +`TokenType` struct in `DFTypes.sol` ```js -enum TokenInfo { +enum TokenType { Unknown, - TokenType, - ArtifactRarity, - ArtifactType, - Biome + Artifact, // 0x01 = Artifact + Spaceship // 0x02 = Spaceship + // etc... } ``` -Each index in `TokenInfo` refers to a chunk in the `tokenId`. +The `Lib.sol`. file **must** have the following methods: -> | TokenType | ArtifactRarity | ArtifactType | Biome | chunk5 | ... chunk 32 | +1. `encode() returns (uint256 tokenId)` +2. `decode(uint256 tokenId) returns ()` +3. `is returns (bool)` -Each of these chunks has 256 options. So you can have 256 Artifact Rarities, Types, Biomes etc... -This should be plenty, but if you need more you just use another chunk. +where `` can be a struct (like Artifacts or Spaceships) or just a uint256 (like Silver). + +Additionally methods can be added to each library, but they must be `internal` functions that can be +inherited by other facets or libraries. ## Artifacts @@ -59,14 +71,14 @@ In hex: > | 0x01 | 0x03| 0x01 | 0x01 | ... Under the hood, we simply calculate the value of the given property (Epic Artifact = 3), convert it -to hex (0x03), and place it in the appropriate location (TokenInfo.ArtifactRarity = 2, so place 0x03 +to hex (0x03), and place it in the appropriate location (ArtifactInfo.ArtifactRarity = 2, so place 0x03 two chunks from the left). To decode, we just reverse this process. Given the id `0x010301010000.....`, I know that it is an Epic Monoliths Artifact from the Ocean Biome. -You can see the actual encoding and decoding take place in `DFToken.sol/encodeArtifact` and -`DFToken.sol/decode Artifact`. +You can see the actual encoding and decoding take place in `LibArtifact.sol/encode` and +`LibArtifact.sol/decode`. ### Minting @@ -107,6 +119,10 @@ your planet, you cannot control my Mothership. This means that the `planetArtifa will fail to enforce this primary rule of Spaceships. To fix this, each Spaceship must have a unique id and be owned by the account that minted it. +To differentiate between Spaceships and Artifacts, we have an additional data structure, +`mapping(uint256 =>uint256[]) planetSpaceships;` in the contract storage that keeps track of which +spaceships are on which planet. + However, we can still use our `tokenId` chunk system because we don't need all 256 bits to uniquely identify a Spaceship. We limit Spaceships to the first 16 chunks (128 bits) and save 128 for a unique id. This uses the [Split-Id](https://eips.ethereum.org/EIPS/eip-1155#split-id-bits) method @@ -114,12 +130,11 @@ recommended in the ERC1155 Proposal. A Mothership Spaceship is represented like so -> | TokenType.Spaceship | ArtifactRarity.Unknown | ArtifactType.ShipMothership | Biome.Unknown -> | ... chunk 16 | uniqueId (16 chunks) | +> | TokenType.Spaceship | SpaceshipType.ShipMothership | ... chunk 16 | uniqueId (16 chunks) | In hex: -> | 0x02 | 0x00 | 0x0a | 0x00 | ... | uniqueId (16 chunks) | +> | 0x02 | 0x01 ... | uniqueId (16 chunks) | A Spaceship's tokenId = `` @@ -138,7 +153,7 @@ If Velorum mints their own Mothership, it would have id: `<0x02000a00><0x02>`. Velorum's Mothership has the same TokenInfo, but a unique identifier at the end. This means the contract stores my Mothership and Velorum's Mothership as completely different collections. However, because our ships share the same first 128 bits, we can still calculate the information -about the Spaceship (ArtifactType, TokenType) just by feeding the `decodeArtifact` function the `tokenId`. +about the Spaceship (SpaceshipType, TokenType) just by feeding the `LibSpaceship.decode` function the `tokenId`. ### Transferring @@ -150,10 +165,12 @@ We could easily turn off this check if we wanted players to be able to buy and s ## Activating +### Artifacts + Every Artifact and one Spaceship (Crescent) must be activated on a planet to be used. -The fungible nature of Artifacts creates a challenge: How do we associate data with specific artifacts? How do I know when my -Epic Monolith is activated? +The fungible nature of Artifacts creates a challenge: How do we associate data with specific +artifacts? How do I know when my Epic Monolith is activated? There are new data structures in `LibStorage.sol` to handle this information. Because Artifact activations are always associated with planets, we can store the needed information on the relevant planets @@ -178,6 +195,15 @@ wormhole from `0xA` to `0xB`. - `planetWormholes[0xA] = 0xB` +### Spaceships + +- The only Spaceship that can be activated is the Crescent, and that is burned in the same + transaction. This means that a Spaceship Id will never be added or removed to the + `planetActiveArtifact` mapping. +- This defines a fundamental difference between Spaceships and Artifacts: Spaceship effects are + always applied on arrival / departure, or are instanenous. There is (at the moment) no concept of + activating them. + ## Deactivating If I deactivate my artifact from `0xA`, we simply undo these maneuvers: From 51c8d787cd4f66d924752de9e057c8c6e7897bcd Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Sat, 1 Oct 2022 16:26:48 +0100 Subject: [PATCH 36/55] chore: move functions that can be internal into LibArtifact and LibSpaceship --- eth/contracts/facets/DFAdminFacet.sol | 5 +- eth/contracts/facets/DFArtifactFacet.sol | 27 +- eth/contracts/facets/DFGetterFacet.sol | 2 +- eth/contracts/facets/DFMoveFacet.sol | 15 +- eth/contracts/libraries/LibArtifact.sol | 284 ++++++++++++++++++- eth/contracts/libraries/LibArtifactUtils.sol | 143 ++-------- eth/contracts/libraries/LibGameUtils.sol | 271 +----------------- eth/contracts/libraries/LibPlanet.sol | 4 +- eth/contracts/libraries/LibSpaceship.sol | 38 +++ eth/tasks/deploy.ts | 10 +- eth/test/DFArtifacts.test.ts | 45 +-- eth/test/utils/TestUtils.ts | 14 +- 12 files changed, 395 insertions(+), 463 deletions(-) diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index dd1877b9..3ee57b8e 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -12,7 +12,9 @@ import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports import {WithStorage} from "../libraries/LibStorage.sol"; +// Contract imports import {DFArtifactFacet} from "./DFArtifactFacet.sol"; +import "hardhat/console.sol"; // Type imports import {Artifact, SpaceType, Spaceship, SpaceshipType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet, TokenType} from "../DFTypes.sol"; @@ -181,8 +183,9 @@ contract DFAdminFacet is WithStorage { tokenId, args.owner ); + // Don't put artifact on planet if no planetId given. - if (args.planetId != 0) LibGameUtils._putArtifactOnPlanet(args.planetId, artifact.id); + if (args.planetId != 0) LibArtifact.putArtifactOnPlanet(args.planetId, artifact.id); emit AdminArtifactCreated(args.owner, artifact.id, args.planetId); } } diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index cbd2e7fc..e92107f2 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -72,11 +72,11 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { returns (Artifact memory) { require(tokenId >= 1, "token id must be positive"); - require(LibArtifact.isArtifact(tokenId), "wrong token type"); + require(LibArtifact.isArtifact(tokenId), "token must be Artifact"); // Account, Id, Amount, Data _mint(owner, tokenId, 1, ""); - return getArtifact(tokenId); + return LibArtifact.decode(tokenId); } function createSpaceship(uint256 tokenId, address owner) @@ -85,7 +85,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { returns (Spaceship memory) { require(tokenId >= 1, "token id must be positive"); - require(LibSpaceship.isShip(tokenId), "wrong token type"); + require(LibSpaceship.isShip(tokenId), "token must be Spaceship"); // Account, Id, Amount, Data _mint(owner, tokenId, 1, ""); @@ -93,36 +93,23 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { return getSpaceship(tokenId); } - function getArtifact(uint256 artifactId) public pure returns (Artifact memory) { - return LibArtifact.decode(artifactId); - } - function getSpaceship(uint256 shipId) public pure returns (Spaceship memory) { return LibSpaceship.decode(shipId); } - function encodeArtifact( - uint256 _collectionType, - uint256 _rarity, - uint256 _artifactType, - uint256 _biome - ) public pure returns (uint256) { - return LibArtifactUtils.encodeArtifact(_collectionType, _rarity, _artifactType, _biome); - } - - function testEncodeSpaceship(Spaceship memory spaceship) public pure returns (uint256) { + function encodeSpaceship(Spaceship memory spaceship) public pure returns (uint256) { return LibSpaceship.encode(spaceship); } - function testDecodeSpaceship(uint256 shipId) public pure returns (Spaceship memory) { + function decodeSpaceship(uint256 shipId) public pure returns (Spaceship memory) { return LibSpaceship.decode(shipId); } - function testEncodeArtifact(Artifact memory artifact) public pure returns (uint256) { + function encodeArtifact(Artifact memory artifact) public pure returns (uint256) { return LibArtifact.encode(artifact); } - function testDecodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { return LibArtifact.decode(artifactId); } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 0865c634..aa15a491 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -388,7 +388,7 @@ contract DFGetterFacet is WithStorage { returns (Artifact memory ret) { uint256 artifactId = gs().planetActiveArtifact[locationId]; - return LibArtifactUtils.decodeArtifact(artifactId); + return LibArtifact.decode(artifactId); } // function bulkGetPlanetArtifacts(uint256[] calldata planetIds) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 44f1713b..a10d438c 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -290,10 +290,9 @@ contract DFMoveFacet is WithStorage { returns (bool wormholePresent, uint256 effectiveDistModifier) { wormholePresent = false; - Artifact memory relevantWormhole; - Artifact memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact(args.oldLoc); - Artifact memory activeArtifactTo = LibArtifactUtils.getActiveArtifact(args.newLoc); + Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); + Artifact memory activeArtifactTo = LibArtifact.getActiveArtifact(args.newLoc); // TODO: take the greater rarity of these, or disallow wormholes between planets that // already have a wormhole between them if ( @@ -325,7 +324,7 @@ contract DFMoveFacet is WithStorage { private returns (bool photoidPresent, Upgrade memory temporaryUpgrade) { - Artifact memory activeArtifactFrom = LibArtifactUtils.getActiveArtifact(args.oldLoc); + Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); if ( activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= @@ -445,12 +444,14 @@ contract DFMoveFacet is WithStorage { // Photoids are burned in _checkPhotoid, so don't remove twice if (args.movedArtifactId != 0) { if (isSpaceship) { - LibGameUtils._takeSpaceshipOffPlanet(args.oldLoc, args.movedArtifactId); - } else { + LibSpaceship.takeSpaceshipOffPlanet(args.oldLoc, args.movedArtifactId); + } else if (LibArtifact.isArtifact(args.movedArtifactId)) { Artifact memory artifact = LibArtifact.decode(args.movedArtifactId); if (artifact.artifactType != ArtifactType.PhotoidCannon) { - LibGameUtils._takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); + LibArtifact.takeArtifactOffPlanet(args.oldLoc, args.movedArtifactId); } + } else { + require(false, "cannot move token of this type"); } } } diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index f09a0435..adbc62b4 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -12,11 +12,16 @@ import "hardhat/console.sol"; import {LibUtils} from "./LibUtils.sol"; // Storage imports +import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports -import {Artifact, ArtifactInfo, ArtifactRarity, ArtifactType, Biome, TokenType} from "../DFTypes.sol"; +import {Artifact, ArtifactInfo, ArtifactRarity, ArtifactType, Biome, SpaceType, TokenType, Upgrade} from "../DFTypes.sol"; library LibArtifact { + function gs() internal pure returns (GameStorage storage) { + return LibStorage.gameStorage(); + } + /** * @notice Create the token ID for a Artifact with the following properties: * @param artifact Artifact @@ -66,4 +71,281 @@ library LibArtifact { uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); return (TokenType(tokenType) == TokenType.Artifact); } + + function _nullArtifactProperties() internal pure returns (Artifact memory) { + return + Artifact( + 0, + TokenType.Unknown, + ArtifactRarity.Unknown, + ArtifactType.Unknown, + Biome.Unknown + ); + } + + function getUpgradeForArtifact(Artifact memory artifact) + internal + pure + returns (Upgrade memory) + { + if (artifact.artifactType == ArtifactType.PlanetaryShield) { + uint256[6] memory defenseMultipliersPerRarity = [uint256(100), 150, 200, 300, 450, 650]; + + return + Upgrade({ + popCapMultiplier: 100, + popGroMultiplier: 100, + rangeMultiplier: 20, + speedMultiplier: 20, + defMultiplier: defenseMultipliersPerRarity[uint256(artifact.rarity)] + }); + } + + if (artifact.artifactType == ArtifactType.PhotoidCannon) { + uint256[6] memory def = [uint256(100), 50, 40, 30, 20, 10]; + return + Upgrade({ + popCapMultiplier: 100, + popGroMultiplier: 100, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: def[uint256(artifact.rarity)] + }); + } + + if (uint256(artifact.artifactType) >= 5) { + return + Upgrade({ + popCapMultiplier: 100, + popGroMultiplier: 100, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: 100 + }); + } + + Upgrade memory ret = Upgrade({ + popCapMultiplier: 100, + popGroMultiplier: 100, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: 100 + }); + + if (artifact.artifactType == ArtifactType.Monolith) { + ret.popCapMultiplier += 5; + ret.popGroMultiplier += 5; + } else if (artifact.artifactType == ArtifactType.Colossus) { + ret.speedMultiplier += 5; + } else if (artifact.artifactType == ArtifactType.Spaceship) { + ret.rangeMultiplier += 5; + } else if (artifact.artifactType == ArtifactType.Pyramid) { + ret.defMultiplier += 5; + } + + if (artifact.planetBiome == Biome.Ocean) { + ret.speedMultiplier += 5; + ret.defMultiplier += 5; + } else if (artifact.planetBiome == Biome.Forest) { + ret.defMultiplier += 5; + ret.popCapMultiplier += 5; + ret.popGroMultiplier += 5; + } else if (artifact.planetBiome == Biome.Grassland) { + ret.popCapMultiplier += 5; + ret.popGroMultiplier += 5; + ret.rangeMultiplier += 5; + } else if (artifact.planetBiome == Biome.Tundra) { + ret.defMultiplier += 5; + ret.rangeMultiplier += 5; + } else if (artifact.planetBiome == Biome.Swamp) { + ret.speedMultiplier += 5; + ret.rangeMultiplier += 5; + } else if (artifact.planetBiome == Biome.Desert) { + ret.speedMultiplier += 10; + } else if (artifact.planetBiome == Biome.Ice) { + ret.rangeMultiplier += 10; + } else if (artifact.planetBiome == Biome.Wasteland) { + ret.defMultiplier += 10; + } else if (artifact.planetBiome == Biome.Lava) { + ret.popCapMultiplier += 10; + ret.popGroMultiplier += 10; + } else if (artifact.planetBiome == Biome.Corrupted) { + ret.rangeMultiplier += 5; + ret.speedMultiplier += 5; + ret.popCapMultiplier += 5; + ret.popGroMultiplier += 5; + } + + uint256 scale = 1 + (uint256(artifact.rarity) / 2); + + ret.popCapMultiplier = scale * ret.popCapMultiplier - (scale - 1) * 100; + ret.popGroMultiplier = scale * ret.popGroMultiplier - (scale - 1) * 100; + ret.speedMultiplier = scale * ret.speedMultiplier - (scale - 1) * 100; + ret.rangeMultiplier = scale * ret.rangeMultiplier - (scale - 1) * 100; + ret.defMultiplier = scale * ret.defMultiplier - (scale - 1) * 100; + + return ret; + } + + function artifactRarityFromPlanetLevel(uint256 planetLevel) + internal + pure + returns (ArtifactRarity) + { + if (planetLevel <= 1) return ArtifactRarity.Common; + else if (planetLevel <= 3) return ArtifactRarity.Rare; + else if (planetLevel <= 5) return ArtifactRarity.Epic; + else if (planetLevel <= 7) return ArtifactRarity.Legendary; + else return ArtifactRarity.Mythic; + } + + // an artifact is only considered 'activated' if this method returns true. + // we do not have an `isActive` field on artifact; the times that the + // artifact was last activated and deactivated are sufficent to determine + // whether or not the artifact is activated. + + function isActivated(uint256 locationId, uint256 artifactId) internal view returns (bool) { + return (gs().planetActiveArtifact[locationId] == artifactId); + } + + function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) + internal + view + returns (bool) + { + for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { + if (gs().planetArtifacts[locationId][i] == artifactId) { + return true; + } + } + + return false; + } + + function randomArtifactTypeAndLevelBonus( + uint256 artifactSeed, + Biome biome, + SpaceType spaceType + ) internal pure returns (ArtifactType, uint256) { + uint256 lastByteOfSeed = artifactSeed % 0xFF; + uint256 secondLastByteOfSeed = ((artifactSeed - lastByteOfSeed) / 256) % 0xFF; + + ArtifactType artifactType = ArtifactType.Pyramid; + + if (lastByteOfSeed < 39) { + artifactType = ArtifactType.Monolith; + } else if (lastByteOfSeed < 78) { + artifactType = ArtifactType.Colossus; + } + // else if (lastByteOfSeed < 117) { + // artifactType = ArtifactType.Spaceship; + // } + else if (lastByteOfSeed < 156) { + artifactType = ArtifactType.Pyramid; + } else if (lastByteOfSeed < 171) { + artifactType = ArtifactType.Wormhole; + } else if (lastByteOfSeed < 186) { + artifactType = ArtifactType.PlanetaryShield; + } else if (lastByteOfSeed < 201) { + artifactType = ArtifactType.PhotoidCannon; + } else if (lastByteOfSeed < 216) { + artifactType = ArtifactType.BloomFilter; + } else if (lastByteOfSeed < 231) { + artifactType = ArtifactType.BlackDomain; + } else { + if (biome == Biome.Ice) { + artifactType = ArtifactType.PlanetaryShield; + } else if (biome == Biome.Lava) { + artifactType = ArtifactType.PhotoidCannon; + } else if (biome == Biome.Wasteland) { + artifactType = ArtifactType.BloomFilter; + } else if (biome == Biome.Corrupted) { + artifactType = ArtifactType.BlackDomain; + } else { + artifactType = ArtifactType.Wormhole; + } + artifactType = ArtifactType.PhotoidCannon; + } + + uint256 bonus = 0; + if (secondLastByteOfSeed < 4) { + bonus = 2; + } else if (secondLastByteOfSeed < 16) { + bonus = 1; + } + + return (artifactType, bonus); + } + + // planets can have multiple artifacts on them. this function updates all the + // internal contract book-keeping to reflect that the given artifact was + // put on. note that this function does not transfer the artifact. + function putArtifactOnPlanet(uint256 locationId, uint256 artifactId) internal { + gs().planetArtifacts[locationId].push(artifactId); + } + + /** + * Remove artifactId from planet with locationId if artifactId exists AND is not active. + */ + function takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) internal { + uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; + + bool hadTheArtifact = false; + + for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { + if (gs().planetArtifacts[locationId][i] == artifactId) { + require( + !isActivated(locationId, artifactId), + "you cannot take an activated artifact off a planet" + ); + + gs().planetArtifacts[locationId][i] = gs().planetArtifacts[locationId][ + artifactsOnThisPlanet - 1 + ]; + + hadTheArtifact = true; + break; + } + } + + require(hadTheArtifact, "this artifact was not present on this planet"); + gs().planetArtifacts[locationId].pop(); + } + + // if the given planet has an activated artifact on it, then return the artifact + // otherwise, return a 'null artifact' + function getActiveArtifact(uint256 locationId) internal view returns (Artifact memory) { + if (hasActiveArtifact(locationId)) { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + return LibArtifact.decode(artifactId); + } else { + return LibArtifact._nullArtifactProperties(); + } + } + + // if the given planet has an activated artifact on it, then return the artifact + // otherwise, return a 'null artifact' + function hasActiveArtifact(uint256 locationId) internal view returns (bool) { + uint256 artifactId = gs().planetActiveArtifact[locationId]; + return artifactId != 0; + } + + // if the given artifact is on the given planet, then return the artifact + // otherwise, throw error + function getPlanetArtifact(uint256 locationId, uint256 artifactId) + internal + view + returns (Artifact memory a) + { + bool found = false; + for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { + if (gs().planetArtifacts[locationId][i] == artifactId) { + a = LibArtifact.decode(artifactId); + found = true; + return a; + } + } + + require(found, "artifact not found"); + } } diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 0d59a8bd..ec5581aa 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -76,7 +76,7 @@ library LibArtifactUtils { tokenId + id, // Make each ship unique owner ); - LibGameUtils._putSpaceshipOnPlanet(planetId, spaceship.id); + LibSpaceship.putSpaceshipOnPlanet(planetId, spaceship.id); return spaceship.id; } @@ -98,10 +98,10 @@ library LibArtifactUtils { ) ); - (ArtifactType artifactType, uint256 levelBonus) = LibGameUtils - ._randomArtifactTypeAndLevelBonus(artifactSeed, biome, planet.spaceType); + (ArtifactType artifactType, uint256 levelBonus) = LibArtifact + .randomArtifactTypeAndLevelBonus(artifactSeed, biome, planet.spaceType); - ArtifactRarity rarity = LibGameUtils.artifactRarityFromPlanetLevel( + ArtifactRarity rarity = LibArtifact.artifactRarityFromPlanetLevel( levelBonus + planet.planetLevel ); uint256 tokenId = LibArtifact.encode( @@ -119,7 +119,7 @@ library LibArtifactUtils { args.coreAddress ); - LibGameUtils._putArtifactOnPlanet(args.planetId, foundArtifact.id); + LibArtifact.putArtifactOnPlanet(args.planetId, foundArtifact.id); planet.hasTriedFindingArtifact = true; gs().players[msg.sender].score += gameConstants().ARTIFACT_POINT_VALUES[ @@ -135,20 +135,22 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - Artifact memory artifact = decodeArtifact(artifactId); + Artifact memory artifact = LibArtifact.decode(artifactId); if (LibSpaceship.isShip(artifactId)) { require( - LibGameUtils.isSpaceshipOnPlanet(locationId, artifactId), + LibSpaceship.isSpaceshipOnPlanet(locationId, artifactId), "can't activate a ship on a planet it's not on" ); activateSpaceshipArtifact(locationId, artifactId, planet); - } else { + } else if (LibArtifact.isArtifact(artifactId)) { require( - LibGameUtils.isArtifactOnPlanet(locationId, artifactId), + LibArtifact.isArtifactOnPlanet(locationId, artifactId), "can't activate an artifact on a planet it's not on" ); activateNonSpaceshipArtifact(locationId, artifactId, wormholeTo, planet, artifact); + } else { + require(false, "token cannot be activated"); } } @@ -185,7 +187,7 @@ library LibArtifactUtils { // TODO: Why not actually burn? // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeSpaceshipOffPlanet(locationId, shipId); + LibSpaceship.takeSpaceshipOffPlanet(locationId, shipId); emit ArtifactDeactivated(msg.sender, locationId, shipId); } } @@ -202,13 +204,13 @@ library LibArtifactUtils { "you must own the planet you are activating an artifact on" ); require( - getActiveArtifact(locationId).tokenType == TokenType.Unknown, + !LibArtifact.hasActiveArtifact(locationId), "there is already an active artifact on this planet" ); require(!planet.destroyed, "planet is destroyed"); require( - getPlanetArtifact(locationId, artifactId).tokenType != TokenType.Unknown, + LibArtifact.getPlanetArtifact(locationId, artifactId).tokenType != TokenType.Unknown, "this artifact is not on this planet" ); @@ -250,11 +252,11 @@ library LibArtifactUtils { emit ArtifactDeactivated(msg.sender, locationId, artifactId); // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); + LibArtifact.takeArtifactOffPlanet(locationId, artifactId); } // this is fine even tho some artifacts are immediately deactivated, because // those artifacts do not buff the planet. - LibGameUtils._buffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); + LibGameUtils._buffPlanet(locationId, LibArtifact.getUpgradeForArtifact(artifact)); } function deactivateArtifact(uint256 locationId) public { @@ -267,15 +269,14 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); - Artifact memory artifact = getActiveArtifact(locationId); - require( - artifact.tokenType != TokenType.Unknown, - "this artifact is not activated on this planet" + LibArtifact.hasActiveArtifact(locationId), + "there is no artifact to deactivate on this planet" ); - // artifact.lastDeactivated = block.timestamp; - // LOL just pretend there is a wormhole. + Artifact memory artifact = LibArtifact.getActiveArtifact(locationId); + + // In case just pretend there is a wormhole. gs().planetWormholes[locationId] = 0; gs().planetActiveArtifact[locationId] = 0; gs().planetArtifactActivationTime[locationId] = 0; @@ -286,10 +287,10 @@ library LibArtifactUtils { artifact.artifactType == ArtifactType.PhotoidCannon; if (shouldBurn) { // burn it after use. will be owned by contract but not on a planet anyone can control - LibGameUtils._takeArtifactOffPlanet(locationId, artifact.id); + LibArtifact.takeArtifactOffPlanet(locationId, artifact.id); } - LibGameUtils._debuffPlanet(locationId, LibGameUtils._getUpgradeForArtifact(artifact)); + LibGameUtils._debuffPlanet(locationId, LibArtifact.getUpgradeForArtifact(artifact)); } function depositArtifact( @@ -307,7 +308,7 @@ library LibArtifactUtils { ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); - Artifact memory artifact = decodeArtifact(artifactId); + Artifact memory artifact = LibArtifact.decode(artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" @@ -319,7 +320,7 @@ library LibArtifactUtils { "too many tokens on this planet" ); - LibGameUtils._putArtifactOnPlanet(locationId, artifactId); + LibArtifact.putArtifactOnPlanet(locationId, artifactId); // artifactId, curr owner, new owner DFArtifactFacet(address(this)).transferArtifact(artifactId, msg.sender, coreAddress); } @@ -333,14 +334,14 @@ library LibArtifactUtils { ); require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.owner == msg.sender, "you can only withdraw from a planet you own"); - Artifact memory artifact = getPlanetArtifact(locationId, artifactId); + Artifact memory artifact = LibArtifact.getPlanetArtifact(locationId, artifactId); require( planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to withdraw this artifact" ); require(!LibSpaceship.isShip(artifactId), "cannot withdraw spaceships"); - LibGameUtils._takeArtifactOffPlanet(locationId, artifactId); + LibArtifact.takeArtifactOffPlanet(locationId, artifactId); // artifactId, curr owner, new owner DFArtifactFacet(address(this)).transferArtifact(artifactId, address(this), msg.sender); @@ -375,94 +376,4 @@ library LibArtifactUtils { return false; } - - /** - * @notice Create the collection ID for a given artifact - * @param _collectionType type of artifact - * @param _rarity rarity of artifact - * @param _artifactType of artifact - * @param _biome of artifact - * @notice this is not a struct because I can't figure out how to bit shift a uint in a struct. - */ - function encodeArtifact( - uint256 _collectionType, - uint256 _rarity, - uint256 _artifactType, - uint256 _biome - ) public pure returns (uint256) { - uint256 tokenType = _collectionType << LibUtils.calcBitShift(uint8(ArtifactInfo.TokenType)); - uint256 rarity = _rarity << LibUtils.calcBitShift(uint8(ArtifactInfo.ArtifactRarity)); - uint256 artifactType = _artifactType << - LibUtils.calcBitShift(uint8(ArtifactInfo.ArtifactType)); - uint256 biome = _biome << LibUtils.calcBitShift(uint8(ArtifactInfo.Biome)); - return tokenType + rarity + artifactType + biome; - } - - /** - * @notice Fetch the Artifact for the given id - * @param artifactId type of artifact - */ - function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { - bytes memory _b = abi.encodePacked(artifactId); - // 0 is left most element. 0 is given the property Unknown in ArtifactInfo. - - // Note: Bit shifting requires an index greater than zero. This is why the ArtifactInfo has - // Unknown as the zero property, so calcBitShift(ArtifactInfo.Level) is correct. - // As a consequence, we need to - // offset fetching the relevant byte from the artifactId by 1. - // However - uint8 tokenType = uint8(_b[uint8(ArtifactInfo.TokenType) - 1]); - uint8 rarity = uint8(_b[uint8(ArtifactInfo.ArtifactRarity) - 1]); - uint8 artifactType = uint8(_b[uint8(ArtifactInfo.ArtifactType) - 1]); - uint8 biome = uint8(_b[uint8(ArtifactInfo.Biome) - 1]); - - Artifact memory a = Artifact({ - id: artifactId, - tokenType: TokenType(tokenType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - planetBiome: Biome(biome) - }); - - return a; - } - - // if the given planet has an activated artifact on it, then return the artifact - // otherwise, return a 'null artifact' - function getActiveArtifact(uint256 locationId) public view returns (Artifact memory) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; - if (artifactId != 0) return decodeArtifact(artifactId); - - return _nullArtifactProperties(); - } - - function _nullArtifactProperties() private pure returns (Artifact memory) { - return - Artifact( - 0, - TokenType.Unknown, - ArtifactRarity.Unknown, - ArtifactType.Unknown, - Biome.Unknown - ); - } - - // if the given artifact is on the given planet, then return the artifact - // otherwise, return a 'null' artifact - function getPlanetArtifact(uint256 locationId, uint256 artifactId) - public - view - returns (Artifact memory a) - { - bool found = false; - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { - a = decodeArtifact(artifactId); - found = true; - return a; - } - } - - require(found, "artifact not found"); - } } diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 1404ac96..9755c863 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -6,6 +6,7 @@ import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; // Library imports import {ABDKMath64x64} from "../vendor/libraries/ABDKMath64x64.sol"; +import {LibArtifact} from "./LibArtifact.sol"; import {LibUtils} from "./LibUtils.sol"; // Storage imports @@ -147,61 +148,6 @@ library LibGameUtils { } } - function _randomArtifactTypeAndLevelBonus( - uint256 artifactSeed, - Biome biome, - SpaceType spaceType - ) internal pure returns (ArtifactType, uint256) { - uint256 lastByteOfSeed = artifactSeed % 0xFF; - uint256 secondLastByteOfSeed = ((artifactSeed - lastByteOfSeed) / 256) % 0xFF; - - ArtifactType artifactType = ArtifactType.Pyramid; - - if (lastByteOfSeed < 39) { - artifactType = ArtifactType.Monolith; - } else if (lastByteOfSeed < 78) { - artifactType = ArtifactType.Colossus; - } - // else if (lastByteOfSeed < 117) { - // artifactType = ArtifactType.Spaceship; - // } - else if (lastByteOfSeed < 156) { - artifactType = ArtifactType.Pyramid; - } else if (lastByteOfSeed < 171) { - artifactType = ArtifactType.Wormhole; - } else if (lastByteOfSeed < 186) { - artifactType = ArtifactType.PlanetaryShield; - } else if (lastByteOfSeed < 201) { - artifactType = ArtifactType.PhotoidCannon; - } else if (lastByteOfSeed < 216) { - artifactType = ArtifactType.BloomFilter; - } else if (lastByteOfSeed < 231) { - artifactType = ArtifactType.BlackDomain; - } else { - if (biome == Biome.Ice) { - artifactType = ArtifactType.PlanetaryShield; - } else if (biome == Biome.Lava) { - artifactType = ArtifactType.PhotoidCannon; - } else if (biome == Biome.Wasteland) { - artifactType = ArtifactType.BloomFilter; - } else if (biome == Biome.Corrupted) { - artifactType = ArtifactType.BlackDomain; - } else { - artifactType = ArtifactType.Wormhole; - } - artifactType = ArtifactType.PhotoidCannon; - } - - uint256 bonus = 0; - if (secondLastByteOfSeed < 4) { - bonus = 2; - } else if (secondLastByteOfSeed < 16) { - bonus = 1; - } - - return (artifactType, bonus); - } - // TODO v0.6: handle corrupted biomes function _getBiome(SpaceType spaceType, uint256 biomebase) public view returns (Biome) { if (spaceType == SpaceType.DEAD_SPACE) { @@ -258,221 +204,6 @@ library LibGameUtils { }); } - function _getUpgradeForArtifact(Artifact memory artifact) public pure returns (Upgrade memory) { - if (artifact.artifactType == ArtifactType.PlanetaryShield) { - uint256[6] memory defenseMultipliersPerRarity = [uint256(100), 150, 200, 300, 450, 650]; - - return - Upgrade({ - popCapMultiplier: 100, - popGroMultiplier: 100, - rangeMultiplier: 20, - speedMultiplier: 20, - defMultiplier: defenseMultipliersPerRarity[uint256(artifact.rarity)] - }); - } - - if (artifact.artifactType == ArtifactType.PhotoidCannon) { - uint256[6] memory def = [uint256(100), 50, 40, 30, 20, 10]; - return - Upgrade({ - popCapMultiplier: 100, - popGroMultiplier: 100, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: def[uint256(artifact.rarity)] - }); - } - - if (uint256(artifact.artifactType) >= 5) { - return - Upgrade({ - popCapMultiplier: 100, - popGroMultiplier: 100, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: 100 - }); - } - - Upgrade memory ret = Upgrade({ - popCapMultiplier: 100, - popGroMultiplier: 100, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: 100 - }); - - if (artifact.artifactType == ArtifactType.Monolith) { - ret.popCapMultiplier += 5; - ret.popGroMultiplier += 5; - } else if (artifact.artifactType == ArtifactType.Colossus) { - ret.speedMultiplier += 5; - } else if (artifact.artifactType == ArtifactType.Spaceship) { - ret.rangeMultiplier += 5; - } else if (artifact.artifactType == ArtifactType.Pyramid) { - ret.defMultiplier += 5; - } - - if (artifact.planetBiome == Biome.Ocean) { - ret.speedMultiplier += 5; - ret.defMultiplier += 5; - } else if (artifact.planetBiome == Biome.Forest) { - ret.defMultiplier += 5; - ret.popCapMultiplier += 5; - ret.popGroMultiplier += 5; - } else if (artifact.planetBiome == Biome.Grassland) { - ret.popCapMultiplier += 5; - ret.popGroMultiplier += 5; - ret.rangeMultiplier += 5; - } else if (artifact.planetBiome == Biome.Tundra) { - ret.defMultiplier += 5; - ret.rangeMultiplier += 5; - } else if (artifact.planetBiome == Biome.Swamp) { - ret.speedMultiplier += 5; - ret.rangeMultiplier += 5; - } else if (artifact.planetBiome == Biome.Desert) { - ret.speedMultiplier += 10; - } else if (artifact.planetBiome == Biome.Ice) { - ret.rangeMultiplier += 10; - } else if (artifact.planetBiome == Biome.Wasteland) { - ret.defMultiplier += 10; - } else if (artifact.planetBiome == Biome.Lava) { - ret.popCapMultiplier += 10; - ret.popGroMultiplier += 10; - } else if (artifact.planetBiome == Biome.Corrupted) { - ret.rangeMultiplier += 5; - ret.speedMultiplier += 5; - ret.popCapMultiplier += 5; - ret.popGroMultiplier += 5; - } - - uint256 scale = 1 + (uint256(artifact.rarity) / 2); - - ret.popCapMultiplier = scale * ret.popCapMultiplier - (scale - 1) * 100; - ret.popGroMultiplier = scale * ret.popGroMultiplier - (scale - 1) * 100; - ret.speedMultiplier = scale * ret.speedMultiplier - (scale - 1) * 100; - ret.rangeMultiplier = scale * ret.rangeMultiplier - (scale - 1) * 100; - ret.defMultiplier = scale * ret.defMultiplier - (scale - 1) * 100; - - return ret; - } - - function artifactRarityFromPlanetLevel(uint256 planetLevel) - public - pure - returns (ArtifactRarity) - { - if (planetLevel <= 1) return ArtifactRarity.Common; - else if (planetLevel <= 3) return ArtifactRarity.Rare; - else if (planetLevel <= 5) return ArtifactRarity.Epic; - else if (planetLevel <= 7) return ArtifactRarity.Legendary; - else return ArtifactRarity.Mythic; - } - - // planets can have multiple artifacts on them. this function updates all the - // internal contract book-keeping to reflect that the given artifact was - // put on. note that this function does not transfer the artifact. - function _putArtifactOnPlanet(uint256 locationId, uint256 artifactId) public { - gs().planetArtifacts[locationId].push(artifactId); - } - - function _putSpaceshipOnPlanet(uint256 locationId, uint256 spaceshipId) public { - gs().planetSpaceships[locationId].push(spaceshipId); - } - - // TODO: Why not burn ? - // planets can have multiple artifacts on them. this function updates all the - // internal contract book-keeping to reflect that the given artifact was - // taken off the given planet. note that this function does not transfer the - // artifact. - // - // if the given artifact is not on the given planet, reverts - // if the given artifact is currently activated, reverts - - /** - * Should remove artifactId from planet with locationId if artifactId exists AND is not active. - */ - - function _takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) public { - uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; - - bool hadTheArtifact = false; - - for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { - require( - !isActivated(locationId, artifactId), - "you cannot take an activated artifact off a planet" - ); - - gs().planetArtifacts[locationId][i] = gs().planetArtifacts[locationId][ - artifactsOnThisPlanet - 1 - ]; - - hadTheArtifact = true; - break; - } - } - - require(hadTheArtifact, "this artifact was not present on this planet"); - gs().planetArtifacts[locationId].pop(); - } - - function _takeSpaceshipOffPlanet(uint256 locationId, uint256 spaceshipId) public { - uint256 shipsOnThisPlanet = gs().planetSpaceships[locationId].length; - - bool hadTheShip = false; - - for (uint256 i = 0; i < shipsOnThisPlanet; i++) { - if (gs().planetSpaceships[locationId][i] == spaceshipId) { - require( - !isActivated(locationId, spaceshipId), - "you cannot take an activated spaceship off a planet" - ); - - gs().planetSpaceships[locationId][i] = gs().planetSpaceships[locationId][ - shipsOnThisPlanet - 1 - ]; - - hadTheShip = true; - break; - } - } - - require(hadTheShip, "this ship was not present on this planet"); - gs().planetSpaceships[locationId].pop(); - } - - // an artifact is only considered 'activated' if this method returns true. - // we do not have an `isActive` field on artifact; the times that the - // artifact was last activated and deactivated are sufficent to determine - // whether or not the artifact is activated. - - function isActivated(uint256 locationId, uint256 artifactId) public view returns (bool) { - return (gs().planetActiveArtifact[locationId] == artifactId); - } - - function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) public view returns (bool) { - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { - return true; - } - } - - return false; - } - - function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) public view returns (bool) { - for (uint256 i; i < gs().planetSpaceships[locationId].length; i++) { - if (gs().planetSpaceships[locationId][i] == shipId) { - return true; - } - } - - return false; - } - // the space junk that a planet starts with function getPlanetDefaultSpaceJunk(Planet memory planet) public view returns (uint256) { if (planet.isHomePlanet) return 0; diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 8c61543b..818553fe 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -353,9 +353,9 @@ library LibPlanet { for (uint256 i = 0; i < 12; i++) { if (tokenIdsToAddToPlanet[i] != 0) { if (LibSpaceship.isShip(tokenIdsToAddToPlanet[i])) { - LibGameUtils._putSpaceshipOnPlanet(location, tokenIdsToAddToPlanet[i]); + LibSpaceship.putSpaceshipOnPlanet(location, tokenIdsToAddToPlanet[i]); } else if (LibArtifact.isArtifact(tokenIdsToAddToPlanet[i])) { - LibGameUtils._putArtifactOnPlanet(location, tokenIdsToAddToPlanet[i]); + LibArtifact.putArtifactOnPlanet(location, tokenIdsToAddToPlanet[i]); } } } diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index 09bf5683..3d143a32 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -12,11 +12,16 @@ import "hardhat/console.sol"; import {LibUtils} from "./LibUtils.sol"; // Storage imports +import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; // Type imports import {Spaceship, SpaceshipInfo, SpaceshipType, TokenType} from "../DFTypes.sol"; library LibSpaceship { + function gs() internal pure returns (GameStorage storage) { + return LibStorage.gameStorage(); + } + /** * @notice Create the token ID for a Spaceship with the following properties: * @param spaceship Spaceship @@ -57,4 +62,37 @@ library LibSpaceship { uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); return (TokenType(tokenType) == TokenType.Spaceship); } + + function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) internal view returns (bool) { + for (uint256 i; i < gs().planetSpaceships[locationId].length; i++) { + if (gs().planetSpaceships[locationId][i] == shipId) { + return true; + } + } + return false; + } + + function putSpaceshipOnPlanet(uint256 locationId, uint256 spaceshipId) internal { + gs().planetSpaceships[locationId].push(spaceshipId); + } + + function takeSpaceshipOffPlanet(uint256 locationId, uint256 spaceshipId) internal { + uint256 shipsOnThisPlanet = gs().planetSpaceships[locationId].length; + + bool hadTheShip = false; + + for (uint256 i = 0; i < shipsOnThisPlanet; i++) { + if (gs().planetSpaceships[locationId][i] == spaceshipId) { + gs().planetSpaceships[locationId][i] = gs().planetSpaceships[locationId][ + shipsOnThisPlanet - 1 + ]; + + hadTheShip = true; + break; + } + } + + require(hadTheShip, "this ship was not present on this planet"); + gs().planetSpaceships[locationId].pop(); + } } diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index 431b5ae0..adedbfb2 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -309,15 +309,9 @@ export async function deployAndCut( return [diamond, diamondInit, initReceipt] as const; } -export async function deployGetterFacet( - {}, - { LibArtifactUtils }: Libraries, - hre: HardhatRuntimeEnvironment -) { +export async function deployGetterFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { const factory = await hre.ethers.getContractFactory('DFGetterFacet', { - libraries: { - LibArtifactUtils, - }, + libraries: {}, }); const contract = await factory.deploy(); await contract.deployTransaction.wait(); diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index e25450d5..7b618752 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -80,40 +80,20 @@ describe('DarkForestArtifacts', function () { }); describe('it tests basic artifact actions', function () { - it('logs bits for artifact old', async function () { - // Must be valid options - const _collectionType = '0x01'; - const _rarity = ArtifactRarity.Legendary; - const _artifactType = ArtifactType.Colossus; - const _biome = Biome.DESERT; - const res = await world.contract.encodeArtifact( - _collectionType, - _rarity, - _artifactType, - _biome - ); - const { tokenType, rarity, planetBiome, artifactType } = await world.contract.getArtifact( - res - ); - expect(tokenType).to.equal(Number(_collectionType)); - expect(rarity).to.equal(Number(_rarity)); - expect(planetBiome).to.equal(Number(_biome)); - expect(artifactType).to.equal(Number(_artifactType)); - }); it('encodes and decodes artifact', async function () { // Must be valid options const tokenType = TokenType.Artifact; const rarity = ArtifactRarity.Legendary; const artifactType = ArtifactType.Colossus; const planetBiome = Biome.DESERT; - const res = await world.contract.testEncodeArtifact({ + const res = await world.contract.encodeArtifact({ id: 0, tokenType, rarity, artifactType, planetBiome, }); - const a = await world.contract.getArtifact(res); + const a = await world.contract.decodeArtifact(res); expect(tokenType).to.equal(Number(a.tokenType)); expect(rarity).to.equal(Number(a.rarity)); expect(artifactType).to.equal(Number(a.artifactType)); @@ -123,12 +103,12 @@ describe('DarkForestArtifacts', function () { // Must be valid options const tokenType = TokenType.Spaceship; const spaceshipType = 2; - const res = await world.contract.testEncodeSpaceship({ + const res = await world.contract.encodeSpaceship({ id: 0, tokenType, spaceshipType, }); - const a = await world.contract.testDecodeSpaceship(res); + const a = await world.contract.decodeSpaceship(res); expect(tokenType).to.equal(Number(a.tokenType)); expect(spaceshipType).to.equal(Number(a.spaceshipType)); }); @@ -144,10 +124,9 @@ describe('DarkForestArtifacts', function () { ); const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); - await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - prettyPrintToken(await world.user1Core.getArtifact(artifactId)); + prettyPrintToken(await world.user1Core.decodeArtifact(artifactId)); // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); @@ -475,7 +454,7 @@ describe('DarkForestArtifacts', function () { await increaseBlockchainTime(); // move artifact - world.user1Core.move( + await world.user1Core.move( ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, newArtifactId) ); @@ -491,7 +470,7 @@ describe('DarkForestArtifacts', function () { world.user1Core.move( ...makeMoveArgs(ARTIFACT_PLANET_1, SPAWN_PLANET_1, 10, 50000, 0, 12345) ) - ).to.be.revertedWith('this artifact was not present on this planet'); + ).to.be.revertedWith('cannot move token of this type'); }); }); @@ -649,7 +628,7 @@ describe('DarkForestArtifacts', function () { TokenType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(artifactId)); + prettyPrintToken(await world.contract.decodeArtifact(artifactId)); activateAndConfirm(world.user1Core, from.id, artifactId, to.id); @@ -810,7 +789,7 @@ describe('DarkForestArtifacts', function () { TokenType.Artifact, { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(newTokenId)); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); @@ -858,7 +837,7 @@ describe('DarkForestArtifacts', function () { { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(newTokenId)); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -966,7 +945,7 @@ describe('DarkForestArtifacts', function () { TokenType.Artifact, { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(newTokenId)); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); await activateAndConfirm(world.user1Core, LVL3_SPACETIME_1.id, newTokenId); @@ -1011,7 +990,7 @@ describe('DarkForestArtifacts', function () { TokenType.Artifact, { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } ); - prettyPrintToken(await world.contract.getArtifact(newTokenId)); + prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index 31363352..a288174b 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -333,14 +333,20 @@ export async function createArtifact( contract: DarkForest, owner: string, planet: TestLocation, - type: ArtifactType, - collectionType = TokenType.Artifact, + artifactType: ArtifactType, + tokenType = TokenType.Artifact, { rarity, biome }: { rarity?: ArtifactRarity; biome?: Biome } = {} ) { rarity ||= ArtifactRarity.Common; biome ||= Biome.FOREST; - const tokenId = await contract.encodeArtifact(collectionType, rarity, type, biome); + const tokenId = await contract.encodeArtifact({ + id: 0, + tokenType, + rarity, + artifactType, + planetBiome: biome, + }); await contract.adminGiveArtifact({ tokenId, discoverer: owner, @@ -348,7 +354,7 @@ export async function createArtifact( planetId: planet.id, rarity: rarity.toString(), biome: biome.toString(), - artifactType: type.toString(), + artifactType: artifactType.toString(), controller: ZERO_ADDRESS, }); From c35263e84339142c7906a4d685dbc6bb368637f6 Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Mon, 3 Oct 2022 06:14:46 -0400 Subject: [PATCH 37/55] Apply tweaks from code reivew Co-authored-by: Blaine Bublitz --- eth/contracts/DFTypes.sol | 9 --------- eth/contracts/facets/DFAdminFacet.sol | 1 - eth/contracts/facets/DFArtifactFacet.sol | 1 - eth/contracts/facets/DFGetterFacet.sol | 1 - eth/contracts/facets/DFMoveFacet.sol | 1 - eth/contracts/libraries/LibArtifact.sol | 7 ------- eth/contracts/libraries/LibGameUtils.sol | 1 - eth/contracts/libraries/LibPlanet.sol | 1 - eth/contracts/libraries/LibSpaceship.sol | 2 -- eth/hardhat.config.ts | 1 - eth/package.json | 1 - eth/tasks/deploy.ts | 1 - 12 files changed, 27 deletions(-) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 893d328e..b0f29eba 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -226,15 +226,6 @@ enum ArtifactRarity { Mythic } -// for artifact getters -// struct ArtifactWithMetadata { -// Artifact artifact; -// Upgrade upgrade; -// Upgrade timeDelayedUpgrade; // for photoid canons specifically. -// address owner; -// uint256 locationId; // 0 if planet is not deposited into contract or is on a voyage -// uint256 voyageId; // 0 is planet is not deposited into contract or is on a planet -// } enum Biome { Unknown, diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 3ee57b8e..64bd9acf 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -14,7 +14,6 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Contract imports import {DFArtifactFacet} from "./DFArtifactFacet.sol"; -import "hardhat/console.sol"; // Type imports import {Artifact, SpaceType, Spaceship, SpaceshipType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet, TokenType} from "../DFTypes.sol"; diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index e92107f2..14df6686 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -21,7 +21,6 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship, SpaceshipType} from "../DFTypes.sol"; -import "hardhat/console.sol"; contract DFArtifactFacet is WithStorage, SolidStateERC1155 { event PlanetProspected(address player, uint256 loc); diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index aa15a491..145f8f39 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -16,7 +16,6 @@ import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorag // Type imports import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade, Spaceship} from "../DFTypes.sol"; -import "hardhat/console.sol"; contract DFGetterFacet is WithStorage { // FIRST-LEVEL GETTERS - mirrors the solidity autogenerated toplevel getters, but for GameStorage diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index a10d438c..97bdf8ff 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -18,7 +18,6 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Spaceship, SpaceshipType, Upgrade} from "../DFTypes.sol"; -import "hardhat/console.sol"; contract DFMoveFacet is WithStorage { modifier notPaused() { diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index adbc62b4..3da3142b 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -5,8 +5,6 @@ pragma solidity ^0.8.0; * Library for all things Artifacts */ -// Contract imports -import "hardhat/console.sol"; // Library imports import {LibUtils} from "./LibUtils.sol"; @@ -277,9 +275,6 @@ library LibArtifact { return (artifactType, bonus); } - // planets can have multiple artifacts on them. this function updates all the - // internal contract book-keeping to reflect that the given artifact was - // put on. note that this function does not transfer the artifact. function putArtifactOnPlanet(uint256 locationId, uint256 artifactId) internal { gs().planetArtifacts[locationId].push(artifactId); } @@ -323,8 +318,6 @@ library LibArtifact { } } - // if the given planet has an activated artifact on it, then return the artifact - // otherwise, return a 'null artifact' function hasActiveArtifact(uint256 locationId) internal view returns (bool) { uint256 artifactId = gs().planetActiveArtifact[locationId]; return artifactId != 0; diff --git a/eth/contracts/libraries/LibGameUtils.sol b/eth/contracts/libraries/LibGameUtils.sol index 9755c863..4bc35df0 100644 --- a/eth/contracts/libraries/LibGameUtils.sol +++ b/eth/contracts/libraries/LibGameUtils.sol @@ -13,7 +13,6 @@ import {LibUtils} from "./LibUtils.sol"; import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStorage.sol"; import {Biome, SpaceType, Planet, PlanetType, PlanetEventType, Artifact, ArtifactType, TokenType, ArtifactRarity, Upgrade, PlanetDefaultStats} from "../DFTypes.sol"; -import "hardhat/console.sol"; library LibGameUtils { function gs() internal pure returns (GameStorage storage) { diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 818553fe..9d317390 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -17,7 +17,6 @@ import {LibStorage, GameStorage, GameConstants, SnarkConstants} from "./LibStora // Type imports import {ArtifactType, Artifact, DFPInitPlanetArgs, Planet, PlanetEventMetadata, PlanetType, RevealedCoords, SpaceType, Spaceship, SpaceshipType, Upgrade, UpgradeBranch} from "../DFTypes.sol"; -import "hardhat/console.sol"; library LibPlanet { function gs() internal pure returns (GameStorage storage) { diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index 3d143a32..a350f98f 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -5,8 +5,6 @@ pragma solidity ^0.8.0; * Library for all things Spaceships */ -// Contract imports -import "hardhat/console.sol"; // Library imports import {LibUtils} from "./LibUtils.sol"; diff --git a/eth/hardhat.config.ts b/eth/hardhat.config.ts index 20a56f9d..fc7dee6e 100644 --- a/eth/hardhat.config.ts +++ b/eth/hardhat.config.ts @@ -219,7 +219,6 @@ const config: HardhatUserConfig = { // This plugin will combine all ABIs from any Smart Contract with `Facet` in the name or path and output it as `DarkForest.json` name: 'DarkForest', include: ['Facet', 'DFDiamond'], - exclude: ['Old'], // We explicitly set `strict` to `true` because we want to validate our facets don't accidentally provide overlapping functions strict: true, // We use our diamond utils to filter some functions we ignore from the combined ABI diff --git a/eth/package.json b/eth/package.json index 45969814..3f2c2f4f 100644 --- a/eth/package.json +++ b/eth/package.json @@ -20,7 +20,6 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", - "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index adedbfb2..8066ff3f 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -409,7 +409,6 @@ export async function deployLibraries({}, hre: HardhatRuntimeEnvironment) { libraries: { LibGameUtils: LibGameUtils.address, LibLazyUpdate: LibLazyUpdate.address, - // LibArtifactUtils: LibArtifactUtils.address, }, }); const LibPlanet = await LibPlanetFactory.deploy(); From 33541026b72232fe818a11631e20592ccf66b5d5 Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Mon, 3 Oct 2022 06:20:52 -0400 Subject: [PATCH 38/55] More tweaks Co-authored-by: Blaine Bublitz --- eth/contracts/libraries/LibArtifact.sol | 1 - eth/contracts/libraries/LibArtifactUtils.sol | 2 -- 2 files changed, 3 deletions(-) diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 3da3142b..417e98a5 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -262,7 +262,6 @@ library LibArtifact { } else { artifactType = ArtifactType.Wormhole; } - artifactType = ArtifactType.PhotoidCannon; } uint256 bonus = 0; diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index ec5581aa..728e8d44 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -16,7 +16,6 @@ import {LibStorage, GameStorage, GameConstants} from "./LibStorage.sol"; // Type imports import {Biome, Planet, PlanetType, ArtifactType, ArtifactRarity, TokenType, DFPFindArtifactArgs, DFTCreateArtifactArgs, Artifact, ArtifactInfo, Spaceship, SpaceshipType} from "../DFTypes.sol"; -import "hardhat/console.sol"; library LibArtifactUtils { function gs() internal pure returns (GameStorage storage) { @@ -247,7 +246,6 @@ library LibArtifactUtils { } if (shouldDeactivateAndBurn) { - // artifact.lastDeactivated = block.timestamp; // immediately deactivate gs().planetActiveArtifact[locationId] = 0; // immediately remove activate artifact emit ArtifactDeactivated(msg.sender, locationId, artifactId); From b8eb16d8466752392afe1a4eed380f188aedf294 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 11:26:43 +0100 Subject: [PATCH 39/55] fix: return when found in getter --- eth/contracts/facets/DFGetterFacet.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 145f8f39..4b005e61 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -372,11 +372,11 @@ contract DFGetterFacet is WithStorage { bool hasToken = false; uint256[] memory artifactIds = gs().planetArtifacts[locationId]; for (uint256 i = 0; i < artifactIds.length; i++) { - if (artifactIds[i] == tokenId) hasToken = true; + if (artifactIds[i] == tokenId) return true; } uint256[] memory shipIds = gs().planetSpaceships[locationId]; for (uint256 i = 0; i < shipIds.length; i++) { - if (shipIds[i] == tokenId) hasToken = true; + if (shipIds[i] == tokenId) return true; } return hasToken; } From 43c53acc83d4c7d9bd6c6d6095796a5dec175275 Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Mon, 3 Oct 2022 06:29:41 -0400 Subject: [PATCH 40/55] README tweaks Co-authored-by: Blaine Bublitz --- eth/contracts/Tokens.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 4c4d5b30..f00caf30 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -8,9 +8,9 @@ The fundamental data structure in ERC1155 is `mapping(uint256 => mapping(address `balances[tokenId][myAddress]` = number of tokens I have of a given collection. -The `uint 256 tokenId`, which identifies a _set_ of tokens, is represented in the following way: +The `uint256` `tokenId`, which identifies a _set_ of tokens, is represented in the following way: -Each `uint256 tokenId` is broken down into 32 chunks of 8 bits each (32\*8 = 256). +Each `uint256` `tokenId` is broken down into 32 chunks of 8 bits each (32\*8 = 256). > | chunk1 | chunk 2 | ... chunk32 |. @@ -55,7 +55,7 @@ The `Lib.sol`. file **must** have the following methods: where `` can be a struct (like Artifacts or Spaceships) or just a uint256 (like Silver). Additionally methods can be added to each library, but they must be `internal` functions that can be -inherited by other facets or libraries. +inlined into other facets or libraries. ## Artifacts @@ -217,7 +217,7 @@ For the wormhole, we do the same: ## Simulteanous Activate and Deactivate -Some Bloom Filters (Artifacts) and Crescents (Spaceships) are burned on use. +Some Artifacts (Bloom Filters) and Spaceships (Crescents) are burned on use. All we do is make sure that we call the Activate and Deactivate functions in the same transaction. From 289c9e5d63df4666027cac43f43b969562f93d52 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 11:31:42 +0100 Subject: [PATCH 41/55] feat: explain chunk properties in README --- eth/contracts/Tokens.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index f00caf30..70ae09bd 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -18,7 +18,11 @@ Chunks are used from left to right, so a token that has a value of `0xff` in chu Another way to visualize the `tokenId` is by highlighting each chunk: `0x**ff**_00_**00**_00_**00**_00_...` -Each chunk allows for 2^8 (256) unique pieces of information. If you need more than 256 properties of a token, you can use an additional chunk +Each chunk allows for 2^8 (256) unique pieces of information. If you need more than 256 properties +of a token, you can use an additional chunk. + +For example, if you wanted to add a new property to an Artifact called `Weather`, you use the next +chunk(s) to encode that value. You would have 256 options for what `Weather` an Artifact could have. This architecture allows us to encode information about a Dark Forest token in the `tokenId` itself, and, more importantly, it allows to create a copy of a token just by using the same `tokenId`. From c47180c2dfd5dccbf3b8faf9e7a04d4f5b7cbc57 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 13:42:15 +0100 Subject: [PATCH 42/55] fix: ordering of args for artifact events --- eth/contracts/facets/DFArtifactFacet.sol | 5 ++--- eth/contracts/libraries/LibArtifactUtils.sol | 8 ++++---- eth/contracts/libraries/LibPlanet.sol | 2 -- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 14df6686..4ae35d12 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -21,7 +21,6 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship, SpaceshipType} from "../DFTypes.sol"; - contract DFArtifactFacet is WithStorage, SolidStateERC1155 { event PlanetProspected(address player, uint256 loc); event ArtifactFound(address player, uint256 artifactId, uint256 loc); @@ -170,7 +169,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { LibArtifactUtils.depositArtifact(locationId, artifactId, address(this)); - emit ArtifactDeposited(msg.sender, locationId, artifactId); + emit ArtifactDeposited(msg.sender, artifactId, locationId); } // withdraws the given artifact from the given planet. you must own the planet, @@ -180,7 +179,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { LibArtifactUtils.withdrawArtifact(locationId, artifactId); - emit ArtifactWithdrawn(msg.sender, locationId, artifactId); + emit ArtifactWithdrawn(msg.sender, artifactId, locationId); } // activates the given artifact on the given planet. the artifact must have diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index 728e8d44..a513a6f4 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -182,12 +182,12 @@ library LibArtifactUtils { } planet.planetType = PlanetType.SILVER_MINE; - emit ArtifactActivated(msg.sender, locationId, shipId); + emit ArtifactActivated(msg.sender, shipId, locationId); // TODO: Why not actually burn? // burn it after use. will be owned by contract but not on a planet anyone can control LibSpaceship.takeSpaceshipOffPlanet(locationId, shipId); - emit ArtifactDeactivated(msg.sender, locationId, shipId); + emit ArtifactDeactivated(msg.sender, shipId, locationId); } } @@ -217,7 +217,7 @@ library LibArtifactUtils { gs().planetArtifactActivationTime[locationId] = block.timestamp; gs().planetActiveArtifact[locationId] = artifactId; - emit ArtifactActivated(msg.sender, locationId, artifactId); + emit ArtifactActivated(msg.sender, artifactId, locationId); if (artifact.artifactType == ArtifactType.Wormhole) { require(wormholeTo != 0, "you must provide a wormholeTo to activate a wormhole"); @@ -248,7 +248,7 @@ library LibArtifactUtils { if (shouldDeactivateAndBurn) { gs().planetActiveArtifact[locationId] = 0; // immediately remove activate artifact - emit ArtifactDeactivated(msg.sender, locationId, artifactId); + emit ArtifactDeactivated(msg.sender, artifactId, locationId); // burn it after use. will be owned by contract but not on a planet anyone can control LibArtifact.takeArtifactOffPlanet(locationId, artifactId); } diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 9d317390..e3bed06d 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -32,8 +32,6 @@ library LibPlanet { } // also need to copy some of DFCore's event signatures - event ArtifactActivated(address player, uint256 artifactId, uint256 loc); - event ArtifactDeactivated(address player, uint256 artifactId, uint256 loc); event PlanetUpgraded(address player, uint256 loc, uint256 branch, uint256 toBranchLevel); function revealLocation( From b682fefd585b90727a4a887ca66d29e3217b1332 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 13:58:56 +0100 Subject: [PATCH 43/55] fix: split up getActiveArtifact into two checks --- eth/contracts/facets/DFMoveFacet.sol | 70 +++++++++++++++---------- eth/contracts/libraries/LibArtifact.sol | 10 ++-- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 97bdf8ff..af8ff2f6 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -17,7 +17,7 @@ import {LibSpaceship} from "../libraries/LibSpaceship.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {ArrivalData, ArrivalType, Artifact, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Spaceship, SpaceshipType, Upgrade} from "../DFTypes.sol"; +import {ArrivalData, ArrivalType, Artifact, ArtifactRarity, ArtifactType, DFPCreateArrivalArgs, DFPMoveArgs, Planet, PlanetEventMetadata, PlanetEventType, Spaceship, SpaceshipType, Upgrade} from "../DFTypes.sol"; contract DFMoveFacet is WithStorage { modifier notPaused() { @@ -289,28 +289,40 @@ contract DFMoveFacet is WithStorage { returns (bool wormholePresent, uint256 effectiveDistModifier) { wormholePresent = false; - Artifact memory relevantWormhole; - Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); - Artifact memory activeArtifactTo = LibArtifact.getActiveArtifact(args.newLoc); - // TODO: take the greater rarity of these, or disallow wormholes between planets that - // already have a wormhole between them - if ( - activeArtifactFrom.artifactType == ArtifactType.Wormhole && - gs().planetWormholes[args.oldLoc] == args.newLoc - ) { - relevantWormhole = activeArtifactFrom; - wormholePresent = true; - } else if ( - activeArtifactTo.artifactType == ArtifactType.Wormhole && - gs().planetWormholes[args.newLoc] == args.oldLoc - ) { - relevantWormhole = activeArtifactTo; - wormholePresent = true; + ArtifactRarity wormholeRarity = ArtifactRarity.Unknown; + + // Check from Loc + if (LibArtifact.hasActiveArtifact(args.oldLoc)) { + Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); + // If active artifact is a Wormhole and destination is newLoc + if ( + activeArtifactFrom.artifactType == ArtifactType.Wormhole && + gs().planetWormholes[args.oldLoc] == args.newLoc + ) { + wormholeRarity = activeArtifactFrom.rarity; + wormholePresent = true; + } + } + // Check to loc + if (LibArtifact.hasActiveArtifact(args.newLoc)) { + Artifact memory activeArtifactTo = LibArtifact.getActiveArtifact(args.newLoc); + // If active artifact is a Wormhole and destination is fromLoc + if ( + activeArtifactTo.artifactType == ArtifactType.Wormhole && + gs().planetWormholes[args.newLoc] == args.oldLoc + ) { + // Ensures higher rarity wormhole will be used. + // TODO: Make sure client knows this. + if (activeArtifactTo.rarity > wormholeRarity) { + wormholeRarity = activeArtifactTo.rarity; + } + wormholePresent = true; + } } if (wormholePresent) { uint256[6] memory speedBoosts = [uint256(1), 2, 4, 8, 16, 32]; - effectiveDistModifier = speedBoosts[uint256(relevantWormhole.rarity)]; + effectiveDistModifier = speedBoosts[uint256(wormholeRarity)]; } } @@ -323,15 +335,17 @@ contract DFMoveFacet is WithStorage { private returns (bool photoidPresent, Upgrade memory temporaryUpgrade) { - Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); - if ( - activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && - block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= - gameConstants().PHOTOID_ACTIVATION_DELAY - ) { - photoidPresent = true; - LibArtifactUtils.deactivateArtifact(args.oldLoc); - temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); + if (LibArtifact.hasActiveArtifact(args.oldLoc)) { + Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); + if ( + activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && + block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= + gameConstants().PHOTOID_ACTIVATION_DELAY + ) { + photoidPresent = true; + LibArtifactUtils.deactivateArtifact(args.oldLoc); + temporaryUpgrade = LibGameUtils.timeDelayUpgrade(activeArtifactFrom); + } } } diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 417e98a5..2a195085 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; * Library for all things Artifacts */ - // Library imports import {LibUtils} from "./LibUtils.sol"; @@ -309,12 +308,9 @@ library LibArtifact { // if the given planet has an activated artifact on it, then return the artifact // otherwise, return a 'null artifact' function getActiveArtifact(uint256 locationId) internal view returns (Artifact memory) { - if (hasActiveArtifact(locationId)) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; - return LibArtifact.decode(artifactId); - } else { - return LibArtifact._nullArtifactProperties(); - } + require(hasActiveArtifact(locationId), "planet does not have an active artifact"); + uint256 artifactId = gs().planetActiveArtifact[locationId]; + return LibArtifact.decode(artifactId); } function hasActiveArtifact(uint256 locationId) internal view returns (bool) { From 84f35a6c2d566ab8c2444d0d513d5033bfc6ec1e Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 14:06:38 +0100 Subject: [PATCH 44/55] fix: remove @solidstate/spec --- package-lock.json | 68 ----------------------------------------------- 1 file changed, 68 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9a23a68..ce5bd949 100644 --- a/package-lock.json +++ b/package-lock.json @@ -227,7 +227,6 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", - "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", @@ -7116,24 +7115,6 @@ } } }, - "node_modules/@solidstate/library": { - "version": "0.0.41", - "resolved": "https://registry.npmjs.org/@solidstate/library/-/library-0.0.41.tgz", - "integrity": "sha512-JWn6wkN4QI/054XEjnzZe/ooNg0q2yq9kHe3EZEQyONSWzLYAFINr/c23dhUu4CnuR1gh2HeOpyOIufpIYSRvw==", - "dev": true, - "dependencies": { - "eth-permit": "^0.1.10" - } - }, - "node_modules/@solidstate/spec": { - "version": "0.0.41", - "resolved": "https://registry.npmjs.org/@solidstate/spec/-/spec-0.0.41.tgz", - "integrity": "sha512-yBKDVFr/tymCncdkJ0lpW9mvH3JXRD9VK1jjWg8nVio7CVXL3B5UWL5s/3Z1WQUGuyTJ4QpxT2jgbEohIgz0ZQ==", - "dev": true, - "dependencies": { - "@solidstate/library": "^0.0.41" - } - }, "node_modules/@spectrum-web-components/action-button": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@spectrum-web-components/action-button/-/action-button-0.7.3.tgz", @@ -15460,15 +15441,6 @@ "resolved": "eth", "link": true }, - "node_modules/eth-permit": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/eth-permit/-/eth-permit-0.1.10.tgz", - "integrity": "sha512-cMAjWvGEaCQoWSlKWBqkBMQvhCtXzVlkTCnYBxGRsCxznbBZSiYuOsHpTAb4EerRwQJkRgwd3vPvcZh3OA7Siw==", - "dev": true, - "dependencies": { - "utf8": "^3.0.0" - } - }, "node_modules/ethereum-cryptography": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", @@ -31154,12 +31126,6 @@ "node": ">=6.14.2" } }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -37574,24 +37540,6 @@ } } }, - "@solidstate/library": { - "version": "0.0.41", - "resolved": "https://registry.npmjs.org/@solidstate/library/-/library-0.0.41.tgz", - "integrity": "sha512-JWn6wkN4QI/054XEjnzZe/ooNg0q2yq9kHe3EZEQyONSWzLYAFINr/c23dhUu4CnuR1gh2HeOpyOIufpIYSRvw==", - "dev": true, - "requires": { - "eth-permit": "^0.1.10" - } - }, - "@solidstate/spec": { - "version": "0.0.41", - "resolved": "https://registry.npmjs.org/@solidstate/spec/-/spec-0.0.41.tgz", - "integrity": "sha512-yBKDVFr/tymCncdkJ0lpW9mvH3JXRD9VK1jjWg8nVio7CVXL3B5UWL5s/3Z1WQUGuyTJ4QpxT2jgbEohIgz0ZQ==", - "dev": true, - "requires": { - "@solidstate/library": "^0.0.41" - } - }, "@spectrum-web-components/action-button": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@spectrum-web-components/action-button/-/action-button-0.7.3.tgz", @@ -43820,7 +43768,6 @@ "@projectsophon/workspace": "^2.0.0", "@solidstate/contracts": "^0.0.41", "@solidstate/hardhat-4byte-uploader": "^1.0.2", - "@solidstate/spec": "^0.0.41", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", "@types/chai": "^4.2.14", @@ -43951,15 +43898,6 @@ } } }, - "eth-permit": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/eth-permit/-/eth-permit-0.1.10.tgz", - "integrity": "sha512-cMAjWvGEaCQoWSlKWBqkBMQvhCtXzVlkTCnYBxGRsCxznbBZSiYuOsHpTAb4EerRwQJkRgwd3vPvcZh3OA7Siw==", - "dev": true, - "requires": { - "utf8": "^3.0.0" - } - }, "ethereum-cryptography": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", @@ -55481,12 +55419,6 @@ "node-gyp-build": "^4.3.0" } }, - "utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", From 6ddc29a1f7e7c92f92e181b9ade170ae2d888900 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 15:19:49 +0100 Subject: [PATCH 45/55] feat: create for Spaceship and Artifact --- eth/contracts/facets/DFAdminFacet.sol | 11 +-- eth/contracts/facets/DFArtifactFacet.sol | 22 +++--- eth/contracts/facets/DFGetterFacet.sol | 4 ++ eth/contracts/libraries/LibArtifact.sol | 74 ++++++++++++-------- eth/contracts/libraries/LibArtifactUtils.sol | 25 ++----- eth/contracts/libraries/LibSpaceship.sol | 21 ++++-- eth/test/DFArtifacts.test.ts | 72 ++++++++----------- eth/test/utils/TestUtils.ts | 20 ++---- 8 files changed, 118 insertions(+), 131 deletions(-) diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 64bd9acf..7a04df0c 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -169,15 +169,8 @@ contract DFAdminFacet is WithStorage { function adminGiveArtifact(DFTCreateArtifactArgs memory args) public onlyAdmin { // Note: calling this in tests should supply Diamond address as args.owner - uint256 tokenId = LibArtifact.encode( - Artifact({ - id: 0, - tokenType: TokenType.Artifact, - rarity: args.rarity, - artifactType: args.artifactType, - planetBiome: args.biome - }) - ); + uint256 tokenId = LibArtifact.create(args.rarity, args.artifactType, args.biome); + Artifact memory artifact = DFArtifactFacet(address(this)).createArtifact( tokenId, args.owner diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 4ae35d12..2e792b56 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -88,26 +88,26 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { // Account, Id, Amount, Data _mint(owner, tokenId, 1, ""); - return getSpaceship(tokenId); + return getSpaceshipFromId(tokenId); } - function getSpaceship(uint256 shipId) public pure returns (Spaceship memory) { + function getSpaceshipFromId(uint256 shipId) public pure returns (Spaceship memory) { return LibSpaceship.decode(shipId); } - function encodeSpaceship(Spaceship memory spaceship) public pure returns (uint256) { - return LibSpaceship.encode(spaceship); + function createSpaceshipId(SpaceshipType spaceshipType) public pure returns (uint256) { + return LibSpaceship.create(spaceshipType); } - function decodeSpaceship(uint256 shipId) public pure returns (Spaceship memory) { - return LibSpaceship.decode(shipId); - } - - function encodeArtifact(Artifact memory artifact) public pure returns (uint256) { - return LibArtifact.encode(artifact); + function createArtifactId( + ArtifactRarity rarity, + ArtifactType artifactType, + Biome biome + ) public pure returns (uint256) { + return LibArtifact.create(rarity, artifactType, biome); } - function decodeArtifact(uint256 artifactId) public pure returns (Artifact memory) { + function getArtifactFromId(uint256 artifactId) public pure returns (Artifact memory) { return LibArtifact.decode(artifactId); } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 4b005e61..9ac4419a 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -381,6 +381,10 @@ contract DFGetterFacet is WithStorage { return hasToken; } + function hasActiveArtifact(uint256 locationId) public view returns (bool) { + return LibArtifact.hasActiveArtifact(locationId); + } + function getActiveArtifactOnPlanet(uint256 locationId) public view diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 2a195085..60b392c8 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -21,23 +21,30 @@ library LibArtifact { /** * @notice Create the token ID for a Artifact with the following properties: - * @param artifact Artifact + * @param _rarity Artifact + * @param _artifactType Artifact + * @param _biome Artifact */ - function encode(Artifact memory artifact) internal pure returns (uint256) { + function create( + ArtifactRarity _rarity, + ArtifactType _artifactType, + Biome _biome + ) internal pure returns (uint256) { // x << y is equivalent to the mathematical expression x * 2**y + require(isValidArtifactRarity(_rarity), "artifact rarity is not valid"); + require(isValidArtifactType(_artifactType), "artifact type is not valid"); + require(isValidBiome(_biome), "artifact biome is not valid"); + uint256 tokenType = LibUtils.shiftLeft( - uint8(artifact.tokenType), - uint8(ArtifactInfo.TokenType) - ); - uint256 rarity = LibUtils.shiftLeft( - uint8(artifact.rarity), - uint8(ArtifactInfo.ArtifactRarity) + uint8(TokenType.Artifact), // value + uint8(ArtifactInfo.TokenType) // chunk position in tokenId ); + uint256 rarity = LibUtils.shiftLeft(uint8(_rarity), uint8(ArtifactInfo.ArtifactRarity)); uint256 artifactType = LibUtils.shiftLeft( - uint8(artifact.artifactType), + uint8(_artifactType), uint8(ArtifactInfo.ArtifactType) ); - uint256 biome = LibUtils.shiftLeft(uint8(artifact.planetBiome), uint8(ArtifactInfo.Biome)); + uint256 biome = LibUtils.shiftLeft(uint8(_biome), uint8(ArtifactInfo.Biome)); return tokenType + rarity + artifactType + biome; } @@ -48,20 +55,40 @@ library LibArtifact { uint8 typeIdx = uint8(ArtifactInfo.ArtifactType) - 1; uint8 biomeIdx = uint8(ArtifactInfo.Biome) - 1; - uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); - uint8 rarity = uint8(LibUtils.calculateByteUInt(_b, rarityIdx, rarityIdx)); - uint8 artifactType = uint8(LibUtils.calculateByteUInt(_b, typeIdx, typeIdx)); - uint8 biome = uint8(LibUtils.calculateByteUInt(_b, biomeIdx, biomeIdx)); + TokenType tokenType = TokenType(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + ArtifactRarity rarity = ArtifactRarity( + LibUtils.calculateByteUInt(_b, rarityIdx, rarityIdx) + ); + ArtifactType artifactType = ArtifactType(LibUtils.calculateByteUInt(_b, typeIdx, typeIdx)); + Biome biome = Biome(LibUtils.calculateByteUInt(_b, biomeIdx, biomeIdx)); + + require(isArtifact(artifactId), "token type is not artifact"); + require(isValidArtifactRarity(rarity), "artifact rarity is not valid"); + require(isValidArtifactType(artifactType), "artifact type is not valid"); + require(isValidBiome(biome), "artifact biome is not valid"); + return Artifact({ id: artifactId, - tokenType: TokenType(tokenType), - rarity: ArtifactRarity(rarity), - artifactType: ArtifactType(artifactType), - planetBiome: Biome(biome) + tokenType: tokenType, + rarity: rarity, + artifactType: artifactType, + planetBiome: biome }); } + function isValidArtifactRarity(ArtifactRarity rarity) internal pure returns (bool) { + return (rarity >= ArtifactRarity.Common && rarity <= ArtifactRarity.Mythic); + } + + function isValidArtifactType(ArtifactType artifactType) internal pure returns (bool) { + return (artifactType >= ArtifactType.Monolith && artifactType <= ArtifactType.BlackDomain); + } + + function isValidBiome(Biome biome) internal pure returns (bool) { + return (biome >= Biome.Ocean && biome <= Biome.Corrupted); + } + function isArtifact(uint256 tokenId) internal pure returns (bool) { bytes memory _b = abi.encodePacked(tokenId); uint8 tokenIdx = uint8(ArtifactInfo.TokenType) - 1; @@ -69,17 +96,6 @@ library LibArtifact { return (TokenType(tokenType) == TokenType.Artifact); } - function _nullArtifactProperties() internal pure returns (Artifact memory) { - return - Artifact( - 0, - TokenType.Unknown, - ArtifactRarity.Unknown, - ArtifactType.Unknown, - Biome.Unknown - ); - } - function getUpgradeForArtifact(Artifact memory artifact) internal pure diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index a513a6f4..d0f8cbfd 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -65,16 +65,10 @@ library LibArtifactUtils { SpaceshipType shipType ) public returns (uint256) { require(shipType != SpaceshipType.Unknown, "incorrect ship type"); - // require(gs().miscNonce < MAX UINT 128) but won't happen. - uint128 id = uint128(gs().miscNonce++); - uint256 tokenId = LibSpaceship.encode( - Spaceship({id: 0, tokenType: TokenType.Spaceship, spaceshipType: shipType}) - ); - Spaceship memory spaceship = DFArtifactFacet(address(this)).createSpaceship( - tokenId + id, // Make each ship unique - owner - ); + uint256 tokenId = LibSpaceship.create(shipType) + uint128(gs().miscNonce++); + + Spaceship memory spaceship = DFArtifactFacet(address(this)).createSpaceship(tokenId, owner); LibSpaceship.putSpaceshipOnPlanet(planetId, spaceship.id); return spaceship.id; @@ -103,15 +97,7 @@ library LibArtifactUtils { ArtifactRarity rarity = LibArtifact.artifactRarityFromPlanetLevel( levelBonus + planet.planetLevel ); - uint256 tokenId = LibArtifact.encode( - Artifact({ - id: 0, - tokenType: TokenType.Artifact, - rarity: rarity, - artifactType: artifactType, - planetBiome: biome - }) - ); + uint256 tokenId = LibArtifact.create(rarity, artifactType, biome); Artifact memory foundArtifact = DFArtifactFacet(address(this)).createArtifact( tokenId, @@ -134,7 +120,6 @@ library LibArtifactUtils { uint256 wormholeTo ) public { Planet storage planet = gs().planets[locationId]; - Artifact memory artifact = LibArtifact.decode(artifactId); if (LibSpaceship.isShip(artifactId)) { require( @@ -143,6 +128,8 @@ library LibArtifactUtils { ); activateSpaceshipArtifact(locationId, artifactId, planet); } else if (LibArtifact.isArtifact(artifactId)) { + Artifact memory artifact = LibArtifact.decode(artifactId); + require( LibArtifact.isArtifactOnPlanet(locationId, artifactId), "can't activate an artifact on a planet it's not on" diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index a350f98f..3cf955c3 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; * Library for all things Spaceships */ - // Library imports import {LibUtils} from "./LibUtils.sol"; @@ -22,16 +21,17 @@ library LibSpaceship { /** * @notice Create the token ID for a Spaceship with the following properties: - * @param spaceship Spaceship + * @param spaceshipType SpaceshipType. */ - function encode(Spaceship memory spaceship) internal pure returns (uint256) { - // x << y is equivalent to the mathematical expression x * 2**y + function create(SpaceshipType spaceshipType) internal pure returns (uint256) { + require(isValidShipType(spaceshipType), "spaceship type is not valid"); + uint256 tokenType = LibUtils.shiftLeft( - uint8(spaceship.tokenType), + uint8(TokenType.Spaceship), uint8(SpaceshipInfo.TokenType) ); uint256 shipType = LibUtils.shiftLeft( - uint8(spaceship.spaceshipType), + uint8(spaceshipType), uint8(SpaceshipInfo.SpaceshipType) ); return tokenType + shipType; @@ -46,6 +46,9 @@ library LibSpaceship { uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); uint8 shipType = uint8(LibUtils.calculateByteUInt(_b, shipInfoIdx, shipInfoIdx)); + require(isShip(spaceshipId), "token type is not spaceship"); + require(isValidShipType(SpaceshipType(shipType)), "spaceship type is not valid"); + return Spaceship({ id: spaceshipId, @@ -58,7 +61,11 @@ library LibSpaceship { bytes memory _b = abi.encodePacked(tokenId); uint8 tokenIdx = uint8(SpaceshipInfo.TokenType) - 1; uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); - return (TokenType(tokenType) == TokenType.Spaceship); + return (tokenType == uint8(TokenType.Spaceship)); + } + + function isValidShipType(SpaceshipType shipType) internal pure returns (bool) { + return (shipType >= SpaceshipType.ShipMothership && shipType <= SpaceshipType.ShipTitan); } function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) internal view returns (bool) { diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index 7b618752..bd353312 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -86,14 +86,8 @@ describe('DarkForestArtifacts', function () { const rarity = ArtifactRarity.Legendary; const artifactType = ArtifactType.Colossus; const planetBiome = Biome.DESERT; - const res = await world.contract.encodeArtifact({ - id: 0, - tokenType, - rarity, - artifactType, - planetBiome, - }); - const a = await world.contract.decodeArtifact(res); + const res = await world.contract.createArtifactId(rarity, artifactType, planetBiome); + const a = await world.contract.getArtifactFromId(res); expect(tokenType).to.equal(Number(a.tokenType)); expect(rarity).to.equal(Number(a.rarity)); expect(artifactType).to.equal(Number(a.artifactType)); @@ -103,12 +97,8 @@ describe('DarkForestArtifacts', function () { // Must be valid options const tokenType = TokenType.Spaceship; const spaceshipType = 2; - const res = await world.contract.encodeSpaceship({ - id: 0, - tokenType, - spaceshipType, - }); - const a = await world.contract.decodeSpaceship(res); + const res = await world.contract.createSpaceshipId(spaceshipType); + const a = await world.contract.getSpaceshipFromId(res); expect(tokenType).to.equal(Number(a.tokenType)); expect(spaceshipType).to.equal(Number(a.spaceshipType)); }); @@ -126,7 +116,7 @@ describe('DarkForestArtifacts', function () { const statSumAfterFound = getStatSum(await world.contract.planets(ARTIFACT_PLANET_1.id)); await world.user1Core.activateArtifact(ARTIFACT_PLANET_1.id, artifactId, 0); - prettyPrintToken(await world.user1Core.decodeArtifact(artifactId)); + prettyPrintToken(await world.user1Core.getArtifactFromId(artifactId)); // artifact and gear should be on planet. Gear is 0 and Artifact is 1. const artifactsOnPlanet = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); @@ -575,8 +565,8 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.Monolith, - TokenType.Artifact, - { rarity: ArtifactRarity.Legendary, biome: Biome.OCEAN } + ArtifactRarity.Legendary, + Biome.OCEAN ); // deposit fails on low level trading post, succeeds on high level trading post @@ -625,10 +615,10 @@ describe('DarkForestArtifacts', function () { world.user1.address, from, ArtifactType.Wormhole, - TokenType.Artifact, - { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } + artifactRarities[i] as ArtifactRarity, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(artifactId)); + prettyPrintToken(await world.contract.getArtifactFromId(artifactId)); activateAndConfirm(world.user1Core, from.id, artifactId, to.id); @@ -695,8 +685,8 @@ describe('DarkForestArtifacts', function () { world.user1.address, from, ArtifactType.Wormhole, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); // Move gear bc too many artifacts on SPAWN_PLANET_1, so can't receive wormhole. @@ -786,10 +776,10 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BloomFilter, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); @@ -833,11 +823,11 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BloomFilter, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); await increaseBlockchainTime(); // so that trading post can fill up to max energy await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); await world.user1Core.move( @@ -868,8 +858,8 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BlackDomain, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); await world.user1Core.depositArtifact(LVL3_SPACETIME_1.id, newTokenId); @@ -918,8 +908,8 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.BlackDomain, - TokenType.Artifact, - { rarity: ArtifactRarity.Common, biome: Biome.OCEAN } + ArtifactRarity.Common, + Biome.OCEAN ); await increaseBlockchainTime(); // so that trading post can fill up to max energy @@ -942,10 +932,10 @@ describe('DarkForestArtifacts', function () { world.user1.address, LVL3_SPACETIME_1, ArtifactType.PlanetaryShield, - TokenType.Artifact, - { rarity: ArtifactRarity.Rare as ArtifactRarity, biome: Biome.OCEAN } + ArtifactRarity.Rare as ArtifactRarity, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); const planetBeforeActivation = await world.user1Core.planets(LVL3_SPACETIME_1.id); await activateAndConfirm(world.user1Core, LVL3_SPACETIME_1.id, newTokenId); @@ -959,11 +949,7 @@ describe('DarkForestArtifacts', function () { // Burned on deactivate await world.user1Core.deactivateArtifact(LVL3_SPACETIME_1.id); - expect((await getArtifactsOnPlanet(world, LVL3_SPACETIME_1.id)).length).to.equal(0); - expect((await world.user1Core.getActiveArtifactOnPlanet(LVL3_SPACETIME_1.id)).id).to.equal(0); - expect(await world.user1Core.getArtifactActivationTimeOnPlanet(LVL3_SPACETIME_1.id)).to.equal( - 0 - ); + testDeactivate(world, LVL3_SPACETIME_3.id); }); }); @@ -987,10 +973,10 @@ describe('DarkForestArtifacts', function () { world.user1.address, ZERO_PLANET, ArtifactType.PhotoidCannon, - TokenType.Artifact, - { rarity: artifactRarities[i] as ArtifactRarity, biome: Biome.OCEAN } + artifactRarities[i] as ArtifactRarity, + Biome.OCEAN ); - prettyPrintToken(await world.contract.decodeArtifact(newTokenId)); + prettyPrintToken(await world.contract.getArtifactFromId(newTokenId)); await world.user1Core.depositArtifact(LVL6_SPACETIME.id, newTokenId); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index a288174b..d37932a6 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -16,7 +16,6 @@ import { Biome, BiomeNames, SpaceshipType, - TokenType, TokenTypeNames, } from '@dfdao/types'; import { bigIntFromKey } from '@dfdao/whitelist'; @@ -310,8 +309,9 @@ export async function user1MintArtifactPlanet(user1Core: DarkForest) { await increaseBlockchainTime(); const findArtifactTx = await user1Core.findArtifact(...makeFindArtifactArgs(ARTIFACT_PLANET_1)); const findArtifactReceipt = await findArtifactTx.wait(); - // 0th event is erc721 transfer (i think); 1st event is UpdateArtifact, 2nd argument of this event is artifactId - const artifactId = findArtifactReceipt.events?.[1].args?.[1]; + // 0th event is erc721 transfer (i think); 1st event is UpdateArtifact, 2nd argument of this event + // is artifactId + const artifactId = findArtifactReceipt.events?.[1].args?.artifactId; return artifactId as BigNumber; } @@ -334,19 +334,13 @@ export async function createArtifact( owner: string, planet: TestLocation, artifactType: ArtifactType, - tokenType = TokenType.Artifact, - { rarity, biome }: { rarity?: ArtifactRarity; biome?: Biome } = {} + rarity?: ArtifactRarity, + biome?: Biome ) { rarity ||= ArtifactRarity.Common; biome ||= Biome.FOREST; - const tokenId = await contract.encodeArtifact({ - id: 0, - tokenType, - rarity, - artifactType, - planetBiome: biome, - }); + const tokenId = await contract.createArtifactId(rarity, artifactType, biome); await contract.adminGiveArtifact({ tokenId, discoverer: owner, @@ -363,7 +357,7 @@ export async function createArtifact( export async function testDeactivate(world: World, locationId: BigNumberish) { expect((await getArtifactsOnPlanet(world, locationId)).length).to.equal(0); - expect((await world.contract.getActiveArtifactOnPlanet(locationId)).id).to.equal(0); + expect(await world.contract.hasActiveArtifact(locationId)).to.equal(false); expect(await world.contract.getArtifactActivationTimeOnPlanet(locationId)).to.equal(0); } From fefef35161a75b210f5c6b0287842cf1478b6a40 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Mon, 3 Oct 2022 15:31:11 +0100 Subject: [PATCH 46/55] feat: simply business logic bc of asserts in token create --- eth/contracts/facets/DFMoveFacet.sol | 19 +++++++++++-------- eth/contracts/libraries/LibArtifactUtils.sol | 5 +---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index af8ff2f6..c27fa294 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -120,7 +120,9 @@ contract DFMoveFacet is WithStorage { arrivalType = ArrivalType.Wormhole; } - if (!_isSpaceshipMove(args)) { + if (_isSpaceshipMove(args)) { + _removeSpaceshipEffectsFromOriginPlanet(args.oldLoc, args.movedArtifactId); + } else if (_isArtifactMove(args)) { (bool photoidPresent, Upgrade memory newTempUpgrade) = _checkPhotoid(args); if (photoidPresent) { temporaryUpgrade = newTempUpgrade; @@ -128,8 +130,6 @@ contract DFMoveFacet is WithStorage { } } - _removeSpaceshipEffectsFromOriginPlanet(args); - uint256 popMoved = args.popMoved; uint256 silverMoved = args.silverMoved; uint256 remainingOriginPlanetPopulation = gs().planets[args.oldLoc].population - popMoved; @@ -271,11 +271,10 @@ contract DFMoveFacet is WithStorage { /** Undo the spaceship effects that were applied when the ship arrived on the planet. */ - function _removeSpaceshipEffectsFromOriginPlanet(DFPMoveArgs memory args) private { - if (!LibSpaceship.isShip(args.movedArtifactId)) return; - Spaceship memory spaceship = LibSpaceship.decode(args.movedArtifactId); - Planet memory planet = applySpaceshipDepart(spaceship, gs().planets[args.oldLoc]); - gs().planets[args.oldLoc] = planet; + function _removeSpaceshipEffectsFromOriginPlanet(uint256 originLoc, uint256 shipId) private { + Spaceship memory spaceship = LibSpaceship.decode(shipId); + Planet memory planet = applySpaceshipDepart(spaceship, gs().planets[originLoc]); + gs().planets[originLoc] = planet; } /** @@ -425,6 +424,10 @@ contract DFMoveFacet is WithStorage { return LibSpaceship.isShip(args.movedArtifactId); } + function _isArtifactMove(DFPMoveArgs memory args) private pure returns (bool) { + return LibArtifact.isArtifact(args.movedArtifactId); + } + function _createArrival(DFPCreateArrivalArgs memory args) private { // enter the arrival data for event id Planet memory planet = gs().planets[args.oldLoc]; diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index d0f8cbfd..c41dc3cb 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -64,8 +64,6 @@ library LibArtifactUtils { address owner, SpaceshipType shipType ) public returns (uint256) { - require(shipType != SpaceshipType.Unknown, "incorrect ship type"); - uint256 tokenId = LibSpaceship.create(shipType) + uint128(gs().miscNonce++); Spaceship memory spaceship = DFArtifactFacet(address(this)).createSpaceship(tokenId, owner); @@ -298,7 +296,6 @@ library LibArtifactUtils { planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to deposit this artifact" ); - require(!LibSpaceship.isShip(artifactId), "cannot deposit spaceships"); require( gs().planetArtifacts[locationId].length + gs().planetSpaceships[locationId].length < 5, @@ -325,7 +322,7 @@ library LibArtifactUtils { planet.planetLevel > uint256(artifact.rarity), "spacetime rip not high enough level to withdraw this artifact" ); - require(!LibSpaceship.isShip(artifactId), "cannot withdraw spaceships"); + LibArtifact.takeArtifactOffPlanet(locationId, artifactId); // artifactId, curr owner, new owner From ec15fd59bda646db646f52d740bc50f08b504110 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Mon, 3 Oct 2022 13:31:06 -0700 Subject: [PATCH 47/55] Store artifact information on contract planets (#28) * Store artifact information on each planet * Remove artifact task, as it does not make sense anymore * Comment out debug tasks until we have token APIs settled * Add ID logging to the prettyPrintToken util * fix: add extra timeout Co-authored-by: cha0sg0d <0xcha0sg0d@gmail.com> --- eth/contracts/DFTypes.sol | 6 + eth/contracts/facets/DFGetterFacet.sol | 24 +- eth/contracts/facets/DFMoveFacet.sol | 10 +- eth/contracts/libraries/LibArtifact.sol | 24 +- eth/contracts/libraries/LibArtifactUtils.sol | 19 +- eth/contracts/libraries/LibPlanet.sol | 6 + eth/contracts/libraries/LibSpaceship.sol | 14 +- eth/contracts/libraries/LibStorage.sol | 8 - eth/hardhat.config.ts | 1 - eth/tasks/artifact.ts | 18 -- eth/tasks/debug.ts | 218 +++++++++---------- eth/test/DFArtifacts.test.ts | 2 + eth/test/utils/TestUtils.ts | 2 +- 13 files changed, 170 insertions(+), 182 deletions(-) delete mode 100644 eth/tasks/artifact.ts diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index b0f29eba..d360e984 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -71,6 +71,12 @@ struct Planet { uint256 invadeStartBlock; address capturer; uint256 locationId; + // Token stuff + uint256[] artifacts; + uint256[] spaceships; + uint256 activeArtifact; + uint256 wormholeTo; + uint256 artifactActivationTime; } struct RevealedCoords { diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 9ac4419a..6c545777 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -91,12 +91,12 @@ contract DFGetterFacet is WithStorage { return gs().planetArrivals[key]; } - function planetArtifacts(uint256 key) public view returns (uint256[] memory) { - return gs().planetArtifacts[key]; + function planetArtifacts(uint256 locationId) public view returns (uint256[] memory) { + return gs().planets[locationId].artifacts; } - function planetSpaceships(uint256 key) public view returns (uint256[] memory) { - return gs().planetSpaceships[key]; + function planetSpaceships(uint256 locationId) public view returns (uint256[] memory) { + return gs().planets[locationId].spaceships; } // ADDITIONAL UTILITY GETTERS @@ -315,7 +315,7 @@ contract DFGetterFacet is WithStorage { } function getArtifactActivationTimeOnPlanet(uint256 locationId) public view returns (uint256) { - return gs().planetArtifactActivationTime[locationId]; + return gs().planets[locationId].artifactActivationTime; } // function getArtifactById(uint256 artifactId) @@ -345,8 +345,12 @@ contract DFGetterFacet is WithStorage { // }); // } + function getUpgradeForArtifact(uint256 artifactId) public pure returns (Upgrade memory) { + return LibArtifact.getUpgradeForArtifact(LibArtifact.decode(artifactId)); + } + function getArtifactsOnPlanet(uint256 locationId) public view returns (Artifact[] memory ret) { - uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + uint256[] memory artifactIds = gs().planets[locationId].artifacts; ret = new Artifact[](artifactIds.length); for (uint256 i = 0; i < artifactIds.length; i++) { ret[i] = LibArtifact.decode(artifactIds[i]); @@ -359,7 +363,7 @@ contract DFGetterFacet is WithStorage { view returns (Spaceship[] memory ret) { - uint256[] memory tokenIds = gs().planetSpaceships[locationId]; + uint256[] memory tokenIds = gs().planets[locationId].spaceships; ret = new Spaceship[](tokenIds.length); for (uint256 i = 0; i < tokenIds.length; i++) { ret[i] = LibSpaceship.decode(tokenIds[i]); @@ -370,11 +374,11 @@ contract DFGetterFacet is WithStorage { // Combo on Ships and Artifacts function tokenExistsOnPlanet(uint256 locationId, uint256 tokenId) public view returns (bool) { bool hasToken = false; - uint256[] memory artifactIds = gs().planetArtifacts[locationId]; + uint256[] memory artifactIds = gs().planets[locationId].artifacts; for (uint256 i = 0; i < artifactIds.length; i++) { if (artifactIds[i] == tokenId) return true; } - uint256[] memory shipIds = gs().planetSpaceships[locationId]; + uint256[] memory shipIds = gs().planets[locationId].spaceships; for (uint256 i = 0; i < shipIds.length; i++) { if (shipIds[i] == tokenId) return true; } @@ -390,7 +394,7 @@ contract DFGetterFacet is WithStorage { view returns (Artifact memory ret) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; + uint256 artifactId = gs().planets[locationId].activeArtifact; return LibArtifact.decode(artifactId); } diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index c27fa294..63b8b373 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -227,8 +227,8 @@ contract DFMoveFacet is WithStorage { if (args.movedArtifactId != 0) { require( - gs().planetArtifacts[args.newLoc].length + - gs().planetSpaceships[args.newLoc].length < + gs().planets[args.newLoc].artifacts.length + + gs().planets[args.newLoc].spaceships.length < 5, "too many tokens on this planet" ); @@ -296,7 +296,7 @@ contract DFMoveFacet is WithStorage { // If active artifact is a Wormhole and destination is newLoc if ( activeArtifactFrom.artifactType == ArtifactType.Wormhole && - gs().planetWormholes[args.oldLoc] == args.newLoc + gs().planets[args.oldLoc].wormholeTo == args.newLoc ) { wormholeRarity = activeArtifactFrom.rarity; wormholePresent = true; @@ -308,7 +308,7 @@ contract DFMoveFacet is WithStorage { // If active artifact is a Wormhole and destination is fromLoc if ( activeArtifactTo.artifactType == ArtifactType.Wormhole && - gs().planetWormholes[args.newLoc] == args.oldLoc + gs().planets[args.newLoc].wormholeTo == args.oldLoc ) { // Ensures higher rarity wormhole will be used. // TODO: Make sure client knows this. @@ -338,7 +338,7 @@ contract DFMoveFacet is WithStorage { Artifact memory activeArtifactFrom = LibArtifact.getActiveArtifact(args.oldLoc); if ( activeArtifactFrom.artifactType == ArtifactType.PhotoidCannon && - block.timestamp - gs().planetArtifactActivationTime[args.oldLoc] >= + block.timestamp - gs().planets[args.oldLoc].artifactActivationTime >= gameConstants().PHOTOID_ACTIVATION_DELAY ) { photoidPresent = true; diff --git a/eth/contracts/libraries/LibArtifact.sol b/eth/contracts/libraries/LibArtifact.sol index 60b392c8..c5c08f3c 100644 --- a/eth/contracts/libraries/LibArtifact.sol +++ b/eth/contracts/libraries/LibArtifact.sol @@ -218,7 +218,7 @@ library LibArtifact { // whether or not the artifact is activated. function isActivated(uint256 locationId, uint256 artifactId) internal view returns (bool) { - return (gs().planetActiveArtifact[locationId] == artifactId); + return (gs().planets[locationId].activeArtifact == artifactId); } function isArtifactOnPlanet(uint256 locationId, uint256 artifactId) @@ -226,8 +226,8 @@ library LibArtifact { view returns (bool) { - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { + for (uint256 i; i < gs().planets[locationId].artifacts.length; i++) { + if (gs().planets[locationId].artifacts[i] == artifactId) { return true; } } @@ -290,25 +290,25 @@ library LibArtifact { } function putArtifactOnPlanet(uint256 locationId, uint256 artifactId) internal { - gs().planetArtifacts[locationId].push(artifactId); + gs().planets[locationId].artifacts.push(artifactId); } /** * Remove artifactId from planet with locationId if artifactId exists AND is not active. */ function takeArtifactOffPlanet(uint256 locationId, uint256 artifactId) internal { - uint256 artifactsOnThisPlanet = gs().planetArtifacts[locationId].length; + uint256 artifactsOnThisPlanet = gs().planets[locationId].artifacts.length; bool hadTheArtifact = false; for (uint256 i = 0; i < artifactsOnThisPlanet; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { + if (gs().planets[locationId].artifacts[i] == artifactId) { require( !isActivated(locationId, artifactId), "you cannot take an activated artifact off a planet" ); - gs().planetArtifacts[locationId][i] = gs().planetArtifacts[locationId][ + gs().planets[locationId].artifacts[i] = gs().planets[locationId].artifacts[ artifactsOnThisPlanet - 1 ]; @@ -318,19 +318,19 @@ library LibArtifact { } require(hadTheArtifact, "this artifact was not present on this planet"); - gs().planetArtifacts[locationId].pop(); + gs().planets[locationId].artifacts.pop(); } // if the given planet has an activated artifact on it, then return the artifact // otherwise, return a 'null artifact' function getActiveArtifact(uint256 locationId) internal view returns (Artifact memory) { require(hasActiveArtifact(locationId), "planet does not have an active artifact"); - uint256 artifactId = gs().planetActiveArtifact[locationId]; + uint256 artifactId = gs().planets[locationId].activeArtifact; return LibArtifact.decode(artifactId); } function hasActiveArtifact(uint256 locationId) internal view returns (bool) { - uint256 artifactId = gs().planetActiveArtifact[locationId]; + uint256 artifactId = gs().planets[locationId].activeArtifact; return artifactId != 0; } @@ -342,8 +342,8 @@ library LibArtifact { returns (Artifact memory a) { bool found = false; - for (uint256 i; i < gs().planetArtifacts[locationId].length; i++) { - if (gs().planetArtifacts[locationId][i] == artifactId) { + for (uint256 i; i < gs().planets[locationId].artifacts.length; i++) { + if (gs().planets[locationId].artifacts[i] == artifactId) { a = LibArtifact.decode(artifactId); found = true; return a; diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index c41dc3cb..f200bc75 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -200,8 +200,8 @@ library LibArtifactUtils { bool shouldDeactivateAndBurn = false; - gs().planetArtifactActivationTime[locationId] = block.timestamp; - gs().planetActiveArtifact[locationId] = artifactId; + gs().planets[locationId].artifactActivationTime = block.timestamp; + gs().planets[locationId].activeArtifact = artifactId; emit ArtifactActivated(msg.sender, artifactId, locationId); if (artifact.artifactType == ArtifactType.Wormhole) { @@ -212,7 +212,7 @@ library LibArtifactUtils { "you can only create a wormhole to a planet you own" ); require(!gs().planets[wormholeTo].destroyed, "planet destroyed"); - gs().planetWormholes[locationId] = wormholeTo; + gs().planets[locationId].wormholeTo = wormholeTo; } else if (artifact.artifactType == ArtifactType.BloomFilter) { require( 2 * uint256(artifact.rarity) >= planet.planetLevel, @@ -231,7 +231,7 @@ library LibArtifactUtils { } if (shouldDeactivateAndBurn) { - gs().planetActiveArtifact[locationId] = 0; // immediately remove activate artifact + gs().planets[locationId].activeArtifact = 0; // immediately remove activate artifact emit ArtifactDeactivated(msg.sender, artifactId, locationId); // burn it after use. will be owned by contract but not on a planet anyone can control @@ -260,9 +260,9 @@ library LibArtifactUtils { Artifact memory artifact = LibArtifact.getActiveArtifact(locationId); // In case just pretend there is a wormhole. - gs().planetWormholes[locationId] = 0; - gs().planetActiveArtifact[locationId] = 0; - gs().planetArtifactActivationTime[locationId] = 0; + gs().planets[locationId].wormholeTo = 0; + gs().planets[locationId].activeArtifact = 0; + gs().planets[locationId].artifactActivationTime = 0; emit ArtifactDeactivated(msg.sender, artifact.id, locationId); @@ -298,7 +298,8 @@ library LibArtifactUtils { ); require( - gs().planetArtifacts[locationId].length + gs().planetSpaceships[locationId].length < 5, + gs().planets[locationId].artifacts.length + gs().planets[locationId].spaceships.length < + 5, "too many tokens on this planet" ); @@ -345,7 +346,7 @@ library LibArtifactUtils { } function containsGear(uint256 locationId) public view returns (bool) { - uint256[] memory tokenIds = gs().planetSpaceships[locationId]; + uint256[] memory tokenIds = gs().planets[locationId].spaceships; for (uint256 i = 0; i < tokenIds.length; i++) { Spaceship memory spaceship = LibSpaceship.decode(tokenIds[i]); if ( diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index e3bed06d..ab34b3c0 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -161,6 +161,12 @@ library LibPlanet { _planet.planetLevel = defaultPlanet.planetLevel; _planet.planetType = defaultPlanet.planetType; + _planet.artifacts = defaultPlanet.artifacts; + _planet.spaceships = defaultPlanet.spaceships; + _planet.activeArtifact = defaultPlanet.activeArtifact; + _planet.wormholeTo = defaultPlanet.wormholeTo; + _planet.artifactActivationTime = defaultPlanet.artifactActivationTime; + _planet.isInitialized = true; _planet.perlin = args.perlin; _planet.spaceType = args.spaceType; diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index 3cf955c3..0cff4526 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -69,8 +69,8 @@ library LibSpaceship { } function isSpaceshipOnPlanet(uint256 locationId, uint256 shipId) internal view returns (bool) { - for (uint256 i; i < gs().planetSpaceships[locationId].length; i++) { - if (gs().planetSpaceships[locationId][i] == shipId) { + for (uint256 i; i < gs().planets[locationId].spaceships.length; i++) { + if (gs().planets[locationId].spaceships[i] == shipId) { return true; } } @@ -78,17 +78,17 @@ library LibSpaceship { } function putSpaceshipOnPlanet(uint256 locationId, uint256 spaceshipId) internal { - gs().planetSpaceships[locationId].push(spaceshipId); + gs().planets[locationId].spaceships.push(spaceshipId); } function takeSpaceshipOffPlanet(uint256 locationId, uint256 spaceshipId) internal { - uint256 shipsOnThisPlanet = gs().planetSpaceships[locationId].length; + uint256 shipsOnThisPlanet = gs().planets[locationId].spaceships.length; bool hadTheShip = false; for (uint256 i = 0; i < shipsOnThisPlanet; i++) { - if (gs().planetSpaceships[locationId][i] == spaceshipId) { - gs().planetSpaceships[locationId][i] = gs().planetSpaceships[locationId][ + if (gs().planets[locationId].spaceships[i] == spaceshipId) { + gs().planets[locationId].spaceships[i] = gs().planets[locationId].spaceships[ shipsOnThisPlanet - 1 ]; @@ -98,6 +98,6 @@ library LibSpaceship { } require(hadTheShip, "this ship was not present on this planet"); - gs().planetSpaceships[locationId].pop(); + gs().planets[locationId].spaceships.pop(); } } diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 279f10bd..1c521033 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -43,14 +43,6 @@ struct GameStorage { mapping(uint256 => PlanetEventMetadata[]) planetEvents; // maps event id to arrival data mapping(uint256 => ArrivalData) planetArrivals; - // Token stuff - mapping(uint256 => uint256[]) planetArtifacts; - mapping(uint256 => uint256[]) planetSpaceships; - mapping(uint256 => uint256) planetActiveArtifact; - // wormhole from => to. planetWormHoles[from] = to; - mapping(uint256 => uint256) planetWormholes; - // planetId to timestamp. For all artifacts, but only used for photoids. - mapping(uint256 => uint256) planetArtifactActivationTime; // Capture Zones uint256 nextChangeBlock; } diff --git a/eth/hardhat.config.ts b/eth/hardhat.config.ts index fc7dee6e..fa307e48 100644 --- a/eth/hardhat.config.ts +++ b/eth/hardhat.config.ts @@ -27,7 +27,6 @@ import { Initializers, } from '@dfdao/settings'; import { workspace } from '@projectsophon/workspace'; -import './tasks/artifact'; import './tasks/circom'; import './tasks/debug'; import './tasks/deploy'; diff --git a/eth/tasks/artifact.ts b/eth/tasks/artifact.ts deleted file mode 100644 index fd74c6db..00000000 --- a/eth/tasks/artifact.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { task } from 'hardhat/config'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; - -task('artifact:read', 'Read Artifact data from Tokens contract').setAction(artifactsRead); - -async function artifactsRead({}, hre: HardhatRuntimeEnvironment) { - const contract = await hre.ethers.getContractAt( - 'DarkForest', - hre.settings.contracts.CONTRACT_ADDRESS - ); - - const id = await contract.tokenByIndex(0); - console.log(id.toString()); - const token = await contract.getArtifact(id); - console.log(token); - const URI = await contract.tokenURI(id); - console.log(URI); -} diff --git a/eth/tasks/debug.ts b/eth/tasks/debug.ts index 66ac78b1..1e1eed53 100644 --- a/eth/tasks/debug.ts +++ b/eth/tasks/debug.ts @@ -1,122 +1,118 @@ // these tasks are intended to simplify the debugging and development workflow by essentially giving -// you god-mode status, allowing you to change the state of the world however you want. - -import { task, types } from 'hardhat/config'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; // see DFTypes.sol - ArtifactType -const artifactOptions = ( - 'Monolith,Colossus,Spaceship,Pyramid,Wormhole,' + - 'PlanetaryShield,PhotoidCannon,BloomFilter,BlackDomain' -).split(','); +// const artifactOptions = ( +// 'Monolith,Colossus,Spaceship,Pyramid,Wormhole,' + +// 'PlanetaryShield,PhotoidCannon,BloomFilter,BlackDomain' +// ).split(','); -// npm run --workspace eth hardhat:dev -- debug:giveArtifact "0x27fd6eec1e1f3ce4a53b40d5813119d868f7b4e3" PhotoidCannon 5 -task('debug:giveArtifact', 'gives the player some amount of a particular type of artifact') - .addPositionalParam( - 'playerAddress', - 'the address of the player to give the artifacts', - undefined, - types.string - ) - .addPositionalParam( - 'artifactType', - 'one of: [Monolith, Colossus, Spaceship, Pyramid, Wormhole, ' + - 'PlanetaryShield, PhotoidCannon, BloomFilter, BlackDomain]', - undefined, - types.string - ) - .addPositionalParam('amount', 'the amount of this artifact to give', 1, types.int) - .addPositionalParam('rarity', 'the rarity of the artifact to give', 1, types.int) - .addPositionalParam('biome', 'the biome of the artifact to give', 1, types.int) - .addPositionalParam( - 'discoveredOn', - 'the planet ID (decimal string) this was discovered on', - '0', - types.string - ) - .setAction(giveArtifact); +// // npm run --workspace eth hardhat:dev -- debug:giveArtifact "0x27fd6eec1e1f3ce4a53b40d5813119d868f7b4e3" PhotoidCannon 5 +// task('debug:giveArtifact', 'gives the player some amount of a particular type of artifact') +// .addPositionalParam( +// 'playerAddress', +// 'the address of the player to give the artifacts', +// undefined, +// types.string +// ) +// .addPositionalParam( +// 'artifactType', +// 'one of: [Monolith, Colossus, Spaceship, Pyramid, Wormhole, ' + +// 'PlanetaryShield, PhotoidCannon, BloomFilter, BlackDomain]', +// undefined, +// types.string +// ) +// .addPositionalParam('amount', 'the amount of this artifact to give', 1, types.int) +// .addPositionalParam('rarity', 'the rarity of the artifact to give', 1, types.int) +// .addPositionalParam('biome', 'the biome of the artifact to give', 1, types.int) +// .addPositionalParam( +// 'discoveredOn', +// 'the planet ID (decimal string) this was discovered on', +// '0', +// types.string +// ) +// .setAction(giveArtifact); -async function giveArtifact( - { - playerAddress, - artifactType, - amount, - rarity, - biome, - discoveredOn, - }: { - playerAddress: string; - artifactType: string; - amount: number; - rarity: number; - biome: number; - discoveredOn: string; - }, - hre: HardhatRuntimeEnvironment -) { - const chosenArtifactType = artifactOptions.indexOf(artifactType) + 1; - const contract = await hre.ethers.getContractAt( - 'DarkForest', - hre.settings.contracts.CONTRACT_ADDRESS - ); +// async function giveArtifact( +// { +// playerAddress, +// artifactType, +// amount, +// rarity, +// biome, +// discoveredOn, +// }: { +// playerAddress: string; +// artifactType: string; +// amount: number; +// rarity: number; +// biome: number; +// discoveredOn: string; +// }, +// hre: HardhatRuntimeEnvironment +// ) { +// const chosenArtifactType = artifactOptions.indexOf(artifactType) + 1; +// const contract = await hre.ethers.getContractAt( +// 'DarkForest', +// hre.settings.contracts.CONTRACT_ADDRESS +// ); - for (let i = 0; i < amount; i++) { - // see contracts/types/ActionTypes.sol - CreateArtifactArgs - const createArtifactArgs = { - tokenId: random256Id(), - discoverer: playerAddress, - planetId: discoveredOn, - rarity: rarity, - biome: biome, - artifactType: chosenArtifactType, - owner: playerAddress, - controller: '0x0000000000000000000000000000000000000000', - }; +// for (let i = 0; i < amount; i++) { +// // see contracts/types/ActionTypes.sol - CreateArtifactArgs +// const createArtifactArgs = { +// tokenId: random256Id(), +// discoverer: playerAddress, +// planetId: discoveredOn, +// rarity: rarity, +// biome: biome, +// artifactType: chosenArtifactType, +// owner: playerAddress, +// controller: '0x0000000000000000000000000000000000000000', +// }; - await (await contract.createArtifact(createArtifactArgs)).wait(); - } -} +// await (await contract.createArtifact(createArtifactArgs)).wait(); +// } +// } // npm run --workspace eth hardhat:dev -- debug:giveOneOfEachArtifact "0x5bcf0ac4c057dcaf9b23e4dd7cb7b035a71dd0dc" 10 -task( - 'debug:giveOneOfEachArtifact', - 'gives the player one of each type of artifact, one of each rarity' -) - .addPositionalParam( - 'playerAddress', - 'the address of the player to give the artifacts', - undefined, - types.string - ) - .addPositionalParam('biome', 'the biome of the artifacts to give', 1, types.int) - .setAction(giveOneOfEachArtifact); +// task( +// 'debug:giveOneOfEachArtifact', +// 'gives the player one of each type of artifact, one of each rarity' +// ) +// .addPositionalParam( +// 'playerAddress', +// 'the address of the player to give the artifacts', +// undefined, +// types.string +// ) +// .addPositionalParam('biome', 'the biome of the artifacts to give', 1, types.int) +// .setAction(giveOneOfEachArtifact); -async function giveOneOfEachArtifact( - { playerAddress, biome }: { playerAddress: string; biome: number }, - hre: HardhatRuntimeEnvironment -) { - for (const artifact of artifactOptions) { - for (let i = 1; i < 6; i++) { - await giveArtifact( - { - playerAddress, - artifactType: artifact, - amount: 1, - rarity: i, - biome, - discoveredOn: '0', - }, - hre - ); - } - } -} +// async function giveOneOfEachArtifact( +// { playerAddress, biome }: { playerAddress: string; biome: number }, +// hre: HardhatRuntimeEnvironment +// ) { +// for (const artifact of artifactOptions) { +// for (let i = 1; i < 6; i++) { +// await giveArtifact( +// { +// playerAddress, +// artifactType: artifact, +// amount: 1, +// rarity: i, +// biome, +// discoveredOn: '0', +// }, +// hre +// ); +// } +// } +// } -function random256Id() { - const alphabet = '0123456789ABCDEF'.split(''); - let result = '0x'; - for (let i = 0; i < 256 / 4; i++) { - result += alphabet[Math.floor(Math.random() * alphabet.length)]; - } - return result; -} +// function random256Id() { +// const alphabet = '0123456789ABCDEF'.split(''); +// let result = '0x'; +// for (let i = 0; i < 256 / 4; i++) { +// result += alphabet[Math.floor(Math.random() * alphabet.length)]; +// } +// return result; +// } diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index bd353312..295346fb 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -214,6 +214,8 @@ describe('DarkForestArtifacts', function () { ); const moveReceipt = await moveTx.wait(); const voyageId = moveReceipt.events?.[0].args?.[1]; // emitted by ArrivalQueued + await increaseBlockchainTime(); + await world.contract.refreshPlanet(ARTIFACT_PLANET_1.id); const oldLocArtifacts = await getArtifactsOnPlanet(world, ARTIFACT_PLANET_1.id); expect(oldLocArtifacts.length).to.equal(0); diff --git a/eth/test/utils/TestUtils.ts b/eth/test/utils/TestUtils.ts index d37932a6..f9bd0c6b 100644 --- a/eth/test/utils/TestUtils.ts +++ b/eth/test/utils/TestUtils.ts @@ -47,7 +47,7 @@ export function hexToBigNumber(hex: string): BigNumber { export function prettyPrintToken(token: ArtifactStructOutput) { console.log( - `~Token~\nCollection: ${TokenTypeNames[token.tokenType]}\nRarity: ${ + `~Token~\nID: ${token.id}\nCollection: ${TokenTypeNames[token.tokenType]}\nRarity: ${ ArtifactRarityNames[token.rarity] }\nType: ${ArtifactTypeNames[token.artifactType]}\nBiome: ${BiomeNames[token.planetBiome]}` ); From 4692d400c74b341b72eef199ca44d8afc511e036 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Tue, 4 Oct 2022 12:18:44 -0700 Subject: [PATCH 48/55] More 1155 changes (#33) * contracts: Separate carried spaceship from carried artifact * contracts: Actually call the beforeTokenTransfer super call --- eth/contracts/DFTypes.sol | 1 + eth/contracts/facets/DFArtifactFacet.sol | 5 ++++- eth/contracts/facets/DFMoveFacet.sol | 3 ++- eth/contracts/libraries/LibLazyUpdate.sol | 15 ++++++++++++--- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index d360e984..e5d65d46 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -177,6 +177,7 @@ struct ArrivalData { uint256 arrivalTime; ArrivalType arrivalType; uint256 carriedArtifactId; + uint256 carriedSpaceshipId; uint256 distance; } diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 2e792b56..789fe815 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -289,7 +289,7 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { uint256[] memory ids, uint256[] memory amounts, bytes memory data - ) internal virtual override { + ) internal override { uint256 length = ids.length; for (uint256 i = 0; i < length; i++) { // Only core contract can transfer Spaceships @@ -297,6 +297,9 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); } } + + // TODO: Are we supposed to call this before or after + super._beforeTokenTransfer(operator, from, to, ids, amounts, data); } /** diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 63b8b373..b8a42c78 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -454,7 +454,8 @@ contract DFMoveFacet is WithStorage { departureTime: block.timestamp, arrivalTime: block.timestamp + args.travelTime, arrivalType: args.arrivalType, - carriedArtifactId: args.movedArtifactId, + carriedArtifactId: isSpaceship ? 0 : args.movedArtifactId, + carriedSpaceshipId: isSpaceship ? args.movedArtifactId : 0, distance: args.actualDist }); // Photoids are burned in _checkPhotoid, so don't remove twice diff --git a/eth/contracts/libraries/LibLazyUpdate.sol b/eth/contracts/libraries/LibLazyUpdate.sol index fa3daaaf..9ffa8809 100644 --- a/eth/contracts/libraries/LibLazyUpdate.sol +++ b/eth/contracts/libraries/LibLazyUpdate.sol @@ -116,7 +116,11 @@ library LibLazyUpdate { function applyArrival(Planet memory planet, ArrivalData memory arrival) private pure - returns (uint256 newArtifactOnPlanet, Planet memory) + returns ( + uint256 newArtifactOnPlanet, + uint256 newSpaceshipOnPlanet, + Planet memory + ) { // checks whether the planet is owned by the player sending ships if (arrival.player == planet.owner) { @@ -165,7 +169,7 @@ library LibLazyUpdate { uint256 _nextSilver = planet.silver + arrival.silverMoved; planet.silver = _maxSilver < _nextSilver ? _maxSilver : _nextSilver; - return (arrival.carriedArtifactId, planet); + return (arrival.carriedArtifactId, arrival.carriedSpaceshipId, planet); } function applyPendingEvents( @@ -227,14 +231,19 @@ library LibLazyUpdate { eventIdsAndArtifacts[numEventsToRemove++] = events[earliestEventIndex].id; uint256 newArtifactId; - (newArtifactId, planet) = applyArrival( + uint256 newSpaceshipId; + (newArtifactId, newSpaceshipId, planet) = applyArrival( planet, gs().planetArrivals[events[earliestEventIndex].id] ); + // TODO: Cleanup this nastiness if (newArtifactId != 0) { eventIdsAndArtifacts[12 + numNewArtifactsOnPlanet++] = newArtifactId; } + if (newSpaceshipId != 0) { + eventIdsAndArtifacts[12 + numNewArtifactsOnPlanet++] = newSpaceshipId; + } } } } while (earliestEventTime <= currentTimestamp); From 2beac0f4f4d626e70094bf807e3053f82b1254f6 Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Tue, 4 Oct 2022 23:08:55 +0100 Subject: [PATCH 49/55] Cha0s/erc1155 ctd (#34) * feat: working DFTokenFacet * add phated changes + new facet * feat: get players aritfacts and ships + tests. and refactor lol * remove unused modifier * remove silver token * fix: spaceship found event Co-authored-by: Blaine Bublitz --- eth/contracts/DFDiamond.sol | 14 +- eth/contracts/facets/DFAdminFacet.sol | 3 +- eth/contracts/facets/DFArtifactFacet.sol | 142 ++------------ eth/contracts/facets/DFGetterFacet.sol | 184 +++++-------------- eth/contracts/facets/DFMoveFacet.sol | 4 +- eth/contracts/facets/DFSpaceshipFacet.sol | 175 ++++++++++++++++++ eth/contracts/facets/DFTokenFacet.sol | 104 +++++++++++ eth/contracts/libraries/LibArtifactUtils.sol | 26 +-- eth/contracts/libraries/LibPlanet.sol | 2 +- eth/contracts/libraries/LibSpaceship.sol | 30 +++ eth/tasks/deploy.ts | 26 ++- eth/test/DFArtifacts.test.ts | 76 -------- eth/test/DFGetter.test.ts | 35 ++++ eth/test/DFLobby.test.ts | 15 +- eth/utils/diamond.ts | 2 +- 15 files changed, 450 insertions(+), 388 deletions(-) create mode 100644 eth/contracts/facets/DFSpaceshipFacet.sol create mode 100644 eth/contracts/facets/DFTokenFacet.sol create mode 100644 eth/test/DFGetter.test.ts diff --git a/eth/contracts/DFDiamond.sol b/eth/contracts/DFDiamond.sol index ab2050ab..e185ff4a 100644 --- a/eth/contracts/DFDiamond.sol +++ b/eth/contracts/DFDiamond.sol @@ -13,9 +13,9 @@ import {ERC165, IERC165, ERC165Storage} from "@solidstate/contracts/introspectio import {DiamondBase, DiamondBaseStorage} from "@solidstate/contracts/proxy/diamond/base/DiamondBase.sol"; import {DiamondReadable, IDiamondReadable} from "@solidstate/contracts/proxy/diamond/readable/DiamondReadable.sol"; import {DiamondWritable, IDiamondWritable} from "@solidstate/contracts/proxy/diamond/writable/DiamondWritable.sol"; -import {IERC721} from "@solidstate/contracts/token/ERC721/IERC721.sol"; -import {IERC721Metadata} from "@solidstate/contracts/token/ERC721/metadata/IERC721Metadata.sol"; -import {IERC721Enumerable} from "@solidstate/contracts/token/ERC721/enumerable/IERC721Enumerable.sol"; +import {IERC1155} from "@solidstate/contracts/token/ERC1155/IERC1155.sol"; +import {IERC1155Metadata} from "@solidstate/contracts/token/ERC1155/metadata/IERC1155Metadata.sol"; +import {IERC1155Enumerable} from "@solidstate/contracts/token/ERC1155/enumerable/IERC1155Enumerable.sol"; /** * @title SolidState "Diamond" proxy reference implementation @@ -57,10 +57,10 @@ contract DFDiamond is DiamondBase, DiamondReadable, DiamondWritable, Ownable, ER erc165.setSupportedInterface(type(IERC173).interfaceId, true); - // Store ERC721 interface - erc165.setSupportedInterface(type(IERC721).interfaceId, true); - erc165.setSupportedInterface(type(IERC721Metadata).interfaceId, true); - erc165.setSupportedInterface(type(IERC721Enumerable).interfaceId, true); + // Store ERC1155 interface + erc165.setSupportedInterface(type(IERC1155).interfaceId, true); + erc165.setSupportedInterface(type(IERC1155Metadata).interfaceId, true); + erc165.setSupportedInterface(type(IERC1155Enumerable).interfaceId, true); // register Diamond diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index 7a04df0c..fad409b3 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -14,6 +14,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Contract imports import {DFArtifactFacet} from "./DFArtifactFacet.sol"; +import {DFSpaceshipFacet} from "./DFSpaceshipFacet.sol"; // Type imports import {Artifact, SpaceType, Spaceship, SpaceshipType, DFPInitPlanetArgs, AdminCreatePlanetArgs, DFTCreateArtifactArgs, ArtifactType, Player, Planet, TokenType} from "../DFTypes.sol"; @@ -146,7 +147,7 @@ contract DFAdminFacet is WithStorage { ) public onlyAdmin { require(gs().planets[locationId].isInitialized, "planet is not initialized"); - uint256 shipId = LibArtifactUtils.createAndPlaceSpaceship(locationId, owner, shipType); + uint256 shipId = LibSpaceship.createAndPlaceSpaceship(locationId, owner, shipType); Spaceship memory spaceship = LibSpaceship.decode(shipId); Planet memory planet = gs().planets[locationId]; diff --git a/eth/contracts/facets/DFArtifactFacet.sol b/eth/contracts/facets/DFArtifactFacet.sol index 789fe815..6fdb9429 100644 --- a/eth/contracts/facets/DFArtifactFacet.sol +++ b/eth/contracts/facets/DFArtifactFacet.sol @@ -2,16 +2,15 @@ pragma solidity ^0.8.0; // Contract imports -import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; import {DFVerifierFacet} from "./DFVerifierFacet.sol"; import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; +import {DFTokenFacet} from "./DFTokenFacet.sol"; // Library Imports import {LibPermissions} from "../libraries/LibPermissions.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; import {LibArtifact} from "../libraries/LibArtifact.sol"; -import {LibSpaceship} from "../libraries/LibSpaceship.sol"; import {LibPlanet} from "../libraries/LibPlanet.sol"; @@ -19,9 +18,9 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs, Spaceship, SpaceshipType} from "../DFTypes.sol"; +import {Artifact, ArtifactRarity, ArtifactType, Biome, TokenType, DFTCreateArtifactArgs, DFPFindArtifactArgs} from "../DFTypes.sol"; -contract DFArtifactFacet is WithStorage, SolidStateERC1155 { +contract DFArtifactFacet is WithStorage { event PlanetProspected(address player, uint256 loc); event ArtifactFound(address player, uint256 artifactId, uint256 loc); event ArtifactDeposited(address player, uint256 artifactId, uint256 loc); @@ -48,18 +47,18 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { _; } - modifier onlyAdminOrCore() { + modifier onlyAdmin() { require( - msg.sender == gs().diamondAddress || msg.sender == LibPermissions.contractOwner(), - "Only the Core or Admin addresses can fiddle with artifacts." + msg.sender == LibPermissions.contractOwner(), + "Only Admin address can perform this action." ); _; } - modifier onlyAdmin() { + modifier onlyAdminOrCore() { require( - msg.sender == LibPermissions.contractOwner(), - "Only Admin address can perform this action." + msg.sender == gs().diamondAddress || msg.sender == LibPermissions.contractOwner(), + "Only the Core or Admin addresses can fiddle with artifacts." ); _; } @@ -72,33 +71,11 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { require(tokenId >= 1, "token id must be positive"); require(LibArtifact.isArtifact(tokenId), "token must be Artifact"); // Account, Id, Amount, Data - _mint(owner, tokenId, 1, ""); + DFTokenFacet(address(this)).mint(owner, tokenId, 1); return LibArtifact.decode(tokenId); } - function createSpaceship(uint256 tokenId, address owner) - public - onlyAdminOrCore - returns (Spaceship memory) - { - require(tokenId >= 1, "token id must be positive"); - require(LibSpaceship.isShip(tokenId), "token must be Spaceship"); - - // Account, Id, Amount, Data - _mint(owner, tokenId, 1, ""); - - return getSpaceshipFromId(tokenId); - } - - function getSpaceshipFromId(uint256 shipId) public pure returns (Spaceship memory) { - return LibSpaceship.decode(shipId); - } - - function createSpaceshipId(SpaceshipType spaceshipType) public pure returns (uint256) { - return LibSpaceship.create(spaceshipType); - } - function createArtifactId( ArtifactRarity rarity, ArtifactType artifactType, @@ -120,17 +97,13 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { ) public onlyAdminOrCore { if (newOwner == address(0)) { // account, id, amount. - _burn(owner, tokenId, 1); + DFTokenFacet(address(this)).burn(owner, tokenId, 1); } else { // sender receiver id amount data - _transfer(owner, owner, newOwner, tokenId, 1, ""); + DFTokenFacet(address(this)).transfer(owner, owner, newOwner, tokenId, 1, ""); } } - function tokenExists(address owner, uint256 tokenId) public view returns (bool) { - return balanceOf(owner, tokenId) > 0; - } - function findArtifact( uint256[2] memory _a, uint256[2][2] memory _b, @@ -219,95 +192,4 @@ contract DFArtifactFacet is WithStorage, SolidStateERC1155 { LibArtifactUtils.prospectPlanet(locationId); emit PlanetProspected(msg.sender, locationId); } - - /** - Gives players 5 spaceships on their home planet. Can only be called once - by a given player. This is a first pass at getting spaceships into the game. - Eventually ships will be able to spawn in the game naturally (construction, capturing, etc.) - */ - function giveSpaceShips(uint256 locationId) public onlyWhitelisted { - require(!gs().players[msg.sender].claimedShips, "player already claimed ships"); - require( - gs().planets[locationId].owner == msg.sender && gs().planets[locationId].isHomePlanet, - "you can only spawn ships on your home planet" - ); - - address owner = gs().planets[locationId].owner; - if (gameConstants().SPACESHIPS.MOTHERSHIP) { - uint256 id1 = LibArtifactUtils.createAndPlaceSpaceship( - locationId, - owner, - SpaceshipType.ShipMothership - ); - emit ArtifactFound(msg.sender, id1, locationId); - } - - if (gameConstants().SPACESHIPS.CRESCENT) { - uint256 id2 = LibArtifactUtils.createAndPlaceSpaceship( - locationId, - owner, - SpaceshipType.ShipCrescent - ); - emit ArtifactFound(msg.sender, id2, locationId); - } - - if (gameConstants().SPACESHIPS.WHALE) { - uint256 id3 = LibArtifactUtils.createAndPlaceSpaceship( - locationId, - owner, - SpaceshipType.ShipWhale - ); - emit ArtifactFound(msg.sender, id3, locationId); - } - - if (gameConstants().SPACESHIPS.GEAR) { - uint256 id4 = LibArtifactUtils.createAndPlaceSpaceship( - locationId, - owner, - SpaceshipType.ShipGear - ); - emit ArtifactFound(msg.sender, id4, locationId); - } - - if (gameConstants().SPACESHIPS.TITAN) { - uint256 id5 = LibArtifactUtils.createAndPlaceSpaceship( - locationId, - owner, - SpaceshipType.ShipTitan - ); - - emit ArtifactFound(msg.sender, id5, locationId); - } - - gs().players[msg.sender].claimedShips = true; - } - - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal override { - uint256 length = ids.length; - for (uint256 i = 0; i < length; i++) { - // Only core contract can transfer Spaceships - if (LibSpaceship.isShip(ids[i])) { - require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); - } - } - - // TODO: Are we supposed to call this before or after - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - } - - /** - * @notice set per-token metadata URI - * @param tokenId token whose metadata URI to set - * @param tokenURI per-token URI - */ - function setTokenURI(uint256 tokenId, string memory tokenURI) public { - _setTokenURI(tokenId, tokenURI); - } } diff --git a/eth/contracts/facets/DFGetterFacet.sol b/eth/contracts/facets/DFGetterFacet.sol index 6c545777..5adb09cd 100644 --- a/eth/contracts/facets/DFGetterFacet.sol +++ b/eth/contracts/facets/DFGetterFacet.sol @@ -3,19 +3,19 @@ pragma solidity ^0.8.0; // External contract imports import {DFArtifactFacet} from "./DFArtifactFacet.sol"; +import {DFTokenFacet} from "./DFTokenFacet.sol"; // Library imports import {LibArtifact} from "../libraries/LibArtifact.sol"; import {LibArtifactUtils} from "../libraries/LibArtifactUtils.sol"; import {LibGameUtils} from "../libraries/LibGameUtils.sol"; import {LibPermissions} from "../libraries/LibPermissions.sol"; -import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; // Type imports -import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade, Spaceship} from "../DFTypes.sol"; +import {RevealedCoords, Artifact, ArrivalData, Planet, PlanetEventType, PlanetEventMetadata, PlanetDefaultStats, PlanetData, Player, Upgrade} from "../DFTypes.sol"; contract DFGetterFacet is WithStorage { // FIRST-LEVEL GETTERS - mirrors the solidity autogenerated toplevel getters, but for GameStorage @@ -95,10 +95,6 @@ contract DFGetterFacet is WithStorage { return gs().planets[locationId].artifacts; } - function planetSpaceships(uint256 locationId) public view returns (uint256[] memory) { - return gs().planets[locationId].spaceships; - } - // ADDITIONAL UTILITY GETTERS function getNPlanets() public view returns (uint256) { @@ -318,33 +314,6 @@ contract DFGetterFacet is WithStorage { return gs().planets[locationId].artifactActivationTime; } - // function getArtifactById(uint256 artifactId) - // public - // view - // returns (ArtifactWithMetadata memory ret) - // { - // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifact(artifactId); - - // address owner; - - // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { - // owner = addr; - // } catch Error(string memory) { - // // artifact is probably burned / owned by 0x0, so owner is 0x0 - // } catch (bytes memory) { - // // this shouldn't happen - // } - - // ret = ArtifactWithMetadata({ - // artifact: artifact, - // upgrade: LibGameUtils._getUpgradeForArtifact(artifact), - // timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), - // owner: owner, - // locationId: gs().artifactIdToPlanetId[artifact.id], - // voyageId: gs().artifactIdToVoyageId[artifact.id] - // }); - // } - function getUpgradeForArtifact(uint256 artifactId) public pure returns (Upgrade memory) { return LibArtifact.getUpgradeForArtifact(LibArtifact.decode(artifactId)); } @@ -358,33 +327,6 @@ contract DFGetterFacet is WithStorage { return ret; } - function getSpaceshipsOnPlanet(uint256 locationId) - public - view - returns (Spaceship[] memory ret) - { - uint256[] memory tokenIds = gs().planets[locationId].spaceships; - ret = new Spaceship[](tokenIds.length); - for (uint256 i = 0; i < tokenIds.length; i++) { - ret[i] = LibSpaceship.decode(tokenIds[i]); - } - return ret; - } - - // Combo on Ships and Artifacts - function tokenExistsOnPlanet(uint256 locationId, uint256 tokenId) public view returns (bool) { - bool hasToken = false; - uint256[] memory artifactIds = gs().planets[locationId].artifacts; - for (uint256 i = 0; i < artifactIds.length; i++) { - if (artifactIds[i] == tokenId) return true; - } - uint256[] memory shipIds = gs().planets[locationId].spaceships; - for (uint256 i = 0; i < shipIds.length; i++) { - if (shipIds[i] == tokenId) return true; - } - return hasToken; - } - function hasActiveArtifact(uint256 locationId) public view returns (bool) { return LibArtifact.hasActiveArtifact(locationId); } @@ -398,85 +340,45 @@ contract DFGetterFacet is WithStorage { return LibArtifact.decode(artifactId); } - // function bulkGetPlanetArtifacts(uint256[] calldata planetIds) - // public - // view - // returns (ArtifactWithMetadata[][] memory) - // { - // ArtifactWithMetadata[][] memory ret = new ArtifactWithMetadata[][](planetIds.length); - - // for (uint256 i = 0; i < planetIds.length; i++) { - // uint256[] memory planetOwnedArtifactIds = gs().planetArtifacts[planetIds[i]]; - // ret[i] = bulkGetArtifactsByIds(planetOwnedArtifactIds); - // } - - // return ret; - // } - - // function bulkGetArtifactsByIds(uint256[] memory ids) - // public - // view - // returns (ArtifactWithMetadata[] memory ret) - // { - // ret = new ArtifactWithMetadata[](ids.length); - - // for (uint256 i = 0; i < ids.length; i++) { - // Artifact memory artifact = LibArtifactUtils.decodeArtifact(ids[i]); - - // address owner; - - // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { - // owner = addr; - // } catch Error(string memory) { - // // artifact is probably burned or owned by 0x0, so owner is 0x0 - // } catch (bytes memory) { - // // this shouldn't happen - // } - - // ret[i] = ArtifactWithMetadata({ - // artifact: artifact, - // upgrade: LibGameUtils._getUpgradeForArtifact(artifact), - // timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), - // owner: owner, - // locationId: gs().artifactIdToPlanetId[artifact.id], - // voyageId: gs().artifactIdToVoyageId[artifact.id] - // }); - // } - // } - - /** - * Get a group or artifacts based on their index, fetch all between startIdx & endIdx. - Indexes are assigned to artifacts based on the order in which they are minted. - * index 0 would be the first Artifact minted, etc. - * @param startIdx index of the first element to get - * @param endIdx index of the last element to get - */ - // function bulkGetArtifacts(uint256 startIdx, uint256 endIdx) - // public - // view - // returns (ArtifactWithMetadata[] memory ret) - // { - // ret = new ArtifactWithMetadata[](endIdx - startIdx); - - // for (uint256 i = startIdx; i < endIdx; i++) { - // Artifact memory artifact = LibArtifactUtils(address(this)).decodeArtifactAtIndex(i); - // address owner = address(0); - - // try DFArtifactFacet(address(this)).ownerOf(artifact.id) returns (address addr) { - // owner = addr; - // } catch Error(string memory) { - // // artifact is probably burned or owned by 0x0, so owner is 0x0 - // } catch (bytes memory) { - // // this shouldn't happen - // } - // ret[i - startIdx] = ArtifactWithMetadata({ - // artifact: artifact, - // upgrade: LibGameUtils._getUpgradeForArtifact(artifact), - // timeDelayedUpgrade: LibGameUtils.timeDelayUpgrade(artifact), - // owner: owner, - // locationId: gs().artifactIdToPlanetId[artifact.id], - // voyageId: gs().artifactIdToVoyageId[artifact.id] - // }); - // } - // } + function bulkGetPlanetAritfacts(uint256[] calldata planetIds) + public + view + returns (Artifact[][] memory) + { + Artifact[][] memory ret = new Artifact[][](planetIds.length); + + for (uint256 i = 0; i < planetIds.length; i++) { + uint256[] memory artifactsOnPlanet = gs().planets[planetIds[i]].artifacts; + ret[i] = bulkGetArtifactsByIds(artifactsOnPlanet); + } + + return ret; + } + + function bulkGetArtifactsByIds(uint256[] memory artifactIds) + public + pure + returns (Artifact[] memory ret) + { + ret = new Artifact[](artifactIds.length); + + for (uint256 i = 0; i < artifactIds.length; i++) { + ret[i] = LibArtifact.decode(artifactIds[i]); + } + } + + function getPlayerArtifacts(address player) public view returns (Artifact[] memory ret) { + uint256[] memory tokens = DFTokenFacet(address(this)).tokensByAccount(player); + uint256 numArtifacts = 0; + for (uint256 i = 0; i < tokens.length; i++) { + if (LibArtifact.isArtifact(tokens[i])) numArtifacts += 1; + } + + ret = new Artifact[](numArtifacts); + numArtifacts = 0; + for (uint256 i = 0; i < tokens.length; i++) { + if (LibArtifact.isArtifact(tokens[i])) + ret[numArtifacts++] = LibArtifact.decode(tokens[i]); + } + } } diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index b8a42c78..99d61166 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; // Contract imports import {DFVerifierFacet} from "./DFVerifierFacet.sol"; -import {DFArtifactFacet} from "./DFArtifactFacet.sol"; +import {DFTokenFacet} from "./DFTokenFacet.sol"; // Library imports import {LibArtifact} from "../libraries/LibArtifact.sol"; @@ -201,7 +201,7 @@ contract DFMoveFacet is WithStorage { require(args.popMoved == 0, "ship moves must move 0 energy"); require(args.silverMoved == 0, "ship moves must move 0 silver"); require( - DFArtifactFacet(address(this)).tokenExists(msg.sender, args.movedArtifactId), + DFTokenFacet(address(this)).tokenIsOwnedBy(msg.sender, args.movedArtifactId), "you can only move your own ships" ); } else { diff --git a/eth/contracts/facets/DFSpaceshipFacet.sol b/eth/contracts/facets/DFSpaceshipFacet.sol new file mode 100644 index 00000000..5cd847e3 --- /dev/null +++ b/eth/contracts/facets/DFSpaceshipFacet.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +// External contract imports +import {DFWhitelistFacet} from "./DFWhitelistFacet.sol"; +import {DFTokenFacet} from "./DFTokenFacet.sol"; + +// Library imports +import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; + +// Storage imports +import {WithStorage, SnarkConstants, GameConstants} from "../libraries/LibStorage.sol"; + +// Type imports +import {Spaceship, SpaceshipType} from "../DFTypes.sol"; + +contract DFSpaceshipFacet is WithStorage { + /** + * Modifiers + */ + + modifier onlyWhitelisted() { + require( + DFWhitelistFacet(address(this)).isWhitelisted(msg.sender) || + msg.sender == LibPermissions.contractOwner(), + "Player is not whitelisted" + ); + _; + } + /** + * Events + */ + event SpaceshipFound(address player, uint256 artifactId, uint256 loc); + + /** + * Getters + */ + function planetSpaceships(uint256 locationId) public view returns (uint256[] memory) { + return gs().planets[locationId].spaceships; + } + + function getSpaceshipsOnPlanet(uint256 locationId) + public + view + returns (Spaceship[] memory ret) + { + uint256[] memory tokenIds = gs().planets[locationId].spaceships; + ret = new Spaceship[](tokenIds.length); + for (uint256 i = 0; i < tokenIds.length; i++) { + ret[i] = LibSpaceship.decode(tokenIds[i]); + } + return ret; + } + + function bulkGetPlanetSpaceships(uint256[] calldata planetIds) + public + view + returns (Spaceship[][] memory) + { + Spaceship[][] memory ret = new Spaceship[][](planetIds.length); + + for (uint256 i = 0; i < planetIds.length; i++) { + uint256[] memory spacehipsOnPlanet = gs().planets[planetIds[i]].spaceships; + ret[i] = bulkGetSpaceshipsByIds(spacehipsOnPlanet); + } + + return ret; + } + + function bulkGetSpaceshipsByIds(uint256[] memory spaceshpIds) + public + pure + returns (Spaceship[] memory ret) + { + ret = new Spaceship[](spaceshpIds.length); + + for (uint256 i = 0; i < spaceshpIds.length; i++) { + ret[i] = LibSpaceship.decode(spaceshpIds[i]); + } + } + + function getPlayerSpaceships(address player) public view returns (Spaceship[] memory ret) { + uint256[] memory tokens = DFTokenFacet(address(this)).tokensByAccount(player); + uint256 numSpaceships = 0; + for (uint256 i = 0; i < tokens.length; i++) { + if (LibSpaceship.isShip(tokens[i])) numSpaceships += 1; + } + ret = new Spaceship[](numSpaceships); + numSpaceships = 0; + for (uint256 i = 0; i < tokens.length; i++) { + if (LibSpaceship.isShip(tokens[i])) + ret[numSpaceships++] = LibSpaceship.decode(tokens[i]); + } + } + + function getSpaceshipFromId(uint256 shipId) public pure returns (Spaceship memory) { + return LibSpaceship.decode(shipId); + } + + /** + * Helpers + */ + + function createSpaceshipId(SpaceshipType spaceshipType) public pure returns (uint256) { + return LibSpaceship.create(spaceshipType); + } + + /** + * Actions + */ + + /** + Gives players 5 spaceships on their home planet. Can only be called once + by a given player. This is a first pass at getting spaceships into the game. + Eventually ships will be able to spawn in the game naturally (construction, capturing, etc.) + */ + + function giveSpaceShips(uint256 locationId) public onlyWhitelisted { + require(!gs().players[msg.sender].claimedShips, "player already claimed ships"); + require( + gs().planets[locationId].owner == msg.sender && gs().planets[locationId].isHomePlanet, + "you can only spawn ships on your home planet" + ); + + address owner = gs().planets[locationId].owner; + if (gameConstants().SPACESHIPS.MOTHERSHIP) { + uint256 id1 = LibSpaceship.createAndPlaceSpaceship( + locationId, + owner, + SpaceshipType.ShipMothership + ); + emit SpaceshipFound(msg.sender, id1, locationId); + } + + if (gameConstants().SPACESHIPS.CRESCENT) { + uint256 id2 = LibSpaceship.createAndPlaceSpaceship( + locationId, + owner, + SpaceshipType.ShipCrescent + ); + emit SpaceshipFound(msg.sender, id2, locationId); + } + + if (gameConstants().SPACESHIPS.WHALE) { + uint256 id3 = LibSpaceship.createAndPlaceSpaceship( + locationId, + owner, + SpaceshipType.ShipWhale + ); + emit SpaceshipFound(msg.sender, id3, locationId); + } + + if (gameConstants().SPACESHIPS.GEAR) { + uint256 id4 = LibSpaceship.createAndPlaceSpaceship( + locationId, + owner, + SpaceshipType.ShipGear + ); + emit SpaceshipFound(msg.sender, id4, locationId); + } + + if (gameConstants().SPACESHIPS.TITAN) { + uint256 id5 = LibSpaceship.createAndPlaceSpaceship( + locationId, + owner, + SpaceshipType.ShipTitan + ); + + emit SpaceshipFound(msg.sender, id5, locationId); + } + + gs().players[msg.sender].claimedShips = true; + } +} diff --git a/eth/contracts/facets/DFTokenFacet.sol b/eth/contracts/facets/DFTokenFacet.sol new file mode 100644 index 00000000..9df710af --- /dev/null +++ b/eth/contracts/facets/DFTokenFacet.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +// Contract imports +import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateERC1155.sol"; + +// Library Imports +import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibSpaceship} from "../libraries/LibSpaceship.sol"; + +// Storage imports +import {WithStorage} from "../libraries/LibStorage.sol"; + +contract DFTokenFacet is WithStorage, SolidStateERC1155 { + modifier onlyAdminOrCore() { + require( + msg.sender == gs().diamondAddress || msg.sender == LibPermissions.contractOwner(), + "Only the Core or Admin addresses can fiddle with tokens." + ); + _; + } + + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal override { + uint256 length = ids.length; + for (uint256 i = 0; i < length; i++) { + // Only core contract can transfer Spaceships + if (LibSpaceship.isShip(ids[i])) { + require(msg.sender == gs().diamondAddress, "player cannot transfer a Spaceship"); + } + } + + // TODO: Are we supposed to call this before or after + super._beforeTokenTransfer(operator, from, to, ids, amounts, data); + } + + /** + * @notice set per-token metadata URI + * @param tokenId token whose metadata URI to set + * @param tokenURI per-token URI + */ + function setTokenURI(uint256 tokenId, string memory tokenURI) public { + _setTokenURI(tokenId, tokenURI); + } + + /** + * @notice ERC1155 mint + * @param owner of new tokens + * @param tokenId tokenId to mint + * @param amount amount of tokens to mint + */ + function mint( + address owner, + uint256 tokenId, + uint256 amount + ) public onlyAdminOrCore { + _mint(owner, tokenId, amount, ""); + } + + /** + * @notice burn given quantity of tokens held by given address + * @param account holder of tokens to burn + * @param id token ID + * @param amount quantity of tokens to burn + */ + function burn( + address account, + uint256 id, + uint256 amount + ) public onlyAdminOrCore { + _burn(account, id, amount); + } + + /** + * @notice transfer tokens between given addresses + * @dev ERC1155Receiver implementation is not checked + * @param operator executor of transfer + * @param sender sender of tokens + * @param recipient receiver of tokens + * @param id token ID + * @param amount quantity of tokens to transfer + * @param data data payload + */ + function transfer( + address operator, + address sender, + address recipient, + uint256 id, + uint256 amount, + bytes memory data + ) public onlyAdminOrCore { + _transfer(operator, sender, recipient, id, amount, data); + } + + function tokenIsOwnedBy(address owner, uint256 tokenId) public view returns (bool) { + return balanceOf(owner, tokenId) > 0; + } +} diff --git a/eth/contracts/libraries/LibArtifactUtils.sol b/eth/contracts/libraries/LibArtifactUtils.sol index f200bc75..71bbe413 100644 --- a/eth/contracts/libraries/LibArtifactUtils.sol +++ b/eth/contracts/libraries/LibArtifactUtils.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.0; // External contract imports import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; -import {DFGetterFacet} from "../facets/DFGetterFacet.sol"; +import {DFSpaceshipFacet} from "../facets/DFSpaceshipFacet.sol"; +import {DFTokenFacet} from "../facets/DFTokenFacet.sol"; // Library imports import {LibArtifact} from "./LibArtifact.sol"; @@ -55,23 +56,6 @@ library LibArtifactUtils { return true; } - /** - * Create a new spaceship and place it on a planet owned by the given player. Returns the id - * of the newly created spaceship. - */ - function createAndPlaceSpaceship( - uint256 planetId, - address owner, - SpaceshipType shipType - ) public returns (uint256) { - uint256 tokenId = LibSpaceship.create(shipType) + uint128(gs().miscNonce++); - - Spaceship memory spaceship = DFArtifactFacet(address(this)).createSpaceship(tokenId, owner); - LibSpaceship.putSpaceshipOnPlanet(planetId, spaceship.id); - - return spaceship.id; - } - function findArtifact(DFPFindArtifactArgs memory args) public returns (uint256 artifactId) { Planet storage planet = gs().planets[args.planetId]; @@ -286,7 +270,7 @@ library LibArtifactUtils { require(!gs().planets[locationId].destroyed, "planet is destroyed"); require(planet.planetType == PlanetType.TRADING_POST, "can only deposit on trading posts"); require( - DFArtifactFacet(address(this)).tokenExists(msg.sender, artifactId), + DFTokenFacet(address(this)).tokenIsOwnedBy(msg.sender, artifactId), "you can only deposit artifacts you own" ); require(planet.owner == msg.sender, "you can only deposit on a planet you own"); @@ -345,13 +329,15 @@ library LibArtifactUtils { planet.prospectedBlockNumber = block.number; } + // Leaving this here because does a msg.sender check. + // Could put it in LibSpaceship but would need to delegatecall. function containsGear(uint256 locationId) public view returns (bool) { uint256[] memory tokenIds = gs().planets[locationId].spaceships; for (uint256 i = 0; i < tokenIds.length; i++) { Spaceship memory spaceship = LibSpaceship.decode(tokenIds[i]); if ( spaceship.spaceshipType == SpaceshipType.ShipGear && - DFArtifactFacet(address(this)).tokenExists(msg.sender, tokenIds[i]) + DFTokenFacet(address(this)).tokenIsOwnedBy(msg.sender, tokenIds[i]) ) { return true; } diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index ab34b3c0..7d452da2 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; // Contract imports import {DFVerifierFacet} from "../facets/DFVerifierFacet.sol"; -import {DFArtifactFacet} from "../facets/DFArtifactFacet.sol"; +import {DFTokenFacet} from "../facets/DFTokenFacet.sol"; // Library imports import {LibArtifact} from "./LibArtifact.sol"; diff --git a/eth/contracts/libraries/LibSpaceship.sol b/eth/contracts/libraries/LibSpaceship.sol index 0cff4526..f8525ee4 100644 --- a/eth/contracts/libraries/LibSpaceship.sol +++ b/eth/contracts/libraries/LibSpaceship.sol @@ -5,6 +5,9 @@ pragma solidity ^0.8.0; * Library for all things Spaceships */ +// Contract Imports +import {DFTokenFacet} from "../facets/DFTokenFacet.sol"; + // Library imports import {LibUtils} from "./LibUtils.sol"; @@ -100,4 +103,31 @@ library LibSpaceship { require(hadTheShip, "this ship was not present on this planet"); gs().planets[locationId].spaceships.pop(); } + + function createSpaceship(uint256 tokenId, address owner) internal returns (Spaceship memory) { + require(tokenId >= 1, "token id must be positive"); + require(LibSpaceship.isShip(tokenId), "token must be Spaceship"); + + // Account, Id, Amount, Data + DFTokenFacet(address(this)).mint(owner, tokenId, 1); + + return decode(tokenId); + } + + /** + * Create a new spaceship and place it on a planet owned by the given player. Returns the id + * of the newly created spaceship. + */ + function createAndPlaceSpaceship( + uint256 planetId, + address owner, + SpaceshipType shipType + ) internal returns (uint256) { + uint256 tokenId = LibSpaceship.create(shipType) + uint128(gs().miscNonce++); + + Spaceship memory spaceship = createSpaceship(tokenId, owner); + LibSpaceship.putSpaceshipOnPlanet(planetId, spaceship.id); + + return spaceship.id; + } } diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index 8066ff3f..7d28c90f 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -246,12 +246,14 @@ export async function deployAndCut( const coreFacet = await deployCoreFacet({}, libraries, hre); const moveFacet = await deployMoveFacet({}, libraries, hre); const captureFacet = await deployCaptureFacet({}, libraries, hre); + const tokenFacet = await deployTokenFacet({}, libraries, hre); const artifactFacet = await deployArtifactFacet( { diamondAddress: diamond.address }, libraries, hre ); const getterFacet = await deployGetterFacet({}, libraries, hre); + const spaceshipFacet = await deploySpaceshipFacet({}, libraries, hre); const whitelistFacet = await deployWhitelistFacet({}, libraries, hre); const verifierFacet = await deployVerifierFacet({}, libraries, hre); const adminFacet = await deployAdminFacet({}, libraries, hre); @@ -270,6 +272,8 @@ export async function deployAndCut( ...changes.getFacetCuts('DFAdminFacet', adminFacet), ...changes.getFacetCuts('DFLobbyFacet', lobbyFacet), ...changes.getFacetCuts('DFRewardFacet', rewardFacet), + ...changes.getFacetCuts('DFSpaceshipFacet', spaceshipFacet), + ...changes.getFacetCuts('DFTokenFacet', tokenFacet), ]; if (isDev) { @@ -308,6 +312,15 @@ export async function deployAndCut( return [diamond, diamondInit, initReceipt] as const; } +export async function deploySpaceshipFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { + const factory = await hre.ethers.getContractFactory('DFSpaceshipFacet', { + libraries: {}, + }); + const contract = await factory.deploy(); + await contract.deployTransaction.wait(); + console.log('DFSpacehipFacet deployed to:', contract.address); + return contract; +} export async function deployGetterFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { const factory = await hre.ethers.getContractFactory('DFGetterFacet', { @@ -321,12 +334,11 @@ export async function deployGetterFacet({}, {}: Libraries, hre: HardhatRuntimeEn export async function deployAdminFacet( {}, - { LibGameUtils, LibPlanet, LibArtifactUtils }: Libraries, + { LibGameUtils, LibPlanet }: Libraries, hre: HardhatRuntimeEnvironment ) { const factory = await hre.ethers.getContractFactory('DFAdminFacet', { libraries: { - LibArtifactUtils, LibGameUtils, LibPlanet, }, @@ -369,6 +381,16 @@ export async function deployVerifierFacet({}, {}: Libraries, hre: HardhatRuntime return contract; } +export async function deployTokenFacet({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { + const factory = await hre.ethers.getContractFactory('DFTokenFacet', { + libraries: {}, + }); + const contract = await factory.deploy(); + await contract.deployTransaction.wait(); + console.log(`DFTokenFacet deployed to: ${contract.address}`); + return contract; +} + export async function deployArtifactFacet( {}, { LibGameUtils, LibPlanet, LibArtifactUtils }: Libraries, diff --git a/eth/test/DFArtifacts.test.ts b/eth/test/DFArtifacts.test.ts index 295346fb..290f9745 100644 --- a/eth/test/DFArtifacts.test.ts +++ b/eth/test/DFArtifacts.test.ts @@ -2,13 +2,11 @@ import { ArtifactRarity, ArtifactType, Biome, SpaceshipType, TokenType } from '@ import { loadFixture, mine } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import hre from 'hardhat'; -import { TestLocation } from './utils/TestLocation'; import { activateAndConfirm, conquerUnownedPlanet, createArtifact, getArtifactsOnPlanet, - getArtifactsOwnedBy, getCurrentTime, getStatSum, increaseBlockchainTime, @@ -30,7 +28,6 @@ import { LVL3_UNOWNED_NEBULA, LVL4_UNOWNED_DEEP_SPACE, LVL6_SPACETIME, - SPACE_PERLIN, SPAWN_PLANET_1, SPAWN_PLANET_2, ZERO_PLANET, @@ -339,79 +336,6 @@ describe('DarkForestArtifacts', function () { "you can't find an artifact on this planet" ); }); - // TODO: Why do we need this test? - it.skip('should mint randomly', async function () { - // This can take upwards of 90000ms in CI - this.timeout(0); - - await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL3_SPACETIME_1); - - /* eslint-disable @typescript-eslint/no-explicit-any */ - let artifacts: any; - let prevLocation = SPAWN_PLANET_1; - - for (let i = 0; i < 20; i++) { - // byte #8 is 18_16 = 24_10 so it's a ruins planet - const randomHex = - `00007c2512896efb182d462faee0000fb33d58930eb9e6b4fbae6d048e9c44` + - (i >= 10 ? i.toString()[0] : 0) + - '' + - (i % 10); - - const planetWithArtifactLoc = new TestLocation({ - hex: randomHex, - perlin: SPACE_PERLIN, - distFromOrigin: 1998, - }); - - await world.contract.adminInitializePlanet( - planetWithArtifactLoc.id, - planetWithArtifactLoc.perlin - ); - - await world.contract.adminGiveSpaceShip( - planetWithArtifactLoc.id, - world.user1.address, - ArtifactType.ShipGear - ); - - await increaseBlockchainTime(); - - await world.user1Core.move( - ...makeMoveArgs(prevLocation, planetWithArtifactLoc, 0, 80000, 0) - ); // move 80000 from asteroids but 160000 from ruins since ruins are higher level - await increaseBlockchainTime(); - - await world.user1Core.prospectPlanet(planetWithArtifactLoc.id); - await increaseBlockchainTime(); - - await world.user1Core.findArtifact(...makeFindArtifactArgs(planetWithArtifactLoc)); - await increaseBlockchainTime(); - - const artifactsOnPlanet = await getArtifactsOnPlanet(world, planetWithArtifactLoc.id); - const artifactId = artifactsOnPlanet[0].id; - - await world.user1Core.move( - ...makeMoveArgs(planetWithArtifactLoc, LVL3_SPACETIME_1, 0, 40000, 0, artifactId) - ); - await world.user1Core.withdrawArtifact(LVL3_SPACETIME_1.id, artifactId); - artifacts = await getArtifactsOwnedBy(world.contract, world.user1.address); - - expect(artifacts[artifacts.length - 1].planetBiome).to.eq(4); // tundra - expect(artifacts[artifacts.length - 1].discoverer).to.eq(world.user1.address); - expect(artifacts[artifacts.length - 1].rarity).to.be.at.least(1); - - prevLocation = planetWithArtifactLoc; - } - - const artifactTypeSet = new Set(); - - for (let i = 0; i < artifacts.length; i++) { - artifactTypeSet.add(artifacts[i].artifactType); - } - - expect(artifactTypeSet.size).to.be.greaterThan(1); - }); it('should not mint an artifact on the same planet twice', async function () { await world.user1Core.prospectPlanet(ARTIFACT_PLANET_1.id); diff --git a/eth/test/DFGetter.test.ts b/eth/test/DFGetter.test.ts new file mode 100644 index 00000000..08d39965 --- /dev/null +++ b/eth/test/DFGetter.test.ts @@ -0,0 +1,35 @@ +import { ArtifactType } from '@dfdao/types'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { createArtifact, increaseBlockchainTime, makeInitArgs } from './utils/TestUtils'; +import { defaultWorldFixture, World } from './utils/TestWorld'; +import { SPAWN_PLANET_1, ZERO_PLANET } from './utils/WorldConstants'; + +describe('DarkForestGetter', function () { + let world: World; + + async function worldFixture() { + const world = await loadFixture(defaultWorldFixture); + const initArgs = makeInitArgs(SPAWN_PLANET_1); + await world.user1Core.initializePlayer(...initArgs); + await world.user1Core.giveSpaceShips(SPAWN_PLANET_1.id); + await increaseBlockchainTime(); + return world; + } + + beforeEach('load fixture', async function () { + world = await loadFixture(worldFixture); + }); + + describe('tokens', function () { + it('gets spaceship tokens', async function () { + expect((await world.contract.tokensByAccount(world.user1.address)).length).to.equal(5); + expect((await world.contract.getPlayerSpaceships(world.user1.address)).length).to.equal(5); + }); + it('gets artifact tokens', async function () { + await createArtifact(world.contract, world.user1.address, ZERO_PLANET, ArtifactType.Colossus); + expect((await world.contract.tokensByAccount(world.user1.address)).length).to.equal(6); + expect((await world.contract.getPlayerArtifacts(world.user1.address)).length).to.equal(1); + }); + }); +}); diff --git a/eth/test/DFLobby.test.ts b/eth/test/DFLobby.test.ts index 4648a370..1e315436 100644 --- a/eth/test/DFLobby.test.ts +++ b/eth/test/DFLobby.test.ts @@ -5,9 +5,9 @@ import hre, { ethers } from 'hardhat'; import { createArena, defaultWorldFixture, World } from './utils/TestWorld'; const _INTERFACE_ID_IERC165 = '0x01ffc9a7'; -const _INTERFACE_ID_IERC721 = '0x80ac58cd'; -const _INTERFACE_ID_IERC721METADATA = '0x5b5e139f'; -const _INTERFACE_ID_IERC721ENUMERABLE = '0x780e9d63'; +const _INTERFACE_ID_IERC1155 = '0xd9b67a26'; +const _INTERFACE_ID_IERC1155METADATA = '0x0e89341c'; +const _INTERFACE_ID_IERC1155ENUMERABLE = '0x464FCF40'; const _INTERFACE_ID_IDIAMOND_READABLE = '0x48e2b093'; const _INTERFACE_ID_IDIAMOND_WRITABLE = '0x1f931c1c'; const _INTERFACE_ID_IERC173 = '0x7f5828d0'; @@ -52,11 +52,12 @@ describe('DarkForestLobby', function () { expect(await lobby.supportsInterface(_INTERFACE_ID_IDIAMOND_WRITABLE)).to.equal(true); expect(await lobby.supportsInterface(_INTERFACE_ID_IERC173)).to.equal(true); }); - it('new Lobby has correct ERC721 interfaces', async function () { - expect(await lobby.supportsInterface(_INTERFACE_ID_IERC721)).to.equal(true); - expect(await lobby.supportsInterface(_INTERFACE_ID_IERC721METADATA)).to.equal(true); - expect(await lobby.supportsInterface(_INTERFACE_ID_IERC721ENUMERABLE)).to.equal(true); + it('new Lobby has correct ERC1155 interfaces', async function () { + expect(await lobby.supportsInterface(_INTERFACE_ID_IERC1155)).to.equal(true); + expect(await lobby.supportsInterface(_INTERFACE_ID_IERC1155METADATA)).to.equal(true); + expect(await lobby.supportsInterface(_INTERFACE_ID_IERC1155ENUMERABLE)).to.equal(true); }); + it('test fallback', async function () { expect(await lobby.getFallbackAddress()).to.equal(ethers.constants.AddressZero); }); diff --git a/eth/utils/diamond.ts b/eth/utils/diamond.ts index ce8d589b..80664ea5 100644 --- a/eth/utils/diamond.ts +++ b/eth/utils/diamond.ts @@ -41,7 +41,7 @@ export function toSignature(abiElement: unknown): string { const signaturesToIgnore = [ // The SolidState contracts adds a `supportsInterface` function, // but we already provide that function through DiamondLoupeFacet - ['DFArtifactFacet$', 'supportsInterface(bytes4)'], + ['DFTokenFacet$', 'supportsInterface(bytes4)'], ] as const; const eventSignatures = new Set(); From 20d8834b0e065ecfec5d47600ef7194bc6a4210d Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Tue, 4 Oct 2022 23:53:48 +0100 Subject: [PATCH 50/55] fix: bye bye spaceship zero moves (#35) --- eth/contracts/facets/DFMoveFacet.sol | 8 +----- eth/contracts/libraries/LibLazyUpdate.sol | 6 ++--- eth/test/DFMove.test.ts | 32 +++++++++++++++++++++-- eth/test/DFSpaceShips.test.ts | 25 ++++++++++++++++++ 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index 99d61166..ea0fae97 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -60,12 +60,6 @@ contract DFMoveFacet is WithStorage { sender: msg.sender }); - if (_isSpaceshipMove(args)) { - // If spaceships moves are not address(0) - // they can conquer planets with 0 energy - args.sender = address(0); - } - uint256 newPerlin = _input[2]; uint256 newRadius = _input[3]; @@ -446,7 +440,7 @@ contract DFMoveFacet is WithStorage { require(isSpaceship ? args.popMoved == 0 : true, "spaceship moves must be 0 energy moves"); gs().planetArrivals[gs().planetEventsCount] = ArrivalData({ id: gs().planetEventsCount, - player: args.player, // player address or address(0) for ship moves + player: args.player, // player address fromPlanet: args.oldLoc, toPlanet: args.newLoc, popArriving: popArriving, diff --git a/eth/contracts/libraries/LibLazyUpdate.sol b/eth/contracts/libraries/LibLazyUpdate.sol index 9ffa8809..ad6bbcdd 100644 --- a/eth/contracts/libraries/LibLazyUpdate.sol +++ b/eth/contracts/libraries/LibLazyUpdate.sol @@ -143,10 +143,10 @@ library LibLazyUpdate { // player /** - This is the zero address so that ships moving to an unowned planet with - no barbarians don't cause the planet to be conquered by the ship's controller. + If the move is a spaceship move, the planet owner does not change. + This prevents spaceship moves capturing planets with zero energy. */ - planet.owner = arrival.player == address(0) ? planet.owner : arrival.player; + planet.owner = arrival.carriedSpaceshipId != 0 ? planet.owner : arrival.player; planet.population = arrival.popArriving - ((planet.population * planet.defense) / 100); diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index a3a4850b..ca78fc04 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -955,7 +955,7 @@ describe('move rate limits', function () { expect(numShipsOnPlanet).to.be.eq(6); }); - it('when moving 6 ships to planet, should not allow an enemy attack', async function () { + it('when moving 6 own ships to planet, SHOULD allow an enemy attack', async function () { await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, LVL2_PLANET_SPACE); await increaseBlockchainTime(); @@ -973,8 +973,36 @@ describe('move rate limits', function () { ); } + await world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, LVL2_PLANET_SPACE, 1, 10000, 0)); + + await increaseBlockchainTime(); + await world.user1Core.refreshPlanet(LVL2_PLANET_SPACE.id); + + const numShipsOnPlanet = (await world.user1Core.getSpaceshipsOnPlanet(LVL2_PLANET_SPACE.id)) + .length; + + expect(numShipsOnPlanet).to.be.eq(6); + }); + it('when moving 6 enemy ships to planet, should not allow an enemy attack', async function () { + await conquerUnownedPlanet(world, world.user2Core, SPAWN_PLANET_2, LVL2_PLANET_SPACE); + await increaseBlockchainTime(); + + for (let i = 0; i < 6; i++) { + await world.contract.adminGiveSpaceShip( + SPAWN_PLANET_1.id, + world.user1.address, + SpaceshipType.ShipMothership + ); + + const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id))[0]; + + await world.user1Core.move( + ...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 10, 0, 0, ship.id) + ); + } + await expect( - world.user2Core.move(...makeMoveArgs(SPAWN_PLANET_2, LVL2_PLANET_SPACE, 1, 10000, 0)) + world.user1Core.move(...makeMoveArgs(SPAWN_PLANET_1, LVL2_PLANET_SPACE, 1, 10000, 0)) ).to.be.revertedWith('Planet is rate-limited'); await increaseBlockchainTime(); diff --git a/eth/test/DFSpaceShips.test.ts b/eth/test/DFSpaceShips.test.ts index 874f6d38..ae21d024 100644 --- a/eth/test/DFSpaceShips.test.ts +++ b/eth/test/DFSpaceShips.test.ts @@ -216,4 +216,29 @@ describe('DarkForestSpaceShips', function () { ); }); }); + describe('capturing unowned planet', async function () { + it('send ship but wont capture', async function () { + const mothership = await getSpaceshipOnPlanetByType( + world.contract, + SPAWN_PLANET_1.id, + SpaceshipType.ShipMothership + ); + await world.user1Core.move( + ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, mothership.id) + ); + await increaseBlockchainTime(); + await world.contract.refreshPlanet(LVL1_ASTEROID_1.id); + + // Ship move doesn't capture planet but does send Mothership to it. + expect((await world.contract.planets(LVL1_ASTEROID_1.id)).owner).to.not.equal( + world.user1.address + ); + const mothershipAfterMove = await getSpaceshipOnPlanetByType( + world.contract, + LVL1_ASTEROID_1.id, + SpaceshipType.ShipMothership + ); + expect(mothershipAfterMove.id).to.equal(mothership.id); + }); + }); }); From 72c355d4a0731f6f5d32debc50ce87b710056b17 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Tue, 4 Oct 2022 23:15:47 -0700 Subject: [PATCH 51/55] feat: ERC1155 clientside changes (#27) * client artifacts hacked to pieces * client: Implement ArtifactInfo and fix artifact decoding * client: Implement spaceship types and serde * client: Re-enable spaceships in arrival utils * client: Removing artifact map stuff * client: Re-enable hasGear check * client: Split spaceships from Artifacts in renderer * client: Render spaceships around planet * client: Add spaceship filename util * client: Split artifactImage and spaceshipImage * client: Fix mine artifact button without any ships * client: Remove some comment spaceship code * client: Remove canActivateArtifact because cooldowns were deleted * chore: Format code * client: Implement fetching wallet artifacts * client: Implement an initial spaceship download * client: Cache the artifacts & spaceships in player wallet * get the stupid fucking code out of the types package * client: Remove unused and unneeded function * client: cleanup share * client: Working to remove isSpaceShip throughout * client: Fix spaceship decoder * client: tokenType cleanup * client: More light cleanup * client: Split ArtifactsList & SpaceshipsList * client: Remove empty message from ArtifactsList * client: Allow sending spaceships via a SpaceshipRowg * client: Fix sending a spaceship * client: Cleanup ArtifactRow * client: Remove location from headers * client: Death to isSpaceShip * client: Fix some artifactId conversion logic * client: Begin getting ArtifactDetails working again * client: Fix wormhole rendering * client: Re-enable ArtifactDetailsPane & fetch artifact upgrades * client: Stop lying * client: Spaceship details & hover * client: Force vite to rebuild deps * client: Fix another ethers decoding problem * client: Remove spaceship code from ArtifactDetailsPane * client: More cleanup in ArtifactDetailsPane * client: Sync artifact inventory & fix some artifact action state * client: Put artifact & spaceship images in the PlanetDex * client: Note the shitty hover component * Update packages/procedural/src/ArtifactProcgen.ts * chore: Format code Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- client/embedded_plugins/Admin-Controls.ts | 2 - client/src/Backend/GameLogic/ArrivalUtils.ts | 52 +-- client/src/Backend/GameLogic/ContractsAPI.ts | 130 ++++---- client/src/Backend/GameLogic/GameManager.ts | 266 ++++++--------- client/src/Backend/GameLogic/GameObjects.ts | 307 ++++-------------- client/src/Backend/GameLogic/GameUIManager.ts | 79 +++-- .../GameLogic/InitialGameStateDownloader.tsx | 31 +- client/src/Backend/Storage/ReaderDataStore.ts | 15 +- .../src/Frontend/Components/ArtifactImage.tsx | 14 +- .../Components/Labels/SpaceshipLabels.tsx | 6 + .../Components/MineArtifactButton.tsx | 10 +- .../Frontend/Components/OpenPaneButtons.tsx | 16 +- .../Frontend/Components/SpaceshipImage.tsx | 35 ++ client/src/Frontend/Components/Text.tsx | 23 +- client/src/Frontend/Pages/ShareArtifact.tsx | 6 +- client/src/Frontend/Panes/ArtifactCard.tsx | 15 +- .../Frontend/Panes/ArtifactDetailsPane.tsx | 216 ++---------- .../src/Frontend/Panes/ArtifactHoverPane.tsx | 13 +- client/src/Frontend/Panes/ArtifactsList.tsx | 258 ++++----------- client/src/Frontend/Panes/HoverPane.tsx | 7 +- client/src/Frontend/Panes/InventoryPane.tsx | 82 +++++ .../ManagePlanetArtifacts/ArtifactActions.tsx | 110 ++++--- .../ManagePlanetArtifacts/ManageArtifacts.tsx | 99 ------ .../ManagePlanetArtifactsPane.tsx | 79 ----- .../ManagePlanetInventoryPane.tsx | 198 +++++++++++ .../src/Frontend/Panes/PlanetContextPane.tsx | 9 +- client/src/Frontend/Panes/PlanetDexPane.tsx | 22 +- .../Frontend/Panes/PlayerArtifactsPane.tsx | 47 --- client/src/Frontend/Panes/SpaceshipCard.tsx | 12 + .../Frontend/Panes/SpaceshipDetailsPane.tsx | 153 +++++++++ .../src/Frontend/Panes/SpaceshipHoverPane.tsx | 16 + client/src/Frontend/Panes/SpaceshipsList.tsx | 99 ++++++ client/src/Frontend/Utils/AppHooks.ts | 109 +++---- client/src/Frontend/Utils/EmitterUtils.ts | 8 - .../src/Frontend/Utils/ShortcutConstants.ts | 4 +- client/src/Frontend/Utils/UIEmitter.ts | 1 + client/src/Frontend/Views/ArtifactLink.tsx | 58 ---- client/src/Frontend/Views/ArtifactRow.tsx | 28 +- .../src/Frontend/Views/GameWindowLayout.tsx | 16 +- client/src/Frontend/Views/ModalIcon.tsx | 2 +- client/src/Frontend/Views/PlanetCard.tsx | 9 +- client/src/Frontend/Views/SendResources.tsx | 48 ++- client/src/Frontend/Views/SidebarPane.tsx | 14 +- client/src/Frontend/Views/SpaceshipRow.tsx | 124 +++++++ .../darkforest/api/ContractsAPITypes.ts | 5 +- client/vite.config.ts | 1 + eth/contracts/DFTypes.sol | 1 - packages/constants/src/index.ts | 7 +- packages/gamelogic/src/artifact.ts | 134 +------- packages/gamelogic/src/index.ts | 1 + packages/gamelogic/src/spaceship.ts | 18 + packages/procedural/src/ArtifactProcgen.ts | 93 +++++- .../src/Entities/PlanetRenderManager.ts | 58 +++- .../renderer/src/Entities/SpriteRenderer.ts | 158 ++++++++- .../renderer/src/Entities/VoyageRenderer.ts | 30 +- .../renderer/src/Entities/WormholeRenderer.ts | 4 +- packages/renderer/src/Renderer.ts | 10 +- packages/renderer/src/TextureManager.ts | 42 ++- packages/serde/src/arrival.ts | 10 +- packages/serde/src/artifact.ts | 81 +++-- packages/serde/src/index.ts | 1 + packages/serde/src/planet.ts | 17 +- packages/serde/src/spaceship.ts | 101 ++++++ packages/serde/src/upgrade.ts | 2 +- packages/types/src/arrival.ts | 7 +- packages/types/src/artifact.ts | 104 +----- packages/types/src/identifier.ts | 8 + packages/types/src/modal.ts | 2 +- packages/types/src/planet.ts | 11 +- packages/types/src/renderer.ts | 18 + packages/types/src/spaceship.ts | 39 ++- packages/types/src/transactions.ts | 5 +- 72 files changed, 2003 insertions(+), 1813 deletions(-) create mode 100644 client/src/Frontend/Components/Labels/SpaceshipLabels.tsx create mode 100644 client/src/Frontend/Components/SpaceshipImage.tsx create mode 100644 client/src/Frontend/Panes/InventoryPane.tsx delete mode 100644 client/src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx delete mode 100644 client/src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane.tsx create mode 100644 client/src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetInventoryPane.tsx delete mode 100644 client/src/Frontend/Panes/PlayerArtifactsPane.tsx create mode 100644 client/src/Frontend/Panes/SpaceshipCard.tsx create mode 100644 client/src/Frontend/Panes/SpaceshipDetailsPane.tsx create mode 100644 client/src/Frontend/Panes/SpaceshipHoverPane.tsx create mode 100644 client/src/Frontend/Panes/SpaceshipsList.tsx delete mode 100644 client/src/Frontend/Views/ArtifactLink.tsx create mode 100644 client/src/Frontend/Views/SpaceshipRow.tsx create mode 100644 packages/gamelogic/src/spaceship.ts create mode 100644 packages/serde/src/spaceship.ts diff --git a/client/embedded_plugins/Admin-Controls.ts b/client/embedded_plugins/Admin-Controls.ts index 89b2279f..a3d648cf 100644 --- a/client/embedded_plugins/Admin-Controls.ts +++ b/client/embedded_plugins/Admin-Controls.ts @@ -14,7 +14,6 @@ import { import { getPlanetNameHash } from 'https://cdn.skypack.dev/@dfdao/procedural'; import { locationIdToDecStr, - artifactIdFromHexStr, locationIdFromDecStr, //@ts-ignore } from 'https://cdn.skypack.dev/@dfdao/serde'; @@ -80,7 +79,6 @@ async function createArtifact( methodName: 'adminGiveArtifact', }); tx.confirmedPromise.then(() => { - df.hardRefreshArtifact(artifactIdFromHexStr(tokenId.slice(2))); df.hardRefreshPlanet(planet.locationId); }); diff --git a/client/src/Backend/GameLogic/ArrivalUtils.ts b/client/src/Backend/GameLogic/ArrivalUtils.ts index e9e0894b..ca78893b 100644 --- a/client/src/Backend/GameLogic/ArrivalUtils.ts +++ b/client/src/Backend/GameLogic/ArrivalUtils.ts @@ -1,14 +1,14 @@ import { CONTRACT_PRECISION } from '@dfdao/constants'; -import { hasOwner, isActivated, isEmojiFlagMessage } from '@dfdao/gamelogic'; +import { hasOwner, isEmojiFlagMessage } from '@dfdao/gamelogic'; import { ArrivalType, - Artifact, ArtifactType, EmojiFlagBody, Planet, PlanetMessage, PlanetType, QueuedArrival, + SpaceshipType, Upgrade, } from '@dfdao/types'; import _ from 'lodash'; @@ -82,7 +82,6 @@ const getEnergyAtTime = (planet: Planet, atTimeMillis: number): number => { export const updatePlanetToTime = ( planet: Planet, - planetArtifacts: Artifact[], atTimeMillis: number, contractConstants: ContractConstants, setPlanet: (p: Planet) => void = () => {} @@ -99,16 +98,25 @@ export const updatePlanetToTime = ( planet.lastUpdated = atTimeMillis / 1000; const photoidActivationTime = contractConstants.PHOTOID_ACTIVATION_DELAY * 1000; - const activePhotoid = planetArtifacts.find( - (a) => - a.artifactType === ArtifactType.PhotoidCannon && - isActivated(a) && - atTimeMillis - a.lastActivated * 1000 >= photoidActivationTime - ); - - if (activePhotoid && !planet.localPhotoidUpgrade) { - planet.localPhotoidUpgrade = activePhotoid.timeDelayedUpgrade; - applyUpgrade(planet, activePhotoid.timeDelayedUpgrade); + if (planet.activeArtifact) { + const activePhotoid = + planet.activeArtifact.artifactType === ArtifactType.PhotoidCannon && + atTimeMillis - planet.artifactActivationTime * 1000 >= photoidActivationTime; + + if (activePhotoid && !planet.localPhotoidUpgrade) { + // TODO: pre-load from contract? + const range = [100, 200, 200, 200, 200, 200]; + const speedBoosts = [100, 500, 1000, 1500, 2000, 2500]; + const timeDelayedUpgrade: Upgrade = { + energyCapMultiplier: 100, + energyGroMultiplier: 100, + rangeMultiplier: range[planet.activeArtifact.rarity], + speedMultiplier: speedBoosts[planet.activeArtifact.rarity], + defMultiplier: 100, + }; + planet.localPhotoidUpgrade = timeDelayedUpgrade; + applyUpgrade(planet, timeDelayedUpgrade); + } } setPlanet(planet); @@ -143,9 +151,7 @@ export interface PlanetDiff { export const arrive = ( toPlanet: Planet, - artifactsOnPlanet: Artifact[], arrival: QueuedArrival, - arrivingArtifact: Artifact | undefined, contractConstants: ContractConstants ): PlanetDiff => { // this function optimistically simulates an arrival @@ -154,7 +160,7 @@ export const arrive = ( } // update toPlanet energy and silver right before arrival - updatePlanetToTime(toPlanet, artifactsOnPlanet, arrival.arrivalTime * 1000, contractConstants); + updatePlanetToTime(toPlanet, arrival.arrivalTime * 1000, contractConstants); const prevPlanet = _.cloneDeep(toPlanet); if (toPlanet.destroyed) { @@ -206,25 +212,25 @@ export const arrive = ( } // transfer artifact if necessary - if (arrival.artifactId) { - toPlanet.heldArtifactIds.push(arrival.artifactId); + if (arrival.artifact) { + toPlanet.artifacts.push(arrival.artifact); } - if (arrivingArtifact) { - if (arrivingArtifact.artifactType === ArtifactType.ShipMothership) { + if (arrival.spaceship) { + toPlanet.spaceships.push(arrival.spaceship); + if (arrival.spaceship.spaceshipType === SpaceshipType.ShipMothership) { if (toPlanet.energyGroDoublers === 0) { toPlanet.energyGrowth *= 2; } toPlanet.energyGroDoublers++; - } else if (arrivingArtifact.artifactType === ArtifactType.ShipWhale) { + } else if (arrival.spaceship.spaceshipType === SpaceshipType.ShipWhale) { if (toPlanet.silverGroDoublers === 0) { toPlanet.silverGrowth *= 2; } toPlanet.silverGroDoublers++; - } else if (arrivingArtifact.artifactType === ArtifactType.ShipTitan) { + } else if (arrival.spaceship.spaceshipType === SpaceshipType.ShipTitan) { toPlanet.pausers++; } - arrivingArtifact.onPlanetId = toPlanet.locationId; } return { arrival, current: toPlanet, previous: prevPlanet }; diff --git a/client/src/Backend/GameLogic/ContractsAPI.ts b/client/src/Backend/GameLogic/ContractsAPI.ts index 69e692f0..892d936f 100644 --- a/client/src/Backend/GameLogic/ContractsAPI.ts +++ b/client/src/Backend/GameLogic/ContractsAPI.ts @@ -10,7 +10,6 @@ import { } from '@dfdao/network'; import { address, - artifactIdFromEthersBN, artifactIdToDecStr, decodeArrival, decodeArtifact, @@ -20,7 +19,11 @@ import { decodePlanetTypeWeights, decodePlayer, decodeRevealedCoords, + decodeSpaceship, + decodeUpgrade, decodeUpgradeBranches, + isArtifact, + isSpaceship, locationIdFromEthersBN, locationIdToDecStr, } from '@dfdao/serde'; @@ -37,9 +40,11 @@ import { QueuedArrival, RevealedCoords, Setting, + Spaceship, Transaction, TransactionId, TxIntent, + Upgrade, VoyageId, } from '@dfdao/types'; import { BigNumber as EthersBN, ContractFunction, Event, providers } from 'ethers'; @@ -243,26 +248,42 @@ export class ContractsAPI extends EventEmitter { rawArtifactId: EthersBN, loc: EthersBN ) => { - const artifactId = artifactIdFromEthersBN(rawArtifactId); - this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); + this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); + }, + [ContractEvent.SpaceshipFound]: ( + playerAddr: string, + rawSpaceshipId: EthersBN, + loc: EthersBN + ) => { + this.emit( + ContractsAPIEvent.SpaceshipFound, + address(playerAddr), + decodeSpaceship(rawSpaceshipId) + ); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.ArtifactDeposited]: ( - _playerAddr: string, + playerAddr: string, rawArtifactId: EthersBN, loc: EthersBN ) => { - const artifactId = artifactIdFromEthersBN(rawArtifactId); - this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); + this.emit( + ContractsAPIEvent.ArtifactDeposited, + address(playerAddr), + decodeArtifact(rawArtifactId) + ); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.ArtifactWithdrawn]: ( - _playerAddr: string, + playerAddr: string, rawArtifactId: EthersBN, loc: EthersBN ) => { - const artifactId = artifactIdFromEthersBN(rawArtifactId); - this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); + this.emit( + ContractsAPIEvent.ArtifactWithdrawn, + address(playerAddr), + decodeArtifact(rawArtifactId) + ); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.ArtifactActivated]: ( @@ -270,8 +291,6 @@ export class ContractsAPI extends EventEmitter { rawArtifactId: EthersBN, loc: EthersBN ) => { - const artifactId = artifactIdFromEthersBN(rawArtifactId); - this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.ArtifactDeactivated]: ( @@ -279,8 +298,6 @@ export class ContractsAPI extends EventEmitter { rawArtifactId: EthersBN, loc: EthersBN ) => { - const artifactId = artifactIdFromEthersBN(rawArtifactId); - this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId); this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc)); }, [ContractEvent.PlayerInitialized]: async (player: string, locRaw: EthersBN, _: Event) => { @@ -796,69 +813,46 @@ export class ContractsAPI extends EventEmitter { return decodePlanet(decStrId, rawPlanet); } - public async getArtifactById(artifactId: ArtifactId): Promise { - const exists = await this.makeCall(this.contract.doesArtifactExist, [ - artifactIdToDecStr(artifactId), - ]); - if (!exists) return undefined; - const rawArtifact = await this.makeCall(this.contract.getArtifactById, [ - artifactIdToDecStr(artifactId), - ]); - - const artifact = decodeArtifact(rawArtifact); - artifact.transactions = new TxCollection(); - return artifact; - } - - public async bulkGetArtifactsOnPlanets( - locationIds: LocationId[], - onProgress?: (fractionCompleted: number) => void - ): Promise { - const rawArtifacts = await aggregateBulkGetter( - locationIds.length, - 200, - async (start, end) => - await this.makeCall(this.contract.bulkGetPlanetArtifacts, [ - locationIds.slice(start, end).map(locationIdToDecStr), - ]), - onProgress - ); - - return rawArtifacts.map((rawArtifactArray) => { - return rawArtifactArray.map(decodeArtifact); - }); - } - - public async bulkGetArtifacts( - artifactIds: ArtifactId[], - onProgress?: (fractionCompleted: number) => void + public async getPlayerArtifacts( + playerId?: EthAddress, + onProgress?: (percent: number) => void ): Promise { - const rawArtifacts = await aggregateBulkGetter( - artifactIds.length, - 200, - async (start, end) => - await this.makeCall(this.contract.bulkGetArtifactsByIds, [ - artifactIds.slice(start, end).map(artifactIdToDecStr), - ]), - onProgress - ); - - const ret: Artifact[] = rawArtifacts.map(decodeArtifact); - ret.forEach((a) => (a.transactions = new TxCollection())); + if (playerId === undefined) return []; - return ret; + const tokenIds = await this.makeCall(this.contract.tokensByAccount, [playerId]); + if (onProgress) { + onProgress(0.95); + } + const artifacts = tokenIds.filter(isArtifact).map(decodeArtifact); + if (onProgress) { + onProgress(1); + } + return artifacts; } - public async getPlayerArtifacts( + public async getPlayerSpaceships( playerId?: EthAddress, onProgress?: (percent: number) => void - ): Promise { + ): Promise { if (playerId === undefined) return []; - const myArtifactIds = (await this.makeCall(this.contract.getPlayerArtifactIds, [playerId])).map( - artifactIdFromEthersBN - ); - return this.bulkGetArtifacts(myArtifactIds, onProgress); + const tokenIds = await this.makeCall(this.contract.tokensByAccount, [playerId]); + if (onProgress) { + onProgress(0.95); + } + const spaceships = tokenIds.filter(isSpaceship).map(decodeSpaceship); + if (onProgress) { + onProgress(1); + } + return spaceships; + } + + public async getUpgradeForArtifact(artifactId: ArtifactId): Promise { + const rawUpgrade = await this.makeCall(this.contract.getUpgradeForArtifact, [ + artifactIdToDecStr(artifactId), + ]); + + return decodeUpgrade(rawUpgrade); } public setDiagnosticUpdater(diagnosticUpdater?: DiagnosticUpdater) { diff --git a/client/src/Backend/GameLogic/GameManager.ts b/client/src/Backend/GameLogic/GameManager.ts index 7534ecdb..d336b981 100644 --- a/client/src/Backend/GameLogic/GameManager.ts +++ b/client/src/Backend/GameLogic/GameManager.ts @@ -6,13 +6,7 @@ import { } from '@dfdao/constants'; import type { DarkForest } from '@dfdao/contracts/typechain'; import { monomitter, Monomitter, Subscription } from '@dfdao/events'; -import { - getRange, - isActivated, - isLocatable, - isSpaceShip, - timeUntilNextBroadcastAvailable, -} from '@dfdao/gamelogic'; +import { getRange, isLocatable, timeUntilNextBroadcastAvailable } from '@dfdao/gamelogic'; import { fakeHash, mimcHash, perlin } from '@dfdao/hashing'; import { createContract, @@ -24,6 +18,8 @@ import { import { getPlanetName } from '@dfdao/procedural'; import { artifactIdToDecStr, + artifactIdToEthersBN, + decodeArtifact, isUnconfirmedActivateArtifactTx, isUnconfirmedBuyHatTx, isUnconfirmedCapturePlanetTx, @@ -40,6 +36,7 @@ import { isUnconfirmedWithdrawSilverTx, locationIdFromBigInt, locationIdToDecStr, + spaceshipIdToDecStr, } from '@dfdao/serde'; import { Artifact, @@ -67,6 +64,8 @@ import { RevealedLocation, Setting, SignedMessage, + Spaceship, + SpaceshipId, SpaceType, Transaction, TxIntent, @@ -90,7 +89,6 @@ import { VoyageId, WorldCoords, WorldLocation, - Wormhole, } from '@dfdao/types'; import bigInt, { BigInteger } from 'big-integer'; import delay from 'delay'; @@ -152,7 +150,6 @@ export enum GameManagerEvent { DiscoveredNewChunk = 'DiscoveredNewChunk', InitializedPlayer = 'InitializedPlayer', InitializedPlayerError = 'InitializedPlayerError', - ArtifactUpdate = 'ArtifactUpdate', Moved = 'Moved', } @@ -375,9 +372,10 @@ class GameManager extends EventEmitter { snarkHelper: SnarkArgsHelper, homeLocation: WorldLocation | undefined, useMockHash: boolean, - artifacts: Map, ethConnection: EthConnection, - paused: boolean + paused: boolean, + myArtifacts: Map, + mySpaceships: Map ) { super(); @@ -462,18 +460,20 @@ class GameManager extends EventEmitter { } } + // TODO: Untangle this mess this.entityStore = new GameObjects( account, touchedPlanets, allTouchedPlanetIds, revealedLocations, claimedLocations, - artifacts, persistentChunkStore.allChunks(), unprocessedArrivals, unprocessedPlanetArrivalIds, contractConstants, - worldRadius + worldRadius, + myArtifacts, + mySpaceships ); this.contractsAPI = contractsAPI; @@ -613,28 +613,16 @@ class GameManager extends EventEmitter { await otherStore.saveTouchedPlanetIds(initialState.allTouchedPlanetIds); await otherStore.saveRevealedCoords(initialState.allRevealedCoords); - const knownArtifacts: Map = new Map(); - - for (let i = 0; i < initialState.loadedPlanets.length; i++) { - const planet = initialState.touchedAndLocatedPlanets.get(initialState.loadedPlanets[i]); - - if (!planet) { - continue; - } - - planet.heldArtifactIds = initialState.heldArtifacts[i].map((a) => a.id); - - for (const heldArtifact of initialState.heldArtifacts[i]) { - knownArtifacts.set(heldArtifact.id, heldArtifact); - } - } + const myArtifacts: Map = new Map(); for (const myArtifact of initialState.myArtifacts) { - knownArtifacts.set(myArtifact.id, myArtifact); + myArtifacts.set(myArtifact.id, myArtifact); } - for (const artifact of initialState.artifactsOnVoyages) { - knownArtifacts.set(artifact.id, artifact); + const mySpaceships: Map = new Map(); + + for (const mySpaceship of initialState.mySpaceships) { + mySpaceships.set(mySpaceship.id, mySpaceship); } // figure out what's my home planet @@ -680,9 +668,10 @@ class GameManager extends EventEmitter { snarkHelper, homeLocation, useMockHash, - knownArtifacts, connection, - initialState.paused + initialState.paused, + myArtifacts, + mySpaceships ); gameManager.setPlayerTwitters(initialState.twitters); @@ -708,9 +697,20 @@ class GameManager extends EventEmitter { // set up listeners: whenever ContractsAPI reports some game state update, do some logic gameManager.contractsAPI - .on(ContractsAPIEvent.ArtifactUpdate, async (artifactId: ArtifactId) => { - await gameManager.hardRefreshArtifact(artifactId); - gameManager.emit(GameManagerEvent.ArtifactUpdate, artifactId); + .on(ContractsAPIEvent.ArtifactWithdrawn, (owner: EthAddress, artifact: Artifact) => { + if (owner === account) { + gameManager.entityStore.addMyArtifact(artifact); + } + }) + .on(ContractsAPIEvent.ArtifactDeposited, (owner: EthAddress, artifact: Artifact) => { + if (owner === account) { + gameManager.entityStore.removeMyArtifact(artifact); + } + }) + .on(ContractsAPIEvent.SpaceshipFound, (owner: EthAddress, spaceship: Spaceship) => { + if (owner === account) { + gameManager.entityStore.addMySpaceship(spaceship); + } }) .on( ContractsAPIEvent.PlanetTransferred, @@ -788,11 +788,7 @@ class GameManager extends EventEmitter { // mining manager should be initialized already via joinGame, but just in case... gameManager.initMiningManager(tx.intent.location.coords, 4); } else if (isUnconfirmedMoveTx(tx)) { - const promises = [gameManager.bulkHardRefreshPlanets([tx.intent.from, tx.intent.to])]; - if (tx.intent.artifact) { - promises.push(gameManager.hardRefreshArtifact(tx.intent.artifact)); - } - await Promise.all(promises); + await Promise.all([gameManager.bulkHardRefreshPlanets([tx.intent.from, tx.intent.to])]); } else if (isUnconfirmedUpgradeTx(tx)) { await gameManager.hardRefreshPlanet(tx.intent.locationId); } else if (isUnconfirmedBuyHatTx(tx)) { @@ -802,27 +798,15 @@ class GameManager extends EventEmitter { } else if (isUnconfirmedFindArtifactTx(tx)) { await gameManager.hardRefreshPlanet(tx.intent.planetId); } else if (isUnconfirmedDepositArtifactTx(tx)) { - await Promise.all([ - gameManager.hardRefreshPlanet(tx.intent.locationId), - gameManager.hardRefreshArtifact(tx.intent.artifactId), - ]); + await Promise.all([gameManager.hardRefreshPlanet(tx.intent.locationId)]); } else if (isUnconfirmedWithdrawArtifactTx(tx)) { - await Promise.all([ - await gameManager.hardRefreshPlanet(tx.intent.locationId), - await gameManager.hardRefreshArtifact(tx.intent.artifactId), - ]); + await Promise.all([await gameManager.hardRefreshPlanet(tx.intent.locationId)]); } else if (isUnconfirmedProspectPlanetTx(tx)) { await gameManager.softRefreshPlanet(tx.intent.planetId); } else if (isUnconfirmedActivateArtifactTx(tx)) { - await Promise.all([ - gameManager.hardRefreshPlanet(tx.intent.locationId), - gameManager.hardRefreshArtifact(tx.intent.artifactId), - ]); + await Promise.all([gameManager.hardRefreshPlanet(tx.intent.locationId)]); } else if (isUnconfirmedDeactivateArtifactTx(tx)) { - await Promise.all([ - gameManager.hardRefreshPlanet(tx.intent.locationId), - gameManager.hardRefreshArtifact(tx.intent.artifactId), - ]); + await Promise.all([gameManager.hardRefreshPlanet(tx.intent.locationId)]); } else if (isUnconfirmedWithdrawSilverTx(tx)) { await gameManager.softRefreshPlanet(tx.intent.locationId); } else if (isUnconfirmedCapturePlanetTx(tx)) { @@ -905,8 +889,6 @@ class GameManager extends EventEmitter { const planet = await this.contractsAPI.getPlanetById(planetId); if (!planet) return; const arrivals = await this.contractsAPI.getArrivalsForPlanet(planetId); - const artifactsOnPlanets = await this.contractsAPI.bulkGetArtifactsOnPlanets([planetId]); - const artifactsOnPlanet = artifactsOnPlanets[0]; const revealedCoords = await this.contractsAPI.getRevealedCoordsByIdIfExists(planetId); let revealedLocation: RevealedLocation | undefined; @@ -922,16 +904,9 @@ class GameManager extends EventEmitter { this.entityStore.replacePlanetFromContractData( planet, arrivals, - artifactsOnPlanet.map((a) => a.id), revealedLocation, claimedCoords?.revealer ); - - // it's important that we reload the artifacts that are on the planet after the move - // completes because this move could have been a photoid canon move. one of the side - // effects of this type of move is that the active photoid canon deactivates upon a move - // meaning we need to reload its data from the blockchain. - artifactsOnPlanet.forEach((a) => this.entityStore.replaceArtifactFromContractData(a)); } private async bulkHardRefreshPlanets(planetIds: LocationId[]): Promise { @@ -939,7 +914,6 @@ class GameManager extends EventEmitter { const allVoyages = await this.contractsAPI.getAllArrivals(planetIds); const planetsToUpdateMap = await this.contractsAPI.bulkGetPlanets(planetIds); - const artifactsOnPlanets = await this.contractsAPI.bulkGetArtifactsOnPlanets(planetIds); planetsToUpdateMap.forEach((planet, locId) => { if (planetsToUpdateMap.has(locId)) { planetVoyageMap.set(locId, []); @@ -965,23 +939,9 @@ class GameManager extends EventEmitter { const voyagesForPlanet = planetVoyageMap.get(planet.locationId); if (voyagesForPlanet) { - this.entityStore.replacePlanetFromContractData( - planet, - voyagesForPlanet, - artifactsOnPlanets[i].map((a) => a.id) - ); + this.entityStore.replacePlanetFromContractData(planet, voyagesForPlanet); } } - - for (const artifacts of artifactsOnPlanets) { - this.entityStore.replaceArtifactsFromContractData(artifacts); - } - } - - public async hardRefreshArtifact(artifactId: ArtifactId): Promise { - const artifact = await this.contractsAPI.getArtifactById(artifactId); - if (!artifact) return; - this.entityStore.replaceArtifactFromContractData(artifact); } private onTxSubmit(tx: Transaction): void { @@ -1356,18 +1316,20 @@ class GameManager extends EventEmitter { } /** - * gets both deposited artifacts that are on planets i own as well as artifacts i own + * Gets the artifacts in the players wallet */ getMyArtifacts(): Artifact[] { if (!this.account) return []; - const ownedByMe = this.entityStore.getArtifactsOwnedBy(this.account); - const onPlanetsOwnedByMe = this.entityStore - .getArtifactsOnPlanetsOwnedBy(this.account) - // filter out space ships because they always show up - // in the `ownedByMe` array. - .filter((a) => !isSpaceShip(a.artifactType)); - return [...ownedByMe, ...onPlanetsOwnedByMe]; + return Array.from(this.entityStore.getMyArtifactMap().values()); + } + /** + * Gets the spaceships in the players wallet + */ + getMySpaceships(): Spaceship[] { + if (!this.account) return []; + + return Array.from(this.entityStore.getMySpaceshipMap().values()); } /** @@ -1411,21 +1373,6 @@ class GameManager extends EventEmitter { return player?.score; } - /** - * Gets the artifact with the given id. Null if no artifact with id exists. - */ - getArtifactWithId(artifactId?: ArtifactId): Artifact | undefined { - return this.entityStore.getArtifactById(artifactId); - } - - /** - * Gets the artifacts with the given ids, including ones we know exist but haven't been loaded, - * represented by `undefined`. - */ - getArtifactsWithIds(artifactIds: ArtifactId[] = []): Array { - return artifactIds.map((id) => this.getArtifactWithId(id)); - } - /** * Gets the level of the given planet. Returns undefined if the planet does not exist. Does * NOT update the planet if the planet is stale, which means this function is fast. @@ -2286,20 +2233,7 @@ class GameManager extends EventEmitter { const tx = await this.contractsAPI.submitTransaction(txIntent); - tx.confirmedPromise - .then(() => { - return this.waitForPlanet(planet.locationId, ({ current }: Diff) => { - return current.heldArtifactIds - .map(this.getArtifactWithId.bind(this)) - .find((a: Artifact) => a?.planetDiscoveredOn === planet.locationId) as Artifact; - }).then((foundArtifact) => { - if (!foundArtifact) throw new Error('Artifact not found?'); - const notifManager = NotificationManager.getInstance(); - - notifManager.artifactFound(planet as LocatablePlanet, foundArtifact); - }); - }) - .catch(console.log); + // TODO: Found notification (through events?) return tx; } catch (e) { @@ -2340,9 +2274,12 @@ class GameManager extends EventEmitter { const tx = await this.contractsAPI.submitTransaction(txIntent); - tx.confirmedPromise.then(() => - this.getGameObjects().updateArtifact(artifactId, (a) => (a.onPlanetId = locationId)) - ); + tx.confirmedPromise.then(() => { + this.getGameObjects().updatePlanet(locationId, (planet) => { + const artifact = decodeArtifact(artifactIdToEthersBN(artifactId)); + planet.artifacts.push(artifact); + }); + }); return tx; } catch (e) { @@ -2394,7 +2331,9 @@ class GameManager extends EventEmitter { const tx = await this.contractsAPI.submitTransaction(txIntent); tx.confirmedPromise.then(() => - this.getGameObjects().updateArtifact(artifactId, (a) => (a.onPlanetId = undefined)) + this.getGameObjects().updatePlanet(locationId, (planet) => { + planet.artifacts = planet.artifacts.filter(({ id }) => id !== artifactId); + }) ); return tx; @@ -2701,6 +2640,7 @@ class GameManager extends EventEmitter { forces: number, silver: number, artifactMoved?: ArtifactId, + spaceshipMoved?: SpaceshipId, abandoning = false, bypassChecks = false ): Promise> { @@ -2748,11 +2688,29 @@ class GameManager extends EventEmitter { if ( ((!bypassChecks && !this.account) || !oldPlanet || oldPlanet.owner !== this.account) && - !isSpaceShip(this.getArtifactWithId(artifactMoved)?.artifactType) + !spaceshipMoved ) { throw new Error('attempted to move from a planet not owned by player'); } + if (artifactMoved) { + if (!bypassChecks) { + if (oldPlanet?.activeArtifact?.id === artifactMoved) { + throw new Error("can't move an activated artifact"); + } + if (!oldPlanet?.artifacts?.find(({ id }) => id === artifactMoved)) { + throw new Error("that artifact isn't on this planet!"); + } + } + } + if (spaceshipMoved) { + if (!bypassChecks) { + if (!oldPlanet?.spaceships?.find(({ id }) => id === spaceshipMoved)) { + throw new Error("that spaceship isn't on this planet!"); + } + } + } + const getArgs = async (): Promise => { const snarkArgs = await this.snarkHelper.getMoveArgs( oldX, @@ -2784,6 +2742,9 @@ class GameManager extends EventEmitter { if (artifactMoved) { args[6] = artifactIdToDecStr(artifactMoved); } + if (spaceshipMoved) { + args[6] = spaceshipIdToDecStr(spaceshipMoved); + } return args; }; @@ -2796,26 +2757,11 @@ class GameManager extends EventEmitter { to: newLocation.hash, forces: shipsMoved, silver: silverMoved, - artifact: artifactMoved, + // TODO: Make this less shit + artifact: artifactMoved || spaceshipMoved, abandoning, }; - if (artifactMoved) { - const artifact = this.entityStore.getArtifactById(artifactMoved); - - if (!bypassChecks) { - if (!artifact) { - throw new Error("couldn't find this artifact"); - } - if (isActivated(artifact)) { - throw new Error("can't move an activated artifact"); - } - if (!oldPlanet?.heldArtifactIds?.includes(artifactMoved)) { - throw new Error("that artifact isn't on this planet!"); - } - } - } - // Always await the submitTransaction so we can catch rejections const tx = await this.contractsAPI.submitTransaction(txIntent); @@ -3167,16 +3113,6 @@ class GameManager extends EventEmitter { return ret; } - /** - * Gets the active artifact on this planet, if one exists. - */ - getActiveArtifact(planet: Planet): Artifact | undefined { - const artifacts = this.getArtifactsWithIds(planet.heldArtifactIds); - const active = artifacts.find((a) => a && isActivated(a)); - - return active; - } - /** * If there's an active artifact on either of these planets which happens to be a wormhole which * is active and targetting the other planet, return the wormhole boost which is greater. Values @@ -3186,21 +3122,21 @@ class GameManager extends EventEmitter { fromPlanet: Planet, toPlanet: Planet ): { distanceFactor: number; speedFactor: number } | undefined { - const fromActiveArtifact = this.getActiveArtifact(fromPlanet); - const toActiveArtifact = this.getActiveArtifact(toPlanet); + const fromActiveArtifact = fromPlanet.activeArtifact; + const toActiveArtifact = toPlanet.activeArtifact; let greaterRarity: ArtifactRarity | undefined; if ( fromActiveArtifact?.artifactType === ArtifactType.Wormhole && - fromActiveArtifact.wormholeTo === toPlanet.locationId + fromPlanet.wormholeTo === toPlanet.locationId ) { greaterRarity = fromActiveArtifact.rarity; } if ( toActiveArtifact?.artifactType === ArtifactType.Wormhole && - toActiveArtifact.wormholeTo === fromPlanet.locationId + toPlanet.wormholeTo === fromPlanet.locationId ) { if (greaterRarity === undefined) { greaterRarity = toActiveArtifact.rarity; @@ -3331,7 +3267,7 @@ class GameManager extends EventEmitter { return NotificationManager.getInstance(); } - getWormholes(): Iterable { + getWormholes(): Iterable<[LocationId, LocationId]> { return this.entityStore.getWormholes(); } @@ -3340,11 +3276,6 @@ class GameManager extends EventEmitter { return this.entityStore.getPlanetMap(); } - /** Return a reference to the artifact map */ - public getArtifactMap(): Map { - return this.entityStore.getArtifactMap(); - } - /** Return a reference to the map of my planets */ public getMyPlanetMap(): Map { return this.entityStore.getMyPlanetMap(); @@ -3359,17 +3290,16 @@ class GameManager extends EventEmitter { return this.entityStore.planetUpdated$; } - public getArtifactUpdated$(): Monomitter { - return this.entityStore.artifactUpdated$; - } - public getMyPlanetsUpdated$(): Monomitter> { return this.entityStore.myPlanetsUpdated$; } - public getMyArtifactsUpdated$(): Monomitter> { + public getMyArtifactsUpdated$(): Monomitter<[ArtifactId, Artifact | undefined]> { return this.entityStore.myArtifactsUpdated$; } + public getMySpaceshipsUpdated$(): Monomitter<[SpaceshipId, Spaceship | undefined]> { + return this.entityStore.mySpaceshipsUpdated$; + } /** * Returns an instance of a `Contract` from the ethersjs library. This is the library we use to @@ -3523,6 +3453,10 @@ class GameManager extends EventEmitter { public getPaused$(): Monomitter { return this.paused$; } + + public getUpgradeForArtifact(artifactId: ArtifactId) { + return this.contractsAPI.getUpgradeForArtifact(artifactId); + } } export default GameManager; diff --git a/client/src/Backend/GameLogic/GameObjects.ts b/client/src/Backend/GameLogic/GameObjects.ts index 81d9d591..5a48c613 100644 --- a/client/src/Backend/GameLogic/GameObjects.ts +++ b/client/src/Backend/GameLogic/GameObjects.ts @@ -1,6 +1,6 @@ import { EMPTY_ADDRESS, MAX_PLANET_LEVEL, MIN_PLANET_LEVEL } from '@dfdao/constants'; import { Monomitter, monomitter } from '@dfdao/events'; -import { hasOwner, isActivated, isLocatable } from '@dfdao/gamelogic'; +import { hasOwner, isLocatable } from '@dfdao/gamelogic'; import { bonusFromHex, getBytesFromHex } from '@dfdao/hexgen'; import { TxCollection } from '@dfdao/network'; import { @@ -37,7 +37,6 @@ import { ArrivalWithTimer, Artifact, ArtifactId, - ArtifactType, Biome, Chunk, ClaimedLocation, @@ -50,26 +49,21 @@ import { QueuedArrival, Radii, RevealedLocation, + Spaceship, + SpaceshipId, SpaceType, Transaction, TransactionCollection, VoyageId, WorldCoords, WorldLocation, - Wormhole, } from '@dfdao/types'; import autoBind from 'auto-bind'; import bigInt from 'big-integer'; import { ethers } from 'ethers'; import _ from 'lodash'; import NotificationManager from '../../Frontend/Game/NotificationManager'; -import { - getArtifactId, - getArtifactOwner, - getPlanetId, - getPlanetOwner, - setObjectSyncState, -} from '../../Frontend/Utils/EmitterUtils'; +import { getPlanetId, getPlanetOwner, setObjectSyncState } from '../../Frontend/Utils/EmitterUtils'; import { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes'; import { arrive, PlanetDiff, updatePlanetToTime } from './ArrivalUtils'; import { LayeredMap } from './LayeredMap'; @@ -110,7 +104,7 @@ export class GameObjects { * * @todo extract the pattern we're using for the field tuples * - {planets, myPlanets, myPlanetsUpdated, planetUpdated$} - * - {artifacts, myArtifacts, myArtifactsUpdated, artifactUpdated$} + * - {artifacts, myArtifacts, myArtifactsUpdated} * * into some sort of class. */ @@ -124,23 +118,22 @@ export class GameObjects { private readonly myPlanets: Map; /** - * Cached index of all known artifact data. + * Cached index of artifacts in a players wallet. * * @see The same warning applys as the one on {@link GameObjects.planets} */ - private readonly artifacts: Map; - + private readonly myArtifacts: Map; /** - * Cached index of artifacts owned by the player. + * Cached index of spaceships in a players wallet. * * @see The same warning applys as the one on {@link GameObjects.planets} */ - private readonly myArtifacts: Map; + private readonly mySpaceships: Map; /** * Map from artifact ids to wormholes. */ - private readonly wormholes: Map; + private readonly wormholes: Map; /** * Set of all planet ids that we know have been interacted-with on-chain. @@ -204,11 +197,6 @@ export class GameObjects { */ public readonly planetUpdated$: Monomitter; - /** - * Event emitter which publishes whenever an artifact has been updated. - */ - public readonly artifactUpdated$: Monomitter; - /** * Whenever a planet is updated, we publish to this event with a reference to a map from location * id to planet. We need to rethink this event emitter because it currently publishes every time @@ -219,10 +207,15 @@ export class GameObjects { public readonly myPlanetsUpdated$: Monomitter>; /** - * Whenever one of the player's artifacts are updated, this event emitter publishes. See + * Whenever an artifact changes in a player's wallet, this event emitter publishes. See * {@link GameObjects.myPlanetsUpdated$} for more info. */ - public readonly myArtifactsUpdated$: Monomitter>; + public readonly myArtifactsUpdated$: Monomitter<[ArtifactId, Artifact | undefined]>; + /** + * Whenever a spaceship changes in a player's wallet, this event emitter publishes. See + * {@link GameObjects.myPlanetsUpdated$} for more info. + */ + public readonly mySpaceshipsUpdated$: Monomitter<[SpaceshipId, Spaceship | undefined]>; constructor( address: EthAddress | undefined, @@ -230,12 +223,13 @@ export class GameObjects { allTouchedPlanetIds: Set, revealedLocations: Map, claimedLocations: Map, - artifacts: Map, allChunks: Iterable, unprocessedArrivals: Map, unprocessedPlanetArrivalIds: Map, contractConstants: ContractConstants, - worldRadius: number + worldRadius: number, + myArtifacts: Map, + mySpaceships: Map ) { autoBind(this); @@ -245,8 +239,8 @@ export class GameObjects { this.touchedPlanetIds = allTouchedPlanetIds; this.revealedLocations = revealedLocations; this.claimedLocations = claimedLocations; - this.artifacts = artifacts; - this.myArtifacts = new Map(); + this.myArtifacts = myArtifacts; + this.mySpaceships = mySpaceships; this.contractConstants = contractConstants; this.coordsToLocation = new Map(); this.planetLocationMap = new Map(); @@ -257,8 +251,8 @@ export class GameObjects { this.layeredMap = new LayeredMap(worldRadius); this.planetUpdated$ = monomitter(); - this.artifactUpdated$ = monomitter(); this.myArtifactsUpdated$ = monomitter(); + this.mySpaceshipsUpdated$ = monomitter(); this.myPlanetsUpdated$ = monomitter(); for (const chunk of allChunks) { @@ -271,8 +265,6 @@ export class GameObjects { this.addPlanetLocation(location); } - this.replaceArtifactsFromContractData(artifacts.values()); - touchedPlanets.forEach((planet, planetId) => { const arrivalIds = unprocessedPlanetArrivalIds.get(planetId); @@ -325,52 +317,18 @@ export class GameObjects { setInterval(() => { this.planets.forEach((planet) => { if (planet && hasOwner(planet)) { - updatePlanetToTime( - planet, - this.getPlanetArtifacts(planet.locationId), - Date.now(), - this.contractConstants - ); + updatePlanetToTime(planet, Date.now(), this.contractConstants); } }); }, 120 * 1000); } - public getWormholes(): Iterable { - return this.wormholes.values(); - } - - public getArtifactById(artifactId?: ArtifactId): Artifact | undefined { - return artifactId ? this.artifacts.get(artifactId) : undefined; - } - - public getArtifactsOwnedBy(addr: EthAddress): Artifact[] { - const ret: Artifact[] = []; - this.artifacts.forEach((artifact) => { - if (artifact.currentOwner === addr || artifact.controller === addr) { - ret.push(artifact); - } - }); - return ret; + public getWormholes(): Iterable<[LocationId, LocationId]> { + return this.wormholes.entries(); } public getPlanetArtifacts(planetId: LocationId): Artifact[] { - return (this.planets.get(planetId)?.heldArtifactIds || []) - .map((id) => this.artifacts.get(id)) - .filter((a) => !!a) as Artifact[]; - } - - public getArtifactsOnPlanetsOwnedBy(addr: EthAddress): Artifact[] { - const ret: Artifact[] = []; - this.artifacts.forEach((artifact) => { - if (artifact.onPlanetId) { - const planet = this.getPlanetWithId(artifact.onPlanetId, false); - if (planet && planet.owner === addr) { - ret.push(artifact); - } - } - }); - return ret; + return this.planets.get(planetId)?.artifacts || []; } // get planet by ID - must be in contract or known chunks @@ -412,25 +370,6 @@ export class GameObjects { } } - /** - * received some artifact data from the contract. update our stores - */ - public replaceArtifactFromContractData(artifact: Artifact): void { - const localArtifact = this.artifacts.get(artifact.id); - if (localArtifact) { - artifact.transactions = localArtifact.transactions; - artifact.onPlanetId = localArtifact.onPlanetId; - } - - this.setArtifact(artifact); - } - - public replaceArtifactsFromContractData(artifacts: Iterable) { - for (const artifact of artifacts) { - this.replaceArtifactFromContractData(artifact); - } - } - /** * Given a planet id, update the state of the given planet by calling the given update function. * If the planet was updated, then also publish the appropriate event. @@ -444,26 +383,12 @@ export class GameObjects { } } - /** - * Given a planet id, update the state of the given planet by calling the given update function. - * If the planet was updated, then also publish the appropriate event. - */ - public updateArtifact(id: ArtifactId | undefined, updateFn: (p: Artifact) => void) { - const artifact = this.getArtifactById(id); - - if (artifact !== undefined) { - updateFn(artifact); - this.setArtifact(artifact); - } - } - /** * received some planet data from the contract. update our stores */ public replacePlanetFromContractData( planet: Planet, updatedArrivals?: QueuedArrival[], - updatedArtifactsOnPlanet?: ArtifactId[], revealedLocation?: RevealedLocation, claimerEthAddress?: EthAddress // TODO: Remove this ): void { @@ -490,16 +415,10 @@ export class GameObjects { planet.emojiZoopAnimation = emojiZoopAnimation; planet.emojiZoopOutAnimation = emojiZoopOutAnimation; planet.messages = messages; - - // Possibly non updated props - planet.heldArtifactIds = localPlanet.heldArtifactIds; } else { this.planets.set(planet.locationId, planet); } - if (updatedArtifactsOnPlanet) { - planet.heldArtifactIds = updatedArtifactsOnPlanet; - } // make planet Locatable if we know its location const loc = this.planetLocationMap.get(planet.locationId) || revealedLocation; if (loc) { @@ -692,13 +611,6 @@ export class GameObjects { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } - if (tx.intent.artifact) { - const artifact = this.getArtifactById(tx.intent.artifact); - if (artifact) { - artifact.transactions?.addTransaction(tx); - this.setArtifact(artifact); - } - } } else if (isUnconfirmedUpgradeTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { @@ -731,48 +643,28 @@ export class GameObjects { } } else if (isUnconfirmedDepositArtifactTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); - const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } - if (artifact) { - artifact.transactions?.addTransaction(tx); - this.setArtifact(artifact); - } } else if (isUnconfirmedWithdrawArtifactTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); - const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } - if (artifact) { - artifact.transactions?.addTransaction(tx); - this.setArtifact(artifact); - } } else if (isUnconfirmedActivateArtifactTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); - const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } - if (artifact) { - artifact.transactions?.addTransaction(tx); - this.setArtifact(artifact); - } } else if (isUnconfirmedDeactivateArtifactTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); - const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.addTransaction(tx); this.setPlanet(planet); } - if (artifact) { - artifact.transactions?.addTransaction(tx); - this.setArtifact(artifact); - } } else if (isUnconfirmedWithdrawSilverTx(tx)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { @@ -820,13 +712,6 @@ export class GameObjects { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } - if (tx.intent.artifact) { - const artifact = this.getArtifactById(tx.intent.artifact); - if (artifact) { - artifact.transactions?.removeTransaction(tx); - this.setArtifact(artifact); - } - } } else if (isUnconfirmedUpgrade(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { @@ -848,28 +733,18 @@ export class GameObjects { } } else if (isUnconfirmedDepositArtifact(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); - const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } - if (artifact) { - artifact.transactions?.removeTransaction(tx); - this.setArtifact(artifact); - } } else if (isUnconfirmedWithdrawArtifact(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); - const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } - if (artifact) { - artifact.transactions?.removeTransaction(tx); - this.setArtifact(artifact); - } } else if (isUnconfirmedTransfer(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.planetId); if (planet) { @@ -884,26 +759,16 @@ export class GameObjects { } } else if (isUnconfirmedActivateArtifact(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); - const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } - if (artifact) { - artifact.transactions?.removeTransaction(tx); - this.setArtifact(artifact); - } } else if (isUnconfirmedDeactivateArtifact(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); - const artifact = this.getArtifactById(tx.intent.artifactId); if (planet) { planet.transactions?.removeTransaction(tx); this.setPlanet(planet); } - if (artifact) { - artifact.transactions?.removeTransaction(tx); - this.setArtifact(artifact); - } } else if (isUnconfirmedWithdrawSilver(tx.intent)) { const planet = this.getPlanetWithId(tx.intent.locationId); if (planet) { @@ -929,10 +794,6 @@ export class GameObjects { return this.planets; } - public getArtifactMap(): Map { - return this.artifacts; - } - public getMyPlanetMap(): Map { return this.myPlanets; } @@ -940,6 +801,28 @@ export class GameObjects { public getMyArtifactMap(): Map { return this.myArtifacts; } + // TODO: This needs to track the AMOUNT of a given artifact in my wallet + public addMyArtifact(artifact: Artifact) { + this.myArtifacts.set(artifact.id, artifact); + this.myArtifactsUpdated$.publish([artifact.id, artifact]); + } + // TODO: This needs to track the AMOUNT of a given artifact in my wallet + public removeMyArtifact(artifact: Artifact) { + this.myArtifacts.delete(artifact.id); + this.myArtifactsUpdated$.publish([artifact.id, undefined]); + } + + public getMySpaceshipMap(): Map { + return this.mySpaceships; + } + public addMySpaceship(spaceship: Spaceship) { + this.mySpaceships.set(spaceship.id, spaceship); + this.mySpaceshipsUpdated$.publish([spaceship.id, spaceship]); + } + public removeMySpaceship(spaceship: Spaceship) { + this.mySpaceships.delete(spaceship.id); + this.mySpaceshipsUpdated$.publish([spaceship.id, undefined]); + } public getRevealedLocations(): Map { return this.revealedLocations; @@ -1014,6 +897,12 @@ export class GameObjects { this.layeredMap.insertPlanet(planet.location, planet.planetLevel); } + if (planet.wormholeTo) { + this.wormholes.set(planet.locationId, planet.wormholeTo); + } else { + this.wormholes.delete(planet.locationId); + } + setObjectSyncState( this.planets, this.myPlanets, @@ -1026,35 +915,6 @@ export class GameObjects { ); } - /** - * Set an artifact into our cached store. Should ALWAYS call this when setting an artifact. - * `this.artifacts` and `this.myArtifacts` should NEVER be accessed directly! - * This function also handles managing artifact update messages and indexing the map of owned artifacts. - * @param artifact the artifact to set - */ - private setArtifact(artifact: Artifact) { - if (artifact.artifactType === ArtifactType.Wormhole && artifact.onPlanetId) { - if (artifact.wormholeTo && isActivated(artifact)) { - this.wormholes.set(artifact.id, { - from: artifact.onPlanetId, - to: artifact.wormholeTo, - }); - } else { - this.wormholes.delete(artifact.id); - } - } - - setObjectSyncState( - this.artifacts, - this.myArtifacts, - this.address, - this.artifactUpdated$, - this.myArtifactsUpdated$, - getArtifactId, - getArtifactOwner, - artifact - ); - } /** * Emit notifications based on a planet's state change */ @@ -1111,13 +971,7 @@ export class GameObjects { try { if (nowInSeconds - arrival.arrivalTime > 0) { // if arrival happened in the past, run this arrival - const update = arrive( - planet, - this.getPlanetArtifacts(planet.locationId), - arrival, - this.getArtifactById(arrival.artifactId), - this.contractConstants - ); + const update = arrive(planet, arrival, this.contractConstants); this.removeArrival(planetId, update.arrival.eventId); this.emitArrivalNotifications(update); @@ -1125,13 +979,7 @@ export class GameObjects { // otherwise, set a timer to do this arrival in the future // and append it to arrivalsWithTimers const applyFutureArrival = setTimeout(() => { - const update = arrive( - planet, - this.getPlanetArtifacts(planet.locationId), - arrival, - this.getArtifactById(arrival.artifactId), - this.contractConstants - ); + const update = arrive(planet, arrival, this.contractConstants); this.emitArrivalNotifications(update); this.removeArrival(planetId, update.arrival.eventId); }, arrival.arrivalTime * 1000 - Date.now()); @@ -1394,7 +1242,12 @@ export class GameObjects { silverSpent: 0, prospectedBlockNumber: undefined, - heldArtifactIds: [], + artifacts: [], + spaceships: [], + activeArtifact: undefined, + artifactActivationTime: 0, + wormholeTo: undefined, + destroyed: false, isInContract: this.touchedPlanetIds.has(hex), syncedWithContract: false, @@ -1416,13 +1269,7 @@ export class GameObjects { private updatePlanetIfStale(planet: Planet): void { const now = Date.now(); if (now / 1000 - planet.lastUpdated > 1) { - updatePlanetToTime( - planet, - this.getPlanetArtifacts(planet.locationId), - now, - this.contractConstants, - this.setPlanet - ); + updatePlanetToTime(planet, now, this.contractConstants, this.setPlanet); } } @@ -1461,32 +1308,6 @@ export class GameObjects { return planet.lastUpdated + timeToTarget; } - /** - * Returns the EthAddress of the player who can control the owner: - * if the artifact is on a planet, this is the owner of the planet - * if the artifact is on a voyage, this is the initiator of the voyage - * if the artifact is not on either, then it is the owner of the artifact NFT - */ - public getArtifactController(artifactId: ArtifactId): EthAddress | undefined { - const artifact = this.getArtifactById(artifactId); - if (!artifact) { - return undefined; - } - - if (artifact.onPlanetId) { - const planet = this.getPlanetWithId(artifact.onPlanetId); - if (!planet) { - return undefined; - } - return planet.owner === EMPTY_ADDRESS ? undefined : planet.owner; - } else if (artifact.onVoyageId) { - const arrival = this.arrivals.get(artifact.onVoyageId); - return arrival?.arrivalData.player || undefined; - } else { - return artifact.currentOwner === EMPTY_ADDRESS ? undefined : artifact.currentOwner; - } - } - /** * Get all of the incoming voyages for a given location. */ diff --git a/client/src/Backend/GameLogic/GameUIManager.ts b/client/src/Backend/GameLogic/GameUIManager.ts index 764d6877..d9a50e28 100644 --- a/client/src/Backend/GameLogic/GameUIManager.ts +++ b/client/src/Backend/GameLogic/GameUIManager.ts @@ -1,6 +1,6 @@ import { EMPTY_ADDRESS } from '@dfdao/constants'; import { Monomitter, monomitter } from '@dfdao/events'; -import { biomeName, isLocatable, isSpaceShip } from '@dfdao/gamelogic'; +import { biomeName, isLocatable } from '@dfdao/gamelogic'; import { planetHasBonus } from '@dfdao/hexgen'; import { EthConnection } from '@dfdao/network'; import { GameGLManager, Renderer } from '@dfdao/renderer'; @@ -24,6 +24,8 @@ import { QueuedArrival, Rectangle, Setting, + Spaceship, + SpaceshipId, SpaceType, Transaction, UnconfirmedActivateArtifact, @@ -33,7 +35,6 @@ import { UpgradeBranchName, WorldCoords, WorldLocation, - Wormhole, } from '@dfdao/types'; import autoBind from 'auto-bind'; import { BigNumber } from 'ethers'; @@ -107,6 +108,7 @@ class GameUIManager extends EventEmitter { private silverSending: { [key: string]: number } = {}; // this is a percentage private artifactSending: { [key: string]: Artifact | undefined } = {}; + private spaceshipSending: { [key: string]: Spaceship | undefined } = {}; private plugins: PluginManager; @@ -114,8 +116,7 @@ class GameUIManager extends EventEmitter { public readonly hoverPlanetId$: Monomitter; public readonly hoverPlanet$: Monomitter; public readonly hoverArtifactId$: Monomitter; - public readonly hoverArtifact$: Monomitter; - public readonly myArtifacts$: Monomitter>; + public readonly hoverSpaceshipId$: Monomitter; public readonly isSending$: Monomitter; public readonly isAbandoning$: Monomitter; @@ -163,12 +164,7 @@ class GameUIManager extends EventEmitter { ); this.hoverArtifactId$ = monomitter(); - this.hoverArtifact$ = getObjectWithIdFromMap( - this.getArtifactMap(), - this.hoverArtifactId$, - this.gameManager.getArtifactUpdated$() - ); - this.myArtifacts$ = this.gameManager.getMyArtifactsUpdated$(); + this.hoverSpaceshipId$ = monomitter(); this.viewportEntities = new ViewportEntities(this.gameManager, this); this.isSending$ = monomitter(true); @@ -558,6 +554,7 @@ class GameUIManager extends EventEmitter { `df.move('${from.locationId}', '${to.locationId}', ${forces}, ${silver})` ); const artifact = this.getArtifactSending(from.locationId); + const spaceship = this.getSpaceshipSending(from.locationId); this.gameManager.move( from.locationId, @@ -565,6 +562,7 @@ class GameUIManager extends EventEmitter { forces, silver, artifact?.id, + spaceship?.id, abandoning ); tutorialManager.acceptInput(TutorialState.SendFleet); @@ -649,10 +647,13 @@ class GameUIManager extends EventEmitter { public setArtifactSending(planetId: LocationId, artifact?: Artifact) { this.artifactSending[planetId] = artifact; - if (this.isSendingShip(planetId)) { - this.abandoning = false; - this.isAbandoning$.publish(false); - } + this.gameManager.getGameObjects().forceTick(planetId); + } + + public setSpaceshipSending(planetId: LocationId, spaceship?: Spaceship) { + this.spaceshipSending[planetId] = spaceship; + this.abandoning = false; + this.isAbandoning$.publish(false); this.gameManager.getGameObjects().forceTick(planetId); } @@ -808,6 +809,7 @@ class GameUIManager extends EventEmitter { // Set to undefined after SendComplete so it can send another one this.artifactSending[locationId] = undefined; + this.spaceshipSending[locationId] = undefined; this.sendingPlanet = undefined; // Done at the end so they clear the artifact @@ -996,7 +998,9 @@ class GameUIManager extends EventEmitter { public setHoveringOverArtifact(artifactId?: ArtifactId) { this.hoverArtifactId$.publish(artifactId); - this.hoverArtifact$.publish(artifactId ? this.getArtifactWithId(artifactId) : undefined); + } + public setHoveringOverSpaceship(spaceshipId?: SpaceshipId) { + this.hoverSpaceshipId$.publish(spaceshipId); } public getHoveringOverPlanet(): Planet | undefined { @@ -1046,6 +1050,10 @@ class GameUIManager extends EventEmitter { if (!planetId) return undefined; return this.artifactSending[planetId]; } + public getSpaceshipSending(planetId?: LocationId): Spaceship | undefined { + if (!planetId) return undefined; + return this.spaceshipSending[planetId]; + } public getAbandonSpeedChangePercent(): number { const { SPACE_JUNK_ENABLED, ABANDON_SPEED_CHANGE_PERCENT } = this.contractConstants; @@ -1067,7 +1075,7 @@ class GameUIManager extends EventEmitter { public isSendingShip(planetId?: LocationId): boolean { if (!planetId) return false; - return isSpaceShip(this.artifactSending[planetId]?.artifactType); + return this.spaceshipSending[planetId] !== undefined; } public isOverOwnPlanet(coords: WorldCoords): Planet | undefined { @@ -1082,9 +1090,8 @@ class GameUIManager extends EventEmitter { public getMyArtifacts(): Artifact[] { return this.gameManager.getMyArtifacts(); } - - public getMyArtifactsNotOnPlanet(): Artifact[] { - return this.getMyArtifacts().filter((a) => !a.onPlanetId); + public getMySpaceships(): Spaceship[] { + return this.gameManager.getMySpaceships(); } public getPlanetWithId(planetId: LocationId | undefined): Planet | undefined { @@ -1099,23 +1106,10 @@ class GameUIManager extends EventEmitter { return this.gameManager.getPlayer(address); } - public getArtifactWithId(artifactId: ArtifactId | undefined): Artifact | undefined { - return this.gameManager.getArtifactWithId(artifactId); - } - public getPlanetWithCoords(coords: WorldCoords | undefined): Planet | undefined { return coords && this.gameManager.getPlanetWithCoords(coords); } - public getArtifactsWithIds(artifactIds?: ArtifactId[]): Array { - return this.gameManager.getArtifactsWithIds(artifactIds); - } - - public getArtifactPlanet(artifact: Artifact): Planet | undefined { - if (!artifact.onPlanetId) return undefined; - return this.getPlanetWithId(artifact.onPlanetId); - } - public getPlanetLevel(planetId: LocationId): PlanetLevel | undefined { return this.gameManager.getPlanetLevel(planetId); } @@ -1158,7 +1152,7 @@ class GameUIManager extends EventEmitter { return this.gameManager.getUnconfirmedWormholeActivations(); } - public getWormholes(): Iterable { + public getWormholes(): Iterable<[LocationId, LocationId]> { return this.gameManager.getWormholes(); } @@ -1285,10 +1279,6 @@ class GameUIManager extends EventEmitter { return this.gameManager.getPlanetMap(); } - public getArtifactMap(): Map { - return this.gameManager.getArtifactMap(); - } - public getMyPlanetMap(): Map { return this.gameManager.getMyPlanetMap(); } @@ -1433,10 +1423,6 @@ class GameUIManager extends EventEmitter { return this.contractConstants.CAPTURE_ZONE_PLANET_LEVEL_SCORE; } - public getArtifactUpdated$() { - return this.gameManager.getArtifactUpdated$(); - } - public getUIEmitter() { return UIEmitter.getInstance(); } @@ -1479,6 +1465,17 @@ class GameUIManager extends EventEmitter { const renderer = this.getRenderer(); if (renderer) renderer.removeCustomRenderer(customRenderer); } + + public getUpgradeForArtifact(artifactId: ArtifactId) { + return this.gameManager.getUpgradeForArtifact(artifactId); + } + + public getMyArtifactsUpdated$() { + return this.gameManager.getMyArtifactsUpdated$(); + } + public getMySpaceshipsUpdated$() { + return this.gameManager.getMySpaceshipsUpdated$(); + } } export default GameUIManager; diff --git a/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx b/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx index 300ed60d..662e03ed 100644 --- a/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx +++ b/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx @@ -1,12 +1,12 @@ import { Artifact, - ArtifactId, ClaimedCoords, LocationId, Planet, Player, QueuedArrival, RevealedCoords, + Spaceship, VoyageId, } from '@dfdao/types'; import _ from 'lodash'; @@ -31,9 +31,8 @@ export interface InitialGameState { allClaimedCoords?: ClaimedCoords[]; pendingMoves: QueuedArrival[]; touchedAndLocatedPlanets: Map; - artifactsOnVoyages: Artifact[]; myArtifacts: Artifact[]; - heldArtifacts: Artifact[][]; + mySpaceships: Spaceship[]; loadedPlanets: LocationId[]; revealedCoordsMap: Map; claimedCoordsMap?: Map; @@ -88,9 +87,8 @@ export class InitialGameStateDownloader { const pendingMovesLoadingBar = this.makeProgressListener('Pending Moves'); const planetsLoadingBar = this.makeProgressListener('Planets'); - const artifactsOnPlanetsLoadingBar = this.makeProgressListener('Artifacts On Planets'); - const artifactsInFlightLoadingBar = this.makeProgressListener('Artifacts On Moves'); const yourArtifactsLoadingBar = this.makeProgressListener('Your Artifacts'); + const yourSpaceshipsLoadingBar = this.makeProgressListener('Your Spaceships'); const contractConstants = contractsAPI.getConstants(); const worldRadius = contractsAPI.getWorldRadius(); @@ -159,26 +157,14 @@ export class InitialGameStateDownloader { arrivals.set(arrival.eventId, arrival); } - const artifactIdsOnVoyages: ArtifactId[] = []; - for (const arrival of pendingMoves) { - if (arrival.artifactId) { - artifactIdsOnVoyages.push(arrival.artifactId); - } - } - - const artifactsOnVoyages = await contractsAPI.bulkGetArtifacts( - artifactIdsOnVoyages, - artifactsInFlightLoadingBar - ); - - const heldArtifacts = contractsAPI.bulkGetArtifactsOnPlanets( - planetsToLoad, - artifactsOnPlanetsLoadingBar - ); const myArtifacts = contractsAPI.getPlayerArtifacts( contractsAPI.getAddress(), yourArtifactsLoadingBar ); + const mySpaceships = contractsAPI.getPlayerSpaceships( + contractsAPI.getAddress(), + yourSpaceshipsLoadingBar + ); const twitters = await tryGetAllTwitters(); const paused = contractsAPI.getIsPaused(); @@ -191,9 +177,8 @@ export class InitialGameStateDownloader { allRevealedCoords, pendingMoves, touchedAndLocatedPlanets, - artifactsOnVoyages, myArtifacts: await myArtifacts, - heldArtifacts: await heldArtifacts, + mySpaceships: await mySpaceships, loadedPlanets: planetsToLoad, revealedCoordsMap, claimedCoordsMap, diff --git a/client/src/Backend/Storage/ReaderDataStore.ts b/client/src/Backend/Storage/ReaderDataStore.ts index 8d1820d2..ef7690f0 100644 --- a/client/src/Backend/Storage/ReaderDataStore.ts +++ b/client/src/Backend/Storage/ReaderDataStore.ts @@ -1,7 +1,6 @@ import { isLocatable } from '@dfdao/gamelogic'; import { EthConnection } from '@dfdao/network'; import { - ArtifactId, Biome, EthAddress, LocatablePlanet, @@ -144,25 +143,15 @@ class ReaderDataStore { for (const arrival of arrivals) { if (nowInSeconds < arrival.arrivalTime) break; - arrive(planet, [], arrival, undefined, contractConstants); + arrive(planet, arrival, contractConstants); } - updatePlanetToTime(planet, [], Date.now(), contractConstants); + updatePlanetToTime(planet, Date.now(), contractConstants); await this.setPlanetLocationIfKnown(planet); return planet; } - public async loadArtifactFromContract(artifactId: ArtifactId) { - const artifact = await this.contractsAPI.getArtifactById(artifactId); - - if (!artifact) { - throw new Error(`unable to load artifact with id ${artifactId}`); - } - - return artifact; - } - // copied from GameEntityMemoryStore. needed to determine biome if we know planet location private spaceTypeFromPerlin(perlin: number): SpaceType { if (perlin < this.contractConstants.PERLIN_THRESHOLD_1) { diff --git a/client/src/Frontend/Components/ArtifactImage.tsx b/client/src/Frontend/Components/ArtifactImage.tsx index 711b3e88..26b3be7f 100644 --- a/client/src/Frontend/Components/ArtifactImage.tsx +++ b/client/src/Frontend/Components/ArtifactImage.tsx @@ -1,11 +1,10 @@ -import { ArtifactFileColor, artifactFileName, isSpaceShip } from '@dfdao/gamelogic'; +import { ArtifactFileColor, artifactFileName } from '@dfdao/gamelogic'; import { Artifact } from '@dfdao/types'; import React from 'react'; import styled, { css } from 'styled-components'; import dfstyles from '../Styles/dfstyles'; export const ARTIFACT_URL = 'https://d2wspbczt15cqu.cloudfront.net/v0.6.0-artifacts/'; -// const ARTIFACT_URL = '/img/artifacts/videos/'; function getArtifactUrl(thumb: boolean, artifact: Artifact, color: ArtifactFileColor): string { const fileName = artifactFileName(true, thumb, artifact, color); @@ -24,17 +23,12 @@ export function ArtifactImage({ bgColor?: ArtifactFileColor; }) { const url = getArtifactUrl(thumb || false, artifact, bgColor || ArtifactFileColor.BLUE); - const image = isSpaceShip(artifact.artifactType) ? ( - - ) : ( - - ); return ( - {image} + ); } diff --git a/client/src/Frontend/Components/Labels/SpaceshipLabels.tsx b/client/src/Frontend/Components/Labels/SpaceshipLabels.tsx new file mode 100644 index 00000000..19f8410b --- /dev/null +++ b/client/src/Frontend/Components/Labels/SpaceshipLabels.tsx @@ -0,0 +1,6 @@ +import { Spaceship, SpaceshipTypeNames } from '@dfdao/types'; +import React from 'react'; + +export const SpaceshipTypeText = ({ spaceship }: { spaceship: Spaceship }) => ( + <>{SpaceshipTypeNames[spaceship.spaceshipType]} +); diff --git a/client/src/Frontend/Components/MineArtifactButton.tsx b/client/src/Frontend/Components/MineArtifactButton.tsx index 400415b2..3d29bc68 100644 --- a/client/src/Frontend/Components/MineArtifactButton.tsx +++ b/client/src/Frontend/Components/MineArtifactButton.tsx @@ -1,5 +1,5 @@ import { isUnconfirmedFindArtifactTx, isUnconfirmedProspectPlanetTx } from '@dfdao/serde'; -import { ArtifactType, Planet, PlanetType, TooltipName } from '@dfdao/types'; +import { Planet, PlanetType, SpaceshipType, TooltipName } from '@dfdao/types'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { isFindable } from '../../Backend/GameLogic/ArrivalUtils'; @@ -42,10 +42,10 @@ export function MineArtifactButton({ const hasGear = useMemo( () => - planetWrapper.value?.heldArtifactIds - .map((id) => uiManager.getArtifactWithId(id)) - .find((artifact) => artifact?.artifactType === ArtifactType.ShipGear), - [planetWrapper, uiManager] + planetWrapper.value?.spaceships?.some( + (spaceship) => spaceship.spaceshipType === SpaceshipType.ShipGear + ), + [planetWrapper] ); const gearEnabled = uiManager.contractConstants.SPACESHIPS.GEAR; diff --git a/client/src/Frontend/Components/OpenPaneButtons.tsx b/client/src/Frontend/Components/OpenPaneButtons.tsx index 7e8d69f4..15431e5a 100644 --- a/client/src/Frontend/Components/OpenPaneButtons.tsx +++ b/client/src/Frontend/Components/OpenPaneButtons.tsx @@ -3,17 +3,17 @@ import React, { useCallback } from 'react'; import { BroadcastPane, BroadcastPaneHelpContent } from '../Panes/BroadcastPane'; import { HatPane } from '../Panes/HatPane'; import { - ManagePlanetArtifactsHelpContent, - ManagePlanetArtifactsPane, + ManagePlanetInventoryHelpContent, + ManagePlanetInventoryPane, PlanetInfoHelpContent, -} from '../Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane'; +} from '../Panes/ManagePlanetArtifacts/ManagePlanetInventoryPane'; import { PlanetInfoPane } from '../Panes/PlanetInfoPane'; import { UpgradeDetailsPane, UpgradeDetailsPaneHelpContent } from '../Panes/UpgradeDetailsPane'; import { TOGGLE_BROADCAST_PANE, TOGGLE_HAT_PANE, - TOGGLE_PLANET_ARTIFACTS_PANE, TOGGLE_PLANET_INFO_PANE, + TOGGLE_PLANET_INVENTORY_PANE, TOGGLE_UPGRADES_PANE, } from '../Utils/ShortcutConstants'; import { ModalHandle } from '../Views/ModalPane'; @@ -105,7 +105,7 @@ export function OpenUpgradeDetailsPaneButton({ /> ); } -export function OpenManagePlanetArtifactsButton({ +export function OpenManagePlanetInventoryButton({ modal, planetId, }: { @@ -116,9 +116,9 @@ export function OpenManagePlanetArtifactsButton({ } - helpContent={ManagePlanetArtifactsHelpContent()} + shortcut={TOGGLE_PLANET_INVENTORY_PANE} + element={() => } + helpContent={ManagePlanetInventoryHelpContent()} /> ); } diff --git a/client/src/Frontend/Components/SpaceshipImage.tsx b/client/src/Frontend/Components/SpaceshipImage.tsx new file mode 100644 index 00000000..7c4c99c0 --- /dev/null +++ b/client/src/Frontend/Components/SpaceshipImage.tsx @@ -0,0 +1,35 @@ +import { spaceshipFileName } from '@dfdao/gamelogic'; +import { Spaceship } from '@dfdao/types'; +import React from 'react'; +import styled, { css } from 'styled-components'; +import dfstyles from '../Styles/dfstyles'; + +export const SPACESHIP_URL = 'https://d2wspbczt15cqu.cloudfront.net/v0.6.0-artifacts/'; + +function getSpaceshipUrl(spaceship: Spaceship): string { + const fileName = spaceshipFileName(spaceship); + return SPACESHIP_URL + fileName; +} + +export function SpaceshipImage({ spaceship, size }: { spaceship: Spaceship; size: number }) { + const url = getSpaceshipUrl(spaceship); + + return ( + + + + ); +} + +const Container = styled.div` + image-rendering: crisp-edges; + + ${({ width, height }: { width: number; height: number }) => css` + width: ${width}px; + height: ${height}px; + min-width: ${width}px; + min-height: ${height}px; + background-color: ${dfstyles.colors.artifactBackground}; + display: inline-block; + `} +`; diff --git a/client/src/Frontend/Components/Text.tsx b/client/src/Frontend/Components/Text.tsx index a7474e1c..5ea65dce 100644 --- a/client/src/Frontend/Components/Text.tsx +++ b/client/src/Frontend/Components/Text.tsx @@ -1,7 +1,13 @@ import { BLOCK_EXPLORER_URL } from '@dfdao/constants'; import { isLocatable } from '@dfdao/gamelogic'; -import { artifactName, getPlanetName } from '@dfdao/procedural'; -import { Artifact, ArtifactId, Chunk, Planet, Transaction, WorldCoords } from '@dfdao/types'; +import { artifactName, getPlanetName, spaceshipName } from '@dfdao/procedural'; +import { + artifactIdToEthersBN, + decodeArtifact, + decodeSpaceship, + spaceshipIdToEthersBN, +} from '@dfdao/serde'; +import { ArtifactId, Chunk, Planet, SpaceshipId, Transaction, WorldCoords } from '@dfdao/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import Viewport from '../Game/Viewport'; @@ -108,8 +114,7 @@ export function CenterPlanetLink({ } export function ArtifactNameLink({ id }: { id: ArtifactId }) { - const uiManager = useUIManager(); - const artifact: Artifact | undefined = uiManager && uiManager.getArtifactWithId(id); + const artifact = decodeArtifact(artifactIdToEthersBN(id)); const click = () => { UIEmitter.getInstance().emit(UIEmitterEvent.ShowArtifact, artifact); @@ -118,6 +123,16 @@ export function ArtifactNameLink({ id }: { id: ArtifactId }) { return {artifactName(artifact)}; } +export function SpaceshipNameLink({ id }: { id: SpaceshipId }) { + const spaceship = decodeSpaceship(spaceshipIdToEthersBN(id)); + + const click = () => { + UIEmitter.getInstance().emit(UIEmitterEvent.ShowSpaceship, spaceship); + }; + + return {spaceshipName(spaceship)}; +} + export function PlanetNameLink({ planet }: { planet: Planet }) { return {getPlanetName(planet)}; } diff --git a/client/src/Frontend/Pages/ShareArtifact.tsx b/client/src/Frontend/Pages/ShareArtifact.tsx index 7cf07edc..16f92790 100644 --- a/client/src/Frontend/Pages/ShareArtifact.tsx +++ b/client/src/Frontend/Pages/ShareArtifact.tsx @@ -1,12 +1,12 @@ +import { artifactIdToEthersBN, decodeArtifact } from '@dfdao/serde'; import { Artifact, ArtifactId } from '@dfdao/types'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import ReaderDataStore from '../../Backend/Storage/ReaderDataStore'; import { Share } from '../Views/Share'; export function ShareArtifact({ match }: RouteComponentProps<{ artifactId: ArtifactId }>) { - function load(dataStore: ReaderDataStore) { - return dataStore.loadArtifactFromContract(match.params.artifactId); + async function load() { + return decodeArtifact(artifactIdToEthersBN(match.params.artifactId)); } return ( diff --git a/client/src/Frontend/Panes/ArtifactCard.tsx b/client/src/Frontend/Panes/ArtifactCard.tsx index 331617aa..1525d3f9 100644 --- a/client/src/Frontend/Panes/ArtifactCard.tsx +++ b/client/src/Frontend/Panes/ArtifactCard.tsx @@ -1,21 +1,12 @@ -import { ArtifactId } from '@dfdao/types'; +import { Artifact, Planet } from '@dfdao/types'; import React from 'react'; import { Padded } from '../Components/CoreUI'; -import { useUIManager } from '../Utils/AppHooks'; import { ArtifactDetailsBody } from './ArtifactDetailsPane'; -export function ArtifactCard({ artifactId }: { artifactId?: ArtifactId }) { - const uiManager = useUIManager(); - - if (!artifactId) return null; - +export function ArtifactCard({ planet, artifact }: { planet: Planet; artifact: Artifact }) { return ( - + ); } diff --git a/client/src/Frontend/Panes/ArtifactDetailsPane.tsx b/client/src/Frontend/Panes/ArtifactDetailsPane.tsx index bf3bbe3d..7e2b34f5 100644 --- a/client/src/Frontend/Panes/ArtifactDetailsPane.tsx +++ b/client/src/Frontend/Panes/ArtifactDetailsPane.tsx @@ -1,35 +1,30 @@ -import { EMPTY_ADDRESS } from '@dfdao/constants'; -import { dateMintedAt, hasStatBoost, isActivated, isSpaceShip } from '@dfdao/gamelogic'; -import { artifactName, getPlanetName, getPlanetNameHash } from '@dfdao/procedural'; +import { hasStatBoost } from '@dfdao/gamelogic'; +import { artifactName } from '@dfdao/procedural'; import { Artifact, - ArtifactId, ArtifactRarityNames, ArtifactType, - EthAddress, LocationId, + Planet, TooltipName, Upgrade, } from '@dfdao/types'; import _ from 'lodash'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { getUpgradeStat } from '../../Backend/Utils/Utils'; -import { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes'; import { StatIdx } from '../../_types/global/GlobalTypes'; import { ArtifactImage } from '../Components/ArtifactImage'; import { Spacer } from '../Components/CoreUI'; import { StatIcon } from '../Components/Icons'; import { ArtifactRarityLabelAnim, ArtifactTypeText } from '../Components/Labels/ArtifactLabels'; import { ArtifactBiomeLabelAnim } from '../Components/Labels/BiomeLabels'; -import { AccountLabel } from '../Components/Labels/Labels'; import { ReadMore } from '../Components/ReadMore'; import { Green, Red, Sub, Text, White } from '../Components/Text'; import { TextPreview } from '../Components/TextPreview'; import { TimeUntil } from '../Components/TimeUntil'; import dfstyles from '../Styles/dfstyles'; -import { useArtifact, useUIManager } from '../Utils/AppHooks'; -import { ModalHandle } from '../Views/ModalPane'; +import { useUIManager } from '../Utils/AppHooks'; import { ArtifactActions } from './ManagePlanetArtifacts/ArtifactActions'; import { TooltipTrigger } from './Tooltip'; @@ -141,71 +136,42 @@ const ArtifactNameSubtitle = styled.div` export function ArtifactDetailsHelpContent() { return (
-

- In this pane, you can see specific information about a particular artifact. You can also - initiate a conversation with the artifact! Try talking to your artifacts. Make some new - friends (^: -

+

In this pane, you can see specific information about a particular artifact.

); } export function ArtifactDetailsBody({ - artifactId, - contractConstants, + planet, + artifact, depositOn, noActions, }: { - artifactId: ArtifactId; - contractConstants: ContractConstants; - modal?: ModalHandle; + planet?: Planet; + artifact: Artifact; depositOn?: LocationId; noActions?: boolean; }) { const uiManager = useUIManager(); - const artifactWrapper = useArtifact(uiManager, artifactId); - const artifact = artifactWrapper.value; - if (!artifact) { - return null; - } - - const account = (addr: EthAddress) => { - const twitter = uiManager?.getTwitter(addr); - if (twitter) { - return '@' + twitter; - } - return ; - }; - - const owner = () => { - if (!artifact) return ''; - return account(artifact.currentOwner); - }; - - const discoverer = () => { - if (!artifact) return ''; - return account(artifact.discoverer); - }; - - // TODO make this common with playerartifactspane - const planetArtifactName = (a: Artifact): string | undefined => { - const onPlanet = uiManager?.getArtifactPlanet(a); - if (!onPlanet) return undefined; - return getPlanetName(onPlanet); - }; - - const planetClicked = (): void => { - if (artifact.onPlanetId) uiManager?.setSelectedId(artifact.onPlanetId); - }; + // TODO: I don't like the async nature of this...we should have the logic on the client somehow + const [upgrade, setUpgrade] = useState(undefined); + useEffect(() => { + uiManager.getUpgradeForArtifact(artifact.id).then(setUpgrade); + }, [uiManager, artifact, setUpgrade]); let readyInStr = undefined; - if (artifact.artifactType === ArtifactType.PhotoidCannon && isActivated(artifact)) { + if ( + planet && + artifact.artifactType === ArtifactType.PhotoidCannon && + planet.activeArtifact?.id === artifact.id + ) { readyInStr = ( @@ -219,23 +185,12 @@ export function ArtifactDetailsBody({
- {isSpaceShip(artifact.artifactType) ? ( - <> - - - - {artifactName(artifact)} - - ) : ( - <> - {artifactName(artifact)} - - {' '} - {' '} - - - - )} + {artifactName(artifact)} + + {' '} + + +
{hasStatBoost(artifact.artifactType) && ( @@ -243,7 +198,7 @@ export function ArtifactDetailsBody({ {_.range(0, 5).map((val) => ( @@ -252,61 +207,15 @@ export function ArtifactDetailsBody({ )} - {isSpaceShip(artifact.artifactType) && ( - - )} - - {!isSpaceShip(artifact.artifactType) && } + -
- Located On - {planetArtifactName(artifact) ? ( - - {planetArtifactName(artifact)} - - ) : ( - n / a - )} -
- - {!isSpaceShip(artifact.artifactType) && ( - <> -
- Minted At - {dateMintedAt(artifact)} -
-
- Discovered On - {getPlanetNameHash(artifact.planetDiscoveredOn)} -
-
- Discovered By - {discoverer()} -
- - )} - - {artifact.controller === EMPTY_ADDRESS && ( -
- Owner - {owner()} -
- )}
ID
- {artifact.controller !== EMPTY_ADDRESS && ( -
- Controller - - - -
- )} {readyInStr && (
Ready In @@ -315,35 +224,13 @@ export function ArtifactDetailsBody({ )} {!noActions && ( - + )} ); } -export function ArtifactDetailsPane({ - modal, - artifactId, - depositOn, -}: { - modal: ModalHandle; - artifactId: ArtifactId; - depositOn?: LocationId; -}) { - const uiManager = useUIManager(); - const contractConstants = uiManager.contractConstants; - - return ( - - ); -} - function ArtifactDescription({ artifact, collapsable, @@ -364,8 +251,6 @@ function ArtifactDescription({ const photoidRanges = [0, 2, 2, 2, 2, 2]; const photoidSpeeds = [0, 5, 10, 15, 20, 25]; - const genericSpaceshipDescription = <>Can move between planets without sending energy.; - switch (artifact.artifactType) { case ArtifactType.BlackDomain: content = ( @@ -421,45 +306,6 @@ function ArtifactDescription({ ); break; - case ArtifactType.ShipMothership: - content = ( - - Doubles energy regeneration of the planet that it is currently on.{' '} - {genericSpaceshipDescription} - - ); - break; - case ArtifactType.ShipCrescent: - content = ( - - Activate to convert an un-owned planet whose level is more than 0 into an Asteroid Field.{' '} - Can only be used once. {genericSpaceshipDescription} - - ); - break; - case ArtifactType.ShipGear: - content = ( - - Allows you to prospect planets, and subsequently find artifacts on them.{' '} - {genericSpaceshipDescription} - - ); - break; - case ArtifactType.ShipTitan: - content = ( - - Pauses energy and silver regeneration on the planet it's on. {genericSpaceshipDescription} - - ); - break; - case ArtifactType.ShipWhale: - content = ( - - Doubles the silver regeneration of the planet that it is currently on.{' '} - {genericSpaceshipDescription} - - ); - break; } if (content) { diff --git a/client/src/Frontend/Panes/ArtifactHoverPane.tsx b/client/src/Frontend/Panes/ArtifactHoverPane.tsx index b8580d8d..9660721f 100644 --- a/client/src/Frontend/Panes/ArtifactHoverPane.tsx +++ b/client/src/Frontend/Panes/ArtifactHoverPane.tsx @@ -1,16 +1,19 @@ +import { artifactIdToEthersBN, decodeArtifact } from '@dfdao/serde'; +import { Planet } from '@dfdao/types'; import React from 'react'; import { useHoverArtifactId, useUIManager } from '../Utils/AppHooks'; import { ArtifactCard } from './ArtifactCard'; import { HoverPane } from './HoverPane'; -export function ArtifactHoverPane() { +export function ArtifactHoverPane({ planet }: { planet: Planet }) { const uiManager = useUIManager(); const hoverArtifactId = useHoverArtifactId(uiManager); + if (!hoverArtifactId.value) return null; + + const artifact = decodeArtifact(artifactIdToEthersBN(hoverArtifactId.value)); + return ( - } - /> + } /> ); } diff --git a/client/src/Frontend/Panes/ArtifactsList.tsx b/client/src/Frontend/Panes/ArtifactsList.tsx index a45bb5c7..3fdec6ac 100644 --- a/client/src/Frontend/Panes/ArtifactsList.tsx +++ b/client/src/Frontend/Panes/ArtifactsList.tsx @@ -1,200 +1,107 @@ -import { isSpaceShip } from '@dfdao/gamelogic'; -import { artifactName, getPlanetName } from '@dfdao/procedural'; -import { Artifact, ArtifactTypeNames, LocationId } from '@dfdao/types'; -import React from 'react'; -import styled from 'styled-components'; -import GameUIManager from '../../Backend/GameLogic/GameUIManager'; -import { CenterBackgroundSubtext, Truncate } from '../Components/CoreUI'; +import { artifactName } from '@dfdao/procedural'; +import { Artifact, ArtifactTypeNames, LocationId, Planet } from '@dfdao/types'; +import React, { useCallback, useEffect } from 'react'; +import { Link } from '../Components/CoreUI'; import { ArtifactRarityLabelAnim } from '../Components/Labels/ArtifactLabels'; import { Sub } from '../Components/Text'; +import { ArtifactDetailsBody } from '../Panes/ArtifactDetailsPane'; +import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; -import { ArtifactLink } from '../Views/ArtifactLink'; import { ModalHandle } from '../Views/ModalPane'; -import { PlanetLink } from '../Views/PlanetLink'; import { SortableTable } from '../Views/SortableTable'; -import { TabbedView } from '../Views/TabbedView'; -const ArtifactsBody = styled.div` - min-height: 200px; - max-height: 400px; - overflow-y: scroll; -`; - -const PlanetName = styled.span` - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 100px; -`; - -const planetArtifactName = (a: Artifact, uiManager: GameUIManager): string | undefined => { - const onPlanet = uiManager?.getArtifactPlanet(a); - if (!onPlanet) return undefined; - return getPlanetName(onPlanet); -}; - -export function ArtifactsList({ +function ArtifactLink({ modal, - artifacts, + children, + planet, + artifact, depositOn, - maxRarity, - noArtifactsMessage, }: { - modal: ModalHandle; - artifacts: Artifact[]; + modal?: ModalHandle; + planet?: Planet; + artifact: Artifact; + children: React.ReactNode | React.ReactNode[]; depositOn?: LocationId; - maxRarity?: number; - noArtifactsMessage?: React.ReactElement; }) { const uiManager = useUIManager(); - let nonShipArtifacts = artifacts.filter((a) => !isSpaceShip(a.artifactType)); - if (maxRarity !== undefined) { - nonShipArtifacts = nonShipArtifacts.filter((a) => a.rarity <= maxRarity); - } - const headers = ['Name', 'Location', 'Type', 'Rarity']; - const alignments: Array<'r' | 'c' | 'l'> = ['l', 'r', 'r', 'r']; - - const columns = [ - (artifact: Artifact) => ( - - {artifactName(artifact)} - - ), - (artifact: Artifact) => { - const planetOn = uiManager.getArtifactPlanet(artifact); - const planetOnName = planetArtifactName(artifact, uiManager); - - return ( - - {planetOnName && planetOn ? ( - - {planetOnName} - - ) : ( - wallet - )} - - ); - }, - (artifact: Artifact) => ( - - {ArtifactTypeNames[artifact.artifactType]} - - ), - (artifact: Artifact) => , - ]; - - const sortFunctions = [ - (left: Artifact, right: Artifact) => artifactName(left).localeCompare(artifactName(right)), - (left: Artifact, right: Artifact) => - planetArtifactName(left, uiManager)?.localeCompare( - planetArtifactName(right, uiManager) || '' - ) || 0, - (left: Artifact, right: Artifact) => - ArtifactTypeNames[left.artifactType]?.localeCompare( - ArtifactTypeNames[right.artifactType] || '' - ) || 0, - (left: Artifact, right: Artifact) => left.rarity - right.rarity, - ]; - if (nonShipArtifacts.length === 0) { - return ( - - {noArtifactsMessage ?? ( - <> - You Don't Have
Any Artifacts - - )} -
- ); - } + useEffect(() => { + // this is called when the component is unrendered + return () => uiManager?.setHoveringOverArtifact(undefined); + }, [uiManager]); + + const onClick = useCallback(() => { + uiManager?.setHoveringOverArtifact(undefined); + modal && + modal.push({ + element() { + return ; + }, + title: artifactName(artifact), + }); + }, [artifact, planet, modal, depositOn, uiManager]); return ( - + { + uiManager?.setHoveringOverArtifact(undefined); + }} + onMouseEnter={() => { + uiManager?.setHoveringOverArtifact(artifact.id); + }} + onMouseLeave={() => { + uiManager?.setHoveringOverArtifact(undefined); + }} + > + {children} + ); } -export function ShipList({ +export function ArtifactsList({ modal, + planet, artifacts, depositOn, - noShipsMessage, + maxRarity, }: { modal: ModalHandle; + planet?: Planet; artifacts: Artifact[]; depositOn?: LocationId; - noShipsMessage?: React.ReactElement; + maxRarity?: number; }) { - const uiManager = useUIManager(); - const headers = ['Name', 'Location', 'Type']; - const alignments: Array<'r' | 'c' | 'l'> = ['l', 'r', 'r', 'r']; - const shipArtifacts = artifacts.filter((a) => isSpaceShip(a.artifactType)); + if (maxRarity !== undefined) { + artifacts = artifacts.filter((a) => a.rarity <= maxRarity); + } + const headers = ['Name', 'Type', 'Rarity']; + const alignments: Array<'r' | 'c' | 'l'> = ['l', 'c', 'r']; const columns = [ (artifact: Artifact) => ( - + {artifactName(artifact)} ), - (artifact: Artifact) => { - const planetOn = uiManager.getArtifactPlanet(artifact); - const planetOnName = planetArtifactName(artifact, uiManager); - - return ( - - {planetOnName && planetOn ? ( - - {planetOnName} - - ) : ( - moving - )} - - ); - }, - (artifact: Artifact) => ( - - {ArtifactTypeNames[artifact.artifactType]} - - ), + (artifact: Artifact) => {ArtifactTypeNames[artifact.artifactType]}, + (artifact: Artifact) => , ]; const sortFunctions = [ (left: Artifact, right: Artifact) => artifactName(left).localeCompare(artifactName(right)), - (left: Artifact, right: Artifact) => - planetArtifactName(left, uiManager)?.localeCompare( - planetArtifactName(right, uiManager) || '' - ) || 0, (left: Artifact, right: Artifact) => ArtifactTypeNames[left.artifactType]?.localeCompare( ArtifactTypeNames[right.artifactType] || '' ) || 0, + (left: Artifact, right: Artifact) => left.rarity - right.rarity, ]; - if (shipArtifacts.length === 0) { - return ( - - {noShipsMessage ?? ( - <> - You Don't Have
Any Ships - - )} -
- ); - } - return ( ); } - -export function AllArtifacts({ - modal, - artifacts, - depositOn, - maxRarity, - noArtifactsMessage, - noShipsMessage, -}: { - modal: ModalHandle; - artifacts: Artifact[]; - depositOn?: LocationId; - maxRarity?: number; - noArtifactsMessage?: React.ReactElement; - noShipsMessage?: React.ReactElement; -}) { - return ( - - { - if (i === 0) { - return ( - - ); - } - - return ( - - ); - }} - /> - - ); -} diff --git a/client/src/Frontend/Panes/HoverPane.tsx b/client/src/Frontend/Panes/HoverPane.tsx index ae948489..0aa3a2ef 100644 --- a/client/src/Frontend/Panes/HoverPane.tsx +++ b/client/src/Frontend/Panes/HoverPane.tsx @@ -54,7 +54,12 @@ export function HoverPane({ return ( {element} diff --git a/client/src/Frontend/Panes/InventoryPane.tsx b/client/src/Frontend/Panes/InventoryPane.tsx new file mode 100644 index 00000000..3c9e17ad --- /dev/null +++ b/client/src/Frontend/Panes/InventoryPane.tsx @@ -0,0 +1,82 @@ +import { RECOMMENDED_MODAL_WIDTH } from '@dfdao/constants'; +import { ModalName } from '@dfdao/types'; +import React from 'react'; +import styled from 'styled-components'; +import { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI'; +import { useMyArtifactsList, useMySpaceshipsList, useUIManager } from '../Utils/AppHooks'; +import { ModalHandle, ModalPane } from '../Views/ModalPane'; +import { TabbedView } from '../Views/TabbedView'; +import { ArtifactsList } from './ArtifactsList'; +import { SpaceshipsList } from './SpaceshipsList'; + +function HelpContent() { + return ( +
+

These are all the Artifacts and Spaceships currently in your wallet.

+ +

+ The table is interactive, and allows you to sort the data by clicking each column's header. + You can also view more information about a particular artifact by clicking on its name. +

+
+ ); +} + +const TabWrapper = styled.div` + min-height: 200px; + max-height: 400px; + overflow-y: scroll; +`; + +export function InvetoryPane({ visible, onClose }: { visible: boolean; onClose: () => void }) { + const uiManager = useUIManager(); + const artifacts = useMyArtifactsList(uiManager); + const spaceships = useMySpaceshipsList(uiManager); + + const render = (handle: ModalHandle) => { + return ( + + { + if (i === 0) { + if (artifacts.length === 0) { + return ( + + You don't have any
Artifacts in your wallet +
+ ); + } + + return ; + } + + if (spaceships.length === 0) { + return ( + + You don't have any
Spaceships in your wallet +
+ ); + } + + return ; + }} + /> +
+ ); + }; + + return ( + + {render} + + ); +} diff --git a/client/src/Frontend/Panes/ManagePlanetArtifacts/ArtifactActions.tsx b/client/src/Frontend/Panes/ManagePlanetArtifacts/ArtifactActions.tsx index d2218b95..21b84318 100644 --- a/client/src/Frontend/Panes/ManagePlanetArtifacts/ArtifactActions.tsx +++ b/client/src/Frontend/Panes/ManagePlanetArtifacts/ArtifactActions.tsx @@ -1,72 +1,63 @@ -import { - canActivateArtifact, - canDepositArtifact, - canWithdrawArtifact, - durationUntilArtifactAvailable, - isActivated, - isLocatable, -} from '@dfdao/gamelogic'; +import { isLocatable } from '@dfdao/gamelogic'; import { isUnconfirmedActivateArtifactTx, isUnconfirmedDeactivateArtifactTx, isUnconfirmedDepositArtifactTx, isUnconfirmedWithdrawArtifactTx, } from '@dfdao/serde'; -import { Artifact, ArtifactId, ArtifactType, LocationId, TooltipName } from '@dfdao/types'; +import { Artifact, ArtifactType, LocationId, Planet, PlanetType, TooltipName } from '@dfdao/types'; import React, { useCallback } from 'react'; import { Btn } from '../../Components/Btn'; import { Spacer } from '../../Components/CoreUI'; import { ArtifactRarityLabelAnim } from '../../Components/Labels/ArtifactLabels'; import { LoadingSpinner } from '../../Components/LoadingSpinner'; -import { Sub } from '../../Components/Text'; -import { formatDuration } from '../../Components/TimeUntil'; -import { - useAccount, - useArtifact, - usePlanet, - usePlanetArtifacts, - useUIManager, -} from '../../Utils/AppHooks'; +import { useAccount, useMyArtifactsList, usePlanet, useUIManager } from '../../Utils/AppHooks'; import { TooltipTrigger, TooltipTriggerProps } from '../Tooltip'; +function hasArtifact(planet: Planet, artifact: Artifact) { + return planet.artifacts.some(({ id }) => id === artifact.id); +} + export function ArtifactActions({ - artifactId, + artifact, + planet, depositOn, }: { - artifactId: ArtifactId; + artifact: Artifact; + planet?: Planet; depositOn?: LocationId; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); - const artifactWrapper = useArtifact(uiManager, artifactId); - const artifact = artifactWrapper.value; + + const myArtifacts = useMyArtifactsList(uiManager); const depositPlanetWrapper = usePlanet(uiManager, depositOn); - const onPlanetWrapper = usePlanet(uiManager, artifact?.onPlanetId); const depositPlanet = depositPlanetWrapper.value; - const onPlanet = onPlanetWrapper.value; - const otherArtifactsOnPlanet = usePlanetArtifacts(onPlanetWrapper, uiManager); + const onPlanetWrapper = usePlanet(uiManager, planet?.locationId); + const onPlanet = onPlanetWrapper.value; const withdraw = useCallback( (artifact: Artifact) => { - onPlanet && uiManager.withdrawArtifact(onPlanet.locationId, artifact?.id); + if (onPlanet && hasArtifact(onPlanet, artifact)) { + uiManager.withdrawArtifact(onPlanet.locationId, artifact.id); + } }, [onPlanet, uiManager] ); const deposit = useCallback( (artifact: Artifact) => { - artifact && - depositPlanetWrapper.value && - uiManager.depositArtifact(depositPlanetWrapper.value.locationId, artifact?.id); + depositPlanetWrapper.value && + uiManager.depositArtifact(depositPlanetWrapper.value.locationId, artifact.id); }, [uiManager, depositPlanetWrapper.value] ); const activate = useCallback( async (artifact: Artifact) => { - if (onPlanet && isLocatable(onPlanet)) { + if (isLocatable(onPlanet) && hasArtifact(onPlanet, artifact)) { let targetPlanetId = undefined; if (artifact.artifactType === ArtifactType.Wormhole) { @@ -82,28 +73,49 @@ export function ArtifactActions({ const deactivate = useCallback( (artifact: Artifact) => { - onPlanet && uiManager.deactivateArtifact(onPlanet.locationId, artifact.id); + if (onPlanet && hasArtifact(onPlanet, artifact)) { + uiManager.deactivateArtifact(onPlanet.locationId, artifact.id); + } }, [onPlanet, uiManager] ); - if (!artifact || (!onPlanet && !depositPlanet) || !account) return null; + if (!artifact || !onPlanet || !account) return null; const actions: TooltipTriggerProps[] = []; - const withdrawing = artifact.transactions?.hasTransaction(isUnconfirmedWithdrawArtifactTx); - const depositing = artifact.transactions?.hasTransaction(isUnconfirmedDepositArtifactTx); - const activating = artifact.transactions?.hasTransaction(isUnconfirmedActivateArtifactTx); - const deactivating = artifact.transactions?.hasTransaction(isUnconfirmedDeactivateArtifactTx); + const withdrawing = onPlanet.transactions?.hasTransaction(isUnconfirmedWithdrawArtifactTx); + const depositing = onPlanet.transactions?.hasTransaction(isUnconfirmedDepositArtifactTx); + const activating = onPlanet.transactions?.hasTransaction(isUnconfirmedActivateArtifactTx); + const deactivating = onPlanet.transactions?.hasTransaction(isUnconfirmedDeactivateArtifactTx); const canHandleDeposit = depositPlanetWrapper.value && depositPlanetWrapper.value.planetLevel > artifact.rarity; - const canHandleWithdraw = - onPlanetWrapper.value && onPlanetWrapper.value.planetLevel > artifact.rarity; - - const wait = durationUntilArtifactAvailable(artifact); - - if (canDepositArtifact(account, artifact, depositPlanetWrapper.value)) { + const canHandleWithdraw = onPlanet && onPlanet.planetLevel > artifact.rarity; + + const canDepositArtifact = + depositPlanet && + !depositPlanet.destroyed && + depositPlanet.owner === account && + depositPlanet.planetType === PlanetType.TRADING_POST && + myArtifacts.some(({ id }) => id === artifact.id); + + const canWithdrawArtifact = + onPlanet && + !onPlanet.destroyed && + onPlanet.owner === account && + onPlanet.planetType === PlanetType.TRADING_POST && + hasArtifact(onPlanet, artifact) && + onPlanet.activeArtifact?.id !== artifact.id; + + const canDeactivateArtifact = + onPlanet.activeArtifact?.id === artifact.id && + artifact.artifactType !== ArtifactType.BlackDomain; + + const canActivateArtifact = + onPlanet.activeArtifact === undefined && hasArtifact(onPlanet, artifact); + + if (canDepositArtifact) { actions.unshift({ name: TooltipName.DepositArtifact, extraContent: !canHandleDeposit && ( @@ -125,7 +137,7 @@ export function ArtifactActions({ ), }); } - if (isActivated(artifact) && artifact.artifactType !== ArtifactType.BlackDomain) { + if (canDeactivateArtifact) { actions.unshift({ name: TooltipName.DeactivateArtifact, children: ( @@ -141,7 +153,7 @@ export function ArtifactActions({ ), }); } - if (canWithdrawArtifact(account, artifact, onPlanet)) { + if (canWithdrawArtifact) { actions.unshift({ name: TooltipName.WithdrawArtifact, extraContent: !canHandleWithdraw && ( @@ -164,7 +176,7 @@ export function ArtifactActions({ }); } - if (canActivateArtifact(artifact, onPlanet, otherArtifactsOnPlanet)) { + if (canActivateArtifact) { actions.unshift({ name: TooltipName.ActivateArtifact, children: ( @@ -181,14 +193,6 @@ export function ArtifactActions({ }); } - if (wait > 0) { - actions.unshift({ - name: TooltipName.Empty, - extraContent: <>You have to wait before activating an artifact again, - children: {formatDuration(wait)}, - }); - } - return (
{actions.length > 0 && } diff --git a/client/src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx b/client/src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx deleted file mode 100644 index 37362798..00000000 --- a/client/src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Artifact, LocatablePlanet } from '@dfdao/types'; -import React, { useEffect, useState } from 'react'; -import styled, { css } from 'styled-components'; -import { Spacer } from '../../Components/CoreUI'; -import { ModalHandle } from '../../Views/ModalPane'; -import { AllArtifacts } from '../ArtifactsList'; - -export function ManageArtifactsPane({ - planet, - artifactsInWallet, - artifactsOnPlanet, - playerAddress, - modal, -}: { - planet: LocatablePlanet; - artifactsInWallet: Artifact[]; - artifactsOnPlanet: Array; - playerAddress: string; - modal: ModalHandle; -}) { - const isMyPlanet = planet.owner === playerAddress && !planet.destroyed; - const [viewingDepositList, setViewingDepositList] = useState(false); - - let action; - - useEffect(() => { - setViewingDepositList(false); - }, [planet.locationId, playerAddress]); - - return ( - <> - !!a - ) as Artifact[] - } - modal={modal} - noArtifactsMessage={ - <> - No Artifacts
On This Planet - - } - noShipsMessage={ - <> - No Ships
On This Planet - - } - /> - {action && ( - <> - - {action} - - - )} - - - - {isMyPlanet && ( - - { - setViewingDepositList(false); - }} - > - On This Planet - - { - setViewingDepositList(true); - }} - > - Activate Artifact - - - )} - - ); -} - -const SelectArtifactsContainer = styled.div` - padding: 4px; - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: row; -`; - -const SelectArtifactList = styled.span` - ${({ selected }: { selected?: boolean }) => css` - ${selected && 'text-decoration: underline;'} - cursor: pointer; - `} -`; diff --git a/client/src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane.tsx b/client/src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane.tsx deleted file mode 100644 index c9a00270..00000000 --- a/client/src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { isLocatable } from '@dfdao/gamelogic'; -import { LocationId } from '@dfdao/types'; -import React from 'react'; -import { CenterBackgroundSubtext, Underline } from '../../Components/CoreUI'; -import { useAccount, useMyArtifactsList, usePlanet, useUIManager } from '../../Utils/AppHooks'; -import { useEmitterValue } from '../../Utils/EmitterHooks'; -import { ModalHandle } from '../../Views/ModalPane'; -import { ManageArtifactsPane } from './ManageArtifacts'; - -export function PlanetInfoHelpContent() { - return ( -
-

Metadata related to this planet.

-
- ); -} - -export function ManagePlanetArtifactsHelpContent() { - return ( -
-

- Using this pane, you can manage the artifacts that are on this planet specifically. You can - activate a single artifact at a time. Some artifacts have a cooldown period after - deactivating during which they can not be activated. -

-
-

- If your planet is a Spacetime Rip, you can also withdraw and deposit - artifacts. When you withdraw an artifact, it is transferred to your address as an ERC 721 - token. -

-
- ); -} - -/** - * This is the place where a user can manage all of their artifacts on a - * particular planet. This includes prospecting, withdrawing, depositing, - * activating, and deactivating artifacts. - */ -export function ManagePlanetArtifactsPane({ - initialPlanetId, - modal, -}: { - initialPlanetId: LocationId | undefined; - modal: ModalHandle; -}) { - const uiManager = useUIManager(); - const account = useAccount(uiManager); - const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId); - const planet = usePlanet(uiManager, planetId).value; - const myArtifacts = useMyArtifactsList(uiManager); - const onPlanet = uiManager.getArtifactsWithIds(planet?.heldArtifactIds || []); - - const artifactsInWallet = []; - for (const a of myArtifacts) { - if (!a.onPlanetId) { - artifactsInWallet.push(a); - } - } - - if (planet && myArtifacts && isLocatable(planet) && account) { - return ( - - ); - } else { - return ( - - Select a Planet - - ); - } -} diff --git a/client/src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetInventoryPane.tsx b/client/src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetInventoryPane.tsx new file mode 100644 index 00000000..12c30984 --- /dev/null +++ b/client/src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetInventoryPane.tsx @@ -0,0 +1,198 @@ +import { isLocatable } from '@dfdao/gamelogic'; +import { Artifact, LocatablePlanet, LocationId, PlanetType } from '@dfdao/types'; +import React, { useEffect, useState } from 'react'; +import styled, { css } from 'styled-components'; +import { CenterBackgroundSubtext, Spacer, Underline } from '../../Components/CoreUI'; +import { useAccount, useMyArtifactsList, usePlanet, useUIManager } from '../../Utils/AppHooks'; +import { useEmitterValue } from '../../Utils/EmitterHooks'; +import { ModalHandle } from '../../Views/ModalPane'; +import { TabbedView } from '../../Views/TabbedView'; +import { ArtifactsList } from '../ArtifactsList'; +import { SpaceshipsList } from '../SpaceshipsList'; + +export function PlanetInfoHelpContent() { + return ( +
+

Metadata related to this planet.

+
+ ); +} + +export function ManagePlanetInventoryHelpContent() { + return ( +
+

+ Using this pane, you can manage the Artifacts and Spaceships that are on this planet + specifically. You can activate a single artifact at a time. +

+
+

+ If your planet is a Spacetime Rip, you can also withdraw and deposit + Artifacts. When you withdraw an Artifact, it is transferred to your address as an ERC1155 + token. +

+
+ ); +} + +const TabWrapper = styled.div` + min-height: 200px; + max-height: 400px; + overflow-y: scroll; +`; + +function ManageInventoryPane({ + planet, + artifactsInWallet, + playerAddress, + modal, +}: { + planet: LocatablePlanet; + artifactsInWallet: Artifact[]; + playerAddress: string; + modal: ModalHandle; +}) { + const isMyTradingPost = + planet.owner === playerAddress && + planet.planetType === PlanetType.TRADING_POST && + !planet.destroyed; + const [viewingDepositList, setViewingDepositList] = useState(false); + + let action; + + useEffect(() => { + setViewingDepositList(false); + }, [planet.locationId, playerAddress]); + + return ( + <> + + { + if (i === 0) { + const artifacts = viewingDepositList ? artifactsInWallet : planet.artifacts; + if (artifacts.length === 0) { + return ( + + You don't have any
Artifacts{' '} + {viewingDepositList ? 'in your wallet' : 'on this planet'} +
+ ); + } + + return ( + + ); + } + + if (planet.spaceships.length === 0) { + return ( + + You don't have any
Spaceships on this planet +
+ ); + } + + return ( + + ); + }} + /> +
+ + {action && ( + <> + + {action} + + + )} + + + + {isMyTradingPost && ( + + { + setViewingDepositList(false); + }} + > + On This Planet + + { + setViewingDepositList(true); + }} + > + Deposit Artifact + + + )} + + ); +} + +const SelectArtifactsContainer = styled.div` + padding: 4px; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: row; +`; + +const SelectArtifactList = styled.span` + ${({ selected }: { selected?: boolean }) => css` + ${selected && 'text-decoration: underline;'} + cursor: pointer; + `} +`; + +/** + * This is the place where a user can manage all of their Artifacts & Spaceships on a + * particular planet. This includes prospecting, withdrawing, depositing, + * activating, and deactivating artifacts. + */ +export function ManagePlanetInventoryPane({ + initialPlanetId, + modal, +}: { + initialPlanetId: LocationId | undefined; + modal: ModalHandle; +}) { + const uiManager = useUIManager(); + const account = useAccount(uiManager); + const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId); + const planet = usePlanet(uiManager, planetId).value; + const myArtifacts = useMyArtifactsList(uiManager); + + if (planet && isLocatable(planet) && account) { + return ( + + ); + } else { + return ( + + Select a Planet + + ); + } +} diff --git a/client/src/Frontend/Panes/PlanetContextPane.tsx b/client/src/Frontend/Panes/PlanetContextPane.tsx index 906bf429..2043084b 100644 --- a/client/src/Frontend/Panes/PlanetContextPane.tsx +++ b/client/src/Frontend/Panes/PlanetContextPane.tsx @@ -5,11 +5,7 @@ import { Wrapper } from '../../Backend/Utils/Wrapper'; import { CapturePlanetButton } from '../Components/CapturePlanetButton'; import { VerticalSplit } from '../Components/CoreUI'; import { MineArtifactButton } from '../Components/MineArtifactButton'; -import { - OpenManagePlanetArtifactsButton, - OpenPlanetInfoButton, - OpenUpgradeDetailsPaneButton, -} from '../Components/OpenPaneButtons'; +import { OpenPlanetInfoButton, OpenUpgradeDetailsPaneButton } from '../Components/OpenPaneButtons'; import { snips } from '../Styles/dfstyles'; import { useAccount, useSelectedPlanet, useUIManager } from '../Utils/AppHooks'; import { useEmitterSubscribe } from '../Utils/EmitterHooks'; @@ -72,7 +68,6 @@ function PlanetContextPaneContent({ planetWrapper={planet} onToggleSendForces={onToggleSendForces} onToggleAbandon={onToggleAbandon} - onToggleExtract={() => {}} /> {captureRow} @@ -83,7 +78,7 @@ function PlanetContextPaneContent({ <> - + {withdrawRow} diff --git a/client/src/Frontend/Panes/PlanetDexPane.tsx b/client/src/Frontend/Panes/PlanetDexPane.tsx index fd55c34b..bb76e926 100644 --- a/client/src/Frontend/Panes/PlanetDexPane.tsx +++ b/client/src/Frontend/Panes/PlanetDexPane.tsx @@ -6,8 +6,10 @@ import { ModalName, Planet, PlanetType, RGBVec } from '@dfdao/types'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { getPlanetRank } from '../../Backend/Utils/Utils'; +import { ArtifactImage } from '../Components/ArtifactImage'; import { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI'; import { Icon, IconType } from '../Components/Icons'; +import { SpaceshipImage } from '../Components/SpaceshipImage'; import { Sub } from '../Components/Text'; import { useUIManager } from '../Utils/AppHooks'; import { ModalPane } from '../Views/ModalPane'; @@ -165,8 +167,8 @@ export function PlanetDexPane({ visible, onClose }: { visible: boolean; onClose: }; }, [visible, uiManager]); - const headers = ['', 'Planet Name', 'Level', 'Energy', 'Silver', 'Inventory']; - const alignments: Array<'r' | 'c' | 'l'> = ['r', 'l', 'r', 'r', 'r', 'r']; + const headers = ['', 'Planet Name', 'Level', 'Energy', 'Silver', 'Artifacts', 'Spaceships']; + const alignments: Array<'r' | 'c' | 'l'> = ['r', 'l', 'r', 'r', 'r', 'c', 'c']; const columns = [ (planet: Planet) => , @@ -178,7 +180,14 @@ export function PlanetDexPane({ visible, onClose }: { visible: boolean; onClose: (planet: Planet) => {planet.planetLevel}, (planet: Planet) => {formatNumber(planet.energy)}, (planet: Planet) => {formatNumber(planet.silver)}, - (planet: Planet) => {formatNumber(planet.heldArtifactIds.length)}, + (planet: Planet) => + planet.artifacts.map((artifact) => ( + + )), + (planet: Planet) => + planet.spaceships.map((spaceship) => ( + + )), ]; const sortingFunctions = [ @@ -197,9 +206,14 @@ export function PlanetDexPane({ visible, onClose }: { visible: boolean; onClose: (a: Planet, b: Planet): number => b.silver - a.silver, // artifacts (a: Planet, b: Planet): number => { - const [numArtifacts, scoreB] = [a.heldArtifactIds.length, b.heldArtifactIds.length]; + const [numArtifacts, scoreB] = [a.artifacts.length, b.artifacts.length]; return scoreB - numArtifacts; }, + // spaceships + (a: Planet, b: Planet): number => { + const [numSpaceships, scoreB] = [a.spaceships.length, b.spaceships.length]; + return scoreB - numSpaceships; + }, ]; let content; diff --git a/client/src/Frontend/Panes/PlayerArtifactsPane.tsx b/client/src/Frontend/Panes/PlayerArtifactsPane.tsx deleted file mode 100644 index 659d771b..00000000 --- a/client/src/Frontend/Panes/PlayerArtifactsPane.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { RECOMMENDED_MODAL_WIDTH } from '@dfdao/constants'; -import { ModalName } from '@dfdao/types'; -import React from 'react'; -import { Spacer } from '../Components/CoreUI'; -import { useMyArtifactsList, useUIManager } from '../Utils/AppHooks'; -import { ModalHandle, ModalPane } from '../Views/ModalPane'; -import { AllArtifacts } from './ArtifactsList'; - -function HelpContent() { - return ( -
-

These are all the artifacts you currently own.

- -

- The table is interactive, and allows you to sort the artifacts by clicking each column's - header. You can also view more information about a particular artifact by clicking on its - name. -

-
- ); -} - -export function PlayerArtifactsPane({ - visible, - onClose, -}: { - visible: boolean; - onClose: () => void; -}) { - const uiManager = useUIManager(); - const artifacts = useMyArtifactsList(uiManager); - - const render = (handle: ModalHandle) => ; - - return ( - - {render} - - ); -} diff --git a/client/src/Frontend/Panes/SpaceshipCard.tsx b/client/src/Frontend/Panes/SpaceshipCard.tsx new file mode 100644 index 00000000..f1f3b30a --- /dev/null +++ b/client/src/Frontend/Panes/SpaceshipCard.tsx @@ -0,0 +1,12 @@ +import { Spaceship } from '@dfdao/types'; +import React from 'react'; +import { Padded } from '../Components/CoreUI'; +import { SpaceshipDetailsBody } from './SpaceshipDetailsPane'; + +export function SpaceshipCard({ spaceship }: { spaceship: Spaceship }) { + return ( + + + + ); +} diff --git a/client/src/Frontend/Panes/SpaceshipDetailsPane.tsx b/client/src/Frontend/Panes/SpaceshipDetailsPane.tsx new file mode 100644 index 00000000..75862172 --- /dev/null +++ b/client/src/Frontend/Panes/SpaceshipDetailsPane.tsx @@ -0,0 +1,153 @@ +import { spaceshipName } from '@dfdao/procedural'; +import { Spaceship, SpaceshipType } from '@dfdao/types'; +import React from 'react'; +import styled from 'styled-components'; +import { Spacer } from '../Components/CoreUI'; +import { SpaceshipTypeText } from '../Components/Labels/SpaceshipLabels'; +import { ReadMore } from '../Components/ReadMore'; +import { SpaceshipImage } from '../Components/SpaceshipImage'; +import { Red, Text } from '../Components/Text'; +import { TextPreview } from '../Components/TextPreview'; +import dfstyles from '../Styles/dfstyles'; + +const StyledArtifactDetailsBody = styled.div` + & > div:first-child p { + text-decoration: underline; + } + + & .row { + display: flex; + flex-direction: row; + justify-content: space-between; + + & > span:first-child { + color: ${dfstyles.colors.subtext}; + } + + & > span:last-child { + text-align: right; + } + } + + & .link { + &:hover { + cursor: pointer; + text-decoration: underline; + } + } +`; + +const SpaceshipName = styled.div` + color: ${dfstyles.colors.text}; + font-weight: bold; +`; + +const SpaceshipNameSubtitle = styled.div` + color: ${dfstyles.colors.subtext}; + margin-bottom: 8px; +`; + +export function SpaceshipDetailsHelpContent() { + return ( +
+

In this pane, you can see specific information about a particular spaceship.

+
+ ); +} + +export function SpaceshipDetailsBody({ spaceship }: { spaceship: Spaceship }) { + return ( + <> +
+ +
+ +
+ {spaceshipName(spaceship)} + + + +
+ + + + + +
+ ID + +
+
+ + ); +} + +function SpaceshipDescription({ + spaceship, + collapsable, +}: { + spaceship: Spaceship; + collapsable?: boolean; +}) { + let content; + + const genericSpaceshipDescription = <>Can move between planets without sending energy.; + + switch (spaceship.spaceshipType) { + case SpaceshipType.ShipMothership: + content = ( + + Doubles energy regeneration of the planet that it is currently on.{' '} + {genericSpaceshipDescription} + + ); + break; + case SpaceshipType.ShipCrescent: + content = ( + + Activate to convert an un-owned planet whose level is more than 0 into an Asteroid Field.{' '} + Can only be used once. {genericSpaceshipDescription} + + ); + break; + case SpaceshipType.ShipGear: + content = ( + + Allows you to prospect planets, and subsequently find artifacts on them.{' '} + {genericSpaceshipDescription} + + ); + break; + case SpaceshipType.ShipTitan: + content = ( + + Pauses energy and silver regeneration on the planet it's on. {genericSpaceshipDescription} + + ); + break; + case SpaceshipType.ShipWhale: + content = ( + + Doubles the silver regeneration of the planet that it is currently on.{' '} + {genericSpaceshipDescription} + + ); + break; + } + + if (content) { + return ( +
+ {collapsable ? ( + + {content} + + ) : ( + content + )} +
+ ); + } + + return null; +} diff --git a/client/src/Frontend/Panes/SpaceshipHoverPane.tsx b/client/src/Frontend/Panes/SpaceshipHoverPane.tsx new file mode 100644 index 00000000..f5a8fd88 --- /dev/null +++ b/client/src/Frontend/Panes/SpaceshipHoverPane.tsx @@ -0,0 +1,16 @@ +import { decodeSpaceship, spaceshipIdToEthersBN } from '@dfdao/serde'; +import React from 'react'; +import { useHoverSpaceshipId, useUIManager } from '../Utils/AppHooks'; +import { HoverPane } from './HoverPane'; +import { SpaceshipCard } from './SpaceshipCard'; + +export function SpaceshipHoverPane() { + const uiManager = useUIManager(); + const hoverSpaceshipId = useHoverSpaceshipId(uiManager); + + if (!hoverSpaceshipId.value) return null; + + const spaceship = decodeSpaceship(spaceshipIdToEthersBN(hoverSpaceshipId.value)); + + return } />; +} diff --git a/client/src/Frontend/Panes/SpaceshipsList.tsx b/client/src/Frontend/Panes/SpaceshipsList.tsx new file mode 100644 index 00000000..124a26b9 --- /dev/null +++ b/client/src/Frontend/Panes/SpaceshipsList.tsx @@ -0,0 +1,99 @@ +import { spaceshipName } from '@dfdao/procedural'; +import { LocationId, Spaceship, SpaceshipTypeNames } from '@dfdao/types'; +import React, { useCallback, useEffect } from 'react'; +import { Link } from '../Components/CoreUI'; +import { Sub } from '../Components/Text'; +import dfstyles from '../Styles/dfstyles'; +import { useUIManager } from '../Utils/AppHooks'; +import { ModalHandle } from '../Views/ModalPane'; +import { SortableTable } from '../Views/SortableTable'; +import { SpaceshipDetailsBody } from './SpaceshipDetailsPane'; + +function SpaceshipLink({ + modal, + children, + spaceship, + depositOn, +}: { + modal?: ModalHandle; + spaceship: Spaceship; + children: React.ReactNode | React.ReactNode[]; + depositOn?: LocationId; +}) { + const uiManager = useUIManager(); + + useEffect(() => { + // this is called when the component is unrendered + return () => uiManager?.setHoveringOverSpaceship(undefined); + }, [uiManager]); + + const onClick = useCallback(() => { + uiManager?.setHoveringOverSpaceship(undefined); + modal && + modal.push({ + element() { + return ; + }, + title: spaceshipName(spaceship), + }); + }, [spaceship, modal, depositOn, uiManager]); + + return ( + { + uiManager?.setHoveringOverSpaceship(undefined); + }} + onMouseEnter={() => { + uiManager?.setHoveringOverSpaceship(spaceship.id); + }} + onMouseLeave={() => { + uiManager?.setHoveringOverSpaceship(undefined); + }} + > + {children} + + ); +} + +export function SpaceshipsList({ + modal, + spaceships, + depositOn, +}: { + modal: ModalHandle; + spaceships: Spaceship[]; + depositOn?: LocationId; +}) { + const headers = ['Name', 'Type']; + const alignments: Array<'r' | 'c' | 'l'> = ['l', 'r']; + + const columns = [ + (spaceship: Spaceship) => ( + + {spaceshipName(spaceship)} + + ), + (spaceship: Spaceship) => {SpaceshipTypeNames[spaceship.spaceshipType]}, + ]; + + const sortFunctions = [ + (left: Spaceship, right: Spaceship) => spaceshipName(left).localeCompare(spaceshipName(right)), + (left: Spaceship, right: Spaceship) => + SpaceshipTypeNames[left.spaceshipType]?.localeCompare( + SpaceshipTypeNames[right.spaceshipType] || '' + ) || 0, + ]; + + return ( + + ); +} diff --git a/client/src/Frontend/Utils/AppHooks.ts b/client/src/Frontend/Utils/AppHooks.ts index 2dc83ec2..15030801 100644 --- a/client/src/Frontend/Utils/AppHooks.ts +++ b/client/src/Frontend/Utils/AppHooks.ts @@ -1,4 +1,3 @@ -import { getActivatedArtifact, isActivated } from '@dfdao/gamelogic'; import { Artifact, ArtifactId, @@ -7,6 +6,7 @@ import { LocationId, Planet, Player, + SpaceshipId, Transaction, TransactionId, } from '@dfdao/types'; @@ -108,85 +108,80 @@ export function useHoverPlanet(uiManager: GameUIManager): Wrapper(uiManager.hoverPlanet$, undefined); } -export function useHoverArtifact(uiManager: GameUIManager): Wrapper { - return useWrappedEmitter(uiManager.hoverArtifact$, undefined); -} - export function useHoverArtifactId(uiManager: GameUIManager): Wrapper { return useWrappedEmitter(uiManager.hoverArtifactId$, undefined); } +export function useHoverSpaceshipId(uiManager: GameUIManager): Wrapper { + return useWrappedEmitter(uiManager.hoverSpaceshipId$, undefined); +} export function useMyArtifactsList(uiManager: GameUIManager) { - const [myArtifacts, setMyArtifacts] = useState(uiManager.getMyArtifacts()); + const [myArtifacts, setMyArtifacts] = useState(new Wrapper(uiManager.getMyArtifacts())); useEmitterSubscribe( - uiManager.getArtifactUpdated$(), + uiManager.getMyArtifactsUpdated$(), () => { - setMyArtifacts(uiManager.getMyArtifacts()); + setMyArtifacts(new Wrapper(uiManager.getMyArtifacts())); }, [uiManager, setMyArtifacts] ); - return myArtifacts; + return myArtifacts.value; +} +export function useMySpaceshipsList(uiManager: GameUIManager) { + const [mySpaceships, setMySpaceships] = useState(new Wrapper(uiManager.getMySpaceships())); + useEmitterSubscribe( + uiManager.getMySpaceshipsUpdated$(), + () => { + setMySpaceships(new Wrapper(uiManager.getMySpaceships())); + }, + [uiManager, setMySpaceships] + ); + return mySpaceships.value; } // note that this is going to throw an error if the pointer to `artifacts` changes but not to `planet` -export function usePlanetArtifacts( - planet: Wrapper, - uiManager: GameUIManager -): Artifact[] { - const artifacts = useMemo( - () => (planet.value ? uiManager.getArtifactsWithIds(planet.value.heldArtifactIds) : []), - [planet, uiManager] - ); +export function usePlanetArtifacts(planet: Wrapper): Artifact[] { + const artifacts = useMemo(() => (planet.value ? planet.value.artifacts : []), [planet]); return artifacts.filter((a) => !!a) as Artifact[]; } -export function usePlanetInactiveArtifacts( - planet: Wrapper, - uiManager: GameUIManager -): Artifact[] { - const artifacts = usePlanetArtifacts(planet, uiManager); - const filtered = useMemo(() => artifacts.filter((a) => !isActivated(a)), [artifacts]); +export function usePlanetInactiveArtifacts(planet: Wrapper): Artifact[] { + const artifacts = usePlanetArtifacts(planet); + + const filtered = useMemo( + () => artifacts.filter(({ id }) => id !== planet.value?.activeArtifact?.id), + [artifacts, planet] + ); return filtered; } -export function useActiveArtifact( - planet: Wrapper, - uiManager: GameUIManager -): Artifact | undefined { - const artifacts = usePlanetArtifacts(planet, uiManager); - return getActivatedArtifact(artifacts); +export function useActiveArtifact(planet: Wrapper): Artifact | undefined { + return useMemo(() => (planet.value ? planet.value.activeArtifact : undefined), [planet]); } -/** - * Create a subscription to the currently selected artifact. - * @param uiManager instance of GameUIManager - */ -export function useSelectedArtifact(uiManager: GameUIManager): Wrapper { - return useWrappedEmitter(uiManager.hoverArtifact$, undefined); -} - -export function useArtifact(uiManager: GameUIManager, artifactId: ArtifactId) { - const [artifact, setArtifact] = useState>( - new Wrapper(uiManager.getArtifactWithId(artifactId)) - ); - - useEmitterSubscribe( - uiManager.getGameManager().getGameObjects().artifactUpdated$, - (id: ArtifactId) => { - if (id === artifactId) { - setArtifact(new Wrapper(uiManager.getArtifactWithId(artifactId))); - } - }, - [uiManager, setArtifact, artifactId] - ); - - useEffect(() => { - setArtifact(new Wrapper(uiManager.getArtifactWithId(artifactId))); - }, [uiManager, artifactId]); - - return artifact; +export function useArtifact(_uiManager: GameUIManager, _artifactId: ArtifactId) { + // const [artifact, setArtifact] = useState>( + // new Wrapper(uiManager.getArtifactWithId(artifactId)) + // ); + + // useEmitterSubscribe( + // uiManager.getGameManager().getGameObjects().planetUpdated$, + // (planetId: LocationId) => { + // const planet = uiManager.getPlanetWithId(planetId); + // if (id === artifactId) { + // setArtifact(new Wrapper(uiManager.getArtifactWithId(artifactId))); + // } + // }, + // [uiManager, setArtifact, artifactId] + // ); + + // useEffect(() => { + // setArtifact(new Wrapper(uiManager.getArtifactWithId(artifactId))); + // }, [uiManager, artifactId]); + + // return artifact; + return new Wrapper(undefined) as unknown as Wrapper; } // TODO cache this globally diff --git a/client/src/Frontend/Utils/EmitterUtils.ts b/client/src/Frontend/Utils/EmitterUtils.ts index 76dd22a8..defc8bac 100644 --- a/client/src/Frontend/Utils/EmitterUtils.ts +++ b/client/src/Frontend/Utils/EmitterUtils.ts @@ -1,5 +1,4 @@ import { monomitter, Monomitter } from '@dfdao/events'; -import { isSpaceShip } from '@dfdao/gamelogic'; import { Artifact, EthAddress, Planet } from '@dfdao/types'; import _ from 'lodash'; @@ -136,10 +135,3 @@ export const getPlanetId = (p: Planet) => p.locationId; export const getPlanetOwner = (p: Planet) => p.owner; export const getArtifactId = (a: Artifact) => a.id; -export const getArtifactOwner = (a: Artifact) => { - if (isSpaceShip(a.artifactType)) { - return a.controller; - } - - return a.currentOwner; -}; diff --git a/client/src/Frontend/Utils/ShortcutConstants.ts b/client/src/Frontend/Utils/ShortcutConstants.ts index b2ed76f8..f810f7fb 100644 --- a/client/src/Frontend/Utils/ShortcutConstants.ts +++ b/client/src/Frontend/Utils/ShortcutConstants.ts @@ -6,12 +6,12 @@ export const CLOSE_MODAL = 't'; export const TOGGLE_SETTINGS_PANE = 'h'; export const TOGGLE_HELP_PANE = 'j'; export const TOGGLE_PLUGINS_PANE = 'k'; -export const TOGGLE_YOUR_ARTIFACTS_PANE = 'l'; +export const TOGGLE_YOUR_INVENTORY_PANE = 'l'; export const TOGGLE_YOUR_PLANETS_DEX_PANE = ';'; export const TOGGLE_TRANSACTIONS_PANE = "'"; // planet context pane shortcuts -export const TOGGLE_PLANET_ARTIFACTS_PANE = 's'; +export const TOGGLE_PLANET_INVENTORY_PANE = 's'; export const TOGGLE_HAT_PANE = 'x'; export const TOGGLE_ABANDON = 'r'; export const TOGGLE_WITHDRAW = ']'; diff --git a/client/src/Frontend/Utils/UIEmitter.ts b/client/src/Frontend/Utils/UIEmitter.ts index 34a85e3f..fe44e310 100644 --- a/client/src/Frontend/Utils/UIEmitter.ts +++ b/client/src/Frontend/Utils/UIEmitter.ts @@ -31,6 +31,7 @@ export const enum UIEmitterEvent { SelectArtifact = 'SelectArtifact', ShowArtifact = 'ShowArtifact', + ShowSpaceship = 'ShowSpaceship', } class UIEmitter extends EventEmitter { diff --git a/client/src/Frontend/Views/ArtifactLink.tsx b/client/src/Frontend/Views/ArtifactLink.tsx deleted file mode 100644 index ffe68c89..00000000 --- a/client/src/Frontend/Views/ArtifactLink.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { artifactName } from '@dfdao/procedural'; -import { Artifact, LocationId } from '@dfdao/types'; -import React, { useCallback, useEffect } from 'react'; -import { Link } from '../Components/CoreUI'; -import { ArtifactDetailsPane } from '../Panes/ArtifactDetailsPane'; -import dfstyles from '../Styles/dfstyles'; -import { useUIManager } from '../Utils/AppHooks'; -import { ModalHandle } from './ModalPane'; - -export function ArtifactLink({ - modal, - children, - artifact, - depositOn, -}: { - modal?: ModalHandle; - artifact: Artifact; - children: React.ReactNode | React.ReactNode[]; - depositOn?: LocationId; -}) { - const uiManager = useUIManager(); - - useEffect(() => { - // this is called when the component is unrendered - return () => uiManager?.setHoveringOverArtifact(undefined); - }, [uiManager]); - - const onClick = useCallback(() => { - uiManager?.setHoveringOverArtifact(undefined); - modal && - modal.push({ - element() { - return ( - - ); - }, - title: artifactName(artifact), - }); - }, [artifact, modal, depositOn, uiManager]); - - return ( - { - uiManager?.setHoveringOverArtifact(undefined); - }} - onMouseEnter={() => { - uiManager?.setHoveringOverArtifact(artifact.id); - }} - onMouseLeave={() => { - uiManager?.setHoveringOverArtifact(undefined); - }} - > - {children} - - ); -} diff --git a/client/src/Frontend/Views/ArtifactRow.tsx b/client/src/Frontend/Views/ArtifactRow.tsx index 76c09641..f142392b 100644 --- a/client/src/Frontend/Views/ArtifactRow.tsx +++ b/client/src/Frontend/Views/ArtifactRow.tsx @@ -1,6 +1,5 @@ -import { isSpaceShip } from '@dfdao/gamelogic'; import { Artifact } from '@dfdao/types'; -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect } from 'react'; import styled, { css } from 'styled-components'; import { ArtifactImage } from '../Components/ArtifactImage'; import { Spacer } from '../Components/CoreUI'; @@ -21,13 +20,13 @@ const thumbActive = css` background-color: ${dfstyles.colors.border}; `; -const StyledArtifactThumb = styled.div<{ active: boolean; enemy: boolean }>` +const StyledArtifactThumb = styled.div<{ active: boolean }>` min-width: 2.5em; min-height: 2.5em; width: 2.5em; height: 2.5em; - border: 1px solid ${({ enemy }) => (enemy ? dfstyles.colors.dfred : dfstyles.colors.borderDark)}; + border: 1px solid ${dfstyles.colors.borderDark}; border-radius: 4px; &:last-child { @@ -53,7 +52,7 @@ const StyledArtifactThumb = styled.div<{ active: boolean; enemy: boolean }>` ${({ active }) => active && thumbActive} `; -export function ArtifactThumb({ +function ArtifactThumb({ artifact, selectedArtifact, onArtifactChange, @@ -63,20 +62,16 @@ export function ArtifactThumb({ artifact: Artifact; }) { const uiManager = useUIManager(); - const enemy = useMemo(() => { - const account = uiManager.getAccount(); - if (isSpaceShip(artifact.artifactType)) { - return artifact?.controller !== account; - } - return false; - }, [artifact, uiManager]); const click = useCallback(() => { - if (!onArtifactChange || enemy) return; + if (!onArtifactChange) return; - if (artifact.id === selectedArtifact?.id) onArtifactChange(undefined); - else onArtifactChange(artifact); - }, [onArtifactChange, artifact, selectedArtifact, enemy]); + if (artifact.id === selectedArtifact?.id) { + onArtifactChange(undefined); + } else { + onArtifactChange(artifact); + } + }, [onArtifactChange, artifact, selectedArtifact]); useEffect(() => { // this is called when the component is unrendered @@ -86,7 +81,6 @@ export function ArtifactThumb({ return ( { uiManager?.setHoveringOverArtifact(artifact.id); diff --git a/client/src/Frontend/Views/GameWindowLayout.tsx b/client/src/Frontend/Views/GameWindowLayout.tsx index 9d59e959..7bef9e64 100644 --- a/client/src/Frontend/Views/GameWindowLayout.tsx +++ b/client/src/Frontend/Views/GameWindowLayout.tsx @@ -10,16 +10,15 @@ import { WindowWrapper, } from '../Components/GameWindowComponents'; import ControllableCanvas from '../Game/ControllableCanvas'; -import { ArtifactHoverPane } from '../Panes/ArtifactHoverPane'; import { CoordsPane } from '../Panes/CoordsPane'; import { DiagnosticsPane } from '../Panes/DiagnosticsPane'; import { ExplorePane } from '../Panes/ExplorePane'; import { HelpPane } from '../Panes/HelpPane'; import { HoverPlanetPane } from '../Panes/HoverPlanetPane'; +import { InvetoryPane } from '../Panes/InventoryPane'; import OnboardingPane from '../Panes/OnboardingPane'; import { PlanetContextPane } from '../Panes/PlanetContextPane'; import { PlanetDexPane } from '../Panes/PlanetDexPane'; -import { PlayerArtifactsPane } from '../Panes/PlayerArtifactsPane'; import { PluginLibraryPane } from '../Panes/PluginLibraryPane'; import { PrivatePane } from '../Panes/PrivatePane'; import { SettingsPane } from '../Panes/SettingsPane'; @@ -70,8 +69,8 @@ export function GameWindowLayout({ const [planetdexVisible, setPlanetdexVisible] = useState( isModalOpen(ModalName.PlanetDex) ); - const [playerArtifactsVisible, setPlayerArtifactsVisible] = useState( - isModalOpen(ModalName.YourArtifacts) + const [playerInventoryVisible, setPlayerInventoryVisible] = useState( + isModalOpen(ModalName.YourInventory) ); const [twitterVerifyVisible, setTwitterVerifyVisible] = useState( isModalOpen(ModalName.TwitterVerify) @@ -150,9 +149,9 @@ export function GameWindowLayout({ onOpenPrivate={() => setPrivateVisible(true)} /> setPrivateVisible(false)} /> - setPlayerArtifactsVisible(false)} + setPlayerInventoryVisible(false)} /> @@ -196,7 +195,6 @@ export function GameWindowLayout({ - diff --git a/client/src/Frontend/Views/ModalIcon.tsx b/client/src/Frontend/Views/ModalIcon.tsx index 30db8233..15bd4f14 100644 --- a/client/src/Frontend/Views/ModalIcon.tsx +++ b/client/src/Frontend/Views/ModalIcon.tsx @@ -28,7 +28,7 @@ const icon = (modal: ModalName): React.ReactNode => { else if (modal === ModalName.Hats) return ; else if (modal === ModalName.Settings) return ; else if (modal === ModalName.Plugins) return ; - else if (modal === ModalName.YourArtifacts) return ; + else if (modal === ModalName.YourInventory) return ; else if (modal === ModalName.WithdrawSilver) return ; else if (modal === ModalName.TransactionLog) return ; return T; diff --git a/client/src/Frontend/Views/PlanetCard.tsx b/client/src/Frontend/Views/PlanetCard.tsx index c9d24546..2e0010bd 100644 --- a/client/src/Frontend/Views/PlanetCard.tsx +++ b/client/src/Frontend/Views/PlanetCard.tsx @@ -22,6 +22,8 @@ import { SpeedText, } from '../Components/Labels/PlanetLabels'; import { Sub } from '../Components/Text'; +import { ArtifactHoverPane } from '../Panes/ArtifactHoverPane'; +import { SpaceshipHoverPane } from '../Panes/SpaceshipHoverPane'; import { PlanetIcons } from '../Renderers/PlanetscapeRenderer/PlanetIcons'; import dfstyles, { snips } from '../Styles/dfstyles'; import { useActiveArtifact, usePlanetArtifacts, useUIManager } from '../Utils/AppHooks'; @@ -66,9 +68,9 @@ export function PlanetCard({ standalone?: boolean; }) { const uiManager = useUIManager(); - const active = useActiveArtifact(p, uiManager); + const active = useActiveArtifact(p); const planet = p.value; - const artifacts = usePlanetArtifacts(p, uiManager); + const artifacts = usePlanetArtifacts(p); const spaceJunkEnabled = uiManager.getSpaceJunkEnabled(); const isAbandoning = useEmitterValue(uiManager.isAbandoning$, uiManager.isAbandoning()); @@ -76,6 +78,9 @@ export function PlanetCard({ return ( <> + {/* TODO: This tooltip stuff is FUBAR and doesn't work if you try to use it from elsewhere */} + + {standalone && ( diff --git a/client/src/Frontend/Views/SendResources.tsx b/client/src/Frontend/Views/SendResources.tsx index be088bf6..58d79923 100644 --- a/client/src/Frontend/Views/SendResources.tsx +++ b/client/src/Frontend/Views/SendResources.tsx @@ -1,6 +1,7 @@ -import { formatNumber, isSpaceShip } from '@dfdao/gamelogic'; +import { formatNumber } from '@dfdao/gamelogic'; +import { nameOfArtifact, nameOfSpaceship } from '@dfdao/procedural'; import { isUnconfirmedMoveTx, isUnconfirmedReleaseTx } from '@dfdao/serde'; -import { Artifact, artifactNameFromArtifact, Planet, TooltipName } from '@dfdao/types'; +import { Artifact, Planet, Spaceship, TooltipName } from '@dfdao/types'; import React, { useCallback } from 'react'; import styled from 'styled-components'; import { Wrapper } from '../../Backend/Utils/Wrapper'; @@ -19,6 +20,7 @@ import { useEmitterValue } from '../Utils/EmitterHooks'; import { useOnUp } from '../Utils/KeyEmitters'; import { TOGGLE_ABANDON, TOGGLE_SEND, TOGGLE_WITHDRAW } from '../Utils/ShortcutConstants'; import { SelectArtifactRow } from './ArtifactRow'; +import { SelectSpaceshipRow } from './SpaceshipRow'; const StyledSendResources = styled.div` display: flex; @@ -201,30 +203,33 @@ function ExtractButton({ function SendRow({ toggleSending, artifact, + spaceship, sending, abandoning, disabled = false, }: { toggleSending: () => void; artifact: Artifact | undefined; + spaceship: Spaceship | undefined; sending: boolean; abandoning?: boolean; disabled?: boolean; }) { let content = 'Send'; if (artifact) { - const artifactName = artifactNameFromArtifact(artifact); - if (isSpaceShip(artifact.artifactType)) { - // Call it "Move" with a spaceship, instead of "Send" - content = `Move ${artifactName}`; - } else { - // Only add the "+" if we are sending Energy & Artifact - content += ` + ${artifactName}`; - } + const artifactName = nameOfArtifact(artifact); + // Only add the "+" if we are sending Energy & Artifact + content += ` + ${artifactName}`; } if (abandoning) { content += ' and Abandon'; } + // Spaceship overrides everything + if (spaceship) { + const spaceshipName = nameOfSpaceship(spaceship); + // Call it "Move" with a spaceship, instead of "Send" + content = `Move ${spaceshipName}`; + } /* Explicitly avoid binding to `onShortcutPressed` so we can support sending on subpanes */ return ( { + if (!locationId) return; + uiManager.setSpaceshipSending(locationId, sendSpaceship); + }, + [uiManager, locationId] + ); // this variable is an array of 10 elements. each element is a key. whenever the user presses a // key, we set the amount of energy that we're sending to be proportional to how late in the array @@ -324,10 +337,7 @@ export function SendResources({ [uiManager, locationId, updateEnergySending] ); - const artifacts = usePlanetInactiveArtifacts(p, uiManager); - const spaceshipsYouOwn = artifacts.filter( - (a) => isSpaceShip(a.artifactType) && a.controller === account - ); + const artifacts = usePlanetInactiveArtifacts(p); let abandonRow; if (p.value && p.value.transactions?.hasTransaction(isUnconfirmedReleaseTx)) { @@ -359,6 +369,7 @@ export function SendResources({ sendRow = ( )} - {spaceshipsYouOwn.length > 0 || owned ? sendRow : null} + {p.value && p.value.spaceships.length > 0 && ( + + )} + {isSendingShip || owned ? sendRow : null} {p.value && p.value.silver > 0 && extractRow} diff --git a/client/src/Frontend/Views/SidebarPane.tsx b/client/src/Frontend/Views/SidebarPane.tsx index 3e068ae2..dfa36561 100644 --- a/client/src/Frontend/Views/SidebarPane.tsx +++ b/client/src/Frontend/Views/SidebarPane.tsx @@ -9,7 +9,7 @@ import { TOGGLE_PLUGINS_PANE, TOGGLE_SETTINGS_PANE, TOGGLE_TRANSACTIONS_PANE, - TOGGLE_YOUR_ARTIFACTS_PANE, + TOGGLE_YOUR_INVENTORY_PANE, TOGGLE_YOUR_PLANETS_DEX_PANE, } from '../Utils/ShortcutConstants'; import { ModalToggleButton } from './ModalIcon'; @@ -18,14 +18,14 @@ export function SidebarPane({ settingsHook, helpHook, pluginsHook, - yourArtifactsHook, + yourInventoryHook, planetdexHook, transactionLogHook, }: { settingsHook: Hook; helpHook: Hook; pluginsHook: Hook; - yourArtifactsHook: Hook; + yourInventoryHook: Hook; planetdexHook: Hook; transactionLogHook: Hook; }) { @@ -65,12 +65,12 @@ export function SidebarPane({ /> ` + min-width: 2.5em; + min-height: 2.5em; + width: 2.5em; + height: 2.5em; + + border: 1px solid ${({ enemy }) => (enemy ? dfstyles.colors.dfred : dfstyles.colors.borderDark)}; + border-radius: 4px; + + &:last-child { + margin-right: none; + } + + display: inline-flex; + flex-direction: row; + justify-content: space-around; + align-items: center; + + background: ${dfstyles.colors.artifactBackground}; + + &:hover { + ${thumbActive} + cursor: pointer; + + & > div { + filter: brightness(1.2); + } + } + + ${({ active }) => active && thumbActive} +`; + +function SpaceshipThumb({ + spaceship, + selectedSpaceship, + onSpaceshipChange, +}: { + spaceship: Spaceship; + selectedSpaceship?: Spaceship | undefined; + onSpaceshipChange?: (spaceship: Spaceship | undefined) => void; +}) { + const uiManager = useUIManager(); + const mySpaceships = useMySpaceshipsList(uiManager); + const mine = mySpaceships.some(({ id }) => spaceship.id === id); + + const click = useCallback(() => { + if (!onSpaceshipChange || !mine) return; + + if (spaceship.id === selectedSpaceship?.id) { + onSpaceshipChange(undefined); + } else { + onSpaceshipChange(spaceship); + } + }, [onSpaceshipChange, spaceship, selectedSpaceship, mine]); + + useEffect(() => { + // this is called when the component is unrendered + return () => uiManager?.setHoveringOverSpaceship(undefined); + }, [uiManager]); + + return ( + { + uiManager?.setHoveringOverSpaceship(spaceship.id); + }} + onMouseLeave={() => { + uiManager?.setHoveringOverSpaceship(undefined); + }} + > + + + ); +} + +export function SelectSpaceshipRow({ + spaceships, + selectedSpaceship, + onSpaceshipChange, +}: { + spaceships: Spaceship[]; + selectedSpaceship?: Spaceship | undefined; + onSpaceshipChange?: (spaceship: Spaceship | undefined) => void; +}) { + return ( + + {spaceships.length > 0 && + spaceships.map((spaceship) => ( + + + + + ))} + + ); +} diff --git a/client/src/_types/darkforest/api/ContractsAPITypes.ts b/client/src/_types/darkforest/api/ContractsAPITypes.ts index 9e7663de..603b6801 100644 --- a/client/src/_types/darkforest/api/ContractsAPITypes.ts +++ b/client/src/_types/darkforest/api/ContractsAPITypes.ts @@ -52,6 +52,7 @@ export const enum ContractEvent { PlanetCaptured = 'PlanetCaptured', LocationRevealed = 'LocationRevealed', ArtifactFound = 'ArtifactFound', + SpaceshipFound = 'SpaceshipFound', ArtifactDeposited = 'ArtifactDeposited', ArtifactWithdrawn = 'ArtifactWithdrawn', ArtifactActivated = 'ArtifactActivated', @@ -66,9 +67,11 @@ export const enum ContractEvent { export const enum ContractsAPIEvent { PlayerUpdate = 'PlayerUpdate', PlanetUpdate = 'PlanetUpdate', + ArtifactWithdrawn = 'ArtifactWithdrawn', + ArtifactDeposited = 'ArtifactDeposited', + SpaceshipFound = 'SpaceshipFound', PauseStateChanged = 'PauseStateChanged', ArrivalQueued = 'ArrivalQueued', - ArtifactUpdate = 'ArtifactUpdate', RadiusUpdated = 'RadiusUpdated', LocationRevealed = 'LocationRevealed', /** diff --git a/client/vite.config.ts b/client/vite.config.ts index 647016b1..ee1b9cf8 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -46,6 +46,7 @@ export default defineConfig(async ({ mode }) => { envPrefix: 'DF_', clearScreen: false, optimizeDeps: { + force: true, include: Array.from(all().keys()).filter((name) => !privateWorkspaces.includes(name)), }, }; diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index e5d65d46..b2a587ff 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -233,7 +233,6 @@ enum ArtifactRarity { Mythic } - enum Biome { Unknown, Ocean, diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts index c7f0c37a..67d0ec46 100644 --- a/packages/constants/src/index.ts +++ b/packages/constants/src/index.ts @@ -31,6 +31,7 @@ import { GasPrices, LocationId, PlanetLevel, + SpaceshipType, } from '@dfdao/types'; import bigInt, { BigInteger } from 'big-integer'; @@ -82,16 +83,16 @@ export const MIN_ARTIFACT_TYPE = ArtifactType.Monolith; /** * The value of the maximum, valid artifact type */ -export const MAX_ARTIFACT_TYPE = ArtifactType.ShipTitan; +export const MAX_ARTIFACT_TYPE = ArtifactType.BlackDomain; /** * The value of the minimum, valid spaceship type */ -export const MIN_SPACESHIP_TYPE = ArtifactType.ShipMothership; +export const MIN_SPACESHIP_TYPE = SpaceshipType.ShipMothership; /** * The value of the maximum, valid spaceship type */ -export const MAX_SPACESHIP_TYPE = ArtifactType.ShipTitan; +export const MAX_SPACESHIP_TYPE = SpaceshipType.ShipTitan; /** * The value of the minimum, valid artifact rarity diff --git a/packages/gamelogic/src/artifact.ts b/packages/gamelogic/src/artifact.ts index 2119567f..61a7a774 100644 --- a/packages/gamelogic/src/artifact.ts +++ b/packages/gamelogic/src/artifact.ts @@ -1,4 +1,3 @@ -import { EMPTY_ADDRESS, MAX_SPACESHIP_TYPE, MIN_SPACESHIP_TYPE } from '@dfdao/constants'; import { hashToInt } from '@dfdao/serde'; import { Abstract, @@ -10,10 +9,7 @@ import { ArtifactTypeNames, Biome, BiomeNames, - EthAddress, - Planet, PlanetLevel, - PlanetType, RenderedArtifact, } from '@dfdao/types'; @@ -34,62 +30,16 @@ export function isBasic(type: ArtifactType): boolean { return ArtifactType.Monolith <= type && type <= ArtifactType.Pyramid; } -export function isSpaceShip(type: ArtifactType | undefined): boolean { - return type !== undefined && type >= MIN_SPACESHIP_TYPE && type <= MAX_SPACESHIP_TYPE; -} - export function hasStatBoost(type: ArtifactType | undefined): boolean { return ( - !isSpaceShip(type) && type !== ArtifactType.BlackDomain && type !== ArtifactType.BloomFilter && type !== ArtifactType.Wormhole ); } -const artifactCooldownHoursMap = { - [ArtifactType.Unknown]: 24, - [ArtifactType.Monolith]: 0, - [ArtifactType.Colossus]: 0, - [ArtifactType.Spaceship]: 0, - [ArtifactType.Pyramid]: 0, - [ArtifactType.Wormhole]: 4, - [ArtifactType.PlanetaryShield]: 4, - [ArtifactType.PhotoidCannon]: 24, - [ArtifactType.BloomFilter]: 24, - [ArtifactType.BlackDomain]: 24, -} as const; - const artifactIsAncientMap: Map = new Map(); -export function durationUntilArtifactAvailable(artifact: Artifact) { - return artifactAvailableTimestamp(artifact) - Date.now(); -} - -export function artifactAvailableTimestamp(artifact: Artifact) { - if (artifact.lastDeactivated === 0) { - return Date.now(); - } - - const availableAtTimestampMs = - artifact.lastDeactivated * 1000 + - artifactCooldownHoursMap[artifact.artifactType] * 60 * 60 * 1000; - - return availableAtTimestampMs; -} - -export function isActivated(artifact: Artifact | undefined) { - if (artifact === undefined) { - return false; - } - - return artifact.lastActivated > artifact.lastDeactivated; -} - -export function getActivatedArtifact(artifacts: Artifact[]): Artifact | undefined { - return artifacts.find(isActivated); -} - export function getArtifactDebugName(a?: Artifact): string { if (!a) { return 'unknown artifact'; @@ -134,8 +84,6 @@ export function artifactRoll(id: ArtifactId): number { export function isAncient(artifact: RenderedArtifact): boolean { if (forceAncient !== undefined) return forceAncient; - if (isSpaceShip(artifact.artifactType)) return false; - const { id, planetBiome: biome } = artifact; if (artifactIsAncientMap.has(id)) { @@ -167,21 +115,6 @@ export function artifactFileName( ): string { const { artifactType: type, rarity, planetBiome: biome, id } = artifact; - if (isSpaceShip(type)) { - switch (type) { - case ArtifactType.ShipWhale: - return '64-whale.png'; - case ArtifactType.ShipMothership: - return '64-mothership.png'; - case ArtifactType.ShipCrescent: - return '64-crescent.png'; - case ArtifactType.ShipGear: - return '64-gear.png'; - case ArtifactType.ShipTitan: - return '64-titan.png'; - } - } - const size = thumb ? '16' : '64'; const ext = videoMode ? 'webm' : 'png'; @@ -219,72 +152,7 @@ export function artifactFileName( export function getActiveBlackDomain(artifacts: Artifact[]): Artifact | undefined { for (const artifact of artifacts) { - if (artifact.artifactType === ArtifactType.BlackDomain && isActivated(artifact)) - return artifact; + if (artifact.artifactType === ArtifactType.BlackDomain) return artifact; } return undefined; } - -export const dateMintedAt = (artifact: Artifact | undefined): string => { - if (!artifact) return '00/00/0000'; - return new Date(artifact.mintedAtTimestamp * 1000).toDateString(); -}; - -export function canActivateArtifact( - artifact: Artifact, - planet: Planet | undefined, - artifactsOnPlanet: Artifact[] -) { - if (isSpaceShip(artifact.artifactType)) { - return ( - planet && - planet.owner === EMPTY_ADDRESS && - artifact.artifactType === ArtifactType.ShipCrescent && - artifact.activations === 0 - ); - } - - const available = artifactAvailableTimestamp(artifact); - if (available !== undefined) { - const now = Date.now(); - const anyArtifactActive = artifactsOnPlanet.some((a) => isActivated(a)); - const waitUntilAvailable = available - now; - const availableToActivate = - waitUntilAvailable <= -0 && - !anyArtifactActive && - planet?.locationId === artifact.onPlanetId && - !!artifact.onPlanetId; - return availableToActivate; - } - - return false; -} - -export function canWithdrawArtifact(account: EthAddress, artifact: Artifact, planet?: Planet) { - return ( - planet && - !planet.destroyed && - planet.owner === account && - planet.planetType === PlanetType.TRADING_POST && - !isActivated(artifact) && - !isSpaceShip(artifact.artifactType) - ); -} - -export function canDepositArtifact(account: EthAddress, artifact: Artifact, planet?: Planet) { - return ( - planet && - !planet.destroyed && - planet.owner === account && - !artifact.onPlanetId && - planet.planetType === PlanetType.TRADING_POST - ); -} - -export function getPlayerControlledSpaceships( - artifacts: (Artifact | undefined)[] | undefined, - owner: EthAddress | undefined -) { - if (!owner) return []; - return (artifacts || []).filter((a) => a?.controller === owner); -} diff --git a/packages/gamelogic/src/index.ts b/packages/gamelogic/src/index.ts index 8e636179..70b1d03f 100644 --- a/packages/gamelogic/src/index.ts +++ b/packages/gamelogic/src/index.ts @@ -1,3 +1,4 @@ export * from './artifact'; export * from './number'; export * from './planet'; +export * from './spaceship'; diff --git a/packages/gamelogic/src/spaceship.ts b/packages/gamelogic/src/spaceship.ts new file mode 100644 index 00000000..b45fb1c9 --- /dev/null +++ b/packages/gamelogic/src/spaceship.ts @@ -0,0 +1,18 @@ +import { RenderedSpaceship, SpaceshipType } from '@dfdao/types'; + +export function spaceshipFileName(spaceship: RenderedSpaceship): string | undefined { + const { spaceshipType: type } = spaceship; + + switch (type) { + case SpaceshipType.ShipWhale: + return '64-whale.png'; + case SpaceshipType.ShipMothership: + return '64-mothership.png'; + case SpaceshipType.ShipCrescent: + return '64-crescent.png'; + case SpaceshipType.ShipGear: + return '64-gear.png'; + case SpaceshipType.ShipTitan: + return '64-titan.png'; + } +} diff --git a/packages/procedural/src/ArtifactProcgen.ts b/packages/procedural/src/ArtifactProcgen.ts index 63c81294..38ef9eeb 100644 --- a/packages/procedural/src/ArtifactProcgen.ts +++ b/packages/procedural/src/ArtifactProcgen.ts @@ -2,21 +2,104 @@ import { EMPTY_ADDRESS, EMPTY_LOCATION_ID } from '@dfdao/constants'; import { Artifact, ArtifactId, - artifactNameFromArtifact, ArtifactRarity, ArtifactType, Biome, + Spaceship, + SpaceshipId, } from '@dfdao/types'; -const namesById = new Map(); +const godGrammar = { + god1: [ + "c'", + 'za', + "ry'", + "ab'", + "bak'", + "dt'", + "ek'", + "fah'", + "q'", + 'qo', + 'van', + 'bow', + 'gui', + 'si', + ], + god2: [ + 'thun', + 'tchalla', + 'thovo', + 'saron', + 'zoth', + 'sharrj', + 'thulu', + 'ra', + 'wer', + 'doin', + 'renstad', + 'nevere', + 'goth', + 'anton', + 'layton', + ], +}; + +/** + * Deterministically generates the name of the artifact from its ID. + * + * @param artifact The artifact to generate a name for + */ +export function nameOfArtifact(artifact: Artifact) { + const idNum = parseInt(artifact.id, 16); + + const roll1 = (idNum % 7919) % godGrammar.god1.length; // 7919 is a big prime + const roll2 = (idNum % 7883) % godGrammar.god2.length; // 7883 is a big prime + + const name = godGrammar.god1[roll1] + godGrammar.god2[roll2]; + const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1); + + return nameCapitalized; +} +/** + * Deterministically generates the name of the spaceship from its ID. + * + * @param spaceship The spaceship to generate a name for + */ +export function nameOfSpaceship(spaceship: Spaceship) { + const idNum = parseInt(spaceship.id, 16); + + const roll1 = (idNum % 7919) % godGrammar.god1.length; // 7919 is a big prime + const roll2 = (idNum % 7883) % godGrammar.god2.length; // 7883 is a big prime + + const name = godGrammar.god1[roll1] + godGrammar.god2[roll2]; + const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1); + + return nameCapitalized; +} + +const artifactNamesById = new Map(); export const artifactName = (artifact: Artifact | undefined): string => { if (!artifact) return 'Unknown'; - const myName = namesById.get(artifact.id); + const myName = artifactNamesById.get(artifact.id); + if (myName) return myName; + + const name = nameOfArtifact(artifact); + artifactNamesById.set(artifact.id, name); + + return name; +}; + +const spaceshipNamesById = new Map(); +export const spaceshipName = (spaceship: Spaceship | undefined): string => { + if (!spaceship) return 'Unknown'; + + const myName = spaceshipNamesById.get(spaceship.id); if (myName) return myName; - const name = artifactNameFromArtifact(artifact); - namesById.set(artifact.id, name); + const name = nameOfSpaceship(spaceship); + spaceshipNamesById.set(spaceship.id, name); return name; }; diff --git a/packages/renderer/src/Entities/PlanetRenderManager.ts b/packages/renderer/src/Entities/PlanetRenderManager.ts index 46ad45a3..200b2e02 100644 --- a/packages/renderer/src/Entities/PlanetRenderManager.ts +++ b/packages/renderer/src/Entities/PlanetRenderManager.ts @@ -1,5 +1,5 @@ import { EMPTY_ADDRESS } from '@dfdao/constants'; -import { formatNumber, getRange, hasOwner, isLocatable, isSpaceShip } from '@dfdao/gamelogic'; +import { formatNumber, getRange, hasOwner, isLocatable } from '@dfdao/gamelogic'; import { getOwnerColorVec, getPlanetCosmetic } from '@dfdao/procedural'; import { isUnconfirmedMoveTx } from '@dfdao/serde'; import { @@ -12,6 +12,7 @@ import { PlanetRenderManagerType, PlanetType, RendererType, + Spaceship, TextAlign, TextAnchor, WorldCoords, @@ -53,9 +54,6 @@ export class PlanetRenderManager implements PlanetRenderManagerType { textAlpha *= renderInfo.radii.radiusPixels / (2 * maxRadius); } - const artifacts = uiManager - .getArtifactsWithIds(planet.heldArtifactIds) - .filter((a) => !!a) as Artifact[]; const color = uiManager.isOwnedByMe(planet) ? whiteA : getOwnerColorVec(planet); // draw planet body @@ -63,7 +61,15 @@ export class PlanetRenderManager implements PlanetRenderManagerType { this.queueAsteroids(planet, planet.location.coords, renderInfo.radii.radiusWorld); this.queueArtifactsAroundPlanet( planet, - artifacts, + planet.artifacts, + planet.location.coords, + renderInfo.radii.radiusWorld, + now, + textAlpha + ); + this.queueSpaceshipsAroundPlanet( + planet, + planet.spaceships, planet.location.coords, renderInfo.radii.radiusWorld, now, @@ -143,10 +149,8 @@ export class PlanetRenderManager implements PlanetRenderManagerType { now: number, alpha: number ) { - const numArtifacts = artifacts.length; - const MS_PER_ROTATION = 10 * 1000 * (planet.planetLevel + 1); - const anglePerArtifact = (Math.PI * 2) / numArtifacts; + const anglePerArtifact = (Math.PI * 2) / artifacts.length; const startingAngle = 0 - Math.PI / 2; const nowAngle = (Math.PI * 2 * (now % MS_PER_ROTATION)) / MS_PER_ROTATION; const artifactSize = 0.67 * radiusW; @@ -173,6 +177,41 @@ export class PlanetRenderManager implements PlanetRenderManagerType { ); } } + private queueSpaceshipsAroundPlanet( + planet: Planet, + spaceships: Spaceship[], + centerW: WorldCoords, + radiusW: number, + now: number, + alpha: number + ) { + const MS_PER_ROTATION = 10 * 1000 * (planet.planetLevel + 1); + const anglePerArtifact = (Math.PI * 2) / spaceships.length; + const startingAngle = 0 - Math.PI / 2; + const nowAngle = (Math.PI * 2 * (now % MS_PER_ROTATION)) / MS_PER_ROTATION; + const artifactSize = 0.67 * radiusW; + const distanceRadiusScale = 1.5; + const distanceFromCenterOfPlanet = radiusW * distanceRadiusScale + artifactSize; + + for (let i = 0; i < spaceships.length; i++) { + const x = + Math.cos(anglePerArtifact * i + startingAngle + nowAngle) * distanceFromCenterOfPlanet + + centerW.x; + const y = + Math.sin(anglePerArtifact * i + startingAngle + nowAngle) * distanceFromCenterOfPlanet + + centerW.y; + + this.renderer.spriteRenderer.queueSpaceshipWorld( + spaceships[i], + { x, y }, + artifactSize, + alpha, + undefined, + undefined, + this.renderer.getViewport() + ); + } + } private drawPlanetMessages( renderInfo: PlanetRenderInfo, @@ -449,8 +488,7 @@ export class PlanetRenderManager implements PlanetRenderManagerType { range: { energy }, } = engineConsts.colors; const { x, y } = planet.location.coords; - const sendingArtifact = this.renderer.context.getArtifactSending(planet.locationId); - const sendingSpaceShip = isSpaceShip(sendingArtifact?.artifactType); + const sendingSpaceShip = this.renderer.context.getSpaceshipSending(planet.locationId); if (sendingSpaceShip) return; diff --git a/packages/renderer/src/Entities/SpriteRenderer.ts b/packages/renderer/src/Entities/SpriteRenderer.ts index 9c890876..1d48bb0b 100644 --- a/packages/renderer/src/Entities/SpriteRenderer.ts +++ b/packages/renderer/src/Entities/SpriteRenderer.ts @@ -5,6 +5,7 @@ import { CanvasCoords, GameViewport, RenderedArtifact, + RenderedSpaceship, RendererType, RGBAVec, RGBVec, @@ -18,6 +19,7 @@ import { loadArtifactAtlas, loadArtifactThumbAtlas, spriteFromArtifact, + spriteFromSpaceship, SpriteRectangle, } from '../TextureManager'; import { GenericRenderer } from '../WebGL/GenericRenderer'; @@ -91,14 +93,28 @@ export class SpriteRenderer const rarity = artifact.rarity; if (rarity >= ArtifactRarity.Rare) { - this.queueOutline(artifact, pos, width, alpha, theta); + this.queueArtifactOutline(artifact, pos, width, alpha, theta); } if (artifact.transactions?.hasTransaction(isUnconfirmedMoveTx)) { alpha = 127; } - this.queueSprite(artifact, pos, width, alpha, color, atFrame, theta); + this.queueArtifactSprite(artifact, pos, width, alpha, color, atFrame, theta); + } + public queueSpaceship( + spaceship: RenderedSpaceship, + pos: CanvasCoords, + width = 128, + alpha = 255, + color: RGBVec | undefined = undefined, + theta: number | undefined = undefined + ) { + if (spaceship.transactions?.hasTransaction(isUnconfirmedMoveTx)) { + alpha = 127; + } + + this.queueSpaceshipSprite(spaceship, pos, width, alpha, color, theta); } /** Queue artifact to worldcoords, centered */ @@ -126,8 +142,31 @@ export class SpriteRenderer theta ); } + /** Queue spaceship to worldcoords, centered */ + public queueSpaceshipWorld( + spaceship: RenderedSpaceship, + posW: CanvasCoords, + widthW: number, + alpha = 255, + color: RGBVec | undefined = undefined, + theta: number | undefined = undefined, + viewport: GameViewport + ) { + const pos = viewport.worldToCanvasCoords(posW); + const width = viewport.worldToCanvasDist(widthW); + const displayedW = Math.max(width, 4); - public queueSprite( + this.queueSpaceship( + spaceship, + { x: pos.x - width / 2, y: pos.y - width / 2 }, + displayedW, + alpha, + color, + theta + ); + } + + public queueArtifactSprite( artifact: RenderedArtifact, topLeft: CanvasCoords, width: number, @@ -206,8 +245,71 @@ export class SpriteRenderer } this.verts += 6; } + public queueSpaceshipSprite( + spaceship: RenderedSpaceship, + topLeft: CanvasCoords, + width: number, + alpha: number, + color: RGBVec | undefined = undefined, + theta: number | undefined = undefined // rotate around [w/2, w/2] + ) { + if (!this.loaded) return; + + const { + position: posA, + texcoord: texA, + rectPos: rectPosA, + color: colorA, + shine: shineA, + invert: invertA, + mythic: mythicA, + } = this.attribManagers; + + /* set up attributes */ + // we'll always want pixel-perfect icons + const { x, y } = { x: Math.floor(topLeft.x), y: Math.floor(topLeft.y) }; + + const shineLoc = -1000; + + const tex: SpriteRectangle = spriteFromSpaceship(spaceship); + + const { x1, x2, y1, y2 } = tex; + + const dim = width; + EngineUtils.makeQuadVec2Buffered(this.posBuffer, 0, 0, dim, dim); + + if (theta !== undefined) { + EngineUtils.translateQuadVec2(this.posBuffer, [-dim / 2, -dim / 2]); + EngineUtils.rotateQuadVec2(this.posBuffer, theta); + EngineUtils.translateQuadVec2(this.posBuffer, [dim / 2, dim / 2]); + } + + EngineUtils.translateQuadVec2(this.posBuffer, [x, y]); + + if (this.flip) { + EngineUtils.makeQuadVec2Buffered(this.texBuffer, x1, 1 - y1, x2, 1 - y2); + } else { + EngineUtils.makeQuadVec2Buffered(this.texBuffer, x1, y1, x2, y2); + } + + // 0, 0, 0 is a special color; the program looks for it + const myColor: RGBAVec = [...(color || [0, 0, 0]), alpha]; + + /* buffer attributes */ + posA.setVertex(this.posBuffer, this.verts); + texA.setVertex(this.texBuffer, this.verts); + rectPosA.setVertex(this.rectposBuffer, this.verts); + + for (let i = 0; i < 6; i++) { + colorA.setVertex(myColor, this.verts + i); + shineA.setVertex([shineLoc], this.verts + i); + invertA.setVertex([0], this.verts + i); + mythicA.setVertex([0], this.verts + i); + } + this.verts += 6; + } - public queueOutline( + public queueArtifactOutline( artifact: RenderedArtifact, { x, y }: CanvasCoords, width: number, @@ -218,17 +320,49 @@ export class SpriteRenderer const s = this.thumb ? width / 16 : width / 64; const iters = this.thumb ? 1 : 2; for (let del = s; del <= iters * s; del += s) { - this.queueSprite(artifact, { x, y: y - del }, width, alpha, color, undefined, theta); - this.queueSprite(artifact, { x, y: y + del }, width, alpha, color, undefined, theta); - this.queueSprite(artifact, { x: x + del, y }, width, alpha, color, undefined, theta); - this.queueSprite(artifact, { x: x - del, y }, width, alpha, color, undefined, theta); + this.queueArtifactSprite(artifact, { x, y: y - del }, width, alpha, color, undefined, theta); + this.queueArtifactSprite(artifact, { x, y: y + del }, width, alpha, color, undefined, theta); + this.queueArtifactSprite(artifact, { x: x + del, y }, width, alpha, color, undefined, theta); + this.queueArtifactSprite(artifact, { x: x - del, y }, width, alpha, color, undefined, theta); } if (iters === 2) { - this.queueSprite(artifact, { x: x - 1, y: y - 1 }, width, alpha, color, undefined, theta); - this.queueSprite(artifact, { x: x - 1, y: y + 1 }, width, alpha, color, undefined, theta); - this.queueSprite(artifact, { x: x + 1, y: y - 1 }, width, alpha, color, undefined, theta); - this.queueSprite(artifact, { x: x + 1, y: y + 1 }, width, alpha, color, undefined, theta); + this.queueArtifactSprite( + artifact, + { x: x - 1, y: y - 1 }, + width, + alpha, + color, + undefined, + theta + ); + this.queueArtifactSprite( + artifact, + { x: x - 1, y: y + 1 }, + width, + alpha, + color, + undefined, + theta + ); + this.queueArtifactSprite( + artifact, + { x: x + 1, y: y - 1 }, + width, + alpha, + color, + undefined, + theta + ); + this.queueArtifactSprite( + artifact, + { x: x + 1, y: y + 1 }, + width, + alpha, + color, + undefined, + theta + ); } } diff --git a/packages/renderer/src/Entities/VoyageRenderer.ts b/packages/renderer/src/Entities/VoyageRenderer.ts index cd8c40b9..b870be69 100644 --- a/packages/renderer/src/Entities/VoyageRenderer.ts +++ b/packages/renderer/src/Entities/VoyageRenderer.ts @@ -116,16 +116,21 @@ export class VoyageRenderer implements VoyageRendererType { const fleetRadius = 4; const artifactSizePixels = 20; cR.queueCircleWorldCenterOnly(shipsLocation, fleetRadius, voyageColor); - if (voyage.artifactId) { - const artifact = gameUIManager.getArtifactWithId(voyage.artifactId); - if (artifact) { - const viewport = this.renderer.getViewport(); - const screenCoords = viewport.worldToCanvasCoords(shipsLocation); - const distanceFromCenterOfFleet = fleetRadius * 1.5 + artifactSizePixels; - const x = distanceFromCenterOfFleet + screenCoords.x; - const y = screenCoords.y; - sR.queueArtifact(artifact, { x, y }, artifactSizePixels); - } + if (voyage.artifact) { + const viewport = this.renderer.getViewport(); + const screenCoords = viewport.worldToCanvasCoords(shipsLocation); + const distanceFromCenterOfFleet = fleetRadius * 1.5 + artifactSizePixels; + const x = distanceFromCenterOfFleet + screenCoords.x; + const y = screenCoords.y; + sR.queueArtifact(voyage.artifact, { x, y }, artifactSizePixels); + } + if (voyage.spaceship) { + const viewport = this.renderer.getViewport(); + const screenCoords = viewport.worldToCanvasCoords(shipsLocation); + const distanceFromCenterOfFleet = fleetRadius * 1.5 + artifactSizePixels; + const x = distanceFromCenterOfFleet + screenCoords.x; + const y = screenCoords.y; + sR.queueSpaceship(voyage.spaceship, { x, y }, artifactSizePixels); } // queue text @@ -175,10 +180,7 @@ export class VoyageRenderer implements VoyageRendererType { for (const voyage of voyages) { const nowS = now / 1000; if (nowS < voyage.arrivalTime) { - const isMyVoyage = - voyage.player === gameUIManager.getAccount() || - gameUIManager.getArtifactWithId(voyage.artifactId)?.controller === - gameUIManager.getPlayer()?.address; + const isMyVoyage = voyage.player === gameUIManager.getAccount(); const isShipVoyage = voyage.player === EMPTY_ADDRESS; const sender = gameUIManager.getPlayer(voyage.player); this.drawVoyagePath(voyage.fromPlanet, voyage.toPlanet, true, isMyVoyage, isShipVoyage); diff --git a/packages/renderer/src/Entities/WormholeRenderer.ts b/packages/renderer/src/Entities/WormholeRenderer.ts index e56e2ea6..e332e7fa 100644 --- a/packages/renderer/src/Entities/WormholeRenderer.ts +++ b/packages/renderer/src/Entities/WormholeRenderer.ts @@ -25,8 +25,8 @@ export class WormholeRenderer implements WormholeRendererType { ); } - for (const wormhole of gameUIManager.getWormholes()) { - this.drawVoyagePath(wormhole.from, wormhole.to, true); + for (const [from, to] of gameUIManager.getWormholes()) { + this.drawVoyagePath(from, to, true); } } diff --git a/packages/renderer/src/Renderer.ts b/packages/renderer/src/Renderer.ts index 79eac86a..50534dc6 100644 --- a/packages/renderer/src/Renderer.ts +++ b/packages/renderer/src/Renderer.ts @@ -1,6 +1,4 @@ import { - Artifact, - ArtifactId, AsteroidRendererType, BackgroundRendererType, BaseRenderer, @@ -37,6 +35,7 @@ import { RuinsRendererType, Setting, SpaceRendererType, + Spaceship, SpacetimeRipRendererType, SpaceType, SpriteRendererType, @@ -49,7 +48,6 @@ import { VoyageRendererType, WorldCoords, WorldLocation, - Wormhole, WormholeRendererType, } from '@dfdao/types'; import autoBind from 'auto-bind'; @@ -125,11 +123,9 @@ export interface RendererGameContext extends DiagnosticUpdater { getUnconfirmedMoves(): Transaction[]; spaceTypeFromPerlin(perlin: number): SpaceType; getPerlinConfig(isBiome: boolean): PerlinConfig; - getArtifactWithId(artifactId: ArtifactId | undefined): Artifact | undefined; getSpaceTypePerlin(coords: WorldCoords, floor: boolean): number; getPerlinThresholds(): [number, number, number]; isOwnedByMe(planet: Planet): boolean; - getArtifactsWithIds(artifactIds: ArtifactId[]): Array; getSelectedPlanet(): LocatablePlanet | undefined; getHoveringOverPlanet(): Planet | undefined; getHoveringOverCoords(): WorldCoords | undefined; @@ -142,7 +138,7 @@ export interface RendererGameContext extends DiagnosticUpdater { energy: number ): number; getIsChoosingTargetPlanet(): boolean; - getWormholes(): Iterable; + getWormholes(): Iterable<[LocationId, LocationId]>; getRadiusOfPlanetLevel(planetRarity: PlanetLevel): number; getDistCoords(from: WorldCoords, to: WorldCoords): number; isOverOwnPlanet(coords: WorldCoords): Planet | undefined; @@ -152,7 +148,7 @@ export interface RendererGameContext extends DiagnosticUpdater { drawAllRunningPlugins(ctx: CanvasRenderingContext2D): void; isSendingShip(planetId: LocationId): boolean; isAbandoning(): boolean; - getArtifactSending(planetId: LocationId): Artifact | undefined; + getSpaceshipSending(planetId: LocationId): Spaceship | undefined; getAbandonRangeChangePercent(): number; getCaptureZones(): Iterable; } diff --git a/packages/renderer/src/TextureManager.ts b/packages/renderer/src/TextureManager.ts index e2daa04c..37851f53 100644 --- a/packages/renderer/src/TextureManager.ts +++ b/packages/renderer/src/TextureManager.ts @@ -1,6 +1,15 @@ import { MAX_ARTIFACT_TYPE, MAX_BIOME, MIN_ARTIFACT_TYPE } from '@dfdao/constants'; -import { isAncient, isBasic, isRelic, isSpaceShip } from '@dfdao/gamelogic'; -import { ArtifactId, ArtifactRarity, ArtifactType, Biome, RenderedArtifact } from '@dfdao/types'; +import { isAncient, isBasic, isRelic } from '@dfdao/gamelogic'; +import { + ArtifactId, + ArtifactRarity, + ArtifactType, + Biome, + RenderedArtifact, + RenderedSpaceship, + SpaceshipId, + SpaceshipType, +} from '@dfdao/types'; export const ARTIFACTS_URL = '/sprites/artifacts.png'; export const ARTIFACTS_THUMBS_URL = '/sprites/artifactthumbs.png'; @@ -160,16 +169,7 @@ export function spriteFromArtifact(artifact: RenderedArtifact): SpriteRectangle if (artifactSpriteMap.has(id)) return artifactSpriteMap.get(id) || EMPTY_SPRITE; - if (isSpaceShip(artifact.artifactType)) { - const idx = { - [ArtifactType.ShipMothership]: 0, - [ArtifactType.ShipCrescent]: 1, - [ArtifactType.ShipWhale]: 2, - [ArtifactType.ShipGear]: 3, - [ArtifactType.ShipTitan]: 4, - }; - return spriteRectangleFromIndex(idx[artifact.artifactType], 11); - } else if (isAncient(artifact)) { + if (isAncient(artifact)) { const info = ancientSpriteLocs[type]; return isShiny(rarity) ? info.shiny : info.normal; @@ -180,3 +180,21 @@ export function spriteFromArtifact(artifact: RenderedArtifact): SpriteRectangle return isShiny(rarity) ? info.shiny : info.normal; } } + +const spaceshipSpriteMap: Map = new Map(); + +const spaceshipSpriteLocs = { + [SpaceshipType.ShipMothership]: 0, + [SpaceshipType.ShipCrescent]: 1, + [SpaceshipType.ShipWhale]: 2, + [SpaceshipType.ShipGear]: 3, + [SpaceshipType.ShipTitan]: 4, +}; + +export function spriteFromSpaceship(spaceship: RenderedSpaceship): SpriteRectangle { + const { id, spaceshipType: type } = spaceship; + + if (spaceshipSpriteMap.has(id)) return spaceshipSpriteMap.get(id) || EMPTY_SPRITE; + + return spriteRectangleFromIndex(spaceshipSpriteLocs[type], 11); +} diff --git a/packages/serde/src/arrival.ts b/packages/serde/src/arrival.ts index def66148..d9fd8733 100644 --- a/packages/serde/src/arrival.ts +++ b/packages/serde/src/arrival.ts @@ -2,8 +2,9 @@ import { CONTRACT_PRECISION } from '@dfdao/constants'; import type { DarkForest } from '@dfdao/contracts/typechain'; import type { ArrivalType, QueuedArrival, VoyageId } from '@dfdao/types'; import { address } from './address'; -import { artifactIdFromEthersBN } from './artifact'; +import { decodeArtifact } from './artifact'; import { locationIdFromDecStr } from './location'; +import { decodeSpaceship } from './spaceship'; export type RawArrival = Awaited>; @@ -25,9 +26,12 @@ export function decodeArrival(rawArrival: RawArrival): QueuedArrival { departureTime: rawArrival.departureTime.toNumber(), arrivalTime: rawArrival.arrivalTime.toNumber(), distance: rawArrival.distance.toNumber(), - artifactId: rawArrival.carriedArtifactId.eq(0) + artifact: rawArrival.carriedArtifactId.eq(0) ? undefined - : artifactIdFromEthersBN(rawArrival.carriedArtifactId), + : decodeArtifact(rawArrival.carriedArtifactId), + spaceship: rawArrival.carriedSpaceshipId.eq(0) + ? undefined + : decodeSpaceship(rawArrival.carriedSpaceshipId), arrivalType: rawArrival.arrivalType as ArrivalType, }; diff --git a/packages/serde/src/artifact.ts b/packages/serde/src/artifact.ts index f7ab204e..4a7d33ce 100644 --- a/packages/serde/src/artifact.ts +++ b/packages/serde/src/artifact.ts @@ -1,11 +1,16 @@ import type { DarkForest } from '@dfdao/contracts/typechain'; -import type { Artifact, ArtifactId, ArtifactPointValues, VoyageId } from '@dfdao/types'; -import { ArtifactRarity, ArtifactType, Biome } from '@dfdao/types'; +import { + Artifact, + ArtifactId, + ArtifactInfo, + ArtifactPointValues, + ArtifactRarity, + ArtifactType, + Biome, + TokenType, +} from '@dfdao/types'; import bigInt from 'big-integer'; -import type { BigNumber as EthersBN } from 'ethers'; -import { address } from './address'; -import { locationIdFromDecStr, locationIdFromEthersBN } from './location'; -import { decodeUpgrade } from './upgrade'; +import { BigNumber as EthersBN, utils } from 'ethers'; /** * Converts a possibly 0x-prefixed string of hex digits to an `ArtifactId`: a @@ -64,6 +69,10 @@ export function artifactIdToDecStr(artifactId: ArtifactId): string { return bigInt(artifactId, 16).toString(10); } +export function artifactIdToEthersBN(artifactId: ArtifactId): EthersBN { + return EthersBN.from(artifactIdToDecStr(artifactId)); +} + export type RawArtifactPointValues = Awaited>; /** @@ -84,37 +93,45 @@ export function decodeArtifactPointValues( }; } -export type RawArtifactWithMetadata = Awaited>; +function calculateByteUInt(tokenId: EthersBN, startByte: number, endByte: number) { + const token = utils.arrayify(tokenId); + let byteUInt = 0; + for (let i = startByte; i <= endByte; i++) { + byteUInt += token[i] * 256 ** (endByte - i); + } + return byteUInt; +} /** - * Converts the raw typechain result of `ArtifactTypes.ArtifactWithMetadata` - * struct to an `Artifact` typescript typed object (see @dfdao/types). + * Converts the raw Token ID to an `Artifact` typescript typed object (see @dfdao/types). * - * @param rawArtifactWithMetadata Raw data of an `ArtifactWithMetadata` struct, - * returned from a blockchain call (assumed to be typed with typechain). + * @param tokenId Raw `tokenId` representing an `Artifact` struct */ -export function decodeArtifact(rawArtifactWithMetadata: RawArtifactWithMetadata): Artifact { - const { artifact, owner, upgrade, timeDelayedUpgrade, locationId, voyageId } = - rawArtifactWithMetadata; +export function decodeArtifact(tokenId: EthersBN): Artifact { + // These account for unknown at the 0-th index + const tokenIdx = ArtifactInfo.TokenType - 1; + const rarityIdx = ArtifactInfo.ArtifactRarity - 1; + const typeIdx = ArtifactInfo.ArtifactType - 1; + const biomeIdx = ArtifactInfo.Biome - 1; + + const _tokenType = calculateByteUInt(tokenId, tokenIdx, tokenIdx); + const rarity = calculateByteUInt(tokenId, rarityIdx, rarityIdx); + const artifactType = calculateByteUInt(tokenId, typeIdx, typeIdx); + const biome = calculateByteUInt(tokenId, biomeIdx, biomeIdx); return { - isInititalized: artifact.isInitialized, - id: artifactIdFromEthersBN(artifact.id), - planetDiscoveredOn: locationIdFromDecStr(artifact.planetDiscoveredOn.toString()), - rarity: artifact.rarity as ArtifactRarity, - planetBiome: artifact.planetBiome as Biome, - mintedAtTimestamp: artifact.mintedAtTimestamp.toNumber(), - discoverer: address(artifact.discoverer), - artifactType: artifact.artifactType as ArtifactType, - activations: artifact.activations.toNumber(), - lastActivated: artifact.lastActivated.toNumber(), - lastDeactivated: artifact.lastDeactivated.toNumber(), - controller: address(artifact.controller), - wormholeTo: artifact.wormholeTo.eq(0) ? undefined : locationIdFromEthersBN(artifact.wormholeTo), - currentOwner: address(owner), - upgrade: decodeUpgrade(upgrade), - timeDelayedUpgrade: decodeUpgrade(timeDelayedUpgrade), - onPlanetId: locationId.eq(0) ? undefined : locationIdFromEthersBN(locationId), - onVoyageId: voyageId.eq(0) ? undefined : (voyageId.toString() as VoyageId), + id: artifactIdFromEthersBN(tokenId), + rarity: rarity as ArtifactRarity, + planetBiome: biome as Biome, + artifactType: artifactType as ArtifactType, }; } + +export function isArtifact(tokenId: EthersBN): boolean { + // These account for unknown at the 0-th index + const tokenIdx = ArtifactInfo.TokenType - 1; + + const tokenType = calculateByteUInt(tokenId, tokenIdx, tokenIdx); + + return tokenType === TokenType.Artifact; +} diff --git a/packages/serde/src/index.ts b/packages/serde/src/index.ts index 13fcf182..2eea831e 100644 --- a/packages/serde/src/index.ts +++ b/packages/serde/src/index.ts @@ -33,5 +33,6 @@ export * from './location'; export * from './planet'; export * from './player'; export * from './reveal'; +export * from './spaceship'; export * from './transactions'; export * from './upgrade'; diff --git a/packages/serde/src/planet.ts b/packages/serde/src/planet.ts index 28d8a9b9..e29fec89 100644 --- a/packages/serde/src/planet.ts +++ b/packages/serde/src/planet.ts @@ -10,7 +10,9 @@ import type { SpaceType, } from '@dfdao/types'; import { address } from './address'; -import { locationIdFromDecStr } from './location'; +import { decodeArtifact } from './artifact'; +import { locationIdFromDecStr, locationIdFromEthersBN } from './location'; +import { decodeSpaceship } from './spaceship'; export type RawPlanet = Awaited>; @@ -21,7 +23,7 @@ export type RawPlanet = Awaited>; * aware of, such as `unconfirmedDepartures`, (2) store derived data that is * calculated later by the client, such as `silverSpent` and `bonus`, or (3) * store data which must be added later from the results of additional contract - * calls, such as `coordsRevealed` and `heldArtifactIds`. Therefore this + * calls, such as `coordsRevealed`. Therefore this * function may not be very useful to you outside of the specific context of the * provided Dark Forest web client. * @@ -80,7 +82,16 @@ export function decodePlanet(rawLocationId: string, rawPlanet: RawPlanet): Plane ? undefined : rawPlanet.prospectedBlockNumber.toNumber(), destroyed: rawPlanet.destroyed, - heldArtifactIds: [], // this is stale and will be updated in GameObjects + artifacts: rawPlanet.artifacts.map(decodeArtifact), + spaceships: rawPlanet.spaceships.map(decodeSpaceship), + // TODO: convert to milliseconds + artifactActivationTime: rawPlanet.artifactActivationTime.toNumber(), + activeArtifact: rawPlanet.activeArtifact.eq(0) + ? undefined + : decodeArtifact(rawPlanet.activeArtifact), + wormholeTo: rawPlanet.wormholeTo.eq(0) + ? undefined + : locationIdFromEthersBN(rawPlanet.wormholeTo), bonus: bonusFromHex(locationId), pausers: rawPlanet.pausers.toNumber(), energyGroDoublers: rawPlanet.energyGroDoublers.toNumber(), diff --git a/packages/serde/src/spaceship.ts b/packages/serde/src/spaceship.ts new file mode 100644 index 00000000..639b933b --- /dev/null +++ b/packages/serde/src/spaceship.ts @@ -0,0 +1,101 @@ +import { Spaceship, SpaceshipId, SpaceshipInfo, SpaceshipType, TokenType } from '@dfdao/types'; +import bigInt from 'big-integer'; +import { BigNumber as EthersBN, utils } from 'ethers'; + +/** + * Converts a possibly 0x-prefixed string of hex digits to an `SpaceshipId`: a + * non-0x-prefixed all lowercase hex string of exactly 64 hex characters + * (0-padded if necessary). Spaceship IDs should only be instantiated through + * `spaceshipIdFromHexStr`, `spaceshipIdFromDecStr`, and `spaceshipIdFromEthersBN`. + * + * @param spaceshipId Possibly 0x-prefixed, possibly unpadded hex `string` + * representation of an spaceship's ID. + */ +export function spaceshipIdFromHexStr(spaceshipId: string): SpaceshipId { + const spaceshipIdBI = bigInt(spaceshipId, 16); + let ret = spaceshipIdBI.toString(16); + if (ret.length > 64) throw new Error('not a valid spaceship id'); + while (ret.length < 64) ret = '0' + ret; + return ret as SpaceshipId; +} + +/** + * Converts a string representing a decimal number into an SpaceshipID: a + * non-0x-prefixed all lowercase hex string of exactly 64 hex characters + * (0-padded if necessary). Spaceship IDs should only be instantiated through + * `spaceshipIdFromHexStr`, `spaceshipIdFromDecStr`, and `spaceshipIdFromEthersBN`. + * + * @param spaceshipId `string` of decimal digits, the base 10 representation of an + * spaceship ID. + */ +export function spaceshipIdFromDecStr(spaceshipId: string): SpaceshipId { + const locationBI = bigInt(spaceshipId); + let ret = locationBI.toString(16); + while (ret.length < 64) ret = '0' + ret; + return ret as SpaceshipId; +} + +/** + * Converts a ethers.js BigNumber (type aliased here as EthersBN) representing a + * decimal number into an SpaceshipID: a non-0x-prefixed all lowercase hex string + * of exactly 64 hex characters (0-padded if necessary). Spaceship IDs should only + * be instantiated through `spaceshipIdFromHexStr`, `spaceshipIdFromDecStr`, and + * `spaceshipIdFromEthersBN`. + * + * @param spaceshipId ether.js `BigNumber` representing spaceship's ID + */ +export function spaceshipIdFromEthersBN(spaceshipId: EthersBN): SpaceshipId { + return spaceshipIdFromDecStr(spaceshipId.toString()); +} + +/** + * Converts an SpaceshipID to a decimal string with equivalent numerical value; + * can be used if you need to pass an spaceship ID into a web3 call. + * + * @param spaceshipId non-0x-prefixed lowercase hex `string` of 64 hex characters + * representing an spaceship's ID + */ +export function spaceshipIdToDecStr(spaceshipId: SpaceshipId): string { + return bigInt(spaceshipId, 16).toString(10); +} + +export function spaceshipIdToEthersBN(spaceshipId: SpaceshipId): EthersBN { + return EthersBN.from(spaceshipIdToDecStr(spaceshipId)); +} + +function calculateByteUInt(tokenId: EthersBN, startByte: number, endByte: number) { + const token = utils.arrayify(tokenId); + let byteUInt = 0; + for (let i = startByte; i <= endByte; i++) { + byteUInt += token[i] * 256 ** (endByte - i); + } + return byteUInt; +} + +/** + * Converts the raw Token ID to an `Spaceship` typescript typed object (see @dfdao/types). + * + * @param tokenId Raw `tokenId` representing an `Spaceship` struct + */ +export function decodeSpaceship(tokenId: EthersBN): Spaceship { + // These account for unknown at the 0-th index + const tokenIdx = SpaceshipInfo.TokenType - 1; + const typeIdx = SpaceshipInfo.SpaceshipType - 1; + + const _tokenType = calculateByteUInt(tokenId, tokenIdx, tokenIdx); + const spaceshipType = calculateByteUInt(tokenId, typeIdx, typeIdx); + + return { + id: spaceshipIdFromEthersBN(tokenId), + spaceshipType: spaceshipType as SpaceshipType, + }; +} + +export function isSpaceship(tokenId: EthersBN): boolean { + // These account for unknown at the 0-th index + const tokenIdx = SpaceshipInfo.TokenType - 1; + + const tokenType = calculateByteUInt(tokenId, tokenIdx, tokenIdx); + + return tokenType === TokenType.Spaceship; +} diff --git a/packages/serde/src/upgrade.ts b/packages/serde/src/upgrade.ts index 729e8d52..bfdb6688 100644 --- a/packages/serde/src/upgrade.ts +++ b/packages/serde/src/upgrade.ts @@ -2,7 +2,7 @@ import type { DarkForest } from '@dfdao/contracts/typechain'; import type { Upgrade, UpgradeBranches } from '@dfdao/types'; // Sort of duplicate of RawArtifactWithMetadata to avoid circular dependency -export type RawUpgrade = Awaited>['upgrade']; +export type RawUpgrade = Awaited>; export type RawUpgradesBranches = Awaited>; /** diff --git a/packages/types/src/arrival.ts b/packages/types/src/arrival.ts index 7cc0d421..0c20ae9e 100644 --- a/packages/types/src/arrival.ts +++ b/packages/types/src/arrival.ts @@ -1,4 +1,6 @@ -import type { ArtifactId, EthAddress, LocationId, VoyageId } from './identifier'; +import type { Artifact } from './artifact'; +import type { EthAddress, LocationId, VoyageId } from './identifier'; +import type { Spaceship } from './spaceship'; import type { Abstract } from './utility'; /** @@ -11,7 +13,8 @@ export interface QueuedArrival { toPlanet: LocationId; energyArriving: number; silverMoved: number; - artifactId?: ArtifactId; + artifact?: Artifact; + spaceship?: Spaceship; departureTime: number; distance: number; arrivalTime: number; diff --git a/packages/types/src/artifact.ts b/packages/types/src/artifact.ts index 44d28735..c5666fe4 100644 --- a/packages/types/src/artifact.ts +++ b/packages/types/src/artifact.ts @@ -1,9 +1,24 @@ import type { Biome } from './game_types'; -import type { ArtifactId, EthAddress, LocationId, VoyageId } from './identifier'; +import type { ArtifactId } from './identifier'; import type { TransactionCollection } from './transaction'; -import type { Upgrade } from './upgrade'; import type { Abstract } from './utility'; +/** + * Abstract type representing an artifact info. + */ +export type ArtifactInfo = Abstract; + +/** + * Enumeration of artifact info. + */ +export const ArtifactInfo = { + Unknown: 0 as ArtifactInfo, + TokenType: 1 as ArtifactInfo, + ArtifactRarity: 2 as ArtifactInfo, + ArtifactType: 3 as ArtifactInfo, + Biome: 4 as ArtifactInfo, +} as const; + /** * Abstract type representing an artifact type. */ @@ -23,11 +38,6 @@ export const ArtifactType = { PhotoidCannon: 7 as ArtifactType, BloomFilter: 8 as ArtifactType, BlackDomain: 9 as ArtifactType, - ShipMothership: 10 as ArtifactType, - ShipCrescent: 11 as ArtifactType, - ShipWhale: 12 as ArtifactType, - ShipGear: 13 as ArtifactType, - ShipTitan: 14 as ArtifactType, // Don't forget to update MIN_ARTIFACT_TYPE and/or MAX_ARTIFACT_TYPE in the `constants` package } as const; @@ -46,11 +56,6 @@ export const ArtifactTypeNames = { [ArtifactType.BlackDomain]: 'Black Domain', [ArtifactType.PhotoidCannon]: 'Photoid Cannon', [ArtifactType.BloomFilter]: 'Bloom Filter', - [ArtifactType.ShipMothership]: 'Mothership', - [ArtifactType.ShipCrescent]: 'Crescent', - [ArtifactType.ShipWhale]: 'Whale', - [ArtifactType.ShipGear]: 'Gear', - [ArtifactType.ShipTitan]: 'Titan', } as const; /** @@ -96,84 +101,14 @@ export type ArtifactPointValues = { [ArtifactRarity: number]: number }; * client that can't send transactions, these fields should be ignored. */ export type Artifact = { - isInititalized: boolean; id: ArtifactId; - planetDiscoveredOn: LocationId; rarity: ArtifactRarity; planetBiome: Biome; - mintedAtTimestamp: number; - discoverer: EthAddress; artifactType: ArtifactType; - activations: number; - lastActivated: number; - lastDeactivated: number; - controller: EthAddress; - - upgrade: Upgrade; - timeDelayedUpgrade: Upgrade; - currentOwner: EthAddress; // owner of the NFT - can be the contract - wormholeTo?: LocationId; - onPlanetId?: LocationId; - onVoyageId?: VoyageId; transactions?: TransactionCollection; }; -// TODO: get this out of here - -const godGrammar = { - god1: [ - "c'", - 'za', - "ry'", - "ab'", - "bak'", - "dt'", - "ek'", - "fah'", - "q'", - 'qo', - 'van', - 'bow', - 'gui', - 'si', - ], - god2: [ - 'thun', - 'tchalla', - 'thovo', - 'saron', - 'zoth', - 'sharrj', - 'thulu', - 'ra', - 'wer', - 'doin', - 'renstad', - 'nevere', - 'goth', - 'anton', - 'layton', - ], -}; - -/** - * Deterministically generates the name of the artifact from its ID. - * - * @param artifact The artifact to generate a name for - */ -export function artifactNameFromArtifact(artifact: Artifact) { - const idNum = parseInt(artifact.id, 16); - - const roll1 = (idNum % 7919) % godGrammar.god1.length; // 7919 is a big prime - const roll2 = (idNum % 7883) % godGrammar.god2.length; // 7883 is a big prime - - const name = godGrammar.god1[roll1] + godGrammar.god2[roll2]; - const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1); - - return nameCapitalized; -} - /** * type interface for ERC721 metadata. */ @@ -196,8 +131,3 @@ export interface RenderedArtifact extends Partial { rarity: ArtifactRarity; id: ArtifactId; // for rolls } - -export type Wormhole = { - from: LocationId; - to: LocationId; -}; diff --git a/packages/types/src/identifier.ts b/packages/types/src/identifier.ts index 16b9cfbf..252d3dfe 100644 --- a/packages/types/src/identifier.ts +++ b/packages/types/src/identifier.ts @@ -31,3 +31,11 @@ export type EthAddress = Abstract; * in `serde`. */ export type ArtifactId = Abstract; + +/** + * A unique identifier for a Dark Forest NFT spaceship. This is a 64-character + * lowercase hex string not prefixed with 0x. Spacehip IDs should only be + * instantiated through `spaceshipIdFromHexStr`, `spaceshipIdFromDecStr`, + * and `spaceshipIdFromEthersBN` in `serde`. + */ +export type SpaceshipId = Abstract; diff --git a/packages/types/src/modal.ts b/packages/types/src/modal.ts index 86533ef9..10f3dc72 100644 --- a/packages/types/src/modal.ts +++ b/packages/types/src/modal.ts @@ -21,7 +21,7 @@ export const ModalName = { Broadcast: 'Broadcast' as ModalName, Hats: 'Hats' as ModalName, Settings: 'Settings' as ModalName, - YourArtifacts: 'YourArtifacts' as ModalName, + YourInventory: 'YourInventory' as ModalName, ManageArtifacts: 'ManageArtifacts' as ModalName, Plugins: 'Plugins' as ModalName, PluginWarning: 'PluginWarning' as ModalName, diff --git a/packages/types/src/planet.ts b/packages/types/src/planet.ts index 9d0dd15b..ac0f28b2 100644 --- a/packages/types/src/planet.ts +++ b/packages/types/src/planet.ts @@ -1,6 +1,8 @@ +import type { Artifact } from './artifact'; import type { Biome, SpaceType } from './game_types'; -import type { ArtifactId, EthAddress, LocationId } from './identifier'; +import type { EthAddress, LocationId } from './identifier'; import type { PlanetMessage } from './planetmessage'; +import type { Spaceship } from './spaceship'; import type { TransactionCollection } from './transaction'; import type { Upgrade, UpgradeState } from './upgrade'; import type { Abstract } from './utility'; @@ -117,7 +119,12 @@ export type Planet = { lastUpdated: number; upgradeState: UpgradeState; hasTriedFindingArtifact: boolean; - heldArtifactIds: ArtifactId[]; + artifacts: Artifact[]; + spaceships: Spaceship[]; + activeArtifact: Artifact | undefined; + artifactActivationTime: number; + wormholeTo: LocationId | undefined; + destroyed: boolean; prospectedBlockNumber?: number; localPhotoidUpgrade?: Upgrade; diff --git a/packages/types/src/renderer.ts b/packages/types/src/renderer.ts index 9eeda685..fa78618f 100644 --- a/packages/types/src/renderer.ts +++ b/packages/types/src/renderer.ts @@ -3,6 +3,7 @@ import type { RenderedArtifact } from './artifact'; import type { HatType } from './hat'; import type { LocationId } from './identifier'; import type { LocatablePlanet, Planet } from './planet'; +import type { RenderedSpaceship } from './spaceship'; import type { Abstract } from './utility'; import type { Chunk, WorldCoords } from './world'; @@ -368,6 +369,15 @@ export interface SpriteRendererType { theta?: number | undefined, viewport?: GameViewport ): void; + queueSpaceshipWorld( + artifact: RenderedSpaceship, + posW: CanvasCoords, + widthW: number, + alpha?: number, + color?: RGBVec | undefined, + theta?: number | undefined, + viewport?: GameViewport + ): void; /** * The game calls the queue function when the entities should be put into a back buffer queue. @@ -391,6 +401,14 @@ export interface SpriteRendererType { color?: RGBVec | undefined, theta?: number | undefined ): void; + queueSpaceship( + artifact: RenderedSpaceship, + pos: CanvasCoords, + width?: number, + alpha?: number, + color?: RGBVec | undefined, + theta?: number | undefined + ): void; /** * Draws all queued Artifacts. diff --git a/packages/types/src/spaceship.ts b/packages/types/src/spaceship.ts index 027df98a..892787e8 100644 --- a/packages/types/src/spaceship.ts +++ b/packages/types/src/spaceship.ts @@ -1,12 +1,28 @@ -import { Abstract } from './utility'; +import type { SpaceshipId } from './identifier'; +import type { TransactionCollection } from './transaction'; +import type { Abstract } from './utility'; /** - * Abstract type representing an artifact type. + * Abstract type representing an spaceship token info. + */ +export type SpaceshipInfo = Abstract; + +/** + * Enumeration of spaceship token info. + */ +export const SpaceshipInfo = { + Unknown: 0 as SpaceshipInfo, + TokenType: 1 as SpaceshipInfo, + SpaceshipType: 2 as SpaceshipInfo, +} as const; + +/** + * Abstract type representing an spaceship type. */ export type SpaceshipType = Abstract; /** - * Enumeration of artifact types. + * Enumeration of spaceship types. */ export const SpaceshipType = { Unknown: 0 as SpaceshipType, @@ -16,7 +32,7 @@ export const SpaceshipType = { ShipGear: 4 as SpaceshipType, ShipTitan: 5 as SpaceshipType, - // Don't forget to update MIN_ARTIFACT_TYPE and/or MAX_ARTIFACT_TYPE in the `constants` package + // Don't forget to update MIN_SPACESHIP_TYPE and/or MAX_SPACESHIP_TYPE in the `constants` package } as const; /** @@ -30,3 +46,18 @@ export const SpaceshipTypeNames = { [SpaceshipType.ShipGear]: 'Gear', [SpaceshipType.ShipTitan]: 'Titan', } as const; + +/** + * Represents data associated with a Dark Forest spaceship NFT. + */ +export type Spaceship = { + id: SpaceshipId; + spaceshipType: SpaceshipType; + + transactions?: TransactionCollection; +}; + +export interface RenderedSpaceship extends Partial { + spaceshipType: SpaceshipType; + id: SpaceshipId; // for rolls +} diff --git a/packages/types/src/transactions.ts b/packages/types/src/transactions.ts index d6c068d9..b4f392ce 100644 --- a/packages/types/src/transactions.ts +++ b/packages/types/src/transactions.ts @@ -1,6 +1,6 @@ import type { Contract } from 'ethers'; import type { LiteralUnion } from 'type-fest'; -import type { ArtifactId, EthAddress, LocationId } from './identifier'; +import type { ArtifactId, EthAddress, LocationId, SpaceshipId } from './identifier'; import type { WorldLocation } from './world'; export type ContractMethodName = @@ -65,7 +65,8 @@ export type UnconfirmedMove = TxIntent & { forces: number; silver: number; abandoning: boolean; - artifact?: ArtifactId; + // TODO: Make this less shit + artifact?: ArtifactId | SpaceshipId; }; /** From e505c7fba86c821bceb8b26ac08b322164aded81 Mon Sep 17 00:00:00 2001 From: Henry Caron Date: Wed, 5 Oct 2022 10:04:57 +0100 Subject: [PATCH 52/55] feat: extract all button withdraws silver --- client/src/Backend/GameLogic/GameUIManager.ts | 10 -- .../Components/CapturePlanetButton.tsx | 118 +++++++++--------- .../src/Frontend/Panes/PlanetContextPane.tsx | 6 +- client/src/Frontend/Views/ArtifactRow.tsx | 40 ++++-- client/src/Frontend/Views/SendResources.tsx | 35 ++---- packages/gamelogic/src/artifact.ts | 5 + 6 files changed, 103 insertions(+), 111 deletions(-) diff --git a/client/src/Backend/GameLogic/GameUIManager.ts b/client/src/Backend/GameLogic/GameUIManager.ts index d9a50e28..50cc9c61 100644 --- a/client/src/Backend/GameLogic/GameUIManager.ts +++ b/client/src/Backend/GameLogic/GameUIManager.ts @@ -398,16 +398,6 @@ class GameUIManager extends EventEmitter { } public withdrawSilver(locationId: LocationId, amount: number) { - const dontShowWarningStorageKey = `${this.getAccount()?.toLowerCase()}-withdrawnWarningAcked`; - - if (localStorage.getItem(dontShowWarningStorageKey) !== 'true') { - localStorage.setItem(dontShowWarningStorageKey, 'true'); - const confirmationText = - `Are you sure you want withdraw this silver? Once you withdraw it, you ` + - `cannot deposit it again. Your withdrawn silver amount will be added to your score. You'll only see this warning once!`; - if (!confirm(confirmationText)) return; - } - this.gameManager.withdrawSilver(locationId, amount); } diff --git a/client/src/Frontend/Components/CapturePlanetButton.tsx b/client/src/Frontend/Components/CapturePlanetButton.tsx index a3ca1535..52fbf433 100644 --- a/client/src/Frontend/Components/CapturePlanetButton.tsx +++ b/client/src/Frontend/Components/CapturePlanetButton.tsx @@ -118,67 +118,65 @@ export function CapturePlanetButton({ }, [gameManager, planetWrapper]); return ( - - {shouldShow && ( - <> - {shouldShowInvade && ( - + {shouldShowInvade && ( + + Invade this planet. } > - Invade this planet. } - > - {invading ? : 'Invade'} - - - )} - - {shouldShowCapture && ( - : 'Invade'} + + + )} + + {shouldShowCapture && ( + + + + Capture this planet for score!{' '} + {!!blocksLeft && blocksLeft >= 0 && ( + <> + You must wait {blocksLeft} blocks until you can capture this + planet. + + )} + {!planetHasEnoughEnergy && ( + The planet requires above 80% energy before you can capture it. + )} + + + } > - - - Capture this planet for score!{' '} - {!!blocksLeft && blocksLeft >= 0 && ( - <> - You must wait {blocksLeft} blocks until you can capture - this planet. - - )} - {!planetHasEnoughEnergy && ( - The planet requires above 80% energy before you can capture it. - )} - - - } - > - {capturing ? : 'Capture!'} - - - )} - - )} - + {capturing ? : 'Capture!'} + + + )} + + ) ); } diff --git a/client/src/Frontend/Panes/PlanetContextPane.tsx b/client/src/Frontend/Panes/PlanetContextPane.tsx index 2043084b..e8000190 100644 --- a/client/src/Frontend/Panes/PlanetContextPane.tsx +++ b/client/src/Frontend/Panes/PlanetContextPane.tsx @@ -5,7 +5,11 @@ import { Wrapper } from '../../Backend/Utils/Wrapper'; import { CapturePlanetButton } from '../Components/CapturePlanetButton'; import { VerticalSplit } from '../Components/CoreUI'; import { MineArtifactButton } from '../Components/MineArtifactButton'; -import { OpenPlanetInfoButton, OpenUpgradeDetailsPaneButton } from '../Components/OpenPaneButtons'; +import { + OpenManagePlanetInventoryButton, + OpenPlanetInfoButton, + OpenUpgradeDetailsPaneButton, +} from '../Components/OpenPaneButtons'; import { snips } from '../Styles/dfstyles'; import { useAccount, useSelectedPlanet, useUIManager } from '../Utils/AppHooks'; import { useEmitterSubscribe } from '../Utils/EmitterHooks'; diff --git a/client/src/Frontend/Views/ArtifactRow.tsx b/client/src/Frontend/Views/ArtifactRow.tsx index f142392b..2b43fb0a 100644 --- a/client/src/Frontend/Views/ArtifactRow.tsx +++ b/client/src/Frontend/Views/ArtifactRow.tsx @@ -1,7 +1,9 @@ +import { isActivated } from '@dfdao/gamelogic'; import { Artifact } from '@dfdao/types'; import React, { useCallback, useEffect } from 'react'; import styled, { css } from 'styled-components'; import { ArtifactImage } from '../Components/ArtifactImage'; +import { Btn } from '../Components/Btn'; import { Spacer } from '../Components/CoreUI'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; @@ -104,18 +106,30 @@ export function SelectArtifactRow({ artifacts: Artifact[]; }) { return ( - - {artifacts.length > 0 && - artifacts.map((a) => ( - - - - - ))} - +
+ + {artifacts.length > 0 && + artifacts.map((a) => ( + + + + + ))} + + {owned && ( + + {isActivated(selectedArtifact, planet) ? 'deactivate' : 'activate'} + + )} +
); } diff --git a/client/src/Frontend/Views/SendResources.tsx b/client/src/Frontend/Views/SendResources.tsx index 58d79923..bcc58e69 100644 --- a/client/src/Frontend/Views/SendResources.tsx +++ b/client/src/Frontend/Views/SendResources.tsx @@ -1,6 +1,10 @@ import { formatNumber } from '@dfdao/gamelogic'; import { nameOfArtifact, nameOfSpaceship } from '@dfdao/procedural'; -import { isUnconfirmedMoveTx, isUnconfirmedReleaseTx } from '@dfdao/serde'; +import { + isUnconfirmedMoveTx, + isUnconfirmedReleaseTx, + isUnconfirmedWithdrawSilverTx, +} from '@dfdao/serde'; import { Artifact, Planet, Spaceship, TooltipName } from '@dfdao/types'; import React, { useCallback } from 'react'; import styled from 'styled-components'; @@ -170,12 +174,10 @@ function AbandonButton({ function ExtractButton({ planet, extracting, - toggleExtracting, disabled, }: { planet?: Planet; extracting: boolean; - toggleExtracting: () => void; disabled?: boolean; }) { const uiManager = useUIManager(); @@ -188,7 +190,7 @@ function ExtractButton({ uiManager.withdrawSilver(planet.locationId, planet.silver)} shortcutKey={TOGGLE_WITHDRAW} shortcutText={TOGGLE_WITHDRAW} disabled={planet.isHomePlanet || disabled} @@ -249,12 +251,10 @@ export function SendResources({ planetWrapper: p, onToggleSendForces, onToggleAbandon, - onToggleExtract, }: { planetWrapper: Wrapper; onToggleSendForces: () => void; onToggleAbandon: () => void; - onToggleExtract: () => void; }) { const uiManager = useUIManager(); const account = useAccount(uiManager); @@ -281,15 +281,6 @@ export function SendResources({ }, [uiManager, locationId] ); - - const updateSilverSending = useCallback( - (silverPercent) => { - if (!locationId) return; - uiManager.setSilverSending(locationId, silverPercent); - }, - [uiManager, locationId] - ); - const updateArtifactSending = useCallback( (sendArtifact) => { if (!locationId) return; @@ -310,16 +301,11 @@ export function SendResources({ // that key is const energyShortcuts = '1234567890'.split(''); - // same as above, except for silver - const silverShortcuts = '!@#$%^&*()'.split(''); - // for each of the above keys, we set up a listener that is triggered whenever that key is // pressed, and sets the corresponding resource sending amount for (let i = 0; i < energyShortcuts.length; i++) { // eslint-disable-next-line react-hooks/rules-of-hooks useOnUp(energyShortcuts[i], () => updateEnergySending((i + 1) * 10), [updateEnergySending]); - // eslint-disable-next-line react-hooks/rules-of-hooks - useOnUp(silverShortcuts[i], () => updateSilverSending((i + 1) * 10), [updateSilverSending]); } useOnUp( @@ -378,7 +364,7 @@ export function SendResources({ } let extractRow; - if (p.value && p.value.transactions?.hasTransaction(isUnconfirmedReleaseTx)) { + if (p.value && p.value.transactions?.hasTransaction(isUnconfirmedWithdrawSilverTx)) { extractRow = ( @@ -386,12 +372,7 @@ export function SendResources({ ); } else if (p.value && !p.value.destroyed) { extractRow = ( - + ); } diff --git a/packages/gamelogic/src/artifact.ts b/packages/gamelogic/src/artifact.ts index 61a7a774..e7993dda 100644 --- a/packages/gamelogic/src/artifact.ts +++ b/packages/gamelogic/src/artifact.ts @@ -9,6 +9,7 @@ import { ArtifactTypeNames, Biome, BiomeNames, + Planet, PlanetLevel, RenderedArtifact, } from '@dfdao/types'; @@ -105,6 +106,10 @@ export function setForceAncient(force: boolean): void { forceAncient = force; } +export function isActivated(artifact: Artifact | undefined, planet: Planet | undefined) { + return !!planet && !!artifact && planet.activeArtifact == artifact; +} + export function artifactFileName( videoMode: boolean, thumb: boolean, From 23f3e3ef2911e89f8fe8fffca8a5f23b679c49c3 Mon Sep 17 00:00:00 2001 From: Henry Caron Date: Wed, 5 Oct 2022 10:26:27 +0100 Subject: [PATCH 53/55] rebase: removed breaking peripheral client changes --- client/src/Frontend/Components/CapturePlanetButton.tsx | 1 + client/src/Frontend/Views/ArtifactRow.tsx | 7 ------- client/src/Frontend/Views/SendResources.tsx | 1 - 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/client/src/Frontend/Components/CapturePlanetButton.tsx b/client/src/Frontend/Components/CapturePlanetButton.tsx index 52fbf433..f39917cf 100644 --- a/client/src/Frontend/Components/CapturePlanetButton.tsx +++ b/client/src/Frontend/Components/CapturePlanetButton.tsx @@ -117,6 +117,7 @@ export function CapturePlanetButton({ gameManager.capturePlanet(planetWrapper.value.locationId); }, [gameManager, planetWrapper]); + if (!shouldShow) return <>; return ( shouldShow && ( diff --git a/client/src/Frontend/Views/ArtifactRow.tsx b/client/src/Frontend/Views/ArtifactRow.tsx index 2b43fb0a..24a800b6 100644 --- a/client/src/Frontend/Views/ArtifactRow.tsx +++ b/client/src/Frontend/Views/ArtifactRow.tsx @@ -1,9 +1,7 @@ -import { isActivated } from '@dfdao/gamelogic'; import { Artifact } from '@dfdao/types'; import React, { useCallback, useEffect } from 'react'; import styled, { css } from 'styled-components'; import { ArtifactImage } from '../Components/ArtifactImage'; -import { Btn } from '../Components/Btn'; import { Spacer } from '../Components/CoreUI'; import dfstyles from '../Styles/dfstyles'; import { useUIManager } from '../Utils/AppHooks'; @@ -125,11 +123,6 @@ export function SelectArtifactRow({ ))} - {owned && ( - - {isActivated(selectedArtifact, planet) ? 'deactivate' : 'activate'} - - )}
); } diff --git a/client/src/Frontend/Views/SendResources.tsx b/client/src/Frontend/Views/SendResources.tsx index bcc58e69..37e3f31a 100644 --- a/client/src/Frontend/Views/SendResources.tsx +++ b/client/src/Frontend/Views/SendResources.tsx @@ -391,7 +391,6 @@ export function SendResources({ {p.value && artifacts.length > 0 && ( Date: Wed, 5 Oct 2022 10:34:40 +0100 Subject: [PATCH 54/55] merge conflict clean --- eth/test/DFMove.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/eth/test/DFMove.test.ts b/eth/test/DFMove.test.ts index 8a2f031b..56cb1a71 100644 --- a/eth/test/DFMove.test.ts +++ b/eth/test/DFMove.test.ts @@ -117,18 +117,6 @@ describe('DarkForestMove', function () { await increaseBlockchainTime(); -<<<<<<< HEAD - const ship = (await world.user1Core.getSpaceshipsOnPlanet(SPAWN_PLANET_1.id)).filter( - (s) => s.spaceshipType === SpaceshipType.ShipGear - )[0]; - - await world.user1Core.move( - ...makeMoveArgs(SPAWN_PLANET_1, LVL1_ASTEROID_1, 100, 0, 0, ship?.id) - ); - - await world.contract.refreshPlanet(SPAWN_PLANET_1.id); - const activePhotoid = await getArtifactsOnPlanet(world, SPAWN_PLANET_1.id); -======= const gearShip = await getSpaceshipOnPlanetByType( world.user1Core, SPAWN_PLANET_1.id, @@ -144,7 +132,6 @@ describe('DarkForestMove', function () { SPAWN_PLANET_1.id, ArtifactType.PhotoidCannon ); ->>>>>>> origin/devcon // If the photoid is not there, it was used during ship move expect(activePhotoid).to.not.eq(undefined); }); From f50b0b3ea491dbd5561a7a8a39d1323f891b462e Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Wed, 5 Oct 2022 10:58:09 +0100 Subject: [PATCH 55/55] remove conflict --- client/src/Frontend/Panes/PlanetContextPane.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/src/Frontend/Panes/PlanetContextPane.tsx b/client/src/Frontend/Panes/PlanetContextPane.tsx index 3b0f0306..e8000190 100644 --- a/client/src/Frontend/Panes/PlanetContextPane.tsx +++ b/client/src/Frontend/Panes/PlanetContextPane.tsx @@ -6,11 +6,6 @@ import { CapturePlanetButton } from '../Components/CapturePlanetButton'; import { VerticalSplit } from '../Components/CoreUI'; import { MineArtifactButton } from '../Components/MineArtifactButton'; import { -<<<<<<< HEAD -======= - OpenBroadcastPaneButton, - OpenHatPaneButton, ->>>>>>> origin/devcon OpenManagePlanetInventoryButton, OpenPlanetInfoButton, OpenUpgradeDetailsPaneButton, @@ -88,10 +83,6 @@ function PlanetContextPaneContent({ <> -<<<<<<< HEAD -======= - {hatRow} ->>>>>>> origin/devcon {withdrawRow}