Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example: Location Guard #9

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
200 changes: 174 additions & 26 deletions packages/hardhat/contracts/Experience.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,23 @@ import { NamedArea, NamedBuild, NamedBuildWithPos, weiToString, getEmptyBlockOnG
contract Experience is ICustomUnregisterDelegation, IOptionalSystemHook {
address public immutable biomeWorldAddress;

address public delegatorAddress;
address public guardAddress;

// Event to show a notification in the Biomes World
event GameNotif(address player, string message);

constructor(address _biomeWorldAddress, address _delegatorAddress) {
VoxelCoord public guardPosition;
VoxelCoord[] public unGuardPositions;
address[] public allowedPlayers;

constructor(address _biomeWorldAddress, address _guardAddress) {
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);

delegatorAddress = _delegatorAddress;
guardAddress = _guardAddress;
}

// Use this modifier to restrict access to the Biomes World contract only
Expand All @@ -59,7 +63,173 @@ contract Experience is ICustomUnregisterDelegation, IOptionalSystemHook {
}

function canUnregister(address delegator) external override onlyBiomeWorld returns (bool) {
return true;
return allowedPlayers.length == 0;
}

function addAllowedPlayer(address player) external {
require(msg.sender == guardAddress, "Only the guard can add players");
for (uint i = 0; i < allowedPlayers.length; i++) {
require(allowedPlayers[i] != player, "Player already allowed");
}
allowedPlayers.push(player);
}

function setGuardPosition(VoxelCoord memory position) external {
require(msg.sender == guardAddress, "Only the guard can set the guard position");
guardPosition = position;
}

function setUnGuardPosition(VoxelCoord[] memory positions) external {
require(msg.sender == guardAddress, "Only the guard can set the guard position");

// Clear the existing storage array
delete unGuardPositions;

// Manually copy each element from the input array to the storage array
for (uint256 i = 0; i < positions.length; i++) {
unGuardPositions.push(VoxelCoord({ x: positions[i].x, y: positions[i].y, z: positions[i].z }));
}
}

function onAfterCallSystem(
address msgSender,
ResourceId systemId,
bytes memory callData
) external override onlyBiomeWorld {
if (isSystemId(systemId, "MoveSystem")) {
if (msgSender == guardAddress) {
return;
}

bytes32 guardEntityId = getEntityFromPlayer(guardAddress);
if (guardEntityId == bytes32(0)) {
return;
}
// check if player is beside the guard
VoxelCoord memory playerCoord = getPosition(getEntityFromPlayer(msgSender));
VoxelCoord memory guardCoord = getPosition(getEntityFromPlayer(guardAddress));
if (voxelCoordsAreEqual(guardCoord, guardPosition)) {
if (inSurroundingCube(playerCoord, 1, guardCoord)) {
bool isAllowed = false;
for (uint i = 0; i < allowedPlayers.length; i++) {
if (allowedPlayers[i] == msgSender) {
isAllowed = true;
break;
}
}
if (!isAllowed) {
return;
}

// Move the guard away from its guarding position
callMove(biomeWorldAddress, guardAddress, unGuardPositions);
emit GameNotif(msgSender, "Guard has moved away from its guarding position");
}
} else {
// move guard back to its guarding position
VoxelCoord[] memory newCoords = new VoxelCoord[](unGuardPositions.length);
for (uint256 i = 0; i < newCoords.length - 1; i++) {
newCoords[i] = unGuardPositions[i];
}
newCoords[unGuardPositions.length - 1] = guardPosition;
callMove(biomeWorldAddress, guardAddress, newCoords);
emit GameNotif(msgSender, "Guard is back at its guarding position");
}
} else if (isSystemId(systemId, "HitSystem")) {
address hitAddress = getHitArgs(callData);
if (msgSender == guardAddress) {
bool isAllowed = false;
for (uint i = 0; i < allowedPlayers.length; i++) {
if (allowedPlayers[i] == hitAddress) {
isAllowed = true;
break;
}
}
require(!isAllowed, "Guard cannot hit allowed players");
} else {
require(hitAddress != guardAddress, "Players cannot hit the guard");
}
}
}

function hitIntruder(address intruder) external {
for (uint i = 0; i < allowedPlayers.length; i++) {
require(allowedPlayers[i] != intruder, "Cannot hit allowed players");
}
callHit(biomeWorldAddress, guardAddress, intruder);
}

function getIntruders() external view returns (address[] memory) {
bytes32 guardEntityId = getEntityFromPlayer(guardAddress);
if (guardEntityId == bytes32(0)) {
return new address[](0);
}
VoxelCoord memory guardCoord = getPosition(guardEntityId);
// Check all possible locations around the guard
address[] memory allIntruders = new address[](26);
uint intrudersCount = 0;
for (int8 dx = -1; dx <= 1; dx++) {
for (int8 dy = -1; dy <= 1; dy++) {
for (int8 dz = -1; dz <= 1; dz++) {
if (dx == 0 && dy == 0 && dz == 0) {
continue;
}
VoxelCoord memory coord = VoxelCoord({ x: guardCoord.x + dx, y: guardCoord.y + dy, z: guardCoord.z + dz });
address player = getPlayerFromEntity(getEntityAtCoord(coord));
if (player != address(0)) {
bool isAllowed = false;
for (uint i = 0; i < allowedPlayers.length; i++) {
if (allowedPlayers[i] == player) {
isAllowed = true;
break;
}
}
if (!isAllowed) {
allIntruders[intrudersCount] = player;
intrudersCount++;
}
}
}
}
}
address[] memory intruders = new address[](intrudersCount);
for (uint i = 0; i < intrudersCount; i++) {
intruders[i] = allIntruders[i];
}

return intruders;
}

function getDisplayName() external view returns (string memory) {
return "Location Guard Service";
}

function getStatus() external view returns (string memory) {
bytes32 guardEntityId = getEntityFromPlayer(guardAddress);
if (guardEntityId == bytes32(0)) {
return "ALERT: Guard is dead!";
}

bool isAllowed = false;
for (uint i = 0; i < allowedPlayers.length; i++) {
if (allowedPlayers[i] == msg.sender) {
isAllowed = true;
break;
}
}
if (isAllowed) {
return "You are allowed to go past the guard";
} else {
return "You are not allowed to go past the guard";
}
}

function getAllowedPlayers() external view returns (address[] memory) {
return allowedPlayers;
}

function getUnguardPositions() external view returns (VoxelCoord[] memory) {
return unGuardPositions;
}

function onRegisterHook(
Expand All @@ -81,26 +251,4 @@ contract Experience is ICustomUnregisterDelegation, IOptionalSystemHook {
ResourceId systemId,
bytes memory callData
) external override onlyBiomeWorld {}

function onAfterCallSystem(
address msgSender,
ResourceId systemId,
bytes memory callData
) external override onlyBiomeWorld {}

function basicGetter() external view returns (uint256) {
return 42;
}

function getRegisteredPlayers() external view returns (address[] memory) {
return new address[](0);
}

function getDisplayName() external view returns (string memory) {
return "Experience";
}

function getStatus() external view returns (string memory) {
return "You are in the Experience";
}
}
2 changes: 1 addition & 1 deletion packages/hardhat/deploy/00_deploy_experience.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const deployExperienceContract: DeployFunction = async function (hre: HardhatRun
await deploy("Experience", {
from: deployer,
// Contract constructor arguments
args: [useBiomesWorldAddress, "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"],
args: [useBiomesWorldAddress, "0xd48125520B141603B4Fbb40B71341436001573e4"],
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
5 changes: 5 additions & 0 deletions packages/nextjs/.firebaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"projects": {
"default": "biomes-guard-location"
}
}
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
64 changes: 33 additions & 31 deletions packages/nextjs/components/Experience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ 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 getAllowedPlayers = viewFunctions.find(({ fn }) => fn.name === "getAllowedPlayers");

if (getAllowedPlayers === undefined) {
return <div>Missing required functions</div>;
}

return (
<div className="flex-1 flex flex-col h-full p-mono">
Expand Down Expand Up @@ -62,44 +66,42 @@ 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">Location Guard Service</h1>
<h1 className="text-left mt-4" style={{ lineHeight: "normal", margin: "0", wordWrap: "break-word" }}>
Your Main Experience Page
Will move when you&apos;re near it, and move back once you&apos;re away from it
</h1>
</div>
<div></div>
<div>
<DisplayVariable
abi={deployedContractData.abi as Abi}
abiFunction={getAllowedPlayers.fn}
contractAddress={deployedContractData.address}
key={"getAllowedPlayers"}
refreshDisplayVariables={refreshDisplayVariables}
inheritedFrom={getAllowedPlayers.inheritedFrom}
poll={2000}
>
{({ result, RefreshButton }) => {
// if (isFetching) return <div>Loading...</div>;

return (
<div style={{ backgroundColor: "#160b21", padding: "16px", border: "1px solid #0e0715" }}>
<div className="flex justify-between mb-4">
<div className="text-lg font-medium">Players Allowed To Move Past Guard</div>
<div className="flex gap-4">{RefreshButton}</div>
</div>
{displayTxResult(result)}
</div>
);
}}
</DisplayVariable>
</div>
</div>
</div>
<div
className="col-span-12 lg:col-span-3 p-12"
style={{ backgroundColor: "#160b21", borderLeft: "1px solid #0e0715" }}
>
{basicGetterFn && (
<DisplayVariable
abi={deployedContractData.abi as Abi}
abiFunction={basicGetterFn.fn}
contractAddress={deployedContractData.address}
key={"getter"}
refreshDisplayVariables={refreshDisplayVariables}
inheritedFrom={basicGetterFn.inheritedFrom}
poll={10000}
>
{({ result, RefreshButton }) => {
return (
<div
className="p-6 text-white text-center border 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>
<div className="text-4xl mt-2">{displayTxResult(result)}</div>
</div>
);
}}
</DisplayVariable>
)}
</div>
></div>
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions packages/nextjs/components/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ export const Landing: React.FC = ({}) => {
<div className="grid grid-cols-12 flex flex-1">
<div className="col-span-12 lg:col-span-9 p-12 flex flex-col justify-between items-center">
<div style={{ width: "80%" }}>
<h1 className="text-3xl font-bold text-left mt-4">Your Experience Title</h1>
<h1 className="text-3xl font-bold text-left mt-4">Location Guard Service</h1>
<h1 className="text-left mt-4" style={{ lineHeight: "normal", margin: "0", wordWrap: "break-word" }}>
Your experience description
Place the guard outside your house to protect it
</h1>
<div
style={{
Expand Down
12 changes: 7 additions & 5 deletions packages/nextjs/components/RegisterBiomes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ExperienceRequiredHooks: string[] = ["LogoffSystem"];
const ExperienceRequiredHooks: string[] = ["MoveSystem", "HitSystem"];

export const RegisterBiomes: React.FC = ({}) => {
const { address: connectedAddress } = useAccount();
Expand All @@ -33,7 +33,7 @@ export const RegisterBiomes: React.FC = ({}) => {
const delegatorAddress = await publicClient.readContract({
address: deployedContractData?.address,
abi: deployedContractData?.abi,
functionName: "delegatorAddress",
functionName: "guardAddress",
args: [],
});
if (delegatorAddress === undefined || delegatorAddress === null || typeof delegatorAddress !== "string") {
Expand All @@ -52,7 +52,7 @@ export const RegisterBiomes: React.FC = ({}) => {

useEffect(() => {
if (deployedContractData) {
const hasDelegatorAddress = deployedContractData?.abi.some(abi => abi.name === "delegatorAddress");
const hasDelegatorAddress = deployedContractData?.abi.some(abi => abi.name === "guardAddress");
if (hasDelegatorAddress) {
checkDelegatorAddress();
} else {
Expand Down Expand Up @@ -87,7 +87,9 @@ export const RegisterBiomes: React.FC = ({}) => {
<h3 className="text-xl font-bold text-left mt-8">HOOKS</h3>
<CardSection
relevantSystems={ExperienceRequiredHooks}
description={"Description of why you need the player to register the hooks on LogoffSystem"}
description={
"When you move in front of the guard, it'll move the guard away, and it'll move it back once you're away from it."
}
>
<RegisterHookButton
hookAddress={contractsData["Experience"].address}
Expand All @@ -99,7 +101,7 @@ export const RegisterBiomes: React.FC = ({}) => {
/>
</CardSection>
</div>
{deployedContractData.abi.some(abi => abi.name === "delegatorAddress") && isDelegatorAddress && (
{deployedContractData.abi.some(abi => abi.name === "guardAddress") && isDelegatorAddress && (
<div className="pt-4">
<h3 className="text-xl font-bold text-left">DELEGATIONS</h3>
<CardSection description={"Delegate unlimited access to the Experience contract"}>
Expand Down
Loading