diff --git a/packages/hardhat/contracts/Experience.sol b/packages/hardhat/contracts/Experience.sol index 2da5210a..1191f732 100644 --- a/packages/hardhat/contracts/Experience.sol +++ b/packages/hardhat/contracts/Experience.sol @@ -32,23 +32,25 @@ import { setExperienceMetadata, deleteExperienceMetadata, setNotification, delet import { setPlayers, pushPlayers, popPlayers, updatePlayers, deletePlayers, setArea, deleteArea, setBuild, deleteBuild, setBuildWithPos, deleteBuildWithPos, setCountdown, setCountdownEndTimestamp, setCountdownEndBlock } from "@biomesaw/experience/src/utils/ExperienceUtils.sol"; import { setChipMetadata, deleteChipMetadata, setChipAttacher, deleteChipAttacher } from "@biomesaw/experience/src/utils/ExperienceUtils.sol"; -contract Experience is ICustomUnregisterDelegation, IOptionalSystemHook { - address public delegatorAddress; +bytes32 constant PRIZE_AREA_ID = bytes32(keccak256("PRIZE_AREA")); - constructor(address _biomeWorldAddress, address _delegatorAddress) { +contract Experience is IOptionalSystemHook { + bool public isGameOver = false; + address public winner; + + constructor(address _biomeWorldAddress, Area memory initialArea) payable { // 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); - initExperience(); - - delegatorAddress = _delegatorAddress; + initExperience(initialArea); } - function initExperience() internal { - setStatus("Test Experience Status"); - setRegisterMsg("Test Experience Register Message"); - setUnregisterMsg("Test Experience Unregister Message"); + function initExperience(Area memory initialArea) internal { + setArea(PRIZE_AREA_ID, "SkyBox", initialArea); + + setStatus("Game in progress. Move your avatar inside the SkyBox to win!"); + setRegisterMsg("Move hook checks if your player has reached the summit."); bytes32[] memory hookSystemIds = new bytes32[](1); hookSystemIds[0] = ResourceId.unwrap(getSystemId("MoveSystem")); @@ -58,8 +60,8 @@ contract Experience is ICustomUnregisterDelegation, IOptionalSystemHook { shouldDelegate: address(0), hookSystemIds: hookSystemIds, joinFee: 0, - name: "Test Experience", - description: "Test Experience Description" + name: "Race To The Sky", + description: "First to reach the designated area in the sky wins the ETH" }) ); } @@ -72,14 +74,7 @@ contract Experience is ICustomUnregisterDelegation, IOptionalSystemHook { } 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( @@ -87,7 +82,14 @@ contract Experience is ICustomUnregisterDelegation, IOptionalSystemHook { ResourceId systemId, uint8 enabledHooksBitmap, bytes32 callDataHash - ) external override onlyBiomeWorld {} + ) external override onlyBiomeWorld { + require(!isGameOver, "Game is already over."); + require( + getEntityFromPlayer(msgSender) != bytes32(0), + "You Must First Spawn An Avatar In Biome-1 To Play The Game." + ); + setNotification(address(0), string.concat(Strings.toHexString(msgSender), " has joined the game")); + } function onUnregisterHook( address msgSender, @@ -106,13 +108,39 @@ contract Experience is ICustomUnregisterDelegation, IOptionalSystemHook { address msgSender, ResourceId systemId, bytes memory callData - ) external override onlyBiomeWorld {} + ) external override onlyBiomeWorld { + if (isGameOver) { + return; + } - function basicGetter() external view returns (uint256) { - return 42; + Area memory prizeArea = getArea(address(this), PRIZE_AREA_ID); + + bytes32 playerEntityId = getEntityFromPlayer(msgSender); + if (playerEntityId == bytes32(0)) { + return; + } + + VoxelCoord memory playerPosition = getPosition(playerEntityId); + + if (insideArea(prizeArea, playerPosition)) { + isGameOver = true; + winner = msgSender; + + setStatus(string.concat("Game over. Winner: ", Strings.toHexString(msgSender))); + setRegisterMsg("Game is over."); + + setNotification(address(0), string.concat(Strings.toHexString(msgSender), " has won the game!")); + + (bool sent, ) = msgSender.call{ value: address(this).balance }(""); + require(sent, "Failed to send Ether"); + } } function getBiomeWorldAddress() external view returns (address) { return WorldContextConsumerLib._world(); } + + function getWinner() external view returns (address) { + return winner; + } } diff --git a/packages/hardhat/deploy/00_deploy_experience.ts b/packages/hardhat/deploy/00_deploy_experience.ts index 8d8da3f3..5b9142b8 100644 --- a/packages/hardhat/deploy/00_deploy_experience.ts +++ b/packages/hardhat/deploy/00_deploy_experience.ts @@ -49,7 +49,14 @@ const deployExperienceContract: DeployFunction = async function (hre: HardhatRun await deploy("Experience", { from: deployer, // Contract constructor arguments - args: [useBiomesWorldAddress, "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"], + args: [ + useBiomesWorldAddress, + { + lowerSouthwestCorner: { x: 221, y: 50, z: -93 }, + size: { x: 3, y: 3, z: 3 }, + }, + ], + value: "33000000000000000", 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/app/debug/_components/contract/ContractUI.tsx b/packages/nextjs/app/debug/_components/contract/ContractUI.tsx index e0a44617..6014f9bc 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractUI.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractUI.tsx @@ -82,7 +82,7 @@ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) =>
{ const setStage = useGlobalState(({ setStage }) => setStage); const isBiomesRegistered = useGlobalState(({ isBiomesRegistered }) => isBiomesRegistered); - const isExperienceRegistered = useGlobalState(({ isExperienceRegistered }) => isExperienceRegistered); + // const isExperienceRegistered = useGlobalState(({ isExperienceRegistered }) => isExperienceRegistered); + const isExperienceRegistered = true; useEffect(() => { if (connectedAddress) { diff --git a/packages/nextjs/components/Experience.tsx b/packages/nextjs/components/Experience.tsx index e3fdc1eb..cfa016c8 100644 --- a/packages/nextjs/components/Experience.tsx +++ b/packages/nextjs/components/Experience.tsx @@ -1,4 +1,5 @@ import { useReducer } from "react"; +import { Balance } from "./scaffold-eth"; import { Abi, AbiFunction } from "abitype"; import { useAccount } from "wagmi"; import { DisplayVariable, displayTxResult } from "~~/app/debug/_components/contract"; @@ -32,7 +33,7 @@ export const Experience: React.FC = ({}) => { }) .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1)); - const basicGetterFn = viewFunctions.find(({ fn }) => fn.name === "basicGetter"); + const getWinnerFn = viewFunctions.find(({ fn }) => fn.name === "getWinner"); return (
@@ -62,38 +63,76 @@ export const Experience: React.FC = ({}) => {
-

Play Experience

+

You Arre in!

- Your Main Experience Page + First one to get their avatar to enter the designated area in the sky wins the eth!

-
+
+ Your Game Image +
- {basicGetterFn && ( +
+
+ Prize: +
+
+ +
+
+ + {getWinnerFn && ( {({ result, RefreshButton }) => { return (
-
- YOUR GETTER {RefreshButton} +
+ Winner: {RefreshButton} +
+
+ {result === "0x0000000000000000000000000000000000000000" ? "None Yet" : displayTxResult(result)}
-
{displayTxResult(result)}
); }} diff --git a/packages/nextjs/components/HowToPlay.tsx b/packages/nextjs/components/HowToPlay.tsx index da4210d3..5d934b67 100644 --- a/packages/nextjs/components/HowToPlay.tsx +++ b/packages/nextjs/components/HowToPlay.tsx @@ -15,8 +15,60 @@ export const HowToPlayComponent: React.FC = ({}) => {

- Your Instructions Here + It's a race! First one to get their avatar to enter the designated area in the sky wins the eth!

+
+ + +
+
+

+ Gameplay Tips +

+
+
+
Build stairs for your avatar to climb:
+ + +
+
+
Kill anyone in the way:
+ + +
+ +
+
Mine blocks below other avatars to drop and hurt them:
+ + +
+ +
Double tap your space key to fly and more easily direct your onchain avatar!
+
+
); diff --git a/packages/nextjs/components/Landing.tsx b/packages/nextjs/components/Landing.tsx index 915b41ca..bb8991de 100644 --- a/packages/nextjs/components/Landing.tsx +++ b/packages/nextjs/components/Landing.tsx @@ -1,4 +1,5 @@ import { useReducer } from "react"; +import { Balance } from "./scaffold-eth"; import { Abi, AbiFunction } from "abitype"; import { DisplayVariable, displayTxResult } from "~~/app/debug/_components/contract"; import { useDeployedContractInfo } from "~~/hooks/scaffold-eth"; @@ -26,7 +27,7 @@ export const Landing: React.FC = ({}) => { }) .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1)); - const basicGetterFn = viewFunctions.find(({ fn }) => fn.name === "basicGetter"); + const basicGetterFn = viewFunctions.find(({ fn }) => fn.name === "getWinner"); return (
@@ -45,9 +46,9 @@ export const Landing: React.FC = ({}) => {
-

Your Experience Title

+

Race To The Sky

- Your experience description + First to reach the designated area in the sky wins the ETH!

{ justifyContent: "center", alignItems: "center", backgroundColor: "#000", // Optional: Background color - color: "#fff", // Text color - fontSize: "20px", // Adjust font size as needed }} className="mt-4" > - Your Experience Image + Your Game Image
@@ -71,6 +78,21 @@ export const Landing: React.FC = ({}) => { className="col-span-12 lg:col-span-3 p-12" style={{ backgroundColor: "#160b21", borderLeft: "1px solid #0e0715" }} > +
+
+ Prize: +
+
+ +
+
+ {basicGetterFn && ( { {({ result, RefreshButton }) => { return (
-
- YOUR GETTER {RefreshButton} +
+ Winner: {RefreshButton} +
+
+ {result === "0x0000000000000000000000000000000000000000" ? "None Yet" : displayTxResult(result)}
-
{displayTxResult(result)}
); }} diff --git a/packages/nextjs/components/RegisterBiomes.tsx b/packages/nextjs/components/RegisterBiomes.tsx index c5d33663..d74f2bc8 100644 --- a/packages/nextjs/components/RegisterBiomes.tsx +++ b/packages/nextjs/components/RegisterBiomes.tsx @@ -87,7 +87,7 @@ export const RegisterBiomes: React.FC = ({}) => {

HOOKS