diff --git a/packages/hardhat/contracts/Game.sol b/packages/hardhat/contracts/Game.sol index ba911094..715573bd 100644 --- a/packages/hardhat/contracts/Game.sol +++ b/packages/hardhat/contracts/Game.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.24; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; import { Hook } from "@latticexyz/store/src/Hook.sol"; @@ -10,58 +11,249 @@ import { IOptionalSystemHook } from "@latticexyz/world/src/IOptionalSystemHook.s import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM, ALL } from "@latticexyz/world/src/systemHookTypes.sol"; import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; import { OptionalSystemHooks } from "@latticexyz/world/src/codegen/tables/OptionalSystemHooks.sol"; +import { hasBeforeAndAfterSystemHook, getEntityAtCoord, getEntityFromPlayer, getPosition, getIsLoggedOff, getPlayerFromEntity } from "../utils/EntityUtils.sol"; import { IWorld } from "@biomesaw/world/src/codegen/world/IWorld.sol"; import { VoxelCoord } from "@biomesaw/utils/src/Types.sol"; +import { decodeCallData } from "../utils/HookUtils.sol"; +import { weiToString } from "../utils/GameUtils.sol"; -contract Game is ICustomUnregisterDelegation, IOptionalSystemHook { - address public immutable biomeWorldAddress; +struct LeaderboardEntry { + address player; + uint256 balance; +} + +struct PlayerBalance { + address player; + uint256 balance; +} - address public delegatorAddress; +struct PlayerWithdrawal { + address player; + uint256 lastWithdrawal; +} + +struct PlayerHitter { + address player; + address lastHitter; +} + +contract Game is IOptionalSystemHook { + address public immutable biomeWorldAddress; event GameNotif(address player, string message); - constructor(address _biomeWorldAddress, address _delegatorAddress) { + constructor(address _biomeWorldAddress) { biomeWorldAddress = _biomeWorldAddress; - - // Set the store address, so that when reading from MUD tables in the - // Biomes world, we don't need to pass the store address every time. StoreSwitch.setStoreAddress(_biomeWorldAddress); + } + + address[] public players; + mapping(address => uint256) public balance; + mapping(address => uint256) public lastWithdrawal; + mapping(address => address) public lastHitter; + + ResourceId HitSystemId = WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "", name: "HitSystem" }); + ResourceId LogoffSystemId = + WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "", name: "LogoffSystem" }); + ResourceId SpawnSystemId = WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "", name: "SpawnSystem" }); + + function isPlayerRegistered(address player) public view returns (bool) { + for (uint i = 0; i < players.length; i++) { + if (players[i] == player) { + return true; + } + } + return false; + } + + function removePlayer(address player) internal { + for (uint i = 0; i < players.length; i++) { + if (players[i] == player) { + players[i] = players[players.length - 1]; + players.pop(); + return; + } + } + } + + function registerPlayer() external payable { + require(msg.value >= 0.00035 ether, "Must send atleast minimum ETH to register"); + + address player = msg.sender; + require( + hasBeforeAndAfterSystemHook(address(this), player, HitSystemId), + "The player hasn't allowed the hit hook yet" + ); + require( + hasBeforeAndAfterSystemHook(address(this), player, LogoffSystemId), + "The player hasn't allowed the logoff hook yet" + ); + require( + hasBeforeAndAfterSystemHook(address(this), player, SpawnSystemId), + "The player hasn't allowed the spawn hook yet" + ); + + require(!isPlayerRegistered(player), "Player is already registered"); + + players.push(player); + balance[player] = msg.value; + lastWithdrawal[player] = block.timestamp; + lastHitter[player] = address(0); - delegatorAddress = _delegatorAddress; + emit GameNotif(address(0), string.concat("Player ", Strings.toHexString(player), " has joined the game")); + } + + function withdraw() external { + address player = msg.sender; + require(isPlayerRegistered(player), "You are not a registered player."); + require(lastWithdrawal[player] + 2 hours < block.timestamp, "Can't withdraw yet."); + + uint256 amount = balance[player]; + require(amount > 0, "Your balance is zero."); + + removePlayer(player); + balance[player] = 0; + lastWithdrawal[player] = block.timestamp; + lastHitter[player] = address(0); + + // Safe transfer of funds + (bool sent, ) = player.call{ value: amount }(""); + require(sent, "Failed to send Ether"); } - // Use this modifier to restrict access to the Biomes World contract only - // eg. for hooks that are only allowed to be called by the Biomes World contract modifier onlyBiomeWorld() { require(msg.sender == biomeWorldAddress, "Caller is not the Biomes World contract"); _; // Continue execution } function supportsInterface(bytes4 interfaceId) external view override returns (bool) { - return - interfaceId == type(ICustomUnregisterDelegation).interfaceId || - interfaceId == type(IOptionalSystemHook).interfaceId || - interfaceId == type(IERC165).interfaceId; - } - - function canUnregister(address delegator) external override onlyBiomeWorld returns (bool) { - return true; + return interfaceId == type(IOptionalSystemHook).interfaceId || interfaceId == type(IERC165).interfaceId; } - function onRegisterHook( + function onUnregisterHook( address msgSender, ResourceId systemId, uint8 enabledHooksBitmap, bytes32 callDataHash - ) external override onlyBiomeWorld {} + ) external override onlyBiomeWorld { + uint256 playerBalance = balance[msgSender]; + address recipient = lastHitter[msgSender]; + removePlayer(msgSender); - function onUnregisterHook( + if (playerBalance > 0) { + balance[msgSender] = 0; + if (recipient == address(0)) { + (bool sent, ) = msgSender.call{ value: playerBalance }(""); + require(sent, "Failed to send Ether"); + } else { + balance[recipient] += playerBalance; + lastHitter[msgSender] = address(0); + } + } + } + + function onAfterCallSystem( + address msgSender, + ResourceId systemId, + bytes memory callData + ) external override onlyBiomeWorld { + if (!isPlayerRegistered(msgSender)) { + return; + } + + if (ResourceId.unwrap(systemId) == ResourceId.unwrap(LogoffSystemId)) { + require(false, "Cannot logoff when registered."); + return; + } else if (ResourceId.unwrap(systemId) == ResourceId.unwrap(SpawnSystemId)) { + uint256 playerBalance = balance[msgSender]; + if (playerBalance == 0) { + return; + } + + address recipient = lastHitter[msgSender]; + balance[msgSender] = 0; + + removePlayer(msgSender); + + if (recipient == address(0)) { + (bool sent, ) = msgSender.call{ value: playerBalance }(""); + require(sent, "Failed to send Ether"); + } else { + balance[recipient] += playerBalance; + lastHitter[msgSender] = address(0); + } + } else if (ResourceId.unwrap(systemId) == ResourceId.unwrap(HitSystemId)) { + (, bytes memory callDataArgs) = decodeCallData(callData); + address hitPlayer = abi.decode(callDataArgs, (address)); + + if (isPlayerRegistered(hitPlayer)) { + lastHitter[hitPlayer] = msgSender; + + bytes32 hitPlayerEntity = getEntityFromPlayer(hitPlayer); + if (hitPlayerEntity == bytes32(0)) { + uint256 hitPlayerBalance = balance[hitPlayer]; + balance[hitPlayer] = 0; + balance[msgSender] += hitPlayerBalance; + removePlayer(hitPlayer); + } + } + } + } + + function getRegisteredPlayers() external view returns (address[] memory) { + return players; + } + + function getBalancesLeaderboard() external view returns (LeaderboardEntry[] memory) { + LeaderboardEntry[] memory leaderboard = new LeaderboardEntry[](players.length); + for (uint256 i = 0; i < players.length; i++) { + leaderboard[i] = LeaderboardEntry({ player: players[i], balance: balance[players[i]] }); + } + + return leaderboard; + } + + function getRegisteredPlayerEntityIds() external view returns (bytes32[] memory) { + bytes32[] memory registeredPlayerEntityIds = new bytes32[](players.length); + for (uint i = 0; i < players.length; i++) { + registeredPlayerEntityIds[i] = getEntityFromPlayer(players[i]); + } + return registeredPlayerEntityIds; + } + + function getAllBalances() public view returns (PlayerBalance[] memory playerBalances) { + playerBalances = new PlayerBalance[](players.length); + for (uint256 i = 0; i < players.length; i++) { + playerBalances[i] = PlayerBalance(players[i], balance[players[i]]); + } + } + + function getAllLastWithdrawals() public view returns (PlayerWithdrawal[] memory playerWithdrawals) { + playerWithdrawals = new PlayerWithdrawal[](players.length); + for (uint256 i = 0; i < players.length; i++) { + playerWithdrawals[i] = PlayerWithdrawal(players[i], lastWithdrawal[players[i]]); + } + } + + function getAllLastHitters() public view returns (PlayerHitter[] memory playerLastHitters) { + playerLastHitters = new PlayerHitter[](players.length); + for (uint256 i = 0; i < players.length; i++) { + playerLastHitters[i] = PlayerHitter(players[i], lastHitter[players[i]]); + } + } + + function onRegisterHook( address msgSender, ResourceId systemId, uint8 enabledHooksBitmap, bytes32 callDataHash - ) external override onlyBiomeWorld {} + ) external override onlyBiomeWorld { + require( + getEntityFromPlayer(msgSender) != bytes32(0), + "You Must First Spawn An Avatar In Biome-1 To Play The Game." + ); + } function onBeforeCallSystem( address msgSender, @@ -69,17 +261,55 @@ contract Game is ICustomUnregisterDelegation, IOptionalSystemHook { bytes memory callData ) external override onlyBiomeWorld {} - function onAfterCallSystem( - address msgSender, - ResourceId systemId, - bytes memory callData - ) external override onlyBiomeWorld {} + function getDisplayName() external view returns (string memory) { + return "Bounty Hunter"; + } - function basicGetter() external view returns (uint256) { - return 42; + function getAvatars() external view returns (address[] memory) { + return players; } - function getRegisteredPlayers() external view returns (address[] memory) { - return new address[](0); + function getStatus() external view returns (string memory) { + if (!isPlayerRegistered(msg.sender)) { + return "You are not registered yet."; + } + + uint256 playerBalanceWei = balance[msg.sender]; + address recipient = lastHitter[msg.sender]; + bool canWithdraw = lastWithdrawal[msg.sender] + 2 hours < block.timestamp; + + return + string.concat( + "Your balance is ", + weiToString(playerBalanceWei), + " ether and your last hitter is ", + recipient != address(0) ? Strings.toHexString(recipient) : "no one", + canWithdraw ? ". You may withdraw!" : ". See countdown for next withdrawal." + ); + } + + function getUnregisterMessage() external view returns (string memory) { + if (!isPlayerRegistered(msg.sender)) { + return ""; + } + + uint256 playerBalance = balance[msg.sender]; + address recipient = lastHitter[msg.sender]; + + if (playerBalance > 0) { + if (recipient == address(0)) { + return "You have unclaimed balance. You will be unregistered and the balance will be sent to you."; + } else { + return "You have unclaimed balance. You will be unregistered and the balance will be sent to your last hitter."; + } + } + } + + function getCountdownEndTimestamp() external view returns (uint256) { + if (!isPlayerRegistered(msg.sender)) { + return 0; + } + + return lastWithdrawal[msg.sender] + 2 hours; } } diff --git a/packages/hardhat/deploy/00_deploy_game.ts b/packages/hardhat/deploy/00_deploy_game.ts index 322eef8e..51412fb7 100644 --- a/packages/hardhat/deploy/00_deploy_game.ts +++ b/packages/hardhat/deploy/00_deploy_game.ts @@ -49,7 +49,7 @@ const deployGameContract: DeployFunction = async function (hre: HardhatRuntimeEn await deploy("Game", { from: deployer, // Contract constructor arguments - args: [useBiomesWorldAddress, "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"], + args: [useBiomesWorldAddress], log: true, // autoMine: can be passed to the deploy function to make the deployment process faster on local networks by // automatically mining the contract deployment transaction. There is no effect on live networks. diff --git a/packages/nextjs/.firebaserc b/packages/nextjs/.firebaserc new file mode 100644 index 00000000..5bd33d43 --- /dev/null +++ b/packages/nextjs/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "biomes-bounty-hunter" + } +} diff --git a/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx b/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx index 49df7359..2cbb858c 100644 --- a/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx +++ b/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx @@ -22,7 +22,7 @@ export interface Area { interface LeaderboardEntry { player: string; - kills: bigint; + balance: bigint; } function BuildComponent({ build }: { build: Build | BuildWithPos }) { @@ -116,9 +116,9 @@ function AreaComponent({ area }: { area: Area }) { function LeaderboardComponent({ leaderboard }: { leaderboard: LeaderboardEntry[] }) { const sortedLeaderboard = leaderboard.sort((a, b) => { - if (a.kills > b.kills) { + if (a.balance > b.balance) { return -1; - } else if (a.kills < b.kills) { + } else if (a.balance < b.balance) { return 1; } else { return 0; @@ -131,14 +131,14 @@ function LeaderboardComponent({ leaderboard }: { leaderboard: LeaderboardEntry[] Player - Kills + Balance {sortedLeaderboard.map((entry, index) => ( {displayTxResult(entry.player)} - {entry.kills.toString()} + {formatEther(entry.balance) + " Ξ"} ))} @@ -227,11 +227,11 @@ export function isValidArea(area: DisplayContent | DisplayContent[]) { } export function isLeaderboardEntry(entry: DisplayContent | DisplayContent[]) { - // Checks if entry has the correct structure for address and numKills + // Checks if entry has the correct structure for address and balance if (entry === undefined || entry === null) return false; if (typeof entry !== "object") return false; - return isAddress(entry.player) && typeof entry.kills === "bigint"; + return isAddress(entry.player) && typeof entry.balance === "bigint"; } export function isLeaderboard(leaderboard: DisplayContent | DisplayContent[]) { diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx index d24f87f5..b6ad148d 100644 --- a/packages/nextjs/app/page.tsx +++ b/packages/nextjs/app/page.tsx @@ -17,7 +17,8 @@ const Home: NextPage = () => { const isBiomesRegistered = useGlobalState(({ isBiomesRegistered }) => isBiomesRegistered); const isGameRegistered = useGlobalState(({ isGameRegistered }) => isGameRegistered); - const isBiomesClientSetup = useGlobalState(({ isBiomesClientSetup }) => isBiomesClientSetup); + // const isBiomesClientSetup = useGlobalState(({ isBiomesClientSetup }) => isBiomesClientSetup); + const isBiomesClientSetup = true; useEffect(() => { if (connectedAddress) { diff --git a/packages/nextjs/components/Game.tsx b/packages/nextjs/components/Game.tsx index 4ad2a72d..a2c5a1ea 100644 --- a/packages/nextjs/components/Game.tsx +++ b/packages/nextjs/components/Game.tsx @@ -1,8 +1,11 @@ import { useReducer } from "react"; +import Link from "next/link"; import { Abi, AbiFunction } from "abitype"; +import { formatEther } from "viem"; import { useAccount } from "wagmi"; -import { DisplayVariable, displayTxResult } from "~~/app/debug/_components/contract"; +import { DisplayVariable, WriteOnlyFunctionForm, displayTxResult } from "~~/app/debug/_components/contract"; import { useDeployedContractInfo } from "~~/hooks/scaffold-eth"; +import { useGlobalState } from "~~/services/store/store"; import { GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract"; export const Game: React.FC = ({}) => { @@ -10,6 +13,8 @@ export const Game: React.FC = ({}) => { const [refreshDisplayVariables] = useReducer(value => !value, false); const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo("Game"); + const setIsBiomesClientSetup = useGlobalState(({ setIsBiomesClientSetup }) => setIsBiomesClientSetup); + if (connectedAddress === undefined) { return
Connect your wallet to continue
; } @@ -32,13 +37,50 @@ export const Game: React.FC = ({}) => { }) .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1)); - const basicGetterFn = viewFunctions.find(({ fn }) => fn.name === "basicGetter"); + const writeFunctions = ((deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[]) + .filter(fn => { + const isWriteableFunction = + fn.stateMutability !== "view" && + fn.stateMutability !== "pure" && + fn.name !== "onAfterCallSystem" && + fn.name !== "onBeforeCallSystem" && + fn.name !== "onRegisterHook" && + fn.name !== "onUnregisterHook" && + fn.name !== "canUnregister"; + return isWriteableFunction; + }) + .map(fn => { + return { + fn, + inheritedFrom: ((deployedContractData as GenericContract)?.inheritedFunctions as InheritedFunctions)?.[fn.name], + }; + }) + .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1)); + + const registeredPlayersGetter = viewFunctions.find(({ fn }) => fn.name === "getRegisteredPlayerEntityIds"); + const balancesGetter = viewFunctions.find(({ fn }) => fn.name === "getAllBalances"); + const withdrawalsGetter = viewFunctions.find(({ fn }) => fn.name === "getAllLastWithdrawals"); + const hittersGetter = viewFunctions.find(({ fn }) => fn.name === "getAllLastHitters"); + const leaderboardGetter = viewFunctions.find(({ fn }) => fn.name === "getBalancesLeaderboard"); + + const withdrawFunction = writeFunctions.find(fn => fn.fn.name === "withdraw"); + + if ( + balancesGetter === undefined || + withdrawalsGetter === undefined || + hittersGetter === undefined || + registeredPlayersGetter === undefined || + leaderboardGetter === undefined || + withdrawFunction === undefined + ) { + return
Missing required functions
; + } return (
- Play in{" "} + Kill participating avatars in{" "} { > Biomes {" "} + to get their ether. Stay alive to keep it. View & withdraw your earnings here.
{
-

Play Game

-

- Your Main Game Screen -

+
+ + {({ CopyButton }) => { + return ( +
+
+
Import New Avatars To Kill Into Biomes:
+
+ + +
{CopyButton}
+
+
+
+ ); + }} +
+ + + {({ result, RefreshButton }) => { + return ( +
+
+
Leaderboard
+
{RefreshButton}
+
+ {displayTxResult(result)} +
+ ); + }} +
+
- {basicGetterFn && ( +
+
+ + {({ result, RefreshButton }) => { + // Find the balance for the connected address + const matchingEntry = result?.find( + entry => entry.player.toLowerCase() === connectedAddress.toLowerCase(), + ); + const balance = matchingEntry ? matchingEntry.balance.toString() : "0"; // Default to '0' if no match is found + + return ( +
+
+ Your Balance {RefreshButton} +
+
{formatEther(balance) + " ETH"}
+
+ ); + }} +
+
+ - {({ result, RefreshButton }) => { - return ( -
-
- YOUR GETTER {RefreshButton} -
-
{displayTxResult(result)}
-
+ {({ result }) => { + // Find the balance for the connected address + const matchingEntry = result?.find( + entry => entry.player.toLowerCase() === connectedAddress.toLowerCase(), ); + const lastWithdrawal = matchingEntry ? matchingEntry.lastWithdrawal.toString() : "0"; // Default to '0' if no match is found + const currentTimestamp = Math.floor(Date.now() / 1000); + + if (currentTimestamp - lastWithdrawal > 7200) { + return ( + { + return; + }} + onBlockConfirmation={(txnReceipt: TransactionReceipt) => { + console.log("txnReceipt", txnReceipt); + }} + contractAddress={deployedContractData.address} + inheritedFrom={withdrawFunction?.inheritedFrom} + /> + ); + } else { + return ( +
+
⏳ {14400 - (currentTimestamp - lastWithdrawal)} Seconds
+ + Until You Can Withdraw + {" "} +
+ ); + } }}
- )} +
+ +
+
+ + + +
+
+
⚠️ You can't logoff until you withdraw your ether or die.
+
Unregister your hooks when done playing!
+
+
diff --git a/packages/nextjs/components/HowToPlay.tsx b/packages/nextjs/components/HowToPlay.tsx index da4210d3..cc6faab5 100644 --- a/packages/nextjs/components/HowToPlay.tsx +++ b/packages/nextjs/components/HowToPlay.tsx @@ -15,8 +15,45 @@ export const HowToPlayComponent: React.FC = ({}) => {

- Your Instructions Here + 1. View Participating Avatars

+
+ + +
+
+ +
+

+ 2. Hit Them +

+
+ + +
+
+ +
+

+ 3. Withdraw Your Earnings... If You Survive +

+
+
If a participating avatar dies and you hit them last, you get their ether.
+
If you survive, withdraw your earned ether every 2 hours.
+
); diff --git a/packages/nextjs/components/Landing.tsx b/packages/nextjs/components/Landing.tsx index dba0f692..b787ea7f 100644 --- a/packages/nextjs/components/Landing.tsx +++ b/packages/nextjs/components/Landing.tsx @@ -45,9 +45,9 @@ export const Landing: React.FC = ({}) => {
-

Your Game Title

+

Bounty Hunter

- Your game description + Kill players to get their ether. Stay alive to keep it.

{ }} className="mt-4" > - Your Game Image + Biomes
diff --git a/packages/nextjs/components/RegisterBiomes.tsx b/packages/nextjs/components/RegisterBiomes.tsx index 2174bf5a..4392b483 100644 --- a/packages/nextjs/components/RegisterBiomes.tsx +++ b/packages/nextjs/components/RegisterBiomes.tsx @@ -7,7 +7,7 @@ import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; import { useGlobalState } from "~~/services/store/store"; import { getAllContracts } from "~~/utils/scaffold-eth/contractsData"; -const GameRequiredHooks: string[] = ["LogoffSystem"]; +const GameRequiredHooks: string[] = ["LogoffSystem", "SpawnSystem", "HitSystem"]; export const RegisterBiomes: React.FC = ({}) => { const { address: connectedAddress } = useAccount(); @@ -87,7 +87,9 @@ export const RegisterBiomes: React.FC = ({}) => {

HOOKS

{

Join Game

- Your Register Game Description + Deposit 0.00035 ETH to register your player. If anyone kills your player, they will get this eth. If you + kill other players, you will get their eth.

@@ -109,19 +110,14 @@ export const RegisterGame: React.FC = ({}) => { contractAddress={deployedContractData.address} abi={deployedContractData.abi as Abi} functionName={registerPlayFunctionData.fn.name} - value={"0.0015"} + value={"0.00035"} onWrite={(txnReceipt: TransactionReceipt) => { console.log("txnReceipt", txnReceipt); checkPlayerRegistered(); }} /> ) : ( - +
Register function not found
)}
) : ( diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index 51929f1a..9b6e80ee 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -7,7 +7,7 @@ import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; const deployedContracts = { 690: { Game: { - address: "0x09F61e35b34EB7855fb234dc81109d774Bb16973", + address: "0x4941Bf9a1B5EAFEde0e311140BD6b05a86C9908f", abi: [ { inputs: [ @@ -16,15 +16,31 @@ const deployedContracts = { name: "_biomeWorldAddress", type: "address", }, - { - internalType: "address", - name: "_delegatorAddress", - type: "address", - }, ], stateMutability: "nonpayable", type: "constructor", }, + { + inputs: [ + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "uint256", + name: "start", + type: "uint256", + }, + { + internalType: "uint256", + name: "end", + type: "uint256", + }, + ], + name: "Slice_OutOfBounds", + type: "error", + }, { anonymous: false, inputs: [ @@ -45,8 +61,14 @@ const deployedContracts = { type: "event", }, { - inputs: [], - name: "basicGetter", + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "balance", outputs: [ { internalType: "uint256", @@ -70,15 +92,206 @@ const deployedContracts = { stateMutability: "view", type: "function", }, + { + inputs: [], + name: "getAllBalances", + outputs: [ + { + components: [ + { + internalType: "address", + name: "player", + type: "address", + }, + { + internalType: "uint256", + name: "balance", + type: "uint256", + }, + ], + internalType: "struct PlayerBalance[]", + name: "playerBalances", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAllLastHitters", + outputs: [ + { + components: [ + { + internalType: "address", + name: "player", + type: "address", + }, + { + internalType: "address", + name: "lastHitter", + type: "address", + }, + ], + internalType: "struct PlayerHitter[]", + name: "playerLastHitters", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAllLastWithdrawals", + outputs: [ + { + components: [ + { + internalType: "address", + name: "player", + type: "address", + }, + { + internalType: "uint256", + name: "lastWithdrawal", + type: "uint256", + }, + ], + internalType: "struct PlayerWithdrawal[]", + name: "playerWithdrawals", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAvatars", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getBalancesLeaderboard", + outputs: [ + { + components: [ + { + internalType: "address", + name: "player", + type: "address", + }, + { + internalType: "uint256", + name: "balance", + type: "uint256", + }, + ], + internalType: "struct LeaderboardEntry[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCountdownEndTimestamp", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getDisplayName", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getRegisteredPlayerEntityIds", + outputs: [ + { + internalType: "bytes32[]", + name: "", + type: "bytes32[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getRegisteredPlayers", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getStatus", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getUnregisterMessage", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, { inputs: [ { internalType: "address", - name: "delegator", + name: "player", type: "address", }, ], - name: "canUnregister", + name: "isPlayerRegistered", outputs: [ { internalType: "bool", @@ -86,12 +299,18 @@ const deployedContracts = { type: "bool", }, ], - stateMutability: "nonpayable", + stateMutability: "view", type: "function", }, { - inputs: [], - name: "delegatorAddress", + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "lastHitter", outputs: [ { internalType: "address", @@ -103,13 +322,19 @@ const deployedContracts = { type: "function", }, { - inputs: [], - name: "getRegisteredPlayers", + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "lastWithdrawal", outputs: [ { - internalType: "address[]", + internalType: "uint256", name: "", - type: "address[]", + type: "uint256", }, ], stateMutability: "view", @@ -217,6 +442,32 @@ const deployedContracts = { stateMutability: "nonpayable", type: "function", }, + { + inputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + name: "players", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "registerPlayer", + outputs: [], + stateMutability: "payable", + type: "function", + }, { inputs: [ { @@ -225,75 +476,417 @@ const deployedContracts = { type: "bytes4", }, ], - name: "supportsInterface", + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "withdraw", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + inheritedFunctions: { + onAfterCallSystem: "@latticexyz/world/src/IOptionalSystemHook.sol", + onBeforeCallSystem: "@latticexyz/world/src/IOptionalSystemHook.sol", + onRegisterHook: "@latticexyz/world/src/IOptionalSystemHook.sol", + onUnregisterHook: "@latticexyz/world/src/IOptionalSystemHook.sol", + supportsInterface: "@latticexyz/world/src/IOptionalSystemHook.sol", + }, + }, + }, + 17069: { + Game: { + address: "0x25074032327f9a4ec7A8c193c310234AF6Ca62De", + abi: [ + { + inputs: [ + { + internalType: "address", + name: "_biomeWorldAddress", + type: "address", + }, + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord", + name: "lowerSouthwestCorner", + type: "tuple", + }, + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord", + name: "size", + type: "tuple", + }, + { + internalType: "address", + name: "_gameStarter", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [ + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "uint256", + name: "start", + type: "uint256", + }, + { + internalType: "uint256", + name: "end", + type: "uint256", + }, + ], + name: "Slice_OutOfBounds", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "player", + type: "address", + }, + { + indexed: false, + internalType: "string", + name: "message", + type: "string", + }, + ], + name: "GameNotif", + type: "event", + }, + { + inputs: [], + name: "biomeWorldAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "claimRewardPool", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "gameEndBlock", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "gameStarter", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAlivePlayers", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAreas", + outputs: [ + { + components: [ + { + internalType: "string", + name: "name", + type: "string", + }, + { + components: [ + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord", + name: "lowerSouthwestCorner", + type: "tuple", + }, + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord", + name: "size", + type: "tuple", + }, + ], + internalType: "struct Area", + name: "area", + type: "tuple", + }, + ], + internalType: "struct NamedArea[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAvatars", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCountdownEndBlock", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getDeadPlayers", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getDisplayName", outputs: [ { - internalType: "bool", + internalType: "string", name: "", - type: "bool", + type: "string", }, ], stateMutability: "view", type: "function", }, - ], - inheritedFunctions: { - canUnregister: "@latticexyz/world/src/ICustomUnregisterDelegation.sol", - supportsInterface: "@latticexyz/world/src/IOptionalSystemHook.sol", - onAfterCallSystem: "@latticexyz/world/src/IOptionalSystemHook.sol", - onBeforeCallSystem: "@latticexyz/world/src/IOptionalSystemHook.sol", - onRegisterHook: "@latticexyz/world/src/IOptionalSystemHook.sol", - onUnregisterHook: "@latticexyz/world/src/IOptionalSystemHook.sol", - }, - }, - }, - 17069: { - Game: { - address: "0xaFFFd91f427b81e0e56be9A4b6369f8DE6f24994", - abi: [ { - inputs: [ - { - internalType: "address", - name: "_biomeWorldAddress", - type: "address", - }, + inputs: [], + name: "getDisqualifiedPlayers", + outputs: [ { - internalType: "address", - name: "_delegatorAddress", - type: "address", + internalType: "address[]", + name: "", + type: "address[]", }, ], - stateMutability: "nonpayable", - type: "constructor", + stateMutability: "view", + type: "function", }, { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "address", - name: "player", - type: "address", - }, + inputs: [], + name: "getKillsLeaderboard", + outputs: [ { - indexed: false, - internalType: "string", - name: "message", - type: "string", + components: [ + { + internalType: "address", + name: "player", + type: "address", + }, + { + internalType: "bool", + name: "isAlive", + type: "bool", + }, + { + internalType: "uint256", + name: "kills", + type: "uint256", + }, + ], + internalType: "struct LeaderboardEntry[]", + name: "", + type: "tuple[]", }, ], - name: "GameNotif", - type: "event", + stateMutability: "view", + type: "function", }, { inputs: [], - name: "basicGetter", + name: "getMatchArea", outputs: [ { - internalType: "uint256", + components: [ + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord", + name: "lowerSouthwestCorner", + type: "tuple", + }, + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord", + name: "size", + type: "tuple", + }, + ], + internalType: "struct Area", name: "", - type: "uint256", + type: "tuple", }, ], stateMutability: "view", @@ -301,44 +894,51 @@ const deployedContracts = { }, { inputs: [], - name: "biomeWorldAddress", + name: "getRegisteredPlayerEntityIds", outputs: [ { - internalType: "address", + internalType: "bytes32[]", name: "", - type: "address", + type: "bytes32[]", }, ], stateMutability: "view", type: "function", }, { - inputs: [ + inputs: [], + name: "getRewardPool", + outputs: [ { - internalType: "address", - name: "delegator", - type: "address", + internalType: "uint256", + name: "", + type: "uint256", }, ], - name: "canUnregister", + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getStatus", outputs: [ { - internalType: "bool", + internalType: "string", name: "", - type: "bool", + type: "string", }, ], - stateMutability: "nonpayable", + stateMutability: "view", type: "function", }, { inputs: [], - name: "delegatorAddress", + name: "getUnregisterMessage", outputs: [ { - internalType: "address", + internalType: "string", name: "", - type: "address", + type: "string", }, ], stateMutability: "view", @@ -346,12 +946,12 @@ const deployedContracts = { }, { inputs: [], - name: "getRegisteredPlayers", + name: "isGameStarted", outputs: [ { - internalType: "address[]", + internalType: "bool", name: "", - type: "address[]", + type: "bool", }, ], stateMutability: "view", @@ -459,6 +1059,78 @@ const deployedContracts = { stateMutability: "nonpayable", type: "function", }, + { + inputs: [], + name: "registerPlayer", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord", + name: "lowerSouthwestCorner", + type: "tuple", + }, + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord", + name: "size", + type: "tuple", + }, + ], + name: "setMatchArea", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "numBlocksToEnd", + type: "uint256", + }, + ], + name: "startGame", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [ { @@ -480,18 +1152,17 @@ const deployedContracts = { }, ], inheritedFunctions: { - canUnregister: "@latticexyz/world/src/ICustomUnregisterDelegation.sol", - supportsInterface: "@latticexyz/world/src/IOptionalSystemHook.sol", onAfterCallSystem: "@latticexyz/world/src/IOptionalSystemHook.sol", onBeforeCallSystem: "@latticexyz/world/src/IOptionalSystemHook.sol", onRegisterHook: "@latticexyz/world/src/IOptionalSystemHook.sol", onUnregisterHook: "@latticexyz/world/src/IOptionalSystemHook.sol", + supportsInterface: "@latticexyz/world/src/IOptionalSystemHook.sol", }, }, }, 31337: { Game: { - address: "0xddE78e6202518FF4936b5302cC2891ec180E8bFf", + address: "0x9385556B571ab92bf6dC9a0DbD75429Dd4d56F91", abi: [ { inputs: [ @@ -509,6 +1180,27 @@ const deployedContracts = { stateMutability: "nonpayable", type: "constructor", }, + { + inputs: [ + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "uint256", + name: "start", + type: "uint256", + }, + { + internalType: "uint256", + name: "end", + type: "uint256", + }, + ], + name: "Slice_OutOfBounds", + type: "error", + }, { anonymous: false, inputs: [ @@ -529,15 +1221,21 @@ const deployedContracts = { type: "event", }, { - inputs: [], - name: "basicGetter", - outputs: [ + inputs: [ { internalType: "uint256", name: "", type: "uint256", }, ], + name: "allowedItemDrops", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], stateMutability: "view", type: "function", }, @@ -573,6 +1271,25 @@ const deployedContracts = { stateMutability: "nonpayable", type: "function", }, + { + inputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + name: "coordHashToBuilder", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, { inputs: [], name: "delegatorAddress", @@ -586,9 +1303,22 @@ const deployedContracts = { stateMutability: "view", type: "function", }, + { + inputs: [ + { + internalType: "bytes32", + name: "toolEntityId", + type: "bytes32", + }, + ], + name: "dropItem", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [], - name: "getRegisteredPlayers", + name: "getAllowedItemDrops", outputs: [ { internalType: "address[]", @@ -599,6 +1329,171 @@ const deployedContracts = { stateMutability: "view", type: "function", }, + { + inputs: [], + name: "getBuild", + outputs: [ + { + components: [ + { + internalType: "uint8[]", + name: "objectTypeIds", + type: "uint8[]", + }, + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord[]", + name: "relativePositions", + type: "tuple[]", + }, + ], + internalType: "struct Build", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getBuilds", + outputs: [ + { + components: [ + { + internalType: "string", + name: "name", + type: "string", + }, + { + components: [ + { + internalType: "uint8[]", + name: "objectTypeIds", + type: "uint8[]", + }, + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord[]", + name: "relativePositions", + type: "tuple[]", + }, + ], + internalType: "struct Build", + name: "build", + type: "tuple", + }, + ], + internalType: "struct NamedBuild[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getDisplayName", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getStatus", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getUnregisterMessage", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord", + name: "baseWorldCoord", + type: "tuple", + }, + ], + name: "matchBuild", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [ { @@ -701,6 +1596,41 @@ const deployedContracts = { stateMutability: "nonpayable", type: "function", }, + { + inputs: [ + { + internalType: "uint8[]", + name: "objectTypeIds", + type: "uint8[]", + }, + { + components: [ + { + internalType: "int16", + name: "x", + type: "int16", + }, + { + internalType: "int16", + name: "y", + type: "int16", + }, + { + internalType: "int16", + name: "z", + type: "int16", + }, + ], + internalType: "struct VoxelCoord[]", + name: "relativePositions", + type: "tuple[]", + }, + ], + name: "setBuild", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [ { @@ -733,4 +1663,4 @@ const deployedContracts = { }, } as const; -export default deployedContracts satisfies GenericContractsDeclaration; +export default deployedContracts satisfies GenericContractsDeclaration; \ No newline at end of file diff --git a/packages/nextjs/firebase.json b/packages/nextjs/firebase.json new file mode 100644 index 00000000..c5c52092 --- /dev/null +++ b/packages/nextjs/firebase.json @@ -0,0 +1,10 @@ +{ + "hosting": { + "public": "out", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + } +} diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 3e8833eb..3abaf2d2 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -11,7 +11,8 @@ "format": "prettier --write . '!(node_modules|.next|contracts)/**/*'", "check-types": "tsc --noEmit --incremental", "vercel": "vercel", - "vercel:yolo": "vercel --build-env NEXT_PUBLIC_IGNORE_BUILD_ERROR=true" + "vercel:yolo": "vercel --build-env NEXT_PUBLIC_IGNORE_BUILD_ERROR=true", + "deploy": "NEXT_PUBLIC_IGNORE_BUILD_ERROR=true yarn run build && firebase use biomes-bounty-hunter && firebase deploy" }, "dependencies": { "@biomesaw/utils": "0.0.8", diff --git a/packages/nextjs/public/bountyhunter.png b/packages/nextjs/public/bountyhunter.png new file mode 100644 index 00000000..a0607297 Binary files /dev/null and b/packages/nextjs/public/bountyhunter.png differ