creation_sourcecode: {"DungeonConfig.sol":{"content":"// SPDX-License-Identifier: MIT\r\n//\r\n// DungeonConfig by DungeonMaster/@DungeonSpawner\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport \"./Ownable.sol\";\r\n\r\ninterface IDungeon {\r\n    function ownerOf(uint256 tokenId) external view returns (address owner);\r\n}\r\n\r\ninterface IDungeonRewards {\r\n    function getStakedTokens(\r\n        address owner\r\n    )\r\n        external\r\n        view\r\n        returns (uint256[] memory dungeons, uint256[] memory avatars);\r\n}\r\n\r\ncontract DungeonConfig is Ownable {\r\n    /*///////////////////////////////////////////////////////////////\r\n                                 EVENTS\r\n    //////////////////////////////////////////////////////////////*/\r\n    event AssignPerms(\r\n        address indexed from,\r\n        address indexed to,\r\n        uint256 indexed tokenId\r\n    );\r\n    event AssignGlobalPerms(address indexed from, address indexed to);\r\n    event RevokeGlobalPerms(address indexed from, address indexed to);\r\n\r\n    /*///////////////////////////////////////////////////////////////\r\n                          METADATA STORAGE/LOGIC\r\n    //////////////////////////////////////////////////////////////*/\r\n    address public dungeonAddress;\r\n    address public dungeonRewardsAddress;\r\n\r\n    struct Permission {\r\n        address grantedBy; // The address that granted the permission\r\n        address grantedTo; // The address that received the permission\r\n    }\r\n    struct GlobalPermission {\r\n        address grantedTo;\r\n        address grantedBy;\r\n    }\r\n\r\n    struct FloorData {\r\n        string txId; // transaction ID for the floor data\r\n        uint256 version; // current version of the floor data (every resetAllFloors increments the version #, outdating previous entries)\r\n    }\r\n\r\n    struct DungeonData {\r\n        bool locked;\r\n        uint256 trialTimeout;\r\n        bool randomizeLayout; // randomize layout indicates each floor after floor 1 has a random layout/environment\r\n        string passwordHash; // DungeonMaster notes: not really secure, of course, since it\u0027s publically available, but the best we have\r\n        string ownersMessage;\r\n        string gameMode; // space theme, 3d, leaderboard, etc... and if starts with https:, NFT will redirect to url after loading up (use for streaming via rumble, etc)\r\n        string tilesetOverride; // override tileset with this txid/url allowing owners further customization\r\n    }\r\n\r\n    // Mapping that tells us which version of the dungeon data we\u0027re dealing with \r\n    // (reseting all floors increments the version # -- saves us gas from having to clear all the existing dungeon floor data)\r\n    mapping(uint256 =\u003e uint256) private tokenVersions;\r\n\r\n    mapping(uint256 =\u003e DungeonData) public dungeons;\r\n    // Mapping from tokenId to a mapping of floorNumber to FloorData\r\n    mapping(uint256 =\u003e mapping(uint256 =\u003e FloorData)) public dungeonFloors;\r\n\r\n    mapping(uint256 =\u003e Permission) public tokenPermissions;\r\n    // Mapping to store global permissions\r\n    mapping(address =\u003e GlobalPermission) public globalPermissions;\r\n\r\n    string public BASE_CODE_TXID = \"\"; // base code txid\r\n\r\n    constructor(address _dungeonAddress, address _dungeonRewardsAddress) {\r\n        dungeonAddress = _dungeonAddress;\r\n        dungeonRewardsAddress = _dungeonRewardsAddress;\r\n    }\r\n\r\n    modifier onlyOwnerOrStaker(uint256 tokenId) {\r\n        IDungeon dungeon = IDungeon(dungeonAddress);\r\n        address owner = dungeon.ownerOf(tokenId);\r\n        bool isStaker = owner == dungeonRewardsAddress \u0026\u0026\r\n            isTokenStakedByAddress(tokenId, msg.sender);\r\n\r\n        require(\r\n            owner == msg.sender || isStaker,\r\n            \"Caller is not owner or staker\"\r\n        );\r\n        _;\r\n    }\r\n\r\n    function isTokenStakedByAddress(\r\n        uint256 tokenId,\r\n        address addressToCheck\r\n    ) private view returns (bool) {\r\n        IDungeonRewards rewards = IDungeonRewards(dungeonRewardsAddress);\r\n        (uint256[] memory stakedDungeons, ) = rewards.getStakedTokens(\r\n            addressToCheck\r\n        );\r\n        for (uint i = 0; i \u003c stakedDungeons.length; i++) {\r\n            if (stakedDungeons[i] == tokenId) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    modifier isPermitted(uint256 tokenId) {\r\n        IDungeon dungeon = IDungeon(dungeonAddress);\r\n        address owner = dungeon.ownerOf(tokenId);\r\n\r\n        // First, check if msg.sender is the owner. If true, no further checks needed.\r\n        if (\r\n            owner == msg.sender ||\r\n            (owner == dungeonRewardsAddress \u0026\u0026\r\n                isTokenStakedByAddress(tokenId, msg.sender))\r\n        ) {\r\n            _;\r\n            return;\r\n        }\r\n\r\n        // Global Permissions Check\r\n        GlobalPermission memory globalPerm = globalPermissions[msg.sender];\r\n        if (globalPerm.grantedTo == msg.sender) {\r\n            bool isGlobalGrantorOwnerOrStaker = globalPerm.grantedBy == owner ||\r\n                (owner == dungeonRewardsAddress \u0026\u0026\r\n                    isTokenStakedByAddress(tokenId, globalPerm.grantedBy));\r\n\r\n            if (isGlobalGrantorOwnerOrStaker) {\r\n                _;\r\n                return;\r\n            }\r\n        }\r\n\r\n        // Only proceed to check permissions if the sender is not the owner or a staker.\r\n        Permission memory permission = tokenPermissions[tokenId];\r\n        bool hasValidPermission = permission.grantedTo == msg.sender \u0026\u0026\r\n            (permission.grantedBy == owner ||\r\n                (owner == dungeonRewardsAddress \u0026\u0026\r\n                    isTokenStakedByAddress(tokenId, permission.grantedBy)));\r\n\r\n        require(hasValidPermission, \"Not authorized\");\r\n\r\n        _;\r\n    }\r\n\r\n    function writeDungeonConfig(\r\n        uint256 tokenId,\r\n        DungeonData memory configData\r\n    ) public isPermitted(tokenId) {\r\n        DungeonData storage dungeon = dungeons[tokenId];\r\n        dungeon.locked = configData.locked;\r\n        dungeon.trialTimeout = configData.trialTimeout;\r\n        dungeon.randomizeLayout = configData.randomizeLayout;\r\n        dungeon.passwordHash = configData.passwordHash;\r\n        dungeon.ownersMessage = configData.ownersMessage;\r\n        dungeon.gameMode = configData.gameMode;\r\n        dungeon.tilesetOverride = configData.tilesetOverride;\r\n    }\r\n\r\n    // same as above, but without tilesetOverride - costs about half the gas\r\n    function writeDungeonConfig(\r\n        uint256 tokenId,\r\n        bool locked,\r\n        uint256 trialTimeout,\r\n        bool randomizeLayout,\r\n        string memory passwordHash,\r\n        string memory ownersMessage,\r\n        string memory gameMode\r\n    ) public isPermitted(tokenId) {\r\n        DungeonData storage dungeon = dungeons[tokenId];\r\n        dungeon.locked = locked;\r\n        dungeon.trialTimeout = trialTimeout;\r\n        dungeon.randomizeLayout = randomizeLayout;\r\n        dungeon.passwordHash = passwordHash;\r\n        dungeon.ownersMessage = ownersMessage;\r\n        dungeon.gameMode = gameMode;\r\n    }\r\n\r\n    function readDungeonConfig(\r\n        uint256 tokenId\r\n    )\r\n        public\r\n        view\r\n        returns (\r\n            bool locked,\r\n            uint256 trialTimeout,\r\n            bool randomizeLayout,\r\n            string memory passwordHash,\r\n            string memory ownersMessage,\r\n            string memory gameMode,\r\n            string memory tilesetOverride,\r\n            string memory codeTxId\r\n        )\r\n    {\r\n        DungeonData memory dungeon = dungeons[tokenId];\r\n\r\n        // could directly assing the fields from the dungeon struct to construct the return values as per the\r\n        // 2nd version of this function, but it\u0027s just as gas efficient to wrap it in a return like this\r\n        return (\r\n            dungeon.locked,\r\n            dungeon.trialTimeout,\r\n            dungeon.randomizeLayout,\r\n            dungeon.passwordHash,\r\n            dungeon.ownersMessage,\r\n            dungeon.gameMode,\r\n            dungeon.tilesetOverride,\r\n            BASE_CODE_TXID\r\n        );\r\n    }\r\n\r\n    // read Dungeon Config with custom floors\r\n    function readDungeonConfig(\r\n        uint256 tokenId, uint256 maxFloorNumber\r\n    )\r\n        public\r\n        view\r\n        returns (\r\n            bool locked,\r\n            uint256 trialTimeout,\r\n            string memory passwordHash,\r\n            string memory ownersMessage,\r\n            string memory gameMode,\r\n            string memory tilesetOverride,\r\n            string memory codeTxId,\r\n            uint256[] memory customFloors\r\n        )\r\n    {\r\n        DungeonData memory dungeon = dungeons[tokenId];\r\n        uint256 currentVersion = tokenVersions[tokenId];\r\n\r\n        // Directly use the fields from the dungeon struct to construct the return values\r\n        locked = dungeon.locked;\r\n        trialTimeout = dungeon.trialTimeout;\r\n        passwordHash = dungeon.passwordHash;\r\n        ownersMessage = dungeon.ownersMessage;\r\n        gameMode = dungeon.gameMode;\r\n        tilesetOverride = dungeon.tilesetOverride;\r\n        codeTxId = BASE_CODE_TXID;\r\n\r\n        // Use the helper function to get customFloors - do it this way to avoid CompilerError: Stack too deep\r\n        customFloors = getCustomFloors(tokenId, maxFloorNumber, currentVersion);\r\n    }\r\n\r\n    // collect customFloors data\r\n    function getCustomFloors(uint256 tokenId, uint256 maxFloorNumber, uint256 currentVersion)\r\n        internal\r\n        view\r\n        returns (uint256[] memory customFloors)\r\n    {\r\n        uint256[] memory tempFloors = new uint256[](maxFloorNumber);\r\n        uint256 count = 0;\r\n\r\n        for (uint256 floor = 0; floor \u003c= maxFloorNumber; floor++) {\r\n            if (dungeonFloors[tokenId][floor].version == currentVersion \u0026\u0026\r\n                bytes(dungeonFloors[tokenId][floor].txId).length \u003e 0) {\r\n                tempFloors[count] = floor;\r\n                count++;\r\n            }\r\n        }\r\n\r\n        customFloors = new uint256[](count);\r\n        for (uint256 i = 0; i \u003c count; i++) {\r\n            customFloors[i] = tempFloors[i];\r\n        }\r\n    }\r\n\r\n    function writeFloorData(\r\n        uint256 tokenId,\r\n        uint256 floorNumber,\r\n        string memory txId\r\n    ) public isPermitted(tokenId) {\r\n        // Store the transaction ID for the specific floor\r\n        dungeonFloors[tokenId][floorNumber] = FloorData(txId, tokenVersions[tokenId]);\r\n    }\r\n\r\n    function readFloorData(\r\n        uint256 tokenId,\r\n        uint256 floorNumber\r\n    ) public view returns (string memory) {\r\n         FloorData memory floorData = dungeonFloors[tokenId][floorNumber];\r\n        // Return the transaction ID for the specific floor\r\n        if(floorData.version == tokenVersions[tokenId]) {\r\n            return floorData.txId;\r\n        } else {\r\n            return \"\"; // Indicates no data for the current version\r\n        }        \r\n    }\r\n\r\n    function resetAllFloors(uint256 tokenId) public isPermitted(tokenId) { \r\n        tokenVersions[tokenId] += 1; // Increment the version to \"reset\" the data\r\n    }\r\n\r\n    function assignPermission(\r\n        uint256 tokenId,\r\n        address to\r\n    ) public onlyOwnerOrStaker(tokenId) {\r\n        tokenPermissions[tokenId] = Permission(msg.sender, to);\r\n\r\n        emit AssignPerms(msg.sender, to, tokenId);\r\n    }\r\n\r\n    // Assign global permission\r\n    function assignGlobalPermission(address to) public {\r\n        globalPermissions[to] = GlobalPermission({\r\n            grantedTo: to,\r\n            grantedBy: msg.sender\r\n        });\r\n\r\n        emit AssignGlobalPerms(msg.sender, to);\r\n    }\r\n\r\n    // Optional: Function to revoke global permission\r\n    function revokeGlobalPermission(address to) public {\r\n        require(\r\n            globalPermissions[to].grantedBy == msg.sender,\r\n            \"Not authorized to revoke\"\r\n        );\r\n        delete globalPermissions[to];\r\n        emit RevokeGlobalPerms(msg.sender, to);\r\n    }\r\n\r\n    // set the ethscription txid of the base codebase\r\n    function setBaseCodeTxid(string memory baseCodeTxid) public onlyOwner {\r\n        BASE_CODE_TXID = baseCodeTxid;\r\n    }\r\n\r\n    // read the ethscription txid of the base codebase\r\n    function getBaseCodeTxid() public view returns (string memory) {\r\n        return BASE_CODE_TXID;\r\n    }\r\n\r\n}\r\n"},"Ownable.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-only\r\npragma solidity ^0.8.10;\r\n\r\nerror NotOwner();\r\n\r\n// https://github.com/m1guelpf/erc721-drop/blob/main/src/LilOwnable.sol\r\nabstract contract Ownable {\r\n    address internal _owner;\r\n\r\n    event OwnershipTransferred(\r\n        address indexed previousOwner,\r\n        address indexed newOwner\r\n    );\r\n\r\n    modifier onlyOwner() {\r\n        require(_owner == msg.sender);\r\n        _;\r\n    }\r\n\r\n    constructor() {\r\n        _owner = msg.sender;\r\n    }\r\n\r\n    function owner() external view returns (address) {\r\n        return _owner;\r\n    }\r\n\r\n    function transferOwnership(address _newOwner) external {\r\n        if (msg.sender != _owner) revert NotOwner();\r\n\r\n        _owner = _newOwner;\r\n    }\r\n\r\n    function renounceOwnership() public {\r\n        if (msg.sender != _owner) revert NotOwner();\r\n\r\n        _owner = address(0);\r\n    }\r\n\r\n    function supportsInterface(bytes4 interfaceId)\r\n        public\r\n        pure\r\n        virtual\r\n        returns (bool)\r\n    {\r\n        return interfaceId == 0x7f5828d0; // ERC165 Interface ID for ERC173\r\n    }\r\n}\r\n"}}