// File: contracts/AdventureHub.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "@limit-break/achievements/contracts/IAchievements.sol";
import "limit-break-contracts/contracts/adventures/IAdventure.sol";
import "limit-break-contracts/contracts/adventures/IAdventurousERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

error AdventureKeyAlreadyActivated();
error AdventureKeyIsInactive();
error CallerNotTokenOwner();
error CannotSpecifyZeroAddressForAchievementsToken();
error NoMoreAdventureKeysCanBeActivated();
error NotAnAdventurousContract();
error NotAnERC721Contract();
error OnQuestEnteredCallbackTriggeredByAddressThatIsNotAnActiveAdventureKey();
error UnknownAdventureKey();

/**
 * @title AdventureHub
 * @author Limit Break, Inc.
 * @notice An Adventure that is compatible with all adventure keys, unlocking crossover events with any partner game.
 */
contract AdventureHub is Ownable, ERC165, IAdventure {
    
    struct KeyState {
        bool isKeyActive;
        uint32 questId;
        uint256 achievementId;
    }

    /// @dev Largest unsigned int 32 bit value
    uint256 private constant MAX_UINT32 = type(uint32).max;

    /// @dev Points to the soulbound achievements token.
    /// Players earn soulbound achievement badges for entering games using their adventure keys.
    IAchievements public immutable achievementsToken;

    /// @dev The quest id of the adventure key that was most recently activated for the first time.
    uint32 public lastQuestId;

    /// @dev Maps an adventure key contract address to its key state (activation status and associated quest id)
    mapping (address => KeyState) public adventureKeyStates;

    /// @dev Maps a quest id to the adventure key contract address it is bound to
    /// If needed, off-chain applications can enumerate over this mapping using `lastQuestId` as the upper bound
    mapping (uint32 => address) public adventureKeyQuestIds;

    /// @dev Emitted whenever an adventure key is activated or deactivated
    event AdventureKeyActivationChanged(address indexed adventureKeyAddress, uint256 questId, uint256 achievementId, bool isActive);

    constructor(address achievementsTokenAddress) {
        if(achievementsTokenAddress == address(0)) {
            revert CannotSpecifyZeroAddressForAchievementsToken();
        }

        achievementsToken = IAchievements(achievementsTokenAddress);
    }

    /// @notice Activates an adventure key for use with the Adventure Hub.
    /// Throws when the caller is not the owner of this contract.
    /// Throws when the number of previously activated adventure keys is equal to the maximum uint32 value.
    /// Throws when the specified adventure key contract does not implement the IAdventurous interface.
    /// Throws when the specified adventure key contract is already activated for use in the Adventure Hub.
    /// Throws if AdventureHub MINTER_ROLE access is revoked on the achievements contract
    ///
    /// Postconditions:
    /// The specified adventure key contract has been activated.  New entries into quests with adventure key are permitted.
    /// `adventureKeyStates` mapping has been updated.
    /// `lastQuestId` value has been incremented.
    /// An achievement id has been reserved for users that enter the quest with the activated key.
    ///
    /// @dev The metadataURI parameter has no effect for re-activations of adventure keys.
    /// If a key has already been activated, it is recommended to leave metadataURI blank.
    function activateAdventureKey(address adventureKeyAddress, string calldata metadataURI) external onlyOwner {
        if(!IERC165(adventureKeyAddress).supportsInterface(type(IAdventurous).interfaceId)) {
            revert NotAnAdventurousContract();
        }

        if(!IERC165(adventureKeyAddress).supportsInterface(type(IERC721).interfaceId)) {
            revert NotAnERC721Contract();
        }

        KeyState memory keyState = adventureKeyStates[adventureKeyAddress];

        if(keyState.questId == 0) {
            if(lastQuestId == MAX_UINT32) {
                revert NoMoreAdventureKeysCanBeActivated();
            }

            unchecked {
                uint32 questId = ++lastQuestId;
                adventureKeyStates[adventureKeyAddress].questId = questId;
                adventureKeyQuestIds[questId] = adventureKeyAddress;
            }

            adventureKeyStates[adventureKeyAddress].achievementId = achievementsToken.reserveAchievementId(metadataURI);
        } else if(keyState.isKeyActive) {
            revert AdventureKeyAlreadyActivated();
        }

        adventureKeyStates[adventureKeyAddress].isKeyActive = true;

        emit AdventureKeyActivationChanged(adventureKeyAddress, adventureKeyStates[adventureKeyAddress].questId, adventureKeyStates[adventureKeyAddress].achievementId, true);
    }

    /// @notice Deactivates an adventure key, preventing new entries into quests using the deactivated key.
    /// Players may still exit after the key is deactivated.
    /// Throws when the caller is not the owner of this contract.
    /// Throws when the adventure key address has never been activated, or is currently de-activated.
    ///
    /// Postconditions:
    /// The specified adventure key contract has been de-activated.  New entries into quests with the de-activated
    /// key will be disabled unless the key is re-activated.
    function deactivateAdventureKey(address adventureKeyAddress) external onlyOwner {
        KeyState memory keyState = adventureKeyStates[adventureKeyAddress];

        if(!keyState.isKeyActive) {
            revert AdventureKeyIsInactive();
        }

        adventureKeyStates[adventureKeyAddress].isKeyActive = false;

        emit AdventureKeyActivationChanged(adventureKeyAddress, keyState.questId, keyState.achievementId, false);
    }

    /// @notice Enters the quest associated with the specified adventure key contract for the specified token id.
    /// Throws when the owner of the adventure key is not the caller.
    /// Throws when the adventure key address has never been activated, or is currently de-activated.
    /// Throws when the AdventureHub is not currently whitelisted on the specified adventure key.
    /// Throws when the AdventureHub has not been approved by user for adventures on the specified adventure key.
    /// Throws when the specified token id on the specified adventure key is already in the quest.
    ///
    /// Postconditions:
    /// The specified token id has entered the quest associated with the specified adventure key.
    function enterQuestWithAdventureKey(address adventureKeyAddress, uint256 tokenId) external {
        _requireCallerOwnsToken(adventureKeyAddress, tokenId);

        KeyState storage keyState = adventureKeyStates[adventureKeyAddress];

        if(!keyState.isKeyActive) {
            revert AdventureKeyIsInactive();
        }

        IAdventurous(adventureKeyAddress).enterQuest(tokenId, keyState.questId);
    }

    /// @notice Exits the quest associated with the specified adventure key contract for the specified token id.
    /// Throws when the owner of the adventure key is not the caller.
    /// Throws when the adventure key address has never been activated.
    /// Throws when the AdventureHub is not currently whitelisted on the specified adventure key.
    /// Throws when the AdventureHub has not been approved by user for adventures on the specified adventure key.
    /// Throws when the specified token id on the specified adventure key is no longer in the quest.
    /// - This condition should be rare, and can only happen if the Adventure Hub is removed from the whitelist
    ///   and re-whitelisted, presenting a limited window of opportunity to backdoor userExitQuest.
    ///
    /// Postconditions:
    /// The specified token id has exited from the quest associated with the specified adventure key.
    function exitQuestWithAdventureKey(address adventureKeyAddress, uint256 tokenId) external {
        _requireCallerOwnsToken(adventureKeyAddress, tokenId);

        KeyState storage keyState = adventureKeyStates[adventureKeyAddress];

        if(keyState.questId == 0) {
            revert UnknownAdventureKey();
        }

        IAdventurous(adventureKeyAddress).exitQuest(tokenId, keyState.questId);
    }

    /// @dev Callback that mints a soulbound achievement to the adventurer that entered the quest if they haven't received the achievement previously.
    function onQuestEntered(address adventurer, uint256 /*tokenId*/, uint256 /*questId*/) external override {
        KeyState storage senderKeyState = adventureKeyStates[_msgSender()];

        if(!senderKeyState.isKeyActive) {
            revert OnQuestEnteredCallbackTriggeredByAddressThatIsNotAnActiveAdventureKey();
        }

        if(achievementsToken.balanceOf(adventurer, senderKeyState.achievementId) == 0) {
            achievementsToken.mint(adventurer, senderKeyState.achievementId, 1);
        }
    }

    /// @dev onQuestExited callback does nothing for this contract, because there is no state to synchronize
    function onQuestExited(address /*adventurer*/, uint256 /*tokenId*/, uint256 /*questId*/, uint256 /*questStartTimestamp*/) external override view {}

    /// @dev Enumerates all tokens that the specified player currently has entered into the quest.
    /// Never use this function in a transaction context - it is fine for a read-only query for 
    /// external applications, but will consume a lot of gas when used in a transaction.
    /// Throws if specified adventure key address has never been activated.
    function findQuestingTokensByAdventureKeyAndPlayer(address adventureKeyAddress, address player, uint256 tokenIdPageStart, uint256 tokenIdPageEnd) external view returns (uint256[] memory tokenIdsInQuest) {
        uint256 questId = adventureKeyStates[adventureKeyAddress].questId;

        if(questId == 0) {
            revert UnknownAdventureKey();
        }

        IAdventurousERC721 adventureKey = IAdventurousERC721(adventureKeyAddress);
        
        unchecked {
            // First, find all the token ids owned by the player
            uint256 ownerBalance = adventureKey.balanceOf(player);
            uint256[] memory ownedTokenIds = new uint256[](ownerBalance);
            uint256 tokenIndex = 0;
            for(uint256 tokenId = tokenIdPageStart; tokenId <= tokenIdPageEnd; ++tokenId) {
                try adventureKey.ownerOf(tokenId) returns (address ownerOfToken) {
                    if(ownerOfToken == player) {
                        ownedTokenIds[tokenIndex++] = tokenId;
                    }
                } catch {}
                
                if(tokenIndex == ownerBalance || tokenId == type(uint256).max) {
                    break;
                }
            }

            // For each owned token id, check the quest count
            // When 1 or greater, the spirit is engaged in a quest on this adventure.
            address thisAddress = address(this);
            uint256 numberOfTokenIdsOnQuest = 0;
            for(uint256 i = 0; i < ownerBalance; ++i) {
                uint256 ownedTokenId = ownedTokenIds[i];
                
                if(ownedTokenId > 0) {
                    (bool isPartipatingInQuest,,) = adventureKey.isParticipatingInQuest(ownedTokenId, thisAddress, questId);
                    if(isPartipatingInQuest) {
                        ++numberOfTokenIdsOnQuest;
                    }
                }
            }

            // Finally, make one more pass and populate the player quests return array
            uint256 questIndex = 0;
            tokenIdsInQuest = new uint256[](numberOfTokenIdsOnQuest);
    
            for(uint256 i = 0; i < ownerBalance; ++i) {
                uint256 ownedTokenId = ownedTokenIds[i];
                if(ownedTokenId > 0) {
                    (bool isPartipatingInQuest,,) = adventureKey.isParticipatingInQuest(ownedTokenId, thisAddress, questId);
                    if(isPartipatingInQuest) {
                        tokenIdsInQuest[questIndex] = ownedTokenId;
                        ++questIndex;
                    }
                }
    
                if(questIndex == numberOfTokenIdsOnQuest) {
                    break;
                }
            }
        }

        return tokenIdsInQuest;
    }

    /// @dev Adventure Keys are always locked/non-transferrable while they are participating in Adventure Hub quests
    function questsLockTokens() external override pure returns (bool) {
        return true;
    }

    /// @dev ERC-165 interface support
    function supportsInterface(bytes4 interfaceId) public view virtual override (ERC165, IERC165) returns (bool) {
        return interfaceId == type(IAdventure).interfaceId || super.supportsInterface(interfaceId);
    }

    /// @dev Validates that the caller owns the specified token for the specified token contract address
    /// Throws when the caller does not own the specified token.
    function _requireCallerOwnsToken(address adventureKeyAddress, uint256 tokenId) internal view {
        if(IERC721(adventureKeyAddress).ownerOf(tokenId) != _msgSender()) {
            revert CallerNotTokenOwner();
        }
    }
}
// File: limit-break-contracts/contracts/adventures/IAdventure.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/**
 * @title IAdventure
 * @author Limit Break, Inc.
 * @notice The base interface that all `Adventure` contracts must conform to.
 * @dev All contracts that implement the adventure/quest system and interact with an {IAdventurous} token are required to implement this interface.
 */
