// Contract: MythicGateway
// Target function: function storeLiquidKey(uint256 tokenId)
 }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value)
        internal
        returns (bool)
    {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value)
        internal
        view
        returns (bool)
    {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index)
        internal
        view
        returns (uint256)
    {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set)
        internal
        view
        returns (uint256[] memory)
    {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        assembly {
            result := store
        }

        return result;
    }
}

pragma solidity ^0.8.0;

/**
 * @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;
}

pragma solidity ^0.8.0;

/* Mythic Gateways and the Mythic Journey [an overview]

   Turn-based RPG which does at least the following:

   1. Requires a Liquid Key to open a Mythic Gateway.
      The Liquid Key must be added to the Mythic Gateway contract.

   2. Requires the user to select an NFT from one of the pre-approved contracts,
      such as Liquid Legends, Liquid Invaders, Azuki Ape Social Club, or
      Typical Tigers.

      a) If a Typical Tiger is selected, also requires a Baby Cub Tiger
         to be permanently stored inside the contract

      - Writes the Token ID of the Liquid Key + the contract of the NFT
        being used along with the Token ID of the NFT being used.

   3. Requires a Mythic Artifact to be burned during the initial setup
      of the Mythic Gateway.

   4. We use the ETH blockrate to determine how many points are gained during
      the time the gateway is open (the NFT is on a "Journey"). Points accrue
      at a specific rate per Journey.

   5. During the Journey, the NFT randomly accumulates items, which are dynamic,
      based on a list of contracts (also dynamic) and periodically the user can
      interact with the contract and claim any item which was randomly
      acquired on the Journey.

   *  Other stuff is proprietary, so... go fack.

 */

// Burn an artifact and open the Gateway
interface burnArtifactOpenGateway {
    function openGateway(address tokenOwner, uint256 tokenId)
        external
        returns (bool);
}

pragma solidity ^0.8.14;

