Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 52 additions & 24 deletions packages/hardhat/contracts/Experience.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand All @@ -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"
})
);
}
Expand All @@ -72,22 +74,22 @@ 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(
address msgSender,
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,
Expand All @@ -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;
}
}
9 changes: 8 additions & 1 deletion packages/hardhat/deploy/00_deploy_experience.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) =>
</div>
<div className="p-5 divide-y divide-base-300 bg-red">
<a
href="https://github.com/tenetxyz/biomes-scaffold/blob/main/packages/hardhat/contracts/Experience.sol"
href="https://github.com/tenetxyz/biomes-scaffold/blob/race-to-the-sky/packages/hardhat/contracts/Experience.sol"
target="_blank"
rel="noreferrer"
className="link"
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const Home: NextPage = () => {
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) {
Expand Down
61 changes: 50 additions & 11 deletions packages/nextjs/components/Experience.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 (
<div className="flex-1 flex flex-col h-full p-mono">
Expand Down Expand Up @@ -62,38 +63,76 @@ export const Experience: React.FC = ({}) => {
<div className="col-span-12 lg:col-span-9 p-12 flex flex-col justify-between items-center">
<div style={{ width: "80%" }} className="flex flex-col gap-12">
<div>
<h1 className="text-3xl font-bold text-left mt-4">Play Experience</h1>
<h1 className="text-3xl font-bold text-left mt-4">You Arre in!</h1>
<h1 className="text-left mt-4" style={{ lineHeight: "normal", margin: "0", wordWrap: "break-word" }}>
Your Main Experience Page
First one to get their avatar to enter the designated area in the sky wins the eth!
</h1>
</div>
<div></div>
<div
style={{
border: "1px solid white",
width: "100%",
height: "500px", // Adjust the height as needed
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "#000", // Optional: Background color
}}
className="mt-4"
>
<img
src="/racetothetop.png"
alt="Your Game Image"
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
</div>
</div>
<div
className="col-span-12 lg:col-span-3 p-12"
style={{ backgroundColor: "#160b21", borderLeft: "1px solid #0e0715" }}
>
{basicGetterFn && (
<div
className="p-6 text-white text-center border border- border-white w-full mb-4"
style={{ backgroundColor: "#42a232" }}
>
<div className="text-lg font-bold flex justify-center items-center flex justify-center align-center items-center">
<span>Prize:</span>
</div>
<div className="my-4 mt-8" style={{ textAlign: "-webkit-center" }}>
<Balance
address={deployedContractData.address}
className="px-0 h-1.5 min-h-[0.375rem] text-4xl text-center"
/>
</div>
</div>

{getWinnerFn && (
<DisplayVariable
abi={deployedContractData.abi as Abi}
abiFunction={basicGetterFn.fn}
abiFunction={getWinnerFn.fn}
contractAddress={deployedContractData.address}
key={"getter"}
refreshDisplayVariables={refreshDisplayVariables}
inheritedFrom={basicGetterFn.inheritedFrom}
inheritedFrom={getWinnerFn.inheritedFrom}
poll={10000}
>
{({ result, RefreshButton }) => {
return (
<div
className="p-6 text-white text-center border border- border-white w-full"
className="p-6 text-white text-center border border-white w-full"
style={{ backgroundColor: "#42a232" }}
>
<div className="text-sm font-bold flex justify-center items-center">
<span>YOUR GETTER</span> <span>{RefreshButton}</span>
<div className="text-lg font-bold flex justify-center items-center">
<span>Winner:</span> <span>{RefreshButton}</span>
</div>
<div className="text-3xl my-4 text-center">
{result === "0x0000000000000000000000000000000000000000" ? "None Yet" : displayTxResult(result)}
</div>
<div className="text-4xl mt-2">{displayTxResult(result)}</div>
</div>
);
}}
Expand Down
54 changes: 53 additions & 1 deletion packages/nextjs/components/HowToPlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,60 @@ export const HowToPlayComponent: React.FC = ({}) => {
</div>
<div className="p-12 flex flex-col justify-between gap-4">
<h2 className="text-xl pb-2" style={{ borderBottom: "0.5px solid white", textAlign: "center" }}>
Your Instructions Here
It&apos;s a race! First one to get their avatar to enter the designated area in the sky wins the eth!
</h2>
<div style={{ display: "flex", width: "90%", gap: "4rem", alignSelf: "center" }}>
<img
alt=""
src="/importareas/four.png"
style={{ width: "50%", height: "auto" }}
className="border rounded-sm"
/>
<img alt="" src="/racetothetopp.png" style={{ width: "50%", height: "auto" }} className="border rounded-sm" />
</div>
<div className="p-12 flex flex-col justify-between gap-4">
<h2 className="text-xl pb-2" style={{ borderBottom: "0.5px solid white", textAlign: "center" }}>
Gameplay Tips
</h2>
<div
className="px-4 pt-2 pb-4 mt-4 flex gap-12 flex-col p-6 text-xl text-left"
style={{ backgroundColor: "#160b21", border: "1px solid #0e0715" }}
>
<div className="flex justify-between">
<div>Build stairs for your avatar to climb: </div>

<img
alt=""
src="/htp_climb.png"
style={{ width: "40%", height: "auto" }}
className="border rounded-sm text-center"
/>
</div>
<div className="flex justify-between">
<div>Kill anyone in the way: </div>

<img
alt=""
src="/htp_hit.png"
style={{ width: "80%", height: "auto" }}
className="border rounded-sm text-center"
/>
</div>

<div className="flex justify-between">
<div>Mine blocks below other avatars to drop and hurt them: </div>

<img
alt=""
src="/htp_gravity.png"
style={{ width: "60%", height: "auto" }}
className="border rounded-sm text-center"
/>
</div>

<div>Double tap your space key to fly and more easily direct your onchain avatar!</div>
</div>
</div>
</div>
</div>
);
Expand Down
Loading