interface IAdventure is IERC165 {

    /**
     * @dev Returns whether or not quests on this adventure lock tokens.
     * Developers of adventure contract should ensure that this is immutable 
     * after deployment of the adventure contract.  Failure to do so
     * can lead to error that deadlock token transfers.
     */
    function questsLockTokens() external view returns (bool);

    /**
     * @dev A callback function that AdventureERC721 must invoke when a quest has been successfully entered.
     * Throws if the caller is not an expected AdventureERC721 contract designed to work with the Adventure.
     * Not permitted to throw in any other case, as this could lead to tokens being locked in quests.
     */
    function onQuestEntered(address adventurer, uint256 tokenId, uint256 questId) external;

    /**
     * @dev A callback function that AdventureERC721 must invoke when a quest has been successfully exited.
     * Throws if the caller is not an expected AdventureERC721 contract designed to work with the Adventure.
     * Not permitted to throw in any other case, as this could lead to tokens being locked in quests.
     */
    function onQuestExited(address adventurer, uint256 tokenId, uint256 questId, uint256 questStartTimestamp) external;
}

// File: limit-break-contracts/contracts/adventures/IAdventurousERC721.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "./IAdventurous.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/**
 * @title IAdventurousERC721
 * @author Limit Break, Inc.
 * @notice Combines all {IAdventurous} and all {IERC721} functionality into a single, unified interface.
 * @dev This interface may be used as a convenience to interact with tokens that support both interface standards.
 */