// See notes above for details... this contract is pretty intense
contract MythicGateway is Ownable, ReentrancyGuard, Pausable {
    using EnumerableSet for EnumerableSet.UintSet;
    using Strings for uint256;

    // Setup the contracts for the pieces in the Journey
    address public Legends = 0x372405A6d95628Ad14518BfE05165D397f43dE1D;
    address public Invaders = 0x2f3A9adc5301600Cd9205eF7657cF0733fF71D04;
    address public Tigers = 0xf4744Ec5D846F7f1a0c5d389F590Cc1344eD3fCf;
    address public AzukiApes = 0x813b5c4aE6b188F4581aa1dfdB7f4Aba44AA578B;
    address public LiquidKeys = 0x753412F4FB7245BCF1c0714fDf59ba89110f39b8;
    address public Artifacts = 0x753412F4FB7245BCF1c0714fDf59ba89110f39b8;

    // Setup the Liquid Key Journey (one address and many possible keys)
    // Define a struct to hold the key, ID, and token ID values
    struct Journeys {
        uint256 keyTokenId;
        address nftAddress;
        uint256 nftTokenId;
        uint256 artifactId;
        string mythicWorld;
        uint256 startTime;
    }

    // Define a mapping where the key is an address and the value
    // is an array of the struct we defined earlier
    mapping(address => Journeys[]) public activeJourneys;

    // Setup the contracts that will controll or be controlled by the game
    address private gateway = 0x866cfDa1B7cD90Cd250485cd8b700211480845D7;
    address private weapons = 0x866cfDa1B7cD90Cd250485cd8b700211480845D7;
    address private enhancements = 0x866cfDa1B7cD90Cd250485cd8b700211480845D7;
    address private overseer = 0x866cfDa1B7cD90Cd250485cd8b700211480845D7;

    // METL Token
    address public METLToken = 0xFcbE615dEf610E806BB64427574A2c5c1fB55510;

    // Specify the IERC20 interface to the METL Token
    IERC20 METLTokenAddress = IERC20(address(METLToken));

    // Setup the blocks, the rates, and how we know Journey time
    uint256 JourneyBlockRate = 222208; // 30 days
    uint256 JourneyDayRate = 7450; // 1 day

    // Setup the probability of finding anything
    // NOTE: This should be done in the sub-contracts, so we
    //       do not paint ourselves into a forever corner.
    //
    uint256 private probability = 2; // 100* (1-(tokenId/(tokenId+2)))
    uint256 private drop_chance = 64; // 100*(tokenId/64)
    uint256 private changeNameCost = 10; // 10 METL

    // ------ NFT Names -------------------------------------------------------------------
    // Define a struct to represent an NFT and the Name of that NFT
    struct NFT {
        uint256 tokenId;
        string name;
    }

    // Define a mapping where the key is the NFT contract address and the value is an array of NFTs
    mapping(address => NFT[]) public nfts;

    // ----- PAUSE and UNPAUSE ----------------------------------------------------------------
    // Pause and unpause the contract, just in case
    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function addJourney(
        address addr,
        uint256 keyTokenId,
        address nftAddress,
        uint256 nftTokenId,
        uint256 artifactId,
        string memory mythicWorld
    ) public whenNotPaused nonReentrant {
        // require the sender be the address
        require(addr == msg.sender, "You are not the sender, go fck");

        // Create a new struct containing the values we want to store in the array
        Journeys memory journey = Journeys(
            keyTokenId,
            nftAddress,
            nftTokenId,
            artifactId,
            mythicWorld,
            block.timestamp
        );

        // Add the struct to the array in the mapping using the address as the key
        activeJourneys[addr].push(journey);
    }

    function removeJourney(address addr, uint256 oldKeyTokenId)
        public
        whenNotPaused
        nonReentrant
    {
        // require the sender be the address
        require(addr == msg.sender, "You are not the sender, go fck");

        // Retrieve the array of journeys associated with the given address
        Journeys[] memory addrJourneys = activeJourneys[addr];

        // Iterate through the journeys in the current array
        for (uint256 i = 0; i < addrJourneys.length; i++) {
            // Check if the current journey has the desired old keyTokenId
            if (addrJourneys[i].keyTokenId == oldKeyTokenId) {
                // Replace the current journey with the new entry
                addrJourneys[i].nftAddress = address(0);
                addrJourneys[i].keyTokenId = 0;
                addrJourneys[i].startTime = 0;
                addrJourneys[i].mythicWorld = "";

                // Return from the function after the item has been replaced
                return;
            }
        }
    }

    function getJourneyByIndex(address addr, uint256 index)
        public
        view
        returns (Journeys memory)
    {
        // Retrieve the array of journeys associated with the given address
        Journeys[] memory journeys = activeJourneys[addr];

        // Check that the index is valid (i.e. less than the length of the journeys array)
        require(index < journeys.length, "Invalid index");

        // Return the journey at the specified index
        return journeys[index];
    }

    function getJourneyTokenAddressIdAndArtifact(
        address addr,
        uint256 keyTokenId
    )
        public
        view
        returns (
            address,
            uint256,
            string memory,
            uint256,
            uint256
        )
    {
        // Retrieve the array of journeys associated with the given address
        Journeys[] memory addrJourneys = activeJourneys[addr];

        // Iterate through the journeys in the current array
        for (uint256 i = 0; i < addrJourneys.length; i++) {
            // Check if the current journey has the desired keyTokenId
            if (addrJourneys[i].keyTokenId == keyTokenId) {
                // Return the journey if the keyTokenId matches
                return (
                    addrJourneys[i].nftAddress,
                    addrJourneys[i].nftTokenId,
                    addrJourneys[i].mythicWorld,
                    addrJourneys[i].artifactId,
                    addrJourneys[i].startTime
                );
            }
        }

        // Return an empty journey if no matching keyTokenId was found
        return (address(0), 0, "", 0, 0);
    }

    function getJourneyByKeyTokenId(address addr, uint256 keyTokenId)
        public
        view
        returns (Journeys memory)
    {
        // Retrieve the array of journeys associated with the given address
        Journeys[] memory addrJourneys = activeJourneys[addr];

        // Iterate through the journeys in the current array
        for (uint256 i = 0; i < addrJourneys.length; i++) {
            // Check if the current journey has the desired keyTokenId
            if (addrJourneys[i].keyTokenId == keyTokenId) {
                // Return the journey if the keyTokenId matches
                return addrJourneys[i];
            }
        }

        // Return an empty journey if no matching keyTokenId was found
        return Journeys(0, address(0), 0, 0, "", 0);
    }