From c541d0b23bfe679f4371de4e53239b735a241fb3 Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Wed, 5 Oct 2022 11:38:57 +0100 Subject: [PATCH 01/17] ERC1155 Silver (#40) * chore: Silver Token Type * fix: silver token type * chore: explain silver precision * chore: remove .only --- eth/contracts/DFTypes.sol | 8 +++- eth/contracts/Tokens.md | 5 ++- eth/contracts/facets/DFTokenFacet.sol | 7 +++ eth/contracts/libraries/LibPlanet.sol | 5 +++ eth/contracts/libraries/LibSilver.sol | 45 +++++++++++++++++++ .../{DFScoringRound2.test.ts => DFSilver.ts} | 12 ++--- 6 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 eth/contracts/libraries/LibSilver.sol rename eth/test/{DFScoringRound2.test.ts => DFSilver.ts} (87%) diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index b2a587ff..859cc0d3 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -250,7 +250,8 @@ enum Biome { enum TokenType { Unknown, Artifact, - Spaceship + Spaceship, + Silver } enum ArtifactInfo { @@ -281,3 +282,8 @@ struct Spaceship { TokenType tokenType; SpaceshipType spaceshipType; } + +enum SilverInfo { + Unknown, + TokenType +} diff --git a/eth/contracts/Tokens.md b/eth/contracts/Tokens.md index 70ae09bd..15c792a7 100644 --- a/eth/contracts/Tokens.md +++ b/eth/contracts/Tokens.md @@ -235,5 +235,6 @@ doesn't change. ## 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. +Silver is a fungible token with TokenType = 3. Although silver on planets is multiplied by 1000 for +precision, Silver the token is exact. As such, any function that converts from silver on planets to +Silver must divide by 1000. diff --git a/eth/contracts/facets/DFTokenFacet.sol b/eth/contracts/facets/DFTokenFacet.sol index 9df710af..a1f06d27 100644 --- a/eth/contracts/facets/DFTokenFacet.sol +++ b/eth/contracts/facets/DFTokenFacet.sol @@ -6,6 +6,7 @@ import {SolidStateERC1155} from "@solidstate/contracts/token/ERC1155/SolidStateE // Library Imports import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibSilver} from "../libraries/LibSilver.sol"; import {LibSpaceship} from "../libraries/LibSpaceship.sol"; // Storage imports @@ -101,4 +102,10 @@ contract DFTokenFacet is WithStorage, SolidStateERC1155 { function tokenIsOwnedBy(address owner, uint256 tokenId) public view returns (bool) { return balanceOf(owner, tokenId) > 0; } + + // TODO: Move this into SilverFacet + function getSilverBalance(address player) public view returns (uint256) { + uint256 silverId = LibSilver.create(); + return balanceOf(player, silverId); + } } diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 7d452da2..50fda7a7 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -10,6 +10,7 @@ import {LibArtifact} from "./LibArtifact.sol"; import {LibArtifactUtils} from "./LibArtifactUtils.sol"; import {LibGameUtils} from "./LibGameUtils.sol"; import {LibLazyUpdate} from "./LibLazyUpdate.sol"; +import {LibSilver} from "./LibSilver.sol"; import {LibSpaceship} from "./LibSpaceship.sol"; // Storage imports @@ -383,6 +384,10 @@ library LibPlanet { // so any of those values coming from the contracts need to be divided by // `CONTRACT_PRECISION` to get their true integer value. uint256 scoreGained = silverToWithdraw / 1000; + // increase silver token count; + uint256 silverId = LibSilver.create(); + DFTokenFacet(address(this)).mint(msg.sender, silverId, scoreGained); + scoreGained = (scoreGained * gameConstants().SILVER_SCORE_VALUE) / 100; gs().players[msg.sender].score += scoreGained; } diff --git a/eth/contracts/libraries/LibSilver.sol b/eth/contracts/libraries/LibSilver.sol new file mode 100644 index 00000000..6949d403 --- /dev/null +++ b/eth/contracts/libraries/LibSilver.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +/** + * Library for all things Silver + */ + +// Library imports +import {LibUtils} from "./LibUtils.sol"; + +// Type imports +import {SilverInfo, TokenType} from "../DFTypes.sol"; + +library LibSilver { + /** + * @notice Create the token ID for Silver. + * Takes no args because silver is a fungible resource. + */ + function create() internal pure returns (uint256) { + // x << y is equivalent to the mathematical expression x * 2**y + uint256 tokenType = LibUtils.shiftLeft( + uint8(TokenType.Silver), + uint8(SilverInfo.TokenType) + ); + return tokenType; + } + + function decode(uint256 silverId) internal pure returns (uint256) { + bytes memory _b = abi.encodePacked(silverId); + // Idx is subtracted by one because each Info enum has Unknown at the zero location. + uint8 tokenIdx = uint8(SilverInfo.TokenType) - 1; + + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + + require(tokenType == uint8(TokenType.Silver), "token is not silver"); + return silverId; + } + + function isSilver(uint256 tokenId) internal pure returns (bool) { + bytes memory _b = abi.encodePacked(tokenId); + uint8 tokenIdx = uint8(SilverInfo.TokenType) - 1; + uint8 tokenType = uint8(LibUtils.calculateByteUInt(_b, tokenIdx, tokenIdx)); + return (tokenType == uint8(TokenType.Silver)); + } +} diff --git a/eth/test/DFScoringRound2.test.ts b/eth/test/DFSilver.ts similarity index 87% rename from eth/test/DFScoringRound2.test.ts rename to eth/test/DFSilver.ts index 79fffe5c..17a08413 100644 --- a/eth/test/DFScoringRound2.test.ts +++ b/eth/test/DFSilver.ts @@ -4,7 +4,7 @@ import { conquerUnownedPlanet, feedSilverToCap, makeInitArgs } from './utils/Tes import { defaultWorldFixture, World } from './utils/TestWorld'; import { LVL1_ASTEROID_1, LVL3_SPACETIME_1, SPAWN_PLANET_1 } from './utils/WorldConstants'; -describe('DFScoringRound2', async function () { +describe('DFSilver', async function () { // Bump the time out so that the test doesn't timeout during // initial fixture creation this.timeout(1000 * 60); @@ -42,7 +42,7 @@ describe('DFScoringRound2', async function () { // FIXME(blaine): This should have been done client-side because this type of // division isn't supposed to be done in the contract. That's the whole point of // `CONTRACT_PRECISION` - expect((await world.contract.players(world.user1.address)).score).to.equal( + expect(await world.contract.getSilverBalance(world.user1.address)).to.equal( withdrawnAmount.div(1000) ); }); @@ -54,7 +54,7 @@ describe('DFScoringRound2', async function () { world.user1Core.withdrawSilver(LVL3_SPACETIME_1.id, withdrawnAmount) ).to.be.revertedWith('tried to withdraw more silver than exists on planet'); - expect((await world.contract.players(world.user1.address)).score).to.equal(0); + expect(await world.contract.getSilverBalance(world.user1.address)).to.equal(0); }); it("doesn't allow player to withdraw silver from non-trading post", async function () { @@ -64,7 +64,7 @@ describe('DFScoringRound2', async function () { world.user1Core.withdrawSilver(LVL1_ASTEROID_1.id, withdrawnAmount) ).to.be.revertedWith('can only withdraw silver from trading posts'); - expect((await world.contract.players(world.user1.address)).score).to.equal(0); + expect(await world.contract.getSilverBalance(world.user1.address)).to.equal(0); }); it("doesn't allow player to withdraw silver from planet that is not theirs", async function () { @@ -74,7 +74,7 @@ describe('DFScoringRound2', async function () { world.user2Core.withdrawSilver(LVL3_SPACETIME_1.id, withdrawnAmount) ).to.be.revertedWith('you must own this planet'); - expect((await world.contract.players(world.user1.address)).score).to.equal(0); - expect((await world.contract.players(world.user2.address)).score).to.equal(0); + expect(await world.contract.getSilverBalance(world.user1.address)).to.equal(0); + expect(await world.contract.getSilverBalance(world.user2.address)).to.equal(0); }); }); From f8faf3a8a13179b9409d42b4edfafaf2153eaa77 Mon Sep 17 00:00:00 2001 From: cha0sg0d <42984734+cha0sg0d@users.noreply.github.com> Date: Wed, 5 Oct 2022 17:00:45 +0100 Subject: [PATCH 02/17] feat: fast lobby create, better start map, search from 0,0 in dev (#45) * feat: fast lobby create, better start map, search from 0,0 in dev * bye bye capture * fix: remove owneraddress Co-authored-by: Henry Caron --- client/src/Backend/GameLogic/GameManager.ts | 6 ++++ client/src/Frontend/Pages/CreateLobby.tsx | 40 +++++++++++++++------ eth/darkforest.toml | 12 +++---- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/client/src/Backend/GameLogic/GameManager.ts b/client/src/Backend/GameLogic/GameManager.ts index fe7be87e..33491c4f 100644 --- a/client/src/Backend/GameLogic/GameManager.ts +++ b/client/src/Backend/GameLogic/GameManager.ts @@ -2032,6 +2032,12 @@ class GameManager extends EventEmitter { } } + // In Development mode start search at 0,0 so map is pre-mined. + if (!import.meta.env.PROD) { + x = 0; + y = 0; + } + const pattern: MiningPattern = new SpiralPattern({ x, y }, MIN_CHUNK_SIZE); const chunkStore = new HomePlanetMinerChunkStore( initPerlinMin, diff --git a/client/src/Frontend/Pages/CreateLobby.tsx b/client/src/Frontend/Pages/CreateLobby.tsx index 159f869b..6b1e1d9f 100644 --- a/client/src/Frontend/Pages/CreateLobby.tsx +++ b/client/src/Frontend/Pages/CreateLobby.tsx @@ -1,14 +1,15 @@ import { INIT_ADDRESS } from '@dfdao/contracts'; import initContractAbiUrl from '@dfdao/contracts/abis/DFInitialize.json?url'; +import { DarkForest } from '@dfdao/contracts/typechain'; import { EthConnection } from '@dfdao/network'; import { address } from '@dfdao/serde'; import { ArtifactRarity, EthAddress, UnconfirmedCreateLobby } from '@dfdao/types'; -import { Contract } from 'ethers'; +import { Contract, providers } from 'ethers'; +import { keccak256, toUtf8Bytes } from 'ethers/lib/utils'; import _ from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { ContractsAPI, makeContractsAPI } from '../../Backend/GameLogic/ContractsAPI'; -import { ContractsAPIEvent } from '../../_types/darkforest/api/ContractsAPITypes'; import { InitRenderState, Wrapper } from '../Components/GameLandingPageComponents'; import { ConfigurationPane } from '../Panes/Lobbies/ConfigurationPane'; import { Minimap } from '../Panes/Lobbies/MinimapPane'; @@ -18,6 +19,23 @@ import { listenForKeyboardEvents, unlinkKeyboardEvents } from '../Utils/KeyEmitt import { CadetWormhole } from '../Views/CadetWormhole'; import { LobbyLandingPage } from './LobbyLandingPage'; +// TODO: Infer this from Dark Forest interface +function getLobbyCreatedEvent( + lobbyReceipt: providers.TransactionReceipt, + contract: DarkForest +): { owner: EthAddress; lobby: EthAddress } { + const lobbyCreatedHash = keccak256(toUtf8Bytes('LobbyCreated(address,address)')); + const log = lobbyReceipt.logs.find((log: any) => log.topics[0] === lobbyCreatedHash); + if (log) { + return { + owner: address(contract.interface.parseLog(log).args.ownerAddress), + lobby: address(contract.interface.parseLog(log).args.lobbyAddress), + }; + } else { + throw new Error('Lobby Created event not found'); + } +} + type ErrorState = | { type: 'invalidAddress' } | { type: 'contractLoad' } @@ -26,7 +44,6 @@ type ErrorState = export function CreateLobby({ match }: RouteComponentProps<{ contract: string }>) { const [connection, setConnection] = useState(); - const [ownerAddress, setOwnerAddress] = useState(); const [contract, setContract] = useState(); const [startingConfig, setStartingConfig] = useState(); const [lobbyAddress, setLobbyAddress] = useState(); @@ -56,7 +73,6 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> const onReady = useCallback( (connection: EthConnection) => { setConnection(connection); - setOwnerAddress(connection.getAddress()); }, [setConnection] ); @@ -167,17 +183,19 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> args: Promise.resolve([initAddress, initFunctionCall]), }; - contract.once(ContractsAPIEvent.LobbyCreated, (owner: EthAddress, lobby: EthAddress) => { - if (owner === ownerAddress) { - setLobbyAddress(lobby); - } - }); + // contract.once(ContractsAPIEvent.LobbyCreated, (owner: EthAddress, lobby: EthAddress) => { + // if (owner === ownerAddress) { + // setLobbyAddress(lobby); + // } + // }); const tx = await contract.submitTransaction(txIntent, { // The createLobby function costs somewhere around 12mil gas - gasLimit: '16777215', + gasLimit: '15000000', }); - await tx.confirmedPromise; + const rct = await tx.confirmedPromise; + const { lobby } = getLobbyCreatedEvent(rct, contract.contract); + setLobbyAddress(lobby); } if (errorState) { diff --git a/eth/darkforest.toml b/eth/darkforest.toml index d1d6cec8..da2503ed 100644 --- a/eth/darkforest.toml +++ b/eth/darkforest.toml @@ -11,7 +11,7 @@ WORLD_RADIUS_MIN = 53000 ################################## # SNARK keys & Perlin parameters # ################################## -DISABLE_ZK_CHECKS = false +DISABLE_ZK_CHECKS = true PLANETHASH_KEY = 6279 SPACETYPE_KEY = 6280 BIOMEBASE_KEY = 6271 @@ -49,7 +49,7 @@ ABANDON_RANGE_CHANGE_PERCENT = 150 ################# # Capture zones # ################# -CAPTURE_ZONES_ENABLED = true +CAPTURE_ZONES_ENABLED = false # How often zones change location. Max 255. CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL = 255 # How big the capture zones are @@ -76,12 +76,12 @@ CAPTURE_ZONES_PER_5000_WORLD_RADIUS = 3 # Game configuration # ###################### MAX_NATURAL_PLANET_LEVEL = 9 -TIME_FACTOR_HUNDREDTHS = 100 # speedup/slowdown game +TIME_FACTOR_HUNDREDTHS = 2000 # speedup/slowdown game PERLIN_THRESHOLD_1 = 14 PERLIN_THRESHOLD_2 = 15 PERLIN_THRESHOLD_3 = 19 -INIT_PERLIN_MIN = 13 -INIT_PERLIN_MAX = 14 +INIT_PERLIN_MIN = 1 +INIT_PERLIN_MAX = 31 SPAWN_RIM_AREA = 0 BIOME_THRESHOLD_1 = 15 BIOME_THRESHOLD_2 = 17 @@ -102,7 +102,7 @@ PLANET_LEVEL_THRESHOLDS = [ 240, 48 ] -PLANET_RARITY = 16384 +PLANET_RARITY = 4096 # How much score silver gives when withdrawing. # Expressed as a percentage integer. # (100 is 100%) From 4642213b0232c6bccb1118068fc09ea4e2790571 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 10:19:45 -0400 Subject: [PATCH 03/17] feat: basic lobby deploy and start works --- eth/contracts/DFInitialize.sol | 397 +------------------------ eth/contracts/DFTypes.sol | 88 ++++++ eth/contracts/facets/DFArenaFacet.sol | 4 + eth/contracts/facets/DFStartFacet.sol | 353 ++++++++++++++++++++++ eth/contracts/libraries/LibStorage.sol | 40 ++- eth/tasks/deploy.ts | 29 +- eth/test/DFLobby.test.ts | 11 +- eth/test/utils/TestWorld.ts | 10 +- 8 files changed, 524 insertions(+), 408 deletions(-) create mode 100644 eth/contracts/facets/DFArenaFacet.sol create mode 100644 eth/contracts/facets/DFStartFacet.sol diff --git a/eth/contracts/DFInitialize.sol b/eth/contracts/DFInitialize.sol index b0033910..00358b3b 100644 --- a/eth/contracts/DFInitialize.sol +++ b/eth/contracts/DFInitialize.sol @@ -24,407 +24,24 @@ pragma solidity ^0.8.0; import {ERC1155MetadataStorage} from "@solidstate/contracts/token/ERC1155/metadata/ERC1155MetadataStorage.sol"; // Library imports -import {WithStorage, SpaceshipConstants} from "./libraries/LibStorage.sol"; -import {LibGameUtils} from "./libraries/LibGameUtils.sol"; +import {WithStorage} from "./libraries/LibStorage.sol"; // Type imports -import {PlanetDefaultStats, Upgrade, UpgradeBranch} from "./DFTypes.sol"; - -struct InitArgs { - bool START_PAUSED; - bool ADMIN_CAN_ADD_PLANETS; - uint256 LOCATION_REVEAL_COOLDOWN; - uint256 TOKEN_MINT_END_TIMESTAMP; - bool WORLD_RADIUS_LOCKED; - uint256 WORLD_RADIUS_MIN; - // SNARK keys and perlin params - bool DISABLE_ZK_CHECKS; - uint256 PLANETHASH_KEY; - uint256 SPACETYPE_KEY; - uint256 BIOMEBASE_KEY; - bool PERLIN_MIRROR_X; - bool PERLIN_MIRROR_Y; - uint256 PERLIN_LENGTH_SCALE; // must be a power of two up to 8192 - // Game config - uint256 MAX_NATURAL_PLANET_LEVEL; - uint256 TIME_FACTOR_HUNDREDTHS; // speedup/slowdown game - uint256 PERLIN_THRESHOLD_1; - uint256 PERLIN_THRESHOLD_2; - uint256 PERLIN_THRESHOLD_3; - uint256 INIT_PERLIN_MIN; - uint256 INIT_PERLIN_MAX; - uint256 SPAWN_RIM_AREA; - uint256 BIOME_THRESHOLD_1; - uint256 BIOME_THRESHOLD_2; - uint256[10] PLANET_LEVEL_THRESHOLDS; - uint256 PLANET_RARITY; - bool PLANET_TRANSFER_ENABLED; - uint8[5][10][4] PLANET_TYPE_WEIGHTS; // spaceType (enum 0-3) -> planetLevel (0-7) -> planetType (enum 0-4) - uint256 SILVER_SCORE_VALUE; - uint256[6] ARTIFACT_POINT_VALUES; - uint256 PHOTOID_ACTIVATION_DELAY; - // Space Junk - bool SPACE_JUNK_ENABLED; - /** - Total amount of space junk a player can take on. - This can be overridden at runtime by updating - this value for a specific player in storage. - */ - uint256 SPACE_JUNK_LIMIT; - /** - The amount of junk that each level of planet - gives the player when moving to it for the - first time. - */ - uint256[10] PLANET_LEVEL_JUNK; - /** - The speed boost a movement receives when abandoning - a planet. - */ - uint256 ABANDON_SPEED_CHANGE_PERCENT; - /** - The range boost a movement receives when abandoning - a planet. - */ - uint256 ABANDON_RANGE_CHANGE_PERCENT; - // Capture Zones - bool CAPTURE_ZONES_ENABLED; - uint256 CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL; - uint256 CAPTURE_ZONE_RADIUS; - uint256[10] CAPTURE_ZONE_PLANET_LEVEL_SCORE; - uint256 CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED; - uint256 CAPTURE_ZONES_PER_5000_WORLD_RADIUS; - SpaceshipConstants SPACESHIPS; - uint256[64] ROUND_END_REWARDS_BY_RANK; -} +import {AuxiliaryArgs, InitArgs} from "./DFTypes.sol"; contract DFInitialize is WithStorage { using ERC1155MetadataStorage for ERC1155MetadataStorage.Layout; // You can add parameters to this function in order to pass in // data to set initialize state variables - function init( - bool whitelistEnabled, - string memory artifactBaseURI, - InitArgs memory initArgs - ) external { + function init(InitArgs calldata initArgs, AuxiliaryArgs calldata auxArgs) external { // Setup the ERC1155 metadata - ERC1155MetadataStorage.layout().baseURI = artifactBaseURI; + ERC1155MetadataStorage.layout().baseURI = auxArgs.baseURI; gs().diamondAddress = address(this); - ws().enabled = whitelistEnabled; - ws().drip = 0.05 ether; - ws().relayerRewardsEnabled = false; - ws().relayerReward = 0.01 ether; - - gs().planetLevelsCount = 10; - - snarkConstants().DISABLE_ZK_CHECKS = initArgs.DISABLE_ZK_CHECKS; - snarkConstants().PLANETHASH_KEY = initArgs.PLANETHASH_KEY; - snarkConstants().SPACETYPE_KEY = initArgs.SPACETYPE_KEY; - snarkConstants().BIOMEBASE_KEY = initArgs.BIOMEBASE_KEY; - snarkConstants().PERLIN_MIRROR_X = initArgs.PERLIN_MIRROR_X; - snarkConstants().PERLIN_MIRROR_Y = initArgs.PERLIN_MIRROR_Y; - snarkConstants().PERLIN_LENGTH_SCALE = initArgs.PERLIN_LENGTH_SCALE; - - gameConstants().ADMIN_CAN_ADD_PLANETS = initArgs.ADMIN_CAN_ADD_PLANETS; - gameConstants().WORLD_RADIUS_LOCKED = initArgs.WORLD_RADIUS_LOCKED; - gameConstants().WORLD_RADIUS_MIN = initArgs.WORLD_RADIUS_MIN; - gameConstants().MAX_NATURAL_PLANET_LEVEL = initArgs.MAX_NATURAL_PLANET_LEVEL; - gameConstants().TIME_FACTOR_HUNDREDTHS = initArgs.TIME_FACTOR_HUNDREDTHS; - gameConstants().PERLIN_THRESHOLD_1 = initArgs.PERLIN_THRESHOLD_1; - gameConstants().PERLIN_THRESHOLD_2 = initArgs.PERLIN_THRESHOLD_2; - gameConstants().PERLIN_THRESHOLD_3 = initArgs.PERLIN_THRESHOLD_3; - gameConstants().INIT_PERLIN_MIN = initArgs.INIT_PERLIN_MIN; - gameConstants().INIT_PERLIN_MAX = initArgs.INIT_PERLIN_MAX; - gameConstants().SPAWN_RIM_AREA = initArgs.SPAWN_RIM_AREA; - gameConstants().BIOME_THRESHOLD_1 = initArgs.BIOME_THRESHOLD_1; - gameConstants().BIOME_THRESHOLD_2 = initArgs.BIOME_THRESHOLD_2; - gameConstants().PLANET_RARITY = initArgs.PLANET_RARITY; - gameConstants().PLANET_LEVEL_THRESHOLDS = initArgs.PLANET_LEVEL_THRESHOLDS; - gameConstants().PLANET_TRANSFER_ENABLED = initArgs.PLANET_TRANSFER_ENABLED; - gameConstants().PHOTOID_ACTIVATION_DELAY = initArgs.PHOTOID_ACTIVATION_DELAY; - gameConstants().LOCATION_REVEAL_COOLDOWN = initArgs.LOCATION_REVEAL_COOLDOWN; - gameConstants().PLANET_TYPE_WEIGHTS = initArgs.PLANET_TYPE_WEIGHTS; - gameConstants().SILVER_SCORE_VALUE = initArgs.SILVER_SCORE_VALUE; - gameConstants().ARTIFACT_POINT_VALUES = initArgs.ARTIFACT_POINT_VALUES; - // Space Junk - gameConstants().SPACE_JUNK_ENABLED = initArgs.SPACE_JUNK_ENABLED; - gameConstants().SPACE_JUNK_LIMIT = initArgs.SPACE_JUNK_LIMIT; - gameConstants().PLANET_LEVEL_JUNK = initArgs.PLANET_LEVEL_JUNK; - gameConstants().ABANDON_SPEED_CHANGE_PERCENT = initArgs.ABANDON_SPEED_CHANGE_PERCENT; - gameConstants().ABANDON_RANGE_CHANGE_PERCENT = initArgs.ABANDON_RANGE_CHANGE_PERCENT; - // Capture Zones - gameConstants().GAME_START_BLOCK = block.number; - gameConstants().CAPTURE_ZONES_ENABLED = initArgs.CAPTURE_ZONES_ENABLED; - gameConstants().CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL = initArgs - .CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL; - gameConstants().CAPTURE_ZONE_RADIUS = initArgs.CAPTURE_ZONE_RADIUS; - gameConstants().CAPTURE_ZONE_PLANET_LEVEL_SCORE = initArgs.CAPTURE_ZONE_PLANET_LEVEL_SCORE; - gameConstants().CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED = initArgs - .CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED; - gameConstants().CAPTURE_ZONES_PER_5000_WORLD_RADIUS = initArgs - .CAPTURE_ZONES_PER_5000_WORLD_RADIUS; - gameConstants().SPACESHIPS = initArgs.SPACESHIPS; - - gs().nextChangeBlock = block.number + initArgs.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL; - - gs().worldRadius = initArgs.WORLD_RADIUS_MIN; // will be overridden by `LibGameUtils.updateWorldRadius()` if !WORLD_RADIUS_LOCKED - - gs().paused = initArgs.START_PAUSED; - gs().TOKEN_MINT_END_TIMESTAMP = initArgs.TOKEN_MINT_END_TIMESTAMP; - - gameConstants().ROUND_END_REWARDS_BY_RANK = initArgs.ROUND_END_REWARDS_BY_RANK; - - initializeDefaults(); - initializeUpgrades(); - - gs().initializedPlanetCountByLevel = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (uint256 i = 0; i < gameConstants().PLANET_LEVEL_THRESHOLDS.length; i += 1) { - gs().cumulativeRarities.push( - (2**24 / gameConstants().PLANET_LEVEL_THRESHOLDS[i]) * initArgs.PLANET_RARITY - ); - } - - LibGameUtils.updateWorldRadius(); - } - - function initializeDefaults() public { - PlanetDefaultStats[] storage planetDefaultStats = planetDefaultStats(); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "Asteroid", - populationCap: 100000, - populationGrowth: 417, - range: 99, - speed: 75, - defense: 400, - silverGrowth: 0, - silverCap: 0, - barbarianPercentage: 0 - }) - ); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "Brown Dwarf", - populationCap: 400000, - populationGrowth: 833, - range: 177, - speed: 75, - defense: 400, - silverGrowth: 56, - silverCap: 100000, - barbarianPercentage: 1 - }) - ); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "Red Dwarf", - populationCap: 1600000, - populationGrowth: 1250, - range: 315, - speed: 75, - defense: 300, - silverGrowth: 167, - silverCap: 500000, - barbarianPercentage: 2 - }) - ); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "White Dwarf", - populationCap: 6000000, - populationGrowth: 1667, - range: 591, - speed: 75, - defense: 300, - silverGrowth: 417, - silverCap: 2500000, - barbarianPercentage: 3 - }) - ); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "Yellow Star", - populationCap: 25000000, - populationGrowth: 2083, - range: 1025, - speed: 75, - defense: 300, - silverGrowth: 833, - silverCap: 12000000, - barbarianPercentage: 4 - }) - ); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "Blue Star", - populationCap: 100000000, - populationGrowth: 2500, - range: 1734, - speed: 75, - defense: 200, - silverGrowth: 1667, - silverCap: 50000000, - barbarianPercentage: 5 - }) - ); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "Giant", - populationCap: 300000000, - populationGrowth: 2917, - range: 2838, - speed: 75, - defense: 200, - silverGrowth: 2778, - silverCap: 100000000, - barbarianPercentage: 7 - }) - ); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "Supergiant", - populationCap: 500000000, - populationGrowth: 3333, - range: 4414, - speed: 75, - defense: 200, - silverGrowth: 2778, - silverCap: 200000000, - barbarianPercentage: 10 - }) - ); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "Unlabeled1", - populationCap: 700000000, - populationGrowth: 3750, - range: 6306, - speed: 75, - defense: 200, - silverGrowth: 2778, - silverCap: 300000000, - barbarianPercentage: 20 - }) - ); - - planetDefaultStats.push( - PlanetDefaultStats({ - label: "Unlabeled2", - populationCap: 800000000, - populationGrowth: 4167, - range: 8829, - speed: 75, - defense: 200, - silverGrowth: 2778, - silverCap: 400000000, - barbarianPercentage: 25 - }) - ); - } - - function initializeUpgrades() public { - Upgrade[4][3] storage upgrades = upgrades(); - - // defense - upgrades[uint256(UpgradeBranch.DEFENSE)][0] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: 120 - }); - upgrades[uint256(UpgradeBranch.DEFENSE)][1] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: 120 - }); - upgrades[uint256(UpgradeBranch.DEFENSE)][2] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: 120 - }); - upgrades[uint256(UpgradeBranch.DEFENSE)][3] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 100, - speedMultiplier: 100, - defMultiplier: 120 - }); - - // range - upgrades[uint256(UpgradeBranch.RANGE)][0] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 125, - speedMultiplier: 100, - defMultiplier: 100 - }); - upgrades[uint256(UpgradeBranch.RANGE)][1] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 125, - speedMultiplier: 100, - defMultiplier: 100 - }); - upgrades[uint256(UpgradeBranch.RANGE)][2] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 125, - speedMultiplier: 100, - defMultiplier: 100 - }); - upgrades[uint256(UpgradeBranch.RANGE)][3] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 125, - speedMultiplier: 100, - defMultiplier: 100 - }); - - // speed - upgrades[uint256(UpgradeBranch.SPEED)][0] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 100, - speedMultiplier: 175, - defMultiplier: 100 - }); - upgrades[uint256(UpgradeBranch.SPEED)][1] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 100, - speedMultiplier: 175, - defMultiplier: 100 - }); - upgrades[uint256(UpgradeBranch.SPEED)][2] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 100, - speedMultiplier: 175, - defMultiplier: 100 - }); - upgrades[uint256(UpgradeBranch.SPEED)][3] = Upgrade({ - popCapMultiplier: 120, - popGroMultiplier: 120, - rangeMultiplier: 100, - speedMultiplier: 175, - defMultiplier: 100 - }); + /* Store initializers */ + initializers().initArgs = initArgs; + initializers().auxArgs = auxArgs; } } diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 859cc0d3..bd9efa00 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -23,6 +23,94 @@ enum UpgradeBranch { SPEED } +struct SpaceshipConstants { + bool GEAR; + bool MOTHERSHIP; + bool TITAN; + bool CRESCENT; + bool WHALE; +} + +struct AuxiliaryArgs { + bool allowListEnabled; + string baseURI; + address[] allowedAddresses; +} + +struct InitArgs { + bool START_PAUSED; + bool ADMIN_CAN_ADD_PLANETS; + uint256 LOCATION_REVEAL_COOLDOWN; + uint256 TOKEN_MINT_END_TIMESTAMP; + bool WORLD_RADIUS_LOCKED; + uint256 WORLD_RADIUS_MIN; + // SNARK keys and perlin params + bool DISABLE_ZK_CHECKS; + uint256 PLANETHASH_KEY; + uint256 SPACETYPE_KEY; + uint256 BIOMEBASE_KEY; + bool PERLIN_MIRROR_X; + bool PERLIN_MIRROR_Y; + uint256 PERLIN_LENGTH_SCALE; // must be a power of two up to 8192 + // Game config + uint256 MAX_NATURAL_PLANET_LEVEL; + uint256 TIME_FACTOR_HUNDREDTHS; // speedup/slowdown game + uint256 PERLIN_THRESHOLD_1; + uint256 PERLIN_THRESHOLD_2; + uint256 PERLIN_THRESHOLD_3; + uint256 INIT_PERLIN_MIN; + uint256 INIT_PERLIN_MAX; + uint256 SPAWN_RIM_AREA; + uint256 BIOME_THRESHOLD_1; + uint256 BIOME_THRESHOLD_2; + uint256[10] PLANET_LEVEL_THRESHOLDS; + uint256 PLANET_RARITY; + bool PLANET_TRANSFER_ENABLED; + uint8[5][10][4] PLANET_TYPE_WEIGHTS; // spaceType (enum 0-3) -> planetLevel (0-7) -> planetType (enum 0-4) + uint256 SILVER_SCORE_VALUE; + uint256[6] ARTIFACT_POINT_VALUES; + uint256 PHOTOID_ACTIVATION_DELAY; + // Space Junk + bool SPACE_JUNK_ENABLED; + /** + Total amount of space junk a player can take on. + This can be overridden at runtime by updating + this value for a specific player in storage. + */ + uint256 SPACE_JUNK_LIMIT; + /** + The amount of junk that each level of planet + gives the player when moving to it for the + first time. + */ + uint256[10] PLANET_LEVEL_JUNK; + /** + The speed boost a movement receives when abandoning + a planet. + */ + uint256 ABANDON_SPEED_CHANGE_PERCENT; + /** + The range boost a movement receives when abandoning + a planet. + */ + uint256 ABANDON_RANGE_CHANGE_PERCENT; + // Capture Zones + bool CAPTURE_ZONES_ENABLED; + uint256 CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL; + uint256 CAPTURE_ZONE_RADIUS; + uint256[10] CAPTURE_ZONE_PLANET_LEVEL_SCORE; + uint256 CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED; + uint256 CAPTURE_ZONES_PER_5000_WORLD_RADIUS; + SpaceshipConstants SPACESHIPS; + uint256[64] ROUND_END_REWARDS_BY_RANK; + // Arena additions + // Target Planet + // uint256 TARGETS_REQUIRED_FOR_VICTORY; + // uint256 CLAIM_VICTORY_ENERGY_PERCENT; + // // Manual Spawn + // bool MANUAL_SPAWN; +} + struct Player { bool isInitialized; address player; diff --git a/eth/contracts/facets/DFArenaFacet.sol b/eth/contracts/facets/DFArenaFacet.sol new file mode 100644 index 00000000..27a03942 --- /dev/null +++ b/eth/contracts/facets/DFArenaFacet.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +contract DFArenaFacet {} diff --git a/eth/contracts/facets/DFStartFacet.sol b/eth/contracts/facets/DFStartFacet.sol new file mode 100644 index 00000000..7ff3b383 --- /dev/null +++ b/eth/contracts/facets/DFStartFacet.sol @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +// Contract imports +import {IERC173} from "@solidstate/contracts/access/IERC173.sol"; + +// Library imports +import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {WithStorage, Initializers} from "../libraries/LibStorage.sol"; + +// Type imports +import {PlanetDefaultStats, Upgrade, UpgradeBranch} from "../DFTypes.sol"; + +contract DFStartFacet is WithStorage { + event ArenaInitialized(address ownerAddress, address lobbyAddress); + + function i() internal pure returns (Initializers storage) { + return initializers(); + } + + function start() public { + gs().diamondAddress = address(this); + + ws().enabled = i().auxArgs.allowListEnabled; + uint256 allowedAddressesLength = i().auxArgs.allowedAddresses.length; + + if (ws().enabled && allowedAddressesLength > 0) { + // delegating call here because msg.sender must reminitializersn intact. + (bool success, bytes memory returndata) = (address(this)).delegatecall( + abi.encodeWithSignature( + "bulkAddToWhitelist(address[])", + i().auxArgs.allowedAddresses + ) + ); + require(success, "whitelisting ownership did not succeed"); + } + + ws().drip = 0.05 ether; + + gs().planetLevelsCount = 10; + + snarkConstants().DISABLE_ZK_CHECKS = i().initArgs.DISABLE_ZK_CHECKS; + snarkConstants().PLANETHASH_KEY = i().initArgs.PLANETHASH_KEY; + snarkConstants().SPACETYPE_KEY = i().initArgs.SPACETYPE_KEY; + snarkConstants().BIOMEBASE_KEY = i().initArgs.BIOMEBASE_KEY; + snarkConstants().PERLIN_MIRROR_X = i().initArgs.PERLIN_MIRROR_X; + snarkConstants().PERLIN_MIRROR_Y = i().initArgs.PERLIN_MIRROR_Y; + snarkConstants().PERLIN_LENGTH_SCALE = i().initArgs.PERLIN_LENGTH_SCALE; + + gameConstants().PLANET_LEVEL_THRESHOLDS = i().initArgs.PLANET_LEVEL_THRESHOLDS; + gameConstants().ADMIN_CAN_ADD_PLANETS = i().initArgs.ADMIN_CAN_ADD_PLANETS; + gameConstants().WORLD_RADIUS_LOCKED = i().initArgs.WORLD_RADIUS_LOCKED; + gameConstants().WORLD_RADIUS_MIN = i().initArgs.WORLD_RADIUS_MIN; + gameConstants().MAX_NATURAL_PLANET_LEVEL = i().initArgs.MAX_NATURAL_PLANET_LEVEL; + gameConstants().TIME_FACTOR_HUNDREDTHS = i().initArgs.TIME_FACTOR_HUNDREDTHS; + gameConstants().PERLIN_THRESHOLD_1 = i().initArgs.PERLIN_THRESHOLD_1; + gameConstants().PERLIN_THRESHOLD_2 = i().initArgs.PERLIN_THRESHOLD_2; + gameConstants().PERLIN_THRESHOLD_3 = i().initArgs.PERLIN_THRESHOLD_3; + gameConstants().INIT_PERLIN_MIN = i().initArgs.INIT_PERLIN_MIN; + gameConstants().INIT_PERLIN_MAX = i().initArgs.INIT_PERLIN_MAX; + gameConstants().SPAWN_RIM_AREA = i().initArgs.SPAWN_RIM_AREA; + gameConstants().BIOME_THRESHOLD_1 = i().initArgs.BIOME_THRESHOLD_1; + gameConstants().BIOME_THRESHOLD_2 = i().initArgs.BIOME_THRESHOLD_2; + gameConstants().PLANET_RARITY = i().initArgs.PLANET_RARITY; + gameConstants().PLANET_TRANSFER_ENABLED = i().initArgs.PLANET_TRANSFER_ENABLED; + gameConstants().PHOTOID_ACTIVATION_DELAY = i().initArgs.PHOTOID_ACTIVATION_DELAY; + gameConstants().LOCATION_REVEAL_COOLDOWN = i().initArgs.LOCATION_REVEAL_COOLDOWN; + gameConstants().PLANET_TYPE_WEIGHTS = i().initArgs.PLANET_TYPE_WEIGHTS; + gameConstants().SILVER_SCORE_VALUE = i().initArgs.SILVER_SCORE_VALUE; + gameConstants().ARTIFACT_POINT_VALUES = i().initArgs.ARTIFACT_POINT_VALUES; + // Space Junk + gameConstants().SPACE_JUNK_ENABLED = i().initArgs.SPACE_JUNK_ENABLED; + gameConstants().SPACE_JUNK_LIMIT = i().initArgs.SPACE_JUNK_LIMIT; + gameConstants().PLANET_LEVEL_JUNK = i().initArgs.PLANET_LEVEL_JUNK; + gameConstants().ABANDON_SPEED_CHANGE_PERCENT = i().initArgs.ABANDON_SPEED_CHANGE_PERCENT; + gameConstants().ABANDON_RANGE_CHANGE_PERCENT = i().initArgs.ABANDON_RANGE_CHANGE_PERCENT; + // Capture Zones + gameConstants().GAME_START_BLOCK = block.number; + gameConstants().CAPTURE_ZONES_ENABLED = i().initArgs.CAPTURE_ZONES_ENABLED; + gameConstants().CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL = i() + .initArgs + .CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL; + gameConstants().CAPTURE_ZONE_RADIUS = i().initArgs.CAPTURE_ZONE_RADIUS; + gameConstants().CAPTURE_ZONE_PLANET_LEVEL_SCORE = i() + .initArgs + .CAPTURE_ZONE_PLANET_LEVEL_SCORE; + gameConstants().CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED = i() + .initArgs + .CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED; + gameConstants().CAPTURE_ZONES_PER_5000_WORLD_RADIUS = i() + .initArgs + .CAPTURE_ZONES_PER_5000_WORLD_RADIUS; + + gs().nextChangeBlock = block.number + i().initArgs.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL; + + gs().worldRadius = i().initArgs.WORLD_RADIUS_MIN; // will be overridden by `LibGameUtils.updateWorldRadius()` if !WORLD_RADIUS_LOCKED + + gs().paused = i().initArgs.START_PAUSED; // || i().initArgs.CONFIRM_START; + + gs().TOKEN_MINT_END_TIMESTAMP = i().initArgs.TOKEN_MINT_END_TIMESTAMP; + + gs().initializedPlanetCountByLevel = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (uint256 idx = 0; idx < i().initArgs.PLANET_LEVEL_THRESHOLDS.length; idx++) { + gs().cumulativeRarities.push( + (2**24 / i().initArgs.PLANET_LEVEL_THRESHOLDS[idx]) * i().initArgs.PLANET_RARITY + ); + } + + initializeDefaults(); + initializeUpgrades(); + LibGameUtils.updateWorldRadius(); + + emit ArenaInitialized(IERC173(address(this)).owner(), address(this)); + } + + function initializeDefaults() private { + PlanetDefaultStats[] storage planetDefaultStats = planetDefaultStats(); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "Asteroid", + populationCap: 100000, + populationGrowth: 417, + range: 99, + speed: 75, + defense: 400, + silverGrowth: 0, + silverCap: 0, + barbarianPercentage: 0 + }) + ); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "Brown Dwarf", + populationCap: 400000, + populationGrowth: 833, + range: 177, + speed: 75, + defense: 400, + silverGrowth: 56, + silverCap: 100000, + barbarianPercentage: 1 + }) + ); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "Red Dwarf", + populationCap: 1600000, + populationGrowth: 1250, + range: 315, + speed: 75, + defense: 300, + silverGrowth: 167, + silverCap: 500000, + barbarianPercentage: 2 + }) + ); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "White Dwarf", + populationCap: 6000000, + populationGrowth: 1667, + range: 591, + speed: 75, + defense: 300, + silverGrowth: 417, + silverCap: 2500000, + barbarianPercentage: 3 + }) + ); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "Yellow Star", + populationCap: 25000000, + populationGrowth: 2083, + range: 1025, + speed: 75, + defense: 300, + silverGrowth: 833, + silverCap: 12000000, + barbarianPercentage: 4 + }) + ); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "Blue Star", + populationCap: 100000000, + populationGrowth: 2500, + range: 1734, + speed: 75, + defense: 200, + silverGrowth: 1667, + silverCap: 50000000, + barbarianPercentage: 5 + }) + ); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "Giant", + populationCap: 300000000, + populationGrowth: 2917, + range: 2838, + speed: 75, + defense: 200, + silverGrowth: 2778, + silverCap: 100000000, + barbarianPercentage: 7 + }) + ); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "Supergiant", + populationCap: 500000000, + populationGrowth: 3333, + range: 4414, + speed: 75, + defense: 200, + silverGrowth: 2778, + silverCap: 200000000, + barbarianPercentage: 10 + }) + ); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "Unlabeled1", + populationCap: 700000000, + populationGrowth: 3750, + range: 6306, + speed: 75, + defense: 200, + silverGrowth: 2778, + silverCap: 300000000, + barbarianPercentage: 20 + }) + ); + + planetDefaultStats.push( + PlanetDefaultStats({ + label: "Unlabeled2", + populationCap: 800000000, + populationGrowth: 4167, + range: 8829, + speed: 75, + defense: 200, + silverGrowth: 2778, + silverCap: 400000000, + barbarianPercentage: 25 + }) + ); + } + + function initializeUpgrades() private { + Upgrade[4][3] storage upgrades = upgrades(); + + // defense + upgrades[uint256(UpgradeBranch.DEFENSE)][0] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: 120 + }); + upgrades[uint256(UpgradeBranch.DEFENSE)][1] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: 120 + }); + upgrades[uint256(UpgradeBranch.DEFENSE)][2] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: 120 + }); + upgrades[uint256(UpgradeBranch.DEFENSE)][3] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 100, + speedMultiplier: 100, + defMultiplier: 120 + }); + + // range + upgrades[uint256(UpgradeBranch.RANGE)][0] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 125, + speedMultiplier: 100, + defMultiplier: 100 + }); + upgrades[uint256(UpgradeBranch.RANGE)][1] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 125, + speedMultiplier: 100, + defMultiplier: 100 + }); + upgrades[uint256(UpgradeBranch.RANGE)][2] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 125, + speedMultiplier: 100, + defMultiplier: 100 + }); + upgrades[uint256(UpgradeBranch.RANGE)][3] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 125, + speedMultiplier: 100, + defMultiplier: 100 + }); + + // speed + upgrades[uint256(UpgradeBranch.SPEED)][0] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 100, + speedMultiplier: 175, + defMultiplier: 100 + }); + upgrades[uint256(UpgradeBranch.SPEED)][1] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 100, + speedMultiplier: 175, + defMultiplier: 100 + }); + upgrades[uint256(UpgradeBranch.SPEED)][2] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 100, + speedMultiplier: 175, + defMultiplier: 100 + }); + upgrades[uint256(UpgradeBranch.SPEED)][3] = Upgrade({ + popCapMultiplier: 120, + popGroMultiplier: 120, + rangeMultiplier: 100, + speedMultiplier: 175, + defMultiplier: 100 + }); + } +} diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index 1c521033..acffd858 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} from "../DFTypes.sol"; +import {ArrivalData, AuxiliaryArgs, InitArgs, Planet, PlanetEventMetadata, PlanetDefaultStats, RevealedCoords, Player, SpaceshipConstants, Upgrade} from "../DFTypes.sol"; struct WhitelistStorage { bool enabled; @@ -106,12 +106,10 @@ struct GameConstants { uint256[64] ROUND_END_REWARDS_BY_RANK; } -struct SpaceshipConstants { - bool GEAR; - bool MOTHERSHIP; - bool TITAN; - bool CRESCENT; - bool WHALE; +// Initializers +struct Initializers { + InitArgs initArgs; + AuxiliaryArgs auxArgs; } // SNARK keys and perlin params @@ -178,6 +176,9 @@ library LibStorage { bytes32 constant PLANET_DEFAULT_STATS_POSITION = keccak256("darkforest.constants.planetDefaultStats"); bytes32 constant UPGRADE_POSITION = keccak256("darkforest.constants.upgrades"); + bytes32 constant ARENA_INITIALIZERS_POSITION = keccak256("darkforest.initializers.arena"); + bytes32 constant ARENA_CONSTANTS_POSITION = keccak256("darkforest.constants.arena"); + bytes32 constant ARENA_STORAGE_POSITION = keccak256("darkforest.storage.arena"); function gameStorage() internal pure returns (GameStorage storage gs) { bytes32 position = GAME_STORAGE_POSITION; @@ -220,6 +221,27 @@ library LibStorage { upgrades.slot := position } } + + // function arenaStorage() internal pure returns (ArenaStorage storage gs) { + // bytes32 position = ARENA_STORAGE_POSITION; + // assembly { + // gs.slot := position + // } + // } + + // function arenaConstants() internal pure returns (ArenaConstants storage gs) { + // bytes32 position = ARENA_CONSTANTS_POSITION; + // assembly { + // gs.slot := position + // } + // } + + function initializers() internal pure returns (Initializers storage i) { + bytes32 position = ARENA_INITIALIZERS_POSITION; + assembly { + i.slot := position + } + } } /** @@ -255,4 +277,8 @@ contract WithStorage { function upgrades() internal pure returns (Upgrade[4][3] storage) { return LibStorage.upgrades(); } + + function initializers() internal pure returns (Initializers storage) { + return LibStorage.initializers(); + } } diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index 7d28c90f..2b4e44c8 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -259,6 +259,7 @@ export async function deployAndCut( const adminFacet = await deployAdminFacet({}, libraries, hre); const lobbyFacet = await deployLobbyFacet({}, {}, hre); const rewardFacet = await deployRewardFacet({}, {}, hre); + const startFacet = await deployStartFacet({}, libraries, hre); // The `cuts` to perform for Dark Forest facets const darkForestFacetCuts = [ @@ -273,6 +274,7 @@ export async function deployAndCut( ...changes.getFacetCuts('DFLobbyFacet', lobbyFacet), ...changes.getFacetCuts('DFRewardFacet', rewardFacet), ...changes.getFacetCuts('DFSpaceshipFacet', spaceshipFacet), + ...changes.getFacetCuts('DFStartFacet', startFacet), ...changes.getFacetCuts('DFTokenFacet', tokenFacet), ]; @@ -298,9 +300,12 @@ export async function deployAndCut( // More info here: https://eips.ethereum.org/EIPS/eip-2535#diamond-interface const initAddress = diamondInit.address; const initFunctionCall = diamondInit.interface.encodeFunctionData('init', [ - whitelistEnabled, - tokenBaseUri, initializers, + { + allowListEnabled: whitelistEnabled, + baseURI: tokenBaseUri, + allowedAddresses: [], + }, ]); const initTx = await diamondCut.diamondCut(toCut, initAddress, initFunctionCall); @@ -502,19 +507,29 @@ async function deployDiamond({}, hre: HardhatRuntimeEnvironment) { return contract; } -export async function deployDiamondInit( +export async function deployDiamondInit({}, {}: Libraries, hre: HardhatRuntimeEnvironment) { + // DFInitialize provides a function that is called when the diamond is upgraded to initialize state variables + // Read about how the diamondCut function works here: https://eips.ethereum.org/EIPS/eip-2535#addingreplacingremoving-functions + const factory = await hre.ethers.getContractFactory('DFInitialize', { + libraries: {}, + }); + const contract = await factory.deploy(); + await contract.deployTransaction.wait(); + console.log(`DFInitialize deployed to: ${contract.address}`); + return contract; +} + +export async function deployStartFacet( {}, { LibGameUtils }: Libraries, hre: HardhatRuntimeEnvironment ) { - // DFInitialize provides a function that is called when the diamond is upgraded to initialize state variables - // Read about how the diamondCut function works here: https://eips.ethereum.org/EIPS/eip-2535#addingreplacingremoving-functions - const factory = await hre.ethers.getContractFactory('DFInitialize', { + const factory = await hre.ethers.getContractFactory('DFStartFacet', { libraries: { LibGameUtils }, }); const contract = await factory.deploy(); await contract.deployTransaction.wait(); - console.log(`DFInitialize deployed to: ${contract.address}`); + console.log(`DFStartFacet deployed to: ${contract.address}`); return contract; } diff --git a/eth/test/DFLobby.test.ts b/eth/test/DFLobby.test.ts index 1e315436..438d2f80 100644 --- a/eth/test/DFLobby.test.ts +++ b/eth/test/DFLobby.test.ts @@ -12,15 +12,19 @@ const _INTERFACE_ID_IDIAMOND_READABLE = '0x48e2b093'; const _INTERFACE_ID_IDIAMOND_WRITABLE = '0x1f931c1c'; const _INTERFACE_ID_IERC173 = '0x7f5828d0'; -describe('DarkForestLobby', function () { +describe.only('DarkForestLobby', function () { let world: World; let lobby: DarkForest; const initAddress = ethers.constants.AddressZero; const initFunctionCall = '0x'; + const NEW_RADIUS = 69; async function worldFixture() { const _world = await loadFixture(defaultWorldFixture); - const _lobby = await createArena(_world.user1Core, hre.settings.darkforest.initializers); + const _lobby = await createArena(_world.user1Core, { + ...hre.settings.darkforest.initializers, + WORLD_RADIUS_MIN: NEW_RADIUS, + }); return { _world, _lobby }; } @@ -36,6 +40,9 @@ describe('DarkForestLobby', function () { 'LobbyCreated' ); }); + it('New lobby has initializer value', async function () { + expect((await lobby.getGameConstants()).WORLD_RADIUS_MIN).to.equal(NEW_RADIUS); + }); it('Transfers Ownership', async function () { await expect(world.contract.transferOwnership(world.user1.address)).to.emit( world.contract, diff --git a/eth/test/utils/TestWorld.ts b/eth/test/utils/TestWorld.ts index bd84809b..350a1bd4 100644 --- a/eth/test/utils/TestWorld.ts +++ b/eth/test/utils/TestWorld.ts @@ -36,9 +36,12 @@ export async function createArena( const { LibGameUtils } = await deployLibraries({}, hre); const DFInitialize = await deployDiamondInit({}, { LibGameUtils }, hre); const initFunctionCall = DFInitialize.interface.encodeFunctionData('init', [ - false, - '', initializers, + { + allowListEnabled: false, + baseURI: '', + allowedAddresses: [], + }, ]); const newLobbyTx = await contract.createLobby(DFInitialize.address, initFunctionCall); @@ -47,6 +50,9 @@ export async function createArena( const events = await contract.queryFilter(eventFilter, 'latest'); const { lobbyAddress } = events[0].args; const lobby = await hre.ethers.getContractAt('DarkForest', lobbyAddress); + // initialize + const startTx = await lobby.start(); + await startTx.wait(); return lobby; } From 2fa58e37f569035f84c09215e5466c4a8d7900dd Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 10:51:38 -0400 Subject: [PATCH 04/17] feat: add new arena constants --- eth/contracts/DFTypes.sol | 8 ++++---- eth/contracts/facets/DFStartFacet.sol | 4 ++++ eth/contracts/libraries/LibStorage.sol | 21 +++++---------------- eth/darkforest.toml | 4 ++++ eth/hardhat.config.ts | 1 - eth/test/DFArena.ts | 26 ++++++++++++++++++++++++++ eth/test/utils/WorldConstants.ts | 9 +++++++++ packages/settings/src/initializers.ts | 4 ++++ 8 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 eth/test/DFArena.ts diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index bd9efa00..38279d16 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -105,10 +105,10 @@ struct InitArgs { uint256[64] ROUND_END_REWARDS_BY_RANK; // Arena additions // Target Planet - // uint256 TARGETS_REQUIRED_FOR_VICTORY; - // uint256 CLAIM_VICTORY_ENERGY_PERCENT; - // // Manual Spawn - // bool MANUAL_SPAWN; + uint256 TARGETS_REQUIRED_FOR_VICTORY; + uint256 CLAIM_VICTORY_ENERGY_PERCENT; + // Manual Spawn + bool MANUAL_SPAWN; } struct Player { diff --git a/eth/contracts/facets/DFStartFacet.sol b/eth/contracts/facets/DFStartFacet.sol index 7ff3b383..e279db5c 100644 --- a/eth/contracts/facets/DFStartFacet.sol +++ b/eth/contracts/facets/DFStartFacet.sol @@ -90,6 +90,10 @@ contract DFStartFacet is WithStorage { gameConstants().CAPTURE_ZONES_PER_5000_WORLD_RADIUS = i() .initArgs .CAPTURE_ZONES_PER_5000_WORLD_RADIUS; + // Arena Values + gameConstants().TARGETS_REQUIRED_FOR_VICTORY = i().initArgs.TARGETS_REQUIRED_FOR_VICTORY; + gameConstants().CLAIM_VICTORY_ENERGY_PERCENT = i().initArgs.CLAIM_VICTORY_ENERGY_PERCENT; + gameConstants().MANUAL_SPAWN = i().initArgs.MANUAL_SPAWN; gs().nextChangeBlock = block.number + i().initArgs.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL; diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index acffd858..d55833fd 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -104,6 +104,11 @@ struct GameConstants { uint256 CAPTURE_ZONES_PER_5000_WORLD_RADIUS; SpaceshipConstants SPACESHIPS; uint256[64] ROUND_END_REWARDS_BY_RANK; + // Arena constants + uint256 TARGETS_REQUIRED_FOR_VICTORY; + uint256 CLAIM_VICTORY_ENERGY_PERCENT; + // Manual Spawn + bool MANUAL_SPAWN; } // Initializers @@ -177,8 +182,6 @@ library LibStorage { keccak256("darkforest.constants.planetDefaultStats"); bytes32 constant UPGRADE_POSITION = keccak256("darkforest.constants.upgrades"); bytes32 constant ARENA_INITIALIZERS_POSITION = keccak256("darkforest.initializers.arena"); - bytes32 constant ARENA_CONSTANTS_POSITION = keccak256("darkforest.constants.arena"); - bytes32 constant ARENA_STORAGE_POSITION = keccak256("darkforest.storage.arena"); function gameStorage() internal pure returns (GameStorage storage gs) { bytes32 position = GAME_STORAGE_POSITION; @@ -222,20 +225,6 @@ library LibStorage { } } - // function arenaStorage() internal pure returns (ArenaStorage storage gs) { - // bytes32 position = ARENA_STORAGE_POSITION; - // assembly { - // gs.slot := position - // } - // } - - // function arenaConstants() internal pure returns (ArenaConstants storage gs) { - // bytes32 position = ARENA_CONSTANTS_POSITION; - // assembly { - // gs.slot := position - // } - // } - function initializers() internal pure returns (Initializers storage i) { bytes32 position = ARENA_INITIALIZERS_POSITION; assembly { diff --git a/eth/darkforest.toml b/eth/darkforest.toml index da2503ed..5b42e7c9 100644 --- a/eth/darkforest.toml +++ b/eth/darkforest.toml @@ -225,6 +225,10 @@ ROUND_END_REWARDS_BY_RANK=[ 0 ] +# Arena constants +TARGETS_REQUIRED_FOR_VICTORY=0 +CLAIM_VICTORY_ENERGY_PERCENT=50 +MANUAL_SPAWN=false [initializers.SPACESHIPS] GEAR = true diff --git a/eth/hardhat.config.ts b/eth/hardhat.config.ts index fa307e48..5a719f7e 100644 --- a/eth/hardhat.config.ts +++ b/eth/hardhat.config.ts @@ -41,7 +41,6 @@ import './tasks/whitelist'; declare module 'hardhat/types' { interface HardhatSettings { contracts: Contracts; - darkforest: { initializers: Initializers; adminPlanets: AdminPlanets; diff --git a/eth/test/DFArena.ts b/eth/test/DFArena.ts new file mode 100644 index 00000000..53234c08 --- /dev/null +++ b/eth/test/DFArena.ts @@ -0,0 +1,26 @@ +import { DarkForest } from '@dfdao/contracts/typechain'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { createArena, defaultWorldFixture, World } from './utils/TestWorld'; +import { arenaInitializers } from './utils/WorldConstants'; + +describe.only('DarkForestArena', function () { + let world: World; + let lobby: DarkForest; + + async function worldFixture() { + const _world = await loadFixture(defaultWorldFixture); + const _lobby = await createArena(_world.user1Core, arenaInitializers); + return { _world, _lobby }; + } + + beforeEach('load fixture', async function () { + const { _world, _lobby } = await loadFixture(worldFixture); + world = _world; + lobby = _lobby; + }); + + it('has arena constants', async function () { + expect((await lobby.getGameConstants()).MANUAL_SPAWN).to.equal(true); + }); +}); diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index d211bf42..349cd6a6 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -105,6 +105,9 @@ const defaultInitializerValues = { 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, 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, 0, 0, 0, 0, 0, ], + TARGETS_REQUIRED_FOR_VICTORY: 0, + CLAIM_VICTORY_ENERGY_PERCENT: 50, + MANUAL_SPAWN: false, }; // This builds a fake HRE-like object used to initialize the test contracts @@ -123,6 +126,12 @@ export const target4Initializers = decodeInitializers({ WORLD_RADIUS_MIN: 1, }); +// This builds a fake HRE-like object used to initialize the test contracts +export const arenaInitializers = decodeInitializers({ + ...defaultInitializerValues, + MANUAL_SPAWN: true, +}); + export const VALID_INIT_PERLIN = initializers.INIT_PERLIN_MIN; export const NEBULA_PERLIN = initializers.PERLIN_THRESHOLD_1 - 1; export const SPACE_PERLIN = initializers.PERLIN_THRESHOLD_1; diff --git a/packages/settings/src/initializers.ts b/packages/settings/src/initializers.ts index 039bc787..977ac367 100644 --- a/packages/settings/src/initializers.ts +++ b/packages/settings/src/initializers.ts @@ -78,6 +78,10 @@ export const decodeInitializers = decoders.guard( WHALE: decoders.boolean, }), ROUND_END_REWARDS_BY_RANK: exactArray64(decoders.number), + // Arena + TARGETS_REQUIRED_FOR_VICTORY: decoders.number, + CLAIM_VICTORY_ENERGY_PERCENT: decoders.number, + MANUAL_SPAWN: decoders.boolean, }), { style: 'simple' } ); From 0f5e842dcc28752595aefe7b8bfccb10c0471853 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 11:33:41 -0400 Subject: [PATCH 05/17] feat: manual spawn and test works --- eth/contracts/DFTypes.sol | 19 +++++++ eth/contracts/facets/DFAdminFacet.sol | 20 ++++--- eth/contracts/facets/DFArenaFacet.sol | 77 +++++++++++++++++++++++++- eth/contracts/facets/DFCoreFacet.sol | 19 ++++++- eth/contracts/libraries/LibPlanet.sol | 23 +++++--- eth/contracts/libraries/LibStorage.sol | 2 + eth/tasks/deploy.ts | 16 ++++++ eth/test/DFArena.test.ts | 68 +++++++++++++++++++++++ eth/test/DFArena.ts | 26 --------- eth/test/utils/WorldConstants.ts | 1 + 10 files changed, 224 insertions(+), 47 deletions(-) create mode 100644 eth/test/DFArena.test.ts delete mode 100644 eth/test/DFArena.ts diff --git a/eth/contracts/DFTypes.sol b/eth/contracts/DFTypes.sol index 38279d16..9af5bef0 100644 --- a/eth/contracts/DFTypes.sol +++ b/eth/contracts/DFTypes.sol @@ -165,6 +165,9 @@ struct Planet { uint256 activeArtifact; uint256 wormholeTo; uint256 artifactActivationTime; + // Arena Stuff + bool spawnPlanet; + bool targetPlanet; } struct RevealedCoords { @@ -210,6 +213,8 @@ struct DFPInitPlanetArgs { SpaceType spaceType; PlanetType planetType; bool isHomePlanet; + bool isSpawnPlanet; + bool isTargetPlanet; } struct DFPMoveArgs { @@ -375,3 +380,17 @@ enum SilverInfo { Unknown, TokenType } + +// Arena Stuff +struct AdminCreateRevealPlanetArgs { + uint256 location; + uint256 x; + uint256 y; + uint256 perlin; + uint256 level; + PlanetType planetType; + bool requireValidLocationId; + bool isTargetPlanet; + bool isSpawnPlanet; + uint256[] blockedPlanetIds; +} diff --git a/eth/contracts/facets/DFAdminFacet.sol b/eth/contracts/facets/DFAdminFacet.sol index fad409b3..0ecec222 100644 --- a/eth/contracts/facets/DFAdminFacet.sol +++ b/eth/contracts/facets/DFAdminFacet.sol @@ -124,15 +124,17 @@ contract DFAdminFacet is WithStorage { } SpaceType spaceType = LibGameUtils.spaceTypeFromPerlin(args.perlin); LibPlanet._initializePlanet( - DFPInitPlanetArgs( - args.location, - args.perlin, - args.level, - gameConstants().TIME_FACTOR_HUNDREDTHS, - spaceType, - args.planetType, - false - ) + DFPInitPlanetArgs({ + location: args.location, + perlin: args.perlin, + level: args.level, + TIME_FACTOR_HUNDREDTHS: gameConstants().TIME_FACTOR_HUNDREDTHS, + spaceType: spaceType, + planetType: args.planetType, + isHomePlanet: false, + isSpawnPlanet: false, + isTargetPlanet: false + }) ); gs().planetIds.push(args.location); gs().initializedPlanetCountByLevel[args.level] += 1; diff --git a/eth/contracts/facets/DFArenaFacet.sol b/eth/contracts/facets/DFArenaFacet.sol index 27a03942..3cf388b4 100644 --- a/eth/contracts/facets/DFArenaFacet.sol +++ b/eth/contracts/facets/DFArenaFacet.sol @@ -1,4 +1,79 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; -contract DFArenaFacet {} +// Library imports +import {LibGameUtils} from "../libraries/LibGameUtils.sol"; +import {LibPermissions} from "../libraries/LibPermissions.sol"; +import {LibPlanet} from "../libraries/LibPlanet.sol"; + +// Storage imports +import {WithStorage} from "../libraries/LibStorage.sol"; + +// Type imports +import {AdminCreateRevealPlanetArgs, DFPInitPlanetArgs, SpaceType} from "../DFTypes.sol"; +import "hardhat/console.sol"; + +contract DFArenaFacet is WithStorage { + event AdminPlanetCreated(uint256 loc); + + function createArenaPlanet(AdminCreateRevealPlanetArgs memory args) public { + require(gameConstants().ADMIN_CAN_ADD_PLANETS, "admin can no longer add planets"); + // TODO: check init planet hash + require(msg.sender == LibPermissions.contractOwner(), "must be admin"); + + if (args.requireValidLocationId) { + require(LibGameUtils._locationIdValid(args.location), "Not a valid planet location"); + } + + if (args.isTargetPlanet) { + require( + gameConstants().TARGETS_REQUIRED_FOR_VICTORY > 0, + "admin cannot create target planets" + ); + gs().targetPlanetIds.push(args.location); + } + if (args.isSpawnPlanet) { + require(gameConstants().MANUAL_SPAWN, "admin cannot create spawn planets"); + gs().spawnPlanetIds.push(args.location); + } + + SpaceType spaceType = LibGameUtils.spaceTypeFromPerlin(args.perlin); + LibPlanet._initializePlanet( + DFPInitPlanetArgs({ + location: args.location, + perlin: args.perlin, + level: args.level, + TIME_FACTOR_HUNDREDTHS: gameConstants().TIME_FACTOR_HUNDREDTHS, + spaceType: spaceType, + planetType: args.planetType, + isHomePlanet: false, + isSpawnPlanet: args.isSpawnPlanet, + isTargetPlanet: args.isTargetPlanet + }) + ); + + gs().planetIds.push(args.location); + gs().initializedPlanetCountByLevel[args.level] += 1; + + emit AdminPlanetCreated(args.location); + } + + /** + * Getters + */ + function getNTargetPlanets() public view returns (uint256) { + return gs().targetPlanetIds.length; + } + + function getNSpawnPlanets() public view returns (uint256) { + return gs().spawnPlanetIds.length; + } + + function targetPlanetIds(uint256 idx) public view returns (uint256) { + return gs().targetPlanetIds[idx]; + } + + function spawnPlanetIds(uint256 idx) public view returns (uint256) { + return gs().spawnPlanetIds[idx]; + } +} diff --git a/eth/contracts/facets/DFCoreFacet.sol b/eth/contracts/facets/DFCoreFacet.sol index cd16c381..2bcd8a2d 100644 --- a/eth/contracts/facets/DFCoreFacet.sol +++ b/eth/contracts/facets/DFCoreFacet.sol @@ -17,6 +17,7 @@ import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports import {SpaceType, Planet, Player, ArtifactType, DFPInitPlanetArgs, DFPMoveArgs, DFPFindArtifactArgs, AdminCreatePlanetArgs} from "../DFTypes.sol"; +import "hardhat/console.sol"; contract DFCoreFacet is WithStorage { using ABDKMath64x64 for *; @@ -121,12 +122,26 @@ contract DFCoreFacet is WithStorage { uint256[2] memory _c, uint256[8] memory _input ) public onlyWhitelisted returns (uint256) { - LibPlanet.initializePlanet(_a, _b, _c, _input, true); - uint256 _location = _input[0]; uint256 _perlin = _input[1]; uint256 _radius = _input[2]; + if (gameConstants().MANUAL_SPAWN) { + Planet storage _planet = gs().planets[_location]; + + require(_planet.spawnPlanet, "Planet is not a spawn planet"); + + require(_planet.isInitialized, "Planet not initialized"); + require(_planet.owner == address(0), "Planet is owned"); + require(!_planet.isHomePlanet, "Planet is already a home planet"); + + _planet.isHomePlanet = true; + _planet.owner = msg.sender; + _planet.population = (_planet.populationCap * 99) / 100; + _planet.lastUpdated = block.timestamp; + } else { + LibPlanet.initializePlanet(_a, _b, _c, _input, true); + } require(LibPlanet.checkPlayerInit(_location, _perlin, _radius)); // Initialize player data diff --git a/eth/contracts/libraries/LibPlanet.sol b/eth/contracts/libraries/LibPlanet.sol index 50fda7a7..0a807754 100644 --- a/eth/contracts/libraries/LibPlanet.sol +++ b/eth/contracts/libraries/LibPlanet.sol @@ -74,15 +74,17 @@ library LibPlanet { } return - DFPInitPlanetArgs( - _location, - _perlin, - level, - gameConstants().TIME_FACTOR_HUNDREDTHS, - spaceType, - planetType, - _isHomePlanet - ); + DFPInitPlanetArgs({ + location: _location, + perlin: _perlin, + level: level, + TIME_FACTOR_HUNDREDTHS: gameConstants().TIME_FACTOR_HUNDREDTHS, + spaceType: spaceType, + planetType: planetType, + isHomePlanet: _isHomePlanet, + isSpawnPlanet: false, + isTargetPlanet: false + }); } /** @@ -181,6 +183,9 @@ library LibPlanet { _planet.energyGroDoublers = 0; _planet.silverGroDoublers = 0; + _planet.spawnPlanet = args.isSpawnPlanet; + _planet.targetPlanet = args.isTargetPlanet; + if (args.isHomePlanet) { _planet.isHomePlanet = true; _planet.owner = msg.sender; diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index d55833fd..fd66e9b2 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -45,6 +45,8 @@ struct GameStorage { mapping(uint256 => ArrivalData) planetArrivals; // Capture Zones uint256 nextChangeBlock; + uint256[] targetPlanetIds; + uint256[] spawnPlanetIds; } // Game config diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index 2b4e44c8..f250111d 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -253,6 +253,7 @@ export async function deployAndCut( hre ); const getterFacet = await deployGetterFacet({}, libraries, hre); + const arenaFacet = await deployArenaFacet({}, libraries, hre); const spaceshipFacet = await deploySpaceshipFacet({}, libraries, hre); const whitelistFacet = await deployWhitelistFacet({}, libraries, hre); const verifierFacet = await deployVerifierFacet({}, libraries, hre); @@ -263,6 +264,7 @@ export async function deployAndCut( // The `cuts` to perform for Dark Forest facets const darkForestFacetCuts = [ + ...changes.getFacetCuts('DFArenaFacet', arenaFacet), ...changes.getFacetCuts('DFCoreFacet', coreFacet), ...changes.getFacetCuts('DFMoveFacet', moveFacet), ...changes.getFacetCuts('DFCaptureFacet', captureFacet), @@ -337,6 +339,20 @@ export async function deployGetterFacet({}, {}: Libraries, hre: HardhatRuntimeEn return contract; } +export async function deployArenaFacet( + {}, + { LibGameUtils, LibPlanet }: Libraries, + hre: HardhatRuntimeEnvironment +) { + const factory = await hre.ethers.getContractFactory('DFArenaFacet', { + libraries: { LibGameUtils, LibPlanet }, + }); + const contract = await factory.deploy(); + await contract.deployTransaction.wait(); + console.log('DFArenaFacet deployed to:', contract.address); + return contract; +} + export async function deployAdminFacet( {}, { LibGameUtils, LibPlanet }: Libraries, diff --git a/eth/test/DFArena.test.ts b/eth/test/DFArena.test.ts new file mode 100644 index 00000000..d1e5a0ee --- /dev/null +++ b/eth/test/DFArena.test.ts @@ -0,0 +1,68 @@ +import { DarkForest } from '@dfdao/contracts/typechain'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { makeInitArgs, makeRevealArgs } from './utils/TestUtils'; +import { createArena, defaultWorldFixture, World } from './utils/TestWorld'; +import { ADMIN_PLANET_CLOAKED, arenaInitializers } from './utils/WorldConstants'; + +describe.only('DarkForestArena', function () { + let world: World; + let lobby: DarkForest; + + async function worldFixture() { + const _world = await loadFixture(defaultWorldFixture); + const _lobby = await createArena(_world.user1Core, arenaInitializers); + return { _world, _lobby }; + } + + beforeEach('load fixture', async function () { + const { _world, _lobby } = await loadFixture(worldFixture); + world = _world; + lobby = _lobby; + }); + + describe('basic functions', function () { + it('has arena constants', async function () { + expect((await lobby.getGameConstants()).MANUAL_SPAWN).to.equal(true); + expect((await lobby.getGameConstants()).ADMIN_CAN_ADD_PLANETS).to.equal(true); + }); + }); + + describe('Planets', function () { + beforeEach('connect lobby to user1', async function () { + lobby = lobby.connect(world.user1); + }); + it('allows admin to create a spawn planet and player to spawn', async function () { + const perlin = 20; + const level = 5; + const planetType = 1; // asteroid field + const x = 10; + const y = 20; + await lobby.createArenaPlanet({ + location: ADMIN_PLANET_CLOAKED.id, + x, + y, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: true, + blockedPlanetIds: [], + }); + + await lobby.revealLocation(...makeRevealArgs(ADMIN_PLANET_CLOAKED, x, y)); + + const numSpawnPlanets = await lobby.getNSpawnPlanets(); + expect(numSpawnPlanets).to.equal(1); + + const spawnPlanet = await lobby.spawnPlanetIds(0); + + expect(spawnPlanet).to.equal(ADMIN_PLANET_CLOAKED.id); + + await expect(lobby.initializePlayer(...makeInitArgs(ADMIN_PLANET_CLOAKED))) + .to.emit(lobby, 'PlayerInitialized') + .withArgs(world.user1.address, ADMIN_PLANET_CLOAKED.id.toString()); + }); + }); +}); diff --git a/eth/test/DFArena.ts b/eth/test/DFArena.ts deleted file mode 100644 index 53234c08..00000000 --- a/eth/test/DFArena.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DarkForest } from '@dfdao/contracts/typechain'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { expect } from 'chai'; -import { createArena, defaultWorldFixture, World } from './utils/TestWorld'; -import { arenaInitializers } from './utils/WorldConstants'; - -describe.only('DarkForestArena', function () { - let world: World; - let lobby: DarkForest; - - async function worldFixture() { - const _world = await loadFixture(defaultWorldFixture); - const _lobby = await createArena(_world.user1Core, arenaInitializers); - return { _world, _lobby }; - } - - beforeEach('load fixture', async function () { - const { _world, _lobby } = await loadFixture(worldFixture); - world = _world; - lobby = _lobby; - }); - - it('has arena constants', async function () { - expect((await lobby.getGameConstants()).MANUAL_SPAWN).to.equal(true); - }); -}); diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index 349cd6a6..54114545 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -130,6 +130,7 @@ export const target4Initializers = decodeInitializers({ export const arenaInitializers = decodeInitializers({ ...defaultInitializerValues, MANUAL_SPAWN: true, + ADMIN_CAN_ADD_PLANETS: true, }); export const VALID_INIT_PERLIN = initializers.INIT_PERLIN_MIN; From 58153c2d301be9da899b2ed4d3708cae487f11b4 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 12:12:03 -0400 Subject: [PATCH 06/17] feat: all tests pass with new deploy logic --- eth/contracts/facets/DFStartFacet.sol | 5 ++++ eth/tasks/deploy.ts | 7 +++++ eth/test/DFArena.test.ts | 39 +++++++++------------------ eth/test/DFLobby.test.ts | 2 +- eth/test/utils/TestWorld.ts | 14 +++++++++- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/eth/contracts/facets/DFStartFacet.sol b/eth/contracts/facets/DFStartFacet.sol index e279db5c..287162a4 100644 --- a/eth/contracts/facets/DFStartFacet.sol +++ b/eth/contracts/facets/DFStartFacet.sol @@ -36,6 +36,8 @@ contract DFStartFacet is WithStorage { } ws().drip = 0.05 ether; + ws().relayerRewardsEnabled = false; + ws().relayerReward = 0.01 ether; gs().planetLevelsCount = 10; @@ -90,6 +92,9 @@ contract DFStartFacet is WithStorage { gameConstants().CAPTURE_ZONES_PER_5000_WORLD_RADIUS = i() .initArgs .CAPTURE_ZONES_PER_5000_WORLD_RADIUS; + + gameConstants().SPACESHIPS = i().initArgs.SPACESHIPS; + // Arena Values gameConstants().TARGETS_REQUIRED_FOR_VICTORY = i().initArgs.TARGETS_REQUIRED_FOR_VICTORY; gameConstants().CLAIM_VICTORY_ENERGY_PERCENT = i().initArgs.CLAIM_VICTORY_ENERGY_PERCENT; diff --git a/eth/tasks/deploy.ts b/eth/tasks/deploy.ts index f250111d..d8e1ea4e 100644 --- a/eth/tasks/deploy.ts +++ b/eth/tasks/deploy.ts @@ -312,10 +312,17 @@ export async function deployAndCut( const initTx = await diamondCut.diamondCut(toCut, initAddress, initFunctionCall); const initReceipt = await initTx.wait(); + if (!initReceipt.status) { throw Error(`Diamond cut failed: ${initTx.hash}`); } console.log('Completed diamond cut'); + const startTx = await diamondCut.start(); + const startRct = await startTx.wait(); + if (!startRct.status) { + throw Error(`Diamond start failed: ${initTx.hash}`); + } + console.log('Completed diamond start'); return [diamond, diamondInit, initReceipt] as const; } diff --git a/eth/test/DFArena.test.ts b/eth/test/DFArena.test.ts index d1e5a0ee..58859151 100644 --- a/eth/test/DFArena.test.ts +++ b/eth/test/DFArena.test.ts @@ -1,44 +1,31 @@ -import { DarkForest } from '@dfdao/contracts/typechain'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { makeInitArgs, makeRevealArgs } from './utils/TestUtils'; -import { createArena, defaultWorldFixture, World } from './utils/TestWorld'; -import { ADMIN_PLANET_CLOAKED, arenaInitializers } from './utils/WorldConstants'; +import { arenaWorldFixture, World } from './utils/TestWorld'; +import { ADMIN_PLANET_CLOAKED } from './utils/WorldConstants'; -describe.only('DarkForestArena', function () { +describe('DarkForestArena', function () { let world: World; - let lobby: DarkForest; - - async function worldFixture() { - const _world = await loadFixture(defaultWorldFixture); - const _lobby = await createArena(_world.user1Core, arenaInitializers); - return { _world, _lobby }; - } beforeEach('load fixture', async function () { - const { _world, _lobby } = await loadFixture(worldFixture); - world = _world; - lobby = _lobby; + world = await loadFixture(arenaWorldFixture); }); describe('basic functions', function () { it('has arena constants', async function () { - expect((await lobby.getGameConstants()).MANUAL_SPAWN).to.equal(true); - expect((await lobby.getGameConstants()).ADMIN_CAN_ADD_PLANETS).to.equal(true); + expect((await world.contract.getGameConstants()).MANUAL_SPAWN).to.equal(true); + expect((await world.contract.getGameConstants()).ADMIN_CAN_ADD_PLANETS).to.equal(true); }); }); - describe('Planets', function () { - beforeEach('connect lobby to user1', async function () { - lobby = lobby.connect(world.user1); - }); + describe('Manual Spawn', function () { it('allows admin to create a spawn planet and player to spawn', async function () { const perlin = 20; const level = 5; const planetType = 1; // asteroid field const x = 10; const y = 20; - await lobby.createArenaPlanet({ + await world.contract.createArenaPlanet({ location: ADMIN_PLANET_CLOAKED.id, x, y, @@ -51,17 +38,17 @@ describe.only('DarkForestArena', function () { blockedPlanetIds: [], }); - await lobby.revealLocation(...makeRevealArgs(ADMIN_PLANET_CLOAKED, x, y)); + await world.user1Core.revealLocation(...makeRevealArgs(ADMIN_PLANET_CLOAKED, x, y)); - const numSpawnPlanets = await lobby.getNSpawnPlanets(); + const numSpawnPlanets = await world.user1Core.getNSpawnPlanets(); expect(numSpawnPlanets).to.equal(1); - const spawnPlanet = await lobby.spawnPlanetIds(0); + const spawnPlanet = await world.user1Core.spawnPlanetIds(0); expect(spawnPlanet).to.equal(ADMIN_PLANET_CLOAKED.id); - await expect(lobby.initializePlayer(...makeInitArgs(ADMIN_PLANET_CLOAKED))) - .to.emit(lobby, 'PlayerInitialized') + await expect(world.user1Core.initializePlayer(...makeInitArgs(ADMIN_PLANET_CLOAKED))) + .to.emit(world.user1Core, 'PlayerInitialized') .withArgs(world.user1.address, ADMIN_PLANET_CLOAKED.id.toString()); }); }); diff --git a/eth/test/DFLobby.test.ts b/eth/test/DFLobby.test.ts index 438d2f80..137edc18 100644 --- a/eth/test/DFLobby.test.ts +++ b/eth/test/DFLobby.test.ts @@ -12,7 +12,7 @@ const _INTERFACE_ID_IDIAMOND_READABLE = '0x48e2b093'; const _INTERFACE_ID_IDIAMOND_WRITABLE = '0x1f931c1c'; const _INTERFACE_ID_IERC173 = '0x7f5828d0'; -describe.only('DarkForestLobby', function () { +describe('DarkForestLobby', function () { let world: World; let lobby: DarkForest; const initAddress = ethers.constants.AddressZero; diff --git a/eth/test/utils/TestWorld.ts b/eth/test/utils/TestWorld.ts index 350a1bd4..95c82f7c 100644 --- a/eth/test/utils/TestWorld.ts +++ b/eth/test/utils/TestWorld.ts @@ -4,7 +4,12 @@ import { BigNumber, utils } from 'ethers'; import hre from 'hardhat'; import type { HardhatRuntimeEnvironment } from 'hardhat/types'; import { deployAndCut, deployDiamondInit, deployLibraries } from '../../tasks/deploy'; -import { initializers, noPlanetTransferInitializers, target4Initializers } from './WorldConstants'; +import { + arenaInitializers, + initializers, + noPlanetTransferInitializers, + target4Initializers, +} from './WorldConstants'; export interface World { contract: DarkForest; @@ -85,6 +90,13 @@ export function noPlanetTransferFixture(): Promise { }); } +export function arenaWorldFixture(): Promise { + return initializeWorld({ + initializers: arenaInitializers, + whitelistEnabled: false, + }); +} + export async function initializeWorld({ initializers, whitelistEnabled, From d219096af665f64aeb49c4e318c5bcb4cb0096b9 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 12:16:00 -0400 Subject: [PATCH 07/17] feat: more manual spawn tests --- eth/test/DFArena.test.ts | 157 ++++++++++++++++++++++++++++++- eth/test/utils/WorldConstants.ts | 2 + 2 files changed, 157 insertions(+), 2 deletions(-) diff --git a/eth/test/DFArena.test.ts b/eth/test/DFArena.test.ts index 58859151..572c4e6d 100644 --- a/eth/test/DFArena.test.ts +++ b/eth/test/DFArena.test.ts @@ -2,9 +2,13 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { makeInitArgs, makeRevealArgs } from './utils/TestUtils'; import { arenaWorldFixture, World } from './utils/TestWorld'; -import { ADMIN_PLANET_CLOAKED } from './utils/WorldConstants'; +import { + ADMIN_PLANET_CLOAKED, + LVL2_PLANET_DEEP_SPACE, + VALID_INIT_PERLIN, +} from './utils/WorldConstants'; -describe('DarkForestArena', function () { +describe.only('DarkForestArena', function () { let world: World; beforeEach('load fixture', async function () { @@ -51,5 +55,154 @@ describe('DarkForestArena', function () { .to.emit(world.user1Core, 'PlayerInitialized') .withArgs(world.user1.address, ADMIN_PLANET_CLOAKED.id.toString()); }); + it('reverts if planet not initialized as a spawn planet', async function () { + await expect( + world.user1Core.initializePlayer(...makeInitArgs(ADMIN_PLANET_CLOAKED)) + ).to.be.revertedWith('Planet is not a spawn planet'); + }); + + it('reverts if spawn planet already initialized', async function () { + const perlin = VALID_INIT_PERLIN; + const level = 0; + const planetType = 0; // planet + await world.contract.createArenaPlanet({ + location: ADMIN_PLANET_CLOAKED.id, + x: 10, + y: 10, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: true, + blockedPlanetIds: [], + }); + + const toPlanetExtended = await world.contract.planets(ADMIN_PLANET_CLOAKED.id); + expect(toPlanetExtended.isInitialized).to.equal(true); + + await expect(world.user1Core.initializePlayer(...makeInitArgs(ADMIN_PLANET_CLOAKED))) + .to.emit(world.contract, 'PlayerInitialized') + .withArgs(world.user1.address, ADMIN_PLANET_CLOAKED.id.toString()); + + await expect( + world.user2Core.initializePlayer(...makeInitArgs(ADMIN_PLANET_CLOAKED)) + ).to.be.revertedWith('Planet is owned'); + }); + + it('allows player to spawn at admin planet that is initialized', async function () { + const perlin = VALID_INIT_PERLIN; + const level = 0; + const planetType = 0; // planet + await world.contract.createArenaPlanet({ + location: ADMIN_PLANET_CLOAKED.id, + x: 10, + y: 10, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: true, + blockedPlanetIds: [], + }); + + const toPlanetExtended = await world.contract.planets(ADMIN_PLANET_CLOAKED.id); + expect(toPlanetExtended.isInitialized).to.equal(true); + + await expect(world.user1Core.initializePlayer(...makeInitArgs(ADMIN_PLANET_CLOAKED))) + .to.emit(world.contract, 'PlayerInitialized') + .withArgs(world.user1.address, ADMIN_PLANET_CLOAKED.id.toString()); + }); + + it('gets false for a planet that is neither spawn nor target planet', async function () { + const perlin = 20; + const level = 5; + const planetType = 1; // asteroid field + const x = 10; + const y = 20; + await world.contract.createArenaPlanet({ + location: ADMIN_PLANET_CLOAKED.id, + x, + y, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: false, + blockedPlanetIds: [], + }); + + await world.contract.revealLocation(...makeRevealArgs(ADMIN_PLANET_CLOAKED, x, y)); + + const numSpawnPlanets = await world.contract.getNSpawnPlanets(); + expect(numSpawnPlanets).to.equal(0); + + const spawnPlanet = await world.contract.planets(ADMIN_PLANET_CLOAKED.id); + + expect(spawnPlanet.spawnPlanet).to.equal(false); + expect(spawnPlanet.targetPlanet).to.equal(false); + }); + + it('sets the planet to the proper values', async function () { + const perlin = 16; + const level = 2; + const planetType = 0; // planet + const x = 10; + const y = 20; + await world.contract.createArenaPlanet({ + location: LVL2_PLANET_DEEP_SPACE.id, + x, + y, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: true, + blockedPlanetIds: [], + }); + + await world.contract.revealLocation(...makeRevealArgs(LVL2_PLANET_DEEP_SPACE, x, y)); + + const numSpawnPlanets = await world.contract.getNSpawnPlanets(); + expect(numSpawnPlanets).to.equal(1); + + await world.user1Core.initializePlayer(...makeInitArgs(LVL2_PLANET_DEEP_SPACE)); + + const spawnPlanetInfo = await world.contract.planets(LVL2_PLANET_DEEP_SPACE.id); + const spawnPlanetArenaInfo = await world.contract.planets(LVL2_PLANET_DEEP_SPACE.id); + + const popCap = spawnPlanetInfo.populationCap.toNumber(); + + expect(spawnPlanetArenaInfo.spawnPlanet).to.be.equal(true); + expect(spawnPlanetInfo.isHomePlanet).to.be.equal(true); + expect(spawnPlanetInfo.owner).to.be.equal(world.user1.address); + expect(spawnPlanetInfo.population.toNumber()).to.be.approximately( + Math.floor(popCap * 0.99), + 10 + ); + }); + + it('reverts if target planet is made', async function () { + const perlin = VALID_INIT_PERLIN; + const level = 0; + const planetType = 0; // planet + await expect( + world.contract.createArenaPlanet({ + location: ADMIN_PLANET_CLOAKED.id, + x: 10, + y: 20, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: true, + isSpawnPlanet: false, + blockedPlanetIds: [], + }) + ).to.be.revertedWith('admin cannot create target planets'); + }); }); }); diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index 54114545..8abfe99b 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -131,6 +131,8 @@ export const arenaInitializers = decodeInitializers({ ...defaultInitializerValues, MANUAL_SPAWN: true, ADMIN_CAN_ADD_PLANETS: true, + INIT_PERLIN_MIN: 1, + INIT_PERLIN_MAX: 31, }); export const VALID_INIT_PERLIN = initializers.INIT_PERLIN_MIN; From 740b03de6b67c34425f675675761a13e350dc1a2 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 12:29:21 -0400 Subject: [PATCH 08/17] feat: bulk arena planet functionality --- eth/contracts/facets/DFArenaFacet.sol | 59 +++++++ eth/test/DFArena.test.ts | 245 +++++++++++++++++++++----- eth/test/utils/WorldConstants.ts | 1 + 3 files changed, 257 insertions(+), 48 deletions(-) diff --git a/eth/contracts/facets/DFArenaFacet.sol b/eth/contracts/facets/DFArenaFacet.sol index 3cf388b4..b676bbf0 100644 --- a/eth/contracts/facets/DFArenaFacet.sol +++ b/eth/contracts/facets/DFArenaFacet.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +// Contract imports +import {DFWhitelistFacet} from "../facets/DFWhitelistFacet.sol"; + // Library imports import {LibGameUtils} from "../libraries/LibGameUtils.sol"; import {LibPermissions} from "../libraries/LibPermissions.sol"; @@ -15,6 +18,24 @@ import "hardhat/console.sol"; contract DFArenaFacet is WithStorage { event AdminPlanetCreated(uint256 loc); + event LocationRevealed(address revealer, uint256 loc, uint256 x, uint256 y); + + modifier onlyWhitelisted() { + require( + DFWhitelistFacet(address(this)).isWhitelisted(msg.sender) || + msg.sender == LibPermissions.contractOwner(), + "Player is not whitelisted" + ); + _; + } + + modifier onlyAdmin() { + require( + msg.sender == LibPermissions.contractOwner(), + "Only Admin address can perform this action." + ); + _; + } function createArenaPlanet(AdminCreateRevealPlanetArgs memory args) public { require(gameConstants().ADMIN_CAN_ADD_PLANETS, "admin can no longer add planets"); @@ -58,6 +79,44 @@ contract DFArenaFacet is WithStorage { emit AdminPlanetCreated(args.location); } + function arenaRevealLocation(AdminCreateRevealPlanetArgs memory args) + public + onlyWhitelisted + returns (uint256) + { + if (!gs().planets[args.location].isInitialized) { + LibPlanet.initializePlanetWithDefaults(args.location, args.perlin, false); + } + + LibPlanet.revealLocation({ + location: args.location, + perlin: args.perlin, + x: args.x, + y: args.y, + /* if this is true, check timestamp for reveal. We want false for admin / init planets */ + checkTimestamp: !(msg.sender == LibPermissions.contractOwner()) // || isInitPlanet(args)) + }); + emit LocationRevealed(msg.sender, args.location, args.x, args.y); + } + + function bulkCreatePlanet(AdminCreateRevealPlanetArgs[] memory planets) public onlyAdmin { + for (uint256 i = 0; i < planets.length; i++) { + createArenaPlanet(planets[i]); + } + } + + /* should be only admin or init planet*/ + function createAndReveal(AdminCreateRevealPlanetArgs memory createPlanetArgs) public { + createArenaPlanet(createPlanetArgs); + arenaRevealLocation(createPlanetArgs); + } + + function bulkCreateAndReveal(AdminCreateRevealPlanetArgs[] calldata createArgsList) public { + for (uint256 i = 0; i < createArgsList.length; i++) { + createAndReveal(createArgsList[i]); + } + } + /** * Getters */ diff --git a/eth/test/DFArena.test.ts b/eth/test/DFArena.test.ts index 572c4e6d..6cc99ea5 100644 --- a/eth/test/DFArena.test.ts +++ b/eth/test/DFArena.test.ts @@ -3,7 +3,9 @@ import { expect } from 'chai'; import { makeInitArgs, makeRevealArgs } from './utils/TestUtils'; import { arenaWorldFixture, World } from './utils/TestWorld'; import { + ADMIN_PLANET, ADMIN_PLANET_CLOAKED, + LVL1_PLANET_SPACE, LVL2_PLANET_DEEP_SPACE, VALID_INIT_PERLIN, } from './utils/WorldConstants'; @@ -15,10 +17,201 @@ describe.only('DarkForestArena', function () { world = await loadFixture(arenaWorldFixture); }); - describe('basic functions', function () { + describe('Create Planets', function () { it('has arena constants', async function () { expect((await world.contract.getGameConstants()).MANUAL_SPAWN).to.equal(true); expect((await world.contract.getGameConstants()).ADMIN_CAN_ADD_PLANETS).to.equal(true); + expect((await world.contract.getGameConstants()).TARGETS_REQUIRED_FOR_VICTORY).to.equal(1); + }); + it('allows admin to create a spawn planet', async function () { + const perlin = 20; + const level = 5; + const planetType = 1; // asteroid field + const x = 10; + const y = 20; + await world.contract.createArenaPlanet({ + location: ADMIN_PLANET_CLOAKED.id, + x, + y, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: true, + blockedPlanetIds: [], + }); + + await world.contract.revealLocation(...makeRevealArgs(ADMIN_PLANET_CLOAKED, x, y)); + + const numSpawnPlanets = await world.contract.getNSpawnPlanets(); + expect(numSpawnPlanets).to.equal(1); + + const spawnPlanet = await world.contract.spawnPlanetIds(0); + + expect(spawnPlanet).to.equal(ADMIN_PLANET_CLOAKED.id); + }); + + it('allows admin to create target planet', async function () { + const perlin = 20; + const level = 5; + const planetType = 1; // asteroid field + const x = 10; + const y = 20; + await world.contract.createArenaPlanet({ + location: ADMIN_PLANET_CLOAKED.id, + x, + y, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: true, + isSpawnPlanet: false, + blockedPlanetIds: [], + }); + + await world.contract.revealLocation(...makeRevealArgs(ADMIN_PLANET_CLOAKED, x, y)); + + const numTargetPlanets = await world.contract.getNTargetPlanets(); + expect(numTargetPlanets).to.equal(1); + + const targetPlanetId = await world.contract.targetPlanetIds(0); + expect(targetPlanetId).to.equal(ADMIN_PLANET_CLOAKED.id); + + const targetPlanet = await world.contract.planets(ADMIN_PLANET_CLOAKED.id); + expect(targetPlanet.spawnPlanet).to.equal(false); + expect(targetPlanet.targetPlanet).to.equal(true); + }); + + it('allows admin to bulk create planets', async function () { + const perlin = 20; + const level = 5; + const planetType = 1; // asteroid field + const x = 10; + const y = 20; + const planets = [ + { + location: ADMIN_PLANET.id, + x, + y, + perlin, + level, + planetType, + requireValidLocationId: true, + isTargetPlanet: false, + isSpawnPlanet: false, + blockedPlanetIds: [], + }, + { + location: ADMIN_PLANET_CLOAKED.id, + x, + y, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: false, + blockedPlanetIds: [], + }, + { + location: LVL1_PLANET_SPACE.id, + x, + y, + perlin, + level, + planetType, + requireValidLocationId: true, + isTargetPlanet: false, + isSpawnPlanet: false, + blockedPlanetIds: [], + }, + ]; + await world.contract.bulkCreatePlanet(planets); + + await world.contract.revealLocation(...makeRevealArgs(ADMIN_PLANET, x, y)); + await world.contract.revealLocation(...makeRevealArgs(LVL1_PLANET_SPACE, 50, 100)); + + const revealedCoords = await world.contract.revealedCoords(ADMIN_PLANET.id); + expect(revealedCoords.x.toNumber()).to.equal(x); + expect(revealedCoords.y.toNumber()).to.equal(y); + + const revealedCoords1 = await world.contract.revealedCoords(LVL1_PLANET_SPACE.id); + expect(revealedCoords1.x.toNumber()).to.equal(50); + expect(revealedCoords1.y.toNumber()).to.equal(100); + + expect((await world.contract.getNRevealedPlanets()).toNumber()).to.equal(2); + expect(await world.contract.revealedPlanetIds(0)).to.be.equal(ADMIN_PLANET.id); + expect(await world.contract.revealedPlanetIds(1)).to.be.equal(LVL1_PLANET_SPACE.id); + }); + + it('creates and reveals one planet', async function () { + const perlin = 20; + const level = 5; + const planetType = 1; // asteroid field + const x = 10; + const y = 30; + const createReveal = await world.contract.createAndReveal({ + location: ADMIN_PLANET_CLOAKED.id, + x, + y, + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: true, + blockedPlanetIds: [], + }); + + const createRevealReceipt = await createReveal.wait(); + + console.log(`createAndReveal used ${createRevealReceipt.gasUsed} gas`); + + const testPlanet = await world.contract.getRevealedCoords(ADMIN_PLANET_CLOAKED.id); + + expect(testPlanet.x).to.equal(x); + expect(testPlanet.y).to.equal(y); + }); + + it('bulk creates and reveals multiple planets', async function () { + const perlin = 20; + const level = 5; + const planetType = 1; // asteroid field + const planets = [ADMIN_PLANET, ADMIN_PLANET_CLOAKED, LVL1_PLANET_SPACE]; + + const planetArgList: any = []; + + planets.map((p) => { + const planetArgs = { + location: p.id, + x: Math.floor(Math.random() * 100), + y: Math.floor(Math.random() * 100), + perlin, + level, + planetType, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: true, + blockedPlanetIds: [], + }; + + planetArgList.push(planetArgs); + }); + + const tx = await world.contract.bulkCreateAndReveal(planetArgList); + const rct = await tx.wait(); + console.log(`created and revealed ${planets.length} planets with ${rct.gasUsed} gas`); + + const data = await world.contract.bulkGetPlanetsDataByIds(planets.map((p) => p.id)); + + for (let i = 0; i < planets.length; i++) { + expect(data[i].revealedCoords.locationId).to.equal(planets[i].id); + expect(data[i].revealedCoords.x).to.equal(planetArgList[i].x); + expect(data[i].revealedCoords.y).to.equal(planetArgList[i].y); + expect(data[i].planet.perlin).to.equal(planetArgList[i].perlin); + } }); }); @@ -89,32 +282,6 @@ describe.only('DarkForestArena', function () { world.user2Core.initializePlayer(...makeInitArgs(ADMIN_PLANET_CLOAKED)) ).to.be.revertedWith('Planet is owned'); }); - - it('allows player to spawn at admin planet that is initialized', async function () { - const perlin = VALID_INIT_PERLIN; - const level = 0; - const planetType = 0; // planet - await world.contract.createArenaPlanet({ - location: ADMIN_PLANET_CLOAKED.id, - x: 10, - y: 10, - perlin, - level, - planetType, - requireValidLocationId: false, - isTargetPlanet: false, - isSpawnPlanet: true, - blockedPlanetIds: [], - }); - - const toPlanetExtended = await world.contract.planets(ADMIN_PLANET_CLOAKED.id); - expect(toPlanetExtended.isInitialized).to.equal(true); - - await expect(world.user1Core.initializePlayer(...makeInitArgs(ADMIN_PLANET_CLOAKED))) - .to.emit(world.contract, 'PlayerInitialized') - .withArgs(world.user1.address, ADMIN_PLANET_CLOAKED.id.toString()); - }); - it('gets false for a planet that is neither spawn nor target planet', async function () { const perlin = 20; const level = 5; @@ -144,7 +311,6 @@ describe.only('DarkForestArena', function () { expect(spawnPlanet.spawnPlanet).to.equal(false); expect(spawnPlanet.targetPlanet).to.equal(false); }); - it('sets the planet to the proper values', async function () { const perlin = 16; const level = 2; @@ -184,25 +350,8 @@ describe.only('DarkForestArena', function () { 10 ); }); - - it('reverts if target planet is made', async function () { - const perlin = VALID_INIT_PERLIN; - const level = 0; - const planetType = 0; // planet - await expect( - world.contract.createArenaPlanet({ - location: ADMIN_PLANET_CLOAKED.id, - x: 10, - y: 20, - perlin, - level, - planetType, - requireValidLocationId: false, - isTargetPlanet: true, - isSpawnPlanet: false, - blockedPlanetIds: [], - }) - ).to.be.revertedWith('admin cannot create target planets'); - }); + }); + describe('Target Planets', function () { + it('creates a target planet', async function () {}); }); }); diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index 8abfe99b..6a4c14c2 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -130,6 +130,7 @@ export const target4Initializers = decodeInitializers({ export const arenaInitializers = decodeInitializers({ ...defaultInitializerValues, MANUAL_SPAWN: true, + TARGETS_REQUIRED_FOR_VICTORY: 1, ADMIN_CAN_ADD_PLANETS: true, INIT_PERLIN_MIN: 1, INIT_PERLIN_MAX: 31, From a52b8c0d1a62067f8c16f34d99b538afbb0d60b6 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 12:58:48 -0400 Subject: [PATCH 09/17] feat: target planet and claim victory works --- eth/contracts/facets/DFArenaFacet.sol | 67 ++++++++++++++++++- eth/contracts/facets/DFMoveFacet.sol | 3 + eth/contracts/libraries/LibStorage.sol | 4 ++ eth/test/DFArena.test.ts | 89 +++++++++++++++++++++++--- eth/test/utils/TestWorld.ts | 8 +++ eth/test/utils/WorldConstants.ts | 10 ++- 6 files changed, 170 insertions(+), 11 deletions(-) diff --git a/eth/contracts/facets/DFArenaFacet.sol b/eth/contracts/facets/DFArenaFacet.sol index b676bbf0..ccfc79a2 100644 --- a/eth/contracts/facets/DFArenaFacet.sol +++ b/eth/contracts/facets/DFArenaFacet.sol @@ -13,11 +13,12 @@ import {LibPlanet} from "../libraries/LibPlanet.sol"; import {WithStorage} from "../libraries/LibStorage.sol"; // Type imports -import {AdminCreateRevealPlanetArgs, DFPInitPlanetArgs, SpaceType} from "../DFTypes.sol"; +import {AdminCreateRevealPlanetArgs, DFPInitPlanetArgs, Planet, SpaceType} from "../DFTypes.sol"; import "hardhat/console.sol"; contract DFArenaFacet is WithStorage { event AdminPlanetCreated(uint256 loc); + event Gameover(address[] winners); event LocationRevealed(address revealer, uint256 loc, uint256 x, uint256 y); modifier onlyWhitelisted() { @@ -37,6 +38,11 @@ contract DFArenaFacet is WithStorage { _; } + modifier notPaused() { + require(!gs().paused, "Game is paused"); + _; + } + function createArenaPlanet(AdminCreateRevealPlanetArgs memory args) public { require(gameConstants().ADMIN_CAN_ADD_PLANETS, "admin can no longer add planets"); // TODO: check init planet hash @@ -117,6 +123,55 @@ contract DFArenaFacet is WithStorage { } } + function _checkGameOver() public returns (bool) { + require(gameConstants().TARGETS_REQUIRED_FOR_VICTORY > 0, "target planets are disabled"); + + uint256[] memory targetPlanets = gs().targetPlanetIds; + uint256 captured = 0; + + for (uint256 i = 0; i < targetPlanets.length; i++) { + uint256 locationId = targetPlanets[i]; + LibPlanet.refreshPlanet(locationId); + Planet memory planet = gs().planets[locationId]; + + bool myPlanet = planet.owner == msg.sender; + + // if (gameConstants().TEAMS_ENABLED) { + // myPlanet = + // arenaStorage().arenaPlayerInfo[planet.owner].team == + // arenaStorage().arenaPlayerInfo[msg.sender].team; + // } + + uint256 playerHomePlanet = gs().players[msg.sender].homePlanetId; + // bool blocked = gs().blocklist[locationId][playerHomePlanet]; + // Blocklist and teams are not in right now + if ( + !myPlanet || + (planet.population * 100) / planet.populationCap < + gameConstants().CLAIM_VICTORY_ENERGY_PERCENT + ) { + continue; + } + + captured += 1; + if (captured >= gameConstants().TARGETS_REQUIRED_FOR_VICTORY) return true; + } + + return false; + } + + function claimVictory() public onlyWhitelisted notPaused { + require(!gs().gameOver, "cannot claim victory when game is over"); + + require(_checkGameOver(), "victory condition not met"); + + gs().winners.push(msg.sender); + gs().gameOver = true; + gs().endTime = block.timestamp; + gs().paused = true; + emit Gameover(gs().winners); + } + /** * Getters */ @@ -135,4 +190,14 @@ contract DFArenaFacet is WithStorage { function spawnPlanetIds(uint256 idx) public view returns (uint256) { return gs().spawnPlanetIds[idx]; } + + function getRoundDuration() public view returns (uint256) { + if (gs().startTime == 0) { + return 0; + } + if (gs().endTime == 0) { + return block.timestamp - gs().startTime; + } + return gs().endTime - gs().startTime; + } } diff --git a/eth/contracts/facets/DFMoveFacet.sol b/eth/contracts/facets/DFMoveFacet.sol index ea0fae97..b9671baa 100644 --- a/eth/contracts/facets/DFMoveFacet.sol +++ b/eth/contracts/facets/DFMoveFacet.sol @@ -44,6 +44,9 @@ contract DFMoveFacet is WithStorage { uint256 movedArtifactId, uint256 isAbandoning ) public notPaused returns (uint256) { + // Start Game if not started. + if (gs().startTime == 0) gs().startTime = block.timestamp; + LibGameUtils.revertIfBadSnarkPerlinFlags( [_input[5], _input[6], _input[7], _input[8], _input[9]], false diff --git a/eth/contracts/libraries/LibStorage.sol b/eth/contracts/libraries/LibStorage.sol index fd66e9b2..fcca278e 100644 --- a/eth/contracts/libraries/LibStorage.sol +++ b/eth/contracts/libraries/LibStorage.sol @@ -47,6 +47,10 @@ struct GameStorage { uint256 nextChangeBlock; uint256[] targetPlanetIds; uint256[] spawnPlanetIds; + address[] winners; + uint256 startTime; + uint256 endTime; + bool gameOver; } // Game config diff --git a/eth/test/DFArena.test.ts b/eth/test/DFArena.test.ts index 6cc99ea5..a9162947 100644 --- a/eth/test/DFArena.test.ts +++ b/eth/test/DFArena.test.ts @@ -1,23 +1,29 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; -import { makeInitArgs, makeRevealArgs } from './utils/TestUtils'; -import { arenaWorldFixture, World } from './utils/TestWorld'; +import { + conquerUnownedPlanet, + increaseBlockchainTime, + makeInitArgs, + makeRevealArgs, +} from './utils/TestUtils'; +import { arenaWorldFixture, targetPlanetWorldFixture, World } from './utils/TestWorld'; import { ADMIN_PLANET, ADMIN_PLANET_CLOAKED, + LVL0_PLANET_DEEP_SPACE, LVL1_PLANET_SPACE, LVL2_PLANET_DEEP_SPACE, + SPAWN_PLANET_1, VALID_INIT_PERLIN, } from './utils/WorldConstants'; describe.only('DarkForestArena', function () { let world: World; - beforeEach('load fixture', async function () { - world = await loadFixture(arenaWorldFixture); - }); - describe('Create Planets', function () { + beforeEach('load fixture', async function () { + world = await loadFixture(arenaWorldFixture); + }); it('has arena constants', async function () { expect((await world.contract.getGameConstants()).MANUAL_SPAWN).to.equal(true); expect((await world.contract.getGameConstants()).ADMIN_CAN_ADD_PLANETS).to.equal(true); @@ -214,8 +220,10 @@ describe.only('DarkForestArena', function () { } }); }); - describe('Manual Spawn', function () { + beforeEach('load fixture', async function () { + world = await loadFixture(arenaWorldFixture); + }); it('allows admin to create a spawn planet and player to spawn', async function () { const perlin = 20; const level = 5; @@ -351,7 +359,70 @@ describe.only('DarkForestArena', function () { ); }); }); - describe('Target Planets', function () { - it('creates a target planet', async function () {}); + describe.only('Target Planets + Claim Victory', function () { + beforeEach('load fixture', async function () { + world = await loadFixture(targetPlanetWorldFixture); + const perlin = 20; + const level = 0; + const planetType = 1; // asteroid field + await world.contract.createArenaPlanet({ + location: target.id, + x: 10, + y: 10, + perlin, + level, + planetType, + requireValidLocationId: true, + isTargetPlanet: true, + isSpawnPlanet: false, + blockedPlanetIds: [], + }); + + const initArgs = makeInitArgs(SPAWN_PLANET_1); + await world.user1Core.initializePlayer(...initArgs); + }); + + const target = LVL0_PLANET_DEEP_SPACE; + + it('confirms target planet', async function () { + const numTargetPlanets = await world.user1Core.getNTargetPlanets(); + expect(numTargetPlanets).to.equal(1); + const targetPlanet = await world.user1Core.targetPlanetIds(0); + expect(targetPlanet).to.equal(target.id); + }); + it('captures target and claims victory', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, target); + await increaseBlockchainTime(600); + + await expect(world.user1Core.claimVictory()) + .to.emit(world.contract, 'Gameover') + .withArgs([world.user1.address]); + + expect((await world.contract.getRoundDuration()).toNumber()).to.be.greaterThan(600); + }); + + it('claim victory fails if target below energy threshold', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, target); + + await world.user1Core.refreshPlanet(target.id); + const planet = await world.contract.planets(target.id); + const popCap = planet.populationCap.toNumber(); + const pop = planet.population.toNumber(); + console.log( + `Planet is ${(pop / popCap) * 100}% full, but needs ${ + (await world.contract.getGameConstants()).CLAIM_VICTORY_ENERGY_PERCENT + }%` + ); + + await expect(world.user1Core.claimVictory()).to.be.revertedWith('victory condition not met'); + }); + + it('get round duration 0 if round not over', async function () { + await conquerUnownedPlanet(world, world.user1Core, SPAWN_PLANET_1, target); + + await increaseBlockchainTime(51); + const roundDuration = await world.user1Core.getRoundDuration(); + expect(roundDuration.toNumber()).to.be.greaterThan(50); + }); }); }); diff --git a/eth/test/utils/TestWorld.ts b/eth/test/utils/TestWorld.ts index 95c82f7c..2b002a7a 100644 --- a/eth/test/utils/TestWorld.ts +++ b/eth/test/utils/TestWorld.ts @@ -9,6 +9,7 @@ import { initializers, noPlanetTransferInitializers, target4Initializers, + targetPlanetInitializers, } from './WorldConstants'; export interface World { @@ -97,6 +98,13 @@ export function arenaWorldFixture(): Promise { }); } +export function targetPlanetWorldFixture(): Promise { + return initializeWorld({ + initializers: targetPlanetInitializers, + whitelistEnabled: false, + }); +} + export async function initializeWorld({ initializers, whitelistEnabled, diff --git a/eth/test/utils/WorldConstants.ts b/eth/test/utils/WorldConstants.ts index 6a4c14c2..8c2c59af 100644 --- a/eth/test/utils/WorldConstants.ts +++ b/eth/test/utils/WorldConstants.ts @@ -126,7 +126,6 @@ export const target4Initializers = decodeInitializers({ WORLD_RADIUS_MIN: 1, }); -// This builds a fake HRE-like object used to initialize the test contracts export const arenaInitializers = decodeInitializers({ ...defaultInitializerValues, MANUAL_SPAWN: true, @@ -136,6 +135,15 @@ export const arenaInitializers = decodeInitializers({ INIT_PERLIN_MAX: 31, }); +export const targetPlanetInitializers = decodeInitializers({ + ...defaultInitializerValues, + MANUAL_SPAWN: false, + TARGETS_REQUIRED_FOR_VICTORY: 1, + ADMIN_CAN_ADD_PLANETS: true, + INIT_PERLIN_MIN: 1, + INIT_PERLIN_MAX: 31, +}); + export const VALID_INIT_PERLIN = initializers.INIT_PERLIN_MIN; export const NEBULA_PERLIN = initializers.PERLIN_THRESHOLD_1 - 1; export const SPACE_PERLIN = initializers.PERLIN_THRESHOLD_1; From 39cb962dcf5fbb80139e9d4e490421fb31ec0d75 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 13:11:55 -0400 Subject: [PATCH 10/17] test: fix client and remove .only --- client/src/Backend/GameLogic/ContractsAPI.ts | 6 ++++++ client/src/Frontend/Pages/CreateLobby.tsx | 3 +++ client/src/Frontend/Panes/Lobbies/Reducer.ts | 9 +++++++++ client/src/_types/darkforest/api/ContractsAPITypes.ts | 3 +++ eth/test/DFArena.test.ts | 4 ++-- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/client/src/Backend/GameLogic/ContractsAPI.ts b/client/src/Backend/GameLogic/ContractsAPI.ts index 033b1553..7e3b0796 100644 --- a/client/src/Backend/GameLogic/ContractsAPI.ts +++ b/client/src/Backend/GameLogic/ContractsAPI.ts @@ -455,6 +455,9 @@ export class ContractsAPI extends EventEmitter { CAPTURE_ZONES_PER_5000_WORLD_RADIUS, SPACESHIPS, ROUND_END_REWARDS_BY_RANK, + MANUAL_SPAWN, + TARGETS_REQUIRED_FOR_VICTORY, + CLAIM_VICTORY_ENERGY_PERCENT, } = await this.makeCall(this.contract.getGameConstants); const TOKEN_MINT_END_SECONDS = ( @@ -638,6 +641,9 @@ export class ContractsAPI extends EventEmitter { ROUND_END_REWARDS_BY_RANK[62].toNumber(), ROUND_END_REWARDS_BY_RANK[63].toNumber(), ], + MANUAL_SPAWN, + TARGETS_REQUIRED_FOR_VICTORY: TARGETS_REQUIRED_FOR_VICTORY.toNumber(), + CLAIM_VICTORY_ENERGY_PERCENT: CLAIM_VICTORY_ENERGY_PERCENT.toNumber(), }; return constants; diff --git a/client/src/Frontend/Pages/CreateLobby.tsx b/client/src/Frontend/Pages/CreateLobby.tsx index 6b1e1d9f..7e82b278 100644 --- a/client/src/Frontend/Pages/CreateLobby.tsx +++ b/client/src/Frontend/Pages/CreateLobby.tsx @@ -150,6 +150,9 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> CAPTURE_ZONES_PER_5000_WORLD_RADIUS: config.CAPTURE_ZONES_PER_5000_WORLD_RADIUS, SPACESHIPS: config.SPACESHIPS, ROUND_END_REWARDS_BY_RANK: config.ROUND_END_REWARDS_BY_RANK, + MANUAL_SPAWN: config.MANUAL_SPAWN, + TARGETS_REQUIRED_FOR_VICTORY: config.TARGETS_REQUIRED_FOR_VICTORY, + CLAIM_VICTORY_ENERGY_PERCENT: config.CLAIM_VICTORY_ENERGY_PERCENT, }); }) .catch((e) => { diff --git a/client/src/Frontend/Panes/Lobbies/Reducer.ts b/client/src/Frontend/Panes/Lobbies/Reducer.ts index 88bb339a..7fd7f3ad 100644 --- a/client/src/Frontend/Panes/Lobbies/Reducer.ts +++ b/client/src/Frontend/Panes/Lobbies/Reducer.ts @@ -773,6 +773,15 @@ export function lobbyConfigInit(startingConfig: LobbyInitializers) { }; break; } + case 'MANUAL_SPAWN': { + break; + } + case 'TARGETS_REQUIRED_FOR_VICTORY': { + break; + } + case 'CLAIM_VICTORY_ENERGY_PERCENT': { + break; + } default: { // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking const _exhaustive: never = key; diff --git a/client/src/_types/darkforest/api/ContractsAPITypes.ts b/client/src/_types/darkforest/api/ContractsAPITypes.ts index 603b6801..8cbb52d1 100644 --- a/client/src/_types/darkforest/api/ContractsAPITypes.ts +++ b/client/src/_types/darkforest/api/ContractsAPITypes.ts @@ -364,6 +364,9 @@ export interface ContractConstants { number, number ]; + MANUAL_SPAWN: boolean; + TARGETS_REQUIRED_FOR_VICTORY: number; + CLAIM_VICTORY_ENERGY_PERCENT: number; } export type ClientMockchainData = diff --git a/eth/test/DFArena.test.ts b/eth/test/DFArena.test.ts index a9162947..2c2dee30 100644 --- a/eth/test/DFArena.test.ts +++ b/eth/test/DFArena.test.ts @@ -17,7 +17,7 @@ import { VALID_INIT_PERLIN, } from './utils/WorldConstants'; -describe.only('DarkForestArena', function () { +describe('DarkForestArena', function () { let world: World; describe('Create Planets', function () { @@ -359,7 +359,7 @@ describe.only('DarkForestArena', function () { ); }); }); - describe.only('Target Planets + Claim Victory', function () { + describe('Target Planets + Claim Victory', function () { beforeEach('load fixture', async function () { world = await loadFixture(targetPlanetWorldFixture); const perlin = 20; From 740d306bc9db2c9a29b3d32ad46cd209877f98df Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 13:25:10 -0400 Subject: [PATCH 11/17] client: use init start method --- client/src/Frontend/Pages/CreateLobby.tsx | 63 ++++++----------------- 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/client/src/Frontend/Pages/CreateLobby.tsx b/client/src/Frontend/Pages/CreateLobby.tsx index 7e82b278..aa37d650 100644 --- a/client/src/Frontend/Pages/CreateLobby.tsx +++ b/client/src/Frontend/Pages/CreateLobby.tsx @@ -10,6 +10,7 @@ import _ from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { ContractsAPI, makeContractsAPI } from '../../Backend/GameLogic/ContractsAPI'; +import { loadDiamondContract } from '../../Backend/Network/Blockchain'; import { InitRenderState, Wrapper } from '../Components/GameLandingPageComponents'; import { ConfigurationPane } from '../Panes/Lobbies/ConfigurationPane'; import { Minimap } from '../Panes/Lobbies/MinimapPane'; @@ -94,40 +95,13 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> .getConstants() .then((config) => { setStartingConfig({ + ...config, // Explicitly defaulting this to false WHITELIST_ENABLED: false, // TODO: Figure out if we should expose this from contract START_PAUSED: false, - ADMIN_CAN_ADD_PLANETS: config.ADMIN_CAN_ADD_PLANETS, - WORLD_RADIUS_LOCKED: config.WORLD_RADIUS_LOCKED, - WORLD_RADIUS_MIN: config.WORLD_RADIUS_MIN, - DISABLE_ZK_CHECKS: config.DISABLE_ZK_CHECKS, - PLANETHASH_KEY: config.PLANETHASH_KEY, - SPACETYPE_KEY: config.SPACETYPE_KEY, - BIOMEBASE_KEY: config.BIOMEBASE_KEY, - PERLIN_MIRROR_X: config.PERLIN_MIRROR_X, - PERLIN_MIRROR_Y: config.PERLIN_MIRROR_Y, - PERLIN_LENGTH_SCALE: config.PERLIN_LENGTH_SCALE, - MAX_NATURAL_PLANET_LEVEL: config.MAX_NATURAL_PLANET_LEVEL, - TIME_FACTOR_HUNDREDTHS: config.TIME_FACTOR_HUNDREDTHS, - PERLIN_THRESHOLD_1: config.PERLIN_THRESHOLD_1, - PERLIN_THRESHOLD_2: config.PERLIN_THRESHOLD_2, - PERLIN_THRESHOLD_3: config.PERLIN_THRESHOLD_3, - INIT_PERLIN_MIN: config.INIT_PERLIN_MIN, - INIT_PERLIN_MAX: config.INIT_PERLIN_MAX, - SPAWN_RIM_AREA: config.SPAWN_RIM_AREA, - BIOME_THRESHOLD_1: config.BIOME_THRESHOLD_1, - BIOME_THRESHOLD_2: config.BIOME_THRESHOLD_2, - PLANET_LEVEL_THRESHOLDS: config.PLANET_LEVEL_THRESHOLDS, - PLANET_RARITY: config.PLANET_RARITY, - LOCATION_REVEAL_COOLDOWN: config.LOCATION_REVEAL_COOLDOWN, - // TODO: Need to think through this implementation a bit more, even if only toggling planet types - PLANET_TYPE_WEIGHTS: config.PLANET_TYPE_WEIGHTS, - // TODO: Rename in one of the places // TODO: Implement... Needs a datetime input component (WIP) TOKEN_MINT_END_TIMESTAMP: 1948939200, // new Date("2031-10-05T04:00:00.000Z").getTime() / 1000, - PHOTOID_ACTIVATION_DELAY: config.PHOTOID_ACTIVATION_DELAY, - SILVER_SCORE_VALUE: config.SILVER_SCORE_VALUE, ARTIFACT_POINT_VALUES: [ config.ARTIFACT_POINT_VALUES[ArtifactRarity.Unknown], config.ARTIFACT_POINT_VALUES[ArtifactRarity.Common], @@ -136,23 +110,6 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> config.ARTIFACT_POINT_VALUES[ArtifactRarity.Legendary], config.ARTIFACT_POINT_VALUES[ArtifactRarity.Mythic], ], - PLANET_TRANSFER_ENABLED: config.PLANET_TRANSFER_ENABLED, - SPACE_JUNK_ENABLED: config.SPACE_JUNK_ENABLED, - SPACE_JUNK_LIMIT: config.SPACE_JUNK_LIMIT, - PLANET_LEVEL_JUNK: config.PLANET_LEVEL_JUNK, - ABANDON_SPEED_CHANGE_PERCENT: config.ABANDON_SPEED_CHANGE_PERCENT, - ABANDON_RANGE_CHANGE_PERCENT: config.ABANDON_RANGE_CHANGE_PERCENT, - CAPTURE_ZONES_ENABLED: config.CAPTURE_ZONES_ENABLED, - CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL: config.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL, - CAPTURE_ZONE_PLANET_LEVEL_SCORE: config.CAPTURE_ZONE_PLANET_LEVEL_SCORE, - CAPTURE_ZONE_RADIUS: config.CAPTURE_ZONE_RADIUS, - CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED: config.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED, - CAPTURE_ZONES_PER_5000_WORLD_RADIUS: config.CAPTURE_ZONES_PER_5000_WORLD_RADIUS, - SPACESHIPS: config.SPACESHIPS, - ROUND_END_REWARDS_BY_RANK: config.ROUND_END_REWARDS_BY_RANK, - MANUAL_SPAWN: config.MANUAL_SPAWN, - TARGETS_REQUIRED_FOR_VICTORY: config.TARGETS_REQUIRED_FOR_VICTORY, - CLAIM_VICTORY_ENERGY_PERCENT: config.CLAIM_VICTORY_ENERGY_PERCENT, }); }) .catch((e) => { @@ -176,9 +133,12 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> const initInterface = Contract.getInterface(InitABI); const initAddress = INIT_ADDRESS; const initFunctionCall = initInterface.encodeFunctionData('init', [ - initializers.WHITELIST_ENABLED, - artifactBaseURI, initializers, + { + allowListEnabled: initializers.WHITELIST_ENABLED, + baseURI: artifactBaseURI, + allowedAddresses: [], + }, ]); const txIntent: UnconfirmedCreateLobby = { methodName: 'createLobby', @@ -198,6 +158,15 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> }); const rct = await tx.confirmedPromise; const { lobby } = getLobbyCreatedEvent(rct, contract.contract); + // Call Start + const newLobby = await contract.ethConnection.loadContract( + lobby, + loadDiamondContract + ); + const startTx = await newLobby.start(); + console.log(`start submitted`); + const startRct = await startTx.wait(); + console.log(`start confirmed with ${startRct.gasUsed} gas`); setLobbyAddress(lobby); } From bbc3ab76468e686e13db5ac0a5670c2a269f5569 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 15:26:34 -0400 Subject: [PATCH 12/17] temp: create spawn and target planets via CreateLobby.tsx --- client/src/Backend/GameLogic/ContractsAPI.ts | 21 ++++++ client/src/Backend/GameLogic/GameManager.ts | 72 +++++++++++++++++++- client/src/Frontend/Pages/CreateLobby.tsx | 51 +++++++++++--- eth/contracts/facets/DFArenaFacet.sol | 12 ++++ eth/contracts/facets/DFCoreFacet.sol | 1 - packages/settings/src/planets.ts | 18 +++++ 6 files changed, 164 insertions(+), 11 deletions(-) diff --git a/client/src/Backend/GameLogic/ContractsAPI.ts b/client/src/Backend/GameLogic/ContractsAPI.ts index 7e3b0796..62e538e6 100644 --- a/client/src/Backend/GameLogic/ContractsAPI.ts +++ b/client/src/Backend/GameLogic/ContractsAPI.ts @@ -854,6 +854,27 @@ export class ContractsAPI extends EventEmitter { return spaceships; } + public async getSpawnPlanetIds( + startingAt: number, + onProgress?: (fractionCompleted: number) => void + ): Promise { + const nPlanets: number = ( + await this.makeCall(this.contract.getNSpawnPlanets) + ).toNumber(); + + const planetIds = await aggregateBulkGetter( + nPlanets - startingAt, + 1000, + async (start, end) => + await this.makeCall(this.contract.bulkGetSpawnPlanetIds, [ + start + startingAt, + end + startingAt, + ]), + onProgress + ); + return planetIds.map(locationIdFromEthersBN); + } + public async getUpgradeForArtifact(artifactId: ArtifactId): Promise { const rawUpgrade = await this.makeCall(this.contract.getUpgradeForArtifact, [ artifactIdToDecStr(artifactId), diff --git a/client/src/Backend/GameLogic/GameManager.ts b/client/src/Backend/GameLogic/GameManager.ts index 33491c4f..0a6deffe 100644 --- a/client/src/Backend/GameLogic/GameManager.ts +++ b/client/src/Backend/GameLogic/GameManager.ts @@ -56,6 +56,7 @@ import { PlanetLevel, PlanetMessageType, PlanetType, + PlanetTypeNames, Player, QueuedArrival, Radii, @@ -92,7 +93,7 @@ import { } from '@dfdao/types'; import bigInt, { BigInteger } from 'big-integer'; import delay from 'delay'; -import { BigNumber, Contract, ContractInterface, providers } from 'ethers'; +import { BigNumber, constants, Contract, ContractInterface, providers } from 'ethers'; import { EventEmitter } from 'events'; import NotificationManager from '../../Frontend/Game/NotificationManager'; import { MIN_CHUNK_SIZE } from '../../Frontend/Utils/constants'; @@ -1864,7 +1865,74 @@ class GameManager extends EventEmitter { throw new Error('game has ended'); } - const planet = await this.findRandomHomePlanet(); + let planet: LocatablePlanet; + if (this.contractConstants.MANUAL_SPAWN) { + this.terminal.current?.println(``); + this.terminal.current?.println(`Retrieving available manual planets`); + this.terminal.current?.println(``); + + const spawnPlanets = await this.contractsAPI.getSpawnPlanetIds(0); + console.log(`spawnPlanets`, spawnPlanets); + // console.log(`all manually created spawn planets: ${spawnPlanets}`); + const potentialHomeIds = spawnPlanets.filter((planetId) => { + const planet = this.getGameObjects().getPlanetWithId(planetId); + if (!planet) { + // console.log('not a planet'); + return false; + } + // console.log(`planet's owner: ${planet.owner}`); + if (planet.owner !== constants.AddressZero) { + return false; + } + if (!isLocatable(planet)) { + // console.log('planet not locatable'); + return false; + } + return true; + }); + + if (potentialHomeIds.length === 0) { + throw new Error('no spawn locations available'); + } + const potentialHomePlanets = potentialHomeIds.map((planetId) => { + return this.getGameObjects().getPlanetWithId(planetId) as LocatablePlanet; + }); + let selected = false; + let selection; + + // If only one spawn planet, don't let player choose. + if (potentialHomePlanets.length === 1) { + planet = potentialHomePlanets[0]; + } else { + do { + for (let i = 0; i < potentialHomePlanets.length; i++) { + const x = potentialHomePlanets[i].location.coords.x; + const y = potentialHomePlanets[i].location.coords.y; + const type = potentialHomePlanets[i].planetType; + + const level = potentialHomePlanets[i].planetLevel; + this.terminal.current?.print(`(${i + 1}): `, TerminalTextStyle.Sub); + this.terminal.current?.println( + `Level ${level} ${PlanetTypeNames[type]} at (${x},${y})` + ); + } + + this.terminal.current?.println(''); + this.terminal.current?.println(`Choose a spawn planet:`, TerminalTextStyle.White); + selection = +((await this.terminal.current?.getInput()) || ''); + if (isNaN(selection) || selection > potentialHomePlanets.length) { + this.terminal.current?.println('Unrecognized input. Please try again.'); + this.terminal.current?.println(''); + } else { + selected = true; + } + } while (!selected); + planet = potentialHomePlanets[selection - 1]; + } + } else { + planet = await this.findRandomHomePlanet(); + } + this.homeLocation = planet.location; this.terminal.current?.println(''); this.terminal.current?.println(`Found Suitable Home Planet: ${getPlanetName(planet)} `); diff --git a/client/src/Frontend/Pages/CreateLobby.tsx b/client/src/Frontend/Pages/CreateLobby.tsx index aa37d650..9c38deda 100644 --- a/client/src/Frontend/Pages/CreateLobby.tsx +++ b/client/src/Frontend/Pages/CreateLobby.tsx @@ -1,9 +1,11 @@ import { INIT_ADDRESS } from '@dfdao/contracts'; import initContractAbiUrl from '@dfdao/contracts/abis/DFInitialize.json?url'; import { DarkForest } from '@dfdao/contracts/typechain'; +import { fakeHash, mimcHash } from '@dfdao/hashing'; import { EthConnection } from '@dfdao/network'; -import { address } from '@dfdao/serde'; -import { ArtifactRarity, EthAddress, UnconfirmedCreateLobby } from '@dfdao/types'; +import { address, locationIdFromBigInt } from '@dfdao/serde'; +import { decodeArenaAdminPlanets } from '@dfdao/settings'; +import { ArtifactRarity, EthAddress, PlanetType, UnconfirmedCreateLobby } from '@dfdao/types'; import { Contract, providers } from 'ethers'; import { keccak256, toUtf8Bytes } from 'ethers/lib/utils'; import _ from 'lodash'; @@ -110,6 +112,9 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> config.ARTIFACT_POINT_VALUES[ArtifactRarity.Legendary], config.ARTIFACT_POINT_VALUES[ArtifactRarity.Mythic], ], + MANUAL_SPAWN: true, + TARGETS_REQUIRED_FOR_VICTORY: 1, + CLAIM_VICTORY_ENERGY_PERCENT: 20, }); }) .catch((e) => { @@ -146,12 +151,6 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> args: Promise.resolve([initAddress, initFunctionCall]), }; - // contract.once(ContractsAPIEvent.LobbyCreated, (owner: EthAddress, lobby: EthAddress) => { - // if (owner === ownerAddress) { - // setLobbyAddress(lobby); - // } - // }); - const tx = await contract.submitTransaction(txIntent, { // The createLobby function costs somewhere around 12mil gas gasLimit: '15000000', @@ -167,6 +166,42 @@ export function CreateLobby({ match }: RouteComponentProps<{ contract: string }> console.log(`start submitted`); const startRct = await startTx.wait(); console.log(`start confirmed with ${startRct.gasUsed} gas`); + const planetHashMimc = config.DISABLE_ZK_CHECKS + ? fakeHash(config.PLANET_RARITY) + : mimcHash(config.PLANETHASH_KEY); + + console.log(`creating planets...`); + const coords = { x: 0, y: 0 }; + const planets = [ + { + location: `0x` + locationIdFromBigInt(planetHashMimc(coords.x, coords.y)), + x: coords.x, + y: coords.y, + perlin: 20, + level: 5, + planetType: PlanetType.SILVER_MINE, + requireValidLocationId: false, + isTargetPlanet: false, + isSpawnPlanet: true, + blockedPlanetIds: [], + }, + { + location: `0x` + locationIdFromBigInt(planetHashMimc(100, 100)), + x: 100, + y: 100, + perlin: 20, + level: 3, + planetType: PlanetType.SILVER_BANK, + requireValidLocationId: false, + isTargetPlanet: true, + isSpawnPlanet: false, + blockedPlanetIds: [], + }, + ]; + const createTx = await newLobby.bulkCreateAndReveal(decodeArenaAdminPlanets(planets)); + const createRct = await createTx.wait(); + console.log(`create planets confirmed with ${createRct.gasUsed} gas`); + setLobbyAddress(lobby); } diff --git a/eth/contracts/facets/DFArenaFacet.sol b/eth/contracts/facets/DFArenaFacet.sol index ccfc79a2..286042f8 100644 --- a/eth/contracts/facets/DFArenaFacet.sol +++ b/eth/contracts/facets/DFArenaFacet.sol @@ -191,6 +191,18 @@ contract DFArenaFacet is WithStorage { return gs().spawnPlanetIds[idx]; } + function bulkGetSpawnPlanetIds(uint256 startIdx, uint256 endIdx) + public + view + returns (uint256[] memory ret) + { + // return slice of spawnPlanetIds array from startIdx through endIdx - 1 + ret = new uint256[](endIdx - startIdx); + for (uint256 i = startIdx; i < endIdx; i++) { + ret[i - startIdx] = gs().spawnPlanetIds[i]; + } + } + function getRoundDuration() public view returns (uint256) { if (gs().startTime == 0) { return 0; diff --git a/eth/contracts/facets/DFCoreFacet.sol b/eth/contracts/facets/DFCoreFacet.sol index 2bcd8a2d..1a52dd30 100644 --- a/eth/contracts/facets/DFCoreFacet.sol +++ b/eth/contracts/facets/DFCoreFacet.sol @@ -130,7 +130,6 @@ contract DFCoreFacet is WithStorage { Planet storage _planet = gs().planets[_location]; require(_planet.spawnPlanet, "Planet is not a spawn planet"); - require(_planet.isInitialized, "Planet not initialized"); require(_planet.owner == address(0), "Planet is owned"); require(!_planet.isHomePlanet, "Planet is already a home planet"); diff --git a/packages/settings/src/planets.ts b/packages/settings/src/planets.ts index 723f07ea..fc33375d 100644 --- a/packages/settings/src/planets.ts +++ b/packages/settings/src/planets.ts @@ -9,8 +9,26 @@ const decodeAdminPlanet = decoders.exact({ revealLocation: decoders.boolean, }); +const decodeAdminArenaPlanet = decoders.exact({ + location: decoders.string, + x: decoders.number, + y: decoders.number, + perlin: decoders.number, + level: decoders.number, + planetType: decoders.number, + requireValidLocationId: decoders.boolean, + isTargetPlanet: decoders.boolean, + isSpawnPlanet: decoders.boolean, + blockedPlanetIds: decoders.array(decoders.string), +}); + export type AdminPlanets = ReturnType; +export type AdminArenaPlanet = ReturnType; export const decodeAdminPlanets = decoders.guard(decoders.array(decodeAdminPlanet), { style: 'simple', }); + +export const decodeArenaAdminPlanets = decoders.guard(decoders.array(decodeAdminArenaPlanet), { + style: 'simple', +}); From 68ca131c16013b25f8e8bc288c7273180e4934bb Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 17:13:45 -0400 Subject: [PATCH 13/17] working claimVictory tx --- client/src/Backend/GameLogic/GameManager.ts | 103 ++++++++++++++++++ client/src/Backend/GameLogic/GameObjects.ts | 2 + packages/gamelogic/src/planet.ts | 8 ++ packages/network/src/Network.ts | 2 +- .../src/Entities/PlanetRenderManager.ts | 11 +- packages/renderer/src/Overlay2DRenderer.ts | 40 +++++++ packages/serde/src/planet.ts | 2 + packages/serde/src/transactions.ts | 11 ++ packages/types/src/modal.ts | 2 + packages/types/src/planet.ts | 2 + packages/types/src/transactions.ts | 8 ++ packages/ui/src/DarkForestIcon.ts | 22 ++++ 12 files changed, 209 insertions(+), 4 deletions(-) diff --git a/client/src/Backend/GameLogic/GameManager.ts b/client/src/Backend/GameLogic/GameManager.ts index 0a6deffe..e789f03f 100644 --- a/client/src/Backend/GameLogic/GameManager.ts +++ b/client/src/Backend/GameLogic/GameManager.ts @@ -74,6 +74,7 @@ import { UnconfirmedBuyHat, UnconfirmedCapturePlanet, UnconfirmedClaimReward, + UnconfirmedClaimVictory, UnconfirmedDeactivateArtifact, UnconfirmedDepositArtifact, UnconfirmedFindArtifact, @@ -1855,6 +1856,104 @@ class GameManager extends EventEmitter { throw e; } } + private async setGameover(gameover: boolean) { + this.gameover = gameover; + this.winners = await this.contractsAPI.getWinners(); + this.startTime = await this.contractsAPI.getStartTime(); + this.endTimeSeconds = await this.contractsAPI.getEndTime(); + } + + public gameDuration() { + if (!this.startTime) { + return 0; + } + if (this.endTimeSeconds) { + return this.endTimeSeconds - this.startTime; + } + return Date.now() / 1000 - this.startTime; + } + /** + * Attempts to claim victory + */ + + public isTargetHeld(planet: Planet): boolean { + if (!this.account) return false; + if (!planet.isTargetPlanet) return false; + // TODO: Add back teams. + // if (constants.TEAMS_ENABLED) { + // const owner = this.getPlayer(planet.owner); + // const me = this.getPlayer(); + // if (!owner || !me || owner.team !== me.team) return false; + // } else if (planet.owner != this.account) return false; + if ( + (planet.energy * 100) / planet.energyCap < + this.contractConstants.CLAIM_VICTORY_ENERGY_PERCENT + ) + return false; + // TODO: Add back blocklist + // if (this.playerCaptureBlocked(this.account, planet.locationId)) return false; + return true; + } + + public getTargetsHeld(address?: EthAddress): Planet[] { + address = address || this.account; + return this.getPlayerTargetPlanets(address).filter((planet) => this.isTargetHeld(planet)); + } + + public getPlayerTargetPlanets(account?: EthAddress): Planet[] { + const player = this.getPlayer(account); + if (!player) return []; + return [...this.getPlanetMap()].reduce( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (total, [location, planet]) => + // TODO: Add back l + // planet.targetPlanet && !this.isMoveBlocked(location, player.homePlanetId) + planet.isTargetPlanet ? [...total, planet] : total, + [] + ); + } + + public checkVictoryCondition(): boolean { + const targetPlanets = this.getPlayerTargetPlanets(); + + let captured = 0; + + for (const planet of targetPlanets) { + if (!this.isTargetHeld(planet)) continue; + + captured++; + if (captured >= this.contractConstants.TARGETS_REQUIRED_FOR_VICTORY) return true; + } + return false; + } + + public async claimVictory() { + try { + if (this.gameover) { + throw new Error('game is over'); + } + + if (this.paused) { + throw new Error('game is paused'); + } + + if (!this.checkVictoryCondition()) { + throw new Error('victory condition not met'); + } + + const txIntent: UnconfirmedClaimVictory = { + methodName: 'claimVictory', + contract: this.contractsAPI.contract, + args: Promise.resolve([]), + }; + + const tx = await this.contractsAPI.submitTransaction(txIntent); + return tx; + } catch (e) { + this.getNotificationsManager().txInitError('claimVictory', e.message); + throw e; + } + } /** * Attempts to join the game. Should not be called once you've already joined. @@ -3534,6 +3633,10 @@ class GameManager extends EventEmitter { public getUpgradeForArtifact(artifactId: ArtifactId) { return this.contractsAPI.getUpgradeForArtifact(artifactId); } + + public claimVictoryPercentage() { + return this.contractConstants.CLAIM_VICTORY_ENERGY_PERCENT; + } } export default GameManager; diff --git a/client/src/Backend/GameLogic/GameObjects.ts b/client/src/Backend/GameLogic/GameObjects.ts index 5a48c613..5401027f 100644 --- a/client/src/Backend/GameLogic/GameObjects.ts +++ b/client/src/Backend/GameLogic/GameObjects.ts @@ -1263,6 +1263,8 @@ export class GameObjects { invader: EMPTY_ADDRESS, capturer: EMPTY_ADDRESS, + isSpawnPlanet: false, + isTargetPlanet: false, }; } diff --git a/packages/gamelogic/src/planet.ts b/packages/gamelogic/src/planet.ts index 76ae8d41..ebc761ea 100644 --- a/packages/gamelogic/src/planet.ts +++ b/packages/gamelogic/src/planet.ts @@ -36,6 +36,14 @@ export function isLocatable(planet?: Planet): planet is LocatablePlanet { return planet !== undefined && (planet as LocatablePlanet).location !== undefined; } +export function isSpawnPlanet(planet?: Planet): boolean { + return planet !== undefined && planet.isSpawnPlanet; +} + +export function isTargetPlanet(planet?: Planet): boolean { + return planet !== undefined && planet.isTargetPlanet; +} + /** * Gets the time (ms) until we can broadcast the coordinates of a planet. */ diff --git a/packages/network/src/Network.ts b/packages/network/src/Network.ts index af3c0d3e..d8bee675 100644 --- a/packages/network/src/Network.ts +++ b/packages/network/src/Network.ts @@ -179,7 +179,7 @@ export function makeProvider(rpcUrl: string): providers.JsonRpcProvider { provider = new providers.WebSocketProvider(rpcUrl); } else { provider = new providers.StaticJsonRpcProvider(rpcUrl); - provider.pollingInterval = 8000; + provider.pollingInterval = 1000; } return provider; diff --git a/packages/renderer/src/Entities/PlanetRenderManager.ts b/packages/renderer/src/Entities/PlanetRenderManager.ts index 200b2e02..b9c80e11 100644 --- a/packages/renderer/src/Entities/PlanetRenderManager.ts +++ b/packages/renderer/src/Entities/PlanetRenderManager.ts @@ -219,11 +219,16 @@ export class PlanetRenderManager implements PlanetRenderManagerType { radiusW: number, textAlpha: number ) { - if (!renderInfo.planet.messages) return; + // TODO: Put back block + // else if () renderInfo.blocked const { overlay2dRenderer: cM } = this.renderer; - cM.drawPlanetMessages(coords, radiusW, renderInfo, textAlpha); + if (renderInfo.planet.isTargetPlanet) + cM.drawEmoji(coords, radiusW, renderInfo, textAlpha, `🎯`); + else if (renderInfo.planet.isSpawnPlanet) + cM.drawEmoji(coords, radiusW, renderInfo, textAlpha, `🍼`); + else if (renderInfo.planet.messages) + cM.drawPlanetMessages(coords, radiusW, renderInfo, textAlpha); } - private queueArtifactIcon(planet: Planet, { x, y }: WorldCoords, radius: number) { const { overlay2dRenderer: cM } = this.renderer; diff --git a/packages/renderer/src/Overlay2DRenderer.ts b/packages/renderer/src/Overlay2DRenderer.ts index f9bdd95d..116a6b10 100644 --- a/packages/renderer/src/Overlay2DRenderer.ts +++ b/packages/renderer/src/Overlay2DRenderer.ts @@ -244,6 +244,46 @@ export class Overlay2DRenderer { }); } + drawEmoji( + centerWorld: WorldCoords, + radiusWorld: number, + renderInfo: PlanetRenderInfo, + textAlpha: number, + emoji: string + ) { + const viewport = this.renderer.getViewport(); + const pixelCoords = viewport.worldToCanvasCoords(centerWorld); + const radiusPixels = viewport.worldToCanvasDist(radiusWorld); + + let size = radiusPixels; + let offsetY = -2; + + if (renderInfo.planet.emojiZoopAnimation !== undefined) { + size *= renderInfo.planet.emojiZoopAnimation.value(); + } + + if (size < 2) { + return; + } + + if (renderInfo.planet.emojiBobAnimation !== undefined) { + offsetY += renderInfo.planet.emojiBobAnimation.value() * (radiusPixels * 0.1); + } + + // don't want to obscure the silver text + if (renderInfo.planet.silver !== 0) { + offsetY -= 15; + } + + this.ctx.font = `${size}px Arial`; + this.ctx.fillStyle = `rgba(0, 0, 0, ${textAlpha})`; + const textSize = this.ctx.measureText(emoji); + this.ctx.fillText( + emoji, + pixelCoords.x - textSize.width / 2, + pixelCoords.y - radiusPixels * 1.3 + offsetY + ); + } drawEmojiMessage( centerWorld: WorldCoords, radiusWorld: number, diff --git a/packages/serde/src/planet.ts b/packages/serde/src/planet.ts index e29fec89..36f58cac 100644 --- a/packages/serde/src/planet.ts +++ b/packages/serde/src/planet.ts @@ -101,6 +101,8 @@ export function decodePlanet(rawLocationId: string, rawPlanet: RawPlanet): Plane invadeStartBlock: rawPlanet.invadeStartBlock.eq(0) ? undefined : rawPlanet.invadeStartBlock.toNumber(), + isSpawnPlanet: rawPlanet.spawnPlanet, + isTargetPlanet: rawPlanet.targetPlanet, }; return planet; diff --git a/packages/serde/src/transactions.ts b/packages/serde/src/transactions.ts index d1b81f1e..5f91dc48 100644 --- a/packages/serde/src/transactions.ts +++ b/packages/serde/src/transactions.ts @@ -4,6 +4,7 @@ import { UnconfirmedActivateArtifact, UnconfirmedBuyHat, UnconfirmedCapturePlanet, + UnconfirmedClaimVictory, UnconfirmedDeactivateArtifact, UnconfirmedDepositArtifact, UnconfirmedFindArtifact, @@ -24,6 +25,10 @@ import { // `TxIntent` subtypes - `isXIntent` // - these `isUnconfirmedX` should check something more than the method name +export function isUnconfirmedClaimVictory(txIntent: TxIntent): txIntent is UnconfirmedClaimVictory { + return txIntent.methodName === 'claimVictory'; +} + export function isUnconfirmedReveal(txIntent: TxIntent): txIntent is UnconfirmedReveal { return txIntent.methodName === 'revealLocation'; } @@ -106,6 +111,12 @@ export function isUnconfirmedInvadePlanet(txIntent: TxIntent): txIntent is Uncon return txIntent.methodName === 'invadePlanet'; } +export function isUnconfirmedClaimVictoryTx( + tx: Transaction +): tx is Transaction { + return isUnconfirmedClaimVictory(tx.intent); +} + export function isUnconfirmedRevealTx(tx: Transaction): tx is Transaction { return isUnconfirmedReveal(tx.intent); } diff --git a/packages/types/src/modal.ts b/packages/types/src/modal.ts index 10f3dc72..c50e0c56 100644 --- a/packages/types/src/modal.ts +++ b/packages/types/src/modal.ts @@ -38,6 +38,8 @@ export const ModalName = { ManageAccount: 'ManageAccount' as ModalName, Onboarding: 'Onboarding' as ModalName, Private: 'Private' as ModalName, + TargetPlanet: 'TargetPlanet' as TooltipName, + SpawnPlanet: 'SpawnPlanet' as TooltipName, }; export type CursorState = Abstract; diff --git a/packages/types/src/planet.ts b/packages/types/src/planet.ts index ac0f28b2..8cd5e6d6 100644 --- a/packages/types/src/planet.ts +++ b/packages/types/src/planet.ts @@ -157,6 +157,8 @@ export type Planet = { invader?: EthAddress; capturer?: EthAddress; invadeStartBlock?: number; + isSpawnPlanet: boolean; + isTargetPlanet: boolean; }; /** diff --git a/packages/types/src/transactions.ts b/packages/types/src/transactions.ts index b4f392ce..f1a6595e 100644 --- a/packages/types/src/transactions.ts +++ b/packages/types/src/transactions.ts @@ -24,6 +24,7 @@ export type ContractMethodName = | 'createLobby' | 'invadePlanet' | 'capturePlanet' + | 'claimVictory' | 'claimReward'; export type EthTxStatus = @@ -69,6 +70,13 @@ export type UnconfirmedMove = TxIntent & { artifact?: ArtifactId | SpaceshipId; }; +/** + * @hidden + */ +export declare type UnconfirmedClaimVictory = TxIntent & { + methodName: 'claimVictory'; +}; + /** * @hidden */ diff --git a/packages/ui/src/DarkForestIcon.ts b/packages/ui/src/DarkForestIcon.ts index 553a4cde..54d73044 100644 --- a/packages/ui/src/DarkForestIcon.ts +++ b/packages/ui/src/DarkForestIcon.ts @@ -51,6 +51,8 @@ export const IconType = { Invadable: 'Invadable' as IconType, Capturable: 'Capturable' as IconType, Destroyed: 'Destroyed' as IconType, + TargetPlanet: 'TargetPlanet' as IconType, + SpawnPlanet: 'SpawnPlanet' as IconType, } as const; export class DarkForestIcon extends LitElement { @@ -92,6 +94,10 @@ export class DarkForestIcon extends LitElement { render() { switch (this.type) { + case IconType.TargetPlanet: + return TargetPlanet(); + case IconType.TargetPlanet: + return SpawnPlanet(); case IconType.Energy: return EnergyIcon(); case IconType.EnergyGrowth: @@ -198,6 +204,22 @@ export class DarkForestIcon extends LitElement { For example: The WithdrawIcon needs `viewBox="0 0 16 16"` */ +function TargetPlanet() { + return svg` + + + + `; +} + +function SpawnPlanet() { + return svg` + + + + `; +} + function EnergyIcon() { return svg` From 3b9bce1235a4977fb807eefb78379bee07396685 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 17:46:22 -0400 Subject: [PATCH 14/17] feat: working game over and claim victory views --- client/src/Backend/GameLogic/ContractsAPI.ts | 25 ++++ client/src/Backend/GameLogic/GameManager.ts | 63 ++++++++-- client/src/Backend/GameLogic/GameUIManager.ts | 11 ++ .../GameLogic/InitialGameStateDownloader.tsx | 7 ++ client/src/Frontend/Utils/AppHooks.ts | 5 + client/src/Frontend/Views/Gameover.tsx | 46 ++++++++ client/src/Frontend/Views/Paused.tsx | 7 +- .../Frontend/Views/TargetPlanetVictory.tsx | 108 ++++++++++++++++++ client/src/Frontend/Views/TopBar.tsx | 4 + .../darkforest/api/ContractsAPITypes.ts | 3 + 10 files changed, 266 insertions(+), 13 deletions(-) create mode 100644 client/src/Frontend/Views/Gameover.tsx create mode 100644 client/src/Frontend/Views/TargetPlanetVictory.tsx diff --git a/client/src/Backend/GameLogic/ContractsAPI.ts b/client/src/Backend/GameLogic/ContractsAPI.ts index 62e538e6..f14db1a4 100644 --- a/client/src/Backend/GameLogic/ContractsAPI.ts +++ b/client/src/Backend/GameLogic/ContractsAPI.ts @@ -226,6 +226,7 @@ export class ContractsAPI extends EventEmitter { contract.filters.PauseStateChanged(null).topics, contract.filters.LobbyCreated(null, null).topics, contract.filters.SpaceshipFound(null, null, null).topics, + contract.filters.Gameover(null).topics, ].map((topicsOrUndefined) => (topicsOrUndefined || [])[0]), ] as Array>, }; @@ -382,6 +383,10 @@ export class ContractsAPI extends EventEmitter { [ContractEvent.LobbyCreated]: (ownerAddr: string, lobbyAddr: string) => { this.emit(ContractsAPIEvent.LobbyCreated, address(ownerAddr), address(lobbyAddr)); }, + [ContractEvent.Gameover]: (players: string[]) => { + players.map((p) => this.emit(ContractsAPIEvent.PlayerUpdate, address(p))); + this.emit(ContractsAPIEvent.Gameover); + }, }; this.ethConnection.subscribeToContractEvents(contract, eventHandlers, filter); @@ -404,6 +409,7 @@ export class ContractsAPI extends EventEmitter { contract.removeAllListeners(ContractEvent.PlanetSilverWithdrawn); contract.removeAllListeners(ContractEvent.PlanetInvaded); contract.removeAllListeners(ContractEvent.PlanetCaptured); + contract.removeAllListeners(ContractEvent.Gameover); } public getContractAddress(): EthAddress { @@ -755,6 +761,25 @@ export class ContractsAPI extends EventEmitter { return this.makeCall(this.contract.paused); } + public async getGameover(): Promise { + return this.makeCall(this.contract.getGameover); + } + + public async getWinners(): Promise { + const winnerString = await this.makeCall(this.contract.getWinners); + return winnerString.map((w) => address(w)); + } + + public async getStartTime(): Promise { + const startTime = (await this.makeCall(this.contract.getStartTime)).toNumber(); + return startTime === 0 ? undefined : startTime; + } + + public async getEndTime(): Promise { + const endTime = (await this.makeCall(this.contract.getEndTime)).toNumber(); + return endTime; + } + public async getRevealedPlanetsCoords( startingAt: number, onProgressIds?: (fractionCompleted: number) => void, diff --git a/client/src/Backend/GameLogic/GameManager.ts b/client/src/Backend/GameLogic/GameManager.ts index e789f03f..130ecaeb 100644 --- a/client/src/Backend/GameLogic/GameManager.ts +++ b/client/src/Backend/GameLogic/GameManager.ts @@ -237,7 +237,7 @@ class GameManager extends EventEmitter { /** * @todo change this to the correct timestamp each round. */ - private readonly endTimeSeconds: number = 1948939200; // new Date("2031-10-05T04:00:00.000Z").getTime() / 1000 + private endTimeSeconds = 1948939200; // new Date("2031-10-05T04:00:00.000Z").getTime() / 1000 /** * An interface to the blockchain that is a little bit lower-level than {@link ContractsAPI}. It @@ -356,6 +356,19 @@ class GameManager extends EventEmitter { */ private captureZoneGenerator: CaptureZoneGenerator | undefined; + /** + * Arena Stuff + */ + private startTime: number | undefined; + + private gameover: boolean; + + public gameover$: Monomitter; + + private winners: EthAddress[]; + + private spectator: boolean; + private constructor( terminal: React.MutableRefObject, account: EthAddress | undefined, @@ -377,7 +390,9 @@ class GameManager extends EventEmitter { ethConnection: EthConnection, paused: boolean, myArtifacts: Map, - mySpaceships: Map + mySpaceships: Map, + gameover: boolean, + winners: EthAddress[] ) { super(); @@ -403,6 +418,9 @@ class GameManager extends EventEmitter { this.networkHealth$ = monomitter(true); this.paused$ = monomitter(true); this.playersUpdated$ = monomitter(); + this.gameover = gameover; + this.winners = winners; + this.gameover$ = monomitter(true); if (contractConstants.CAPTURE_ZONES_ENABLED) { this.captureZoneGenerator = new CaptureZoneGenerator( @@ -673,7 +691,9 @@ class GameManager extends EventEmitter { connection, initialState.paused, myArtifacts, - mySpaceships + mySpaceships, + initialState.gameover, + initialState.winners ); gameManager.setPlayerTwitters(initialState.twitters); @@ -839,6 +859,10 @@ class GameManager extends EventEmitter { .on(ContractsAPIEvent.RadiusUpdated, async () => { const newRadius = await gameManager.contractsAPI.getWorldRadius(); gameManager.setRadius(newRadius); + }) + .on(ContractsAPIEvent.Gameover, async () => { + gameManager.setGameover(true); + gameManager.gameover$.publish(true); }); const unconfirmedTxs = await otherStore.getUnconfirmedSubmittedEthTxs(); @@ -1596,6 +1620,12 @@ class GameManager extends EventEmitter { this.minerManager.stopExplore(); } } + private async setGameover(gameover: boolean) { + this.gameover = gameover; + this.winners = await this.contractsAPI.getWinners(); + this.startTime = await this.contractsAPI.getStartTime(); + this.endTimeSeconds = await this.contractsAPI.getEndTime(); + } private setRadius(worldRadius: number) { this.worldRadius = worldRadius; @@ -1856,12 +1886,6 @@ class GameManager extends EventEmitter { throw e; } } - private async setGameover(gameover: boolean) { - this.gameover = gameover; - this.winners = await this.contractsAPI.getWinners(); - this.startTime = await this.contractsAPI.getStartTime(); - this.endTimeSeconds = await this.contractsAPI.getEndTime(); - } public gameDuration() { if (!this.startTime) { @@ -1884,7 +1908,8 @@ class GameManager extends EventEmitter { // const owner = this.getPlayer(planet.owner); // const me = this.getPlayer(); // if (!owner || !me || owner.team !== me.team) return false; - // } else if (planet.owner != this.account) return false; + // } + else if (planet.owner !== this.account) return false; if ( (planet.energy * 100) / planet.energyCap < this.contractConstants.CLAIM_VICTORY_ENERGY_PERCENT @@ -1919,9 +1944,11 @@ class GameManager extends EventEmitter { let captured = 0; for (const planet of targetPlanets) { - if (!this.isTargetHeld(planet)) continue; + console.log(`is`, planet, `held?`); + if (this.isTargetHeld(planet)) { + console.log(`held`), captured++; + } - captured++; if (captured >= this.contractConstants.TARGETS_REQUIRED_FOR_VICTORY) return true; } return false; @@ -3630,6 +3657,18 @@ class GameManager extends EventEmitter { return this.paused$; } + public getGameover(): boolean { + return this.gameover; + } + + public getWinners(): EthAddress[] { + return this.winners; + } + + public getGameover$(): Monomitter { + return this.gameover$; + } + public getUpgradeForArtifact(artifactId: ArtifactId) { return this.contractsAPI.getUpgradeForArtifact(artifactId); } diff --git a/client/src/Backend/GameLogic/GameUIManager.ts b/client/src/Backend/GameLogic/GameUIManager.ts index d9a50e28..e970565f 100644 --- a/client/src/Backend/GameLogic/GameUIManager.ts +++ b/client/src/Backend/GameLogic/GameUIManager.ts @@ -1411,6 +1411,17 @@ class GameUIManager extends EventEmitter { return this.gameManager.getPaused$(); } + getGameover(): boolean { + return this.gameManager.getGameover(); + } + + getWinners(): EthAddress[] { + return this.gameManager.getWinners(); + } + getGameover$(): Monomitter { + return this.gameManager.getGameover$(); + } + public getSilverScoreValue(): number { return this.contractConstants.SILVER_SCORE_VALUE; } diff --git a/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx b/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx index 662e03ed..85a52e53 100644 --- a/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx +++ b/client/src/Backend/GameLogic/InitialGameStateDownloader.tsx @@ -1,6 +1,7 @@ import { Artifact, ClaimedCoords, + EthAddress, LocationId, Planet, Player, @@ -40,6 +41,8 @@ export interface InitialGameState { arrivals: Map; twitters: AddressTwitterMap; paused: boolean; + gameover: boolean; + winners: EthAddress[]; } export class InitialGameStateDownloader { @@ -168,6 +171,8 @@ export class InitialGameStateDownloader { const twitters = await tryGetAllTwitters(); const paused = contractsAPI.getIsPaused(); + const gameover = contractsAPI.getGameover(); + const winners = contractsAPI.getWinners(); const initialState: InitialGameState = { contractConstants: await contractConstants, @@ -186,6 +191,8 @@ export class InitialGameStateDownloader { arrivals, twitters, paused: await paused, + gameover: await gameover, + winners: await winners, }; return initialState; diff --git a/client/src/Frontend/Utils/AppHooks.ts b/client/src/Frontend/Utils/AppHooks.ts index 15030801..8d0fda09 100644 --- a/client/src/Frontend/Utils/AppHooks.ts +++ b/client/src/Frontend/Utils/AppHooks.ts @@ -277,3 +277,8 @@ export function usePaused() { const ui = useUIManager(); return useEmitterValue(ui.getPaused$(), ui.getPaused()); } + +export function useGameover() { + const ui = useUIManager(); + return useEmitterValue(ui.getGameover$(), ui.getGameover()); +} diff --git a/client/src/Frontend/Views/Gameover.tsx b/client/src/Frontend/Views/Gameover.tsx new file mode 100644 index 00000000..1fad32c5 --- /dev/null +++ b/client/src/Frontend/Views/Gameover.tsx @@ -0,0 +1,46 @@ +import { EthAddress, TooltipName } from '@dfdao/types'; +import React from 'react'; +import styled from 'styled-components'; +import { AccountLabel } from '../Components/Labels/Labels'; +import { Gold } from '../Components/Text'; +import { TooltipTrigger } from '../Panes/Tooltip'; +import { useGameover, useUIManager } from '../Utils/AppHooks'; + +export function Gameover() { + const uiManager = useUIManager(); + const winners = uiManager.getWinners(); + const gameover = useGameover(); + const teamsEnabled = false; + const winningTeam = ''; + + if (!gameover) { + return <>; + } + + return ( + <> + + GAMEOVER! The winner is {teamsEnabled ? `Team ${winningTeam}` : winners[0]} + } + name={TooltipName.Empty} + > + GAMEOVER! +
+ Winner:{' '} + {teamsEnabled ? ( + `Team ${winningTeam}` + ) : ( + + )} + + + + ); +} + +const GameoverContainer = styled.div` + font-size: 2em; + text-align: center; +`; diff --git a/client/src/Frontend/Views/Paused.tsx b/client/src/Frontend/Views/Paused.tsx index ae5b792d..67365bad 100644 --- a/client/src/Frontend/Views/Paused.tsx +++ b/client/src/Frontend/Views/Paused.tsx @@ -2,15 +2,20 @@ import { TooltipName } from '@dfdao/types'; import React from 'react'; import styled from 'styled-components'; import { TooltipTrigger } from '../Panes/Tooltip'; -import { usePaused } from '../Utils/AppHooks'; +import { useGameover, usePaused } from '../Utils/AppHooks'; export function Paused() { const paused = usePaused(); + const gameOver = useGameover(); if (!paused) { return <>; } + if (gameOver) { + return <>; + } + return ( ; + const requiredPlanets = uiManager.contractConstants.TARGETS_REQUIRED_FOR_VICTORY; + const requiredEnergy = uiManager.contractConstants.CLAIM_VICTORY_ENERGY_PERCENT; + + async function handleClaimVictory() { + setClaiming(true); + try { + const tx = await gameManager.claimVictory(); + const res = await tx.confirmedPromise; + // Manual emit just to be sure + uiManager.getGameManager().getContractAPI().emit(ContractsAPIEvent.PlayerUpdate, player); + // uiManager.getGameManager().getContractAPI().emit(ContractsAPIEvent.Gameover); + } catch (error) { + setClaiming(false); + } + } + + if (gameover) { + return <>; + } + return ( + <> + + + In this game, you need to capture {requiredPlanets} target planet + {requiredPlanets !== 1 && 's'} and fill each with{' '} + {requiredEnergy}% energy. Then you can claim victory and win the game! + + } + name={TooltipName.Empty} + style={{ gap: '5px' }} + > + + Targets: {gameManager.getTargetsHeld().length}/{requiredPlanets} + + + {canClaimVictory && ( + { + const tutorialManager = TutorialManager.getInstance(this); + tutorialManager.acceptInput(TutorialState.HowToGetScore); + handleClaimVictory(); + }} + > + {claiming ? 'Claiming...' : 'Claim Victory'!} + + )} + + + {/* Game length: {prettyTime(gameDuration)} */} + + ); +} + +const GameoverContainer = styled.div` + // font-size: 2em; + text-align: center; +`; +const TimeContainer = styled.div` + font-size: 1em; + text-align: center; +`; + +export const LobbyButton = styled.button<{ primary?: boolean }>` + text-transform: uppercase; + letter-spacing: 0.06em; + border: ${({ primary }) => (primary ? '2px solid #2EE7BA' : '1px solid #5F5F5F')}; + color: ${({ primary }) => (primary ? '#2EE7BA' : '#fff')}; + background: ${({ primary }) => (primary ? '#09352B' : '#252525')}; + padding: 16px; + border-radius: 4px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + transition: background 80ms ease 0s, border-color; + &:hover:not([disabled]) { + background: ${({ primary }) => (primary ? '#0E5141' : '#3D3D3D')}; + border-color: ${({ primary }) => (primary ? '#30FFCD' : '#797979')}; + } + &:disabled { + cursor: not-allowed; + background: #0e5141; + border-color: transparent; + } +`; diff --git a/client/src/Frontend/Views/TopBar.tsx b/client/src/Frontend/Views/TopBar.tsx index f4340c61..b1aedcdc 100644 --- a/client/src/Frontend/Views/TopBar.tsx +++ b/client/src/Frontend/Views/TopBar.tsx @@ -12,9 +12,11 @@ import { TooltipTrigger } from '../Panes/Tooltip'; import { usePlayer, useUIManager } from '../Utils/AppHooks'; import { DFZIndex } from '../Utils/constants'; import { useEmitterSubscribe, useEmitterValue } from '../Utils/EmitterHooks'; +import { Gameover } from './Gameover'; import { ModalToggleButton } from './ModalIcon'; import { NetworkHealth } from './NetworkHealth'; import { Paused } from './Paused'; +import { TargetPlanetVictory } from './TargetPlanetVictory'; const TopBarContainer = styled.div` z-index: ${DFZIndex.MenuBar}; @@ -232,6 +234,8 @@ export function TopBar({ twitterVerifyHook }: { twitterVerifyHook: Hook + + ); } diff --git a/client/src/_types/darkforest/api/ContractsAPITypes.ts b/client/src/_types/darkforest/api/ContractsAPITypes.ts index 8cbb52d1..629a1d7c 100644 --- a/client/src/_types/darkforest/api/ContractsAPITypes.ts +++ b/client/src/_types/darkforest/api/ContractsAPITypes.ts @@ -62,6 +62,7 @@ export const enum ContractEvent { AdminGiveSpaceship = 'AdminGiveSpaceship', PauseStateChanged = 'PauseStateChanged', LobbyCreated = 'LobbyCreated', + Gameover = 'Gameover', } export const enum ContractsAPIEvent { @@ -74,6 +75,8 @@ export const enum ContractsAPIEvent { ArrivalQueued = 'ArrivalQueued', RadiusUpdated = 'RadiusUpdated', LocationRevealed = 'LocationRevealed', + Gameover = 'Gameover', + /** * The transaction has been queued for future execution. */ From b47220f365a167351ff9e6ad873af97361fcf11b Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 18:00:05 -0400 Subject: [PATCH 15/17] contracts: add getters --- eth/contracts/facets/DFArenaFacet.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/eth/contracts/facets/DFArenaFacet.sol b/eth/contracts/facets/DFArenaFacet.sol index 286042f8..71e3a9ed 100644 --- a/eth/contracts/facets/DFArenaFacet.sol +++ b/eth/contracts/facets/DFArenaFacet.sol @@ -212,4 +212,20 @@ contract DFArenaFacet is WithStorage { } return gs().endTime - gs().startTime; } + + function getWinners() public view returns (address[] memory) { + return gs().winners; + } + + function getGameover() public view returns (bool) { + return gs().gameOver; + } + + function getStartTime() public view returns (uint256) { + return gs().startTime; + } + + function getEndTime() public view returns (uint256) { + return gs().endTime; + } } From 5cf2ec44bf6fbff051d25f0b5b6a2e462ad38d3a Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 18:11:23 -0400 Subject: [PATCH 16/17] fix: lint error --- eth/test/DFArena.test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/eth/test/DFArena.test.ts b/eth/test/DFArena.test.ts index 2c2dee30..8600295b 100644 --- a/eth/test/DFArena.test.ts +++ b/eth/test/DFArena.test.ts @@ -187,10 +187,8 @@ describe('DarkForestArena', function () { const planetType = 1; // asteroid field const planets = [ADMIN_PLANET, ADMIN_PLANET_CLOAKED, LVL1_PLANET_SPACE]; - const planetArgList: any = []; - - planets.map((p) => { - const planetArgs = { + const planetArgList = planets.map((p) => { + return { location: p.id, x: Math.floor(Math.random() * 100), y: Math.floor(Math.random() * 100), @@ -202,8 +200,6 @@ describe('DarkForestArena', function () { isSpawnPlanet: true, blockedPlanetIds: [], }; - - planetArgList.push(planetArgs); }); const tx = await world.contract.bulkCreateAndReveal(planetArgList); From eb1a456a79d5902ae54cd3360174f67ae7a141f4 Mon Sep 17 00:00:00 2001 From: cha0sg0d Date: Thu, 6 Oct 2022 18:24:50 -0400 Subject: [PATCH 17/17] fix: client lint --- client/src/Frontend/Pages/CreateLobby.tsx | 1 + client/src/Frontend/Views/TargetPlanetVictory.tsx | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/client/src/Frontend/Pages/CreateLobby.tsx b/client/src/Frontend/Pages/CreateLobby.tsx index 9c38deda..82d30bb5 100644 --- a/client/src/Frontend/Pages/CreateLobby.tsx +++ b/client/src/Frontend/Pages/CreateLobby.tsx @@ -28,6 +28,7 @@ function getLobbyCreatedEvent( contract: DarkForest ): { owner: EthAddress; lobby: EthAddress } { const lobbyCreatedHash = keccak256(toUtf8Bytes('LobbyCreated(address,address)')); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const log = lobbyReceipt.logs.find((log: any) => log.topics[0] === lobbyCreatedHash); if (log) { return { diff --git a/client/src/Frontend/Views/TargetPlanetVictory.tsx b/client/src/Frontend/Views/TargetPlanetVictory.tsx index 292efc81..9271245b 100644 --- a/client/src/Frontend/Views/TargetPlanetVictory.tsx +++ b/client/src/Frontend/Views/TargetPlanetVictory.tsx @@ -11,7 +11,6 @@ export function TargetPlanetVictory() { const uiManager = useUIManager(); const gameManager = uiManager.getGameManager(); const canClaimVictory = gameManager.checkVictoryCondition(); - console.log(`can claim victory`, canClaimVictory); const gameover = useGameover(); const [claiming, setClaiming] = useState(false); @@ -24,10 +23,10 @@ export function TargetPlanetVictory() { setClaiming(true); try { const tx = await gameManager.claimVictory(); - const res = await tx.confirmedPromise; + await tx.confirmedPromise; // Manual emit just to be sure uiManager.getGameManager().getContractAPI().emit(ContractsAPIEvent.PlayerUpdate, player); - // uiManager.getGameManager().getContractAPI().emit(ContractsAPIEvent.Gameover); + uiManager.getGameManager().getContractAPI().emit(ContractsAPIEvent.Gameover); } catch (error) { setClaiming(false); } @@ -69,7 +68,6 @@ export function TargetPlanetVictory() { )} - {/* Game length: {prettyTime(gameDuration)} */} ); } @@ -78,10 +76,6 @@ const GameoverContainer = styled.div` // font-size: 2em; text-align: center; `; -const TimeContainer = styled.div` - font-size: 1em; - text-align: center; -`; export const LobbyButton = styled.button<{ primary?: boolean }>` text-transform: uppercase;