interface IAdventurousERC721 is IERC721, IAdventurous {

}
// File: @limit-break/achievements/contracts/IAchievements.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";

/**
 * @title IAchievements
 * @author Limit Break, Inc.
 * @notice Interface for the Achievements token contract
 */
interface IAchievements is IERC1155MetadataURI {
 
    /// @dev Reserves an achievement id and associates the achievement id with a single allowed minter.
    function reserveAchievementId(string calldata metadataURI) external returns (uint256);

    /// @dev Mints an achievement of type `id` to the `to` address.
    function mint(address to, uint256 id, uint256 amount) external;

    /// @dev Batch mints achievements to the `to` address.
    function mintBatch(address to, uint256[] calldata ids, uint256[] calldata amounts) external;
}
// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/token/ERC721/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// File: @openzeppelin/contracts/utils/introspection/ERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: limit-break-contracts/contracts/adventures/IAdventurous.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "./Quest.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/**
 * @title IAdventurous
 * @author Limit Break, Inc.
 * @notice The base interface that all `Adventurous` token contracts must conform to in order to support adventures and quests.
 * @dev All contracts that support adventures and quests are required to implement this interface.
 */
interface IAdventurous is IERC165 {

    /**
     * @dev Emitted when a token enters or exits a quest
     */
    event QuestUpdated(uint256 indexed tokenId, address indexed tokenOwner, address indexed adventure, uint256 questId, bool active, bool booted);

    /**
     * @notice Transfers a player's token if they have opted into an authorized, whitelisted adventure.
     */
    function adventureTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @notice Safe transfers a player's token if they have opted into an authorized, whitelisted adventure.
     */
    function adventureSafeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @notice Burns a player's token if they have opted into an authorized, whitelisted adventure.
     */
    function adventureBurn(uint256 tokenId) external;

    /**
     * @notice Enters a player's token into a quest if they have opted into an authorized, whitelisted adventure.
     */
    function enterQuest(uint256 tokenId, uint256 questId) external;

    /**
     * @notice Exits a player's token from a quest if they have opted into an authorized, whitelisted adventure.
     */
    function exitQuest(uint256 tokenId, uint256 questId) external;

    /**
     * @notice Returns the number of quests a token is actively participating in for a specified adventure
     */
    function getQuestCount(uint256 tokenId, address adventure) external view returns (uint256);

    /**
     * @notice Returns the amount of time a token has been participating in the specified quest
     */
    function getTimeOnQuest(uint256 tokenId, address adventure, uint256 questId) external view returns (uint256);

    /**
     * @notice Returns whether or not a token is currently participating in the specified quest as well as the time it was started and the quest index
     */
    function isParticipatingInQuest(uint256 tokenId, address adventure, uint256 questId) external view returns (bool participatingInQuest, uint256 startTimestamp, uint256 index);

    /**
     * @notice Returns a list of all active quests for the specified token id and adventure
     */
    function getActiveQuests(uint256 tokenId, address adventure) external view returns (Quest[] memory activeQuests);
}

// File: limit-break-contracts/contracts/adventures/Quest.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
 * @title Quest
 * @author Limit Break, Inc.
 * @notice Quest data structure for {IAdventurous} contracts.
 */
struct Quest {
    bool isActive;
    uint32 questId;
    uint64 startTimestamp;
    uint32 arrayIndex;
}
// File: @openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)

pragma solidity ^0.8.0;

import "../IERC1155.sol";

/**
 * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
 * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155MetadataURI is IERC1155 {
    /**
     * @dev Returns the URI for token type `id`.
     *
     * If the `\{id\}` substring is present in the URI, it must be replaced by
     * clients with the actual token type ID.
     */
    function uri(uint256 id) external view returns (string memory);
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}