/

    Contract Diff Checker

    Contract Name:
    ForestMowseDungeonFacet

    Contract Source Code:

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IAccessControlUpgradeable.sol";
    import "../utils/ContextUpgradeable.sol";
    import "../utils/StringsUpgradeable.sol";
    import "../utils/introspection/ERC165Upgradeable.sol";
    import {Initializable} from "../proxy/utils/Initializable.sol";
    
    /**
     * @dev Contract module that allows children to implement role-based access
     * control mechanisms. This is a lightweight version that doesn't allow enumerating role
     * members except through off-chain means by accessing the contract event logs. Some
     * applications may benefit from on-chain enumerability, for those cases see
     * {AccessControlEnumerable}.
     *
     * Roles are referred to by their `bytes32` identifier. These should be exposed
     * in the external API and be unique. The best way to achieve this is by
     * using `public constant` hash digests:
     *
     * ```solidity
     * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
     * ```
     *
     * Roles can be used to represent a set of permissions. To restrict access to a
     * function call, use {hasRole}:
     *
     * ```solidity
     * function foo() public {
     *     require(hasRole(MY_ROLE, msg.sender));
     *     ...
     * }
     * ```
     *
     * Roles can be granted and revoked dynamically via the {grantRole} and
     * {revokeRole} functions. Each role has an associated admin role, and only
     * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
     *
     * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
     * that only accounts with this role will be able to grant or revoke other
     * roles. More complex role relationships can be created by using
     * {_setRoleAdmin}.
     *
     * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
     * grant and revoke this role. Extra precautions should be taken to secure
     * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
     * to enforce additional security measures for this role.
     */
    abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
        struct RoleData {
            mapping(address => bool) members;
            bytes32 adminRole;
        }
    
        mapping(bytes32 => RoleData) private _roles;
    
        bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
    
        /**
         * @dev Modifier that checks that an account has a specific role. Reverts
         * with a standardized message including the required role.
         *
         * The format of the revert reason is given by the following regular expression:
         *
         *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
         *
         * _Available since v4.1._
         */
        modifier onlyRole(bytes32 role) {
            _checkRole(role);
            _;
        }
    
        function __AccessControl_init() internal onlyInitializing {
        }
    
        function __AccessControl_init_unchained() internal onlyInitializing {
        }
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
            return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
        }
    
        /**
         * @dev Returns `true` if `account` has been granted `role`.
         */
        function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
            return _roles[role].members[account];
        }
    
        /**
         * @dev Revert with a standard message if `_msgSender()` is missing `role`.
         * Overriding this function changes the behavior of the {onlyRole} modifier.
         *
         * Format of the revert message is described in {_checkRole}.
         *
         * _Available since v4.6._
         */
        function _checkRole(bytes32 role) internal view virtual {
            _checkRole(role, _msgSender());
        }
    
        /**
         * @dev Revert with a standard message if `account` is missing `role`.
         *
         * The format of the revert reason is given by the following regular expression:
         *
         *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
         */
        function _checkRole(bytes32 role, address account) internal view virtual {
            if (!hasRole(role, account)) {
                revert(
                    string(
                        abi.encodePacked(
                            "AccessControl: account ",
                            StringsUpgradeable.toHexString(account),
                            " is missing role ",
                            StringsUpgradeable.toHexString(uint256(role), 32)
                        )
                    )
                );
            }
        }
    
        /**
         * @dev Returns the admin role that controls `role`. See {grantRole} and
         * {revokeRole}.
         *
         * To change a role's admin, use {_setRoleAdmin}.
         */
        function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
            return _roles[role].adminRole;
        }
    
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         *
         * May emit a {RoleGranted} event.
         */
        function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
            _grantRole(role, account);
        }
    
        /**
         * @dev Revokes `role` from `account`.
         *
         * If `account` had been granted `role`, emits a {RoleRevoked} event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         *
         * May emit a {RoleRevoked} event.
         */
        function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
            _revokeRole(role, account);
        }
    
        /**
         * @dev Revokes `role` from the calling account.
         *
         * Roles are often managed via {grantRole} and {revokeRole}: this function's
         * purpose is to provide a mechanism for accounts to lose their privileges
         * if they are compromised (such as when a trusted device is misplaced).
         *
         * If the calling account had been revoked `role`, emits a {RoleRevoked}
         * event.
         *
         * Requirements:
         *
         * - the caller must be `account`.
         *
         * May emit a {RoleRevoked} event.
         */
        function renounceRole(bytes32 role, address account) public virtual override {
            require(account == _msgSender(), "AccessControl: can only renounce roles for self");
    
            _revokeRole(role, account);
        }
    
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event. Note that unlike {grantRole}, this function doesn't perform any
         * checks on the calling account.
         *
         * May emit a {RoleGranted} event.
         *
         * [WARNING]
         * ====
         * This function should only be called from the constructor when setting
         * up the initial roles for the system.
         *
         * Using this function in any other way is effectively circumventing the admin
         * system imposed by {AccessControl}.
         * ====
         *
         * NOTE: This function is deprecated in favor of {_grantRole}.
         */
        function _setupRole(bytes32 role, address account) internal virtual {
            _grantRole(role, account);
        }
    
        /**
         * @dev Sets `adminRole` as ``role``'s admin role.
         *
         * Emits a {RoleAdminChanged} event.
         */
        function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
            bytes32 previousAdminRole = getRoleAdmin(role);
            _roles[role].adminRole = adminRole;
            emit RoleAdminChanged(role, previousAdminRole, adminRole);
        }
    
        /**
         * @dev Grants `role` to `account`.
         *
         * Internal function without access restriction.
         *
         * May emit a {RoleGranted} event.
         */
        function _grantRole(bytes32 role, address account) internal virtual {
            if (!hasRole(role, account)) {
                _roles[role].members[account] = true;
                emit RoleGranted(role, account, _msgSender());
            }
        }
    
        /**
         * @dev Revokes `role` from `account`.
         *
         * Internal function without access restriction.
         *
         * May emit a {RoleRevoked} event.
         */
        function _revokeRole(bytes32 role, address account) internal virtual {
            if (hasRole(role, account)) {
                _roles[role].members[account] = false;
                emit RoleRevoked(role, account, _msgSender());
            }
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[49] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev External interface of AccessControl declared to support ERC165 detection.
     */
    interface IAccessControlUpgradeable {
        /**
         * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
         *
         * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
         * {RoleAdminChanged} not being emitted signaling this.
         *
         * _Available since v3.1._
         */
        event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
    
        /**
         * @dev Emitted when `account` is granted `role`.
         *
         * `sender` is the account that originated the contract call, an admin role
         * bearer except when using {AccessControl-_setupRole}.
         */
        event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
    
        /**
         * @dev Emitted when `account` is revoked `role`.
         *
         * `sender` is the account that originated the contract call:
         *   - if using `revokeRole`, it is the admin role bearer
         *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
         */
        event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
    
        /**
         * @dev Returns `true` if `account` has been granted `role`.
         */
        function hasRole(bytes32 role, address account) external view returns (bool);
    
        /**
         * @dev Returns the admin role that controls `role`. See {grantRole} and
         * {revokeRole}.
         *
         * To change a role's admin, use {AccessControl-_setRoleAdmin}.
         */
        function getRoleAdmin(bytes32 role) external view returns (bytes32);
    
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function grantRole(bytes32 role, address account) external;
    
        /**
         * @dev Revokes `role` from `account`.
         *
         * If `account` had been granted `role`, emits a {RoleRevoked} event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function revokeRole(bytes32 role, address account) external;
    
        /**
         * @dev Revokes `role` from the calling account.
         *
         * Roles are often managed via {grantRole} and {revokeRole}: this function's
         * purpose is to provide a mechanism for accounts to lose their privileges
         * if they are compromised (such as when a trusted device is misplaced).
         *
         * If the calling account had been granted `role`, emits a {RoleRevoked}
         * event.
         *
         * Requirements:
         *
         * - the caller must be `account`.
         */
        function renounceRole(bytes32 role, address account) external;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
     * proxy whose upgrades are fully controlled by the current implementation.
     */
    interface IERC1822ProxiableUpgradeable {
        /**
         * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
         * address.
         *
         * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
         * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
         * function revert if invoked through a proxy.
         */
        function proxiableUUID() external view returns (bytes32);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol)
    
    pragma solidity ^0.8.0;
    
    import "../utils/introspection/IERC165Upgradeable.sol";

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
     *
     * _Available since v4.8.3._
     */
    interface IERC1967Upgradeable {
        /**
         * @dev Emitted when the implementation is upgraded.
         */
        event Upgraded(address indexed implementation);
    
        /**
         * @dev Emitted when the admin account has changed.
         */
        event AdminChanged(address previousAdmin, address newAdmin);
    
        /**
         * @dev Emitted when the beacon is changed.
         */
        event BeaconUpgraded(address indexed beacon);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4906.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IERC165Upgradeable.sol";
    import "./IERC721Upgradeable.sol";
    
    /// @title EIP-721 Metadata Update Extension
    interface IERC4906Upgradeable is IERC165Upgradeable, IERC721Upgradeable {
        /// @dev This event emits when the metadata of a token is changed.
        /// So that the third-party platforms such as NFT market could
        /// timely update the images and related attributes of the NFT.
        event MetadataUpdate(uint256 _tokenId);
    
        /// @dev This event emits when the metadata of a range of tokens is changed.
        /// So that the third-party platforms such as NFT market could
        /// timely update the images and related attributes of the NFTs.
        event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol)
    
    pragma solidity ^0.8.0;
    
    interface IERC5267Upgradeable {
        /**
         * @dev MAY be emitted to signal that the domain could have changed.
         */
        event EIP712DomainChanged();
    
        /**
         * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
         * signature.
         */
        function eip712Domain()
            external
            view
            returns (
                bytes1 fields,
                string memory name,
                string memory version,
                uint256 chainId,
                address verifyingContract,
                bytes32 salt,
                uint256[] memory extensions
            );
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721.sol)
    
    pragma solidity ^0.8.0;
    
    import "../token/ERC721/IERC721Upgradeable.sol";

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev This is the interface that {BeaconProxy} expects of its beacon.
     */
    interface IBeaconUpgradeable {
        /**
         * @dev Must return an address that can be used as a delegate call target.
         *
         * {BeaconProxy} will check that this address is a contract.
         */
        function implementation() external view returns (address);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
    
    pragma solidity ^0.8.2;
    
    import "../beacon/IBeaconUpgradeable.sol";
    import "../../interfaces/IERC1967Upgradeable.sol";
    import "../../interfaces/draft-IERC1822Upgradeable.sol";
    import "../../utils/AddressUpgradeable.sol";
    import "../../utils/StorageSlotUpgradeable.sol";
    import {Initializable} from "../utils/Initializable.sol";
    
    /**
     * @dev This abstract contract provides getters and event emitting update functions for
     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
     *
     * _Available since v4.1._
     */
    abstract contract ERC1967UpgradeUpgradeable is Initializable, IERC1967Upgradeable {
        // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
        bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
    
        /**
         * @dev Storage slot with the address of the current implementation.
         * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
         * validated in the constructor.
         */
        bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    
        function __ERC1967Upgrade_init() internal onlyInitializing {
        }
    
        function __ERC1967Upgrade_init_unchained() internal onlyInitializing {
        }
        /**
         * @dev Returns the current implementation address.
         */
        function _getImplementation() internal view returns (address) {
            return StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
        }
    
        /**
         * @dev Stores a new address in the EIP1967 implementation slot.
         */
        function _setImplementation(address newImplementation) private {
            require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract");
            StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
        }
    
        /**
         * @dev Perform implementation upgrade
         *
         * Emits an {Upgraded} event.
         */
        function _upgradeTo(address newImplementation) internal {
            _setImplementation(newImplementation);
            emit Upgraded(newImplementation);
        }
    
        /**
         * @dev Perform implementation upgrade with additional setup call.
         *
         * Emits an {Upgraded} event.
         */
        function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
            _upgradeTo(newImplementation);
            if (data.length > 0 || forceCall) {
                AddressUpgradeable.functionDelegateCall(newImplementation, data);
            }
        }
    
        /**
         * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
         *
         * Emits an {Upgraded} event.
         */
        function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
            // Upgrades from old implementations will perform a rollback test. This test requires the new
            // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
            // this special case will break upgrade paths from old UUPS implementation to new ones.
            if (StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT).value) {
                _setImplementation(newImplementation);
            } else {
                try IERC1822ProxiableUpgradeable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                    require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
                } catch {
                    revert("ERC1967Upgrade: new implementation is not UUPS");
                }
                _upgradeToAndCall(newImplementation, data, forceCall);
            }
        }
    
        /**
         * @dev Storage slot with the admin of the contract.
         * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
         * validated in the constructor.
         */
        bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    
        /**
         * @dev Returns the current admin.
         */
        function _getAdmin() internal view returns (address) {
            return StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value;
        }
    
        /**
         * @dev Stores a new address in the EIP1967 admin slot.
         */
        function _setAdmin(address newAdmin) private {
            require(newAdmin != address(0), "ERC1967: new admin is the zero address");
            StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
        }
    
        /**
         * @dev Changes the admin of the proxy.
         *
         * Emits an {AdminChanged} event.
         */
        function _changeAdmin(address newAdmin) internal {
            emit AdminChanged(_getAdmin(), newAdmin);
            _setAdmin(newAdmin);
        }
    
        /**
         * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
         * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
         */
        bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
    
        /**
         * @dev Returns the current beacon.
         */
        function _getBeacon() internal view returns (address) {
            return StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value;
        }
    
        /**
         * @dev Stores a new beacon in the EIP1967 beacon slot.
         */
        function _setBeacon(address newBeacon) private {
            require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract");
            require(
                AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()),
                "ERC1967: beacon implementation is not a contract"
            );
            StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value = newBeacon;
        }
    
        /**
         * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
         * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
         *
         * Emits a {BeaconUpgraded} event.
         */
        function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
            _setBeacon(newBeacon);
            emit BeaconUpgraded(newBeacon);
            if (data.length > 0 || forceCall) {
                AddressUpgradeable.functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data);
            }
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[50] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
    
    pragma solidity ^0.8.2;
    
    import "../../utils/AddressUpgradeable.sol";
    
    /**
     * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
     * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
     * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
     * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
     *
     * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
     * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
     * case an upgrade adds a module that needs to be initialized.
     *
     * For example:
     *
     * [.hljs-theme-light.nopadding]
     * ```solidity
     * contract MyToken is ERC20Upgradeable {
     *     function initialize() initializer public {
     *         __ERC20_init("MyToken", "MTK");
     *     }
     * }
     *
     * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
     *     function initializeV2() reinitializer(2) public {
     *         __ERC20Permit_init("MyToken");
     *     }
     * }
     * ```
     *
     * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
     * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
     *
     * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
     * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
     *
     * [CAUTION]
     * ====
     * Avoid leaving a contract uninitialized.
     *
     * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
     * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
     * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
     *
     * [.hljs-theme-light.nopadding]
     * ```
     * /// @custom:oz-upgrades-unsafe-allow constructor
     * constructor() {
     *     _disableInitializers();
     * }
     * ```
     * ====
     */
    abstract contract Initializable {
        /**
         * @dev Indicates that the contract has been initialized.
         * @custom:oz-retyped-from bool
         */
        uint8 private _initialized;
    
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool private _initializing;
    
        /**
         * @dev Triggered when the contract has been initialized or reinitialized.
         */
        event Initialized(uint8 version);
    
        /**
         * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
         * `onlyInitializing` functions can be used to initialize parent contracts.
         *
         * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
         * constructor.
         *
         * Emits an {Initialized} event.
         */
        modifier initializer() {
            bool isTopLevelCall = !_initializing;
            require(
                (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
                "Initializable: contract is already initialized"
            );
            _initialized = 1;
            if (isTopLevelCall) {
                _initializing = true;
            }
            _;
            if (isTopLevelCall) {
                _initializing = false;
                emit Initialized(1);
            }
        }
    
        /**
         * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
         * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
         * used to initialize parent contracts.
         *
         * A reinitializer may be used after the original initialization step. This is essential to configure modules that
         * are added through upgrades and that require initialization.
         *
         * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
         * cannot be nested. If one is invoked in the context of another, execution will revert.
         *
         * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
         * a contract, executing them in the right order is up to the developer or operator.
         *
         * WARNING: setting the version to 255 will prevent any future reinitialization.
         *
         * Emits an {Initialized} event.
         */
        modifier reinitializer(uint8 version) {
            require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
            _initialized = version;
            _initializing = true;
            _;
            _initializing = false;
            emit Initialized(version);
        }
    
        /**
         * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
         * {initializer} and {reinitializer} modifiers, directly or indirectly.
         */
        modifier onlyInitializing() {
            require(_initializing, "Initializable: contract is not initializing");
            _;
        }
    
        /**
         * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
         * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
         * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
         * through proxies.
         *
         * Emits an {Initialized} event the first time it is successfully executed.
         */
        function _disableInitializers() internal virtual {
            require(!_initializing, "Initializable: contract is initializing");
            if (_initialized != type(uint8).max) {
                _initialized = type(uint8).max;
                emit Initialized(type(uint8).max);
            }
        }
    
        /**
         * @dev Returns the highest version that has been initialized. See {reinitializer}.
         */
        function _getInitializedVersion() internal view returns (uint8) {
            return _initialized;
        }
    
        /**
         * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
         */
        function _isInitializing() internal view returns (bool) {
            return _initializing;
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/UUPSUpgradeable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../../interfaces/draft-IERC1822Upgradeable.sol";
    import "../ERC1967/ERC1967UpgradeUpgradeable.sol";
    import {Initializable} from "./Initializable.sol";
    
    /**
     * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
     * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
     *
     * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
     * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
     * `UUPSUpgradeable` with a custom implementation of upgrades.
     *
     * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
     *
     * _Available since v4.1._
     */
    abstract contract UUPSUpgradeable is Initializable, IERC1822ProxiableUpgradeable, ERC1967UpgradeUpgradeable {
        /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
        address private immutable __self = address(this);
    
        /**
         * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
         * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
         * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
         * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
         * fail.
         */
        modifier onlyProxy() {
            require(address(this) != __self, "Function must be called through delegatecall");
            require(_getImplementation() == __self, "Function must be called through active proxy");
            _;
        }
    
        /**
         * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
         * callable on the implementing contract but not through proxies.
         */
        modifier notDelegated() {
            require(address(this) == __self, "UUPSUpgradeable: must not be called through delegatecall");
            _;
        }
    
        function __UUPSUpgradeable_init() internal onlyInitializing {
        }
    
        function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
        }
        /**
         * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
         * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
         *
         * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
         * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
         * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
         */
        function proxiableUUID() external view virtual override notDelegated returns (bytes32) {
            return _IMPLEMENTATION_SLOT;
        }
    
        /**
         * @dev Upgrade the implementation of the proxy to `newImplementation`.
         *
         * Calls {_authorizeUpgrade}.
         *
         * Emits an {Upgraded} event.
         *
         * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
         */
        function upgradeTo(address newImplementation) public virtual onlyProxy {
            _authorizeUpgrade(newImplementation);
            _upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
        }
    
        /**
         * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
         * encoded in `data`.
         *
         * Calls {_authorizeUpgrade}.
         *
         * Emits an {Upgraded} event.
         *
         * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
         */
        function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
            _authorizeUpgrade(newImplementation);
            _upgradeToAndCallUUPS(newImplementation, data, true);
        }
    
        /**
         * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
         * {upgradeTo} and {upgradeToAndCall}.
         *
         * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
         *
         * ```solidity
         * function _authorizeUpgrade(address) internal override onlyOwner {}
         * ```
         */
        function _authorizeUpgrade(address newImplementation) internal virtual;
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[50] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../utils/ContextUpgradeable.sol";
    import {Initializable} from "../proxy/utils/Initializable.sol";
    
    /**
     * @dev Contract module which allows children to implement an emergency stop
     * mechanism that can be triggered by an authorized account.
     *
     * This module is used through inheritance. It will make available the
     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
     * the functions of your contract. Note that they will not be pausable by
     * simply including this module, only once the modifiers are put in place.
     */
    abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
        /**
         * @dev Emitted when the pause is triggered by `account`.
         */
        event Paused(address account);
    
        /**
         * @dev Emitted when the pause is lifted by `account`.
         */
        event Unpaused(address account);
    
        bool private _paused;
    
        /**
         * @dev Initializes the contract in unpaused state.
         */
        function __Pausable_init() internal onlyInitializing {
            __Pausable_init_unchained();
        }
    
        function __Pausable_init_unchained() internal onlyInitializing {
            _paused = false;
        }
    
        /**
         * @dev Modifier to make a function callable only when the contract is not paused.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        modifier whenNotPaused() {
            _requireNotPaused();
            _;
        }
    
        /**
         * @dev Modifier to make a function callable only when the contract is paused.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        modifier whenPaused() {
            _requirePaused();
            _;
        }
    
        /**
         * @dev Returns true if the contract is paused, and false otherwise.
         */
        function paused() public view virtual returns (bool) {
            return _paused;
        }
    
        /**
         * @dev Throws if the contract is paused.
         */
        function _requireNotPaused() internal view virtual {
            require(!paused(), "Pausable: paused");
        }
    
        /**
         * @dev Throws if the contract is not paused.
         */
        function _requirePaused() internal view virtual {
            require(paused(), "Pausable: not paused");
        }
    
        /**
         * @dev Triggers stopped state.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        function _pause() internal virtual whenNotPaused {
            _paused = true;
            emit Paused(_msgSender());
        }
    
        /**
         * @dev Returns to normal state.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        function _unpause() internal virtual whenPaused {
            _paused = false;
            emit Unpaused(_msgSender());
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[49] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IERC20Upgradeable.sol";
    import "./extensions/IERC20MetadataUpgradeable.sol";
    import "../../utils/ContextUpgradeable.sol";
    import {Initializable} from "../../proxy/utils/Initializable.sol";
    
    /**
     * @dev Implementation of the {IERC20} interface.
     *
     * This implementation is agnostic to the way tokens are created. This means
     * that a supply mechanism has to be added in a derived contract using {_mint}.
     * For a generic mechanism see {ERC20PresetMinterPauser}.
     *
     * TIP: For a detailed writeup see our guide
     * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
     * to implement supply mechanisms].
     *
     * The default value of {decimals} is 18. To change this, you should override
     * this function so it returns a different value.
     *
     * We have followed general OpenZeppelin Contracts guidelines: functions revert
     * instead returning `false` on failure. This behavior is nonetheless
     * conventional and does not conflict with the expectations of ERC20
     * applications.
     *
     * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
     * This allows applications to reconstruct the allowance for all accounts just
     * by listening to said events. Other implementations of the EIP may not emit
     * these events, as it isn't required by the specification.
     *
     * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
     * functions have been added to mitigate the well-known issues around setting
     * allowances. See {IERC20-approve}.
     */
    contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {
        mapping(address => uint256) private _balances;
    
        mapping(address => mapping(address => uint256)) private _allowances;
    
        uint256 private _totalSupply;
    
        string private _name;
        string private _symbol;
    
        /**
         * @dev Sets the values for {name} and {symbol}.
         *
         * All two of these values are immutable: they can only be set once during
         * construction.
         */
        function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
            __ERC20_init_unchained(name_, symbol_);
        }
    
        function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
            _name = name_;
            _symbol = symbol_;
        }
    
        /**
         * @dev Returns the name of the token.
         */
        function name() public view virtual override returns (string memory) {
            return _name;
        }
    
        /**
         * @dev Returns the symbol of the token, usually a shorter version of the
         * name.
         */
        function symbol() public view virtual override returns (string memory) {
            return _symbol;
        }
    
        /**
         * @dev Returns the number of decimals used to get its user representation.
         * For example, if `decimals` equals `2`, a balance of `505` tokens should
         * be displayed to a user as `5.05` (`505 / 10 ** 2`).
         *
         * Tokens usually opt for a value of 18, imitating the relationship between
         * Ether and Wei. This is the default value returned by this function, unless
         * it's overridden.
         *
         * NOTE: This information is only used for _display_ purposes: it in
         * no way affects any of the arithmetic of the contract, including
         * {IERC20-balanceOf} and {IERC20-transfer}.
         */
        function decimals() public view virtual override returns (uint8) {
            return 18;
        }
    
        /**
         * @dev See {IERC20-totalSupply}.
         */
        function totalSupply() public view virtual override returns (uint256) {
            return _totalSupply;
        }
    
        /**
         * @dev See {IERC20-balanceOf}.
         */
        function balanceOf(address account) public view virtual override returns (uint256) {
            return _balances[account];
        }
    
        /**
         * @dev See {IERC20-transfer}.
         *
         * Requirements:
         *
         * - `to` cannot be the zero address.
         * - the caller must have a balance of at least `amount`.
         */
        function transfer(address to, uint256 amount) public virtual override returns (bool) {
            address owner = _msgSender();
            _transfer(owner, to, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-allowance}.
         */
        function allowance(address owner, address spender) public view virtual override returns (uint256) {
            return _allowances[owner][spender];
        }
    
        /**
         * @dev See {IERC20-approve}.
         *
         * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
         * `transferFrom`. This is semantically equivalent to an infinite approval.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function approve(address spender, uint256 amount) public virtual override returns (bool) {
            address owner = _msgSender();
            _approve(owner, spender, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-transferFrom}.
         *
         * Emits an {Approval} event indicating the updated allowance. This is not
         * required by the EIP. See the note at the beginning of {ERC20}.
         *
         * NOTE: Does not update the allowance if the current allowance
         * is the maximum `uint256`.
         *
         * Requirements:
         *
         * - `from` and `to` cannot be the zero address.
         * - `from` must have a balance of at least `amount`.
         * - the caller must have allowance for ``from``'s tokens of at least
         * `amount`.
         */
        function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
            address spender = _msgSender();
            _spendAllowance(from, spender, amount);
            _transfer(from, to, amount);
            return true;
        }
    
        /**
         * @dev Atomically increases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
            address owner = _msgSender();
            _approve(owner, spender, allowance(owner, spender) + addedValue);
            return true;
        }
    
        /**
         * @dev Atomically decreases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `spender` must have allowance for the caller of at least
         * `subtractedValue`.
         */
        function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
            address owner = _msgSender();
            uint256 currentAllowance = allowance(owner, spender);
            require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
            unchecked {
                _approve(owner, spender, currentAllowance - subtractedValue);
            }
    
            return true;
        }
    
        /**
         * @dev Moves `amount` of tokens from `from` to `to`.
         *
         * This internal function is equivalent to {transfer}, and can be used to
         * e.g. implement automatic token fees, slashing mechanisms, etc.
         *
         * Emits a {Transfer} event.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `from` must have a balance of at least `amount`.
         */
        function _transfer(address from, address to, uint256 amount) internal virtual {
            require(from != address(0), "ERC20: transfer from the zero address");
            require(to != address(0), "ERC20: transfer to the zero address");
    
            _beforeTokenTransfer(from, to, amount);
    
            uint256 fromBalance = _balances[from];
            require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
            unchecked {
                _balances[from] = fromBalance - amount;
                // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
                // decrementing then incrementing.
                _balances[to] += amount;
            }
    
            emit Transfer(from, to, amount);
    
            _afterTokenTransfer(from, to, amount);
        }
    
        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
         * the total supply.
         *
         * Emits a {Transfer} event with `from` set to the zero address.
         *
         * Requirements:
         *
         * - `account` cannot be the zero address.
         */
        function _mint(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: mint to the zero address");
    
            _beforeTokenTransfer(address(0), account, amount);
    
            _totalSupply += amount;
            unchecked {
                // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
                _balances[account] += amount;
            }
            emit Transfer(address(0), account, amount);
    
            _afterTokenTransfer(address(0), account, amount);
        }
    
        /**
         * @dev Destroys `amount` tokens from `account`, reducing the
         * total supply.
         *
         * Emits a {Transfer} event with `to` set to the zero address.
         *
         * Requirements:
         *
         * - `account` cannot be the zero address.
         * - `account` must have at least `amount` tokens.
         */
        function _burn(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: burn from the zero address");
    
            _beforeTokenTransfer(account, address(0), amount);
    
            uint256 accountBalance = _balances[account];
            require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
            unchecked {
                _balances[account] = accountBalance - amount;
                // Overflow not possible: amount <= accountBalance <= totalSupply.
                _totalSupply -= amount;
            }
    
            emit Transfer(account, address(0), amount);
    
            _afterTokenTransfer(account, address(0), amount);
        }
    
        /**
         * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
         *
         * This internal function is equivalent to `approve`, and can be used to
         * e.g. set automatic allowances for certain subsystems, etc.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `owner` cannot be the zero address.
         * - `spender` cannot be the zero address.
         */
        function _approve(address owner, address spender, uint256 amount) internal virtual {
            require(owner != address(0), "ERC20: approve from the zero address");
            require(spender != address(0), "ERC20: approve to the zero address");
    
            _allowances[owner][spender] = amount;
            emit Approval(owner, spender, amount);
        }
    
        /**
         * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
         *
         * Does not update the allowance amount in case of infinite allowance.
         * Revert if not enough allowance is available.
         *
         * Might emit an {Approval} event.
         */
        function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
            uint256 currentAllowance = allowance(owner, spender);
            if (currentAllowance != type(uint256).max) {
                require(currentAllowance >= amount, "ERC20: insufficient allowance");
                unchecked {
                    _approve(owner, spender, currentAllowance - amount);
                }
            }
        }
    
        /**
         * @dev Hook that is called before any transfer of tokens. This includes
         * minting and burning.
         *
         * Calling conditions:
         *
         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
         * will be transferred to `to`.
         * - when `from` is zero, `amount` tokens will be minted for `to`.
         * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
         * - `from` and `to` are never both zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
    
        /**
         * @dev Hook that is called after any transfer of tokens. This includes
         * minting and burning.
         *
         * Calling conditions:
         *
         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
         * has been transferred to `to`.
         * - when `from` is zero, `amount` tokens have been minted for `to`.
         * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
         * - `from` and `to` are never both zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[45] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/draft-ERC20Permit.sol)
    
    pragma solidity ^0.8.0;
    
    // EIP-2612 is Final as of 2022-11-01. This file is deprecated.
    
    import "./ERC20PermitUpgradeable.sol";

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../ERC20Upgradeable.sol";
    import "../../../utils/ContextUpgradeable.sol";
    import {Initializable} from "../../../proxy/utils/Initializable.sol";
    
    /**
     * @dev Extension of {ERC20} that allows token holders to destroy both their own
     * tokens and those that they have an allowance for, in a way that can be
     * recognized off-chain (via event analysis).
     */
    abstract contract ERC20BurnableUpgradeable is Initializable, ContextUpgradeable, ERC20Upgradeable {
        function __ERC20Burnable_init() internal onlyInitializing {
        }
    
        function __ERC20Burnable_init_unchained() internal onlyInitializing {
        }
        /**
         * @dev Destroys `amount` tokens from the caller.
         *
         * See {ERC20-_burn}.
         */
        function burn(uint256 amount) public virtual {
            _burn(_msgSender(), amount);
        }
    
        /**
         * @dev Destroys `amount` tokens from `account`, deducting from the caller's
         * allowance.
         *
         * See {ERC20-_burn} and {ERC20-allowance}.
         *
         * Requirements:
         *
         * - the caller must have allowance for ``accounts``'s tokens of at least
         * `amount`.
         */
        function burnFrom(address account, uint256 amount) public virtual {
            _spendAllowance(account, _msgSender(), amount);
            _burn(account, amount);
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[50] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/ERC20Permit.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IERC20PermitUpgradeable.sol";
    import "../ERC20Upgradeable.sol";
    import "../../../utils/cryptography/ECDSAUpgradeable.sol";
    import "../../../utils/cryptography/EIP712Upgradeable.sol";
    import "../../../utils/CountersUpgradeable.sol";
    import {Initializable} from "../../../proxy/utils/Initializable.sol";
    
    /**
     * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
     *
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
     * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
     * need to send a transaction, and thus is not required to hold Ether at all.
     *
     * _Available since v3.4._
     *
     * @custom:storage-size 51
     */
    abstract contract ERC20PermitUpgradeable is Initializable, ERC20Upgradeable, IERC20PermitUpgradeable, EIP712Upgradeable {
        using CountersUpgradeable for CountersUpgradeable.Counter;
    
        mapping(address => CountersUpgradeable.Counter) private _nonces;
    
        // solhint-disable-next-line var-name-mixedcase
        bytes32 private constant _PERMIT_TYPEHASH =
            keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
        /**
         * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
         * However, to ensure consistency with the upgradeable transpiler, we will continue
         * to reserve a slot.
         * @custom:oz-renamed-from _PERMIT_TYPEHASH
         */
        // solhint-disable-next-line var-name-mixedcase
        bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;
    
        /**
         * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
         *
         * It's a good idea to use the same `name` that is defined as the ERC20 token name.
         */
        function __ERC20Permit_init(string memory name) internal onlyInitializing {
            __EIP712_init_unchained(name, "1");
        }
    
        function __ERC20Permit_init_unchained(string memory) internal onlyInitializing {}
    
        /**
         * @inheritdoc IERC20PermitUpgradeable
         */
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) public virtual override {
            require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
    
            bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
    
            bytes32 hash = _hashTypedDataV4(structHash);
    
            address signer = ECDSAUpgradeable.recover(hash, v, r, s);
            require(signer == owner, "ERC20Permit: invalid signature");
    
            _approve(owner, spender, value);
        }
    
        /**
         * @inheritdoc IERC20PermitUpgradeable
         */
        function nonces(address owner) public view virtual override returns (uint256) {
            return _nonces[owner].current();
        }
    
        /**
         * @inheritdoc IERC20PermitUpgradeable
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view override returns (bytes32) {
            return _domainSeparatorV4();
        }
    
        /**
         * @dev "Consume a nonce": return the current value and increment.
         *
         * _Available since v4.1._
         */
        function _useNonce(address owner) internal virtual returns (uint256 current) {
            CountersUpgradeable.Counter storage nonce = _nonces[owner];
            current = nonce.current();
            nonce.increment();
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[49] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
    
    pragma solidity ^0.8.0;
    
    import "../IERC20Upgradeable.sol";
    
    /**
     * @dev Interface for the optional metadata functions from the ERC20 standard.
     *
     * _Available since v4.1._
     */
    interface IERC20MetadataUpgradeable is IERC20Upgradeable {
        /**
         * @dev Returns the name of the token.
         */
        function name() external view returns (string memory);
    
        /**
         * @dev Returns the symbol of the token.
         */
        function symbol() external view returns (string memory);
    
        /**
         * @dev Returns the decimals places of the token.
         */
        function decimals() external view returns (uint8);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
     *
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
     * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
     * need to send a transaction, and thus is not required to hold Ether at all.
     *
     * ==== Security Considerations
     *
     * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
     * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
     * considered as an intention to spend the allowance in any specific way. The second is that because permits have
     * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
     * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
     * generally recommended is:
     *
     * ```solidity
     * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
     *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
     *     doThing(..., value);
     * }
     *
     * function doThing(..., uint256 value) public {
     *     token.safeTransferFrom(msg.sender, address(this), value);
     *     ...
     * }
     * ```
     *
     * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
     * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
     * {SafeERC20-safeTransferFrom}).
     *
     * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
     * contracts should have entry points that don't rely on permit.
     */
    interface IERC20PermitUpgradeable {
        /**
         * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
         * given ``owner``'s signed approval.
         *
         * IMPORTANT: The same issues {IERC20-approve} has related to transaction
         * ordering also apply here.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `deadline` must be a timestamp in the future.
         * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
         * over the EIP712-formatted function arguments.
         * - the signature must use ``owner``'s current nonce (see {nonces}).
         *
         * For more information on the signature format, see the
         * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
         * section].
         *
         * CAUTION: See Security Considerations above.
         */
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
    
        /**
         * @dev Returns the current nonce for `owner`. This value must be
         * included whenever a signature is generated for {permit}.
         *
         * Every successful call to {permit} increases ``owner``'s nonce by one. This
         * prevents a signature from being used multiple times.
         */
        function nonces(address owner) external view returns (uint256);
    
        /**
         * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view returns (bytes32);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20Upgradeable {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves `amount` tokens from the caller's account to `to`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address to, uint256 amount) external returns (bool);
    
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
    
        /**
         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 amount) external returns (bool);
    
        /**
         * @dev Moves `amount` tokens from `from` to `to` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address from, address to, uint256 amount) external returns (bool);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IERC721Upgradeable.sol";
    import "./IERC721ReceiverUpgradeable.sol";
    import "./extensions/IERC721MetadataUpgradeable.sol";
    import "../../utils/AddressUpgradeable.sol";
    import "../../utils/ContextUpgradeable.sol";
    import "../../utils/StringsUpgradeable.sol";
    import "../../utils/introspection/ERC165Upgradeable.sol";
    import {Initializable} from "../../proxy/utils/Initializable.sol";
    
    /**
     * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
     * the Metadata extension, but not including the Enumerable extension, which is available separately as
     * {ERC721Enumerable}.
     */
    contract ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721Upgradeable, IERC721MetadataUpgradeable {
        using AddressUpgradeable for address;
        using StringsUpgradeable for uint256;
    
        // Token name
        string private _name;
    
        // Token symbol
        string private _symbol;
    
        // Mapping from token ID to owner address
        mapping(uint256 => address) private _owners;
    
        // Mapping owner address to token count
        mapping(address => uint256) private _balances;
    
        // Mapping from token ID to approved address
        mapping(uint256 => address) private _tokenApprovals;
    
        // Mapping from owner to operator approvals
        mapping(address => mapping(address => bool)) private _operatorApprovals;
    
        /**
         * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
         */
        function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {
            __ERC721_init_unchained(name_, symbol_);
        }
    
        function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
            _name = name_;
            _symbol = symbol_;
        }
    
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {
            return
                interfaceId == type(IERC721Upgradeable).interfaceId ||
                interfaceId == type(IERC721MetadataUpgradeable).interfaceId ||
                super.supportsInterface(interfaceId);
        }
    
        /**
         * @dev See {IERC721-balanceOf}.
         */
        function balanceOf(address owner) public view virtual override returns (uint256) {
            require(owner != address(0), "ERC721: address zero is not a valid owner");
            return _balances[owner];
        }
    
        /**
         * @dev See {IERC721-ownerOf}.
         */
        function ownerOf(uint256 tokenId) public view virtual override returns (address) {
            address owner = _ownerOf(tokenId);
            require(owner != address(0), "ERC721: invalid token ID");
            return owner;
        }
    
        /**
         * @dev See {IERC721Metadata-name}.
         */
        function name() public view virtual override returns (string memory) {
            return _name;
        }
    
        /**
         * @dev See {IERC721Metadata-symbol}.
         */
        function symbol() public view virtual override returns (string memory) {
            return _symbol;
        }
    
        /**
         * @dev See {IERC721Metadata-tokenURI}.
         */
        function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
            _requireMinted(tokenId);
    
            string memory baseURI = _baseURI();
            return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
        }
    
        /**
         * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
         * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
         * by default, can be overridden in child contracts.
         */
        function _baseURI() internal view virtual returns (string memory) {
            return "";
        }
    
        /**
         * @dev See {IERC721-approve}.
         */
        function approve(address to, uint256 tokenId) public virtual override {
            address owner = ERC721Upgradeable.ownerOf(tokenId);
            require(to != owner, "ERC721: approval to current owner");
    
            require(
                _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                "ERC721: approve caller is not token owner or approved for all"
            );
    
            _approve(to, tokenId);
        }
    
        /**
         * @dev See {IERC721-getApproved}.
         */
        function getApproved(uint256 tokenId) public view virtual override returns (address) {
            _requireMinted(tokenId);
    
            return _tokenApprovals[tokenId];
        }
    
        /**
         * @dev See {IERC721-setApprovalForAll}.
         */
        function setApprovalForAll(address operator, bool approved) public virtual override {
            _setApprovalForAll(_msgSender(), operator, approved);
        }
    
        /**
         * @dev See {IERC721-isApprovedForAll}.
         */
        function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
            return _operatorApprovals[owner][operator];
        }
    
        /**
         * @dev See {IERC721-transferFrom}.
         */
        function transferFrom(address from, address to, uint256 tokenId) public virtual override {
            //solhint-disable-next-line max-line-length
            require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
    
            _transfer(from, to, tokenId);
        }
    
        /**
         * @dev See {IERC721-safeTransferFrom}.
         */
        function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
            safeTransferFrom(from, to, tokenId, "");
        }
    
        /**
         * @dev See {IERC721-safeTransferFrom}.
         */
        function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
            require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
            _safeTransfer(from, to, tokenId, data);
        }
    
        /**
         * @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.
         *
         * `data` is additional data, it has no specified format and it is sent in call to `to`.
         *
         * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
         * implement alternative mechanisms to perform token transfer, such as signature-based.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `tokenId` token must exist and be owned by `from`.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
            _transfer(from, to, tokenId);
            require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
        }
    
        /**
         * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
         */
        function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
            return _owners[tokenId];
        }
    
        /**
         * @dev Returns whether `tokenId` exists.
         *
         * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
         *
         * Tokens start existing when they are minted (`_mint`),
         * and stop existing when they are burned (`_burn`).
         */
        function _exists(uint256 tokenId) internal view virtual returns (bool) {
            return _ownerOf(tokenId) != address(0);
        }
    
        /**
         * @dev Returns whether `spender` is allowed to manage `tokenId`.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
            address owner = ERC721Upgradeable.ownerOf(tokenId);
            return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
        }
    
        /**
         * @dev Safely mints `tokenId` and transfers it to `to`.
         *
         * Requirements:
         *
         * - `tokenId` must not exist.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function _safeMint(address to, uint256 tokenId) internal virtual {
            _safeMint(to, tokenId, "");
        }
    
        /**
         * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
         * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
         */
        function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
            _mint(to, tokenId);
            require(
                _checkOnERC721Received(address(0), to, tokenId, data),
                "ERC721: transfer to non ERC721Receiver implementer"
            );
        }
    
        /**
         * @dev Mints `tokenId` and transfers it to `to`.
         *
         * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
         *
         * Requirements:
         *
         * - `tokenId` must not exist.
         * - `to` cannot be the zero address.
         *
         * Emits a {Transfer} event.
         */
        function _mint(address to, uint256 tokenId) internal virtual {
            require(to != address(0), "ERC721: mint to the zero address");
            require(!_exists(tokenId), "ERC721: token already minted");
    
            _beforeTokenTransfer(address(0), to, tokenId, 1);
    
            // Check that tokenId was not minted by `_beforeTokenTransfer` hook
            require(!_exists(tokenId), "ERC721: token already minted");
    
            unchecked {
                // Will not overflow unless all 2**256 token ids are minted to the same owner.
                // Given that tokens are minted one by one, it is impossible in practice that
                // this ever happens. Might change if we allow batch minting.
                // The ERC fails to describe this case.
                _balances[to] += 1;
            }
    
            _owners[tokenId] = to;
    
            emit Transfer(address(0), to, tokenId);
    
            _afterTokenTransfer(address(0), to, tokenId, 1);
        }
    
        /**
         * @dev Destroys `tokenId`.
         * The approval is cleared when the token is burned.
         * This is an internal function that does not check if the sender is authorized to operate on the token.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         *
         * Emits a {Transfer} event.
         */
        function _burn(uint256 tokenId) internal virtual {
            address owner = ERC721Upgradeable.ownerOf(tokenId);
    
            _beforeTokenTransfer(owner, address(0), tokenId, 1);
    
            // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
            owner = ERC721Upgradeable.ownerOf(tokenId);
    
            // Clear approvals
            delete _tokenApprovals[tokenId];
    
            unchecked {
                // Cannot overflow, as that would require more tokens to be burned/transferred
                // out than the owner initially received through minting and transferring in.
                _balances[owner] -= 1;
            }
            delete _owners[tokenId];
    
            emit Transfer(owner, address(0), tokenId);
    
            _afterTokenTransfer(owner, address(0), tokenId, 1);
        }
    
        /**
         * @dev Transfers `tokenId` from `from` to `to`.
         *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
         *
         * Requirements:
         *
         * - `to` cannot be the zero address.
         * - `tokenId` token must be owned by `from`.
         *
         * Emits a {Transfer} event.
         */
        function _transfer(address from, address to, uint256 tokenId) internal virtual {
            require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
            require(to != address(0), "ERC721: transfer to the zero address");
    
            _beforeTokenTransfer(from, to, tokenId, 1);
    
            // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
            require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
    
            // Clear approvals from the previous owner
            delete _tokenApprovals[tokenId];
    
            unchecked {
                // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
                // `from`'s balance is the number of token held, which is at least one before the current
                // transfer.
                // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
                // all 2**256 token ids to be minted, which in practice is impossible.
                _balances[from] -= 1;
                _balances[to] += 1;
            }
            _owners[tokenId] = to;
    
            emit Transfer(from, to, tokenId);
    
            _afterTokenTransfer(from, to, tokenId, 1);
        }
    
        /**
         * @dev Approve `to` to operate on `tokenId`
         *
         * Emits an {Approval} event.
         */
        function _approve(address to, uint256 tokenId) internal virtual {
            _tokenApprovals[tokenId] = to;
            emit Approval(ERC721Upgradeable.ownerOf(tokenId), to, tokenId);
        }
    
        /**
         * @dev Approve `operator` to operate on all of `owner` tokens
         *
         * Emits an {ApprovalForAll} event.
         */
        function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
            require(owner != operator, "ERC721: approve to caller");
            _operatorApprovals[owner][operator] = approved;
            emit ApprovalForAll(owner, operator, approved);
        }
    
        /**
         * @dev Reverts if the `tokenId` has not been minted yet.
         */
        function _requireMinted(uint256 tokenId) internal view virtual {
            require(_exists(tokenId), "ERC721: invalid token ID");
        }
    
        /**
         * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
         * The call is not executed if the target address is not a contract.
         *
         * @param from address representing the previous owner of the given token ID
         * @param to target address that will receive the tokens
         * @param tokenId uint256 ID of the token to be transferred
         * @param data bytes optional data to send along with the call
         * @return bool whether the call correctly returned the expected magic value
         */
        function _checkOnERC721Received(
            address from,
            address to,
            uint256 tokenId,
            bytes memory data
        ) private returns (bool) {
            if (to.isContract()) {
                try IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                    return retval == IERC721ReceiverUpgradeable.onERC721Received.selector;
                } catch (bytes memory reason) {
                    if (reason.length == 0) {
                        revert("ERC721: transfer to non ERC721Receiver implementer");
                    } else {
                        /// @solidity memory-safe-assembly
                        assembly {
                            revert(add(32, reason), mload(reason))
                        }
                    }
                }
            } else {
                return true;
            }
        }
    
        /**
         * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
         * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
         *
         * Calling conditions:
         *
         * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
         * - When `from` is zero, the tokens will be minted for `to`.
         * - When `to` is zero, ``from``'s tokens will be burned.
         * - `from` and `to` are never both zero.
         * - `batchSize` is non-zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
    
        /**
         * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
         * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
         *
         * Calling conditions:
         *
         * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
         * - When `from` is zero, the tokens were minted for `to`.
         * - When `to` is zero, ``from``'s tokens were burned.
         * - `from` and `to` are never both zero.
         * - `batchSize` is non-zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
    
        /**
         * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
         *
         * WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
         * being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
         * that `ownerOf(tokenId)` is `a`.
         */
        // solhint-disable-next-line func-name-mixedcase
        function __unsafe_increaseBalance(address account, uint256 amount) internal {
            _balances[account] += amount;
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[44] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Enumerable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../ERC721Upgradeable.sol";
    import "./IERC721EnumerableUpgradeable.sol";
    import {Initializable} from "../../../proxy/utils/Initializable.sol";
    
    /**
     * @dev This implements an optional extension of {ERC721} defined in the EIP that adds
     * enumerability of all the token ids in the contract as well as all token ids owned by each
     * account.
     */
    abstract contract ERC721EnumerableUpgradeable is Initializable, ERC721Upgradeable, IERC721EnumerableUpgradeable {
        // Mapping from owner to list of owned token IDs
        mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
    
        // Mapping from token ID to index of the owner tokens list
        mapping(uint256 => uint256) private _ownedTokensIndex;
    
        // Array with all token ids, used for enumeration
        uint256[] private _allTokens;
    
        // Mapping from token id to position in the allTokens array
        mapping(uint256 => uint256) private _allTokensIndex;
    
        function __ERC721Enumerable_init() internal onlyInitializing {
        }
    
        function __ERC721Enumerable_init_unchained() internal onlyInitializing {
        }
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165Upgradeable, ERC721Upgradeable) returns (bool) {
            return interfaceId == type(IERC721EnumerableUpgradeable).interfaceId || super.supportsInterface(interfaceId);
        }
    
        /**
         * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
         */
        function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
            require(index < ERC721Upgradeable.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
            return _ownedTokens[owner][index];
        }
    
        /**
         * @dev See {IERC721Enumerable-totalSupply}.
         */
        function totalSupply() public view virtual override returns (uint256) {
            return _allTokens.length;
        }
    
        /**
         * @dev See {IERC721Enumerable-tokenByIndex}.
         */
        function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
            require(index < ERC721EnumerableUpgradeable.totalSupply(), "ERC721Enumerable: global index out of bounds");
            return _allTokens[index];
        }
    
        /**
         * @dev See {ERC721-_beforeTokenTransfer}.
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 firstTokenId,
            uint256 batchSize
        ) internal virtual override {
            super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
    
            if (batchSize > 1) {
                // Will only trigger during construction. Batch transferring (minting) is not available afterwards.
                revert("ERC721Enumerable: consecutive transfers not supported");
            }
    
            uint256 tokenId = firstTokenId;
    
            if (from == address(0)) {
                _addTokenToAllTokensEnumeration(tokenId);
            } else if (from != to) {
                _removeTokenFromOwnerEnumeration(from, tokenId);
            }
            if (to == address(0)) {
                _removeTokenFromAllTokensEnumeration(tokenId);
            } else if (to != from) {
                _addTokenToOwnerEnumeration(to, tokenId);
            }
        }
    
        /**
         * @dev Private function to add a token to this extension's ownership-tracking data structures.
         * @param to address representing the new owner of the given token ID
         * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
         */
        function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
            uint256 length = ERC721Upgradeable.balanceOf(to);
            _ownedTokens[to][length] = tokenId;
            _ownedTokensIndex[tokenId] = length;
        }
    
        /**
         * @dev Private function to add a token to this extension's token tracking data structures.
         * @param tokenId uint256 ID of the token to be added to the tokens list
         */
        function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
            _allTokensIndex[tokenId] = _allTokens.length;
            _allTokens.push(tokenId);
        }
    
        /**
         * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
         * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
         * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
         * This has O(1) time complexity, but alters the order of the _ownedTokens array.
         * @param from address representing the previous owner of the given token ID
         * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
         */
        function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
            // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
            // then delete the last slot (swap and pop).
    
            uint256 lastTokenIndex = ERC721Upgradeable.balanceOf(from) - 1;
            uint256 tokenIndex = _ownedTokensIndex[tokenId];
    
            // When the token to delete is the last token, the swap operation is unnecessary
            if (tokenIndex != lastTokenIndex) {
                uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
    
                _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
                _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
            }
    
            // This also deletes the contents at the last position of the array
            delete _ownedTokensIndex[tokenId];
            delete _ownedTokens[from][lastTokenIndex];
        }
    
        /**
         * @dev Private function to remove a token from this extension's token tracking data structures.
         * This has O(1) time complexity, but alters the order of the _allTokens array.
         * @param tokenId uint256 ID of the token to be removed from the tokens list
         */
        function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
            // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
            // then delete the last slot (swap and pop).
    
            uint256 lastTokenIndex = _allTokens.length - 1;
            uint256 tokenIndex = _allTokensIndex[tokenId];
    
            // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
            // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
            // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
            uint256 lastTokenId = _allTokens[lastTokenIndex];
    
            _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
    
            // This also deletes the contents at the last position of the array
            delete _allTokensIndex[tokenId];
            _allTokens.pop();
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[46] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721URIStorage.sol)
    
    pragma solidity ^0.8.0;
    
    import "../ERC721Upgradeable.sol";
    import "../../../interfaces/IERC4906Upgradeable.sol";
    import {Initializable} from "../../../proxy/utils/Initializable.sol";
    
    /**
     * @dev ERC721 token with storage based token URI management.
     */
    abstract contract ERC721URIStorageUpgradeable is Initializable, IERC4906Upgradeable, ERC721Upgradeable {
        using StringsUpgradeable for uint256;
    
        // Optional mapping for token URIs
        mapping(uint256 => string) private _tokenURIs;
    
        function __ERC721URIStorage_init() internal onlyInitializing {
        }
    
        function __ERC721URIStorage_init_unchained() internal onlyInitializing {
        }
        /**
         * @dev See {IERC165-supportsInterface}
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Upgradeable, IERC165Upgradeable) returns (bool) {
            return interfaceId == bytes4(0x49064906) || super.supportsInterface(interfaceId);
        }
    
        /**
         * @dev See {IERC721Metadata-tokenURI}.
         */
        function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
            _requireMinted(tokenId);
    
            string memory _tokenURI = _tokenURIs[tokenId];
            string memory base = _baseURI();
    
            // If there is no base URI, return the token URI.
            if (bytes(base).length == 0) {
                return _tokenURI;
            }
            // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
            if (bytes(_tokenURI).length > 0) {
                return string(abi.encodePacked(base, _tokenURI));
            }
    
            return super.tokenURI(tokenId);
        }
    
        /**
         * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
         *
         * Emits {MetadataUpdate}.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
            require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
            _tokenURIs[tokenId] = _tokenURI;
    
            emit MetadataUpdate(tokenId);
        }
    
        /**
         * @dev See {ERC721-_burn}. This override additionally checks to see if a
         * token-specific URI was set for the token, and if so, it deletes the token URI from
         * the storage mapping.
         */
        function _burn(uint256 tokenId) internal virtual override {
            super._burn(tokenId);
    
            if (bytes(_tokenURIs[tokenId]).length != 0) {
                delete _tokenURIs[tokenId];
            }
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[49] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../IERC721Upgradeable.sol";
    
    /**
     * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
     * @dev See https://eips.ethereum.org/EIPS/eip-721
     */
    interface IERC721EnumerableUpgradeable is IERC721Upgradeable {
        /**
         * @dev Returns the total amount of tokens stored by the contract.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
         * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
         */
        function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
    
        /**
         * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
         * Use along with {totalSupply} to enumerate all tokens.
         */
        function tokenByIndex(uint256 index) external view returns (uint256);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
    
    pragma solidity ^0.8.0;
    
    import "../IERC721Upgradeable.sol";
    
    /**
     * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
     * @dev See https://eips.ethereum.org/EIPS/eip-721
     */
    interface IERC721MetadataUpgradeable is IERC721Upgradeable {
        /**
         * @dev Returns the token collection name.
         */
        function name() external view returns (string memory);
    
        /**
         * @dev Returns the token collection symbol.
         */
        function symbol() external view returns (string memory);
    
        /**
         * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
         */
        function tokenURI(uint256 tokenId) external view returns (string memory);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @title ERC721 token receiver interface
     * @dev Interface for any contract that wants to support safeTransfers
     * from ERC721 asset contracts.
     */
    interface IERC721ReceiverUpgradeable {
        /**
         * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
         * by `operator` from `from`, this function is called.
         *
         * It must return its Solidity selector to confirm the token transfer.
         * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
         *
         * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
         */
        function onERC721Received(
            address operator,
            address from,
            uint256 tokenId,
            bytes calldata data
        ) external returns (bytes4);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
    
    pragma solidity ^0.8.0;
    
    import "../../utils/introspection/IERC165Upgradeable.sol";
    
    /**
     * @dev Required interface of an ERC721 compliant contract.
     */
    interface IERC721Upgradeable is IERC165Upgradeable {
        /**
         * @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: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
         * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
         * understand this adds an external call which potentially creates a reentrancy vulnerability.
         *
         * 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);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
    
    pragma solidity ^0.8.1;
    
    /**
     * @dev Collection of functions related to the address type
     */
    library AddressUpgradeable {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         *
         * Furthermore, `isContract` will also return true if the target contract within
         * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
         * which only has an effect at the end of a transaction.
         * ====
         *
         * [IMPORTANT]
         * ====
         * You shouldn't rely on `isContract` to protect against flash loan attacks!
         *
         * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
         * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
         * constructor.
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // This method relies on extcodesize/address.code.length, which returns 0
            // for contracts in construction, since the code is only stored at the end
            // of the constructor execution.
    
            return account.code.length > 0;
        }
    
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
    
            (bool success, ) = recipient.call{value: amount}("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
    
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason, it is bubbled up by this
         * function (like regular Solidity function calls).
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, "Address: low-level call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
         * `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
         * with `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(address(this).balance >= value, "Address: insufficient balance for call");
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
         * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
         *
         * _Available since v4.8._
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            if (success) {
                if (returndata.length == 0) {
                    // only check isContract if the call was successful and the return data is empty
                    // otherwise we already know that it was a contract
                    require(isContract(target), "Address: call to non-contract");
                }
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        /**
         * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
         * revert reason or using the provided one.
         *
         * _Available since v4.3._
         */
        function verifyCallResult(
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal pure returns (bytes memory) {
            if (success) {
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        function _revert(bytes memory returndata, string memory errorMessage) private pure {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
    
    pragma solidity ^0.8.0;
    import {Initializable} from "../proxy/utils/Initializable.sol";
    
    /**
     * @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 ContextUpgradeable is Initializable {
        function __Context_init() internal onlyInitializing {
        }
    
        function __Context_init_unchained() internal onlyInitializing {
        }
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[50] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @title Counters
     * @author Matt Condon (@shrugs)
     * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
     * of elements in a mapping, issuing ERC721 ids, or counting request ids.
     *
     * Include with `using Counters for Counters.Counter;`
     */
    library CountersUpgradeable {
        struct Counter {
            // This variable should never be directly accessed by users of the library: interactions must be restricted to
            // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
            // this feature: see https://github.com/ethereum/solidity/issues/4637
            uint256 _value; // default: 0
        }
    
        function current(Counter storage counter) internal view returns (uint256) {
            return counter._value;
        }
    
        function increment(Counter storage counter) internal {
            unchecked {
                counter._value += 1;
            }
        }
    
        function decrement(Counter storage counter) internal {
            uint256 value = counter._value;
            require(value > 0, "Counter: decrement overflow");
            unchecked {
                counter._value = value - 1;
            }
        }
    
        function reset(Counter storage counter) internal {
            counter._value = 0;
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
    
    pragma solidity ^0.8.0;
    
    import "../StringsUpgradeable.sol";
    
    /**
     * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
     *
     * These functions can be used to verify that a message was signed by the holder
     * of the private keys of a given address.
     */
    library ECDSAUpgradeable {
        enum RecoverError {
            NoError,
            InvalidSignature,
            InvalidSignatureLength,
            InvalidSignatureS,
            InvalidSignatureV // Deprecated in v4.8
        }
    
        function _throwError(RecoverError error) private pure {
            if (error == RecoverError.NoError) {
                return; // no error: do nothing
            } else if (error == RecoverError.InvalidSignature) {
                revert("ECDSA: invalid signature");
            } else if (error == RecoverError.InvalidSignatureLength) {
                revert("ECDSA: invalid signature length");
            } else if (error == RecoverError.InvalidSignatureS) {
                revert("ECDSA: invalid signature 's' value");
            }
        }
    
        /**
         * @dev Returns the address that signed a hashed message (`hash`) with
         * `signature` or error string. This address can then be used for verification purposes.
         *
         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
         * this function rejects them by requiring the `s` value to be in the lower
         * half order, and the `v` value to be either 27 or 28.
         *
         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
         * verification to be secure: it is possible to craft signatures that
         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
         * this is by receiving a hash of the original message (which may otherwise
         * be too long), and then calling {toEthSignedMessageHash} on it.
         *
         * Documentation for signature generation:
         * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
         * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
            if (signature.length == 65) {
                bytes32 r;
                bytes32 s;
                uint8 v;
                // ecrecover takes the signature parameters, and the only way to get them
                // currently is to use assembly.
                /// @solidity memory-safe-assembly
                assembly {
                    r := mload(add(signature, 0x20))
                    s := mload(add(signature, 0x40))
                    v := byte(0, mload(add(signature, 0x60)))
                }
                return tryRecover(hash, v, r, s);
            } else {
                return (address(0), RecoverError.InvalidSignatureLength);
            }
        }
    
        /**
         * @dev Returns the address that signed a hashed message (`hash`) with
         * `signature`. This address can then be used for verification purposes.
         *
         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
         * this function rejects them by requiring the `s` value to be in the lower
         * half order, and the `v` value to be either 27 or 28.
         *
         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
         * verification to be secure: it is possible to craft signatures that
         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
         * this is by receiving a hash of the original message (which may otherwise
         * be too long), and then calling {toEthSignedMessageHash} on it.
         */
        function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, signature);
            _throwError(error);
            return recovered;
        }
    
        /**
         * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
         *
         * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    
        /**
         * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
         *
         * _Available since v4.2._
         */
        function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, r, vs);
            _throwError(error);
            return recovered;
        }
    
        /**
         * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
         * `r` and `s` signature fields separately.
         *
         * _Available since v4.3._
         */
        function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
            // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
            // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
            // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
            // signatures from current libraries generate a unique signature with an s-value in the lower half order.
            //
            // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
            // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
            // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
            // these malleable signatures as well.
            if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                return (address(0), RecoverError.InvalidSignatureS);
            }
    
            // If the signature is valid (and not malleable), return the signer address
            address signer = ecrecover(hash, v, r, s);
            if (signer == address(0)) {
                return (address(0), RecoverError.InvalidSignature);
            }
    
            return (signer, RecoverError.NoError);
        }
    
        /**
         * @dev Overload of {ECDSA-recover} that receives the `v`,
         * `r` and `s` signature fields separately.
         */
        function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
            (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
            _throwError(error);
            return recovered;
        }
    
        /**
         * @dev Returns an Ethereum Signed Message, created from a `hash`. This
         * produces hash corresponding to the one signed with the
         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
         * JSON-RPC method as part of EIP-191.
         *
         * See {recover}.
         */
        function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
            // 32 is the length in bytes of hash,
            // enforced by the type signature above
            /// @solidity memory-safe-assembly
            assembly {
                mstore(0x00, "\x19Ethereum Signed Message:\n32")
                mstore(0x1c, hash)
                message := keccak256(0x00, 0x3c)
            }
        }
    
        /**
         * @dev Returns an Ethereum Signed Message, created from `s`. This
         * produces hash corresponding to the one signed with the
         * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
         * JSON-RPC method as part of EIP-191.
         *
         * See {recover}.
         */
        function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", StringsUpgradeable.toString(s.length), s));
        }
    
        /**
         * @dev Returns an Ethereum Signed Typed Data, created from a
         * `domainSeparator` and a `structHash`. This produces hash corresponding
         * to the one signed with the
         * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
         * JSON-RPC method as part of EIP-712.
         *
         * See {recover}.
         */
        function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
            /// @solidity memory-safe-assembly
            assembly {
                let ptr := mload(0x40)
                mstore(ptr, "\x19\x01")
                mstore(add(ptr, 0x02), domainSeparator)
                mstore(add(ptr, 0x22), structHash)
                data := keccak256(ptr, 0x42)
            }
        }
    
        /**
         * @dev Returns an Ethereum Signed Data with intended validator, created from a
         * `validator` and `data` according to the version 0 of EIP-191.
         *
         * See {recover}.
         */
        function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked("\x19\x00", validator, data));
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol)
    
    pragma solidity ^0.8.8;
    
    import "./ECDSAUpgradeable.sol";
    import "../../interfaces/IERC5267Upgradeable.sol";
    import {Initializable} from "../../proxy/utils/Initializable.sol";
    
    /**
     * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
     *
     * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
     * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
     * they need in their contracts using a combination of `abi.encode` and `keccak256`.
     *
     * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
     * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
     * ({_hashTypedDataV4}).
     *
     * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
     * the chain id to protect against replay attacks on an eventual fork of the chain.
     *
     * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
     * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
     *
     * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
     * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the
     * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
     *
     * _Available since v3.4._
     *
     * @custom:storage-size 52
     */
    abstract contract EIP712Upgradeable is Initializable, IERC5267Upgradeable {
        bytes32 private constant _TYPE_HASH =
            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
    
        /// @custom:oz-renamed-from _HASHED_NAME
        bytes32 private _hashedName;
        /// @custom:oz-renamed-from _HASHED_VERSION
        bytes32 private _hashedVersion;
    
        string private _name;
        string private _version;
    
        /**
         * @dev Initializes the domain separator and parameter caches.
         *
         * The meaning of `name` and `version` is specified in
         * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
         *
         * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
         * - `version`: the current major version of the signing domain.
         *
         * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
         * contract upgrade].
         */
        function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
            __EIP712_init_unchained(name, version);
        }
    
        function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
            _name = name;
            _version = version;
    
            // Reset prior values in storage if upgrading
            _hashedName = 0;
            _hashedVersion = 0;
        }
    
        /**
         * @dev Returns the domain separator for the current chain.
         */
        function _domainSeparatorV4() internal view returns (bytes32) {
            return _buildDomainSeparator();
        }
    
        function _buildDomainSeparator() private view returns (bytes32) {
            return keccak256(abi.encode(_TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
        }
    
        /**
         * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
         * function returns the hash of the fully encoded EIP712 message for this domain.
         *
         * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
         *
         * ```solidity
         * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
         *     keccak256("Mail(address to,string contents)"),
         *     mailTo,
         *     keccak256(bytes(mailContents))
         * )));
         * address signer = ECDSA.recover(digest, signature);
         * ```
         */
        function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
            return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(), structHash);
        }
    
        /**
         * @dev See {EIP-5267}.
         *
         * _Available since v4.9._
         */
        function eip712Domain()
            public
            view
            virtual
            override
            returns (
                bytes1 fields,
                string memory name,
                string memory version,
                uint256 chainId,
                address verifyingContract,
                bytes32 salt,
                uint256[] memory extensions
            )
        {
            // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
            // and the EIP712 domain is not reliable, as it will be missing name and version.
            require(_hashedName == 0 && _hashedVersion == 0, "EIP712: Uninitialized");
    
            return (
                hex"0f", // 01111
                _EIP712Name(),
                _EIP712Version(),
                block.chainid,
                address(this),
                bytes32(0),
                new uint256[](0)
            );
        }
    
        /**
         * @dev The name parameter for the EIP712 domain.
         *
         * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
         * are a concern.
         */
        function _EIP712Name() internal virtual view returns (string memory) {
            return _name;
        }
    
        /**
         * @dev The version parameter for the EIP712 domain.
         *
         * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
         * are a concern.
         */
        function _EIP712Version() internal virtual view returns (string memory) {
            return _version;
        }
    
        /**
         * @dev The hash of the name parameter for the EIP712 domain.
         *
         * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
         */
        function _EIP712NameHash() internal view returns (bytes32) {
            string memory name = _EIP712Name();
            if (bytes(name).length > 0) {
                return keccak256(bytes(name));
            } else {
                // If the name is empty, the contract may have been upgraded without initializing the new storage.
                // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
                bytes32 hashedName = _hashedName;
                if (hashedName != 0) {
                    return hashedName;
                } else {
                    return keccak256("");
                }
            }
        }
    
        /**
         * @dev The hash of the version parameter for the EIP712 domain.
         *
         * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
         */
        function _EIP712VersionHash() internal view returns (bytes32) {
            string memory version = _EIP712Version();
            if (bytes(version).length > 0) {
                return keccak256(bytes(version));
            } else {
                // If the version is empty, the contract may have been upgraded without initializing the new storage.
                // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
                bytes32 hashedVersion = _hashedVersion;
                if (hashedVersion != 0) {
                    return hashedVersion;
                } else {
                    return keccak256("");
                }
            }
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[48] private __gap;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IERC165Upgradeable.sol";
    import {Initializable} from "../../proxy/utils/Initializable.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 ERC165Upgradeable is Initializable, IERC165Upgradeable {
        function __ERC165_init() internal onlyInitializing {
        }
    
        function __ERC165_init_unchained() internal onlyInitializing {
        }
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
            return interfaceId == type(IERC165Upgradeable).interfaceId;
        }
    
        /**
         * @dev This empty reserved space is put in place to allow future versions to add new
         * variables without shifting down storage in the inheritance chain.
         * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
         */
        uint256[50] private __gap;
    }

    // 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 IERC165Upgradeable {
        /**
         * @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);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Standard math utilities missing in the Solidity language.
     */
    library MathUpgradeable {
        enum Rounding {
            Down, // Toward negative infinity
            Up, // Toward infinity
            Zero // Toward zero
        }
    
        /**
         * @dev Returns the largest of two numbers.
         */
        function max(uint256 a, uint256 b) internal pure returns (uint256) {
            return a > b ? a : b;
        }
    
        /**
         * @dev Returns the smallest of two numbers.
         */
        function min(uint256 a, uint256 b) internal pure returns (uint256) {
            return a < b ? a : b;
        }
    
        /**
         * @dev Returns the average of two numbers. The result is rounded towards
         * zero.
         */
        function average(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b) / 2 can overflow.
            return (a & b) + (a ^ b) / 2;
        }
    
        /**
         * @dev Returns the ceiling of the division of two numbers.
         *
         * This differs from standard division with `/` in that it rounds up instead
         * of rounding down.
         */
        function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b - 1) / b can overflow on addition, so we distribute.
            return a == 0 ? 0 : (a - 1) / b + 1;
        }
    
        /**
         * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
         * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
         * with further edits by Uniswap Labs also under MIT license.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
            unchecked {
                // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                // variables such that product = prod1 * 2^256 + prod0.
                uint256 prod0; // Least significant 256 bits of the product
                uint256 prod1; // Most significant 256 bits of the product
                assembly {
                    let mm := mulmod(x, y, not(0))
                    prod0 := mul(x, y)
                    prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                }
    
                // Handle non-overflow cases, 256 by 256 division.
                if (prod1 == 0) {
                    // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                    // The surrounding unchecked block does not change this fact.
                    // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                    return prod0 / denominator;
                }
    
                // Make sure the result is less than 2^256. Also prevents denominator == 0.
                require(denominator > prod1, "Math: mulDiv overflow");
    
                ///////////////////////////////////////////////
                // 512 by 256 division.
                ///////////////////////////////////////////////
    
                // Make division exact by subtracting the remainder from [prod1 prod0].
                uint256 remainder;
                assembly {
                    // Compute remainder using mulmod.
                    remainder := mulmod(x, y, denominator)
    
                    // Subtract 256 bit number from 512 bit number.
                    prod1 := sub(prod1, gt(remainder, prod0))
                    prod0 := sub(prod0, remainder)
                }
    
                // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                // See https://cs.stackexchange.com/q/138556/92363.
    
                // Does not overflow because the denominator cannot be zero at this stage in the function.
                uint256 twos = denominator & (~denominator + 1);
                assembly {
                    // Divide denominator by twos.
                    denominator := div(denominator, twos)
    
                    // Divide [prod1 prod0] by twos.
                    prod0 := div(prod0, twos)
    
                    // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                    twos := add(div(sub(0, twos), twos), 1)
                }
    
                // Shift in bits from prod1 into prod0.
                prod0 |= prod1 * twos;
    
                // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                // four bits. That is, denominator * inv = 1 mod 2^4.
                uint256 inverse = (3 * denominator) ^ 2;
    
                // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                // in modular arithmetic, doubling the correct bits in each step.
                inverse *= 2 - denominator * inverse; // inverse mod 2^8
                inverse *= 2 - denominator * inverse; // inverse mod 2^16
                inverse *= 2 - denominator * inverse; // inverse mod 2^32
                inverse *= 2 - denominator * inverse; // inverse mod 2^64
                inverse *= 2 - denominator * inverse; // inverse mod 2^128
                inverse *= 2 - denominator * inverse; // inverse mod 2^256
    
                // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                // is no longer required.
                result = prod0 * inverse;
                return result;
            }
        }
    
        /**
         * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
            uint256 result = mulDiv(x, y, denominator);
            if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                result += 1;
            }
            return result;
        }
    
        /**
         * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
         *
         * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
         */
        function sqrt(uint256 a) internal pure returns (uint256) {
            if (a == 0) {
                return 0;
            }
    
            // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
            //
            // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
            // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
            //
            // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
            // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
            // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
            //
            // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
            uint256 result = 1 << (log2(a) >> 1);
    
            // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
            // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
            // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
            // into the expected uint128 result.
            unchecked {
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                return min(result, a / result);
            }
        }
    
        /**
         * @notice Calculates sqrt(a), following the selected rounding direction.
         */
        function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = sqrt(a);
                return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 2, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 128;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 64;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 32;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 16;
                }
                if (value >> 8 > 0) {
                    value >>= 8;
                    result += 8;
                }
                if (value >> 4 > 0) {
                    value >>= 4;
                    result += 4;
                }
                if (value >> 2 > 0) {
                    value >>= 2;
                    result += 2;
                }
                if (value >> 1 > 0) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log2(value);
                return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 10, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >= 10 ** 64) {
                    value /= 10 ** 64;
                    result += 64;
                }
                if (value >= 10 ** 32) {
                    value /= 10 ** 32;
                    result += 32;
                }
                if (value >= 10 ** 16) {
                    value /= 10 ** 16;
                    result += 16;
                }
                if (value >= 10 ** 8) {
                    value /= 10 ** 8;
                    result += 8;
                }
                if (value >= 10 ** 4) {
                    value /= 10 ** 4;
                    result += 4;
                }
                if (value >= 10 ** 2) {
                    value /= 10 ** 2;
                    result += 2;
                }
                if (value >= 10 ** 1) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log10(value);
                return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 256, rounded down, of a positive value.
         * Returns 0 if given 0.
         *
         * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
         */
        function log256(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 16;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 8;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 4;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 2;
                }
                if (value >> 8 > 0) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log256(value);
                return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
            }
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Standard signed math utilities missing in the Solidity language.
     */
    library SignedMathUpgradeable {
        /**
         * @dev Returns the largest of two signed numbers.
         */
        function max(int256 a, int256 b) internal pure returns (int256) {
            return a > b ? a : b;
        }
    
        /**
         * @dev Returns the smallest of two signed numbers.
         */
        function min(int256 a, int256 b) internal pure returns (int256) {
            return a < b ? a : b;
        }
    
        /**
         * @dev Returns the average of two signed numbers without overflow.
         * The result is rounded towards zero.
         */
        function average(int256 a, int256 b) internal pure returns (int256) {
            // Formula from the book "Hacker's Delight"
            int256 x = (a & b) + ((a ^ b) >> 1);
            return x + (int256(uint256(x) >> 255) & (a ^ b));
        }
    
        /**
         * @dev Returns the absolute unsigned value of a signed value.
         */
        function abs(int256 n) internal pure returns (uint256) {
            unchecked {
                // must be unchecked in order to support `n = type(int256).min`
                return uint256(n >= 0 ? n : -n);
            }
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
    // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Library for reading and writing primitive types to specific storage slots.
     *
     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
     * This library helps with reading and writing to such slots without the need for inline assembly.
     *
     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
     *
     * Example usage to set ERC1967 implementation slot:
     * ```solidity
     * contract ERC1967 {
     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
     *
     *     function _getImplementation() internal view returns (address) {
     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
     *     }
     *
     *     function _setImplementation(address newImplementation) internal {
     *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
     *     }
     * }
     * ```
     *
     * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
     * _Available since v4.9 for `string`, `bytes`._
     */
    library StorageSlotUpgradeable {
        struct AddressSlot {
            address value;
        }
    
        struct BooleanSlot {
            bool value;
        }
    
        struct Bytes32Slot {
            bytes32 value;
        }
    
        struct Uint256Slot {
            uint256 value;
        }
    
        struct StringSlot {
            string value;
        }
    
        struct BytesSlot {
            bytes value;
        }
    
        /**
         * @dev Returns an `AddressSlot` with member `value` located at `slot`.
         */
        function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
         */
        function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
         */
        function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
         */
        function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `StringSlot` with member `value` located at `slot`.
         */
        function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
         */
        function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := store.slot
            }
        }
    
        /**
         * @dev Returns an `BytesSlot` with member `value` located at `slot`.
         */
        function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
         */
        function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
            /// @solidity memory-safe-assembly
            assembly {
                r.slot := store.slot
            }
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
    
    pragma solidity ^0.8.0;
    
    import "./math/MathUpgradeable.sol";
    import "./math/SignedMathUpgradeable.sol";
    
    /**
     * @dev String operations.
     */
    library StringsUpgradeable {
        bytes16 private constant _SYMBOLS = "0123456789abcdef";
        uint8 private constant _ADDRESS_LENGTH = 20;
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            unchecked {
                uint256 length = MathUpgradeable.log10(value) + 1;
                string memory buffer = new string(length);
                uint256 ptr;
                /// @solidity memory-safe-assembly
                assembly {
                    ptr := add(buffer, add(32, length))
                }
                while (true) {
                    ptr--;
                    /// @solidity memory-safe-assembly
                    assembly {
                        mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                    }
                    value /= 10;
                    if (value == 0) break;
                }
                return buffer;
            }
        }
    
        /**
         * @dev Converts a `int256` to its ASCII `string` decimal representation.
         */
        function toString(int256 value) internal pure returns (string memory) {
            return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMathUpgradeable.abs(value))));
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            unchecked {
                return toHexString(value, MathUpgradeable.log256(value) + 1);
            }
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
    
        /**
         * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
         */
        function toHexString(address addr) internal pure returns (string memory) {
            return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
        }
    
        /**
         * @dev Returns true if the two strings are equal.
         */
        function equal(string memory a, string memory b) internal pure returns (bool) {
            return keccak256(bytes(a)) == keccak256(bytes(b));
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1155.sol)
    
    pragma solidity ^0.8.0;
    
    import "../token/ERC1155/IERC1155.sol";

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)
    
    pragma solidity ^0.8.0;
    
    import "../token/ERC20/IERC20.sol";

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721.sol)
    
    pragma solidity ^0.8.0;
    
    import "../token/ERC721/IERC721.sol";

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Enumerable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../token/ERC721/extensions/IERC721Enumerable.sol";

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (proxy/Clones.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
     * deploying minimal proxy contracts, also known as "clones".
     *
     * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
     * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
     *
     * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
     * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
     * deterministic method.
     *
     * _Available since v3.4._
     */
    library Clones {
        /**
         * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
         *
         * This function uses the create opcode, which should never revert.
         */
        function clone(address implementation) internal returns (address instance) {
            /// @solidity memory-safe-assembly
            assembly {
                // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
                // of the `implementation` address with the bytecode before the address.
                mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
                // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
                mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
                instance := create(0, 0x09, 0x37)
            }
            require(instance != address(0), "ERC1167: create failed");
        }
    
        /**
         * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
         *
         * This function uses the create2 opcode and a `salt` to deterministically deploy
         * the clone. Using the same `implementation` and `salt` multiple time will revert, since
         * the clones cannot be deployed twice at the same address.
         */
        function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
            /// @solidity memory-safe-assembly
            assembly {
                // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
                // of the `implementation` address with the bytecode before the address.
                mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
                // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
                mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
                instance := create2(0, 0x09, 0x37, salt)
            }
            require(instance != address(0), "ERC1167: create2 failed");
        }
    
        /**
         * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
         */
        function predictDeterministicAddress(
            address implementation,
            bytes32 salt,
            address deployer
        ) internal pure returns (address predicted) {
            /// @solidity memory-safe-assembly
            assembly {
                let ptr := mload(0x40)
                mstore(add(ptr, 0x38), deployer)
                mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
                mstore(add(ptr, 0x14), implementation)
                mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
                mstore(add(ptr, 0x58), salt)
                mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
                predicted := keccak256(add(ptr, 0x43), 0x55)
            }
        }
    
        /**
         * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
         */
        function predictDeterministicAddress(
            address implementation,
            bytes32 salt
        ) internal view returns (address predicted) {
            return predictDeterministicAddress(implementation, salt, address(this));
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.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;
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves `amount` tokens from the caller's account to `to`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address to, uint256 amount) external returns (bool);
    
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
    
        /**
         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 amount) external returns (bool);
    
        /**
         * @dev Moves `amount` tokens from `from` to `to` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address from, address to, uint256 amount) external returns (bool);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../IERC721.sol";
    
    /**
     * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
     * @dev See https://eips.ethereum.org/EIPS/eip-721
     */
    interface IERC721Enumerable is IERC721 {
        /**
         * @dev Returns the total amount of tokens stored by the contract.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
         * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
         */
        function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
    
        /**
         * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
         * Use along with {totalSupply} to enumerate all tokens.
         */
        function tokenByIndex(uint256 index) external view returns (uint256);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.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: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
         * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
         * understand this adds an external call which potentially creates a reentrancy vulnerability.
         *
         * 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);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @title ERC721 token receiver interface
     * @dev Interface for any contract that wants to support safeTransfers
     * from ERC721 asset contracts.
     */
    interface IERC721Receiver {
        /**
         * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
         * by `operator` from `from`, this function is called.
         *
         * It must return its Solidity selector to confirm the token transfer.
         * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
         *
         * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
         */
        function onERC721Received(
            address operator,
            address from,
            uint256 tokenId,
            bytes calldata data
        ) external returns (bytes4);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/utils/ERC721Holder.sol)
    
    pragma solidity ^0.8.0;
    
    import "../IERC721Receiver.sol";
    
    /**
     * @dev Implementation of the {IERC721Receiver} interface.
     *
     * Accepts all token transfers.
     * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}.
     */
    contract ERC721Holder is IERC721Receiver {
        /**
         * @dev See {IERC721Receiver-onERC721Received}.
         *
         * Always returns `IERC721Receiver.onERC721Received.selector`.
         */
        function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
            return this.onERC721Received.selector;
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.4) (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;
        }
    
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }

    // 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);
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Standard math utilities missing in the Solidity language.
     */
    library Math {
        enum Rounding {
            Down, // Toward negative infinity
            Up, // Toward infinity
            Zero // Toward zero
        }
    
        /**
         * @dev Returns the largest of two numbers.
         */
        function max(uint256 a, uint256 b) internal pure returns (uint256) {
            return a > b ? a : b;
        }
    
        /**
         * @dev Returns the smallest of two numbers.
         */
        function min(uint256 a, uint256 b) internal pure returns (uint256) {
            return a < b ? a : b;
        }
    
        /**
         * @dev Returns the average of two numbers. The result is rounded towards
         * zero.
         */
        function average(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b) / 2 can overflow.
            return (a & b) + (a ^ b) / 2;
        }
    
        /**
         * @dev Returns the ceiling of the division of two numbers.
         *
         * This differs from standard division with `/` in that it rounds up instead
         * of rounding down.
         */
        function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b - 1) / b can overflow on addition, so we distribute.
            return a == 0 ? 0 : (a - 1) / b + 1;
        }
    
        /**
         * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
         * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
         * with further edits by Uniswap Labs also under MIT license.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
            unchecked {
                // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                // variables such that product = prod1 * 2^256 + prod0.
                uint256 prod0; // Least significant 256 bits of the product
                uint256 prod1; // Most significant 256 bits of the product
                assembly {
                    let mm := mulmod(x, y, not(0))
                    prod0 := mul(x, y)
                    prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                }
    
                // Handle non-overflow cases, 256 by 256 division.
                if (prod1 == 0) {
                    // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                    // The surrounding unchecked block does not change this fact.
                    // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                    return prod0 / denominator;
                }
    
                // Make sure the result is less than 2^256. Also prevents denominator == 0.
                require(denominator > prod1, "Math: mulDiv overflow");
    
                ///////////////////////////////////////////////
                // 512 by 256 division.
                ///////////////////////////////////////////////
    
                // Make division exact by subtracting the remainder from [prod1 prod0].
                uint256 remainder;
                assembly {
                    // Compute remainder using mulmod.
                    remainder := mulmod(x, y, denominator)
    
                    // Subtract 256 bit number from 512 bit number.
                    prod1 := sub(prod1, gt(remainder, prod0))
                    prod0 := sub(prod0, remainder)
                }
    
                // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                // See https://cs.stackexchange.com/q/138556/92363.
    
                // Does not overflow because the denominator cannot be zero at this stage in the function.
                uint256 twos = denominator & (~denominator + 1);
                assembly {
                    // Divide denominator by twos.
                    denominator := div(denominator, twos)
    
                    // Divide [prod1 prod0] by twos.
                    prod0 := div(prod0, twos)
    
                    // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                    twos := add(div(sub(0, twos), twos), 1)
                }
    
                // Shift in bits from prod1 into prod0.
                prod0 |= prod1 * twos;
    
                // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                // four bits. That is, denominator * inv = 1 mod 2^4.
                uint256 inverse = (3 * denominator) ^ 2;
    
                // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                // in modular arithmetic, doubling the correct bits in each step.
                inverse *= 2 - denominator * inverse; // inverse mod 2^8
                inverse *= 2 - denominator * inverse; // inverse mod 2^16
                inverse *= 2 - denominator * inverse; // inverse mod 2^32
                inverse *= 2 - denominator * inverse; // inverse mod 2^64
                inverse *= 2 - denominator * inverse; // inverse mod 2^128
                inverse *= 2 - denominator * inverse; // inverse mod 2^256
    
                // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                // is no longer required.
                result = prod0 * inverse;
                return result;
            }
        }
    
        /**
         * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
            uint256 result = mulDiv(x, y, denominator);
            if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                result += 1;
            }
            return result;
        }
    
        /**
         * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
         *
         * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
         */
        function sqrt(uint256 a) internal pure returns (uint256) {
            if (a == 0) {
                return 0;
            }
    
            // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
            //
            // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
            // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
            //
            // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
            // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
            // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
            //
            // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
            uint256 result = 1 << (log2(a) >> 1);
    
            // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
            // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
            // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
            // into the expected uint128 result.
            unchecked {
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                return min(result, a / result);
            }
        }
    
        /**
         * @notice Calculates sqrt(a), following the selected rounding direction.
         */
        function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = sqrt(a);
                return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 2, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 128;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 64;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 32;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 16;
                }
                if (value >> 8 > 0) {
                    value >>= 8;
                    result += 8;
                }
                if (value >> 4 > 0) {
                    value >>= 4;
                    result += 4;
                }
                if (value >> 2 > 0) {
                    value >>= 2;
                    result += 2;
                }
                if (value >> 1 > 0) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log2(value);
                return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 10, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >= 10 ** 64) {
                    value /= 10 ** 64;
                    result += 64;
                }
                if (value >= 10 ** 32) {
                    value /= 10 ** 32;
                    result += 32;
                }
                if (value >= 10 ** 16) {
                    value /= 10 ** 16;
                    result += 16;
                }
                if (value >= 10 ** 8) {
                    value /= 10 ** 8;
                    result += 8;
                }
                if (value >= 10 ** 4) {
                    value /= 10 ** 4;
                    result += 4;
                }
                if (value >= 10 ** 2) {
                    value /= 10 ** 2;
                    result += 2;
                }
                if (value >= 10 ** 1) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log10(value);
                return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 256, rounded down, of a positive value.
         * Returns 0 if given 0.
         *
         * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
         */
        function log256(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 16;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 8;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 4;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 2;
                }
                if (value >> 8 > 0) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log256(value);
                return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
            }
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Standard signed math utilities missing in the Solidity language.
     */
    library SignedMath {
        /**
         * @dev Returns the largest of two signed numbers.
         */
        function max(int256 a, int256 b) internal pure returns (int256) {
            return a > b ? a : b;
        }
    
        /**
         * @dev Returns the smallest of two signed numbers.
         */
        function min(int256 a, int256 b) internal pure returns (int256) {
            return a < b ? a : b;
        }
    
        /**
         * @dev Returns the average of two signed numbers without overflow.
         * The result is rounded towards zero.
         */
        function average(int256 a, int256 b) internal pure returns (int256) {
            // Formula from the book "Hacker's Delight"
            int256 x = (a & b) + ((a ^ b) >> 1);
            return x + (int256(uint256(x) >> 255) & (a ^ b));
        }
    
        /**
         * @dev Returns the absolute unsigned value of a signed value.
         */
        function abs(int256 n) internal pure returns (uint256) {
            unchecked {
                // must be unchecked in order to support `n = type(int256).min`
                return uint256(n >= 0 ? n : -n);
            }
        }
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
    
    pragma solidity ^0.8.0;
    
    import "./math/Math.sol";
    import "./math/SignedMath.sol";
    
    /**
     * @dev String operations.
     */
    library Strings {
        bytes16 private constant _SYMBOLS = "0123456789abcdef";
        uint8 private constant _ADDRESS_LENGTH = 20;
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            unchecked {
                uint256 length = Math.log10(value) + 1;
                string memory buffer = new string(length);
                uint256 ptr;
                /// @solidity memory-safe-assembly
                assembly {
                    ptr := add(buffer, add(32, length))
                }
                while (true) {
                    ptr--;
                    /// @solidity memory-safe-assembly
                    assembly {
                        mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                    }
                    value /= 10;
                    if (value == 0) break;
                }
                return buffer;
            }
        }
    
        /**
         * @dev Converts a `int256` to its ASCII `string` decimal representation.
         */
        function toString(int256 value) internal pure returns (string memory) {
            return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            unchecked {
                return toHexString(value, Math.log256(value) + 1);
            }
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
    
        /**
         * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
         */
        function toHexString(address addr) internal pure returns (string memory) {
            return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
        }
    
        /**
         * @dev Returns true if the two strings are equal.
         */
        function equal(string memory a, string memory b) internal pure returns (bool) {
            return keccak256(bytes(a)) == keccak256(bytes(b));
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    import { IAccessControl } from './IAccessControl.sol';
    import { AccessControlInternal } from './AccessControlInternal.sol';
    
    /**
     * @title Role-based access control system
     * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
     */
    abstract contract AccessControl is IAccessControl, AccessControlInternal {
        /**
         * @inheritdoc IAccessControl
         */
        function grantRole(
            bytes32 role,
            address account
        ) external onlyRole(_getRoleAdmin(role)) {
            _grantRole(role, account);
        }
    
        /**
         * @inheritdoc IAccessControl
         */
        function hasRole(
            bytes32 role,
            address account
        ) external view returns (bool) {
            return _hasRole(role, account);
        }
    
        /**
         * @inheritdoc IAccessControl
         */
        function getRoleAdmin(bytes32 role) external view returns (bytes32) {
            return _getRoleAdmin(role);
        }
    
        /**
         * @inheritdoc IAccessControl
         */
        function revokeRole(
            bytes32 role,
            address account
        ) external onlyRole(_getRoleAdmin(role)) {
            _revokeRole(role, account);
        }
    
        /**
         * @inheritdoc IAccessControl
         */
        function renounceRole(bytes32 role) external {
            _renounceRole(role);
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    import { EnumerableSet } from '../../data/EnumerableSet.sol';
    import { AddressUtils } from '../../utils/AddressUtils.sol';
    import { UintUtils } from '../../utils/UintUtils.sol';
    import { IAccessControlInternal } from './IAccessControlInternal.sol';
    import { AccessControlStorage } from './AccessControlStorage.sol';
    
    /**
     * @title Role-based access control system
     * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
     */
    abstract contract AccessControlInternal is IAccessControlInternal {
        using AddressUtils for address;
        using EnumerableSet for EnumerableSet.AddressSet;
        using UintUtils for uint256;
    
        modifier onlyRole(bytes32 role) {
            _checkRole(role);
            _;
        }
    
        /*
         * @notice query whether role is assigned to account
         * @param role role to query
         * @param account account to query
         * @return whether role is assigned to account
         */
        function _hasRole(
            bytes32 role,
            address account
        ) internal view virtual returns (bool) {
            return
                AccessControlStorage.layout().roles[role].members.contains(account);
        }
    
        /**
         * @notice revert if sender does not have given role
         * @param role role to query
         */
        function _checkRole(bytes32 role) internal view virtual {
            _checkRole(role, msg.sender);
        }
    
        /**
         * @notice revert if given account does not have given role
         * @param role role to query
         * @param account to query
         */
        function _checkRole(bytes32 role, address account) internal view virtual {
            if (!_hasRole(role, account)) {
                revert(
                    string(
                        abi.encodePacked(
                            'AccessControl: account ',
                            account.toString(),
                            ' is missing role ',
                            uint256(role).toHexString(32)
                        )
                    )
                );
            }
        }
    
        /*
         * @notice query admin role for given role
         * @param role role to query
         * @return admin role
         */
        function _getRoleAdmin(
            bytes32 role
        ) internal view virtual returns (bytes32) {
            return AccessControlStorage.layout().roles[role].adminRole;
        }
    
        /**
         * @notice set role as admin role
         * @param role role to set
         * @param adminRole admin role to set
         */
        function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
            bytes32 previousAdminRole = _getRoleAdmin(role);
            AccessControlStorage.layout().roles[role].adminRole = adminRole;
            emit RoleAdminChanged(role, previousAdminRole, adminRole);
        }
    
        /*
         * @notice assign role to given account
         * @param role role to assign
         * @param account recipient of role assignment
         */
        function _grantRole(bytes32 role, address account) internal virtual {
            AccessControlStorage.layout().roles[role].members.add(account);
            emit RoleGranted(role, account, msg.sender);
        }
    
        /*
         * @notice unassign role from given account
         * @param role role to unassign
         * @parm account
         */
        function _revokeRole(bytes32 role, address account) internal virtual {
            AccessControlStorage.layout().roles[role].members.remove(account);
            emit RoleRevoked(role, account, msg.sender);
        }
    
        /**
         * @notice relinquish role
         * @param role role to relinquish
         */
        function _renounceRole(bytes32 role) internal virtual {
            _revokeRole(role, msg.sender);
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    import { EnumerableSet } from '../../data/EnumerableSet.sol';
    
    library AccessControlStorage {
        struct RoleData {
            EnumerableSet.AddressSet members;
            bytes32 adminRole;
        }
    
        struct Layout {
            mapping(bytes32 => RoleData) roles;
        }
    
        bytes32 internal constant DEFAULT_ADMIN_ROLE = 0x00;
    
        bytes32 internal constant STORAGE_SLOT =
            keccak256('solidstate.contracts.storage.AccessControl');
    
        function layout() internal pure returns (Layout storage l) {
            bytes32 slot = STORAGE_SLOT;
            assembly {
                l.slot := slot
            }
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    import { IAccessControlInternal } from './IAccessControlInternal.sol';
    
    /**
     * @title AccessControl interface
     */
    interface IAccessControl is IAccessControlInternal {
        /*
         * @notice query whether role is assigned to account
         * @param role role to query
         * @param account account to query
         * @return whether role is assigned to account
         */
        function hasRole(
            bytes32 role,
            address account
        ) external view returns (bool);
    
        /*
         * @notice query admin role for given role
         * @param role role to query
         * @return admin role
         */
        function getRoleAdmin(bytes32 role) external view returns (bytes32);
    
        /*
         * @notice assign role to given account
         * @param role role to assign
         * @param account recipient of role assignment
         */
        function grantRole(bytes32 role, address account) external;
    
        /*
         * @notice unassign role from given account
         * @param role role to unassign
         * @parm account
         */
        function revokeRole(bytes32 role, address account) external;
    
        /**
         * @notice relinquish role
         * @param role role to relinquish
         */
        function renounceRole(bytes32 role) external;
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    /**
     * @title Partial AccessControl interface needed by internal functions
     */
    interface IAccessControlInternal {
        event RoleAdminChanged(
            bytes32 indexed role,
            bytes32 indexed previousAdminRole,
            bytes32 indexed newAdminRole
        );
    
        event RoleGranted(
            bytes32 indexed role,
            address indexed account,
            address indexed sender
        );
    
        event RoleRevoked(
            bytes32 indexed role,
            address indexed account,
            address indexed sender
        );
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    /**
     * @title Map implementation with enumeration functions
     * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
     */
    library EnumerableMap {
        error EnumerableMap__IndexOutOfBounds();
        error EnumerableMap__NonExistentKey();
    
        struct MapEntry {
            bytes32 _key;
            bytes32 _value;
        }
    
        struct Map {
            MapEntry[] _entries;
            // 1-indexed to allow 0 to signify nonexistence
            mapping(bytes32 => uint256) _indexes;
        }
    
        struct AddressToAddressMap {
            Map _inner;
        }
    
        struct UintToAddressMap {
            Map _inner;
        }
    
        function at(
            AddressToAddressMap storage map,
            uint256 index
        ) internal view returns (address, address) {
            (bytes32 key, bytes32 value) = _at(map._inner, index);
    
            return (
                address(uint160(uint256(key))),
                address(uint160(uint256(value)))
            );
        }
    
        function at(
            UintToAddressMap storage map,
            uint256 index
        ) internal view returns (uint256, address) {
            (bytes32 key, bytes32 value) = _at(map._inner, index);
            return (uint256(key), address(uint160(uint256(value))));
        }
    
        function contains(
            AddressToAddressMap storage map,
            address key
        ) internal view returns (bool) {
            return _contains(map._inner, bytes32(uint256(uint160(key))));
        }
    
        function contains(
            UintToAddressMap storage map,
            uint256 key
        ) internal view returns (bool) {
            return _contains(map._inner, bytes32(key));
        }
    
        function length(
            AddressToAddressMap storage map
        ) internal view returns (uint256) {
            return _length(map._inner);
        }
    
        function length(
            UintToAddressMap storage map
        ) internal view returns (uint256) {
            return _length(map._inner);
        }
    
        function get(
            AddressToAddressMap storage map,
            address key
        ) internal view returns (address) {
            return
                address(
                    uint160(
                        uint256(_get(map._inner, bytes32(uint256(uint160(key)))))
                    )
                );
        }
    
        function get(
            UintToAddressMap storage map,
            uint256 key
        ) internal view returns (address) {
            return address(uint160(uint256(_get(map._inner, bytes32(key)))));
        }
    
        function set(
            AddressToAddressMap storage map,
            address key,
            address value
        ) internal returns (bool) {
            return
                _set(
                    map._inner,
                    bytes32(uint256(uint160(key))),
                    bytes32(uint256(uint160(value)))
                );
        }
    
        function set(
            UintToAddressMap storage map,
            uint256 key,
            address value
        ) internal returns (bool) {
            return _set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
        }
    
        function remove(
            AddressToAddressMap storage map,
            address key
        ) internal returns (bool) {
            return _remove(map._inner, bytes32(uint256(uint160(key))));
        }
    
        function remove(
            UintToAddressMap storage map,
            uint256 key
        ) internal returns (bool) {
            return _remove(map._inner, bytes32(key));
        }
    
        function toArray(
            AddressToAddressMap storage map
        )
            internal
            view
            returns (address[] memory keysOut, address[] memory valuesOut)
        {
            uint256 len = map._inner._entries.length;
    
            keysOut = new address[](len);
            valuesOut = new address[](len);
    
            unchecked {
                for (uint256 i; i < len; ++i) {
                    keysOut[i] = address(
                        uint160(uint256(map._inner._entries[i]._key))
                    );
                    valuesOut[i] = address(
                        uint160(uint256(map._inner._entries[i]._value))
                    );
                }
            }
        }
    
        function toArray(
            UintToAddressMap storage map
        )
            internal
            view
            returns (uint256[] memory keysOut, address[] memory valuesOut)
        {
            uint256 len = map._inner._entries.length;
    
            keysOut = new uint256[](len);
            valuesOut = new address[](len);
    
            unchecked {
                for (uint256 i; i < len; ++i) {
                    keysOut[i] = uint256(map._inner._entries[i]._key);
                    valuesOut[i] = address(
                        uint160(uint256(map._inner._entries[i]._value))
                    );
                }
            }
        }
    
        function keys(
            AddressToAddressMap storage map
        ) internal view returns (address[] memory keysOut) {
            uint256 len = map._inner._entries.length;
    
            keysOut = new address[](len);
    
            unchecked {
                for (uint256 i; i < len; ++i) {
                    keysOut[i] = address(
                        uint160(uint256(map._inner._entries[i]._key))
                    );
                }
            }
        }
    
        function keys(
            UintToAddressMap storage map
        ) internal view returns (uint256[] memory keysOut) {
            uint256 len = map._inner._entries.length;
    
            keysOut = new uint256[](len);
    
            unchecked {
                for (uint256 i; i < len; ++i) {
                    keysOut[i] = uint256(map._inner._entries[i]._key);
                }
            }
        }
    
        function values(
            AddressToAddressMap storage map
        ) internal view returns (address[] memory valuesOut) {
            uint256 len = map._inner._entries.length;
    
            valuesOut = new address[](len);
    
            unchecked {
                for (uint256 i; i < len; ++i) {
                    valuesOut[i] = address(
                        uint160(uint256(map._inner._entries[i]._value))
                    );
                }
            }
        }
    
        function values(
            UintToAddressMap storage map
        ) internal view returns (address[] memory valuesOut) {
            uint256 len = map._inner._entries.length;
    
            valuesOut = new address[](len);
    
            unchecked {
                for (uint256 i; i < len; ++i) {
                    valuesOut[i] = address(
                        uint160(uint256(map._inner._entries[i]._value))
                    );
                }
            }
        }
    
        function _at(
            Map storage map,
            uint256 index
        ) private view returns (bytes32, bytes32) {
            if (index >= map._entries.length)
                revert EnumerableMap__IndexOutOfBounds();
    
            MapEntry storage entry = map._entries[index];
            return (entry._key, entry._value);
        }
    
        function _contains(
            Map storage map,
            bytes32 key
        ) private view returns (bool) {
            return map._indexes[key] != 0;
        }
    
        function _length(Map storage map) private view returns (uint256) {
            return map._entries.length;
        }
    
        function _get(Map storage map, bytes32 key) private view returns (bytes32) {
            uint256 keyIndex = map._indexes[key];
            if (keyIndex == 0) revert EnumerableMap__NonExistentKey();
            unchecked {
                return map._entries[keyIndex - 1]._value;
            }
        }
    
        function _set(
            Map storage map,
            bytes32 key,
            bytes32 value
        ) private returns (bool) {
            uint256 keyIndex = map._indexes[key];
    
            if (keyIndex == 0) {
                map._entries.push(MapEntry({ _key: key, _value: value }));
                map._indexes[key] = map._entries.length;
                return true;
            } else {
                unchecked {
                    map._entries[keyIndex - 1]._value = value;
                }
                return false;
            }
        }
    
        function _remove(Map storage map, bytes32 key) private returns (bool) {
            uint256 keyIndex = map._indexes[key];
    
            if (keyIndex != 0) {
                unchecked {
                    MapEntry storage last = map._entries[map._entries.length - 1];
    
                    // move last entry to now-vacant index
                    map._entries[keyIndex - 1] = last;
                    map._indexes[last._key] = keyIndex;
                }
    
                // clear last index
                map._entries.pop();
                delete map._indexes[key];
    
                return true;
            } else {
                return false;
            }
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    /**
     * @title Set implementation with enumeration functions
     * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)
     */
    library EnumerableSet {
        error EnumerableSet__IndexOutOfBounds();
    
        struct Set {
            bytes32[] _values;
            // 1-indexed to allow 0 to signify nonexistence
            mapping(bytes32 => uint256) _indexes;
        }
    
        struct Bytes32Set {
            Set _inner;
        }
    
        struct AddressSet {
            Set _inner;
        }
    
        struct UintSet {
            Set _inner;
        }
    
        function at(
            Bytes32Set storage set,
            uint256 index
        ) internal view returns (bytes32) {
            return _at(set._inner, index);
        }
    
        function at(
            AddressSet storage set,
            uint256 index
        ) internal view returns (address) {
            return address(uint160(uint256(_at(set._inner, index))));
        }
    
        function at(
            UintSet storage set,
            uint256 index
        ) internal view returns (uint256) {
            return uint256(_at(set._inner, index));
        }
    
        function contains(
            Bytes32Set storage set,
            bytes32 value
        ) internal view returns (bool) {
            return _contains(set._inner, value);
        }
    
        function contains(
            AddressSet storage set,
            address value
        ) internal view returns (bool) {
            return _contains(set._inner, bytes32(uint256(uint160(value))));
        }
    
        function contains(
            UintSet storage set,
            uint256 value
        ) internal view returns (bool) {
            return _contains(set._inner, bytes32(value));
        }
    
        function indexOf(
            Bytes32Set storage set,
            bytes32 value
        ) internal view returns (uint256) {
            return _indexOf(set._inner, value);
        }
    
        function indexOf(
            AddressSet storage set,
            address value
        ) internal view returns (uint256) {
            return _indexOf(set._inner, bytes32(uint256(uint160(value))));
        }
    
        function indexOf(
            UintSet storage set,
            uint256 value
        ) internal view returns (uint256) {
            return _indexOf(set._inner, bytes32(value));
        }
    
        function length(Bytes32Set storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
    
        function length(AddressSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
    
        function length(UintSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
    
        function add(
            Bytes32Set storage set,
            bytes32 value
        ) internal returns (bool) {
            return _add(set._inner, value);
        }
    
        function add(
            AddressSet storage set,
            address value
        ) internal returns (bool) {
            return _add(set._inner, bytes32(uint256(uint160(value))));
        }
    
        function add(UintSet storage set, uint256 value) internal returns (bool) {
            return _add(set._inner, bytes32(value));
        }
    
        function remove(
            Bytes32Set storage set,
            bytes32 value
        ) internal returns (bool) {
            return _remove(set._inner, value);
        }
    
        function remove(
            AddressSet storage set,
            address value
        ) internal returns (bool) {
            return _remove(set._inner, bytes32(uint256(uint160(value))));
        }
    
        function remove(
            UintSet storage set,
            uint256 value
        ) internal returns (bool) {
            return _remove(set._inner, bytes32(value));
        }
    
        function toArray(
            Bytes32Set storage set
        ) internal view returns (bytes32[] memory) {
            return set._inner._values;
        }
    
        function toArray(
            AddressSet storage set
        ) internal view returns (address[] memory) {
            bytes32[] storage values = set._inner._values;
            address[] storage array;
    
            assembly {
                array.slot := values.slot
            }
    
            return array;
        }
    
        function toArray(
            UintSet storage set
        ) internal view returns (uint256[] memory) {
            bytes32[] storage values = set._inner._values;
            uint256[] storage array;
    
            assembly {
                array.slot := values.slot
            }
    
            return array;
        }
    
        function _at(
            Set storage set,
            uint256 index
        ) private view returns (bytes32) {
            if (index >= set._values.length)
                revert EnumerableSet__IndexOutOfBounds();
            return set._values[index];
        }
    
        function _contains(
            Set storage set,
            bytes32 value
        ) private view returns (bool) {
            return set._indexes[value] != 0;
        }
    
        function _indexOf(
            Set storage set,
            bytes32 value
        ) private view returns (uint256) {
            unchecked {
                return set._indexes[value] - 1;
            }
        }
    
        function _length(Set storage set) private view returns (uint256) {
            return set._values.length;
        }
    
        function _add(
            Set storage set,
            bytes32 value
        ) private returns (bool status) {
            if (!_contains(set, value)) {
                set._values.push(value);
                set._indexes[value] = set._values.length;
                status = true;
            }
        }
    
        function _remove(
            Set storage set,
            bytes32 value
        ) private returns (bool status) {
            uint256 valueIndex = set._indexes[value];
    
            if (valueIndex != 0) {
                unchecked {
                    bytes32 last = set._values[set._values.length - 1];
    
                    // move last value to now-vacant index
    
                    set._values[valueIndex - 1] = last;
                    set._indexes[last] = valueIndex;
                }
                // clear last index
    
                set._values.pop();
                delete set._indexes[value];
    
                status = true;
            }
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC165Internal } from './IERC165Internal.sol';
    
    /**
     * @title ERC165 interface registration interface
     * @dev see https://eips.ethereum.org/EIPS/eip-165
     */
    interface IERC165 is IERC165Internal {
        /**
         * @notice query whether contract has registered support for given interface
         * @param interfaceId interface id
         * @return bool whether interface is supported
         */
        function supportsInterface(bytes4 interfaceId) external view returns (bool);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC165Internal } from './IERC165Internal.sol';
    
    /**
     * @title ERC165 interface registration interface
     */
    interface IERC165Internal {
    
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC165 } from './IERC165.sol';
    import { IERC721Internal } from './IERC721Internal.sol';
    
    /**
     * @title ERC721 interface
     * @dev see https://eips.ethereum.org/EIPS/eip-721
     */
    interface IERC721 is IERC721Internal, IERC165 {
        /**
         * @notice query the balance of given address
         * @return balance quantity of tokens held
         */
        function balanceOf(address account) external view returns (uint256 balance);
    
        /**
         * @notice query the owner of given token
         * @param tokenId token to query
         * @return owner token owner
         */
        function ownerOf(uint256 tokenId) external view returns (address owner);
    
        /**
         * @notice transfer token between given addresses, checking for ERC721Receiver implementation if applicable
         * @param from sender of token
         * @param to receiver of token
         * @param tokenId token id
         */
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId
        ) external payable;
    
        /**
         * @notice transfer token between given addresses, checking for ERC721Receiver implementation if applicable
         * @param from sender of token
         * @param to receiver of token
         * @param tokenId token id
         * @param data data payload
         */
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId,
            bytes calldata data
        ) external payable;
    
        /**
         * @notice transfer token between given addresses, without checking for ERC721Receiver implementation if applicable
         * @param from sender of token
         * @param to receiver of token
         * @param tokenId token id
         */
        function transferFrom(
            address from,
            address to,
            uint256 tokenId
        ) external payable;
    
        /**
         * @notice grant approval to given account to spend token
         * @param operator address to be approved
         * @param tokenId token to approve
         */
        function approve(address operator, uint256 tokenId) external payable;
    
        /**
         * @notice get approval status for given token
         * @param tokenId token to query
         * @return operator address approved to spend token
         */
        function getApproved(
            uint256 tokenId
        ) external view returns (address operator);
    
        /**
         * @notice grant approval to or revoke approval from given account to spend all tokens held by sender
         * @param operator address to be approved
         * @param status approval status
         */
        function setApprovalForAll(address operator, bool status) external;
    
        /**
         * @notice query approval status of given operator with respect to given address
         * @param account address to query for approval granted
         * @param operator address to query for approval received
         * @return status whether operator is approved to spend tokens held by account
         */
        function isApprovedForAll(
            address account,
            address operator
        ) external view returns (bool status);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    /**
     * @title Partial ERC721 interface needed by internal functions
     */
    interface IERC721Internal {
        event Transfer(
            address indexed from,
            address indexed to,
            uint256 indexed tokenId
        );
    
        event Approval(
            address indexed owner,
            address indexed operator,
            uint256 indexed tokenId
        );
    
        event ApprovalForAll(
            address indexed owner,
            address indexed operator,
            bool approved
        );
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    interface IERC721Receiver {
        function onERC721Received(
            address operator,
            address from,
            uint256 tokenId,
            bytes calldata data
        ) external returns (bytes4);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC165 } from '../../../interfaces/IERC165.sol';
    import { IERC165Base } from './IERC165Base.sol';
    import { ERC165BaseInternal } from './ERC165BaseInternal.sol';
    import { ERC165BaseStorage } from './ERC165BaseStorage.sol';
    
    /**
     * @title ERC165 implementation
     */
    abstract contract ERC165Base is IERC165Base, ERC165BaseInternal {
        /**
         * @inheritdoc IERC165
         */
        function supportsInterface(bytes4 interfaceId) public view returns (bool) {
            return _supportsInterface(interfaceId);
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC165BaseInternal } from './IERC165BaseInternal.sol';
    import { ERC165BaseStorage } from './ERC165BaseStorage.sol';
    
    /**
     * @title ERC165 implementation
     */
    abstract contract ERC165BaseInternal is IERC165BaseInternal {
        /**
         * @notice indicates whether an interface is already supported based on the interfaceId
         * @param interfaceId id of interface to check
         * @return bool indicating whether interface is supported
         */
        function _supportsInterface(
            bytes4 interfaceId
        ) internal view returns (bool) {
            return ERC165BaseStorage.layout().supportedInterfaces[interfaceId];
        }
    
        /**
         * @notice sets status of interface support
         * @param interfaceId id of interface to set status for
         * @param status boolean indicating whether interface will be set as supported
         */
        function _setSupportsInterface(bytes4 interfaceId, bool status) internal {
            if (interfaceId == 0xffffffff) revert ERC165Base__InvalidInterfaceId();
            ERC165BaseStorage.layout().supportedInterfaces[interfaceId] = status;
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    library ERC165BaseStorage {
        struct Layout {
            mapping(bytes4 => bool) supportedInterfaces;
        }
    
        bytes32 internal constant STORAGE_SLOT =
            keccak256('solidstate.contracts.storage.ERC165Base');
    
        function layout() internal pure returns (Layout storage l) {
            bytes32 slot = STORAGE_SLOT;
            assembly {
                l.slot := slot
            }
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    import { IERC165 } from '../../../interfaces/IERC165.sol';
    import { IERC165BaseInternal } from './IERC165BaseInternal.sol';
    
    interface IERC165Base is IERC165, IERC165BaseInternal {}

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    import { IERC165Internal } from '../../../interfaces/IERC165Internal.sol';
    
    interface IERC165BaseInternal is IERC165Internal {
        error ERC165Base__InvalidInterfaceId();
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC721 } from '../../../interfaces/IERC721.sol';
    import { IERC721Base } from './IERC721Base.sol';
    import { ERC721BaseInternal } from './ERC721BaseInternal.sol';
    
    /**
     * @title Base ERC721 implementation, excluding optional extensions
     */
    abstract contract ERC721Base is IERC721Base, ERC721BaseInternal {
        /**
         * @inheritdoc IERC721
         */
        function balanceOf(address account) external view returns (uint256) {
            return _balanceOf(account);
        }
    
        /**
         * @inheritdoc IERC721
         */
        function ownerOf(uint256 tokenId) external view returns (address) {
            return _ownerOf(tokenId);
        }
    
        /**
         * @inheritdoc IERC721
         */
        function getApproved(uint256 tokenId) external view returns (address) {
            return _getApproved(tokenId);
        }
    
        /**
         * @inheritdoc IERC721
         */
        function isApprovedForAll(
            address account,
            address operator
        ) external view returns (bool) {
            return _isApprovedForAll(account, operator);
        }
    
        /**
         * @inheritdoc IERC721
         */
        function transferFrom(
            address from,
            address to,
            uint256 tokenId
        ) external payable {
            _transferFrom(from, to, tokenId);
        }
    
        /**
         * @inheritdoc IERC721
         */
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId
        ) external payable {
            _safeTransferFrom(from, to, tokenId);
        }
    
        /**
         * @inheritdoc IERC721
         */
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId,
            bytes memory data
        ) external payable {
            _safeTransferFrom(from, to, tokenId, data);
        }
    
        /**
         * @inheritdoc IERC721
         */
        function approve(address operator, uint256 tokenId) external payable {
            _approve(operator, tokenId);
        }
    
        /**
         * @inheritdoc IERC721
         */
        function setApprovalForAll(address operator, bool status) external {
            _setApprovalForAll(operator, status);
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC721Receiver } from '../../../interfaces/IERC721Receiver.sol';
    import { EnumerableMap } from '../../../data/EnumerableMap.sol';
    import { EnumerableSet } from '../../../data/EnumerableSet.sol';
    import { AddressUtils } from '../../../utils/AddressUtils.sol';
    import { IERC721BaseInternal } from './IERC721BaseInternal.sol';
    import { ERC721BaseStorage } from './ERC721BaseStorage.sol';
    
    /**
     * @title Base ERC721 internal functions
     */
    abstract contract ERC721BaseInternal is IERC721BaseInternal {
        using AddressUtils for address;
        using EnumerableMap for EnumerableMap.UintToAddressMap;
        using EnumerableSet for EnumerableSet.UintSet;
    
        function _balanceOf(
            address account
        ) internal view virtual returns (uint256) {
            if (account == address(0)) revert ERC721Base__BalanceQueryZeroAddress();
            return ERC721BaseStorage.layout().holderTokens[account].length();
        }
    
        function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
            address owner = ERC721BaseStorage.layout().tokenOwners.get(tokenId);
            if (owner == address(0)) revert ERC721Base__InvalidOwner();
            return owner;
        }
    
        function _exists(uint256 tokenId) internal view virtual returns (bool) {
            return ERC721BaseStorage.layout().tokenOwners.contains(tokenId);
        }
    
        function _getApproved(
            uint256 tokenId
        ) internal view virtual returns (address) {
            if (!_exists(tokenId)) revert ERC721Base__NonExistentToken();
    
            return ERC721BaseStorage.layout().tokenApprovals[tokenId];
        }
    
        function _isApprovedForAll(
            address account,
            address operator
        ) internal view virtual returns (bool) {
            return ERC721BaseStorage.layout().operatorApprovals[account][operator];
        }
    
        function _isApprovedOrOwner(
            address spender,
            uint256 tokenId
        ) internal view virtual returns (bool) {
            if (!_exists(tokenId)) revert ERC721Base__NonExistentToken();
    
            address owner = _ownerOf(tokenId);
    
            return (spender == owner ||
                _getApproved(tokenId) == spender ||
                _isApprovedForAll(owner, spender));
        }
    
        function _mint(address to, uint256 tokenId) internal virtual {
            if (to == address(0)) revert ERC721Base__MintToZeroAddress();
            if (_exists(tokenId)) revert ERC721Base__TokenAlreadyMinted();
    
            _beforeTokenTransfer(address(0), to, tokenId);
    
            ERC721BaseStorage.Layout storage l = ERC721BaseStorage.layout();
    
            l.holderTokens[to].add(tokenId);
            l.tokenOwners.set(tokenId, to);
    
            emit Transfer(address(0), to, tokenId);
        }
    
        function _safeMint(address to, uint256 tokenId) internal virtual {
            _safeMint(to, tokenId, '');
        }
    
        function _safeMint(
            address to,
            uint256 tokenId,
            bytes memory data
        ) internal virtual {
            _mint(to, tokenId);
            if (!_checkOnERC721Received(address(0), to, tokenId, data))
                revert ERC721Base__ERC721ReceiverNotImplemented();
        }
    
        function _burn(uint256 tokenId) internal virtual {
            address owner = _ownerOf(tokenId);
    
            _beforeTokenTransfer(owner, address(0), tokenId);
    
            ERC721BaseStorage.Layout storage l = ERC721BaseStorage.layout();
    
            l.holderTokens[owner].remove(tokenId);
            l.tokenOwners.remove(tokenId);
    
            l.tokenApprovals[tokenId] = address(0);
    
            emit Approval(owner, address(0), tokenId);
            emit Transfer(owner, address(0), tokenId);
        }
    
        function _transfer(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual {
            address owner = _ownerOf(tokenId);
    
            if (owner != from) revert ERC721Base__NotTokenOwner();
            if (to == address(0)) revert ERC721Base__TransferToZeroAddress();
    
            _beforeTokenTransfer(from, to, tokenId);
    
            ERC721BaseStorage.Layout storage l = ERC721BaseStorage.layout();
    
            l.holderTokens[from].remove(tokenId);
            l.holderTokens[to].add(tokenId);
            l.tokenOwners.set(tokenId, to);
            l.tokenApprovals[tokenId] = address(0);
    
            emit Approval(owner, address(0), tokenId);
            emit Transfer(from, to, tokenId);
        }
    
        function _transferFrom(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual {
            _handleTransferMessageValue(from, to, tokenId, msg.value);
            if (!_isApprovedOrOwner(msg.sender, tokenId))
                revert ERC721Base__NotOwnerOrApproved();
            _transfer(from, to, tokenId);
        }
    
        function _safeTransfer(
            address from,
            address to,
            uint256 tokenId,
            bytes memory data
        ) internal virtual {
            _transfer(from, to, tokenId);
            if (!_checkOnERC721Received(from, to, tokenId, data))
                revert ERC721Base__ERC721ReceiverNotImplemented();
        }
    
        function _safeTransferFrom(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual {
            _safeTransferFrom(from, to, tokenId, '');
        }
    
        function _safeTransferFrom(
            address from,
            address to,
            uint256 tokenId,
            bytes memory data
        ) internal virtual {
            _handleTransferMessageValue(from, to, tokenId, msg.value);
            if (!_isApprovedOrOwner(msg.sender, tokenId))
                revert ERC721Base__NotOwnerOrApproved();
            _safeTransfer(from, to, tokenId, data);
        }
    
        function _approve(address operator, uint256 tokenId) internal virtual {
            _handleApproveMessageValue(operator, tokenId, msg.value);
    
            address owner = _ownerOf(tokenId);
    
            if (operator == owner) revert ERC721Base__SelfApproval();
            if (msg.sender != owner && !_isApprovedForAll(owner, msg.sender))
                revert ERC721Base__NotOwnerOrApproved();
    
            ERC721BaseStorage.layout().tokenApprovals[tokenId] = operator;
            emit Approval(owner, operator, tokenId);
        }
    
        function _setApprovalForAll(
            address operator,
            bool status
        ) internal virtual {
            if (operator == msg.sender) revert ERC721Base__SelfApproval();
            ERC721BaseStorage.layout().operatorApprovals[msg.sender][
                operator
            ] = status;
            emit ApprovalForAll(msg.sender, operator, status);
        }
    
        function _checkOnERC721Received(
            address from,
            address to,
            uint256 tokenId,
            bytes memory data
        ) internal virtual returns (bool) {
            if (!to.isContract()) {
                return true;
            }
    
            bytes memory returnData = to.functionCall(
                abi.encodeWithSelector(
                    IERC721Receiver(to).onERC721Received.selector,
                    msg.sender,
                    from,
                    tokenId,
                    data
                ),
                'ERC721: transfer to non ERC721Receiver implementer'
            );
    
            bytes4 returnValue = abi.decode(returnData, (bytes4));
            return returnValue == type(IERC721Receiver).interfaceId;
        }
    
        /**
         * @notice ERC721 hook, called before externally called approvals for processing of included message value
         * @param operator beneficiary of approval
         * @param tokenId id of transferred token
         * @param value message value
         */
        function _handleApproveMessageValue(
            address operator,
            uint256 tokenId,
            uint256 value
        ) internal virtual {}
    
        /**
         * @notice ERC721 hook, called before externally called transfers for processing of included message value
         * @param from sender of token
         * @param to receiver of token
         * @param tokenId id of transferred token
         * @param value message value
         */
        function _handleTransferMessageValue(
            address from,
            address to,
            uint256 tokenId,
            uint256 value
        ) internal virtual {}
    
        /**
         * @notice ERC721 hook, called before all transfers including mint and burn
         * @dev function should be overridden and new implementation must call super
         * @param from sender of token
         * @param to receiver of token
         * @param tokenId id of transferred token
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual {}
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { EnumerableMap } from '../../../data/EnumerableMap.sol';
    import { EnumerableSet } from '../../../data/EnumerableSet.sol';
    
    library ERC721BaseStorage {
        using EnumerableSet for EnumerableSet.UintSet;
        using EnumerableMap for EnumerableMap.UintToAddressMap;
    
        bytes32 internal constant STORAGE_SLOT =
            keccak256('solidstate.contracts.storage.ERC721Base');
    
        struct Layout {
            EnumerableMap.UintToAddressMap tokenOwners;
            mapping(address => EnumerableSet.UintSet) holderTokens;
            mapping(uint256 => address) tokenApprovals;
            mapping(address => mapping(address => bool)) operatorApprovals;
        }
    
        function layout() internal pure returns (Layout storage l) {
            bytes32 slot = STORAGE_SLOT;
            assembly {
                l.slot := slot
            }
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC721 } from '../../../interfaces/IERC721.sol';
    import { IERC721BaseInternal } from './IERC721BaseInternal.sol';
    
    /**
     * @title ERC721 base interface
     */
    interface IERC721Base is IERC721BaseInternal, IERC721 {
    
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC721Internal } from '../../../interfaces/IERC721Internal.sol';
    
    /**
     * @title ERC721 base interface
     */
    interface IERC721BaseInternal is IERC721Internal {
        error ERC721Base__NotOwnerOrApproved();
        error ERC721Base__SelfApproval();
        error ERC721Base__BalanceQueryZeroAddress();
        error ERC721Base__ERC721ReceiverNotImplemented();
        error ERC721Base__InvalidOwner();
        error ERC721Base__MintToZeroAddress();
        error ERC721Base__NonExistentToken();
        error ERC721Base__NotTokenOwner();
        error ERC721Base__TokenAlreadyMinted();
        error ERC721Base__TransferToZeroAddress();
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { EnumerableMap } from '../../../data/EnumerableMap.sol';
    import { EnumerableSet } from '../../../data/EnumerableSet.sol';
    import { ERC721BaseStorage } from '../base/ERC721BaseStorage.sol';
    import { IERC721Enumerable } from './IERC721Enumerable.sol';
    import { ERC721EnumerableInternal } from './ERC721EnumerableInternal.sol';
    
    abstract contract ERC721Enumerable is
        IERC721Enumerable,
        ERC721EnumerableInternal
    {
        using EnumerableMap for EnumerableMap.UintToAddressMap;
        using EnumerableSet for EnumerableSet.UintSet;
    
        /**
         * @inheritdoc IERC721Enumerable
         */
        function totalSupply() public view returns (uint256) {
            return _totalSupply();
        }
    
        /**
         * @inheritdoc IERC721Enumerable
         */
        function tokenOfOwnerByIndex(
            address owner,
            uint256 index
        ) public view returns (uint256) {
            return _tokenOfOwnerByIndex(owner, index);
        }
    
        /**
         * @inheritdoc IERC721Enumerable
         */
        function tokenByIndex(uint256 index) public view returns (uint256) {
            return _tokenByIndex(index);
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { EnumerableMap } from '../../../data/EnumerableMap.sol';
    import { EnumerableSet } from '../../../data/EnumerableSet.sol';
    import { ERC721BaseStorage } from '../base/ERC721BaseStorage.sol';
    
    abstract contract ERC721EnumerableInternal {
        using EnumerableMap for EnumerableMap.UintToAddressMap;
        using EnumerableSet for EnumerableSet.UintSet;
    
        /**
         * @notice TODO
         */
        function _totalSupply() internal view returns (uint256) {
            return ERC721BaseStorage.layout().tokenOwners.length();
        }
    
        /**
         * @notice TODO
         */
        function _tokenOfOwnerByIndex(
            address owner,
            uint256 index
        ) internal view returns (uint256) {
            return ERC721BaseStorage.layout().holderTokens[owner].at(index);
        }
    
        /**
         * @notice TODO
         */
        function _tokenByIndex(
            uint256 index
        ) internal view returns (uint256 tokenId) {
            (tokenId, ) = ERC721BaseStorage.layout().tokenOwners.at(index);
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    interface IERC721Enumerable {
        /**
         * @notice get total token supply
         * @return total supply
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @notice get token of given owner at given internal storage index
         * @param owner token holder to query
         * @param index position in owner's token list to query
         * @return tokenId id of retrieved token
         */
        function tokenOfOwnerByIndex(
            address owner,
            uint256 index
        ) external view returns (uint256 tokenId);
    
        /**
         * @notice get token at given internal storage index
         * @param index position in global token list to query
         * @return tokenId id of retrieved token
         */
        function tokenByIndex(
            uint256 index
        ) external view returns (uint256 tokenId);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC721Base } from './base/IERC721Base.sol';
    import { IERC721Enumerable } from './enumerable/IERC721Enumerable.sol';
    import { IERC721Metadata } from './metadata/IERC721Metadata.sol';
    
    interface ISolidStateERC721 is IERC721Base, IERC721Enumerable, IERC721Metadata {
        error SolidStateERC721__PayableApproveNotSupported();
        error SolidStateERC721__PayableTransferNotSupported();
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { ERC721MetadataInternal } from './ERC721MetadataInternal.sol';
    import { IERC721Metadata } from './IERC721Metadata.sol';
    
    /**
     * @title ERC721 metadata extensions
     */
    abstract contract ERC721Metadata is IERC721Metadata, ERC721MetadataInternal {
        /**
         * @notice inheritdoc IERC721Metadata
         */
        function name() external view virtual returns (string memory) {
            return _name();
        }
    
        /**
         * @notice inheritdoc IERC721Metadata
         */
        function symbol() external view virtual returns (string memory) {
            return _symbol();
        }
    
        /**
         * @notice inheritdoc IERC721Metadata
         */
        function tokenURI(
            uint256 tokenId
        ) external view virtual returns (string memory) {
            return _tokenURI(tokenId);
        }
    
        /**
         * @inheritdoc ERC721MetadataInternal
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual override {
            super._beforeTokenTransfer(from, to, tokenId);
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { UintUtils } from '../../../utils/UintUtils.sol';
    import { ERC721BaseStorage } from '../base/ERC721BaseStorage.sol';
    import { ERC721BaseInternal } from '../base/ERC721Base.sol';
    import { IERC721MetadataInternal } from './IERC721MetadataInternal.sol';
    import { ERC721MetadataStorage } from './ERC721MetadataStorage.sol';
    import { ERC721MetadataInternal } from './ERC721MetadataInternal.sol';
    
    /**
     * @title ERC721Metadata internal functions
     */
    abstract contract ERC721MetadataInternal is
        IERC721MetadataInternal,
        ERC721BaseInternal
    {
        using UintUtils for uint256;
    
        /**
         * @notice get token name
         * @return token name
         */
        function _name() internal view virtual returns (string memory) {
            return ERC721MetadataStorage.layout().name;
        }
    
        /**
         * @notice get token symbol
         * @return token symbol
         */
        function _symbol() internal view virtual returns (string memory) {
            return ERC721MetadataStorage.layout().symbol;
        }
    
        /**
         * @notice get generated URI for given token
         * @return token URI
         */
        function _tokenURI(
            uint256 tokenId
        ) internal view virtual returns (string memory) {
            if (!_exists(tokenId)) revert ERC721Metadata__NonExistentToken();
    
            ERC721MetadataStorage.Layout storage l = ERC721MetadataStorage.layout();
    
            string memory tokenIdURI = l.tokenURIs[tokenId];
            string memory baseURI = l.baseURI;
    
            if (bytes(baseURI).length == 0) {
                return tokenIdURI;
            } else if (bytes(tokenIdURI).length > 0) {
                return string(abi.encodePacked(baseURI, tokenIdURI));
            } else {
                return string(abi.encodePacked(baseURI, tokenId.toString()));
            }
        }
    
        /**
         * @notice ERC721 hook: clear per-token URI data on burn
         * @inheritdoc ERC721BaseInternal
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual override {
            super._beforeTokenTransfer(from, to, tokenId);
    
            if (to == address(0)) {
                delete ERC721MetadataStorage.layout().tokenURIs[tokenId];
            }
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    library ERC721MetadataStorage {
        bytes32 internal constant STORAGE_SLOT =
            keccak256('solidstate.contracts.storage.ERC721Metadata');
    
        struct Layout {
            string name;
            string symbol;
            string baseURI;
            mapping(uint256 => string) tokenURIs;
        }
    
        function layout() internal pure returns (Layout storage l) {
            bytes32 slot = STORAGE_SLOT;
            assembly {
                l.slot := slot
            }
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC721MetadataInternal } from './IERC721MetadataInternal.sol';
    
    /**
     * @title ERC721Metadata interface
     */
    interface IERC721Metadata is IERC721MetadataInternal {
        /**
         * @notice get token name
         * @return token name
         */
        function name() external view returns (string memory);
    
        /**
         * @notice get token symbol
         * @return token symbol
         */
        function symbol() external view returns (string memory);
    
        /**
         * @notice get generated URI for given token
         * @return token URI
         */
        function tokenURI(uint256 tokenId) external view returns (string memory);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { IERC721BaseInternal } from '../base/IERC721BaseInternal.sol';
    
    /**
     * @title ERC721Metadata internal interface
     */
    interface IERC721MetadataInternal is IERC721BaseInternal {
        error ERC721Metadata__NonExistentToken();
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { ERC165Base } from '../../introspection/ERC165/base/ERC165Base.sol';
    import { ERC721Base, ERC721BaseInternal } from './base/ERC721Base.sol';
    import { ERC721Enumerable } from './enumerable/ERC721Enumerable.sol';
    import { ERC721Metadata } from './metadata/ERC721Metadata.sol';
    import { ISolidStateERC721 } from './ISolidStateERC721.sol';
    
    /**
     * @title SolidState ERC721 implementation, including recommended extensions
     */
    abstract contract SolidStateERC721 is
        ISolidStateERC721,
        ERC721Base,
        ERC721Enumerable,
        ERC721Metadata,
        ERC165Base
    {
        /**
         * @notice ERC721 hook: revert if value is included in external approve function call
         * @inheritdoc ERC721BaseInternal
         */
        function _handleApproveMessageValue(
            address operator,
            uint256 tokenId,
            uint256 value
        ) internal virtual override {
            if (value > 0) revert SolidStateERC721__PayableApproveNotSupported();
            super._handleApproveMessageValue(operator, tokenId, value);
        }
    
        /**
         * @notice ERC721 hook: revert if value is included in external transfer function call
         * @inheritdoc ERC721BaseInternal
         */
        function _handleTransferMessageValue(
            address from,
            address to,
            uint256 tokenId,
            uint256 value
        ) internal virtual override {
            if (value > 0) revert SolidStateERC721__PayableTransferNotSupported();
            super._handleTransferMessageValue(from, to, tokenId, value);
        }
    
        /**
         * @inheritdoc ERC721BaseInternal
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual override(ERC721BaseInternal, ERC721Metadata) {
            super._beforeTokenTransfer(from, to, tokenId);
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    import { UintUtils } from './UintUtils.sol';
    
    library AddressUtils {
        using UintUtils for uint256;
    
        error AddressUtils__InsufficientBalance();
        error AddressUtils__NotContract();
        error AddressUtils__SendValueFailed();
    
        function toString(address account) internal pure returns (string memory) {
            return uint256(uint160(account)).toHexString(20);
        }
    
        function isContract(address account) internal view returns (bool) {
            uint256 size;
            assembly {
                size := extcodesize(account)
            }
            return size > 0;
        }
    
        function sendValue(address payable account, uint256 amount) internal {
            (bool success, ) = account.call{ value: amount }('');
            if (!success) revert AddressUtils__SendValueFailed();
        }
    
        function functionCall(
            address target,
            bytes memory data
        ) internal returns (bytes memory) {
            return
                functionCall(target, data, 'AddressUtils: failed low-level call');
        }
    
        function functionCall(
            address target,
            bytes memory data,
            string memory error
        ) internal returns (bytes memory) {
            return _functionCallWithValue(target, data, 0, error);
        }
    
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value
        ) internal returns (bytes memory) {
            return
                functionCallWithValue(
                    target,
                    data,
                    value,
                    'AddressUtils: failed low-level call with value'
                );
        }
    
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory error
        ) internal returns (bytes memory) {
            if (value > address(this).balance)
                revert AddressUtils__InsufficientBalance();
            return _functionCallWithValue(target, data, value, error);
        }
    
        /**
         * @notice execute arbitrary external call with limited gas usage and amount of copied return data
         * @dev derived from https://github.com/nomad-xyz/ExcessivelySafeCall (MIT License)
         * @param target recipient of call
         * @param gasAmount gas allowance for call
         * @param value native token value to include in call
         * @param maxCopy maximum number of bytes to copy from return data
         * @param data encoded call data
         * @return success whether call is successful
         * @return returnData copied return data
         */
        function excessivelySafeCall(
            address target,
            uint256 gasAmount,
            uint256 value,
            uint16 maxCopy,
            bytes memory data
        ) internal returns (bool success, bytes memory returnData) {
            returnData = new bytes(maxCopy);
    
            assembly {
                // execute external call via assembly to avoid automatic copying of return data
                success := call(
                    gasAmount,
                    target,
                    value,
                    add(data, 0x20),
                    mload(data),
                    0,
                    0
                )
    
                // determine whether to limit amount of data to copy
                let toCopy := returndatasize()
    
                if gt(toCopy, maxCopy) {
                    toCopy := maxCopy
                }
    
                // store the length of the copied bytes
                mstore(returnData, toCopy)
    
                // copy the bytes from returndata[0:toCopy]
                returndatacopy(add(returnData, 0x20), 0, toCopy)
            }
        }
    
        function _functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory error
        ) private returns (bytes memory) {
            if (!isContract(target)) revert AddressUtils__NotContract();
    
            (bool success, bytes memory returnData) = target.call{ value: value }(
                data
            );
    
            if (success) {
                return returnData;
            } else if (returnData.length > 0) {
                assembly {
                    let returnData_size := mload(returnData)
                    revert(add(32, returnData), returnData_size)
                }
            } else {
                revert(error);
            }
        }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.8;
    
    /**
     * @title utility functions for uint256 operations
     * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)
     */
    library UintUtils {
        error UintUtils__InsufficientHexLength();
    
        bytes16 private constant HEX_SYMBOLS = '0123456789abcdef';
    
        function add(uint256 a, int256 b) internal pure returns (uint256) {
            return b < 0 ? sub(a, -b) : a + uint256(b);
        }
    
        function sub(uint256 a, int256 b) internal pure returns (uint256) {
            return b < 0 ? add(a, -b) : a - uint256(b);
        }
    
        function toString(uint256 value) internal pure returns (string memory) {
            if (value == 0) {
                return '0';
            }
    
            uint256 temp = value;
            uint256 digits;
    
            while (temp != 0) {
                digits++;
                temp /= 10;
            }
    
            bytes memory buffer = new bytes(digits);
    
            while (value != 0) {
                digits -= 1;
                buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                value /= 10;
            }
    
            return string(buffer);
        }
    
        function toHexString(uint256 value) internal pure returns (string memory) {
            if (value == 0) {
                return '0x00';
            }
    
            uint256 length = 0;
    
            for (uint256 temp = value; temp != 0; temp >>= 8) {
                unchecked {
                    length++;
                }
            }
    
            return toHexString(value, length);
        }
    
        function toHexString(
            uint256 value,
            uint256 length
        ) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = '0';
            buffer[1] = 'x';
    
            unchecked {
                for (uint256 i = 2 * length + 1; i > 1; --i) {
                    buffer[i] = HEX_SYMBOLS[value & 0xf];
                    value >>= 4;
                }
            }
    
            if (value != 0) revert UintUtils__InsufficientHexLength();
    
            return string(buffer);
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.8;
    
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@solidstate/contracts/access/access_control/AccessControl.sol";
    import {AccessControlStorage} from "@solidstate/contracts/access/access_control/AccessControlStorage.sol";
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    
    import {MOWSE_MINTER_ROLE, MOWSEWEAR_MINTER_ROLE, MOWSE_ADMIN_ROLE, MOWSEWEAR_ADMIN_ROLE, MOWSEGAME_ADMIN_ROLE, MOWSELOOTBOX_MINTER_ROLE, MOWSEJOB_ADMIN_ROLE} from "../libraries/LibStorage.sol";
    
    import "./../libraries/LibStorage.sol";
    
    contract AccessControlFacet is AccessControl {
        GameStorage internal gs;
        error MissingAdminRole(string);
    
        modifier onlyAdminOrCore() {
            require(
                msg.sender == gs.diamondAddress || msg.sender == LibDiamond.contractOwner(),
                "Only the Core or Admin addresses can perform this action"
            );
            _;
        }
        modifier onlyAccessControlAdmin() {
            if (!_hasRole(AccessControlStorage.DEFAULT_ADMIN_ROLE, msg.sender)) revert MissingAdminRole("Missing Admin role");
            _;
        }
    
        function hasAdminRole(address account) external view returns (bool) {
            return _hasRole(AccessControlStorage.DEFAULT_ADMIN_ROLE, account);
        }
    
        // MowseAdminRole is responsible for facet-to-facet communication
        function hasMowseAdminRole(address account) external view returns (bool) {
            return _hasRole(MOWSE_ADMIN_ROLE, account);
        }
    
        function hasMowseMinterRole(address account) external view returns (bool) {
            return _hasRole(MOWSE_MINTER_ROLE, account);
        }
    
        function hasMowseWearMinterRole(address account) external view returns (bool) {
            return _hasRole(MOWSEWEAR_MINTER_ROLE, account);
        }
    
        function hasMowseWearAdminRole(address account) external view returns (bool) {
            return _hasRole(MOWSEWEAR_ADMIN_ROLE, account);
        }
    
        function hasMowseGameAdminRole(address account) external view returns (bool) {
            return _hasRole(MOWSEGAME_ADMIN_ROLE, account);
        }
    
        function hasMowseLootboxMinterRole(address account) external view returns (bool) {
            return _hasRole(MOWSELOOTBOX_MINTER_ROLE, account);
        }
    
        function hasMowseJobAdminRole(address account) external view returns (bool) {
            return _hasRole(MOWSEJOB_ADMIN_ROLE, account);
        }
    
        // Grant
        function addAccountToAdminRole(address account) external onlyAdminOrCore {
            _grantRole(AccessControlStorage.DEFAULT_ADMIN_ROLE, account);
        }
    
        function addAccountToMowseAdminRole(address account) external onlyAccessControlAdmin {
            _grantRole(MOWSE_ADMIN_ROLE, account);
        }
    
        function addAccountToMowseMinterRole(address account) external onlyAccessControlAdmin {
            _grantRole(MOWSE_MINTER_ROLE, account);
        }
    
        function addAccountToMowseWearMinterRole(address account) external onlyAccessControlAdmin {
            _grantRole(MOWSEWEAR_MINTER_ROLE, account);
        }
    
        function addAccountToMowseWearAdminRole(address account) external onlyAccessControlAdmin {
            _grantRole(MOWSEWEAR_ADMIN_ROLE, account);
        }
    
        function addAccountToMowseGameAdminRole(address account) external onlyAccessControlAdmin {
            _grantRole(MOWSEGAME_ADMIN_ROLE, account);
        }
    
        function addAccountToMowseLootboxMinterRole(address account) external onlyAccessControlAdmin {
            _grantRole(MOWSELOOTBOX_MINTER_ROLE, account);
        }
    
        function addAccountToMowseJobAdminRole(address account) external onlyAccessControlAdmin {
            _grantRole(MOWSEJOB_ADMIN_ROLE, account);
        }
    
        // Revoke
        function revokeAccountToMowseAdminRole(address account) external onlyAccessControlAdmin {
            _revokeRole(MOWSE_ADMIN_ROLE, account);
        }
    
        function revokeAccountToMowseMinterRole(address account) external onlyAccessControlAdmin {
            _revokeRole(MOWSE_MINTER_ROLE, account);
        }
    
        function revokeAccountToMowseWearMinterRole(address account) external onlyAccessControlAdmin {
            _revokeRole(MOWSEWEAR_MINTER_ROLE, account);
        }
    
        function revokeAccountToMowseWearAdminRole(address account) external onlyAccessControlAdmin {
            _revokeRole(MOWSEWEAR_ADMIN_ROLE, account);
        }
    
        function revokeAccountToMowseGameAdminRole(address account) external onlyAccessControlAdmin {
            _revokeRole(MOWSEGAME_ADMIN_ROLE, account);
        }
    
        function revokeAccountToMowseLootboxMinterRole(address account) external onlyAccessControlAdmin {
            _revokeRole(MOWSELOOTBOX_MINTER_ROLE, account);
        }
    
        function revokeAccountToMowseJobAdminRole(address account) external onlyAccessControlAdmin {
            _revokeRole(MOWSEJOB_ADMIN_ROLE, account);
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.19;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    import "../libraries/SlothVerifiableDelay.sol";
    
    import {Mowse, GameStorage} from "../libraries/LibStorage.sol";
    
    import {MowseWearFacet} from "./MowseWearFacet.sol";
    import {RandomFortuneFacet} from "./RandomFortuneFacet.sol";
    import {MowseJobFacet} from "./MowseJobFacet.sol";
    import {MowseFacet} from "./MowseFacet.sol";
    
    import "../MowseGold.sol";
    import "../MowseAvatar.sol";
    import "./../libraries/LibStorage.sol";
    
    contract DonationTreeFacet is ERC721Holder {
        using Strings for uint256;
        GameStorage internal gs;
    
        event DonationTreeWish(address indexed player, bool indexed wishSucceeded, uint256 mintAmount);
    
        error InvalidDailyWishNonce(uint256 nonce);
        error DailyWishCooldown(string);
        error MustOwnMowse(string);
        error ContractPaused(string);
    
        modifier notPaused() {
            if (gs.paused) revert ContractPaused("Contract is paused");
            _;
        }
    
        function getWishCount(address player) external view returns (uint256) {
            return gs.wishCount[player];
        }
    
        function getNextWishTime(address player) external view returns (uint256) {
            return gs.nextWish[player];
        }
    
        // Wish to the donation tree once a day to have a chance at a random reward
        function dailyWish() external notPaused {
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
            MowseAvatar mowseAvatar = MowseAvatar(gs.mowseAvatarContractAddress);
            // If the user does not own a MowseAvatar, they cannot wish
            // However, if they have purchasedMowseGold, then they can wish
            // I realize this *can* be a vulnerability where a script can just transfer the same mowseAvatar between accounts to wish multiple times
            // But I want to keep the daily wish available for everyone. If it becomes a problem, I can adjust the requirements
            if (!(mowseAvatar.balanceOf(msg.sender) > 0 || gs.hasPurchasedMowseGold[msg.sender])) revert MustOwnMowse("Must own a Mowse to wish");
            if (gs.nextWish[msg.sender] > block.timestamp) revert DailyWishCooldown("Can only wish once a day");
            gs.nextWish[msg.sender] = block.timestamp + 23 hours;
            gs.wishCount[msg.sender]++;
    
            uint256 currentTotalWishCount = gs.totalWishCount;
            uint256 vdfSeed = uint256(keccak256(abi.encodePacked(msg.sender, gs.donationTreeNonce++, block.timestamp, blockhash((block.number - 1)))));
            gs.donationTreeWishCountToSeed[currentTotalWishCount] = vdfSeed;
            gs.totalWishCount++;
    
            // Did the player's wish succeed?
            uint256 randomness = vdfSeed;
            uint256 _randomSeed = randomness % 500;
            bool wishSucceeded;
            // 10 in 500 chance of wish successful ~2%
            // 1 in 10 of getting a donated mowsewear if wish successful, if no more donated mowsewear, then get some mowsegold
            uint256 mintAmount = 100 * 1e18;
            if (_randomSeed < 10) {
                wishSucceeded = true;
                if (_randomSeed == 0) {
                    // try to find a random donated mowsewear to mint
                    uint256 balance = MowseWearFacet(gs.diamondAddress).balanceOf(address(this));
                    if (balance > 0) {
                        randomness >>= 2;
                        uint256 randomIndex = randomness % balance;
                        uint256 tokenId = MowseWearFacet(gs.diamondAddress).tokenOfOwnerByIndex(address(this), randomIndex);
                        MowseWearFacet(gs.diamondAddress).transferFrom(address(this), msg.sender, tokenId);
                    } else {
                        // If no more donated mowsewear, mint some extra mowsegold to be able to mint their own mowsewear
                        mintAmount += 1000 * 1e18;
                    }
                }
                mowsegold.mint(msg.sender, mintAmount);
            }
            emit DonationTreeWish(msg.sender, wishSucceeded, mintAmount);
    
            // May need to revisit this function to only use mainMowse for condition checks
            // Grant a small amount of exp to main mowse
            uint256 mainMowse = MowseFacet(gs.diamondAddress).getMainMowse(msg.sender);
            if (mainMowse != 0) {
                if (gs.mowseNextWish[mainMowse] <= block.timestamp) {
                    MowseJobFacet(gs.diamondAddress).grantLifeExperience(mainMowse, 10);
                    gs.mowseNextWish[mainMowse] = block.timestamp + 23 hours;
                }
            }
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        // vdf
        function _prove(uint256 proof, uint256 seed) internal view returns (bool) {
            return SlothVerifiableDelay.verify(proof, seed, gs.donationTreePrime, gs.donationTreeIterations);
        }
    
        function getDonationTreeProofById(uint256 wishCount) external view returns (uint256) {
            if (gs.donationTreeWishCountToSeed[wishCount] == 0) revert InvalidDailyWishNonce(wishCount);
    
            return SlothVerifiableDelay.compute(gs.donationTreeWishCountToSeed[wishCount], gs.donationTreePrime, gs.donationTreeIterations);
        }
    
        function proveDonationTreeById(uint256 wishCount, uint256 proof) external view returns (bool) {
            if (gs.donationTreeWishCountToSeed[wishCount] == 0) revert InvalidDailyWishNonce(wishCount);
    
            if (_prove(proof, gs.donationTreeWishCountToSeed[wishCount])) {
                return true;
            } else {
                return false;
            }
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.8;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    
    import {AccessControlFacet} from "./AccessControlFacet.sol";
    // import { MowseFacet } from "./MowseFacet.sol";
    import "../MowseGold.sol";
    // import "../MowseBank.sol";
    import "./../libraries/LibStorage.sol";
    
    contract GameStorageFacet {
        GameStorage internal gs;
        modifier onlyAdminOrCore() {
            require(
                msg.sender == gs.diamondAddress || msg.sender == LibDiamond.contractOwner(),
                "Only the Core or Admin addresses can perform this action"
            );
            _;
        }
        modifier testPatchExists(uint256 patchVersion) {
            require(gs.testPatchCount >= patchVersion, "Test Patch does not exist");
            _;
        }
    
        function isPaused() external view returns (bool) {
            return gs.paused;
        }
    
        function getMowseTokenIdCounter() external view returns (uint256) {
            return gs.mowseTokenIdCounter;
        }
    
        // For MowseGames
        function setHasPurchasedMowseGold(address account) external {
            require(AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender), "Only MowseGame Admin addresses can perform this action");
            gs.hasPurchasedMowseGold[account] = true;
        }
    
        function getHasPurchasedMowseGold(address account) external view returns (bool) {
            return gs.hasPurchasedMowseGold[account];
        }
    
        // TestPatch methods
        function getPatchVersion() external view returns (uint256) {
            return gs.testPatchCount;
        }
    
        function getUniqueInteractionCount(uint256 patchVersion) external view testPatchExists(patchVersion) returns (uint256) {
            return gs.testPatches[patchVersion].uniqueInteractionCount;
        }
    
        function getInteractedAddresses(uint256 patchVersion) external view testPatchExists(patchVersion) returns (address[] memory) {
            return gs.testPatches[patchVersion].interactedAddresses;
        }
    
        function getNumberOfInteractionsByAddress(uint256 patchVersion, address account) external view testPatchExists(patchVersion) returns (uint256) {
            return gs.testPatches[patchVersion].numberOfInteractions[account];
        }
    
        function testPatchInteraction(address account) external {
            console.log("Test Patch Interaction", account, msg.sender);
            uint256 patchVersion = gs.testPatchCount;
            if (gs.testPatches[patchVersion].numberOfInteractions[account] == 0) {
                gs.testPatches[patchVersion].uniqueInteractionCount++;
                gs.testPatches[patchVersion].interactedAddresses.push(account);
            }
            gs.testPatches[patchVersion].numberOfInteractions[account]++;
        }
    
        function nextPatch() external onlyAdminOrCore {
            gs.testPatchCount++;
            gs.testPatches[gs.testPatchCount].testPatchVersion++;
        }
    
        function pause(bool _state) external onlyAdminOrCore {
            gs.paused = _state;
        }
    
        // function approveMowseGoldForMowse(uint256 mowseId, uint256 amount) external {
        //   require(gs.mowses[mowseId].tokenId != 0, 'Mowse does not exist');
        //   require(MowseFacet(gs.diamondAddress).getOwnerAddress(mowseId) == msg.sender, 'Must own Mowse');
        //   MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
        //   MowseBank mowsebank = MowseBank(gs.mowseBankContractAddress);
        //   address mowseAddress = mowseBank.getWalletAddress(mowseId);
        //   mowsegold.approve(mowseAddress, amount);
        // }
        function approveMowseGold(address recipient) external onlyAdminOrCore {
            uint256 approve_amount = 115792089237316195423570985008687907853269984665640564039457584007913129639935; //(2^256 - 1 )
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
    
            // Approve unlimited
            mowsegold.approve(recipient, approve_amount);
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibUtils.sol";
    import "../../libraries/LibStorage.sol";
    import "../../libraries/LibDungeon.sol";
    
    import {WithStorage, GameStorage, MowseDungeonMonsterEnum, MowseDungeonMonsterAttributes, MowseDungeonSpecialAbilityEnum, MowseDungeonStatusEffectEnum, MowseDungeonClassEnum, MowseDungeonCompanionEnum, MowseDungeonMerchantRoom, MowseDungeonDungeonTypeEnum} from "../../libraries/LibStorage.sol";
    
    import {RandomFortuneFacet} from "../RandomFortuneFacet.sol";
    import {AccessControlFacet} from "../AccessControlFacet.sol";
    import {MowseDungeonAchievementFacet} from "./MowseDungeonAchievementFacet.sol";
    import {MowseDungeonFacet} from "./MowseDungeonFacet.sol";
    
    import "../../MowseAvatar.sol";
    
    contract ForestMowseDungeonFacet is WithStorage {
        using Strings for uint256;
        using Strings for uint64;
        using Strings for uint16;
        using Strings for uint8;
        using Strings for int16;
        using Strings for int32;
        GameStorage internal gs;
    
        event DungeonStarted(uint256 indexed tokenId, MowseDungeonClassEnum class, MowseDungeonDungeonTypeEnum dungeonType);
    
        error ContractPaused(string);
        error MustOwnMowse();
        error MissingAdminRole(string);
        error HasNotUnlockedClass();
        error HasNotUnlockedCompanion();
        error HasNotUnlockedDescentLevel();
    
        modifier onlyAdmin() {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            _;
        }
        modifier notPaused() {
            if (gs.paused) revert ContractPaused("Contract is paused");
            _;
        }
        // Either owner of mowse or admin can call this function
        modifier mustOwnMowse(uint256 tokenId) {
            MowseAvatar mowseavatar = MowseAvatar(gs.mowseAvatarContractAddress);
            address ownerOfMowse = mowseavatar.ownerOf(tokenId);
            if (ownerOfMowse != msg.sender && !AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MustOwnMowse();
            _;
        }
    
        function startForestDungeon(
            uint256 tokenId,
            MowseDungeonClassEnum class,
            MowseDungeonCompanionEnum companion,
            uint256 descentLevel
        ) external notPaused mustOwnMowse(tokenId) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            // Check if player has unlocked class or companion
            if (companion != MowseDungeonCompanionEnum.NONE && player.companions[companion].isUnlocked == false) revert HasNotUnlockedCompanion();
            _checkClassUnlocked(tokenId, class);
            if (player.descentLevel.forestDungeon < descentLevel) revert HasNotUnlockedDescentLevel();
    
            // First quit any active dungeon
            if (gs.mowsedungeons[tokenId][player.dungeonRunCount].active) {
                MowseDungeonFacet(gs.diamondAddress).quitDungeon(tokenId);
            }
    
            player.dungeonRunCount++;
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
            dungeon.active = true;
            console.log("set dungeon active true");
            dungeon.descentLevel = descentLevel;
            // DEV:
            // uint256 vdfSeed = uint256(keccak256(abi.encodePacked(tokenId, gs.mowseDungeonNonce++)));
            uint256 vdfSeed = uint256(keccak256(abi.encodePacked(tokenId, gs.mowseDungeonNonce++, block.timestamp, blockhash(block.number - 1))));
            // Save vdf seed to be proved later
            gs.mowseDungeonRunCountToSeed[tokenId][player.dungeonRunCount];
            _generateRoomsForForestDungeon(vdfSeed, tokenId);
            // Set player stats
            player.mowseId = tokenId;
            player.stats = _getMowseStats(tokenId);
            player.currentCompanion = player.companions[companion];
            player.class = class;
            player.coins = 0;
            (player.maxHealth, player.shield, player.attack, player.action) = MowseDungeonFacet(gs.diamondAddress).getStatsForClass(player.stats, class);
            _resetTrinketLevels(player);
            _generateMerchantRoomItems(tokenId);
            // Reset player achievement progress for dungeon
            MowseDungeonAchievementFacet(gs.diamondAddress).resetPlayerAchievementProgress(tokenId);
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
    
            emit DungeonStarted(tokenId, class, MowseDungeonDungeonTypeEnum.FOREST);
        }
    
        function _generateMerchantRoomItems(uint256 tokenId) internal {
            MowseDungeonMerchantRoom storage room = gs.mowsedungeons[tokenId][gs.mowseDungeonPlayer[tokenId].dungeonRunCount].merchantRoom;
            // MowseDungeonMerchantItem[] memory items = new MowseDungeonMerchantItem[](5);
            uint256 seed = LibDungeon.getNewSeed(
                gs.mowsedungeons[tokenId][gs.mowseDungeonPlayer[tokenId].dungeonRunCount].id,
                gs.mowsedungeons[tokenId][gs.mowseDungeonPlayer[tokenId].dungeonRunCount].currentRoomIndex
            );
            delete room.items;
    
            for (uint256 i = 0; i < 5; i++) {
                MowseDungeonMerchantItem memory item;
                item.trinketId = MowseDungeonTrinketEnum(seed % gs.mowseDungeonTrinketDefaultsCount);
                item.trinket = gs.mowseDungeonTrinketDefaults[item.trinketId];
                item.cost = (seed % 20) + (i + 1) * 50;
                room.items.push(item);
    
                if (seed >> 2 == 0) {
                    seed = LibDungeon.getNewSeed(seed, tokenId);
                } else {
                    seed >>= 2;
                }
            }
        }
    
        function _getMowseStats(uint256 tokenId) internal view returns (int32[SKILL_TYPE_NUM] memory) {
            int32[SKILL_TYPE_NUM] memory stats;
            for (uint256 i = 0; i < SKILL_TYPE_NUM; i++) {
                stats[i] = gs.mowses[tokenId].skillLevel[i] + gs.mowses[tokenId].skillLevelBoosts[i];
            }
            return stats;
        }
    
        function _resetTrinketLevels(MowseDungeonPlayer storage _player) internal {
            for (uint256 i = 0; i < gs.mowseDungeonTrinketDefaultsCount; i++) {
                delete _player.trinkets[MowseDungeonTrinketEnum(i)].level;
            }
        }
    
        function _checkClassUnlocked(uint256 tokenId, MowseDungeonClassEnum _class) internal view {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            if (uint256(_class) > uint256(MowseDungeonClassEnum.ROGUE) && uint256(_class) < uint256(MowseDungeonClassEnum.INVALID_CLASS)) {
                if (!player.achievementsUnlocked[MowseDungeonAchievementsEnum.LEARN_THE_ROPES]) {
                    revert HasNotUnlockedClass();
                }
            }
        }
    
        function _generateRoomsForForestDungeon(uint256 seed, uint256 tokenId) internal {
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][gs.mowseDungeonPlayer[tokenId].dungeonRunCount];
            dungeon.id = seed;
            dungeon.dungeonType = MowseDungeonDungeonTypeEnum.FOREST;
    
            delete dungeon.rooms;
    
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_SLIME_1);
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_SLIME_2);
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_SLIME_3);
            _generateCoinLootRoom(dungeon);
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_MUSHY);
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_PEBBLE);
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_TOAD);
            _generateTrinketLootRoom(dungeon);
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_ROOSTA);
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_STICK);
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_WRAT_PUNK);
            _generateMerchantRoom(dungeon);
            _generateDefaultMonsterRoom(dungeon, MowseDungeonRoomEnum.FOREST_WRAT_GANG_LEADER);
        }
    
        function _generateMerchantRoom(MowseDungeon storage _dungeon) internal {
            MowseDungeonRoom storage room = _dungeon.rooms.push();
            room.id = MowseDungeonRoomEnum.MERCHANT;
            room.roomType = MowseDungeonRoomTypeEnum.MERCHANT;
            delete room.monsters;
        }
    
        function _generateCoinLootRoom(MowseDungeon storage _dungeon) internal {
            MowseDungeonRoom storage room = _dungeon.rooms.push();
            room.id = MowseDungeonRoomEnum.COIN_LOOT_ROOM;
            room.roomType = MowseDungeonRoomTypeEnum.COIN_LOOT;
            delete room.monsters;
        }
    
        function _generateTrinketLootRoom(MowseDungeon storage _dungeon) internal {
            MowseDungeonRoom storage room = _dungeon.rooms.push();
            room.id = MowseDungeonRoomEnum.TRINKET_LOOT_ROOM;
            room.roomType = MowseDungeonRoomTypeEnum.TRINKET_LOOT;
            delete room.monsters;
        }
    
        function _generateDefaultMonsterRoom(MowseDungeon storage _dungeon, MowseDungeonRoomEnum roomEnum) internal {
            MowseDungeonRoom storage room = _dungeon.rooms.push();
            MowseDungeonRoom storage defaultRoom = gs.mowseDungeonRoomDefaults[roomEnum];
    
            room.id = defaultRoom.id;
            room.roomType = defaultRoom.roomType;
    
            delete room.monsters;
    
            for (uint256 i = 0; i < defaultRoom.monsters.length; i++) {
                // MowseDungeonMonsterAttributes memory monsterAttribute = defaultRoom.monsters[i];
                // monsterAttribute.monsterId = gs.mowseDungeonMonsterIdCount++;
                room.monsters.push(defaultRoom.monsters[i]);
            }
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibUtils.sol";
    import "../../libraries/LibStorage.sol";
    import "../../libraries/LibDungeon.sol";
    
    import {WithStorage, GameStorage, MowseDungeonMonsterEnum, MowseDungeonMonsterAttributes, MowseDungeonSpecialAbilityEnum, MowseDungeonStatusEffectEnum, MowseDungeonClassEnum, MowseDungeonCompanionEnum, MowseDungeonMerchantRoom, MowseDungeonAchievementsEnum, MowseDungeonPostBattleResults, MowseDungeonRoomTypeEnum, MowseDungeonBattleStatusEnum} from "../../libraries/LibStorage.sol";
    
    import {RandomFortuneFacet} from "../RandomFortuneFacet.sol";
    import {AccessControlFacet} from "../AccessControlFacet.sol";
    import {MowseDungeonAdminUtilsFacet} from "./MowseDungeonAdminUtilsFacet.sol";
    import "../../MowseGold.sol";
    import "../../MowseAvatar.sol";
    
    contract MowseDungeonAchievementFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        event AchievementUnlocked(uint256 indexed tokenId, MowseDungeonAchievementsEnum achievementId);
        event AchievementClaimed(uint256 indexed tokenId, MowseDungeonAchievementsEnum achievementId);
        event CompanionUnlocked(uint256 indexed tokenId, MowseDungeonCompanionEnum companionId);
    
        error MissingAdminRole(string);
        error AchievementAlreadyClaimed();
        error AchievementNotUnlocked();
        error MustOwnMowse();
    
        modifier onlyAdmin() {
            console.log('onlyAdmin');
            console.log(msg.sender);
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            _;
        }
        modifier mustOwnMowse(uint256 tokenId) {
            MowseAvatar mowseavatar = MowseAvatar(gs.mowseAvatarContractAddress);
            address ownerOfMowse = mowseavatar.ownerOf(tokenId);
            if (ownerOfMowse != msg.sender && !AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MustOwnMowse();
            _;
        }
    
        function getUnlockedAchievements(uint256 tokenId) external view returns (bool[] memory) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            bool[] memory unlockedAchievements = new bool[](uint256(MowseDungeonAchievementsEnum.INVALID_ACHIEVEMENT));
            for (uint256 i = 0; i < uint256(MowseDungeonAchievementsEnum.INVALID_ACHIEVEMENT); i++) {
                unlockedAchievements[i] = player.achievementsUnlocked[MowseDungeonAchievementsEnum(i)];
            }
            return unlockedAchievements;
            
        }
        function getClaimedAchievements(uint256 tokenId) external view returns (bool[] memory) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            bool[] memory claimedAchievements = new bool[](uint256(MowseDungeonAchievementsEnum.INVALID_ACHIEVEMENT));
            for (uint256 i = 0; i < uint256(MowseDungeonAchievementsEnum.INVALID_ACHIEVEMENT); i++) {
                claimedAchievements[i] = player.achievementsClaimed[MowseDungeonAchievementsEnum(i)];
            }   
            return claimedAchievements;
        }
    
        function getPlayerAchievementsProgress(uint256 tokenId) external view returns (MowseDungeonSimplePlayerAchievementsProgress memory) {
            return gs.mowseDungeonPlayer[tokenId].achievementsProgress;
        }
    
        function resetPlayerAchievementProgress(uint256 tokenId) external onlyAdmin {
            console.log('resetPlayerAChievementProgress');
            delete gs.mowseDungeonPlayer[tokenId].achievementsProgress;
        }
    
        // Add to player achievement progress and unlock achievements based on progress
        function addToPlayerAchievementProgress(MowseDungeonPostBattleResults memory results) external onlyAdmin {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[results.mowseId];
            MowseDungeon storage dungeon = gs.mowsedungeons[player.mowseId][player.dungeonRunCount];
            player.achievementsProgress.damageDealt += results.achievementProgress.damageDealt;
            player.achievementsProgress.damageTaken += results.achievementProgress.damageTaken;
            player.achievementsProgress.healthHealed += results.achievementProgress.healthHealed;
            player.achievementsProgress.burnDamageDealt += results.achievementProgress.burnDamageDealt;
            player.achievementsProgress.poisonDamageDealt += results.achievementProgress.poisonDamageDealt;
            player.achievementsProgress.bleedDamageDealt += results.achievementProgress.bleedDamageDealt;
            player.achievementsProgress.curseDamageDealt += results.achievementProgress.curseDamageDealt;
            player.achievementsProgress.trinketsStolen += results.achievementProgress.trinketsStolen;
    
            if (results.achievementProgress.damageDealt > 1000 && player.achievementsUnlocked[MowseDungeonAchievementsEnum.ONE_MAN_ARMY] == false) {
                // Grant achievement for dealing over 1000 damage in a single dungeon run
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.ONE_MAN_ARMY] = true;
                player.achievements.oneManArmy = true;
                emit AchievementUnlocked(player.mowseId, MowseDungeonAchievementsEnum.ONE_MAN_ARMY);
            }
            if (results.achievementProgress.poisonDamageDealt > 99 && player.achievementsUnlocked[MowseDungeonAchievementsEnum.POISON_MASTER] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.POISON_MASTER] = true;
                player.achievements.poisonMaster = true;
                emit AchievementUnlocked(player.mowseId, MowseDungeonAchievementsEnum.POISON_MASTER);
            }
            if (
                dungeon.rooms[dungeon.currentRoomIndex].roomType == MowseDungeonRoomTypeEnum.BOSS &&
                results.achievementProgress.damageTaken == 0 &&
                results.status == MowseDungeonBattleStatusEnum.VICTORY &&
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.UNTOUCHABLE] == false
            ) {
                // Grant achievement for defeating boss without taking damage
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.UNTOUCHABLE] = true;
                player.achievements.untouchable = true;
                emit AchievementUnlocked(player.mowseId, MowseDungeonAchievementsEnum.UNTOUCHABLE);
            }
            if (results.achievementProgress.trinketsStolen >= 5 && player.achievementsUnlocked[MowseDungeonAchievementsEnum.LOOT_GOBLIN] == false) {
                // Grant achievement for stealing 5 trinkets in a single dungeon run
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.LOOT_GOBLIN] = true;
                player.achievements.lootGoblin = true;
                emit AchievementUnlocked(player.mowseId, MowseDungeonAchievementsEnum.LOOT_GOBLIN);
            }
            if (player.coins >= 500 && player.achievementsUnlocked[MowseDungeonAchievementsEnum.COIN_GOBLIN] == false) {
                // Grant achievement for having 500 coins
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.COIN_GOBLIN] = true;
                player.achievements.coinGoblin = true;
                emit AchievementUnlocked(player.mowseId, MowseDungeonAchievementsEnum.COIN_GOBLIN);
            }
        }
    
        // Unlock achievements on victory
        function getDungeonVictoryAchievements(uint256 tokenId, MowseDungeonDungeonTypeEnum _dungeonType) external onlyAdmin {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
            if (player.class == MowseDungeonClassEnum.WARRIOR && player.achievementsUnlocked[MowseDungeonAchievementsEnum.WARRIOR] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.WARRIOR] = true;
                player.achievements.warrior = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.WARRIOR);
            } else if (player.class == MowseDungeonClassEnum.MAGE && player.achievementsUnlocked[MowseDungeonAchievementsEnum.MAGE] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.MAGE] = true;
                player.achievements.mage = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.MAGE);
            } else if (player.class == MowseDungeonClassEnum.ARCHER && player.achievementsUnlocked[MowseDungeonAchievementsEnum.ARCHER] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.ARCHER] = true;
                player.achievements.archer = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.ARCHER);
            } else if (player.class == MowseDungeonClassEnum.ROGUE && player.achievementsUnlocked[MowseDungeonAchievementsEnum.ROGUE] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.ROGUE] = true;
                player.achievements.rogue = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.ROGUE);
            } else if (player.class == MowseDungeonClassEnum.PALADIN && player.achievementsUnlocked[MowseDungeonAchievementsEnum.PALADIN] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.PALADIN] = true;
                player.achievements.paladin = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.PALADIN);
            } else if (player.class == MowseDungeonClassEnum.GUARDIAN && player.achievementsUnlocked[MowseDungeonAchievementsEnum.GUARDIAN] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.GUARDIAN] = true;
                player.achievements.guardian = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.GUARDIAN);
            } else if (player.class == MowseDungeonClassEnum.BERSERKER && player.achievementsUnlocked[MowseDungeonAchievementsEnum.BERSERKER] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.BERSERKER] = true;
                player.achievements.berserker = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.BERSERKER);
            } else if (player.class == MowseDungeonClassEnum.CLERIC && player.achievementsUnlocked[MowseDungeonAchievementsEnum.CLERIC] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.CLERIC] = true;
                player.achievements.cleric = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.CLERIC);
            } else if (player.class == MowseDungeonClassEnum.NECROMANCER && player.achievementsUnlocked[MowseDungeonAchievementsEnum.NECROMANCER] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.NECROMANCER] = true;
                player.achievements.necromancer = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.NECROMANCER);
            } else if (player.class == MowseDungeonClassEnum.BARD && player.achievementsUnlocked[MowseDungeonAchievementsEnum.BARD] == false) {
                player.achievementsUnlocked[MowseDungeonAchievementsEnum.BARD] = true;
                player.achievements.bard = true;
                emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.BARD);
            }
            if (player.achievementsUnlocked[MowseDungeonAchievementsEnum.LEARN_THE_ROPES] == false) {
                if (
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.WARRIOR] && 
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.MAGE] && 
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.ARCHER] && 
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.ROGUE] 
                ) {
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.LEARN_THE_ROPES] = true;
                    player.achievements.learnTheRopes = true;
                    emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.LEARN_THE_ROPES);
                }
            }
            if (_dungeonType == MowseDungeonDungeonTypeEnum.FOREST && dungeon.descentLevel < 11 && dungeon.descentLevel == player.descentLevel.forestDungeon) {
                player.descentLevel.forestDungeon++;
                if (player.descentLevel.forestDungeon == 1 && !player.achievementsUnlocked[MowseDungeonAchievementsEnum.NOVICE]) {
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.NOVICE] = true;
                    player.achievements.novice = true;
                console.log("unlocked novice achievement");
                    emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.NOVICE);
                } else if (player.descentLevel.forestDungeon == 11 && !player.achievementsUnlocked[MowseDungeonAchievementsEnum.FOREST_MASTER]) {
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.FOREST_MASTER] = true;
                    player.achievements.forestMaster = true;
                    emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.FOREST_MASTER);
                }
            } else if (_dungeonType == MowseDungeonDungeonTypeEnum.MEDIEVAL && dungeon.descentLevel < 10 && dungeon.descentLevel == player.descentLevel.medievalDungeon) {
                player.descentLevel.medievalDungeon++;
                if (player.descentLevel.medievalDungeon == 1 && !player.achievementsUnlocked[MowseDungeonAchievementsEnum.COPPER_SQUIRE]) {
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.COPPER_SQUIRE] = true;
                    player.achievements.copperSquire = true;
                    emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.COPPER_SQUIRE);
                } else if (player.descentLevel.medievalDungeon == 4 && !player.achievementsUnlocked[MowseDungeonAchievementsEnum.IRON_MAIDEN]) {
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.IRON_MAIDEN] = true;
                    player.achievements.ironMaiden = true;
                    emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.IRON_MAIDEN);
                } else if (player.descentLevel.medievalDungeon == 7 && !player.achievementsUnlocked[MowseDungeonAchievementsEnum.STEEL_KNIGHT]) {
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.STEEL_KNIGHT] = true;
                    player.achievements.steelKnight = true;
                    emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.STEEL_KNIGHT);
                } else if (player.descentLevel.medievalDungeon == 10 && !player.achievementsUnlocked[MowseDungeonAchievementsEnum.GOLD_KING]) {
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.GOLD_KING] = true;
                    player.achievements.goldKing = true;
                    emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.GOLD_KING);
                } else if (player.descentLevel.medievalDungeon == 11 && !player.achievementsUnlocked[MowseDungeonAchievementsEnum.MASTER_OF_DEADLY_SINS]) {
                    player.achievementsUnlocked[MowseDungeonAchievementsEnum.MASTER_OF_DEADLY_SINS] = true;
                    player.achievements.masterOfDeadlySins = true;
                    emit AchievementUnlocked(tokenId, MowseDungeonAchievementsEnum.MASTER_OF_DEADLY_SINS);
                }
            }
        }
        function claimAchievementPrize(uint256 tokenId, MowseDungeonAchievementsEnum achievementId) external mustOwnMowse(tokenId) {
            console.log('ClaimAchievementPrize');
            console.log(msg.sender);
            // Make sure user owns the tokenId
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
            MowseAvatar mowseavatar = MowseAvatar(gs.mowseAvatarContractAddress);
            address ownerOfMowse = mowseavatar.ownerOf(tokenId);
    
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            if (!player.achievementsUnlocked[achievementId]) revert AchievementNotUnlocked();
            if (player.achievementsClaimed[achievementId]) revert AchievementAlreadyClaimed();
            player.achievementsClaimed[achievementId] = true;
            uint256 mintAmount = 500 * 10**18;
            console.log('MINT MOWSEGOLD FROM CLAIMED ACHIEVEMENT');
            if (achievementId == MowseDungeonAchievementsEnum.NOVICE) {
                MowseDungeonAdminUtilsFacet(gs.diamondAddress).unlockCompanion(tokenId, MowseDungeonCompanionEnum.WONDER_CHILD);
            } else if (achievementId == MowseDungeonAchievementsEnum.COPPER_SQUIRE) {
                MowseDungeonAdminUtilsFacet(gs.diamondAddress).unlockCompanion(tokenId, MowseDungeonCompanionEnum.DEVILISH);
            } else if (achievementId == MowseDungeonAchievementsEnum.UNTOUCHABLE) {
                MowseDungeonAdminUtilsFacet(gs.diamondAddress).unlockCompanion(tokenId, MowseDungeonCompanionEnum.DIAMOND_BACK);
            }
            if (
                achievementId == MowseDungeonAchievementsEnum.FOREST_MASTER ||
                achievementId == MowseDungeonAchievementsEnum.MASTER_OF_DEADLY_SINS
            ) {
                mintAmount += 10000 * 10**18;
            }
            mowsegold.mint(ownerOfMowse, mintAmount);
            emit AchievementClaimed(tokenId, achievementId);
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibUtils.sol";
    import "../../libraries/LibStorage.sol";
    
    import {WithStorage, GameStorage, MowseDungeonCompanionEnum, MowseDungeonPlayer} from "../../libraries/LibStorage.sol";
    
    import {AccessControlFacet} from "../AccessControlFacet.sol";
    
    contract MowseDungeonAdminUtilsFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        event CompanionUnlocked(uint256 indexed tokenId, MowseDungeonCompanionEnum companionId);
    
        error MissingAdminRole(string);
        
        modifier onlyAdmin() {
          console.log('Only Admin', msg.sender);
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            _;
        }
    
        function unlockCompanion(uint256 tokenId, MowseDungeonCompanionEnum _companionId) external onlyAdmin {
            console.log('Unlock Companion');
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            player.companions[_companionId].isUnlocked = true;
            player.companions[_companionId].companionId = _companionId;
            player.companions[_companionId].level = 1;
            emit CompanionUnlocked(tokenId, MowseDungeonCompanionEnum(_companionId));
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    import {
        GameStorage, 
        MowseDungeonCompanionEnum, 
        MowseDungeonCompanion, 
        MowseDungeonPlayer, 
        MowseDungeonSimpleMonster,  
        MowseDungeonBattleSimulation,
        MowseDungeonBattleLog, 
        MowseDungeonBattleLogTypeEnum, 
        MowseDungeonBattleLogSourceEnum
    } from "../../libraries/LibStorage.sol";
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibDungeon.sol";
    import {AccessControlFacet} from "../AccessControlFacet.sol";
    import {RandomFortuneFacet} from "../RandomFortuneFacet.sol";
    
    import "../../MowseAvatar.sol";
    
    contract MowseDungeonCompanionAttackFacet {
        using Strings for uint256;
        GameStorage internal gs;
        
        error MustOwnMowse();
        error MaxLevelReached();
        error NotEnoughExperience();
        error InvalidCompanionId();
        error CompanionNotUnlocked();
    
        modifier mustOwnMowse(uint256 tokenId) {
            MowseAvatar mowseavatar = MowseAvatar(gs.mowseAvatarContractAddress);
            address ownerOfMowse = mowseavatar.ownerOf(tokenId);
            if (ownerOfMowse != msg.sender && !AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MustOwnMowse();
            _;
        }
    
        function getCompanions(uint256 tokenId) external view returns (MowseDungeonCompanion[] memory) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeonCompanion[] memory companions = new MowseDungeonCompanion[](uint256(MowseDungeonCompanionEnum.INVALID_COMPANION) - 1);
            for (uint256 i = 1; i < uint256(MowseDungeonCompanionEnum.INVALID_COMPANION); i++) {
                companions[i - 1] = player.companions[MowseDungeonCompanionEnum(i)];
                companions[i - 1].companionId = MowseDungeonCompanionEnum(i);
            }
            return companions;
        }
        function canLevelUpCompanion(uint256 tokenId, MowseDungeonCompanionEnum companionId) public view returns (bool) {
            if (companionId == MowseDungeonCompanionEnum.NONE || companionId == MowseDungeonCompanionEnum.INVALID_COMPANION) return false;
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeonCompanion storage companion = player.companions[companionId];
            if (!companion.isUnlocked) return false;
            if (companion.level >= 10) return false;
            if (companion.experience < getCompanionExpNeededForLevelUp(companion.level)) return false;
            return true;
        }
        function getCompanionExpNeededForLevelUp(uint256 level) public pure returns (uint256) {
            if (level == 0) return 0;
            return 1000 * 2**(level - 1);
        }
        function levelUpCompanion(uint256 tokenId, MowseDungeonCompanionEnum companionId) external mustOwnMowse(tokenId) {
            if (companionId == MowseDungeonCompanionEnum.NONE || companionId == MowseDungeonCompanionEnum.INVALID_COMPANION) revert InvalidCompanionId();
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeonCompanion storage companion = player.companions[companionId];
            if (!companion.isUnlocked) revert CompanionNotUnlocked();
            if (companion.level >= 10) revert MaxLevelReached();
            if (!canLevelUpCompanion(tokenId, companionId)) revert NotEnoughExperience();
            companion.experience -= getCompanionExpNeededForLevelUp(companion.level);
            companion.level++;
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        function performCompanionAction(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster
        ) external pure returns (MowseDungeonBattleSimulation memory) {
            if (_battleSimulation.player.companion.companionId == MowseDungeonCompanionEnum.NONE) {
                return _battleSimulation;
            } else {
                if (_battleSimulation.player.companion.companionId == MowseDungeonCompanionEnum.DIAMOND_BACK) {
                    _battleSimulation = _diamondBack(_battleSimulation);
                } else if (_battleSimulation.player.companion.companionId == MowseDungeonCompanionEnum.DEVILISH) {
                    _battleSimulation = _devilish(_battleSimulation, _monster, _battleSimulation.targetIndex);
                } else if (_battleSimulation.player.companion.companionId == MowseDungeonCompanionEnum.WONDER_CHILD) {
                    _battleSimulation = _wonderChild(_battleSimulation);
                }
            }
            // Resolve back the target monster to the battle simulation monster index
            if (_battleSimulation.targetIndex == 0) {
                _battleSimulation.monster1 = _monster;
            } else if (_battleSimulation.targetIndex == 1) {
                _battleSimulation.monster2 = _monster;
            } else {
                _battleSimulation.monster3 = _monster;
            }
            return _battleSimulation;
        }
    
        function _diamondBack(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.player.statusEffects.thorns += _battleSimulation.player.companion.level;
            _battleSimulation.player.shield += 5 * _battleSimulation.player.companion.level;
            _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_THORNS, _battleSimulation.player.companion.level);
            return _battleSimulation;
        }
    
        function _devilish(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            // Chance to apply death
            if (_battleSimulation.seed >> 2 == 0) {
                _battleSimulation.seed = LibDungeon.getNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
            } else {
                _battleSimulation.seed >>= 2;
            }
            if (_battleSimulation.seed % 100 < 10 + _battleSimulation.player.companion.level * 10) {
                if (_monster.statusEffects.death > 0) {
                    _monster.statusEffects.death--;
                    if (_monster.statusEffects.death == 0) {
                        if (_monster.isBoss) {
                            (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                                _monster.maxHealth / 10,
                                _monster.currentHealth,
                                _monster.shield
                            );
                            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.COMPANION, _monster.maxHealth / 10);    
                        } else {
                            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.COMPANION, _monster.currentHealth);    
                            _monster.currentHealth = 0;
                        }
                    }
                } else {
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_DEATH, 5);    
                    _monster.statusEffects.death = 5;
                }
            }
            if (_monster.currentHealth > 0) {
                // Deal 6 * level damage
                (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                    6 * _battleSimulation.player.companion.level,
                    _monster.currentHealth,
                    _monster.shield
                );
                _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.COMPANION, 6 * _battleSimulation.player.companion.level);    
            }
            return _battleSimulation;
        }
    
        function _wonderChild(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.player.statusEffects.regen++;
            _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_REGEN, 1);
            _battleSimulation.player.currentHealth += 3 * _battleSimulation.player.companion.level;
            _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.COMPANION, MowseDungeonBattleLogSourceEnum.PLAYER, 3 * _battleSimulation.player.companion.level);
            if (_battleSimulation.player.currentHealth > _battleSimulation.player.maxHealth) {
                _battleSimulation.player.currentHealth = _battleSimulation.player.maxHealth;
            }
    
            if (_battleSimulation.seed >> 2 == 0) {
                _battleSimulation.seed = LibDungeon.getNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
            } else {
                _battleSimulation.seed >>= 2;
            }
            if (_battleSimulation.seed % 100 < 20 + _battleSimulation.player.companion.level * 20) {
                _battleSimulation.player.statusEffects.strengthen++;
                _battleSimulation.player.statusEffects.fireResist++;
                _battleSimulation.player.statusEffects.poisonResist++;
                _battleSimulation.player.statusEffects.freezeResist++;
                _battleSimulation.player.statusEffects.stunResist++;
                _battleSimulation.player.statusEffects.sleepResist++;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STRENGTHEN, 1);
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_FIRE_RESIST, 1);
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON_RESIST, 1);
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_FREEZE_RESIST, 1);
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STUN_RESIST, 1);
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_SLEEP_RESIST, 1);
            }
            return _battleSimulation;
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibUtils.sol";
    import "../../libraries/SlothVerifiableDelay.sol";
    import "../../libraries/LibStorage.sol";
    import "../../libraries/LibDungeon.sol";
    
    import {WithStorage, GameStorage, MowseDungeonMonsterEnum, MowseDungeonMonsterAttributes, MowseDungeonSpecialAbilityEnum, MowseDungeonStatusEffectEnum, MowseDungeonClassEnum, MowseDungeonCompanionEnum, MowseDungeonMerchantRoom, MowseDungeonDungeonTypeEnum, MowseDungeonDungeonRewards} from "../../libraries/LibStorage.sol";
    
    import {AccessControlFacet} from "../AccessControlFacet.sol";
    import {MowseDungeonSimulateBattleFacet} from "./MowseDungeonSimulateBattleFacet.sol";
    import {MowseDungeonAchievementFacet} from "./MowseDungeonAchievementFacet.sol";
    import {MowseJobFacet} from "../MowseJobFacet.sol";
    import {RandomFortuneFacet} from "../RandomFortuneFacet.sol";
    
    import "../../MowseGold.sol";
    import "../../MowseAvatar.sol";
    
    contract MowseDungeonFacet is WithStorage {
        using Strings for uint256;
        using Strings for uint64;
        using Strings for uint16;
        using Strings for uint8;
        using Strings for int16;
        using Strings for int32;
        GameStorage internal gs;
    
        event DungeonStarted(uint256 indexed tokenId, MowseDungeonClassEnum class);
        event DungeonVictory(uint256 indexed tokenId, uint256 dungeonId);
        event DungeonDefeat(uint256 indexed tokenId, uint256 dungeonId);
        event BattleVictory(uint256 indexed tokenId, uint256 dungeonId);
        event GainedTrinket(uint256 indexed tokenId, MowseDungeonTrinketEnum trinketId);
    
        error ContractPaused(string);
        error MustOwnMowse();
        error MissingAdminRole(string);
        error InvalidDungeonSimulation();
        error MonsterRoomBattleOngoing();
        error InvalidTrinketLootRoomIndex();
        error InvalidMerchantRoomIndex();
        error InvalidTrinketId();
        error MerchantItemAlreadyPurchased();
        error MerchantRoomInsufficientCoins();
        error InvalidRoom();
    
        modifier onlyAdmin() {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            _;
        }
        modifier notPaused() {
            if (gs.paused) revert ContractPaused("Contract is paused");
            _;
        }
        modifier mustOwnMowse(uint256 tokenId) {
            MowseAvatar mowseavatar = MowseAvatar(gs.mowseAvatarContractAddress);
            address ownerOfMowse = mowseavatar.ownerOf(tokenId);
            if (ownerOfMowse != msg.sender && !AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MustOwnMowse();
            _;
        }
        // DEV:
        // function overrideStats(uint256 tokenId, int32[SKILL_TYPE_NUM] memory stats) external onlyAdmin {
        //     MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
        //     player.stats = stats;
        // }
    
        function quitDungeon(uint256 tokenId) public notPaused mustOwnMowse(tokenId) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
            if (!dungeon.active) revert InvalidRoom();
    
            emit DungeonDefeat(tokenId, dungeon.id);
            _getDungeonRewards(tokenId, false);
        }
    
        function resolveMonsterRoom(uint256 tokenId, uint8[] calldata actions, bytes32 actionsRunHash, MowseDungeonPostBattleResults calldata results, bytes32 resultsHash) external notPaused mustOwnMowse(tokenId) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
    
            if (!dungeon.active) revert InvalidRoom();
            if (
                !(dungeon.rooms[dungeon.currentRoomIndex].roomType == MowseDungeonRoomTypeEnum.MONSTER ||
                    dungeon.rooms[dungeon.currentRoomIndex].roomType == MowseDungeonRoomTypeEnum.BOSS)
            ) revert InvalidRoom();
    
            if (keccak256(abi.encodePacked(keccak256(abi.encode(actions)),keccak256(abi.encode(dungeon.id)))) != actionsRunHash) revert InvalidDungeonSimulation();
            if (keccak256(abi.encodePacked(keccak256(abi.encode(results)),keccak256(abi.encode(dungeon.id)))) != resultsHash) revert InvalidDungeonSimulation();
            
            // Verify results by quickly checking basic info
            if (results.mowseId != tokenId) revert InvalidDungeonSimulation();
            if (results.descentLevel != dungeon.descentLevel) revert InvalidDungeonSimulation();
    
            // Add to player achievement progress
            MowseDungeonAchievementFacet(gs.diamondAddress).addToPlayerAchievementProgress(results);
    
            if (results.status == MowseDungeonBattleStatusEnum.VICTORY) {
                if (dungeon.currentRoomIndex == dungeon.rooms.length - 1) {
                    emit DungeonVictory(tokenId, dungeon.id);
                    _getDungeonRewards(tokenId, true);
                    MowseDungeonAchievementFacet(gs.diamondAddress).getDungeonVictoryAchievements(tokenId, dungeon.dungeonType);
                } else {
                    // Reset player stats back to base
                    (player.maxHealth, player.shield, player.attack, player.action) = getStatsForClass(player.stats, player.class);
                    // Generate a trinket drop from the monster
                    _generateMonsterDrop(tokenId);
                    // Update MowseDungeonPlayer storage (coins, stolen items, etc);
                    _updateMowseDungeonPlayerAfterMonsterRoomVictory(tokenId, results);
                    // Go to next room
                    dungeon.currentRoomIndex++;
                }
            } else if (results.status == MowseDungeonBattleStatusEnum.DEFEAT) {
                emit DungeonDefeat(tokenId, dungeon.id);
                _getDungeonRewards(tokenId, false);
            } else if (results.status == MowseDungeonBattleStatusEnum.ONGOING) {
                revert MonsterRoomBattleOngoing();
            }
            
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        function _generateMonsterDrop(uint256 tokenId) internal {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            uint256 seed = LibDungeon.getNewSeed(
                gs.mowsedungeons[tokenId][player.dungeonRunCount].id,
                gs.mowsedungeons[tokenId][player.dungeonRunCount].currentRoomIndex
            );
            console.log("Monster drop: ", seed % gs.mowseDungeonTrinketDefaultsCount);
            MowseDungeonTrinketEnum trinket = MowseDungeonTrinketEnum(seed % gs.mowseDungeonTrinketDefaultsCount);
            player.trinkets[trinket].level++;
    
            emit GainedTrinket(tokenId, trinket);
        }
    
        function _updateMowseDungeonPlayerAfterMonsterRoomVictory(uint256 tokenId, MowseDungeonPostBattleResults memory results) internal {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            // Update player coins with battle simulation results coins (monsters can steal coins)
            player.coins = results.coins;
    
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
            uint256 coinsDropped;
            console.log("Before Player coins: ", player.coins);
            // Update trinkets from battle results (in the case a steal was successful)
            for (uint256 i = 0; i < results.stolenTrinketCount; i++) {
                player.trinkets[results.stolenTrinkets[i]].level++;
            }
            // Grant coins from defeated monsters
            if (results.monster1 != MowseDungeonMonsterEnum.NONE) {
                coinsDropped +=
                    gs.mowseDungeonMonsterDefaults[results.monster1].coins +
                    (gs.mowseDungeonMonsterDefaults[results.monster1].coins *
                        (player.trinkets[MowseDungeonTrinketEnum.GOLD_RING].level * 10 + 10)) /
                    100;
            }
            if (results.monster2 != MowseDungeonMonsterEnum.NONE) {
                coinsDropped +=
                    gs.mowseDungeonMonsterDefaults[results.monster2].coins +
                    (gs.mowseDungeonMonsterDefaults[results.monster2].coins *
                        (player.trinkets[MowseDungeonTrinketEnum.GOLD_RING].level * 10 + 10)) /
                    100;
            }
            if (results.monster3 != MowseDungeonMonsterEnum.NONE) {
                coinsDropped +=
                    gs.mowseDungeonMonsterDefaults[results.monster3].coins +
                    (gs.mowseDungeonMonsterDefaults[results.monster3].coins *
                        (player.trinkets[MowseDungeonTrinketEnum.GOLD_RING].level * 10 + 10)) /
                    100;
            }
    
            // descentLevel 3,7,8,10 drop 25% less coins
            if (dungeon.descentLevel == 3 || dungeon.descentLevel == 7 || dungeon.descentLevel == 8 || dungeon.descentLevel == 10) {
                coinsDropped = (coinsDropped * 75) / 100;
            }
            player.coins += coinsDropped;
    
            console.log("Player coins: ", player.coins);
        }
    
        // Coin Loot Room
        function resolveCoinLootRoom(uint256 tokenId, bool isDoubleOrNothing) external notPaused mustOwnMowse(tokenId) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
            if (!dungeon.active) revert InvalidRoom();
            if (dungeon.rooms[dungeon.currentRoomIndex].roomType != MowseDungeonRoomTypeEnum.COIN_LOOT) revert InvalidRoom();
    
            uint256 seed = LibDungeon.getNewSeed(dungeon.id, dungeon.currentRoomIndex);
            if (isDoubleOrNothing) {
                if (seed % 100 > 60) {
                    console.log("Double or nothing succeed");
                    player.coins += 100;
                }
            } else {
                player.coins += 50;
            }
            dungeon.currentRoomIndex++;
         
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        // Trinket Loot Room
        function getTrinketData(MowseDungeonTrinketEnum trinketId) public view returns (MowseDungeonTrinket memory) {
            if (uint256(trinketId) >= gs.mowseDungeonTrinketDefaultsCount) revert InvalidTrinketId();
            return gs.mowseDungeonTrinketDefaults[trinketId];
        }
    
        function _getTrinketLootRoom(uint256 tokenId) internal view returns (MowseDungeonTrinketEnum[] memory) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            uint256 seed = LibDungeon.getNewSeed(
                gs.mowsedungeons[tokenId][player.dungeonRunCount].id,
                gs.mowsedungeons[tokenId][player.dungeonRunCount].currentRoomIndex
            );
            MowseDungeonTrinketEnum[] memory trinkets = new MowseDungeonTrinketEnum[](3);
            for (uint256 i = 0; i < 3; i++) {
                trinkets[i] = MowseDungeonTrinketEnum(seed % gs.mowseDungeonTrinketDefaultsCount);
                if (seed >> 2 == 0) {
                    seed = LibDungeon.getNewSeed(seed, tokenId);
                } else {
                    seed >>= 2;
                }
            }
            return trinkets;
        }
    
        function getTrinketLootRoom(uint256 tokenId) external view returns (MowseDungeonTrinket[] memory) {
            MowseDungeonTrinketEnum[] memory trinkets = _getTrinketLootRoom(tokenId);
            MowseDungeonTrinket[] memory trinketData = new MowseDungeonTrinket[](3);
            for (uint256 i = 0; i < 3; i++) {
                trinketData[i] = getTrinketData(trinkets[i]);
            }
            return trinketData;
        }
    
        function resolveTrinketLootRoom(uint256 tokenId, uint256 index) external notPaused mustOwnMowse(tokenId) {
            if (index > 2) revert InvalidTrinketLootRoomIndex();
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
            if (!dungeon.active) revert InvalidRoom();
            if (dungeon.rooms[dungeon.currentRoomIndex].roomType != MowseDungeonRoomTypeEnum.TRINKET_LOOT) revert InvalidRoom();
    
            MowseDungeonTrinketEnum[] memory trinkets = _getTrinketLootRoom(tokenId);
            player.trinkets[trinkets[index]].level++;
    
            emit GainedTrinket(tokenId, trinkets[index]);
    
            gs.mowsedungeons[tokenId][player.dungeonRunCount].currentRoomIndex++;
        
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        // Merchant Room
        function resolveMerchantRoom(uint256 tokenId) external notPaused mustOwnMowse(tokenId) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
            if (!dungeon.active) revert InvalidRoom();
            if (dungeon.rooms[dungeon.currentRoomIndex].roomType != MowseDungeonRoomTypeEnum.MERCHANT) revert InvalidRoom();
    
            // Generate new merchant room for next time
            _generateMerchantRoomItems(tokenId);
    
            gs.mowsedungeons[tokenId][player.dungeonRunCount].currentRoomIndex++;
        
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        function _generateMerchantRoomItems(uint256 tokenId) internal {
            MowseDungeonMerchantRoom storage room = gs.mowsedungeons[tokenId][gs.mowseDungeonPlayer[tokenId].dungeonRunCount].merchantRoom;
            // MowseDungeonMerchantItem[] memory items = new MowseDungeonMerchantItem[](5);
            uint256 seed = LibDungeon.getNewSeed(
                gs.mowsedungeons[tokenId][gs.mowseDungeonPlayer[tokenId].dungeonRunCount].id,
                gs.mowsedungeons[tokenId][gs.mowseDungeonPlayer[tokenId].dungeonRunCount].currentRoomIndex
            );
            delete room.items;
    
            for (uint256 i = 0; i < 5; i++) {
                MowseDungeonMerchantItem memory item;
                item.trinketId = MowseDungeonTrinketEnum(seed % gs.mowseDungeonTrinketDefaultsCount);
                item.trinket = gs.mowseDungeonTrinketDefaults[item.trinketId];
                item.cost = (seed % 20) + (i + 1) * 50;
                room.items.push(item);
    
                if (seed >> 2 == 0) {
                    seed = LibDungeon.getNewSeed(seed, tokenId);
                } else {
                    seed >>= 2;
                }
            }
        }
    
        function getMerchantRoom(uint256 tokenId) public view returns (MowseDungeonMerchantRoom memory) {
            return gs.mowsedungeons[tokenId][gs.mowseDungeonPlayer[tokenId].dungeonRunCount].merchantRoom;
        }
    
        function purchaseMerchantItem(uint256 tokenId, uint256 index) external notPaused mustOwnMowse(tokenId) {
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
            if (!dungeon.active) revert InvalidRoom();
            if (dungeon.rooms[dungeon.currentRoomIndex].roomType != MowseDungeonRoomTypeEnum.MERCHANT) revert InvalidRoom();
            if (index > 4) revert InvalidMerchantRoomIndex();
            MowseDungeonMerchantRoom storage merchantRoom = gs.mowsedungeons[tokenId][player.dungeonRunCount].merchantRoom;
            MowseDungeonMerchantItem storage item = merchantRoom.items[index];
            if (item.hasPurchased) revert MerchantItemAlreadyPurchased();
            if (player.coins >= item.cost) {
                player.coins -= item.cost;
                player.trinkets[item.trinketId].level++;
                item.hasPurchased = true;
    
                emit GainedTrinket(tokenId, item.trinketId);
            } else {
                revert MerchantRoomInsufficientCoins();
            }
        
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        // Chr, Con, Dex, Int, Luk, Str, Wis
        function getStatsForClass(int32[SKILL_TYPE_NUM] memory _playerStats, MowseDungeonClassEnum _class) public pure returns (uint256, uint256, uint256, uint256) {
            uint256 maxHealth = 50;
            uint256 shield = 0;
            uint256 attack = 10;
            uint256 action = 1;
    
            if (_class == MowseDungeonClassEnum.WARRIOR) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 3;
                }
                if (_playerStats[STAT_STRENGTH] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_STRENGTH]);
                }
                if (_playerStats[STAT_DEXTERITY] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_DEXTERITY]) / 3;
                }
            } else if (_class == MowseDungeonClassEnum.ROGUE) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 5;
                }
                if (_playerStats[STAT_DEXTERITY] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_DEXTERITY]) / 3;
                }
                if (_playerStats[STAT_LUCK] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_LUCK]);
                }
            } else if (_class == MowseDungeonClassEnum.MAGE) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 5;
                }
                if (_playerStats[STAT_INTELLIGENCE] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_INTELLIGENCE]);
                }
                if (_playerStats[STAT_WISDOM] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_WISDOM]) / 3;
                }
                action = 3;
            } else if (_class == MowseDungeonClassEnum.ARCHER) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 5;
                }
                if (_playerStats[STAT_DEXTERITY] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_DEXTERITY]);
                }
                if (_playerStats[STAT_STRENGTH] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_STRENGTH]) / 3;
                }
                action = 2;
            } else if (_class == MowseDungeonClassEnum.PALADIN) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 2;
                    shield += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 4;
                }
                if (_playerStats[STAT_STRENGTH] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_STRENGTH]) / 3;
                }
                if (_playerStats[STAT_INTELLIGENCE] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_INTELLIGENCE]) / 3;
                }
            } else if (_class == MowseDungeonClassEnum.GUARDIAN) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]);
                    shield += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 2;
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 5;
                }
                if (_playerStats[STAT_STRENGTH] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_STRENGTH]) / 3;
                }
            } else if (_class == MowseDungeonClassEnum.BERSERKER) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 3;
                }
                if (_playerStats[STAT_STRENGTH] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_STRENGTH]);
                }
                action = 0;
            } else if (_class == MowseDungeonClassEnum.CLERIC) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 5;
                }
                if (_playerStats[STAT_WISDOM] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_WISDOM]);
                }
                if (_playerStats[STAT_INTELLIGENCE] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_INTELLIGENCE]) / 3;
                }
                action = 2;
            } else if (_class == MowseDungeonClassEnum.NECROMANCER) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 5;
                }
                if (_playerStats[STAT_WISDOM] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_WISDOM]) / 2;
                }
                if (_playerStats[STAT_INTELLIGENCE] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_INTELLIGENCE]) / 2;
                }
                action = 2;
            } else if (_class == MowseDungeonClassEnum.BARD) {
                if (_playerStats[STAT_CONSTITUTION] > 0) {
                    maxHealth += LibDungeon._int32ToUint256(_playerStats[STAT_CONSTITUTION]) / 5;
                }
                if (_playerStats[STAT_CHARISMA] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_CHARISMA]);
                }
                if (_playerStats[STAT_DEXTERITY] > 0) {
                    attack += LibDungeon._int32ToUint256(_playerStats[STAT_DEXTERITY]) / 3;
                }
                action = 3;
            }
    
            return (maxHealth, shield, attack, action);
        }
    
        function _getDungeonRewards(uint256 tokenId, bool hasWon) internal {
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
            MowseAvatar mowseavatar = MowseAvatar(gs.mowseAvatarContractAddress);
    
            MowseDungeonPlayer storage player = gs.mowseDungeonPlayer[tokenId];
    
            // Get rewards based off how far in the dungeon the player got
            MowseDungeon storage dungeon = gs.mowsedungeons[tokenId][player.dungeonRunCount];
            if (dungeon.rooms.length == 0) {
                return;
            }
            uint256 mintAmount;
            uint256 xpGained = 5;
            if (dungeon.currentRoomIndex == 0) {
                // If 0 progress, don't mint anything
                delete mintAmount;
                delete xpGained;
            } else {
                if (dungeon.dungeonType == MowseDungeonDungeonTypeEnum.FOREST) {
                    mintAmount = 5 * (dungeon.descentLevel + 1) * 1e18;
                } else if (dungeon.dungeonType == MowseDungeonDungeonTypeEnum.MEDIEVAL) {
                    mintAmount = 10 * (dungeon.descentLevel + 1) * 1e18;
                }
                // Calculate the percentage of the dungeon completed.
                uint256 completionPercentage = (dungeon.currentRoomIndex * 100) / (dungeon.rooms.length - 1);
    
                // Scale the reward after the 5th & 9th room
                uint256 additionalFactor = 0;
    
                if (completionPercentage > 75) {
                    additionalFactor = (completionPercentage - 75) * 20;
                } else if (completionPercentage > 50) {
                    additionalFactor = (completionPercentage - 50) * 10;
                } else if (completionPercentage > 25) {
                    additionalFactor = (completionPercentage - 25) * 5;
                } else if (completionPercentage > 5) {
                    additionalFactor = (completionPercentage - 5) * 1;
                }
                mintAmount += (mintAmount * additionalFactor / 100);
    
                // grant mowsexp based on completion percentage
                xpGained = (xpGained * additionalFactor / 100); 
                if (hasWon) {
                    // Victory
                    if (mintAmount > 0) {
                        mowsegold.mint(mowseavatar.ownerOf(tokenId), mintAmount * 2);
                        MowseJobFacet(gs.diamondAddress).grantLifeExperience(tokenId, xpGained * 2);
                        // grant companion xp based on completion percentage and descentLevel
                        player.companions[player.currentCompanion.companionId].experience += xpGained * (dungeon.descentLevel * completionPercentage * 20) / 100;
                        gs.mowseDungeonDungeonRewards[tokenId][player.dungeonRunCount] = MowseDungeonDungeonRewards({
                            hasRewards: true,
                            mgoldMintAmount: mintAmount * 2 / 1e18,
                            xpGainedAmount: xpGained * 2,
                            companionXpGained: player.currentCompanion.companionId != MowseDungeonCompanionEnum.NONE ? xpGained * (dungeon.descentLevel * completionPercentage * 20) / 100 : 0,
                            completionPercentage: completionPercentage,
                            additionalFactor: additionalFactor
                        });
                    } else {
                        gs.mowseDungeonDungeonRewards[tokenId][player.dungeonRunCount].hasRewards = true;
                    }
                } else {
                    // Defeat
                    if (mintAmount > 0) {
                        mintAmount = (mintAmount * dungeon.currentRoomIndex) / dungeon.rooms.length;
                        xpGained = (xpGained * dungeon.currentRoomIndex) / dungeon.rooms.length;
                        mowsegold.mint(mowseavatar.ownerOf(tokenId), mintAmount);
                        MowseJobFacet(gs.diamondAddress).grantLifeExperience(tokenId, xpGained);
                        // grant companion xp based on completion percentage and descentLevel
                        player.companions[player.currentCompanion.companionId].experience += xpGained * (dungeon.descentLevel * completionPercentage * 10) / 100;
                        gs.mowseDungeonDungeonRewards[tokenId][player.dungeonRunCount] = MowseDungeonDungeonRewards({
                            hasRewards: true,
                            mgoldMintAmount: mintAmount / 1e18,
                            xpGainedAmount: xpGained,
                            companionXpGained: player.currentCompanion.companionId != MowseDungeonCompanionEnum.NONE ? xpGained * (dungeon.descentLevel * completionPercentage * 10) / 100 : 0,
                            completionPercentage: completionPercentage,
                            additionalFactor: additionalFactor
                        });
                    } else {
                        gs.mowseDungeonDungeonRewards[tokenId][player.dungeonRunCount].hasRewards = true;
                    }
                }
            }
    
            // Mark dungeon as complete (inactive)
            delete dungeon.active;
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    import {GameStorage, MowseDungeonClassEnum, MowseDungeonActionTypeEnum, MowseDungeonSimpleMonster, MowseDungeonBattleSimulation, STAT_DEXTERITY, STAT_STRENGTH, STAT_INTELLIGENCE, STAT_CONSTITUTION, STAT_CHARISMA, STAT_LUCK, STAT_WISDOM, MowseDungeonBattleLog, MowseDungeonBattleLogSourceEnum, MowseDungeonBattleLogTypeEnum} from "../../libraries/LibStorage.sol";
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibDungeon.sol";
    
    contract MowseDungeonPlayerClassAttackFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        function playerClassAttack(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster
        ) external pure returns (MowseDungeonBattleSimulation memory, MowseDungeonSimpleMonster memory) {
            MowseDungeonSimpleMonster memory updatedMonster;
            // Calculate damage of player attack to monster
            _battleSimulation.player.tempAttack = (_battleSimulation.player.tempAttack * _classBasicAttackRatio(_battleSimulation.player.class)) / 100;
            // Slingshot only for max health damage
            if (_battleSimulation.player.trinkets.slingshot > 0) {
                if (_monster.currentHealth == _monster.maxHealth) {
                    _battleSimulation.player.damageMultiplier = 110 + 10 * _battleSimulation.player.trinkets.slingshot;
                }
            }
            if (_monster.statusEffects.bulk > 0) {
                _battleSimulation.player.damageMultiplier -= 30;
            }
            if (_monster.statusEffects.frail > 0) {
                _battleSimulation.player.damageMultiplier += 30;
            }
            if (_monster.statusEffects.sleep > 0) {
                _battleSimulation.player.damageMultiplier += 50;
                _monster.statusEffects.sleep = 0;
            }
            _battleSimulation.player.tempAttack = (_battleSimulation.player.tempAttack * _battleSimulation.player.damageMultiplier) / 100;
            _battleSimulation.player.numberOfHits = 1;
            if (_battleSimulation.player.class == MowseDungeonClassEnum.WARRIOR && _battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                // Warrior
                if (_battleSimulation.monster1.currentHealth > 0) {
                  (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _battleSimulation.monster1, 0);
                  _battleSimulation.monster1 = updatedMonster;
                  if (_battleSimulation.targetIndex == 0) {
                    _monster = updatedMonster;
                  }
                }
                if (_battleSimulation.monster2.currentHealth > 0) {
                  (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _battleSimulation.monster2, 1);
                  _battleSimulation.monster2 = updatedMonster;
                  if (_battleSimulation.targetIndex == 1) {
                    _monster = updatedMonster;
                  }
                }
                if (_battleSimulation.monster3.currentHealth > 0) {
                  (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _battleSimulation.monster3, 2);
                  _battleSimulation.monster3 = updatedMonster;
                  if (_battleSimulation.targetIndex == 2) {
                    _monster = updatedMonster;
                  }
                }
            } else if (
                // Mage
                _battleSimulation.player.class == MowseDungeonClassEnum.MAGE &&
                _battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY
            ) {
                // Deal damage based off base attack
                _battleSimulation.player.tempAttack = _battleSimulation.player.attack * 2;
                (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
    
                // Apply burn stack
                if (updatedMonster.statusEffects.fireResist == 0) {
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BURN, 1);    
                    updatedMonster.statusEffects.burn++;
                }
                _monster = updatedMonster;
            } else if (
                // Archer
                _battleSimulation.player.class == MowseDungeonClassEnum.ARCHER &&
                _battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY
            ) {
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_EAGLE_EYE, 2);
                _battleSimulation.player.statusEffects.eagleEye += 2;
                // First do a basic attack            
                (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
                // Then do while loop for every 20 dex, 50% chance to hit again
                if (_battleSimulation.player.stats[STAT_DEXTERITY] > 0) {
                  uint256 numberOfTries = LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_DEXTERITY]) / 20 + 1;
                  while(numberOfTries > 0) {
                    _battleSimulation.seed = LibDungeon.updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
                    if (_battleSimulation.seed % 2 == 0) {
                      console.log('Hit again!');
                      (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, updatedMonster, _battleSimulation.targetIndex);
                      _battleSimulation.player.numberOfHits++;
                    }
                    numberOfTries--;
                  }
                } else {
                  // Else only one chance of an additional hit
                  _battleSimulation.seed = LibDungeon.updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
                  if (_battleSimulation.seed % 2 == 0) {
                    console.log('Hit Again!');
                    (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
                    _battleSimulation.player.numberOfHits++;
                  }
                }
                _monster = updatedMonster;
            } else if (_battleSimulation.player.class == MowseDungeonClassEnum.ROGUE) {
                // Rogue
                if (_battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                    _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_DODGE, 2);
                    _battleSimulation.player.statusEffects.dodge += 2;
                }
                _battleSimulation.player.numberOfHits = 2;
    
                for (uint256 i = 0; i < 2; i++) {
                    (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
    
                    // Apply POISON stack
                    if (updatedMonster.statusEffects.poisonResist == 0) {
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, 1);    
                        updatedMonster.statusEffects.poison++;
                    }
                }
                _monster = updatedMonster;
            } else if (_battleSimulation.player.class == MowseDungeonClassEnum.PALADIN && _battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                // Paladin
                (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
                // Try to heal
                if (_battleSimulation.player.statusEffects.healBlock == 0) {
                  uint256 maxHealthToRestore = 30;
                  if (_battleSimulation.player.stats[STAT_STRENGTH] > 0) {
                      maxHealthToRestore += LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_STRENGTH]) / 10 + 1;
                  }
                  if (_battleSimulation.player.stats[STAT_INTELLIGENCE] > 0) {
                      maxHealthToRestore += LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_INTELLIGENCE]) / 10 + 1;
                  }
                  _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.PLAYER, maxHealthToRestore * _battleSimulation.player.maxHealth / 100);
                  _battleSimulation.player.currentHealth += maxHealthToRestore * _battleSimulation.player.maxHealth / 100;
                  // Make sure health doesn't exceed maxHealth
                  if (_battleSimulation.player.currentHealth > _battleSimulation.player.maxHealth) {
                      _battleSimulation.player.currentHealth = _battleSimulation.player.maxHealth;
                  }
                  _battleSimulation.achievementProgress.healthHealed += maxHealthToRestore * _battleSimulation.player.maxHealth / 100;
                } else {
                    _battleSimulation.player.statusEffects.healBlock--;
                }
                _monster = updatedMonster;
            } else if (_battleSimulation.player.class == MowseDungeonClassEnum.GUARDIAN) {
              if (_battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                // Guardian Shield Bash
                // Double shield, then deal damage based on shield value
                _battleSimulation.player.shield *= 2;
                _battleSimulation.player.tempAttack += _battleSimulation.player.shield;
              } else {
                // Guardian basic attack
                // Gain small shield on attack
                uint256 shieldToGain = _battleSimulation.player.attack / 10;
                if (_battleSimulation.player.stats[STAT_STRENGTH] > 0) {
                    shieldToGain += LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_STRENGTH]) / 10 + 1;
                }
                if (_battleSimulation.player.stats[STAT_CONSTITUTION] > 0) {
                    shieldToGain += LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_CONSTITUTION]) / 5 + 1;
                }
              }
              (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
              _monster = updatedMonster;
            } else if (_battleSimulation.player.class == MowseDungeonClassEnum.BERSERKER) {
              if (_battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                // Berserker RAGE
                _battleSimulation.player.statusEffects.strengthen += 2;
                _battleSimulation.player.statusEffects.frail++;
                _battleSimulation.player.statusEffects.lastHope++;
                _battleSimulation.player.statusEffects.silence += 2;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STRENGTHEN, 2);
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_FRAIL, 1);
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_LAST_HOPE, 1);
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_SILENCE, 2);
              }
              (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
              _monster = updatedMonster;
            } else if (_battleSimulation.player.class == MowseDungeonClassEnum.CLERIC && _battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                // Cleric Heal
                // Heal based on intelligence & wisdom
                uint256 healthToRestore = _battleSimulation.player.attack;
                if (_battleSimulation.player.stats[STAT_INTELLIGENCE] > 0) {
                    healthToRestore += LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_INTELLIGENCE]) / 10 + 1;
                }
                if (_battleSimulation.player.stats[STAT_WISDOM] > 0) {
                    healthToRestore += LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_WISDOM]) / 5 + 1;
                }
                _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.PLAYER, healthToRestore);
                _battleSimulation.player.currentHealth += healthToRestore;
                // Make sure health doesn't exceed maxHealth
                if (_battleSimulation.player.currentHealth > _battleSimulation.player.maxHealth) {
                    _battleSimulation.player.currentHealth = _battleSimulation.player.maxHealth;
                }
                _battleSimulation.achievementProgress.healthHealed += healthToRestore;
            } else if (_battleSimulation.player.class == MowseDungeonClassEnum.NECROMANCER) {
              if (_battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                // Necromancer Life tap
                // Deal damage to monster, if monster dies, then permanently gain attack
                (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
                if (updatedMonster.currentHealth == 0) {
                  _battleSimulation.player.attack += _battleSimulation.player.attack / 10;
                }
              } else {
                (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
                // Life Steal on basic attack
                uint256 healthToRestore = _battleSimulation.player.attack / 10;
                if (_battleSimulation.player.stats[STAT_INTELLIGENCE] > 0) {
                    healthToRestore += LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_INTELLIGENCE]) / 10 + 1;
                }
                if (_battleSimulation.player.stats[STAT_WISDOM] > 0) {
                    healthToRestore += LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_WISDOM]) / 10 + 1;
                }
                _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.PLAYER, healthToRestore);
                _battleSimulation.player.currentHealth += healthToRestore;
                // Make sure health doesn't exceed maxHealth
                if (_battleSimulation.player.currentHealth > _battleSimulation.player.maxHealth) {
                    _battleSimulation.player.currentHealth = _battleSimulation.player.maxHealth;
                }
                _battleSimulation.achievementProgress.healthHealed += healthToRestore;
                _monster = updatedMonster;
              }
            } else if (_battleSimulation.player.class == MowseDungeonClassEnum.BARD && _battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                // Bard CHARM all monsters
                _battleSimulation.player.tempAttack = _battleSimulation.player.attack;
                if (_battleSimulation.player.stats[STAT_CHARISMA] > 0) {
                    _battleSimulation.player.tempAttack += LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_CHARISMA]) / 5 + 1;
                }
                // Deal damage to all monsters
                if (_battleSimulation.monster1.currentHealth > 0) {
                  (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _battleSimulation.monster1, 0);
                  _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARM, 2);    
                  updatedMonster.statusEffects.charm += 2;
                  _battleSimulation.monster1 = updatedMonster;
                  if (_battleSimulation.targetIndex == 0) {
                    _monster = updatedMonster;
                  }
                }
                if (_battleSimulation.monster2.currentHealth > 0) {
                  (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _battleSimulation.monster2, 1);
                  _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 1, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARM, 2);    
                  updatedMonster.statusEffects.charm += 2;
                  _battleSimulation.monster2 = updatedMonster;
                  if (_battleSimulation.targetIndex == 1) {
                    _monster = updatedMonster;
                  }
                }
                if (_battleSimulation.monster3.currentHealth > 0) {
                  (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _battleSimulation.monster3, 2);
                  _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 2, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARM, 2);    
                  updatedMonster.statusEffects.charm += 2;
                  _battleSimulation.monster3 = updatedMonster;
                  if (_battleSimulation.targetIndex == 2) {
                    _monster = updatedMonster;
                  }
                }
            } else {
                // Default
                (_battleSimulation, updatedMonster) = LibDungeon._dealPlayerDamageToMonster(_battleSimulation, _monster, _battleSimulation.targetIndex);
                _monster = updatedMonster;
            }
    
            return (_battleSimulation, _monster);
        }
    
        function _classBasicAttackRatio(MowseDungeonClassEnum _class) internal pure returns (uint256) {
            if (_class == MowseDungeonClassEnum.WARRIOR) {
                return 100;
            } else if (_class == MowseDungeonClassEnum.MAGE) {
                return 60;
            } else if (_class == MowseDungeonClassEnum.ARCHER) {
                return 80;
            } else if (_class == MowseDungeonClassEnum.ROGUE) {
                return 50;
            } else if (_class == MowseDungeonClassEnum.PALADIN) {
                return 70;
            } else if (_class == MowseDungeonClassEnum.GUARDIAN) {
                return 50;
            } else if (_class == MowseDungeonClassEnum.BERSERKER) {
                return 120;
            } else if (_class == MowseDungeonClassEnum.CLERIC) {
                return 40;
            } else if (_class == MowseDungeonClassEnum.NECROMANCER) {
                return 55;
            } else if (_class == MowseDungeonClassEnum.BARD) {
                return 60;
            } else {
                return 50;
            }
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    import {GameStorage, MowseDungeonMonsterEnum, MowseDungeonMonsterAttributes, MowseDungeonSpecialAbilityEnum, MowseDungeonStatusEffectEnum, MowseDungeonClassEnum, MowseDungeonCompanionEnum, MowseDungeonActionTypeEnum, MowseDungeonPlayer, MowseDungeonMonster, MowseDungeonSimplePlayer, MowseDungeonSimpleMonster, MowseDungeonPerformAction, MowseDungeonBattleStatusEnum, MowseDungeonTrinketEnum, MowseDungeon, MowseDungeonRoom, MowseDungeonSimpleStatusEffect, MowseDungeonSimpleTrinkets, MowseDungeonBattleSimulation, MowseDungeonBattleResults, MowseDungeonSimplePlayerAchievementsProgress, MowseDungeonPostBattleResults, MowseDungeonBattleLog} from "../../libraries/LibStorage.sol";
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibDungeon.sol";
    
    import {MowseDungeonSimulatePerformActionFacet} from "./MowseDungeonSimulatePerformActionFacet.sol";
    import {MowseDungeonSimulateMonsterAttackFacet} from "./MowseDungeonSimulateMonsterAttackFacet.sol";
    import {MowseDungeonTrinketFacet} from "./MowseDungeonTrinketFacet.sol";
    
    contract MowseDungeonSimulateBattleFacet {
        using Strings for uint256;
        using Strings for uint8;
        GameStorage internal gs;
    
        error InvalidAction();
        error InvalidBattleSimulation();
    
        function getPlayerStats(uint256 tokenId) external view returns (MowseDungeonSimplePlayer memory) {
            MowseDungeonSimplePlayer memory player = _toSimplePlayer(gs.mowseDungeonPlayer[tokenId]);
            player = _applyTrinketStats(player);
            return player;
        }
    
        function simulateBattle(
            uint256 mowseTokenId,
            uint256 dungeonRunCount,
            uint8[] calldata actions
        ) external view returns (MowseDungeonBattleResults memory battleResults) {
            if (dungeonRunCount > gs.mowseDungeonPlayer[mowseTokenId].dungeonRunCount) revert InvalidBattleSimulation();
    
            MowseDungeonBattleSimulation memory battleSimulation = _toBattleSimulation(gs.mowsedungeons[mowseTokenId][dungeonRunCount], mowseTokenId);
    
            battleSimulation.player = _applyTrinketStats(battleSimulation.player);
            for (uint256 i = 0; i < actions.length; i++) {
                // Reset battle log to only show the latest action log
                battleSimulation.battleLog = new MowseDungeonBattleLog[](100);
                delete battleSimulation.battleLogCount;
    
                battleSimulation.actionsRun++;
                // Unpack action
                (battleSimulation.actionType, battleSimulation.targetIndex) = _unpackAction(actions[uint8(i)]);
                if (battleSimulation.player.currentHealth == 0) {
                    return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.DEFEAT, actions);
                }
                if (
                    battleSimulation.monster1.currentHealth == 0 &&
                    battleSimulation.monster2.currentHealth == 0 &&
                    battleSimulation.monster3.currentHealth == 0
                ) {
                    return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.VICTORY, actions);
                }
                // Check if the monster can be directly targeted (ROYAL_DECREE)
                if (battleSimulation.monster1.statusEffects.royalDecree > 0) {
                    if (battleSimulation.monster2.currentHealth > 0 || battleSimulation.monster3.currentHealth > 0) {
                        if (battleSimulation.targetIndex == 0) {
                            revert InvalidAction();
                        }
                    }
                }
                // reset accuracy/tempAttack/canHit for player and monsters
                battleSimulation = _resetAccuracy(battleSimulation);
                // First perform player turn (pp = post-player)
                battleSimulation = MowseDungeonSimulatePerformActionFacet(gs.diamondAddress)._performAction(battleSimulation);
                if (battleSimulation.player.currentHealth == 0) {
                    return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.DEFEAT, actions);
                }
                if (
                    battleSimulation.monster1.currentHealth == 0 &&
                    battleSimulation.monster2.currentHealth == 0 &&
                    battleSimulation.monster3.currentHealth == 0
                ) {
                    return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.VICTORY, actions);
                }
                // Then perform monster(s) turn (pm = post-monster)
                if (battleSimulation.monster1.currentHealth > 0) {
                    battleSimulation = MowseDungeonSimulateMonsterAttackFacet(gs.diamondAddress)._performMonsterAttack(battleSimulation, 0);
                    if (battleSimulation.player.currentHealth == 0) {
                        return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.DEFEAT, actions);
                    }
                    if (
                        battleSimulation.monster1.currentHealth == 0 &&
                        battleSimulation.monster2.currentHealth == 0 &&
                        battleSimulation.monster3.currentHealth == 0
                    ) {
                        return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.VICTORY, actions);
                    }
                }
                if (battleSimulation.monster2.currentHealth > 0) {
                    battleSimulation = MowseDungeonSimulateMonsterAttackFacet(gs.diamondAddress)._performMonsterAttack(battleSimulation, 1);
                    if (battleSimulation.player.currentHealth == 0) {
                        return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.DEFEAT, actions);
                    }
                    if (
                        battleSimulation.monster2.currentHealth == 0 &&
                        battleSimulation.monster1.currentHealth == 0 &&
                        battleSimulation.monster3.currentHealth == 0
                    ) {
                        return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.VICTORY, actions);
                    }
                }
                if (battleSimulation.monster3.currentHealth > 0) {
                    battleSimulation = MowseDungeonSimulateMonsterAttackFacet(gs.diamondAddress)._performMonsterAttack(battleSimulation, 2);
                    if (battleSimulation.player.currentHealth == 0) {
                        return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.DEFEAT, actions);
                    }
                    if (
                        battleSimulation.monster3.currentHealth == 0 &&
                        battleSimulation.monster1.currentHealth == 0 &&
                        battleSimulation.monster2.currentHealth == 0
                    ) {
                        return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.VICTORY, actions);
                    }
                }
                // Reset temporary buffs (defend)
                battleSimulation.player = _resetTemporaryBuffs(battleSimulation.player);
                battleSimulation.monster1 = _endOfTurnStatusEffects(battleSimulation.monster1);
                battleSimulation.monster2 = _endOfTurnStatusEffects(battleSimulation.monster2);
                battleSimulation.monster3 = _endOfTurnStatusEffects(battleSimulation.monster3);
            }
            // If we reach this point, the battle is still ongoing
            return _generateBattleResults(battleSimulation, MowseDungeonBattleStatusEnum.ONGOING, actions);
        }
    
        function _applyTrinketStats(MowseDungeonSimplePlayer memory _player) internal view returns (MowseDungeonSimplePlayer memory) {
            // Apply trinket stats to player
            for (uint256 i = 0; i < gs.mowseDungeonTrinketDefaultsCount; i++) {
                if (gs.mowseDungeonPlayer[_player.mowseId].trinkets[MowseDungeonTrinketEnum(i)].level > 0) {
                    _player.maxHealth =
                        _player.maxHealth +
                        gs.mowseDungeonTrinketDefaults[MowseDungeonTrinketEnum(i)].health *
                        gs.mowseDungeonPlayer[_player.mowseId].trinkets[MowseDungeonTrinketEnum(i)].level;
                    _player.currentHealth = _player.maxHealth;
                    _player.shield =
                        _player.shield +
                        gs.mowseDungeonTrinketDefaults[MowseDungeonTrinketEnum(i)].shield *
                        gs.mowseDungeonPlayer[_player.mowseId].trinkets[MowseDungeonTrinketEnum(i)].level;
                    _player.attack =
                        _player.attack +
                        gs.mowseDungeonTrinketDefaults[MowseDungeonTrinketEnum(i)].attack *
                        gs.mowseDungeonPlayer[_player.mowseId].trinkets[MowseDungeonTrinketEnum(i)].level;
                }
            }
            return _player;
        }
    
        function _resetAccuracy(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            // Reset Player Accuracy
            delete _battleSimulation.player.canHit;
            _battleSimulation.player.accuracy = 100;
            _battleSimulation.player.tempAttack = _battleSimulation.player.attack;
            _battleSimulation.player.damageMultiplier = 100;
            if (_battleSimulation.player.statusEffects.invincible > 0) {
                _battleSimulation.player.statusEffects.invincible--;
            }
            // Reset Monster Accuracy
            _battleSimulation.monster1 = _resetAccuracyForMonster(_battleSimulation.monster1);
            _battleSimulation.monster2 = _resetAccuracyForMonster(_battleSimulation.monster2);
            _battleSimulation.monster3 = _resetAccuracyForMonster(_battleSimulation.monster3);
    
            return _battleSimulation;
        }
    
        function _resetAccuracyForMonster(MowseDungeonSimpleMonster memory _monster) internal pure returns (MowseDungeonSimpleMonster memory) {
            delete _monster.canHit;
            _monster.accuracy = 100;
            _monster.tempAttack = _monster.attack;
            _monster.damageMultiplier = 100;
            if (_monster.statusEffects.invincible > 0) {
                _monster.statusEffects.invincible--;
            }
            return _monster;
        }
    
        function _resetTemporaryBuffs(MowseDungeonSimplePlayer memory _player) internal pure returns (MowseDungeonSimplePlayer memory) {
            // Tick down strengthen, weaken
            if (_player.statusEffects.strengthen > 0) {
                _player.statusEffects.strengthen--;
            }
            if (_player.statusEffects.weaken > 0) {
                _player.statusEffects.weaken--;
            }
            if (_player.statusEffects.bulk > 0) {
                _player.statusEffects.bulk--;
            }
            if (_player.statusEffects.frail > 0) {
                _player.statusEffects.frail--;
            }
            // Tick down counter, thorns
            if (_player.statusEffects.counter > 0) {
                _player.statusEffects.counter--;
            }
            if (_player.statusEffects.thorns > 0) {
                _player.statusEffects.thorns--;
            }
            return _player;
        }
    
        function _endOfTurnStatusEffects(MowseDungeonSimpleMonster memory _monster) internal pure returns (MowseDungeonSimpleMonster memory) {
            if (_monster.statusEffects.rampage > 0) {
                _monster.attack = (_monster.attack * 110) / 100;
            }
            return _monster;
        }
    
        function _toSimplePlayer(MowseDungeonPlayer storage _player) internal view returns (MowseDungeonSimplePlayer memory) {
            MowseDungeon memory dungeon = gs.mowsedungeons[_player.mowseId][_player.dungeonRunCount];
            return
                MowseDungeonSimplePlayer({
                    mowseId: _player.mowseId,
                    stats: _player.stats,
                    maxHealth: _player.maxHealth,
                    currentHealth: _player.maxHealth,
                    shield: _player.shield,
                    attack: _player.attack,
                    canHit: false,
                    accuracy: 100,
                    tempAttack: _player.attack,
                    damageMultiplier: 100,
                    action: _player.action + gs.mowseDungeonPlayer[_player.mowseId].trinkets[MowseDungeonTrinketEnum.EMBLEM_OF_INITIATIVE].level,
                    numberOfHits: 1,
                    class: _player.class,
                    statusEffects: _toSimplePlayerStatusEffect(_player),
                    trinkets: MowseDungeonTrinketFacet(gs.diamondAddress).toSimplePlayerTrinkets(_player.mowseId),
                    companion: _player.currentCompanion,
                    coins: _player.coins,
                    currentDungeonRunCount: _player.dungeonRunCount,
                    currentRoomIndex: dungeon.currentRoomIndex,
                    descentLevel: _player.descentLevel
                });
        }
    
        function _monsterEnumToSimpleMonster(MowseDungeonMonsterEnum _monsterType) public view returns (MowseDungeonSimpleMonster memory) {
            MowseDungeonMonsterAttributes memory _monster = gs.mowseDungeonMonsterDefaults[_monsterType];
            return
                MowseDungeonSimpleMonster({
                    monsterType: _monster.monsterType,
                    monsterId: _monster.monsterId,
                    maxHealth: _monster.maxHealth,
                    currentHealth: _monster.maxHealth,
                    shield: _monster.shield,
                    attack: _monster.attack,
                    canHit: false,
                    accuracy: 100,
                    tempAttack: _monster.attack,
                    damageMultiplier: 100,
                    specialAbility: _monster.specialAbility,
                    isBoss: _monster.isBoss,
                    hasBeenStolen: false,
                    usedSpecialAbility: false,
                    statusEffects: _monster.statusEffects,
                    image: _monster.image
                });
        }
    
        function _toSimplePlayerStatusEffect(MowseDungeonPlayer storage _player) internal view returns (MowseDungeonSimpleStatusEffect memory) {
            return
                MowseDungeonSimpleStatusEffect({
                    blind: _player.statusEffects[MowseDungeonStatusEffectEnum.BLIND],
                    eagleEye: _player.statusEffects[MowseDungeonStatusEffectEnum.EAGLE_EYE],
                    poison: _player.statusEffects[MowseDungeonStatusEffectEnum.POISON],
                    burn: _player.statusEffects[MowseDungeonStatusEffectEnum.BURN],
                    bleed: _player.statusEffects[MowseDungeonStatusEffectEnum.BLEED],
                    curse: _player.statusEffects[MowseDungeonStatusEffectEnum.CURSE],
                    stun: _player.statusEffects[MowseDungeonStatusEffectEnum.STUN],
                    silence: _player.statusEffects[MowseDungeonStatusEffectEnum.SILENCE],
                    freeze: _player.statusEffects[MowseDungeonStatusEffectEnum.FREEZE],
                    strengthen: _player.statusEffects[MowseDungeonStatusEffectEnum.STRENGTHEN],
                    weaken: _player.statusEffects[MowseDungeonStatusEffectEnum.WEAKEN],
                    bulk: _player.statusEffects[MowseDungeonStatusEffectEnum.BULK],
                    frail: _player.statusEffects[MowseDungeonStatusEffectEnum.FRAIL],
                    thorns: _player.statusEffects[MowseDungeonStatusEffectEnum.THORNS],
                    regen: _player.statusEffects[MowseDungeonStatusEffectEnum.REGEN],
                    sleep: _player.statusEffects[MowseDungeonStatusEffectEnum.SLEEP],
                    death: _player.statusEffects[MowseDungeonStatusEffectEnum.DEATH],
                    invincible: _player.statusEffects[MowseDungeonStatusEffectEnum.INVINCIBLE],
                    lastHope: _player.statusEffects[MowseDungeonStatusEffectEnum.LAST_HOPE],
                    dodge: _player.statusEffects[MowseDungeonStatusEffectEnum.DODGE],
                    healBlock: _player.statusEffects[MowseDungeonStatusEffectEnum.HEAL_BLOCK],
                    charm: _player.statusEffects[MowseDungeonStatusEffectEnum.CHARM],
                    counter: _player.statusEffects[MowseDungeonStatusEffectEnum.COUNTER],
                    pierce: _player.statusEffects[MowseDungeonStatusEffectEnum.PIERCE],
                    cleave: _player.statusEffects[MowseDungeonStatusEffectEnum.CLEAVE],
                    revive: _player.statusEffects[MowseDungeonStatusEffectEnum.REVIVE],
                    unstoppable: _player.statusEffects[MowseDungeonStatusEffectEnum.UNSTOPPABLE],
                    petrify: _player.statusEffects[MowseDungeonStatusEffectEnum.PETRIFY],
                    fireResist: _player.statusEffects[MowseDungeonStatusEffectEnum.FIRE_RESIST],
                    poisonResist: _player.statusEffects[MowseDungeonStatusEffectEnum.POISON_RESIST],
                    freezeResist: _player.statusEffects[MowseDungeonStatusEffectEnum.FREEZE_RESIST],
                    stunResist: _player.statusEffects[MowseDungeonStatusEffectEnum.STUN_RESIST],
                    sleepResist: _player.statusEffects[MowseDungeonStatusEffectEnum.SLEEP_RESIST],
                    charged: _player.statusEffects[MowseDungeonStatusEffectEnum.CHARGED],
                    doubleUp: _player.statusEffects[MowseDungeonStatusEffectEnum.DOUBLE_UP],
                    rampage: _player.statusEffects[MowseDungeonStatusEffectEnum.RAMPAGE],
                    loneSurvivor: _player.statusEffects[MowseDungeonStatusEffectEnum.LONE_SURVIVOR],
                    royalDecree: _player.statusEffects[MowseDungeonStatusEffectEnum.ROYAL_DECREE],
                    wrathfulReprisal: _player.statusEffects[MowseDungeonStatusEffectEnum.WRATHFUL_REPRISAL]
                });
        }
    
        function _toBattleSimulation(MowseDungeon memory _dungeon, uint256 _mowseTokenId) internal view returns (MowseDungeonBattleSimulation memory) {
            MowseDungeonTrinketEnum[] memory _stolenTrinkets = new MowseDungeonTrinketEnum[](3);
            MowseDungeonSimpleMonster memory _monster1;
            MowseDungeonSimpleMonster memory _monster2;
            MowseDungeonSimpleMonster memory _monster3;
            // MowseDungeonSimplePlayerAchievementsProgress memory _progress;
            MowseDungeonBattleLog[] memory _battleLog = new MowseDungeonBattleLog[](100);
            if (_dungeon.rooms[_dungeon.currentRoomIndex].monsters.length == 1) {
                _monster1 = _monsterEnumToSimpleMonster(_dungeon.rooms[_dungeon.currentRoomIndex].monsters[0]);
            } else if (_dungeon.rooms[_dungeon.currentRoomIndex].monsters.length == 2) {
                _monster1 = _monsterEnumToSimpleMonster(_dungeon.rooms[_dungeon.currentRoomIndex].monsters[0]);
                _monster2 = _monsterEnumToSimpleMonster(_dungeon.rooms[_dungeon.currentRoomIndex].monsters[1]);
            } else if (_dungeon.rooms[_dungeon.currentRoomIndex].monsters.length == 3) {
                _monster1 = _monsterEnumToSimpleMonster(_dungeon.rooms[_dungeon.currentRoomIndex].monsters[0]);
                _monster2 = _monsterEnumToSimpleMonster(_dungeon.rooms[_dungeon.currentRoomIndex].monsters[1]);
                _monster3 = _monsterEnumToSimpleMonster(_dungeon.rooms[_dungeon.currentRoomIndex].monsters[2]);
            }
            // Update monster stats based on descent level
            _monster1 = _updateMonsterByDescentLevel(_monster1, _dungeon.descentLevel);
            _monster2 = _updateMonsterByDescentLevel(_monster2, _dungeon.descentLevel);
            _monster3 = _updateMonsterByDescentLevel(_monster3, _dungeon.descentLevel);
    
            return
                MowseDungeonBattleSimulation({
                    player: _toSimplePlayer(gs.mowseDungeonPlayer[_mowseTokenId]),
                    monster1: _monster1,
                    monster2: _monster2,
                    monster3: _monster3,
                    dungeonId: _dungeon.id,
                    seed: LibDungeon.getNewSeed(_dungeon.id, _dungeon.currentRoomIndex),
                    actionType: MowseDungeonActionTypeEnum.INVALID_ACTION,
                    targetIndex: 0,
                    actionsRun: 0,
                    stolenTrinkets: _stolenTrinkets,
                    stolenTrinketCount: 0,
                    isStealSuccessful: false,
                    status: MowseDungeonBattleStatusEnum.ONGOING,
                    achievementProgress: gs.mowseDungeonPlayer[_mowseTokenId].achievementsProgress,
                    descentLevel: _dungeon.descentLevel,
                    battleLog: _battleLog,
                    battleLogCount: 0
                });
        }
    
        function _updateMonsterByDescentLevel(
            MowseDungeonSimpleMonster memory _monster,
            uint256 _descentLevel
        ) public pure returns (MowseDungeonSimpleMonster memory) {
            if (_descentLevel == 0) {
                return _monster;
            } else if (_descentLevel == 1) {
                // Increase monster health & shield by 25%
                _monster.maxHealth = (_monster.maxHealth * 125) / 100;
                _monster.currentHealth = _monster.maxHealth;
                _monster.shield = (_monster.shield * 125) / 100;
            } else if (_descentLevel == 2) {
                // Increase monster attack by 25%
                _monster.attack = (_monster.attack * 125) / 100;
            } else if (_descentLevel == 3) {
                // Increase monster attack by 25%, monster coins -25%
                _monster.attack = (_monster.attack * 125) / 100;
            } else if (_descentLevel == 4) {
                // Increase monster attack by 25%, hp shield by 25%
                _monster.attack = (_monster.attack * 125) / 100;
                _monster.shield = (_monster.shield * 125) / 100;
                _monster.maxHealth = (_monster.maxHealth * 125) / 100;
                _monster.currentHealth = _monster.maxHealth;
            } else if (_descentLevel == 5) {
                // Increase monster attack by 25%, hp shield by 25%, items cost 10% more
                _monster.attack = (_monster.attack * 125) / 100;
                _monster.shield = (_monster.shield * 125) / 100;
                _monster.maxHealth = (_monster.maxHealth * 125) / 100;
                _monster.currentHealth = _monster.maxHealth;
            } else if (_descentLevel == 6) {
                // Increase monster attack by 25%, hp shield by 50%
                _monster.attack = (_monster.attack * 125) / 100;
                _monster.shield = (_monster.shield * 150) / 100;
                _monster.maxHealth = (_monster.maxHealth * 150) / 100;
                _monster.currentHealth = _monster.maxHealth;
            } else if (_descentLevel == 7) {
                // Increase monster attack by 25%, hp shield by 50%, monster coins -25%
                _monster.attack = (_monster.attack * 125) / 100;
                _monster.shield = (_monster.shield * 150) / 100;
                _monster.maxHealth = (_monster.maxHealth * 150) / 100;
                _monster.currentHealth = _monster.maxHealth;
            } else if (_descentLevel == 8) {
                // Increase monster attack by 25%, hp shield by 50%, monster coins -25%, items cost 10% more
                _monster.attack = (_monster.attack * 125) / 100;
                _monster.shield = (_monster.shield * 150) / 100;
                _monster.maxHealth = (_monster.maxHealth * 150) / 100;
                _monster.currentHealth = _monster.maxHealth;
            } else if (_descentLevel == 9) {
                // Increase monster hp shield by 100%, all monsters have rampage
                _monster.shield = _monster.shield * 2;
                _monster.maxHealth = _monster.maxHealth * 2;
                _monster.currentHealth = _monster.maxHealth;
                _monster.statusEffects.rampage = 1;
            } else if (_descentLevel == 10) {
                // Increase monster attack by 25%, hp shield by 100%, all monsters have rampage, monster coins -25%, items cost 10% more
                _monster.attack = (_monster.attack * 125) / 100;
                _monster.shield = _monster.shield * 2;
                _monster.maxHealth = _monster.maxHealth * 2;
                _monster.currentHealth = _monster.maxHealth;
                _monster.statusEffects.rampage = 1;
            }
            return _monster;
        }
    
        function _unpackAction(uint8 packedData) internal pure returns (MowseDungeonActionTypeEnum, uint8) {
            uint8 actionType = packedData & 0x03;
            uint8 targetIndex = (packedData >> 2) & 0x03;
            if (actionType >= uint8(MowseDungeonActionTypeEnum.INVALID_ACTION) || targetIndex > 2) revert InvalidAction();
    
            return (MowseDungeonActionTypeEnum(actionType), targetIndex);
        }
        function _generatePostBattleResults(MowseDungeonBattleSimulation memory _simulation) internal pure returns (MowseDungeonPostBattleResults memory) {
            return
                MowseDungeonPostBattleResults({
                    mowseId: _simulation.player.mowseId,
                    coins: _simulation.player.coins,
                    monster1: _simulation.monster1.monsterType,
                    monster2: _simulation.monster2.monsterType,
                    monster3: _simulation.monster3.monsterType,
                    stolenTrinkets: _simulation.stolenTrinkets,
                    stolenTrinketCount: _simulation.stolenTrinketCount,
                    status: _simulation.status,
                    achievementProgress: _simulation.achievementProgress,
                    descentLevel: _simulation.descentLevel
                });
        }
        function _generateBattleResults(MowseDungeonBattleSimulation memory _simulation, MowseDungeonBattleStatusEnum _status, uint8[] calldata _actions) internal pure returns (MowseDungeonBattleResults memory) {
            uint256 tempDungeonId = _simulation.dungeonId;
            delete _simulation.dungeonId;
            delete _simulation.seed;
    
            _simulation.status = _status;
            bytes32 actionsRunHash = keccak256(abi.encodePacked(keccak256(abi.encode(_actions)), keccak256(abi.encode(tempDungeonId))));
            bytes32 postBattleResultsHash = keccak256(abi.encodePacked(keccak256(abi.encode(_generatePostBattleResults(_simulation))), keccak256(abi.encode(tempDungeonId))));
            
            if (_status == MowseDungeonBattleStatusEnum.ONGOING) {
                delete actionsRunHash;
                delete postBattleResultsHash;
            }
    
            return
                MowseDungeonBattleResults({
                    simulation: _simulation,
                    postBattleResults: _generatePostBattleResults(_simulation),
                    actionsRunHash: actionsRunHash,
                    postBattleResultsHash: postBattleResultsHash
                });
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    import {GameStorage, MowseDungeonMonsterEnum, MowseDungeonMonsterAttributes, MowseDungeonSpecialAbilityEnum, MowseDungeonStatusEffectEnum, MowseDungeonClassEnum, MowseDungeonCompanionEnum, MowseDungeonActionTypeEnum, MowseDungeonPlayer, MowseDungeonMonster, MowseDungeonSimplePlayer, MowseDungeonSimpleMonster, MowseDungeonBattleStatusEnum, MowseDungeonTrinketEnum, MowseDungeon, MowseDungeonRoom, MowseDungeonSimpleStatusEffect, MowseDungeonSimpleTrinkets, MowseDungeonBattleSimulation, MowseDungeonBattleResults, MowseDungeonBattleLog, MowseDungeonBattleLogSourceEnum, MowseDungeonBattleLogTypeEnum} from "../../libraries/LibStorage.sol";
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibDungeon.sol";
    
    import {MowseDungeonSimulateBattleFacet} from "./MowseDungeonSimulateBattleFacet.sol";
    import {MowseDungeonSimulateMonsterSpecialAbilityFacet} from "./MowseDungeonSimulateMonsterSpecialAbilityFacet.sol";
    
    contract MowseDungeonSimulateMonsterAttackFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        function _performMonsterAttack(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) external view returns (MowseDungeonBattleSimulation memory) {
            // Calculate pre-attack monster stats
            if (_monsterIndex == 0) {
                _battleSimulation.monster1 = _beginningOfAttackStatusEffects(_battleSimulation.monster1);
                _battleSimulation.monster1 = _calculatePreAttackMonsterAttack(_battleSimulation.monster1);
                // Lone Survivor status effect
                if (
                    _battleSimulation.monster2.currentHealth == 0 &&
                    _battleSimulation.monster3.currentHealth == 0 &&
                    _battleSimulation.monster1.statusEffects.loneSurvivor > 0
                ) {
                    console.log('LONE SURVIVOR');
                    _battleSimulation.monster1.tempAttack = _battleSimulation.monster1.tempAttack * 2;
                }
                (_battleSimulation, _battleSimulation.monster1) = _calculatePreAttackMonsterHealth(_battleSimulation, _battleSimulation.monster1, 0);
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2 = _beginningOfAttackStatusEffects(_battleSimulation.monster2);
                _battleSimulation.monster2 = _calculatePreAttackMonsterAttack(_battleSimulation.monster2);
                if (
                    _battleSimulation.monster1.currentHealth == 0 &&
                    _battleSimulation.monster3.currentHealth == 0 &&
                    _battleSimulation.monster2.statusEffects.loneSurvivor > 0
                ) {
                    _battleSimulation.monster2.tempAttack = _battleSimulation.monster2.tempAttack * 2;
                }
                (_battleSimulation, _battleSimulation.monster2) = _calculatePreAttackMonsterHealth(_battleSimulation, _battleSimulation.monster2, 1);
            } else {
                _battleSimulation.monster3 = _beginningOfAttackStatusEffects(_battleSimulation.monster3);
                _battleSimulation.monster3 = _calculatePreAttackMonsterAttack(_battleSimulation.monster3);
                if (
                    _battleSimulation.monster1.currentHealth == 0 &&
                    _battleSimulation.monster2.currentHealth == 0 &&
                    _battleSimulation.monster3.statusEffects.loneSurvivor > 0
                ) {
                    _battleSimulation.monster3.tempAttack = _battleSimulation.monster3.tempAttack * 2;
                }
                (_battleSimulation, _battleSimulation.monster3) = _calculatePreAttackMonsterHealth(_battleSimulation, _battleSimulation.monster3, 2);
            }
            // Check if player or enemy has died
            if (_battleSimulation.player.currentHealth == 0) {
                return _battleSimulation;
            }
            if (_monsterIndex == 0) {
                if (_battleSimulation.monster1.currentHealth == 0) {
                    return _battleSimulation;
                }
            } else if (_monsterIndex == 1) {
                if (_battleSimulation.monster2.currentHealth == 0) {
                    return _battleSimulation;
                }
            } else {
                if (_battleSimulation.monster3.currentHealth == 0) {
                    return _battleSimulation;
                }
            }
            if (_monsterIndex == 0) {
                (_battleSimulation, _battleSimulation.monster1) = _calculateMonsterAccuracy(_battleSimulation, _battleSimulation.monster1);
            } else if (_monsterIndex == 1) {
                (_battleSimulation, _battleSimulation.monster2) = _calculateMonsterAccuracy(_battleSimulation, _battleSimulation.monster2);
            } else {
                (_battleSimulation, _battleSimulation.monster3) = _calculateMonsterAccuracy(_battleSimulation, _battleSimulation.monster3);
            }
    
            // Perform enemy action and calculate damage
            if (_monsterIndex == 0) {
                if (_battleSimulation.monster1.canHit) {
                    _battleSimulation = _calculateMonsterDamageToPlayer(_battleSimulation, _monsterIndex);
                    if (
                        _battleSimulation.player.currentHealth == 0 ||
                        (_battleSimulation.monster1.currentHealth == 0 &&
                            _battleSimulation.monster2.currentHealth == 0 &&
                            _battleSimulation.monster3.currentHealth == 0)
                    ) {
                        return _battleSimulation;
                    }
                }
            } else if (_monsterIndex == 1) {
                if (_battleSimulation.monster2.canHit) {
                    _battleSimulation = _calculateMonsterDamageToPlayer(_battleSimulation, _monsterIndex);
                    if (
                        _battleSimulation.player.currentHealth == 0 ||
                        (_battleSimulation.monster1.currentHealth == 0 &&
                            _battleSimulation.monster2.currentHealth == 0 &&
                            _battleSimulation.monster3.currentHealth == 0)
                    ) {
                        return _battleSimulation;
                    }
                }
            } else {
                if (_battleSimulation.monster3.canHit) {
                    _battleSimulation = _calculateMonsterDamageToPlayer(_battleSimulation, _monsterIndex);
                    if (
                        _battleSimulation.player.currentHealth == 0 ||
                        (_battleSimulation.monster1.currentHealth == 0 &&
                            _battleSimulation.monster2.currentHealth == 0 &&
                            _battleSimulation.monster3.currentHealth == 0)
                    ) {
                        return _battleSimulation;
                    }
                }
            }
    
            // Apply post-attack damage back to enemy
            if (_monsterIndex == 0) {
                (_battleSimulation, _battleSimulation.monster1) = _calculatePostAttackMonsterHealth(_battleSimulation, _battleSimulation.monster1, _monsterIndex);
            } else if (_monsterIndex == 1) {
                (_battleSimulation, _battleSimulation.monster2) = _calculatePostAttackMonsterHealth(_battleSimulation, _battleSimulation.monster2, _monsterIndex);
            } else {
                (_battleSimulation, _battleSimulation.monster3) = _calculatePostAttackMonsterHealth(_battleSimulation, _battleSimulation.monster3, _monsterIndex);
            }
    
            return _battleSimulation;
        }
    
        // Tick down counter, thorns, etc.
        function _beginningOfAttackStatusEffects(MowseDungeonSimpleMonster memory _monster) internal pure returns (MowseDungeonSimpleMonster memory) {
            if (_monster.statusEffects.counter > 0) {
                _monster.statusEffects.counter--;
            }
            if (_monster.statusEffects.thorns > 0) {
                _monster.statusEffects.thorns--;
            }
            if (_monster.statusEffects.wrathfulReprisal > 0) {
                _monster.statusEffects.wrathfulReprisal--;
            }
            if (_monster.statusEffects.bulk > 0) {
                _monster.statusEffects.bulk--;
            }
            if (_monster.statusEffects.frail > 0) {
                _monster.statusEffects.frail--;
            }
    
            return _monster;
        }
    
        function _calculatePreAttackMonsterAttack(MowseDungeonSimpleMonster memory _monster) internal pure returns (MowseDungeonSimpleMonster memory) {
            // Calculate pre-attack stats for monster
            if (_monster.statusEffects.strengthen > 0) {
                _monster.tempAttack = _monster.tempAttack * 2;
                _monster.statusEffects.strengthen--;
            }
            if (_monster.statusEffects.weaken > 0) {
                _monster.tempAttack = _monster.tempAttack / 2;
                _monster.statusEffects.weaken--;
            }
            return _monster;
        }
    
        function _calculatePreAttackMonsterHealth(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory, MowseDungeonSimpleMonster memory) {
            // Apply heals first
            if (_monster.statusEffects.healBlock == 0) {
                if (_monster.statusEffects.regen > 0) {
                    _monster.currentHealth = _monster.currentHealth + ((_monster.maxHealth * 5) / 100) * _monster.statusEffects.regen;
                    // Make sure health doesn't exceed maxHealth
                    if (_monster.currentHealth > _monster.maxHealth) {
                        _monster.currentHealth = _monster.maxHealth;
                    } {
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_REGEN, 5 * _monster.statusEffects.regen);
                        _monster.statusEffects.regen--;
                    }
                }
            } else {
                _monster.statusEffects.healBlock--;
            }
            // Then apply damage
            if (_monster.statusEffects.invincible == 0) {
                // POISON
                if (_monster.statusEffects.poison > 0) {
                    if (_monster.statusEffects.sleep > 0) {
                        (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                            (5 * (_monster.statusEffects.poison) * 150) / 100,
                            _monster.currentHealth,
                            _monster.shield
                        );
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, (5 * (_monster.statusEffects.poison) * 150) / 100);
                    } else {
                        (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                            5 * _monster.statusEffects.poison,
                            _monster.currentHealth,
                            _monster.shield
                        );
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, 5 * _monster.statusEffects.poison);
                    }
                    _battleSimulation.achievementProgress.poisonDamageDealt += (
                        _monster.statusEffects.sleep > 0 ? ((5 * (_monster.statusEffects.poison)) * 150) / 100 : 5 * (_monster.statusEffects.poison)
                    );
                    _battleSimulation.achievementProgress.damageDealt += (
                        _monster.statusEffects.sleep > 0 ? ((5 * (_monster.statusEffects.poison)) * 150) / 100 : 5 * (_monster.statusEffects.poison)
                    );
                    if (_monster.statusEffects.sleep > 0) {
                        _monster.statusEffects.sleep = 0;
                    }
                    _monster = LibDungeon._checkMonsterRevive(_monster);
                }
    
                // CURSE
                if (_monster.statusEffects.curse > 0) {
                    if (_monster.statusEffects.sleep > 0) {
                        (_monster.currentHealth) = LibDungeon._damageHealth(
                            (((_monster.maxHealth * (9 + _monster.statusEffects.curse)) / 100) * 150) / 100,
                            _monster.currentHealth
                        );
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CURSE, (((_monster.maxHealth * (9 + _monster.statusEffects.curse)) / 100) * 150) / 100);
                    } else {
                        (_monster.currentHealth) = LibDungeon._damageHealth(
                            (_monster.maxHealth * (9 + _monster.statusEffects.curse)) / 100,
                            _monster.currentHealth
                        );
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CURSE, (_monster.maxHealth * (9 + _monster.statusEffects.curse)) / 100);
                    }
                    _battleSimulation.achievementProgress.curseDamageDealt += (
                        _monster.statusEffects.sleep > 0 ? (((_monster.maxHealth * 10) / 100) * 150) / 100 : (_monster.maxHealth * 10) / 100
                    );
                    _battleSimulation.achievementProgress.damageDealt += (
                        _monster.statusEffects.sleep > 0 ? (((_monster.maxHealth * 10) / 100) * 150) / 100 : (_monster.maxHealth * 10) / 100
                    );
                    if (_monster.statusEffects.sleep > 0) {
                        _monster.statusEffects.sleep = 0;
                    }
                    _monster = LibDungeon._checkMonsterRevive(_monster);
                }
            }
            return (_battleSimulation, _monster);
        }
    
        function _calculateMonsterAccuracy(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster
        ) internal pure returns (MowseDungeonBattleSimulation memory, MowseDungeonSimpleMonster memory) {
            // Calculate accuracy of player attack
            if (_monster.statusEffects.blind > 0) {
                _monster.accuracy = _monster.accuracy - 50;
                _monster.statusEffects.blind--;
            }
            if (_monster.statusEffects.eagleEye > 0) {
                _monster.accuracy = _monster.accuracy + 50;
                _monster.statusEffects.eagleEye--;
            }
            if (_battleSimulation.player.statusEffects.dodge > 0) {
                _monster.accuracy = _monster.accuracy - 30;
                _battleSimulation.player.statusEffects.dodge--;
            }
            if (_monster.statusEffects.stun > 0) {
                _monster.accuracy = 0;
                _monster.statusEffects.stun--;
            }
            if (_monster.statusEffects.sleep > 0) {
                _monster.accuracy = 0;
                _monster.statusEffects.sleep--;
            }
            if (_monster.statusEffects.charm > 0) {
                _monster.accuracy = 0;
                _monster.statusEffects.charm--;
            }
            if (_monster.statusEffects.freeze > 0) {
                _monster.accuracy = 0;
                _monster.statusEffects.freeze--;
            }
            // Calculate the accuracy
            if (_battleSimulation.seed >> 2 == 0) {
                _battleSimulation.seed = LibDungeon.getNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
            } else {
                _battleSimulation.seed >>= 2;
            }
            if (_battleSimulation.seed % 100 < _monster.accuracy) {
                _monster.canHit = true;
            }
            return (_battleSimulation, _monster);
        }
    
        function _calculateMonsterDamageToPlayer(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal view returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                (_battleSimulation, _battleSimulation.monster1) = _calculateMonsterDamageMultiplier(_battleSimulation, _battleSimulation.monster1);
                if (_battleSimulation.monster1.statusEffects.charged > 0) {
                    (_battleSimulation, _battleSimulation.monster1) = _performChargedAttack(_battleSimulation, _battleSimulation.monster1, _monsterIndex);
                } else {
                    // Randomize to see if attack is basic or special
                    _battleSimulation.seed = LibDungeon.updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
                    // 60% chance of basic attack. If silenced, do basic attack
                    if (_battleSimulation.seed % 100 < 60 || _battleSimulation.monster1.statusEffects.silence > 0) {
                        delete _battleSimulation.monster1.usedSpecialAbility;
                        if (_battleSimulation.player.statusEffects.invincible == 0) {
                            if (_battleSimulation.monster1.statusEffects.pierce > 0) {
                                (_battleSimulation.player.currentHealth) = LibDungeon._damageHealth(
                                    (_battleSimulation.monster1.tempAttack * _battleSimulation.monster1.damageMultiplier) / 100,
                                    _battleSimulation.player.currentHealth
                                );
                            } else {
                                (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                                    (_battleSimulation.monster1.tempAttack * _battleSimulation.monster1.damageMultiplier) / 100,
                                    _battleSimulation.player.currentHealth,
                                    _battleSimulation.player.shield
                                );
                            }
                            _battleSimulation = LibDungeon._addToBattleLogForPlayerFromMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, (_battleSimulation.monster1.tempAttack * _battleSimulation.monster1.damageMultiplier) / 100);
                            _battleSimulation.achievementProgress.damageTaken += (_battleSimulation.monster1.tempAttack * _battleSimulation.monster1.damageMultiplier) / 100;
                            _battleSimulation.player = LibDungeon._checkPlayerRevive(_battleSimulation.player);
                        }
                        if (_battleSimulation.player.currentHealth > 0) {
                            (_battleSimulation, _battleSimulation.monster1) = _playerRetaliate(_battleSimulation, _battleSimulation.monster1, _monsterIndex);
                        }
                    } else {
                        _battleSimulation.monster1.usedSpecialAbility = true;
                        _battleSimulation = MowseDungeonSimulateMonsterSpecialAbilityFacet(gs.diamondAddress).monsterSpecialAbility(
                            _battleSimulation,
                            _battleSimulation.monster1.specialAbility,
                            _monsterIndex
                        );
                    }
                    if (_battleSimulation.monster1.statusEffects.silence > 0) {
                        _battleSimulation.monster1.statusEffects.silence--;
                    }
                }
            } else if (_monsterIndex == 1) {
                (_battleSimulation, _battleSimulation.monster2) = _calculateMonsterDamageMultiplier(_battleSimulation, _battleSimulation.monster2);
                if (_battleSimulation.monster2.statusEffects.charged > 0) {
                    (_battleSimulation, _battleSimulation.monster2) = _performChargedAttack(_battleSimulation, _battleSimulation.monster2, _monsterIndex);
                } else {
                    // Randomize to see if attack is basic or special
                    _battleSimulation.seed = LibDungeon.updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
                    if (_battleSimulation.seed % 100 < 60 || _battleSimulation.monster2.statusEffects.silence > 0) {
                        delete _battleSimulation.monster2.usedSpecialAbility;
                        if (_battleSimulation.player.statusEffects.invincible == 0) {
                            if (_battleSimulation.monster2.statusEffects.pierce > 0) {
                                (_battleSimulation.player.currentHealth) = LibDungeon._damageHealth(
                                    (_battleSimulation.monster2.tempAttack * _battleSimulation.monster2.damageMultiplier) / 100,
                                    _battleSimulation.player.currentHealth
                                );
                            } else {
                                (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                                    (_battleSimulation.monster2.tempAttack * _battleSimulation.monster2.damageMultiplier) / 100,
                                    _battleSimulation.player.currentHealth,
                                    _battleSimulation.player.shield
                                );
                            }
                            _battleSimulation = LibDungeon._addToBattleLogForPlayerFromMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, (_battleSimulation.monster2.tempAttack * _battleSimulation.monster2.damageMultiplier) / 100);
                            _battleSimulation.achievementProgress.damageTaken += (_battleSimulation.monster2.tempAttack * _battleSimulation.monster2.damageMultiplier) / 100;
                            _battleSimulation.player = LibDungeon._checkPlayerRevive(_battleSimulation.player);
                        }
                        if (_battleSimulation.player.currentHealth > 0) {
                            (_battleSimulation, _battleSimulation.monster2) = _playerRetaliate(_battleSimulation, _battleSimulation.monster2, _monsterIndex);
                        }
                    } else {
                        _battleSimulation.monster2.usedSpecialAbility = true;
                        _battleSimulation = MowseDungeonSimulateMonsterSpecialAbilityFacet(gs.diamondAddress).monsterSpecialAbility(
                            _battleSimulation,
                            _battleSimulation.monster2.specialAbility,
                            _monsterIndex
                        );
                    }
                    if (_battleSimulation.monster2.statusEffects.silence > 0) {
                        _battleSimulation.monster2.statusEffects.silence--;
                    }
                }
            } else {
                (_battleSimulation, _battleSimulation.monster3) = _calculateMonsterDamageMultiplier(_battleSimulation, _battleSimulation.monster3);
                if (_battleSimulation.monster3.statusEffects.charged > 0) {
                    (_battleSimulation, _battleSimulation.monster3) = _performChargedAttack(_battleSimulation, _battleSimulation.monster3, _monsterIndex);
                } else {
                    // Randomize to see if attack is basic or special
                    _battleSimulation.seed = LibDungeon.updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
                    if (_battleSimulation.seed % 100 < 60 || _battleSimulation.monster3.statusEffects.silence > 0) {
                        delete _battleSimulation.monster3.usedSpecialAbility;
                        if (_battleSimulation.player.statusEffects.invincible == 0) {
                            if (_battleSimulation.monster3.statusEffects.pierce > 0) {
                                (_battleSimulation.player.currentHealth) = LibDungeon._damageHealth(
                                    (_battleSimulation.monster3.tempAttack * _battleSimulation.monster3.damageMultiplier) / 100,
                                    _battleSimulation.player.currentHealth
                                );
                            } else {
                                (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                                    (_battleSimulation.monster3.tempAttack * _battleSimulation.monster3.damageMultiplier) / 100,
                                    _battleSimulation.player.currentHealth,
                                    _battleSimulation.player.shield
                                );
                            }
                            _battleSimulation = LibDungeon._addToBattleLogForPlayerFromMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, (_battleSimulation.monster3.tempAttack * _battleSimulation.monster3.damageMultiplier) / 100);
                            _battleSimulation.achievementProgress.damageTaken += (_battleSimulation.monster3.tempAttack * _battleSimulation.monster3.damageMultiplier) / 100;
                            _battleSimulation.player = LibDungeon._checkPlayerRevive(_battleSimulation.player);
                        }
                        if (_battleSimulation.player.currentHealth > 0) {
                            (_battleSimulation, _battleSimulation.monster3) = _playerRetaliate(_battleSimulation, _battleSimulation.monster3, _monsterIndex);
                        }
                    } else {
                        _battleSimulation.monster3.usedSpecialAbility = true;
                        _battleSimulation = MowseDungeonSimulateMonsterSpecialAbilityFacet(gs.diamondAddress).monsterSpecialAbility(
                            _battleSimulation,
                            _battleSimulation.monster3.specialAbility,
                            _monsterIndex
                        );
                    }
                    if (_battleSimulation.monster3.statusEffects.silence > 0) {
                        _battleSimulation.monster3.statusEffects.silence--;
                    }
                }
            }
            return _battleSimulation;
        }
    
        function _performChargedAttack(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory, MowseDungeonSimpleMonster memory) {
            // Check if monster was previously charging an attack
            if (_monster.monsterType == MowseDungeonMonsterEnum.PEBBLE_GOLEM) {
                _monster.tempAttack *= 3;
                _monster.statusEffects.charged = 0;
                if (_battleSimulation.player.statusEffects.stunResist == 0 && _battleSimulation.player.statusEffects.unstoppable == 0) {
                    _battleSimulation.player.statusEffects.stun++;
                    _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STUN, 1);
                    // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STUN, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: 1}));
                }
            } else if (_monster.monsterType == MowseDungeonMonsterEnum.IRON_MAIDEN) {
                if (_monster.statusEffects.charged == 1) {
                    _monster.tempAttack += _monster.shield;
                    _monster.statusEffects.charged = 0;
                } else {
                    _monster.statusEffects.charged--;
                }
            } else if (_monster.monsterType == MowseDungeonMonsterEnum.GOLD_KING) {
                if (_monster.statusEffects.charged == 1) {
                    _monster.tempAttack += _battleSimulation.player.coins;
                    _monster.statusEffects.charged = 0;
                } else {
                    _monster.statusEffects.charged--;
                }
            }
    
            if (_battleSimulation.player.statusEffects.invincible == 0) {
                if (_monster.statusEffects.pierce > 0) {
                    (_battleSimulation.player.currentHealth) = LibDungeon._damageHealth(
                        (_monster.tempAttack * _monster.damageMultiplier) / 100,
                        _battleSimulation.player.currentHealth
                    );
                } else {
                    (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                        (_monster.tempAttack * _monster.damageMultiplier) / 100,
                        _battleSimulation.player.currentHealth,
                        _battleSimulation.player.shield
                    );
                }
                _battleSimulation = LibDungeon._addToBattleLogForPlayerFromMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, (_monster.tempAttack * _monster.damageMultiplier) / 100);
                _battleSimulation.achievementProgress.damageTaken += (_monster.tempAttack * _monster.damageMultiplier) / 100;
                _battleSimulation.player = LibDungeon._checkPlayerRevive(_battleSimulation.player);
            }
            if (_battleSimulation.player.currentHealth > 0) {
                (_battleSimulation, _monster) = _playerRetaliate(_battleSimulation, _monster, _monsterIndex);
            }
    
            return (_battleSimulation, _monster);
        }
    
        function _calculateMonsterDamageMultiplier(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster
        ) internal pure returns (MowseDungeonBattleSimulation memory, MowseDungeonSimpleMonster memory) {
            if (_battleSimulation.player.statusEffects.bulk > 0) {
                _monster.damageMultiplier -= 30;
            }
            if (_battleSimulation.player.statusEffects.frail > 0) {
                _monster.damageMultiplier += 30;
            }
            if (_battleSimulation.player.statusEffects.sleep > 0) {
                _monster.damageMultiplier += 50;
                _battleSimulation.player.statusEffects.sleep = 0;
            }
    
            return (_battleSimulation, _monster);
        }
    
        function _playerRetaliate(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory, MowseDungeonSimpleMonster memory) {
            if (_battleSimulation.player.statusEffects.counter > 0) {
                if (_monster.statusEffects.invincible == 0) {
                    (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                        _battleSimulation.player.attack,
                        _monster.currentHealth,
                        _monster.shield
                    );
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.PLAYER, _battleSimulation.player.attack);
                    _battleSimulation.achievementProgress.damageDealt += _battleSimulation.player.attack;
                    _monster = LibDungeon._checkMonsterRevive(_monster);
                }
            }
            if (_battleSimulation.player.statusEffects.thorns > 0) {
                if (_monster.statusEffects.invincible == 0) {
                    (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                        5 * _battleSimulation.player.statusEffects.thorns,
                        _monster.currentHealth,
                        _monster.shield
                    );
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_THORNS, 5 * _battleSimulation.player.statusEffects.thorns);
                    _battleSimulation.achievementProgress.damageDealt += 5 * _battleSimulation.player.statusEffects.thorns;
                    _monster = LibDungeon._checkMonsterRevive(_monster);
                }
            }
            return (_battleSimulation, _monster);
        }
    
        function _calculatePostAttackMonsterHealth(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory, MowseDungeonSimpleMonster memory) {
            if (_monster.statusEffects.invincible == 0) {
                // BURN
                if (_monster.statusEffects.burn > 0) {
                    if (_monster.statusEffects.sleep > 0) {
                        (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                            (5 * _monster.statusEffects.burn * 150) / 100,
                            _monster.currentHealth,
                            _monster.shield
                        );
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BURN, (5 * _monster.statusEffects.burn * 150) / 100);
                    } else {
                        (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                            5 * _monster.statusEffects.burn,
                            _monster.currentHealth,
                            _monster.shield
                        );
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BURN, 5 * _monster.statusEffects.burn);
                    }
                    _battleSimulation.achievementProgress.burnDamageDealt += (
                        _monster.statusEffects.sleep > 0 ? ((5 * _monster.statusEffects.burn) * 150) / 100 : 5 * _monster.statusEffects.burn
                    );
                    if (_monster.statusEffects.sleep > 0) {
                        _monster.statusEffects.sleep = 0;
                    }
                    _monster = LibDungeon._checkMonsterRevive(_monster);
                }
    
                // BLEED
                if (_monster.statusEffects.bleed > 0) {
                    if (_monster.statusEffects.sleep > 0) {
                        (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                            ((_monster.statusEffects.bleed + 5) * _monster.maxHealth * 150) / 10000,
                            _monster.currentHealth,
                            _monster.shield
                        );
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BLEED, ((_monster.statusEffects.bleed + 5) * _monster.maxHealth * 150) / 10000);
                    } else {
                        (_monster.currentHealth, _monster.shield) = LibDungeon._damageShieldAndHealth(
                            ((_monster.statusEffects.bleed + 5) * _monster.maxHealth) / 100,
                            _monster.currentHealth,
                            _monster.shield
                        );
                        _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BLEED, ((_monster.statusEffects.bleed + 5) * _monster.maxHealth) / 100);
                    }
                    _battleSimulation.achievementProgress.bleedDamageDealt += (
                        _monster.statusEffects.sleep > 0
                            ? ((((_monster.statusEffects.bleed + 5) * _monster.maxHealth) / 100) * 150) / 100
                            : ((_monster.statusEffects.bleed + 5) * _monster.maxHealth) / 100
                    );
                    if (_monster.statusEffects.sleep > 0) {
                        _monster.statusEffects.sleep = 0;
                    }
                    _monster = LibDungeon._checkMonsterRevive(_monster);
                }
            }
            if (_monster.statusEffects.death > 0) {
                _monster.statusEffects.death--;
                if (_monster.statusEffects.death == 0) {
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_DEATH, _monster.currentHealth);
                    _monster.currentHealth = 0;
                }
            }
            return (_battleSimulation, _monster);
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    import {
        GameStorage, 
        MowseDungeonMonsterEnum, 
        MowseDungeonSpecialAbilityEnum, 
        MowseDungeonBattleSimulation, 
        MowseDungeonBattleLog, 
        MowseDungeonBattleLogTypeEnum, 
        MowseDungeonBattleLogSourceEnum
    } from "../../libraries/LibStorage.sol";
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibDungeon.sol";
    
    import {MowseDungeonSimulateBattleFacet} from "./MowseDungeonSimulateBattleFacet.sol";
    
    contract MowseDungeonSimulateMonsterSpecialAbilityFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        function monsterSpecialAbility(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSpecialAbilityEnum _specialAbility,
            uint256 _monsterIndex
        ) external view returns (MowseDungeonBattleSimulation memory) {
            if (_specialAbility == MowseDungeonSpecialAbilityEnum.DEFEND) {
                _battleSimulation = _defend(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.POISON_GAS) {
                _battleSimulation = _poisonGas(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.BOULDER_TOSS) {
                _battleSimulation = _boulderToss(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.RAGE) {
                _battleSimulation = _rage(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.SUMMON_CLUCKO) {
                _battleSimulation = _summonClucko(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.SUMMON_PUNK) {
                _battleSimulation = _summonPunk(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.THIN_STICK) {
                _battleSimulation = _thinStick(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.BADMOUTH) {
                _battleSimulation = _badMouth(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.CURSED_SLIME) {
                _battleSimulation = _cursedSlime(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.BLEEDING_SLIME) {
                _battleSimulation = _bleedingSlime(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.IRON_GRASP) {
                _battleSimulation = _ironGrasp(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.IRON_PRAYER) {
                _battleSimulation = _ironPrayer(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.JESTER_DANCE) {
                _battleSimulation = _jesterDance(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.KNIGHTS_HONOR) {
                _battleSimulation = _knightsHonor(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.QUEENS_GRACE) {
                _battleSimulation = _queensGrace(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.KINGS_AUTHORITY) {
                _battleSimulation = _kingsAuthority(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.ROYAL_DECREE) {
                _battleSimulation = _royalDecree(_battleSimulation, _monsterIndex);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.HOARDERS_STASH) {
                _battleSimulation = _hoardersStash(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.WRATHFUL_REPRISAL) {
                _battleSimulation = _wrathfulReprisal(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.DESTRUCTIVE_ENVY) {
                _battleSimulation = _destructiveEnvy(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.SEDUCTIVE_GAZE) {
                _battleSimulation = _seductiveGaze(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.INSATIABLE_FEAST) {
                _battleSimulation = _insatiableFeast(_battleSimulation);
            } else if (_specialAbility == MowseDungeonSpecialAbilityEnum.ANTI_SONIC_SPEED) {
                _battleSimulation = _antiSonicSpeed(_battleSimulation);
            } else {
                // If NONE, do nothing
                _battleSimulation = LibDungeon._addToBattleLogForPlayerFromMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, 0);
            }
    
            return _battleSimulation;
        }
    
        function _defend(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                _battleSimulation.monster1.statusEffects.thorns++;
                _battleSimulation.monster1.statusEffects.counter++;
                _battleSimulation.monster1.shield += 5;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2.statusEffects.thorns++;
                _battleSimulation.monster2.statusEffects.counter++;
                _battleSimulation.monster2.shield += 5;
            } else {
                _battleSimulation.monster3.statusEffects.thorns++;
                _battleSimulation.monster3.statusEffects.counter++;
                _battleSimulation.monster3.shield += 5;
            }
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_THORNS, 1);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_COUNTER, 1);
            return _battleSimulation;
        }
    
        function _poisonGas(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_battleSimulation.player.statusEffects.poisonResist == 0) {
                _battleSimulation.player.statusEffects.poison += 2;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, 2);
            }
            if (_monsterIndex == 0) {
                _battleSimulation.monster1.statusEffects.dodge++;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2.statusEffects.dodge++;
            } else {
                _battleSimulation.monster3.statusEffects.dodge++;
            }
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_DODGE, 1);
            return _battleSimulation;
        }
    
        function _boulderToss(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                _battleSimulation.monster1.statusEffects.charged++;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2.statusEffects.charged++;
            } else {
                _battleSimulation.monster3.statusEffects.charged++;
            }
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARGED, 1);
            return _battleSimulation;
        }
    
        function _rage(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                _battleSimulation.monster1.statusEffects.strengthen += 2;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2.statusEffects.strengthen += 2;
            } else {
                _battleSimulation.monster3.statusEffects.strengthen += 2;
            }
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STRENGTHEN, 2);
            return _battleSimulation;
        }
    
        // When summoning monsters, summon later monsters stunned so they don't immediately attack
        function _summonClucko(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal view returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                if (_battleSimulation.monster2.currentHealth == 0) {
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.CLUCKO);
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster2, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster2.statusEffects;
                    _battleSimulation.monster2.statusEffects.stun++;
                    _battleSimulation.monster2.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_2, 0);
                } else if (_battleSimulation.monster3.currentHealth == 0) {
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.CLUCKO);
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster3, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster3.statusEffects;
                    _battleSimulation.monster3.statusEffects.stun++;
                    _battleSimulation.monster3.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_3, 0);
                }
            } else if (_monsterIndex == 1) {
                if (_battleSimulation.monster1.currentHealth == 0) {
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.CLUCKO);
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster1, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster1.statusEffects;
                    _battleSimulation.monster1.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_1, 0);
                } else if (_battleSimulation.monster3.currentHealth == 0) {
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.CLUCKO);
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster3, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster3.statusEffects;
                    _battleSimulation.monster3.statusEffects.stun++;
                    _battleSimulation.monster3.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_3, 0);
                }
            } else {
                if (_battleSimulation.monster1.currentHealth == 0) {
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.CLUCKO);
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster1, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster1.statusEffects;
                    _battleSimulation.monster1.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_1, 0);
                } else if (_battleSimulation.monster2.currentHealth == 0) {
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.CLUCKO);
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster2, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster2.statusEffects;
                    _battleSimulation.monster2.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_2, 0);
                }
            }
            if (_monsterIndex == 0) {
                _battleSimulation.monster1.statusEffects.silence += 10;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2.statusEffects.silence += 10;
            } else {
                _battleSimulation.monster3.statusEffects.silence += 10;
            }
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_SILENCE, 10);
            return _battleSimulation;
        }
    
        function _thinStick(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                _battleSimulation.monster1.statusEffects.dodge += 3;
                _battleSimulation.monster1.statusEffects.lastHope++;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2.statusEffects.dodge += 3;
                _battleSimulation.monster2.statusEffects.lastHope++;
            } else {
                _battleSimulation.monster3.statusEffects.dodge += 3;
                _battleSimulation.monster3.statusEffects.lastHope++;
            }
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_DODGE, 3);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_LAST_HOPE, 1);
            return _battleSimulation;
        }
    
        function _badMouth(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.player.statusEffects.silence++;
            _battleSimulation.player.statusEffects.weaken++;
            _battleSimulation.player.statusEffects.healBlock++;
            _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_SILENCE, 1);
            _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_WEAKEN, 1);
            _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_HEAL_BLOCK, 1);
            return _battleSimulation;
        }
    
        function _summonPunk(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal view returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                if (_battleSimulation.monster2.currentHealth == 0) {
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.WRAT_PUNK);
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster2, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster2.statusEffects;
                    _battleSimulation.monster2.statusEffects.stun++;
                    _battleSimulation.monster2.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_2, 0);
                }
                if (_battleSimulation.monster3.currentHealth == 0) {
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.WRAT_PUNK);
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster3, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster3.statusEffects;
                    _battleSimulation.monster3.statusEffects.stun++;
                    _battleSimulation.monster3.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_3, 0);
                }
            } else if (_monsterIndex == 1) {
                if (_battleSimulation.monster1.currentHealth == 0) {
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.WRAT_PUNK);
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster1, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster1.statusEffects;
                    _battleSimulation.monster1.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_1, 0);
                }
                if (_battleSimulation.monster3.currentHealth == 0) {
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.WRAT_PUNK);
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster3, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster3.statusEffects;
                    _battleSimulation.monster3.statusEffects.stun++;
                    _battleSimulation.monster3.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_3, 0);
                }
            } else {
                if (_battleSimulation.monster1.currentHealth == 0) {
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.WRAT_PUNK);
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster1, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster1.statusEffects;
                    _battleSimulation.monster1.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_1, 0);
                }
                if (_battleSimulation.monster2.currentHealth == 0) {
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.WRAT_PUNK);
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster2, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster2.statusEffects;
                    _battleSimulation.monster2.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_2, 0);
                }
            }
            return _battleSimulation;
        }
    
        function _cursedSlime(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.player.statusEffects.curse++;
            _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CURSE, 1);
            return _battleSimulation;
        }
    
        function _bleedingSlime(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.player.statusEffects.bleed++;
            _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BLEED, 1);
            return _battleSimulation;
        }
    
        function _ironGrasp(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                _battleSimulation.monster1.statusEffects.charged++;
                _battleSimulation.monster1.shield *= 2;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2.statusEffects.charged++;
                _battleSimulation.monster2.shield *= 2;
            } else {
                _battleSimulation.monster3.statusEffects.charged++;
                _battleSimulation.monster3.shield *= 2;
            }
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARGED, 1);
            return _battleSimulation;
        }
    
        function _ironPrayer(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                _battleSimulation.monster2.shield += 5;
                _battleSimulation.monster3.shield += 5;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster1.shield += 5;
                _battleSimulation.monster3.shield += 5;
            } else {
                _battleSimulation.monster1.shield += 5;
                _battleSimulation.monster2.shield += 5;
            }
            return _battleSimulation;
        }
    
        function _jesterDance(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_battleSimulation.seed >> 2 == 0) {
                _battleSimulation.seed = LibDungeon.getNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
            } else {
                _battleSimulation.seed >>= 2;
            }
            uint256 _random = _battleSimulation.seed % 8;
            if (_random == 0) {
                _battleSimulation.player.statusEffects.curse++;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CURSE, 1);
            } else if (_random == 1) {
                _battleSimulation.player.statusEffects.bleed++;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BLEED, 1);
            } else if (_random == 2) {
                _battleSimulation.player.statusEffects.burn++;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BURN, 1);
            } else if (_random == 3) {
                _battleSimulation.player.statusEffects.poison++;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, 1);
            } else if (_random == 4) {
                _battleSimulation.player.statusEffects.stun++;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STUN, 1);
            } else if (_random == 5) {
                _battleSimulation.player.statusEffects.sleep++;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_SLEEP, 1);
            } else if (_random == 6) {
                _battleSimulation.player.statusEffects.charm++;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARM, 1);
            } else {
                _battleSimulation.player.statusEffects.freeze++;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_FREEZE, 1);
            }
            return _battleSimulation;
        }
    
        function _knightsHonor(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                _battleSimulation.monster1.statusEffects.strengthen++;
                _battleSimulation.monster1.shield += 6;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2.statusEffects.strengthen++;
                _battleSimulation.monster2.shield += 6;
            } else {
                _battleSimulation.monster3.statusEffects.strengthen++;
                _battleSimulation.monster3.shield += 6;
            }
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STRENGTHEN, 1);
            return _battleSimulation;
        }
    
        function _queensGrace(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.monster1.statusEffects.strengthen++;
            _battleSimulation.monster1.statusEffects.regen++;
            _battleSimulation.monster1.statusEffects.bulk++;
            _battleSimulation.monster2.statusEffects.strengthen++;
            _battleSimulation.monster2.statusEffects.regen++;
            _battleSimulation.monster2.statusEffects.bulk++;
            _battleSimulation.monster3.statusEffects.strengthen++;
            _battleSimulation.monster3.statusEffects.regen++;
            _battleSimulation.monster3.statusEffects.bulk++;
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STRENGTHEN, 1);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_REGEN, 1);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BULK, 1);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 1, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STRENGTHEN, 1);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 1, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_REGEN, 1);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 1, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BULK, 1);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 2, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STRENGTHEN, 1);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 2, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_REGEN, 1);
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 2, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BULK, 1);
    
            return _battleSimulation;
        }
    
        function _kingsAuthority(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.player.statusEffects.stun++;
            _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STUN, 1);
    
            if (_monsterIndex == 0) {
                _battleSimulation.monster1.statusEffects.charged += 2;
            } else if (_monsterIndex == 1) {
                _battleSimulation.monster2.statusEffects.charged += 2;
            } else {
                _battleSimulation.monster3.statusEffects.charged += 2;
            }
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARGED, 2);
            return _battleSimulation;
        }
        function _royalDecree(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _monsterIndex
        ) internal view returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                if (_battleSimulation.monster2.currentHealth == 0) {
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.COPPER_SQUIRE);
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster2, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster2.statusEffects;
                    _battleSimulation.monster2.statusEffects.stun++;
                    _battleSimulation.monster2.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_2, 0);
                }
                if (_battleSimulation.monster3.currentHealth == 0) {
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.COPPER_SQUIRE);
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster3, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster3.statusEffects;
                    _battleSimulation.monster3.statusEffects.stun++;
                    _battleSimulation.monster3.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_3, 0);
                }
            } else if (_monsterIndex == 1) {
                if (_battleSimulation.monster1.currentHealth == 0) {
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.COPPER_SQUIRE);
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster1, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster1.statusEffects;
                    _battleSimulation.monster1.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_1, 0);
                }
                if (_battleSimulation.monster3.currentHealth == 0) {
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.COPPER_SQUIRE);
                    _battleSimulation.monster3 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster3, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster3.statusEffects;
                    _battleSimulation.monster3.statusEffects.stun++;
                    _battleSimulation.monster3.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_3, 0);
                }
            } else {
                if (_battleSimulation.monster1.currentHealth == 0) {
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.COPPER_SQUIRE);
                    _battleSimulation.monster1 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster1, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster1.statusEffects;
                    _battleSimulation.monster1.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_1, 0);
                }
                if (_battleSimulation.monster2.currentHealth == 0) {
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._monsterEnumToSimpleMonster(MowseDungeonMonsterEnum.COPPER_SQUIRE);
                    _battleSimulation.monster2 = MowseDungeonSimulateBattleFacet(gs.diamondAddress)._updateMonsterByDescentLevel(_battleSimulation.monster2, _battleSimulation.descentLevel);
                    delete _battleSimulation.monster2.statusEffects;
                    _battleSimulation.monster2.hasBeenStolen = true;
                    _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.SUMMON, MowseDungeonBattleLogSourceEnum.MONSTER_2, 0);
                }
            }
            return _battleSimulation;
        }
        function _hoardersStash(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            uint256 coinsToSteal = _battleSimulation.player.coins * 25 / 100;
            _battleSimulation.monster1.maxHealth += coinsToSteal;
            _battleSimulation.monster1.currentHealth += coinsToSteal;
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.MONSTER_1, coinsToSteal);
    
            return _battleSimulation;
        }
        function _wrathfulReprisal(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.monster1.statusEffects.wrathfulReprisal++;
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_WRATHFUL_REPRISAL, 1);
            return _battleSimulation;
        }
        function _destructiveEnvy(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.monster1.attack = _battleSimulation.player.attack;
            _battleSimulation.monster1.statusEffects.charged++;
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARGED, 1);
            return _battleSimulation;
        }
        function _seductiveGaze(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.player.statusEffects.charm += 2;
            _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARM, 2);
            return _battleSimulation;
        }
        function _insatiableFeast(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            uint256 stacks;
            // Consume all buffs from player
            if (_battleSimulation.player.statusEffects.eagleEye > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.eagleEye = 0;
            }
            if (_battleSimulation.player.statusEffects.strengthen > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.strengthen = 0;
            }
            if (_battleSimulation.player.statusEffects.bulk > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.bulk = 0;
            }
            if (_battleSimulation.player.statusEffects.thorns > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.thorns = 0;
            }
            if (_battleSimulation.player.statusEffects.regen > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.regen = 0;
            }
            if (_battleSimulation.player.statusEffects.invincible > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.invincible = 0;
            }
            if (_battleSimulation.player.statusEffects.lastHope > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.lastHope = 0;
            }
            if (_battleSimulation.player.statusEffects.dodge > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.dodge = 0;
            }
            if (_battleSimulation.player.statusEffects.counter > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.counter = 0;
            }
            if (_battleSimulation.player.statusEffects.pierce > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.pierce = 0;
            }
            if (_battleSimulation.player.statusEffects.cleave > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.cleave = 0;
            }
            if (_battleSimulation.player.statusEffects.revive > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.revive = 0;
            }
            if (_battleSimulation.player.statusEffects.fireResist > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.fireResist = 0;
            }
            if (_battleSimulation.player.statusEffects.poisonResist > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.poisonResist = 0;
            }
            if (_battleSimulation.player.statusEffects.freezeResist > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.freezeResist = 0;
            }
            if (_battleSimulation.player.statusEffects.stunResist > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.stunResist = 0;
            }
            if (_battleSimulation.player.statusEffects.sleepResist > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.sleepResist = 0;
            }
            if (_battleSimulation.player.statusEffects.doubleUp > 0) {
                stacks++;
                _battleSimulation.player.statusEffects.doubleUp = 0;
            }
            // Consume all debuffs on monster
            if (_battleSimulation.monster1.statusEffects.blind > 0) {
                stacks++;
                _battleSimulation.monster1.statusEffects.blind = 0;
            }
            if (_battleSimulation.monster1.statusEffects.poison > 0) {
                stacks++;
                _battleSimulation.monster1.statusEffects.poison = 0;
            }
            if (_battleSimulation.monster1.statusEffects.burn > 0) {
                stacks++;
                _battleSimulation.monster1.statusEffects.burn = 0;
            }
            if (_battleSimulation.monster1.statusEffects.bleed > 0) {
                stacks++;
                _battleSimulation.monster1.statusEffects.bleed = 0;
            }
            if (_battleSimulation.monster1.statusEffects.curse > 0) {
                stacks++;
                _battleSimulation.monster1.statusEffects.curse = 0;
            }
            if (_battleSimulation.monster1.statusEffects.weaken > 0) {
                stacks++;
                _battleSimulation.monster1.statusEffects.weaken = 0;
            }
            if (_battleSimulation.monster1.statusEffects.frail > 0) {
                stacks++;
                _battleSimulation.monster1.statusEffects.frail = 0;
            }
            if (_battleSimulation.monster1.statusEffects.healBlock > 0) {
                stacks++;
                _battleSimulation.monster1.statusEffects.healBlock = 0;
            }
            _battleSimulation.monster1.currentHealth += stacks * 10;
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.MONSTER_1, stacks * 10);
            
            // Make sure health doesn't exceed maxHealth
            if (_battleSimulation.monster1.currentHealth > _battleSimulation.monster1.maxHealth) {
                _battleSimulation.monster1.currentHealth = _battleSimulation.monster1.maxHealth;
            }
            _battleSimulation.monster1.statusEffects.charged++;
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CHARGED, 1);
    
            return _battleSimulation;
        }
        function _antiSonicSpeed(MowseDungeonBattleSimulation memory _battleSimulation) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation.monster1.statusEffects.sleep += 2;
            _battleSimulation = LibDungeon._addToBattleLogForMonsterIndex(_battleSimulation, 0, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_SLEEP, 2);
            if (_battleSimulation.player.statusEffects.sleepResist == 0) {
                _battleSimulation.player.statusEffects.sleep += 2;
                _battleSimulation = LibDungeon._addStatusEffectToBattleLogForPlayer(_battleSimulation, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_SLEEP, 2);
            }
            return _battleSimulation;
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    import {GameStorage, MowseDungeonMonsterEnum, MowseDungeonMonsterAttributes, MowseDungeonSpecialAbilityEnum, MowseDungeonStatusEffectEnum, MowseDungeonClassEnum, MowseDungeonCompanionEnum, MowseDungeonCompanion, MowseDungeonActionTypeEnum, MowseDungeonPlayer, MowseDungeonMonster, MowseDungeonSimplePlayer, MowseDungeonSimpleMonster, MowseDungeonBattleStatusEnum, MowseDungeonTrinketEnum, MowseDungeon, MowseDungeonRoom, MowseDungeonSimpleStatusEffect, MowseDungeonSimpleTrinkets, MowseDungeonBattleSimulation, MowseDungeonBattleResults, STAT_LUCK, MowseDungeonBattleLogTypeEnum, MowseDungeonBattleLogSourceEnum, MowseDungeonBattleLog} from "../../libraries/LibStorage.sol";
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibDungeon.sol";
    import {MowseDungeonCompanionAttackFacet} from "./MowseDungeonCompanionAttackFacet.sol";
    import {MowseDungeonPlayerClassAttackFacet} from "./MowseDungeonPlayerClassAttackFacet.sol";
    import {MowseDungeonTrinketFacet} from "./MowseDungeonTrinketFacet.sol";
    
    contract MowseDungeonSimulatePerformActionFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        error InvalidAction();
    
        function _performAction(MowseDungeonBattleSimulation memory _battleSimulation) external view returns (MowseDungeonBattleSimulation memory) {
            MowseDungeonSimpleMonster memory updatedMonster;
            if (_battleSimulation.actionType != MowseDungeonActionTypeEnum.DEFEND) {
                if (_battleSimulation.targetIndex == 0 && _battleSimulation.monster1.currentHealth == 0) {
                    revert InvalidAction();
                }
                if (_battleSimulation.targetIndex == 1 && _battleSimulation.monster2.currentHealth == 0)
                    revert InvalidAction();
                if (_battleSimulation.targetIndex == 2 && _battleSimulation.monster3.currentHealth == 0)
                    revert InvalidAction();
            }
            if (
                _battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY &&
                _battleSimulation.player.action == 0
            ) {
                console.log('reverted no actions');
                revert InvalidAction();
            }
    
            // Calculate Pre-attack player stats (for damage calculation)
            _battleSimulation = _calculatePreAttackPlayerAttack(_battleSimulation);
            // Apply pre-attack status effects (heals)
            _battleSimulation = _calculatePreAttackPlayerHealth(_battleSimulation);
            // Check if player has died or all enemy has died
            if (
                _battleSimulation.player.currentHealth == 0 ||
                (_battleSimulation.monster1.currentHealth == 0 &&
                    _battleSimulation.monster2.currentHealth == 0 &&
                    _battleSimulation.monster3.currentHealth == 0)
            ) {
                return _battleSimulation;
            }
            // If player is stunned/sleep, don't perform action
            if (
                _battleSimulation.player.statusEffects.stun > 0 ||
                _battleSimulation.player.statusEffects.sleep > 0 ||
                _battleSimulation.player.statusEffects.freeze > 0 ||
                _battleSimulation.player.statusEffects.charm > 0
            ) {
                // Do nothing
                if (_battleSimulation.player.statusEffects.stun > 0) {
                    _battleSimulation.player.statusEffects.stun--;
                }
                if (_battleSimulation.player.statusEffects.sleep > 0) {
                    _battleSimulation.player.statusEffects.sleep--;
                }
                if (_battleSimulation.player.statusEffects.freeze > 0) {
                    _battleSimulation.player.statusEffects.freeze--;
                }
                if (_battleSimulation.player.statusEffects.charm > 0) {
                    _battleSimulation.player.statusEffects.charm--;
                }
            } else {
                // Check accuracy of player attack
                if (
                    _battleSimulation.actionType == MowseDungeonActionTypeEnum.BASIC_ATTACK ||
                    _battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY
                ) {
                    _battleSimulation = _calculatePlayerAccuracy(_battleSimulation);
    
                    if (_battleSimulation.player.statusEffects.silence > 0) {
                        if (_battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                            console.log('Youre silenced cheater');
                            revert InvalidAction();
                        }
                        _battleSimulation.player.statusEffects.silence--;
                    }
                    // Reduce action points if using special ability
                    if (_battleSimulation.actionType == MowseDungeonActionTypeEnum.SPECIAL_ABILITY) {
                        _battleSimulation.player.action--;
                        console.log("Used action", _battleSimulation.player.action);
                    }
                    if (_battleSimulation.player.canHit) {
                        // Perform the action and calculate damage
                        // Cleave attack all monsters
                        if (
                            _battleSimulation.player.statusEffects.cleave > 0
                        ) {
                            if (_battleSimulation.monster1.currentHealth > 0) {
                                (_battleSimulation, updatedMonster) = MowseDungeonPlayerClassAttackFacet(gs.diamondAddress).playerClassAttack(
                                    _battleSimulation,
                                    _battleSimulation.monster1
                                );
                                _battleSimulation.monster1 = updatedMonster;
                            }
                            if (_battleSimulation.monster2.currentHealth > 0) {
                                (_battleSimulation, updatedMonster) = MowseDungeonPlayerClassAttackFacet(gs.diamondAddress).playerClassAttack(
                                    _battleSimulation,
                                    _battleSimulation.monster2
                                );
                                _battleSimulation.monster2 = updatedMonster;
                            }
                            if (_battleSimulation.monster3.currentHealth > 0) {
                                (_battleSimulation, updatedMonster) = MowseDungeonPlayerClassAttackFacet(gs.diamondAddress).playerClassAttack(
                                    _battleSimulation,
                                    _battleSimulation.monster3
                                );
                                _battleSimulation.monster3 = updatedMonster;
                            }
                        } else {
                            // Else single target attack
                            if (_battleSimulation.targetIndex == 0) {
                                (_battleSimulation, updatedMonster) = MowseDungeonPlayerClassAttackFacet(gs.diamondAddress).playerClassAttack(
                                    _battleSimulation,
                                    _battleSimulation.monster1
                                );
                                _battleSimulation.monster1 = updatedMonster;
                            } else if (_battleSimulation.targetIndex == 1) {
                                (_battleSimulation, updatedMonster) = MowseDungeonPlayerClassAttackFacet(gs.diamondAddress).playerClassAttack(
                                    _battleSimulation,
                                    _battleSimulation.monster2
                                );
                                _battleSimulation.monster2 = updatedMonster;
                            } else {
                                (_battleSimulation, updatedMonster) = MowseDungeonPlayerClassAttackFacet(gs.diamondAddress).playerClassAttack(
                                    _battleSimulation,
                                    _battleSimulation.monster3
                                );
                                _battleSimulation.monster3 = updatedMonster;
                            }
                        }
                    }
                    if (
                        _battleSimulation.player.currentHealth == 0 ||
                        (_battleSimulation.monster1.currentHealth == 0 &&
                            _battleSimulation.monster2.currentHealth == 0 &&
                            _battleSimulation.monster3.currentHealth == 0)
                    ) {
                        return _battleSimulation;
                    }
                } else if (_battleSimulation.actionType == MowseDungeonActionTypeEnum.STEAL) {
                    // Perform steal action
                    if (_battleSimulation.targetIndex == 0) {
                        _battleSimulation = _stealFromMonster(_battleSimulation, _battleSimulation.monster1);
                    } else if (_battleSimulation.targetIndex == 1) {
                        _battleSimulation = _stealFromMonster(_battleSimulation, _battleSimulation.monster2);
                    } else {
                        _battleSimulation = _stealFromMonster(_battleSimulation, _battleSimulation.monster3);
                    }
                } else if (_battleSimulation.actionType == MowseDungeonActionTypeEnum.DEFEND) {
                    // Perform defend action
                    _battleSimulation.player.statusEffects.bulk++;
                    // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BULK, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: 1}));
                    _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BULK, MowseDungeonBattleLogSourceEnum.PLAYER, 1);
                }
            }
            // Apply post-attack damage back to player (burn/bleed), tick down any status effects
            _battleSimulation = _calculatePostAttackPlayerHealth(_battleSimulation);
            if (_battleSimulation.player.currentHealth == 0) {
                return _battleSimulation;
            }
            // Companion attack
            if (_battleSimulation.targetIndex == 0) {
                _battleSimulation = MowseDungeonCompanionAttackFacet(gs.diamondAddress).performCompanionAction(
                    _battleSimulation,
                    _battleSimulation.monster1
                );
            } else if (_battleSimulation.targetIndex == 1) {
                _battleSimulation = MowseDungeonCompanionAttackFacet(gs.diamondAddress).performCompanionAction(
                    _battleSimulation,
                    _battleSimulation.monster2
                );
            } else {
                _battleSimulation = MowseDungeonCompanionAttackFacet(gs.diamondAddress).performCompanionAction(
                    _battleSimulation,
                    _battleSimulation.monster3
                );
            }
            return _battleSimulation;
        }
    
        function _calculatePreAttackPlayerAttack(
            MowseDungeonBattleSimulation memory _battleSimulation
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            // Calculate pre-attack stats for player
            if (_battleSimulation.player.trinkets.dagger > 0) {
                _battleSimulation.player.tempAttack =
                    _battleSimulation.player.tempAttack +
                    ((_battleSimulation.player.tempAttack * 10) / 100) *
                    _battleSimulation.player.trinkets.dagger;
            }
            if (_battleSimulation.player.statusEffects.strengthen > 0) {
                _battleSimulation.player.tempAttack = _battleSimulation.player.tempAttack * 2;
            }
            if (_battleSimulation.player.statusEffects.weaken > 0) {
                _battleSimulation.player.tempAttack = _battleSimulation.player.tempAttack / 2;
            }
            return _battleSimulation;
        }
    
        function _calculatePreAttackPlayerHealth(
            MowseDungeonBattleSimulation memory _battleSimulation
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            // Apply heals first
            if (_battleSimulation.player.statusEffects.healBlock == 0) {
                if (_battleSimulation.player.statusEffects.regen > 0) {
                    _battleSimulation.player.currentHealth =
                        _battleSimulation.player.currentHealth +
                        ((_battleSimulation.player.maxHealth * 5) / 100) *
                        _battleSimulation.player.statusEffects.regen;
                    // Make sure health doesn't exceed maxHealth
                    if (_battleSimulation.player.currentHealth > _battleSimulation.player.maxHealth) {
                        _battleSimulation.player.currentHealth = _battleSimulation.player.maxHealth;
                    }
                    _battleSimulation.achievementProgress.healthHealed +=
                        ((_battleSimulation.player.maxHealth * 5) / 100) *
                        _battleSimulation.player.statusEffects.regen;
                    _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_REGEN, MowseDungeonBattleLogSourceEnum.PLAYER, ((_battleSimulation.player.maxHealth * 5) / 100) * _battleSimulation.player.statusEffects.regen);
                    _battleSimulation.player.statusEffects.regen--;
                }
                if (_battleSimulation.player.trinkets.pendantOfLife > 0) {
                    _battleSimulation.player.currentHealth =
                        _battleSimulation.player.currentHealth +
                        (5 * _battleSimulation.player.trinkets.pendantOfLife);
                    // Make sure health doesn't exceed maxHealth
                    if (_battleSimulation.player.currentHealth > _battleSimulation.player.maxHealth) {
                        _battleSimulation.player.currentHealth = _battleSimulation.player.maxHealth;
                    }
                    _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.TRINKET_PENDANT_OF_LIFE, MowseDungeonBattleLogSourceEnum.PLAYER, (5 * _battleSimulation.player.trinkets.pendantOfLife));
                    _battleSimulation.achievementProgress.healthHealed += (5 * _battleSimulation.player.trinkets.pendantOfLife);
                }
            } else {
                _battleSimulation.player.statusEffects.healBlock--;
            }
            // Then apply damage
            if (_battleSimulation.player.statusEffects.invincible == 0) {
                // POISON
                if (_battleSimulation.player.statusEffects.poison > 0) {
                    if (_battleSimulation.player.statusEffects.sleep > 0) {
                        (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                            (5 * (_battleSimulation.player.statusEffects.poison) * 150) / 100,
                            _battleSimulation.player.currentHealth,
                            _battleSimulation.player.shield
                        );
                        // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.DAMAGE, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: (5 * (_battleSimulation.player.statusEffects.poison) * 150) / 100}));
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, MowseDungeonBattleLogSourceEnum.PLAYER, (5 * (_battleSimulation.player.statusEffects.poison) * 150) / 100);
                    } else {
                        (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                            5 * _battleSimulation.player.statusEffects.poison,
                            _battleSimulation.player.currentHealth,
                            _battleSimulation.player.shield
                        );
                        // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.DAMAGE, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: 5 * _battleSimulation.player.statusEffects.poison}));
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, MowseDungeonBattleLogSourceEnum.PLAYER, 5 * _battleSimulation.player.statusEffects.poison);
                    }
                    _battleSimulation.achievementProgress.damageTaken += (
                        _battleSimulation.player.statusEffects.sleep > 0
                            ? ((5 * (_battleSimulation.player.statusEffects.poison)) * 150) / 100
                            : 5 * (_battleSimulation.player.statusEffects.poison)
                    );
                    // wake up
                    if (_battleSimulation.player.statusEffects.sleep > 0) {
                        _battleSimulation.player.statusEffects.sleep = 0;
                    }
                    _battleSimulation.player = LibDungeon._checkPlayerRevive(_battleSimulation.player);
                }
    
                // CURSE deals 10% true damage
                if (_battleSimulation.player.statusEffects.curse > 0) {
                    if (_battleSimulation.player.statusEffects.sleep > 0) {
                        (_battleSimulation.player.currentHealth) = LibDungeon._damageHealth(
                            (((_battleSimulation.player.maxHealth * (9 + _battleSimulation.player.statusEffects.curse)) / 100) * 150) / 100,
                            _battleSimulation.player.currentHealth
                        );
                        // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.DAMAGE, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CURSE, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: (((_battleSimulation.player.maxHealth * (9 + _battleSimulation.player.statusEffects.curse)) / 100) * 150) / 100}));
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CURSE, MowseDungeonBattleLogSourceEnum.PLAYER, (((_battleSimulation.player.maxHealth * (9 + _battleSimulation.player.statusEffects.curse)) / 100) * 150) / 100);
                    } else {
                        (_battleSimulation.player.currentHealth) = LibDungeon._damageHealth(
                            (_battleSimulation.player.maxHealth * (9 + _battleSimulation.player.statusEffects.curse)) / 100,
                            _battleSimulation.player.currentHealth
                        );
                        // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.DAMAGE, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CURSE, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: (_battleSimulation.player.maxHealth * (9 + _battleSimulation.player.statusEffects.curse)) / 100}));
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CURSE, MowseDungeonBattleLogSourceEnum.PLAYER, (_battleSimulation.player.maxHealth * (9 + _battleSimulation.player.statusEffects.curse)) / 100);
                    }
    
                    _battleSimulation.achievementProgress.damageTaken += (
                        _battleSimulation.player.statusEffects.sleep > 0
                            ? (((_battleSimulation.player.maxHealth * (10 + _battleSimulation.player.statusEffects.curse)) / 100) * 150) / 100
                            : (_battleSimulation.player.maxHealth * (10 + _battleSimulation.player.statusEffects.curse)) / 100
                    );
                    if (_battleSimulation.player.statusEffects.sleep > 0) {
                        _battleSimulation.player.statusEffects.sleep = 0;
                    }
                    _battleSimulation.player = LibDungeon._checkPlayerRevive(_battleSimulation.player);
                }
            }
            return _battleSimulation;
        }
    
        function _calculatePostAttackPlayerHealth(
            MowseDungeonBattleSimulation memory _battleSimulation
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_battleSimulation.player.statusEffects.invincible == 0) {
                // BURN
                if (_battleSimulation.player.statusEffects.burn > 0) {
                    if (_battleSimulation.player.statusEffects.sleep > 0) {
                        (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                            (5 * (_battleSimulation.player.statusEffects.burn) * 150) / 100,
                            _battleSimulation.player.currentHealth,
                            _battleSimulation.player.shield
                        );
                        // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.DAMAGE, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BURN, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: (5 * (_battleSimulation.player.statusEffects.burn) * 150) / 100}));
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BURN, MowseDungeonBattleLogSourceEnum.PLAYER, (5 * (_battleSimulation.player.statusEffects.burn) * 150) / 100);
                    } else {
                        (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                            5 * _battleSimulation.player.statusEffects.burn,
                            _battleSimulation.player.currentHealth,
                            _battleSimulation.player.shield
                        );
                        // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.DAMAGE, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BURN, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: 5 * _battleSimulation.player.statusEffects.burn}));
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BURN, MowseDungeonBattleLogSourceEnum.PLAYER, 5 * _battleSimulation.player.statusEffects.burn);
                    }
    
                    _battleSimulation.achievementProgress.damageTaken += (
                        _battleSimulation.player.statusEffects.sleep > 0
                            ? ((5 * _battleSimulation.player.statusEffects.burn) * 150) / 100
                            : 5 * _battleSimulation.player.statusEffects.burn
                    );
    
                    if (_battleSimulation.player.statusEffects.sleep > 0) {
                        _battleSimulation.player.statusEffects.sleep = 0;
                    }
                    _battleSimulation.player = LibDungeon._checkPlayerRevive(_battleSimulation.player);
                }
    
                // BLEED
                if (_battleSimulation.player.statusEffects.bleed > 0) {
                    if (_battleSimulation.player.statusEffects.sleep > 0) {
                        (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                            ((((_battleSimulation.player.statusEffects.bleed + 5) * _battleSimulation.player.maxHealth) / 100) * 150) / 100,
                            _battleSimulation.player.currentHealth,
                            _battleSimulation.player.shield
                        );
                        // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.DAMAGE, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BLEED, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: ((((_battleSimulation.player.statusEffects.bleed + 5) * _battleSimulation.player.maxHealth) / 100) * 150) / 100}));
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BLEED, MowseDungeonBattleLogSourceEnum.PLAYER, ((((_battleSimulation.player.statusEffects.bleed + 5) * _battleSimulation.player.maxHealth) / 100) * 150) / 100);
                    } else {
                        (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = LibDungeon._damageShieldAndHealth(
                            ((_battleSimulation.player.statusEffects.bleed + 5) * _battleSimulation.player.maxHealth) / 100,
                            _battleSimulation.player.currentHealth,
                            _battleSimulation.player.shield
                        );
                        // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.DAMAGE, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BLEED, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: ((_battleSimulation.player.statusEffects.bleed + 5) * _battleSimulation.player.maxHealth) / 100}));
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BLEED, MowseDungeonBattleLogSourceEnum.PLAYER, ((_battleSimulation.player.statusEffects.bleed + 5) * _battleSimulation.player.maxHealth) / 100);
                    }
    
                    _battleSimulation.achievementProgress.damageTaken += (
                        _battleSimulation.player.statusEffects.sleep > 0
                            ? ((((_battleSimulation.player.statusEffects.bleed + 5) * _battleSimulation.player.maxHealth) / 100) * 150) / 100
                            : ((_battleSimulation.player.statusEffects.bleed + 5) * _battleSimulation.player.maxHealth) / 100
                    );
                    if (_battleSimulation.player.statusEffects.sleep > 0) {
                        _battleSimulation.player.statusEffects.sleep = 0;
                    }
                    _battleSimulation.player = LibDungeon._checkPlayerRevive(_battleSimulation.player);
                }
            }
            if (_battleSimulation.player.statusEffects.death > 0) {
                _battleSimulation.player.statusEffects.death--;
                if (_battleSimulation.player.statusEffects.death == 0) {
                    // _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: MowseDungeonBattleLogTypeEnum.DAMAGE, source: MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_DEATH, target: MowseDungeonBattleLogSourceEnum.PLAYER, amount: _battleSimulation.player.currentHealth}));
                    _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_DEATH, MowseDungeonBattleLogSourceEnum.PLAYER, _battleSimulation.player.currentHealth);
                    _battleSimulation.player.currentHealth = 0;
                }
            }
            return _battleSimulation;
        }
    
        function _calculatePlayerAccuracy(
            MowseDungeonBattleSimulation memory _battleSimulation
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_battleSimulation.player.statusEffects.blind > 0) {
                _battleSimulation.player.accuracy = _battleSimulation.player.accuracy - 50;
                _battleSimulation.player.statusEffects.blind--;
            }
            if (_battleSimulation.player.statusEffects.eagleEye > 0) {
                _battleSimulation.player.accuracy = _battleSimulation.player.accuracy + 50;
                _battleSimulation.player.statusEffects.eagleEye--;
            }
            if (_battleSimulation.targetIndex == 0) {
                if (_battleSimulation.monster1.statusEffects.dodge > 0) {
                    _battleSimulation.player.accuracy = _battleSimulation.player.accuracy - 30;
                    _battleSimulation.monster1.statusEffects.dodge--;
                }
            } else if (_battleSimulation.targetIndex == 1) {
                if (_battleSimulation.monster2.statusEffects.dodge > 0) {
                    _battleSimulation.player.accuracy = _battleSimulation.player.accuracy - 30;
                    _battleSimulation.monster2.statusEffects.dodge--;
                }
            } else {
                if (_battleSimulation.monster3.statusEffects.dodge > 0) {
                    _battleSimulation.player.accuracy = _battleSimulation.player.accuracy - 30;
                    _battleSimulation.monster3.statusEffects.dodge--;
                }
            }
            // Calculate the accuracy
            _battleSimulation.seed = LibDungeon.updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
            _battleSimulation.player.canHit = (_battleSimulation.seed % 100 < _battleSimulation.player.accuracy);
            return _battleSimulation;
        }
    
        function _stealFromMonster(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster
        ) internal view returns (MowseDungeonBattleSimulation memory) {
            // Only allow monster to be stolen successfully once
            if (_monster.hasBeenStolen || _monster.currentHealth == 0) {
                return _battleSimulation;
            }
    
            _battleSimulation.seed = LibDungeon.updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
            if (_battleSimulation.player.stats[STAT_LUCK] > 0) {
                console.log('steal', _battleSimulation.seed % 100);
                console.log(LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_LUCK]) / 5);
                if (_battleSimulation.seed % 100 < 40 + LibDungeon._int32ToUint256(_battleSimulation.player.stats[STAT_LUCK]) / 5) {
                    // get a random trinket
                    _battleSimulation.seed = LibDungeon.updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
                    uint256 trinketId = _battleSimulation.seed % gs.mowseDungeonTrinketDefaultsCount;
                    _battleSimulation = MowseDungeonTrinketFacet(gs.diamondAddress).stealTrinket(_battleSimulation, trinketId);
    
                    _monster.hasBeenStolen = true;
                    _battleSimulation.stolenTrinkets[_battleSimulation.stolenTrinketCount] = MowseDungeonTrinketEnum(trinketId);
                    _battleSimulation.stolenTrinketCount++;
                    _battleSimulation.isStealSuccessful = true;
                    _battleSimulation.achievementProgress.trinketsStolen++;
                    if (_battleSimulation.targetIndex == 0) {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_SUCCESS, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_1, trinketId);
                    } else if (_battleSimulation.targetIndex == 1) {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_SUCCESS, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_2, trinketId);
                    } else {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_SUCCESS, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_3, trinketId);
                    }
                } else {
                    _battleSimulation.isStealSuccessful = false;
                    if (_battleSimulation.targetIndex == 0) {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_FAILURE, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_1, 0);
                    } else if (_battleSimulation.targetIndex == 1) {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_FAILURE, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_2, 0);
                    } else {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_FAILURE, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_3, 0);
                    }
                }
            } else {
                    // DEV:
                    // if (_battleSimulation.seed % 100 > 20) {
                    if (_battleSimulation.seed % 100 < 40) {
                    _battleSimulation.seed = LibDungeon.updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
                    uint256 trinketId = _battleSimulation.seed % gs.mowseDungeonTrinketDefaultsCount;
                    _battleSimulation = MowseDungeonTrinketFacet(gs.diamondAddress).stealTrinket(_battleSimulation, trinketId);
    
                    _monster.hasBeenStolen = true;
                    console.log("stolen trinket", trinketId);
                    _battleSimulation.stolenTrinkets[_battleSimulation.stolenTrinketCount] = MowseDungeonTrinketEnum(trinketId);
                    _battleSimulation.stolenTrinketCount++;
                    _battleSimulation.isStealSuccessful = true;
                    _battleSimulation.achievementProgress.trinketsStolen++;
                    if (_battleSimulation.targetIndex == 0) {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_SUCCESS, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_1, trinketId);
                    } else if (_battleSimulation.targetIndex == 1) {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_SUCCESS, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_2, trinketId);
                    } else {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_SUCCESS, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_3, trinketId);
                    }
                } else {
                    console.log("failed to steal");
                    _battleSimulation.isStealSuccessful = false;
                    if (_battleSimulation.targetIndex == 0) {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_FAILURE, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_1, 0);
                    } else if (_battleSimulation.targetIndex == 1) {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_FAILURE, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_2, 0);
                    } else {
                        _battleSimulation = LibDungeon._addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.STEAL_FAILURE, MowseDungeonBattleLogSourceEnum.PLAYER, MowseDungeonBattleLogSourceEnum.MONSTER_3, 0);
                    }
                }
            }
            if (_battleSimulation.targetIndex == 0) {
                _battleSimulation.monster1 = _monster;
            } else if (_battleSimulation.targetIndex == 1) {
                _battleSimulation.monster2 = _monster;
            } else {
                _battleSimulation.monster3 = _monster;
            }
    
            return _battleSimulation;
        }
    }

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    import {
      GameStorage, 
      MowseDungeonBattleSimulation,
      MowseDungeonPlayer,
      MowseDungeonSimpleTrinkets,
      MowseDungeonTrinketEnum
      } from "../../libraries/LibStorage.sol";
    import {LibDiamond} from "../../libraries/LibDiamond.sol";
    import "../../libraries/Base64.sol";
    import "../../libraries/LibDungeon.sol";
    import {AccessControlFacet} from "../AccessControlFacet.sol";
    
    contract MowseDungeonTrinketFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        error MissingAdminRole(string);
    
        modifier onlyAdmin() {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            _;
        }
    
        function stealTrinket(
            MowseDungeonBattleSimulation memory _battleSimulation,
            uint256 _trinketId
        ) external pure returns (MowseDungeonBattleSimulation memory) {
            if (_trinketId == 0) {
                _battleSimulation.player.trinkets.dagger++;
            } else if (_trinketId == 1) {
                _battleSimulation.player.trinkets.slingshot++;
            } else if (_trinketId == 2) {
                _battleSimulation.player.trinkets.pendantOfLife++;
            } else if (_trinketId == 3) {
                _battleSimulation.player.trinkets.leech++;
            } else if (_trinketId == 4) {
                _battleSimulation.player.trinkets.poisonDab++;
            } else if (_trinketId == 5) {
                _battleSimulation.player.trinkets.kindle++;
            } else if (_trinketId == 6) {
                _battleSimulation.player.trinkets.bloodLetter++;
            } else if (_trinketId == 7) {
                _battleSimulation.player.trinkets.cursedRing++;
            } else if (_trinketId == 8) {
                _battleSimulation.player.trinkets.stunRod++;
            } else if (_trinketId == 9) {
                _battleSimulation.player.trinkets.silenceBell++;
            } else if (_trinketId == 10) {
                _battleSimulation.player.trinkets.strengthBelt++;
            } else if (_trinketId == 11) {
                _battleSimulation.player.trinkets.weakenedChain++;
            } else if (_trinketId == 12) {
                _battleSimulation.player.trinkets.emblemOfInitiative++;
            } else if (_trinketId == 13) {
                _battleSimulation.player.trinkets.goldRing++;
            }
    
            return _battleSimulation;
        }
        function toSimplePlayerTrinkets(uint256 tokenId) external view returns (MowseDungeonSimpleTrinkets memory) {
            MowseDungeonPlayer storage _player = gs.mowseDungeonPlayer[tokenId];
            return
                MowseDungeonSimpleTrinkets({
                    dagger: _player.trinkets[MowseDungeonTrinketEnum.DAGGER].level,
                    slingshot: _player.trinkets[MowseDungeonTrinketEnum.SLINGSHOT].level,
                    pendantOfLife: _player.trinkets[MowseDungeonTrinketEnum.PENDANT_OF_LIFE].level,
                    leech: _player.trinkets[MowseDungeonTrinketEnum.LEECH].level,
                    poisonDab: _player.trinkets[MowseDungeonTrinketEnum.POISON_DAB].level,
                    kindle: _player.trinkets[MowseDungeonTrinketEnum.KINDLE].level,
                    bloodLetter: _player.trinkets[MowseDungeonTrinketEnum.BLOOD_LETTER].level,
                    cursedRing: _player.trinkets[MowseDungeonTrinketEnum.CURSED_RING].level,
                    stunRod: _player.trinkets[MowseDungeonTrinketEnum.STUN_ROD].level,
                    silenceBell: _player.trinkets[MowseDungeonTrinketEnum.SILENCE_BELL].level,
                    strengthBelt: _player.trinkets[MowseDungeonTrinketEnum.STRENGTH_BELT].level,
                    weakenedChain: _player.trinkets[MowseDungeonTrinketEnum.WEAKENED_CHAIN].level,
                    emblemOfInitiative: _player.trinkets[MowseDungeonTrinketEnum.EMBLEM_OF_INITIATIVE].level,
                    goldRing: _player.trinkets[MowseDungeonTrinketEnum.GOLD_RING].level
                });
        }
        function updateTrinketStats(MowseDungeonTrinketEnum trinketId, uint256 health, uint256 shield, uint256 attack) external onlyAdmin {
            gs.mowseDungeonTrinketDefaults[trinketId].health = health;
            gs.mowseDungeonTrinketDefaults[trinketId].shield = shield;
            gs.mowseDungeonTrinketDefaults[trinketId].attack = attack;
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.8;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    
    import {Mowse, MOWSE_MINTER_ROLE, GameStorage, MOWSEBANK_CREATE2_SALT, SKILL_TYPE_NUM} from "../libraries/LibStorage.sol";
    
    import {MowseWearFacet} from "./MowseWearFacet.sol";
    import {MowseVisualizerFacet} from "./MowseVisualizerFacet.sol";
    import {AccessControlFacet} from "./AccessControlFacet.sol";
    import {GameStorageFacet} from "./GameStorageFacet.sol";
    import {MowseJobFacet} from "./MowseJobFacet.sol";
    
    import "../MowseGold.sol";
    import "../MowseBank.sol";
    import "./../libraries/LibStorage.sol";
    
    contract MowseFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        event MowseMinted(address indexed player, uint256 indexed tokenId);
    
        error MissingMowseMinterRole(string);
        error MissingMowseAdminRole(string);
        error ContractPaused(string);
        error MowseMustExist(uint256 tokenId);
        error MowseAlreadyUpdated(uint256 tokenId);
        error InvalidCaller(string);
        error MustOwnMowse();
        error InvalidSkillIndex(string);
    
        modifier notPaused() {
            if (gs.paused) revert ContractPaused("Contract is paused");
            _;
        }
        modifier mowseMustExist(uint256 _tokenId) {
            if (gs.mowses[_tokenId].tokenId == 0) revert MowseMustExist(_tokenId);
            _;
        }
    
        // Only allow Mowse Minting through MowseAvatar contract
        function mintMowse() external notPaused {
            if (!AccessControlFacet(address(this)).hasMowseMinterRole(msg.sender)) revert MissingMowseMinterRole("Must have Mowse Minter role");
            _mint();
        }
    
        function freeMint() external {
            if (!AccessControlFacet(address(this)).hasMowseAdminRole(msg.sender)) revert MissingMowseAdminRole("Must have Mowse Admin role");
            _mint();
        }
    
        function getSkillLevelBySkillIndex(uint256 mowseId, uint256 skillIndex) external view mowseMustExist(mowseId) returns (int32) {
            if (skillIndex >= SKILL_TYPE_NUM) revert InvalidSkillIndex("Invalid skill index");
    
            return gs.mowses[mowseId].skillLevel[skillIndex] + gs.mowses[mowseId].skillLevelBoosts[skillIndex];
        }
    
        function getMowseData(
            uint256 mowseId
        )
            external
            view
            mowseMustExist(mowseId)
            returns (
                uint16 generation,
                uint8 primarySkillType,
                uint8 secondarySkillType,
                uint8 profession,
                uint16 lifeLevel,
                uint256 lifeExperiencePoints,
                uint256 experienceCap
            )
        {
            Mowse memory mowse = gs.mowses[mowseId];
            uint256 experienceCapByLevel = MowseJobFacet(gs.diamondAddress).experienceCapByLevel(mowse.lifeLevel);
            return (
                mowse.generation,
                mowse.primarySkillType,
                mowse.secondarySkillType,
                mowse.profession,
                mowse.lifeLevel,
                mowse.lifeExperiencePoints,
                experienceCapByLevel
            );
        }
    
        function mowseTokenURI(uint256 mowseId) external view mowseMustExist(mowseId) returns (string memory) {
            return MowseVisualizerFacet(gs.diamondAddress).getMowseTokenURI(mowseId);
        }
    
        function getOwnerAddress(uint256 mowseId) public view mowseMustExist(mowseId) returns (address) {
            return gs.mowses[mowseId].ownerAddress;
        }
    
        function getMowseBankWalletImplementation() external view returns (address) {
            return gs.mowseBankWalletImplementation;
        }
    
        function getMowseBankBalances(address owner) external view returns (uint256) {
            return gs.mowseBankBalances[owner];
        }
    
        function getMowseBankTokenIdForWallet(address owner) external view returns (uint256) {
            return gs.mowseBankTokenIdForWallet[owner];
        }
    
        function setMowseBankBalances(address owner, uint256 amount) external {
            if (gs.mowseBankContractAddress != msg.sender) revert InvalidCaller("Can only be called from MowseBank");
            gs.mowseBankBalances[owner] = amount;
        }
    
        function setMowseBankTokenIdForWallet(address owner, uint256 mowseId) external {
            if (gs.mowseBankContractAddress != msg.sender) revert InvalidCaller("Can only be called from MowseBank");
            gs.mowseBankTokenIdForWallet[owner] = mowseId;
        }
    
        function removeMowseBankTokenIdForWallet(address owner) external {
            if (gs.mowseBankContractAddress != msg.sender) revert InvalidCaller("Can only be called from MowseBank");
            delete gs.mowseBankTokenIdForWallet[owner];
        }
    
        function changeOwnerAddress(uint256 mowseId, address to) external {
            if (gs.mowseAvatarContractAddress != msg.sender) revert InvalidCaller("Can only be called from MowseAvatar");
            gs.mowses[mowseId].ownerAddress = to;
        }
    
        function setMainMowse(uint256 mowseId) external mowseMustExist(mowseId) {
            if (msg.sender != getOwnerAddress(mowseId)) revert MustOwnMowse();
            gs.mainMowse[msg.sender] = mowseId;
        }
    
        function unsetMainMowse(address user) external {
            if (gs.mowseAvatarContractAddress != msg.sender) revert InvalidCaller("Can only be called from MowseAvatar");
            delete gs.mainMowse[user];
        }
    
        function getMainMowse(address user) external view returns (uint256) {
            return gs.mainMowse[user];
        }
    
        // Checks if an address is a Mowse address
        function isMowse(address _address) external view returns (bool) {
            return gs.isMowse[_address];
        }
    
        function _mint() internal {
            console.log("mint a mowse", gs.mowseTokenIdCounter);
            _setupMowse(gs.mowseTokenIdCounter, 1);
            gs.mowses[gs.mowseTokenIdCounter].tokenId = gs.mowseTokenIdCounter; // Flag for a mowse was minted
            // gs.mowses[gs.mowseTokenIdCounter].mowsewearClaimed = true; // Minted Mowse always true
            gs.mowses[gs.mowseTokenIdCounter].lifeLevel = 1;
            gs.mowseTokenIdCounter++;
        }
    
        // Set up Mowse with individual MowseWear assets and other randomly generated stats
        function _setupMowse(uint256 mowseId, uint16 generation) internal {
            // Set individual traits as base
            gs.mowses[mowseId].baseBackgroundColor = gs.mowseWearDictionary[0][0];
            gs.mowses[mowseId].baseSkinColor = gs.mowseWearDictionary[2][0];
            gs.mowses[mowseId].baseEarType = gs.mowseWearDictionary[3][0];
            gs.mowses[mowseId].baseBodyType = gs.mowseWearDictionary[5][0];
            gs.mowses[mowseId].baseMouth = gs.mowseWearDictionary[6][0];
            gs.mowses[mowseId].baseEyeType = gs.mowseWearDictionary[8][0];
    
            // Set generation
            gs.mowses[mowseId].generation = generation;
    
            // Assign initial proficiency
            uint256 _randomness = _getRandomSeed(mowseId);
            uint256 randomness = _randomness;
            gs.mowses[mowseId].primarySkillType = uint8(randomness % SKILL_TYPE_NUM);
            randomness >>= 2;
            if (randomness == 0) {
                randomness = _getRandomSeed(mowseId);
            }
            gs.mowses[mowseId].secondarySkillType = uint8(randomness % SKILL_TYPE_NUM);
    
            MowseBank mowsebank = MowseBank(gs.mowseBankContractAddress);
    
            gs.isMowse[mowsebank.getWalletAddress(mowseId)] = true;
        }
    
        function _getRandomSeed(uint256 mowseId) internal returns (uint256 randomness) {
            uint256 _randomness = uint256(
                keccak256(abi.encodePacked(gs.mowsePrngSeed, gs.mowses[mowseId].prngNonce++, block.timestamp, msg.sender, blockhash(block.number - 1)))
            );
            return _randomness;
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    
    import {GameStorage, SKILL_TYPE_NUM, MowseSkillTypes} from "../libraries/LibStorage.sol";
    
    import {AccessControlFacet} from "./AccessControlFacet.sol";
    import {RandomFortuneFacet} from "./RandomFortuneFacet.sol";
    import {WithdrawalFacet} from "./WithdrawalFacet.sol";
    
    import "../MowseAvatar.sol";
    import "../MowseGold.sol";
    import "../MowseBank.sol";
    import "../MowseLootbox.sol";
    import "./../libraries/LibStorage.sol";
    
    contract MowseJobFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        event MowseLevelUp(uint256 indexed mowseId, int32[SKILL_TYPE_NUM] statIncreases);
    
        error MissingAdminRole(string);
        error MissingMowseJobAdminRole(string);
        error ContractPaused(string);
        error MustOwnMowse(uint256);
        error MowseMustExist(uint256);
        error NotEnoughExperience(string);
        error InvalidLevelUpPointIndex();
        error LevelUpPaymentNotSuccessful(uint256);
    
        modifier notPaused() {
            if (gs.paused) revert ContractPaused("Contract is paused");
            _;
        }
        modifier mowseExists(uint256 tokenId) {
            MowseAvatar mowse = MowseAvatar(gs.mowseAvatarContractAddress);
            if (!mowse.exists(tokenId)) revert MowseMustExist(tokenId);
            _;
        }
        modifier hasAdminRole(address caller) {
            if (!AccessControlFacet(address(this)).hasMowseJobAdminRole(caller)) revert MissingMowseJobAdminRole("Must have MowseJob Admin role");
            _;
        }
        modifier ownsMowse(uint256 tokenId, address caller) {
            MowseAvatar mowse = MowseAvatar(gs.mowseAvatarContractAddress);
            if (caller != mowse.ownerOf(tokenId)) revert MustOwnMowse(tokenId);
            _;
        }
    
        // Add Life Experience points. Spend Life Experience points to level up and add points to Skill Levels
        function grantLifeExperience(uint256 mowseId, uint256 experiencePoints) external hasAdminRole(msg.sender) mowseExists(mowseId) notPaused {
            uint256 expCap = experienceCapByLevel(gs.mowses[mowseId].lifeLevel);
            uint256 mowseXP = gs.mowses[mowseId].lifeExperiencePoints + experiencePoints;
            if (mowseXP > expCap) {
                // Do nothing. Mowse has already reached the xp cap
                gs.mowses[mowseId].lifeExperiencePoints = expCap;
            } else {
                gs.mowses[mowseId].lifeExperiencePoints = mowseXP;
            }
        }
    
        function _getRandomSeed(uint256 mowseId) internal returns (uint256 randomness, uint256 randomSeed) {
            uint256 _randomness = uint256(
                keccak256(abi.encodePacked(gs.mowseJobPrngSeed, gs.mowses[mowseId].prngNonce++, block.timestamp, msg.sender, blockhash(block.number - 1)))
            );
            uint256 _randomSeed = _randomness % 4294967295;
            return (_randomness, _randomSeed);
        }
    
        function getCurrentExperience(uint256 mowseId) external view mowseExists(mowseId) returns (uint256) {
            return gs.mowses[mowseId].lifeExperiencePoints;
        }
    
        function canLevelUp(uint256 mowseId) external view mowseExists(mowseId) returns (bool) {
            uint256 expNeededToLevelUp = experienceCapByLevel(gs.mowses[mowseId].lifeLevel);
            // lifeExperiencePoints should not ever be greater than experienceCap but check anyways
            return gs.mowses[mowseId].lifeExperiencePoints >= expNeededToLevelUp;
        }
    
        function levelUp(
            uint256 mowseId,
            uint256 firstPoint,
            uint256 secondPoint,
            uint256 thirdPoint,
            uint256 fourthPoint
        ) external ownsMowse(mowseId, msg.sender) mowseExists(mowseId) notPaused {
            // SKILL_TYPE_NUM ranges from 0-6
            if (firstPoint > 6 || secondPoint > 6 || thirdPoint > 6 || fourthPoint > 6) revert InvalidLevelUpPointIndex();
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
            uint256 costToLevelUp = checkLevelUpCost(mowseId);
            if (!mowsegold.transferFrom(msg.sender, address(WithdrawalFacet(gs.diamondAddress)), costToLevelUp))
                revert LevelUpPaymentNotSuccessful(costToLevelUp);
            _checkLevelUpXP(mowseId);
            _levelUp(mowseId, firstPoint, secondPoint, thirdPoint, fourthPoint);
            // On LevelUp, give a random lootbox
            _levelUpFreeMint(mowseId);
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        function checkLevelUpCost(uint256 mowseId) public view returns (uint256) {
            uint16 lifeLevel = gs.mowses[mowseId].lifeLevel;
            uint256 costToLevelUp = (uint256(lifeLevel / 5) * 500 + 100 * uint256(lifeLevel)) * 1e18;
            return costToLevelUp;
        }
    
        function _levelUpFreeMint(uint256 mowseId) internal {
            MowseBank mowsebank = MowseBank(gs.mowseBankContractAddress);
            MowseLootbox mowselootbox = MowseLootbox(gs.mowseLootboxContractAddress);
            mowselootbox.freeMint(mowsebank.getWalletAddress(mowseId), 12);
        }
    
        function _checkLevelUpXP(uint256 mowseId) internal view {
            uint256 expNeededToLevelUp = experienceCapByLevel(gs.mowses[mowseId].lifeLevel);
            if (gs.mowses[mowseId].lifeExperiencePoints < expNeededToLevelUp) revert NotEnoughExperience("Insufficient Experience");
        }
    
        function _levelUp(uint256 mowseId, uint256 firstPoint, uint256 secondPoint, uint256 thirdPoint, uint256 fourthPoint) internal {
            uint16 lifeLevel = gs.mowses[mowseId].lifeLevel;
            (uint256 _randomness, ) = _getRandomSeed(mowseId);
            uint256 randomness = _randomness;
            uint256 bitCounter = 0;
    
            int32[SKILL_TYPE_NUM] memory statIncreases;
            // Every 5 levels, get extra bonus points randomly assigned
            if (lifeLevel % 5 == 0) {
                for (uint32 i = 0; i < ((lifeLevel / 5) * 2) + 3; i++) {
                    randomness >>= 2;
                    if (randomness == 0) {
                        randomness = _randomness + _randomness * bitCounter;
                        bitCounter++;
                    }
                    uint256 bonusIndex = randomness % SKILL_TYPE_NUM;
                    gs.mowses[mowseId].skillLevel[bonusIndex]++;
                    statIncreases[bonusIndex]++;
                }
            }
            // Always give 3 stat points randomly assigned
            for (uint32 i = 0; i < 3; i++) {
                randomness >>= 2;
                if (randomness == 0) {
                    randomness = _randomness + _randomness * bitCounter;
                    bitCounter++;
                }
                uint256 bonusIndex = randomness % SKILL_TYPE_NUM;
                gs.mowses[mowseId].skillLevel[bonusIndex]++;
                statIncreases[bonusIndex]++;
            }
            // User input stat points
            gs.mowses[mowseId].skillLevel[firstPoint]++;
            gs.mowses[mowseId].skillLevel[secondPoint]++;
            gs.mowses[mowseId].skillLevel[thirdPoint]++;
            gs.mowses[mowseId].skillLevel[fourthPoint]++;
            statIncreases[firstPoint]++;
            statIncreases[secondPoint]++;
            statIncreases[thirdPoint]++;
            statIncreases[fourthPoint]++;
    
            // Reset Life Experience Points and increase level by 1
            gs.mowses[mowseId].lifeLevel++;
            gs.mowses[mowseId].lifeExperiencePoints = 0;
    
            emit MowseLevelUp(mowseId, statIncreases);
        }
    
        // Calculates how much experience is capped per level
        function experienceCapByLevel(uint16 level) public pure returns (uint256) {
            // EXPERIENCE FORMULA
            return (uint((level * 40)) * uint(((level / 5 + 1) ** 2)) * 15) / 10;
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    // import "@openzeppelin/contracts/utils/Context.sol";
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    import "../libraries/SlothVerifiableDelay.sol";
    
    // import {GameStorage} from "../libraries/LibStorage.sol";
    
    import {MowseWearFacet} from "./MowseWearFacet.sol";
    import {AccessControlFacet} from "./AccessControlFacet.sol";
    import {GameStorageFacet} from "./GameStorageFacet.sol";
    import {MowseVisualizerFacet} from "./MowseVisualizerFacet.sol";
    import {WithdrawalFacet} from "./WithdrawalFacet.sol";
    import {RandomFortuneFacet} from "./RandomFortuneFacet.sol";
    
    import "../MowseLootbox.sol";
    import "../MowseGold.sol";
    import "./../libraries/LibStorage.sol";
    
    contract MowseLootboxFacet {
        using Strings for uint256;
        using Strings for uint64;
        using Strings for uint16;
        using Strings for address;
        GameStorage internal gs;
    
        event MowseLootboxPurchased(address indexed player, uint256 indexed tokenId);
        event MowseLootboxOpened(address indexed player, uint256 indexed tokenId, string traitName, uint8 itemRarity);
    
        error ContractPaused(string);
        error MissingAdminRole(string);
        error MissingMinterRole(string);
        error NotEnoughFunds(uint256 userBalance);
        error LootPoolNotActive(uint64 lootPoolIndex, bool isActive);
        error LootPoolEmpty(uint64 lootPoolIndex);
        error LootboxPaymentUnsuccessful(uint64 lootPoolIndex);
        error LootboxMustHaveName(string);
        error LootboxMustHaveImage(uint64 lootPoolIndex);
        error LootboxItemMustExist(uint64 lootPoolIndex);
        error LootboxMustBeOwned(uint256 lootboxId);
        error LootboxMustExist(uint256 lootboxId);
        error MowseWearNotFromLootbox(uint256 mowseWearTokenId);
        error InvalidCaller(string);
        error LootpoolOutOfRange(string);
    
        modifier notPaused() {
            if (gs.paused) revert ContractPaused("Contract is paused");
            _;
        }
    
        function getMowseLootboxCount() external view returns (uint64) {
            return gs.mowseLootboxIndexCount;
        }
    
        function getMowseLootboxName(uint64 index) external view returns (string memory) {
            return gs.mowselootboxes[index].name;
        }
        function getMowseLootboxLootpoolCount(uint64 index) external view returns (uint16) {
            return gs.mowselootboxes[index].itemCount;
        }
    
        function getMowseLootboxLootpoolDataFromRange(uint64 index, uint16 from, uint16 to) external view returns (SimpleMowseLootboxPoolItem[] memory lootpool) {
            if (from > to) revert LootpoolOutOfRange("Invalid range");
            if (to >= gs.mowselootboxes[index].itemCount) revert LootpoolOutOfRange("Range cannot exceed item count");
            if ((to - from + 1) > 10) revert LootpoolOutOfRange("Range too large");
    
            MowseLootboxPool storage lootboxData = gs.mowselootboxes[index];
            uint16 count = to - from + 1;
            SimpleMowseLootboxPoolItem[] memory lootPool = new SimpleMowseLootboxPoolItem[](count);
            for (uint16 i = 0; i < count; i++) {
                lootPool[i] = MowseVisualizerFacet(gs.diamondAddress).getMowseWearByDictionaryIndex(lootboxData.lootPool[from + i], true);
            }
            return lootPool;
        }
    
        function getMowseLootboxData(uint64 index, bool showImage) external view returns (MowseLootboxPoolData memory lootboxPool) {
            MowseLootboxPool storage lootboxData = gs.mowselootboxes[index];
            SimpleMowseLootboxPoolItem[] memory lootPool = new SimpleMowseLootboxPoolItem[](lootboxData.itemCount);
            if (index == 12) {
                return MowseLootboxPoolData({
                    name: lootboxData.name,
                    price: lootboxData.price,
                    active: lootboxData.active,
                    image: lootboxData.image,
                    lootPool: lootPool,
                    itemCount: lootboxData.itemCount,
                    dateAdded: lootboxData.dateAdded,
                    index: index
                });
            }
            if (lootboxData.itemCount < 20) {
                for (uint16 i = 0; i < lootboxData.itemCount; i++) {
                    lootPool[i] = MowseVisualizerFacet(gs.diamondAddress).getMowseWearByDictionaryIndex(lootboxData.lootPool[i], showImage);
                }
            }
            return
                MowseLootboxPoolData({
                    name: lootboxData.name,
                    price: lootboxData.price,
                    active: lootboxData.active,
                    image: lootboxData.image,
                    lootPool: lootPool,
                    itemCount: lootboxData.itemCount,
                    dateAdded: lootboxData.dateAdded,
                    index: index
                });
        }
        function getMowseLootboxDataWithoutLootPool(uint64 index) external view returns (MowseLootboxPoolData memory lootboxPool) {
            MowseLootboxPool storage lootboxData = gs.mowselootboxes[index];
            SimpleMowseLootboxPoolItem[] memory lootPool = new SimpleMowseLootboxPoolItem[](lootboxData.itemCount);
            
            return
                MowseLootboxPoolData({
                    name: lootboxData.name,
                    price: lootboxData.price,
                    active: lootboxData.active,
                    image: lootboxData.image,
                    lootPool: lootPool,
                    itemCount: lootboxData.itemCount,
                    dateAdded: lootboxData.dateAdded,
                    index: index
                });
        }
    
        // Should only be called through MowseLootbox.sol
        function openMowseLootbox(uint256 tokenId, address caller) external notPaused {
            MowseLootbox mowselootbox = MowseLootbox(gs.mowseLootboxContractAddress);
            if (msg.sender != gs.mowseLootboxContractAddress) revert InvalidCaller("Invalid Caller");
            uint64 index = gs.mowseLootboxIndexByTokenId[tokenId];
            if (!gs.mowselootboxes[index].active) revert LootPoolNotActive(index, gs.mowselootboxes[index].active);
            if (gs.mowselootboxes[index].itemCount == 0) revert LootPoolEmpty(index);
            if (!mowselootbox.exists(tokenId)) revert LootboxMustExist(tokenId);
            if (mowselootbox.ownerOf(tokenId) != caller) revert LootboxMustBeOwned(tokenId);
            
            uint256 currentMowseWearTokenId = gs.mowseWearTokenIdCounter;
            
            randomlyMintLootboxItem(index, caller);
    
            emit MowseLootboxOpened(
                caller,
                currentMowseWearTokenId,
                gs.mowsewears[currentMowseWearTokenId].metadata.traitName,
                gs.mowsewears[currentMowseWearTokenId].bonuses.itemRarity
            );
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(caller);
        }
    
        // Should only be called through MowseLootbox.sol
        function purchaseMowseLootbox(uint64 index, address caller) external notPaused {
            if (msg.sender != gs.mowseLootboxContractAddress) revert InvalidCaller("Invalid Caller");
            uint256 lootboxPrice = gs.mowselootboxes[index].price;
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
    
            if (mowsegold.balanceOf(caller) <= lootboxPrice) revert NotEnoughFunds(mowsegold.balanceOf(caller));
            if (!mowsegold.transferFrom(caller, address(WithdrawalFacet(gs.diamondAddress)), lootboxPrice)) revert LootboxPaymentUnsuccessful(index);
            mintMowseLootbox(index, caller);
            
            RandomFortuneFacet(gs.diamondAddress).randomFortune(caller);
        }
    
        // Should only be called through MowseLootbox.sol
        function freeMowseLootbox(uint64 index, address caller) external notPaused {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
    
            mintMowseLootbox(index, caller);
        }
    
        // StitchMowseLootbox is used by other Facets to mint a new MowseWear
        function stitchMowseLootbox(address sender) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
    
            // Randomly stitch something from index12 lootpool (all mowsewear)
            randomlyMintLootboxItem(12, sender);
        }
    
        function mintMowseLootbox(uint64 index, address caller) internal {
            if (!gs.mowselootboxes[index].active) revert LootPoolNotActive(index, gs.mowselootboxes[index].active);
            if (gs.mowselootboxes[index].itemCount == 0) revert LootPoolEmpty(index);
            if (!AccessControlFacet(gs.diamondAddress).hasMowseLootboxMinterRole(msg.sender)) revert MissingMinterRole("Must have Minter Role");
    
            emit MowseLootboxPurchased(caller, gs.mowseLootboxTokenIdCounter);
    
            console.log("Mint This Counter");
            console.log(gs.mowseLootboxTokenIdCounter);
            gs.mowseLootboxIndexByTokenId[gs.mowseLootboxTokenIdCounter] = index;
    
            // first get vdf seed
            uint256 vdfSeed = uint256(keccak256(abi.encodePacked(caller, gs.mowseLootboxNonce++, block.timestamp, blockhash(block.number - 1))));
            uint256 currentMowseLootboxTokenId = gs.mowseLootboxTokenIdCounter;
            console.log("randomly lootbox seed");
            console.log(currentMowseLootboxTokenId);
            gs.mowseLootboxTokenIdToSeed[currentMowseLootboxTokenId] = vdfSeed;
    
            gs.mowseLootboxTokenIdCounter++;
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(caller);
        }
    
        function randomlyMintLootboxItem(uint64 index, address sender) internal {
            // first get vdf seed
            // uint256 vdfSeed = uint256(keccak256(abi.encodePacked(sender, gs.mowseLootboxNonce++, block.timestamp, blockhash(block.number - 1))));
            // uint256 currentMowseWearTokenId = gs.mowseWearTokenIdCounter;
            // console.log('randomly mint lootbox item');
            // console.log(currentMowseWearTokenId);
            // gs.mowseLootboxTokenIdToSeed[currentMowseWearTokenId] = vdfSeed;
            // mint using this seed
            uint16 randomLootPoolIndex = uint16(gs.mowseLootboxTokenIdToSeed[gs.mowseLootboxTokenIdCounter] % gs.mowselootboxes[index].itemCount);
            uint256 dictionaryIndex = gs.mowselootboxes[index].lootPool[randomLootPoolIndex];
            uint8 traitType = gs.mowseWearDictionaryByDictionaryIndex[dictionaryIndex].traitType;
            uint16 traitIndex = gs.mowseWearDictionaryByDictionaryIndex[dictionaryIndex].traitIndex;
    
            MowseWearFacet(gs.diamondAddress).mintMowseWear(sender, traitType, traitIndex, 0);
        }
    
        // index is the loot pool index; [0-11] are trait type specific, [12] is general pool, 13+ are any other specific pools
        function updateMowseLootboxItem(uint64 index, uint256 dictionaryIndex, bool justAdding, uint16 poolIndex) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
    
            // if justAdding = true, ignore poolIndex
            if (justAdding) {
                gs.mowselootboxes[index].lootPool[gs.mowselootboxes[index].itemCount] = dictionaryIndex;
                gs.mowselootboxes[index].itemCount++;
            } else {
                if (poolIndex >= gs.mowselootboxes[index].itemCount) revert LootboxItemMustExist(index);
    
                // only update traitType and traitIndex
                gs.mowselootboxes[index].lootPool[poolIndex] = dictionaryIndex;
            }
        }
    
        function mowseLootboxTokenURI(uint256 tokenId) external view returns (string memory) {
            return MowseVisualizerFacet(gs.diamondAddress).getMowseLootboxTokenURI(tokenId, true);
        }
    
        function _prove(uint256 proof, uint256 seed) internal view returns (bool) {
            return SlothVerifiableDelay.verify(proof, seed, gs.mowseLootboxPrime, gs.mowseLootboxIterations);
        }
    
        function getMowseLootboxProofById(uint256 tokenId) external view returns (uint256) {
            if (gs.mowseLootboxTokenIdToSeed[tokenId] == 0) revert MowseWearNotFromLootbox(tokenId);
    
            return SlothVerifiableDelay.compute(gs.mowseLootboxTokenIdToSeed[tokenId], gs.mowseLootboxPrime, gs.mowseLootboxIterations);
        }
    
        function proveMowseLootboxById(uint256 tokenId, uint256 proof) external view returns (bool) {
            if (gs.mowseLootboxTokenIdToSeed[tokenId] == 0) revert MowseWearNotFromLootbox(tokenId);
    
            if (_prove(proof, gs.mowseLootboxTokenIdToSeed[tokenId])) {
                return true;
            } else {
                return false;
            }
        }
    
        function resetMowseLootbox(uint64 index) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
    
            // de-active lootbox during reset
            gs.mowselootboxes[index].active = false;
            uint16 currentItemCount = gs.mowselootboxes[index].itemCount;
            for (uint16 i = 0; i < currentItemCount; i++) {
                delete gs.mowselootboxes[index].lootPool[i];
            }
    
            gs.mowselootboxes[index].itemCount = 0;
        }
    
        function setMowseLootboxActive(uint64 index, bool active) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
    
            gs.mowselootboxes[index].active = active;
        }
    
        function setMowseLootboxPrice(uint64 index, uint256 price) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
    
            gs.mowselootboxes[index].price = price;
        }
    
        function setMowseLootboxName(uint64 index, string memory name) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            if (bytes(name).length == 0) revert LootboxMustHaveName(name);
    
            gs.mowselootboxes[index].name = name;
        }
    
        function setMowseLootboxImage(uint64 index, uint16 width, uint16 height, string memory image) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            if (bytes(image).length == 0) revert LootboxMustHaveImage(index);
    
            gs.mowselootboxes[index].width = width;
            gs.mowselootboxes[index].height = height;
            gs.mowselootboxes[index].image = image;
        }
    
        // Add lootboxes to the game
        function setMowseLootboxInit(
            string memory name,
            uint256 price,
            bool active,
            uint16 width,
            uint16 height,
            string memory image
        ) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            if (bytes(name).length == 0) revert LootboxMustHaveName(name);
            // if (bytes(image).length == 0) revert LootboxMustHaveImage(index);
    
            gs.mowselootboxes[gs.mowseLootboxIndexCount].active = active;
            gs.mowselootboxes[gs.mowseLootboxIndexCount].price = price;
            gs.mowselootboxes[gs.mowseLootboxIndexCount].name = name;
            gs.mowselootboxes[gs.mowseLootboxIndexCount].image = image;
            gs.mowselootboxes[gs.mowseLootboxIndexCount].dateAdded = block.timestamp;
            gs.mowselootboxes[gs.mowseLootboxIndexCount].width = width;
            gs.mowselootboxes[gs.mowseLootboxIndexCount].height = height;
    
            gs.mowseLootboxIndexCount++;
        }
        function updateMowseLootboxImage(uint64 index, string calldata image) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            if (bytes(image).length == 0) revert LootboxMustHaveImage(index);
    
            gs.mowselootboxes[index].image = image;
        }
        function concatMowseLootboxImage(uint64 index, string calldata image) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            gs.mowselootboxes[index].image = string(
                abi.encodePacked(gs.mowselootboxes[index].image, image)
            );
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    import "../libraries/Base64.sol";
    import "../libraries/LibUtils.sol";
    import {WithStorage, GameStorage, MowseWearMetadata, MowseWear, MowseWearDimensions, SKILL_TYPE_NUM, EQUIPPED_WEARABLE_SLOTS} from "../libraries/LibStorage.sol";
    
    import {MowseFacet} from "./MowseFacet.sol";
    import {MowseWearFacet} from "./MowseWearFacet.sol";
    
    import "../MowseLootbox.sol";
    import "../MowseAvatar.sol";
    import "../MowseGold.sol";
    import "../MowseBank.sol";
    import "./../libraries/LibStorage.sol";
    
    contract MowseVisualizerFacet is WithStorage {
        using Strings for uint256;
        using Strings for uint64;
        using Strings for uint16;
        using Strings for uint8;
        using Strings for int16;
        using Strings for int32;
        GameStorage internal gs;
    
        error MowseWearMustExist(uint256 mowseWearId);
        error MowseLootboxMustExist(uint256 mowseLootboxId);
    
        function getMowseTokenURI(uint256 mowseId) public view returns (string memory) {
            return
                string(
                    abi.encodePacked(
                        "data:application/json;base64,",
                        Base64.encode(
                            abi.encodePacked(
                                "{",
                                _getNameJSON(mowseId),
                                _getGenerationJSON(mowseId, ","),
                                _getStatsJSON(mowseId, ","),
                                _getBaseAttributesJSON(","),
                                _getWearableAttributesJSON(mowseId, ","),
                                _getMowseImageJSON(mowseId, ","),
                                "}"
                            )
                        )
                    )
                );
        }
    
        function getMowseWearTokenURI(uint256 mowseWearTokenId) public view returns (string memory) {
            return
                string(
                    abi.encodePacked(
                        "data:application/json;base64,",
                        Base64.encode(
                            abi.encodePacked(
                                '{"name": "',
                                gs.mowsewears[mowseWearTokenId].metadata.traitName,
                                '", "description": "On chain tradeable assets to customize your Mowse.", "attributes":',
                                _getMowseWearAttributeJSONByTokenId(mowseWearTokenId),
                                ', "image":"',
                                getMowseWearImage(mowseWearTokenId),
                                '"}'
                            )
                        )
                    )
                );
        }
    
        function getMowseLootboxTokenURI(uint256 mowseLootboxTokenId, bool showLootPool) public view returns (string memory) {
            uint64 lootboxIndex = gs.mowseLootboxIndexByTokenId[mowseLootboxTokenId];
            return
                string(
                    abi.encodePacked(
                        "data:application/json;base64,",
                        Base64.encode(
                            abi.encodePacked(
                                _mowseLootboxMetadata(mowseLootboxTokenId),
                                ',"description": "On chain tradeable lootbox that contains MowseWear assets to customize your Mowse.", "lootPool":',
                                showLootPool == true ? _getMowseLootboxJSON(mowseLootboxTokenId) : "[]",
                                ', "image":"',
                                getMowseLootboxImageJSON(lootboxIndex),
                                '"}'
                            )
                        )
                    )
                );
        }
    
        function _mowseLootboxMetadata(uint256 mowseLootboxTokenId) internal view returns (string memory) {
            uint64 lootboxIndex = gs.mowseLootboxIndexByTokenId[mowseLootboxTokenId];
            return
                string(
                    abi.encodePacked(
                        '{"name": "',
                        gs.mowselootboxes[lootboxIndex].name,
                        '", "id":',
                        mowseLootboxTokenId.toString(),
                        ', "itemCount":',
                        gs.mowselootboxes[lootboxIndex].itemCount.toString(),
                        ', "dateAdded":',
                        gs.mowselootboxes[lootboxIndex].dateAdded.toString(),
                        ', "index": ',
                        lootboxIndex.toString()
                    )
                );
        }
    
        function getMowseWearImageFromDictionary(uint8 traitType, uint16 traitIndex) external view returns (string memory) {
            return
                string(
                    abi.encodePacked(
                        "data:image/svg+xml;base64,",
                        Base64.encode(
                            abi.encodePacked(
                                '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512"><defs><style>.cls-1{fill:#fff;opacity:0;}</style></defs><filter id="blurFilter"><feGaussianBlur stdDeviation="1"/></filter><g id="Layer_2" data-name="Layer 2"><g id="canvas" data-name="canvas"><rect class="cls-1" width="512" height="512"/></g>',
                                string(
                                    _getMowseWearImageJSON(
                                        traitType,
                                        gs.mowseWearDictionary[traitType][traitIndex].traitName,
                                        traitIndex,
                                        gs.mowseWearDictionary[traitType][traitIndex].baseDimensions
                                    )
                                ),
                                "</g></svg>"
                            )
                        )
                    )
                );
        }
    
        function getMowseWearImage(uint256 mowseWearTokenId) public view returns (string memory) {
            if (!MowseWearFacet(gs.diamondAddress).mowseWearExists(mowseWearTokenId)) revert MowseWearMustExist(mowseWearTokenId);
    
            return
                string(
                    abi.encodePacked(
                        "data:image/svg+xml;base64,",
                        Base64.encode(
                            abi.encodePacked(
                                '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512"><defs><style>.cls-1{fill:#fff;opacity:0;}</style></defs><filter id="blurFilter"><feGaussianBlur stdDeviation="1"/></filter><g id="Layer_2" data-name="Layer 2"><g id="canvas" data-name="canvas"><rect class="cls-1" width="512" height="512"/></g>',
                                string(_getMowseWearFilter(gs.mowsewears[mowseWearTokenId].metadata.svgFilter)),
                                string(
                                    _getMowseWearImageJSON(
                                        gs.mowsewears[mowseWearTokenId].metadata.traitType,
                                        gs.mowsewears[mowseWearTokenId].metadata.traitName,
                                        gs.mowsewears[mowseWearTokenId].metadata.traitIndex,
                                        gs.mowsewears[mowseWearTokenId].dimensions
                                    )
                                ),
                                "</g></svg>"
                            )
                        )
                    )
                );
        }
    
        function _getNameJSON(uint256 mowseId) private view returns (bytes memory) {
            MowseBank mowseBank = MowseBank(gs.mowseBankContractAddress);
            address mowseAddress = mowseBank.getWalletAddress(mowseId);
            return
                abi.encodePacked(
                    '"name":"Mowse #',
                    mowseId.toString(),
                    '","token_id":',
                    mowseId.toString(),
                    ',"address":"',
                    Strings.toHexString(uint256(uint160(mowseAddress)), 20),
                    '"'
                );
        }
    
        function _getGenerationJSON(uint256 mowseId, string memory prepend) private view returns (bytes memory) {
            return
                abi.encodePacked(
                    prepend,
                    '"generation":',
                    gs.mowses[mowseId].generation.toString(),
                    ',"primarySkillType":',
                    gs.mowses[mowseId].primarySkillType.toString(),
                    ',"secondarySkillType":',
                    gs.mowses[mowseId].secondarySkillType.toString(),
                    ',"profession":',
                    gs.mowses[mowseId].profession.toString()
                );
        }
    
        function _getStatsJSON(uint256 mowseId, string memory prepend) private view returns (bytes memory) {
            return
                abi.encodePacked(
                    prepend,
                    '"stats":{',
                    _convertSkillStaticArrayToPartsWithBaseStats(gs.mowses[mowseId].skillLevelBoosts, gs.mowses[mowseId].skillLevel),
                    '},"lifeLevel":',
                    gs.mowses[mowseId].lifeLevel.toString(),
                    ',"lifeExperience":',
                    gs.mowses[mowseId].lifeExperiencePoints.toString(),
                    ',"inventory":',
                    _getInventoryJSON(mowseId)
                );
        }
    
        function _getBaseAttributesJSON(string memory prepend) private view returns (bytes memory) {
            bytes memory _traitsOne = abi.encodePacked(
                _getBaseAttributeJSON(0, 0, false),
                _getBaseAttributeJSON(2, 0, true),
                _getBaseAttributeJSON(3, 0, true)
            );
            bytes memory _traitsTwo = abi.encodePacked(
                _getBaseAttributeJSON(5, 0, true),
                _getBaseAttributeJSON(6, 0, true),
                _getBaseAttributeJSON(8, 0, true)
            );
            return abi.encodePacked(prepend, '"baseAttributes":[', _traitsOne, _traitsTwo, "]");
        }
    
        function _getBaseAttributeJSON(uint8 traitType, uint16 traitIndex, bool prepend) internal view returns (bytes memory) {
            return abi.encodePacked(prepend ? "," : "", _getMowseWearBaseAttributeJSON(traitType, traitIndex));
        }
    
        function _getMowseWearBaseAttributeJSON(uint8 traitType, uint16 traitIndex) internal view returns (string memory) {
            return
                string(
                    abi.encodePacked(
                        '{"traitType":"',
                        WithStorage.equippedWearableSlotToString(traitType),
                        '","value":"',
                        gs.mowseWearDictionary[traitType][traitIndex].traitName,
                        '"}'
                    )
                );
        }
    
        function _getWearableAttributesJSON(uint256 mowseId, string memory prepend) private view returns (bytes memory) {
            bytes memory _traitsOne = abi.encodePacked(
                _getAttributeJSON(mowseId, 0, false),
                _getAttributeJSON(mowseId, 1, true),
                _getAttributeJSON(mowseId, 2, true)
            );
            bytes memory _traitsTwo = abi.encodePacked(
                _getAttributeJSON(mowseId, 3, true),
                _getAttributeJSON(mowseId, 4, true),
                _getAttributeJSON(mowseId, 5, true)
            );
            bytes memory _traitsThree = abi.encodePacked(
                _getAttributeJSON(mowseId, 6, true),
                _getAttributeJSON(mowseId, 7, true),
                _getAttributeJSON(mowseId, 8, true)
            );
            bytes memory _traitsFour = abi.encodePacked(
                _getAttributeJSON(mowseId, 9, true),
                _getAttributeJSON(mowseId, 10, true),
                _getAttributeJSON(mowseId, 11, true)
            );
            // Check if mowse not wearing anything to save gas?
            uint256[EQUIPPED_WEARABLE_SLOTS] memory emptySlots;
            if (keccak256(abi.encodePacked(gs.mowses[mowseId].equippedMowseWearByTokenIds)) == keccak256(abi.encodePacked(emptySlots))) {
                return abi.encodePacked(prepend, '"wearing":[]');
            }
            // If wearing, make sure JSON is valid
            string memory fullWearableJSON = string(abi.encodePacked(_traitsOne, _traitsTwo, _traitsThree, _traitsFour));
            fullWearableJSON = keccak256(abi.encodePacked(LibUtils.getFirstChar(fullWearableJSON))) == keccak256(abi.encodePacked(","))
                ? LibUtils.substring(fullWearableJSON, 1, LibUtils.utfStringLength(fullWearableJSON))
                : fullWearableJSON;
    
            return abi.encodePacked(prepend, '"wearing":[', fullWearableJSON, "]");
        }
    
        function _getAttributeJSON(uint256 mowseId, uint8 traitType, bool prepend) internal view returns (bytes memory) {
            uint256 mowseWearTokenId = gs.mowses[mowseId].equippedMowseWearByTokenIds[traitType];
            return mowseWearTokenId > 0 ? abi.encodePacked(prepend ? "," : "", _getMowseWearAttributeJSONByTokenId(mowseWearTokenId)) : bytes("");
        }
    
        function _getMowseLootboxJSON(uint256 mowseLootboxTokenId) internal view returns (string memory) {
            uint64 mowseLootboxIndex = gs.mowseLootboxIndexByTokenId[mowseLootboxTokenId];
            uint16 currentItemCount = gs.mowselootboxes[mowseLootboxIndex].itemCount;
            string memory lootpool = string(abi.encodePacked(""));
            uint16 j = 0;
            // If mowseLootboxIndex is 12 (generic lootpool), don't show every metadata
            if (mowseLootboxIndex == 12) {
                return string(abi.encodePacked("[]"));
            } else {
                for (j = 0; j < currentItemCount; j++) {
                    lootpool = string.concat(
                        lootpool,
                        string(
                            abi.encodePacked(
                                j > 0 ? "," : "",
                                _getMowseWearMetadataAttributeJSONByDictionaryIndex(gs.mowselootboxes[mowseLootboxIndex].lootPool[j])
                            )
                        )
                    );
                }
                return string(abi.encodePacked("[", lootpool, "]"));
            }
        }
    
        function _getInventoryJSON(uint256 mowseId) internal view returns (string memory) {
            MowseLootbox mowselootbox = MowseLootbox(gs.mowseLootboxContractAddress);
            MowseAvatar mowseAvatar = MowseAvatar(gs.mowseAvatarContractAddress);
            MowseGold mowseGold = MowseGold(gs.mowseGoldContractAddress);
            MowseBank mowseBank = MowseBank(gs.mowseBankContractAddress);
            address mowseAddress = mowseBank.getWalletAddress(mowseId);
            uint256 mowseLootboxBalance = mowselootbox.balanceOf(mowseAddress);
            uint256 mowseAvatarBalance = mowseAvatar.balanceOf(mowseAddress);
            uint256 mowseGoldBalance = mowseGold.balanceOf(mowseAddress);
    
            return
                string(
                    abi.encodePacked(
                        '{"lootboxes":',
                        mowseLootboxBalance.toString(),
                        ',"mowses":',
                        mowseAvatarBalance.toString(),
                        ',"mgold":"',
                        mowseGoldBalance.toString(),
                        '","ftm":"',
                        mowseBank.getBalanceForMowse(mowseId).toString(),
                        '"}'
                    )
                );
        }
    
        function _getMowseWearMetadataAttributeJSONByDictionaryIndex(uint256 dictionaryIndex) internal view returns (string memory) {
            MowseWearMetadata memory mowseWearMetadata = gs.mowseWearDictionaryByDictionaryIndex[dictionaryIndex];
            return
                string(
                    abi.encodePacked(
                        '{"traitName":"',
                        mowseWearMetadata.traitName,
                        '","traitType":"',
                        WithStorage.equippedWearableSlotToString(mowseWearMetadata.traitType),
                        '","image":"data:image/svg+xml;base64,',
                        Base64.encode(
                            abi.encodePacked(
                                '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512"><defs><style><![CDATA[]]></style><filter id="blur" y="-5" height="40"><feGaussianBlur in="SourceGraphic" stdDeviation="5" y="-"/></filter></defs><g id="MowseWear" data-name="MowseWear">',
                                bytes(
                                    _getMowseWearImageJSON(
                                        mowseWearMetadata.traitType,
                                        mowseWearMetadata.traitName,
                                        mowseWearMetadata.traitIndex,
                                        mowseWearMetadata.baseDimensions
                                    )
                                ),
                                "</g></svg>"
                            )
                        ),
                        '"}'
                    )
                );
        }
    
        function _getMowseWearAttributeJSONByTokenId(uint256 mowseWearTokenId) internal view returns (string memory) {
            uint8 traitType = gs.mowsewears[mowseWearTokenId].metadata.traitType;
            uint16 traitIndex = gs.mowsewears[mowseWearTokenId].metadata.traitIndex;
    
            return
                string(
                    abi.encodePacked(
                        '{"id":',
                        mowseWearTokenId.toString(),
                        ",",
                        _getMowseWearAttributeMetadata(mowseWearTokenId),
                        ',"traitType":"',
                        WithStorage.equippedWearableSlotToString(traitType),
                        '","value":"',
                        gs.mowsewears[mowseWearTokenId].metadata.traitName,
                        '",',
                        _getBonusStatsJSON(
                            gs.mowsewears[mowseWearTokenId].bonuses.itemRarity,
                            gs.mowsewears[mowseWearTokenId].bonuses.baseSkillLevelBoosts,
                            gs.mowsewears[mowseWearTokenId].bonuses.additionalSkillLevelBoosts
                        ),
                        ',"frequency":"',
                        _getTraitFrequency(traitType, traitIndex),
                        '"}'
                    )
                );
        }
    
        function _getMowseWearAttributeMetadata(uint256 mowseWearTokenId) internal view returns (string memory) {
            return
                string(
                    abi.encodePacked(
                        '"wornBy":',
                        gs.mowsewears[mowseWearTokenId].equippedBy.toString(),
                        ',"durability":',
                        gs.mowsewears[mowseWearTokenId].durability.toString(),
                        ',"alterCount":',
                        gs.mowsewears[mowseWearTokenId].alterCount.toString(),
                        ',"maxAlterCount":',
                        gs.mowsewears[mowseWearTokenId].maxAlterCount.toString(),
                        ',"nonTransferrable":',
                        gs.mowsewears[mowseWearTokenId].metadata.nonTransferrable ? "true" : "false"
                    )
                );
        }
    
        function _getTraitFrequency(uint8 traitType, uint16 traitIndex) internal view returns (string memory) {
            uint256 _frequency = uint32((gs.mowseWearCountByTraitIndex[traitType][traitIndex] * 100000) / gs.mowseWearCountByTraitType[traitType]);
            uint256 _whole = _frequency / 1000;
            uint256 _decimals = _frequency % 1000;
            return string(abi.encodePacked(_whole.toString(), ".", _decimals < 10 ? "00" : (_decimals < 100 ? "0" : ""), _decimals.toString(), "%"));
        }
    
        function _getBonusStatsJSON(
            uint8 itemRarity,
            int32[SKILL_TYPE_NUM] memory baseSkillLevelBoosts,
            int32[SKILL_TYPE_NUM] memory additionalSkillLevelBoosts
        ) internal pure returns (string memory) {
            string memory bonusStats = string(
                abi.encodePacked(
                    '"itemRarity":"',
                    WithStorage.itemRarityToString(itemRarity),
                    '","baseSkillLevelBonus":{',
                    _convertSkillStaticArrayToParts(baseSkillLevelBoosts),
                    '},"additionalSkillLevelBonus":{',
                    _convertSkillStaticArrayToParts(additionalSkillLevelBoosts),
                    "}"
                )
            );
            return bonusStats;
        }
    
        function _convertSkillStaticArrayToPartsWithBaseStats(
            int32[SKILL_TYPE_NUM] memory skillLevelBonuses,
            int32[SKILL_TYPE_NUM] memory skillLevels
        ) internal pure returns (string memory) {
            return
                string(
                    abi.encodePacked(
                        '"charisma":',
                        LibUtils.int32ToString(skillLevelBonuses[0] + skillLevels[0]),
                        ',"constitution":',
                        LibUtils.int32ToString(skillLevelBonuses[1] + skillLevels[1]),
                        ',"dexterity":',
                        LibUtils.int32ToString(skillLevelBonuses[2] + skillLevels[2]),
                        ',"intelligence":',
                        LibUtils.int32ToString(skillLevelBonuses[3] + skillLevels[3]),
                        ',"luck":',
                        LibUtils.int32ToString(skillLevelBonuses[4] + skillLevels[4]),
                        ',"strength":',
                        LibUtils.int32ToString(skillLevelBonuses[5] + skillLevels[5]),
                        ',"wisdom":',
                        LibUtils.int32ToString(skillLevelBonuses[6] + skillLevels[6]),
                        ""
                    )
                );
        }
    
        function _convertSkillStaticArrayToParts(int32[SKILL_TYPE_NUM] memory skillLevels) internal pure returns (string memory) {
            return
                string(
                    abi.encodePacked(
                        '"charisma":',
                        LibUtils.int32ToString(skillLevels[0]),
                        ',"constitution":',
                        LibUtils.int32ToString(skillLevels[1]),
                        ',"dexterity":',
                        LibUtils.int32ToString(skillLevels[2]),
                        ',"intelligence":',
                        LibUtils.int32ToString(skillLevels[3]),
                        ',"luck":',
                        LibUtils.int32ToString(skillLevels[4]),
                        ',"strength":',
                        LibUtils.int32ToString(skillLevels[5]),
                        ',"wisdom":',
                        LibUtils.int32ToString(skillLevels[6]),
                        ""
                    )
                );
        }
    
        function _getMowseImageJSON(uint256 mowseId, string memory prepend) private view returns (bytes memory) {
            return abi.encodePacked(prepend, '"image":"data:image/svg+xml;base64,', Base64.encode(_getSVG(mowseId)), '"');
        }
    
        function _getSVG(uint256 mowseId) private view returns (bytes memory) {
            bytes memory _imagesOne = abi.encodePacked(
                _getSvgLayer(mowseId, 0, true),
                _getSvgLayer(mowseId, 1, false),
                _getSvgLayer(mowseId, 2, true),
                _getSvgLayer(mowseId, 3, true),
                _getSvgLayer(mowseId, 4, false),
                _getSvgLayer(mowseId, 5, true)
            );
            bytes memory _imagesTwo = abi.encodePacked(
                _getSvgLayer(mowseId, 6, true),
                _getSvgLayer(mowseId, 7, false),
                _getSvgLayer(mowseId, 8, true),
                _getSvgLayer(mowseId, 9, false),
                _getSvgLayer(mowseId, 10, false),
                _getSvgLayer(mowseId, 11, false)
            );
            return
                abi.encodePacked(
                    '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512"><defs><style><![CDATA[]]></style><filter id="blur" y="-5" height="40"><feGaussianBlur in="SourceGraphic" stdDeviation="5" y="-"/></filter></defs><g id="Mowse" data-name="Mowse">',
                    _imagesOne,
                    _imagesTwo,
                    "</g></svg>"
                );
        }
    
        function _getSvgLayer(uint256 mowseId, uint8 traitType, bool defaultIfEmpty) internal view returns (bytes memory) {
            uint256 mowseWearTokenId = gs.mowses[mowseId].equippedMowseWearByTokenIds[traitType];
            return
                mowseWearTokenId > 0
                    ? bytes(
                        _getMowseWearImageJSON(
                            traitType,
                            gs.mowsewears[mowseWearTokenId].metadata.traitName,
                            gs.mowsewears[mowseWearTokenId].metadata.traitIndex,
                            gs.mowsewears[mowseWearTokenId].dimensions
                        )
                    )
                    : (
                        defaultIfEmpty
                            ? bytes(
                                _getMowseWearImageJSON(
                                    traitType,
                                    gs.mowseWearDictionary[traitType][0].traitName,
                                    0,
                                    gs.mowseWearDictionary[traitType][0].baseDimensions
                                )
                            )
                            : bytes("")
                    );
        }
    
        function _getMowseWearImageJSON(
            uint8 traitType,
            string memory traitName,
            uint16 traitIndex,
            MowseWearDimensions memory dimensions
        ) internal pure returns (bytes memory imageJSON) {
            return
                bytes(
                    abi.encodePacked(
                        '<g id="',
                        string(abi.encodePacked(traitType.toString(), "_", traitIndex.toString())),
                        '" data-name="',
                        traitName,
                        '">',
                        (
                            keccak256(abi.encodePacked(traitName)) == keccak256(abi.encodePacked("None"))
                                ? abi.encodePacked("<g></g>")
                                : abi.encodePacked(
                                    '<image id="',
                                    string(abi.encodePacked(traitType.toString(), "_", traitIndex.toString())),
                                    '_Image" filter="url(#mowseFilter)" data-name="',
                                    traitName,
                                    ' Image" width="',
                                    dimensions.width.toString(),
                                    '" height="',
                                    dimensions.height.toString(),
                                    '" transform="',
                                    dimensions.transform,
                                    '" style="',
                                    dimensions.style,
                                    '" xlink:href="',
                                    dimensions.image,
                                    '"/>'
                                )
                        ),
                        "</g>"
                    )
                );
        }
    
        // https://fecolormatrix.com/
        function _getMowseWearFilter(string memory svgFilter) internal pure returns (bytes memory filterJSON) {
            return
                bytes(
                    keccak256(abi.encodePacked(svgFilter)) == keccak256(abi.encodePacked(""))
                        ? abi.encodePacked("")
                        : abi.encodePacked('<filter id="mowseFilter">', svgFilter, "</filter>")
                );
        }
    
        function getMowseLootboxImageJSON(uint64 lootboxIndex) public view returns (string memory) {
            uint16 width = gs.mowselootboxes[lootboxIndex].width;
            uint16 height = gs.mowselootboxes[lootboxIndex].height;
            string memory image = gs.mowselootboxes[lootboxIndex].image;
            bytes memory _imagesOne = abi.encodePacked(
                width.toString(),
                '" height="',
                height.toString(),
                '" xml:space="preserve" version="1.1" viewBox="0 0 ',
                width.toString(),
                " ",
                height.toString()
            );
            bytes memory _imagesTwo = abi.encodePacked('"><image width="', width.toString(), '" height="', height.toString(), '" xlink:href="', image);
            return
                string(
                    abi.encodePacked(
                        "data:image/svg+xml;base64,",
                        Base64.encode(
                            abi.encodePacked(
                                '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="',
                                _imagesOne,
                                _imagesTwo,
                                '"/></svg>'
                            )
                        )
                    )
                );
        }
    
        function getAllMowseWearTokenURIByAddress(address account) external view returns (string[] memory) {
            uint256 mowseWearCount = MowseWearFacet(gs.diamondAddress).balanceOf(account);
            string[] memory tokenURIs = new string[](mowseWearCount);
            for (uint256 i = 0; i < mowseWearCount; i++) {
                uint256 mowseWearTokenId = MowseWearFacet(gs.diamondAddress).tokenOfOwnerByIndex(account, i);
                tokenURIs[i] = getMowseWearTokenURI(mowseWearTokenId);
            }
            return tokenURIs;
        }
        
        function getAllMowseAvatarTokenURIByAddress(address account) external view returns (string[] memory) {
            MowseAvatar mowseAvatar = MowseAvatar(gs.mowseAvatarContractAddress);
            uint256 mowseCount = mowseAvatar.balanceOf(account);
            string[] memory tokenURIs = new string[](mowseCount);
            for (uint256 i = 0; i < mowseCount; i++) {
                uint256 mowseId = mowseAvatar.tokenOfOwnerByIndex(account, i);
                tokenURIs[i] = getMowseTokenURI(mowseId);
            }
            return tokenURIs;
        }
    
        function getAllMowseLootboxTokenURIByAddress(address account) external view returns (string[] memory) {
            MowseLootbox mowseLootbox = MowseLootbox(gs.mowseLootboxContractAddress);
            uint256 mowseLootboxCount = mowseLootbox.balanceOf(account);
            string[] memory tokenURIs = new string[](mowseLootboxCount);
            for (uint256 i = 0; i < mowseLootboxCount; i++) {
                uint256 mowseLootboxTokenId = mowseLootbox.tokenOfOwnerByIndex(account, i);
                tokenURIs[i] = getMowseLootboxTokenURI(mowseLootboxTokenId, false);
            }
            return tokenURIs;
        }
    
        function getAllMowseLootboxPools() external view returns (MowseLootboxPoolData[] memory) {
            MowseLootboxPoolData[] memory mowseLootboxes = new MowseLootboxPoolData[](gs.mowseLootboxIndexCount);
            for (uint64 i = 0; i < gs.mowseLootboxIndexCount; i++) {
                mowseLootboxes[i] = MowseLootboxFacet(gs.diamondAddress).getMowseLootboxDataWithoutLootPool(i);
            }
            return mowseLootboxes;
        }
    
        function getMowseWearByDictionaryIndex(uint256 index, bool showImage) external view returns (SimpleMowseLootboxPoolItem memory) {
            MowseWearMetadata memory mowsewear = gs.mowseWearDictionaryByDictionaryIndex[index];
            string memory image;
            if (showImage) {
                image = string(
                    abi.encodePacked(
                        "data:image/svg+xml;base64,",
                        Base64.encode(
                            abi.encodePacked(
                                '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512"><defs><style><![CDATA[]]></style><filter id="blur" y="-5" height="40"><feGaussianBlur in="SourceGraphic" stdDeviation="5" y="-"/></filter></defs><g id="MowseWear" data-name="MowseWear">',
                                bytes(_getMowseWearImageJSON(mowsewear.traitType, mowsewear.traitName, mowsewear.traitIndex, mowsewear.baseDimensions)),
                                "</g></svg>"
                            )
                        )
                    )
                );
            }
            return
                SimpleMowseLootboxPoolItem({
                    traitIndex: mowsewear.traitIndex,
                    traitName: mowsewear.traitName,
                    traitType: mowsewear.traitType,
                    image: image
                });
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
    															 
    /*************************************************************************/
    
    import {SolidStateERC721} from "@solidstate/contracts/token/ERC721/SolidStateERC721.sol";
    import {ERC721Metadata} from "@solidstate/contracts/token/ERC721/metadata/ERC721Metadata.sol";
    import {IERC721Metadata} from "@solidstate/contracts/token/ERC721/metadata/IERC721Metadata.sol";
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    
    import {GameStorage, MowseWear, MowseSkillTypes, MowseWearMetadata, SKILL_TYPE_NUM, EQUIPPED_WEARABLE_SLOTS} from "../libraries/LibStorage.sol";
    
    import {MowseFacet} from "./MowseFacet.sol";
    import {MowseVisualizerFacet} from "./MowseVisualizerFacet.sol";
    import {AccessControlFacet} from "./AccessControlFacet.sol";
    import {RandomFortuneFacet} from "./RandomFortuneFacet.sol";
    import {MowseLootboxFacet} from "./MowseLootboxFacet.sol";
    import {WithdrawalFacet} from "./WithdrawalFacet.sol";
    import {DonationTreeFacet} from "./DonationTreeFacet.sol";
    
    import "../MowseAvatar.sol";
    import "../MowseGold.sol";
    import "../MowseBank.sol";
    import "./../libraries/LibStorage.sol";
    
    contract MowseWearFacet is SolidStateERC721 {
        using Strings for uint256;
        using Strings for uint8;
        using Strings for bool;
        GameStorage internal gs;
    
        event MowseWearMinted(address indexed player, uint256 indexed tokenId);
        event MowseWearHemmed(address indexed player, uint256 indexed tokenId, bool success);
        event MowseWearStitched(address indexed player);
        event MowseWearDonated(address indexed player, uint256 indexed tokenId, uint256 donationAmount);
    
        error MissingAdminRole(string);
        error MissingMowseWearMinterRole(string);
        error MissingMowseWearAdminRole(string);
        error ContractPaused(string);
        error MowseWearMustExist(uint256 tokenId);
        error CannotFindMowseWear(uint8 traitType, uint16 traitIndex);
        error MustOwnMowseWear(uint256 tokenId);
        error MustOwnMowse(uint256 tokenId);
        error NotEnoughFunds(uint256 userBalance);
        error LootPoolNotActive(uint8 lootPoolIndex, bool isActive);
        error LootPoolEmpty(uint8 lootPoolIndex);
        error LootboxPaymentUnsuccessful(uint8 lootPoolIndex);
        error InvalidDictionaryIndex(uint256 dictionaryIndex);
        error MowseWearIsTooWorn(uint256 tokenId, uint256 durability);
        error MowseWearIsTooAltered(uint256 tokenId);
        error MowseWearMustBeUnequipped(string);
        error MowseWearNotApprovedToTransfer(uint256 tokenId);
        error MowseWearPaymentNotSuccessful(uint256 tokenId);
        error MowseWearDonationNotSuccessful(uint256 tokenId);
        error DictionaryInvalidTraitType(uint8 traitType);
        error DictionaryMissingTraitName(uint8 traitType);
        error MowseWearNotTransferrable(uint256 tokenId);
    
        modifier onlyAdmin() {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Admin Role");
            _;
        }
        modifier notPaused() {
            if (gs.paused) revert ContractPaused("Contract is paused");
            _;
        }
        modifier mowseWearMustExist(uint256 _tokenId) {
            if (!_exists(_tokenId) || _tokenId == 0) revert MowseWearMustExist(_tokenId);
            _;
        }
        modifier ownsMowseWear(uint256 mowseWearId) {
            if (!_exists(mowseWearId) || mowseWearId == 0) revert MowseWearMustExist(mowseWearId);
            if (msg.sender != _ownerOf(mowseWearId)) revert MustOwnMowseWear(mowseWearId);
            _;
        }
    
        // Only used for mowsepack minting, other minting for general purposes
        function mintAndWearMowseWear(address to, uint256 mowseId, uint8 traitType, uint16 traitIndex) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseWearMinterRole(msg.sender))
                revert MissingMowseWearMinterRole("Must have MowseWear Minter Role");
            if (bytes(gs.mowseWearDictionary[traitType][traitIndex].traitName).length == 0) revert CannotFindMowseWear(traitType, traitIndex);
    
            uint256 currentMowseWearTokenId = gs.mowseWearTokenIdCounter;
            console.log("Mint MowseWear: ", currentMowseWearTokenId);
            _mint(to, currentMowseWearTokenId);
            gs.mowsewears[currentMowseWearTokenId].tokenId = currentMowseWearTokenId;
    
            _setupMowseWear(currentMowseWearTokenId, traitType, traitIndex, 0);
    
            gs.mowseWearCountByTraitIndex[traitType][traitIndex]++;
            gs.mowseWearCountByTraitType[traitType]++;
    
            // Wear newly minted mowsewear
            gs.mowses[mowseId].equippedMowseWearByTokenIds[traitType] = currentMowseWearTokenId;
            gs.mowsewears[currentMowseWearTokenId].isEquipped = true;
            gs.mowsewears[currentMowseWearTokenId].equippedBy = mowseId;
            _updateMowseStats(mowseId);
    
            emit MowseWearMinted(to, currentMowseWearTokenId);
    
            gs.mowseWearTokenIdCounter++;
        }
    
        // StitchMowseWear is a function that combines 3 mowsewear into a new mowsewear
        // NOTE: The 3 mowsewear are not burned, but are instead altered
        function stitchMowseWear(
            uint256 mowseWearId1,
            uint256 mowseWearId2,
            uint256 mowseWearId3
        ) external ownsMowseWear(mowseWearId1) ownsMowseWear(mowseWearId2) ownsMowseWear(mowseWearId3) notPaused {
            if (gs.mowsewears[mowseWearId1].isEquipped || gs.mowsewears[mowseWearId2].isEquipped || gs.mowsewears[mowseWearId3].isEquipped)
                revert MowseWearMustBeUnequipped("MowseWear must be unequipped to stitch");
            if (gs.mowsewears[mowseWearId1].durability > block.timestamp) revert MowseWearIsTooWorn(mowseWearId1, gs.mowsewears[mowseWearId1].durability);
            if (gs.mowsewears[mowseWearId2].durability > block.timestamp) revert MowseWearIsTooWorn(mowseWearId2, gs.mowsewears[mowseWearId2].durability);
            if (gs.mowsewears[mowseWearId3].durability > block.timestamp) revert MowseWearIsTooWorn(mowseWearId3, gs.mowsewears[mowseWearId3].durability);
            if (gs.mowsewears[mowseWearId1].alterCount >= gs.mowsewears[mowseWearId1].maxAlterCount) revert MowseWearIsTooAltered(mowseWearId1);
            if (gs.mowsewears[mowseWearId2].alterCount >= gs.mowsewears[mowseWearId2].maxAlterCount) revert MowseWearIsTooAltered(mowseWearId2);
            if (gs.mowsewears[mowseWearId3].alterCount >= gs.mowsewears[mowseWearId3].maxAlterCount) revert MowseWearIsTooAltered(mowseWearId3);
    
            MowseLootboxFacet(gs.diamondAddress).stitchMowseLootbox(msg.sender);
    
            gs.mowsewears[mowseWearId1].durability = block.timestamp + getDurabilityDays(mowseWearId1);
            gs.mowsewears[mowseWearId2].durability = block.timestamp + getDurabilityDays(mowseWearId2);
            gs.mowsewears[mowseWearId3].durability = block.timestamp + getDurabilityDays(mowseWearId3);
            gs.mowsewears[mowseWearId1].alterCount++;
            gs.mowsewears[mowseWearId2].alterCount++;
            gs.mowsewears[mowseWearId3].alterCount++;
    
            emit MowseWearStitched(msg.sender);
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        // DonateMowseWear is a function that sends the mowsewear to a donation wallet.
        // The less altered the mowsewear, the greater the reward for donating
        // The higher rarity, the greater the reward for donating
        function donateMowseWear(uint256 mowseWearTokenId) external ownsMowseWear(mowseWearTokenId) notPaused {
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
    
            if (gs.mowsewears[mowseWearTokenId].isEquipped) revert MowseWearMustBeUnequipped("MowseWear must be unequipped to donate");
            if (!_isApprovedOrOwner(address(this), mowseWearTokenId)) revert MowseWearNotApprovedToTransfer(mowseWearTokenId);
            // Transfer this mowsewear to donation address
            _safeTransferFrom(msg.sender, address(DonationTreeFacet(gs.diamondAddress)), mowseWearTokenId);
            uint256 alterCountPercent = ((gs.mowsewears[mowseWearTokenId].maxAlterCount - gs.mowsewears[mowseWearTokenId].alterCount) * 100) /
                gs.mowsewears[mowseWearTokenId].maxAlterCount;
            // Mint mowsegold to the user (100 base mowsegold + 100 * (100 * alterCountPercent / 100)) * (itemRarity + 1))
            // Ex: Legendary unaltered mowsewear with 7 max alter count would give 100 + 100 * (100 * 100 / 100)) * (4 + 1)) = 100 + 100 * 100 * 5 = 5100 mowsegold
            // Ex: Common fully altered mowsewear with 1 max alter count would give 100 + 100 * (100 * 0 / 100)) * (0 + 1) = 100 mowsegold
            uint256 donationAmount = (100 + ((100 * alterCountPercent) / 100) * (gs.mowsewears[mowseWearTokenId].bonuses.itemRarity + 1)) * 1e18;
    
            mowsegold.mint(msg.sender, donationAmount);
            emit MowseWearDonated(msg.sender, mowseWearTokenId, donationAmount);
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        function getDurabilityDays(uint256 mowseWearId) internal view returns (uint256 durabilityDays) {
            if (gs.mowsewears[mowseWearId].bonuses.itemRarity == 0) return 7 days;
            if (gs.mowsewears[mowseWearId].bonuses.itemRarity == 1) return 6 days;
            if (gs.mowsewears[mowseWearId].bonuses.itemRarity == 2) return 5 days;
            if (gs.mowsewears[mowseWearId].bonuses.itemRarity == 3) return 4 days;
            if (gs.mowsewears[mowseWearId].bonuses.itemRarity == 4) return 3 days;
            if (gs.mowsewears[mowseWearId].bonuses.itemRarity == 5) return 2 days;
        }
    
        function wearMowseWear(uint256 mowseId, uint256 mowseWearId) external ownsMowseWear(mowseWearId) notPaused {
            console.log(_ownerOf(mowseWearId));
            console.log(gs.mowsewears[mowseWearId].durability);
            if (gs.mowsewears[mowseWearId].durability > block.timestamp) revert MowseWearIsTooWorn(mowseWearId, gs.mowsewears[mowseWearId].durability);
            console.log(gs.mowsewears[mowseWearId].equippedBy);
            if (gs.mowsewears[mowseWearId].equippedBy > 0 && gs.mowsewears[mowseWearId].equippedBy != mowseId)
                revert MowseWearMustBeUnequipped("Another Mowse is wearing that!");
            console.log(msg.sender);
            MowseAvatar mowse = MowseAvatar(gs.mowseAvatarContractAddress);
            MowseBank mowseBank = MowseBank(gs.mowseBankContractAddress);
            console.log('attempt to get the mowse Address', mowseId);
            address mowseAddress = mowseBank.getWalletAddress(mowseId);
            console.log(mowse.ownerOf(mowseId));
            if (msg.sender != mowse.ownerOf(mowseId)) {
                // Could be a proxy call
                if (msg.sender == mowseAddress) {
                    // Do nothing
                } else {
                    revert MustOwnMowse(mowseId);
                }
            }
    
            uint8 traitType = gs.mowsewears[mowseWearId].metadata.traitType;
            uint256 mowseWearCurrentlyEquipped = gs.mowses[mowseId].equippedMowseWearByTokenIds[traitType];
            // Iff same mowseWear is equipped, unequip, else
            if (mowseWearCurrentlyEquipped > 0) {
                if (mowseWearCurrentlyEquipped == mowseWearId) {
                    delete gs.mowses[mowseId].equippedMowseWearByTokenIds[traitType];
                    delete gs.mowsewears[mowseWearId].isEquipped;
                    delete gs.mowsewears[mowseWearId].equippedBy;
                } else {
                    // Unequip the currently equipped mowsewear and then equip the new one
                    delete gs.mowsewears[mowseWearCurrentlyEquipped].isEquipped;
                    delete gs.mowsewears[mowseWearCurrentlyEquipped].equippedBy;
                    gs.mowses[mowseId].equippedMowseWearByTokenIds[traitType] = mowseWearId;
                    gs.mowsewears[mowseWearId].isEquipped = true;
                    gs.mowsewears[mowseWearId].equippedBy = mowseId;
                }
            } else {
                gs.mowses[mowseId].equippedMowseWearByTokenIds[traitType] = mowseWearId;
                gs.mowsewears[mowseWearId].isEquipped = true;
                gs.mowsewears[mowseWearId].equippedBy = mowseId;
            }
            _updateMowseStats(mowseId);
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        function mintMowseWear(address to, uint8 traitType, uint16 traitIndex, uint8 minRarity) external notPaused {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseWearMinterRole(msg.sender))
                revert MissingMowseWearMinterRole("Must have MowseWear Minter Role");
            if (bytes(gs.mowseWearDictionary[traitType][traitIndex].traitName).length == 0) revert CannotFindMowseWear(traitType, traitIndex);
    
            uint256 currentMowseWearTokenId = gs.mowseWearTokenIdCounter;
            console.log("Minted MowseWear ", currentMowseWearTokenId);
    
            _mint(to, currentMowseWearTokenId);
            gs.mowsewears[currentMowseWearTokenId].tokenId = currentMowseWearTokenId;
    
            _setupMowseWear(currentMowseWearTokenId, traitType, traitIndex, minRarity);
    
            gs.mowseWearCountByTraitIndex[traitType][traitIndex]++;
            gs.mowseWearCountByTraitType[traitType]++;
    
            emit MowseWearMinted(to, currentMowseWearTokenId);
    
            gs.mowseWearTokenIdCounter++;
        }
    
        // Unequip all mowsewear not owned by the mowse tokenId
        function unequipAllUnownedMowseWear(address caller, uint256 tokenId) external {
            MowseBank mowseBank = MowseBank(gs.mowseBankContractAddress);
            address mowseAddress = mowseBank.getWalletAddress(tokenId);
            MowseAvatar mowse = MowseAvatar(gs.mowseAvatarContractAddress);
            if (caller != mowse.ownerOf(tokenId)) revert MustOwnMowse(tokenId);
    
            for (uint8 i = 0; i < EQUIPPED_WEARABLE_SLOTS; i++) {
                uint256 equippedMowseWearTokenId = gs.mowses[tokenId].equippedMowseWearByTokenIds[i];
                if (equippedMowseWearTokenId > 0 && _ownerOf(equippedMowseWearTokenId) != mowseAddress) {
                    delete gs.mowsewears[equippedMowseWearTokenId].isEquipped;
                    delete gs.mowsewears[equippedMowseWearTokenId].equippedBy;
                    delete gs.mowses[tokenId].equippedMowseWearByTokenIds[i];
                }
            }
            _updateMowseStats(tokenId);
        }
    
        function unequipAllMowseWear(uint256 tokenId) external {
            MowseAvatar mowse = MowseAvatar(gs.mowseAvatarContractAddress);
            if (msg.sender != mowse.ownerOf(tokenId)) revert MustOwnMowse(tokenId);
    
            for (uint8 i = 0; i < EQUIPPED_WEARABLE_SLOTS; i++) {
                uint256 equippedMowseWearTokenId = gs.mowses[tokenId].equippedMowseWearByTokenIds[i];
                if (equippedMowseWearTokenId > 0) {
                    delete gs.mowsewears[equippedMowseWearTokenId].isEquipped;
                    delete gs.mowsewears[equippedMowseWearTokenId].equippedBy;
                    delete gs.mowses[tokenId].equippedMowseWearByTokenIds[i];
                }
            }
            _updateMowseStats(tokenId);
        }
    
        function getMowseWearNameFromDictionary(uint8 traitType, uint16 traitIndex) public view returns (string memory traitName) {
            if (bytes(gs.mowseWearDictionary[traitType][traitIndex].traitName).length == 0) revert CannotFindMowseWear(traitType, traitIndex);
    
            return gs.mowseWearDictionary[traitType][traitIndex].traitName;
        }
    
        // NOTE: Need to update image separately through
        function addMowseWearToDictionary(
            uint8 traitType,
            string memory traitName,
            bool nonTransferrable,
            uint16 width,
            uint16 height,
            string memory transform,
            string memory style,
            string calldata image,
            uint16 weight,
            bool ignoreFromLootPool
        ) external onlyAdmin {
            // console.log("Adding MowseWear to Dictionary", traitName);
            if (traitType >= EQUIPPED_WEARABLE_SLOTS) revert DictionaryInvalidTraitType(traitType);
            if (bytes(traitName).length == 0) revert DictionaryMissingTraitName(traitType);
    
            uint16 currentTraitIndex = gs.mowseWearDictionaryTraitCount[traitType];
            // console.log("Should add index", traitType);
            uint256 dictionaryIndex = gs.mowseWearDictionaryCount;
            _updateMowseWearDictionary(
                traitType,
                traitName,
                currentTraitIndex,
                dictionaryIndex,
                nonTransferrable,
                width,
                height,
                transform,
                style,
                image,
                weight
            );
    
            // For event items, don't add to loot pool. Manually add them as admin
            if (!ignoreFromLootPool) {
                // Add mowseWear to general lootpool [12]
                MowseLootboxFacet(gs.diamondAddress).updateMowseLootboxItem(12, dictionaryIndex, true, 0);
                // Add to trait-specific lootpool [0-11]
                MowseLootboxFacet(gs.diamondAddress).updateMowseLootboxItem(traitType, dictionaryIndex, true, 0);
            }
    
            // Add mowsewear to dictionary index
            gs.mowseWearDictionaryByDictionaryIndex[dictionaryIndex] = gs.mowseWearDictionary[traitType][currentTraitIndex];
            gs.mowseWearDictionaryTraitCount[traitType]++;
            gs.mowseWearDictionaryCount++;
    
            console.log("Added ", dictionaryIndex, " to dictionary");
        }
        // Updates mowseWearDictionaryByDictionaryIndex
        // Make sure to call MowseWearFacet2.concatMowseWearImageOnMowseWearDictionary to update mowseWearDictionary as well
        // To update an image fully:
        // Optionally reset both images (MowseWearFacet2.resetMowseWearDictionaryImage & MowseWearFacet2.resetMowseWearDictionaryByDictionaryIndexImage)
        // 1. Call MowseWearFacet.updateMowseWearDictionary/MowseWearFacet2.concatMowseWearImageOnMowseWearDictionary
        // 2. Call MowseWearFacet2.syncMowseWearDictionaryByDictionaryIndexWithMowseWearDictionary
        // or
        // 1. Call MowseWearFacet.concatMowseWearImage
        // 2. Call MowseWearFacet2.concatMowseWearImageOnMowseWearDictionary
        function concatMowseWearImage(uint256 dictionaryIndex, string calldata image) external onlyAdmin {
            if (dictionaryIndex >= gs.mowseWearDictionaryCount) revert InvalidDictionaryIndex(dictionaryIndex);
            gs.mowseWearDictionaryByDictionaryIndex[dictionaryIndex].baseDimensions.image = string(
                abi.encodePacked(gs.mowseWearDictionaryByDictionaryIndex[dictionaryIndex].baseDimensions.image, image)
            );
        }
    
        function updateMowseWearDictionary(
            uint8 traitType,
            string memory traitName,
            uint16 traitIndex,
            bool nonTransferrable,
            uint16 width,
            uint16 height,
            string memory transform,
            string memory style,
            string memory image,
            uint16 weight
        ) external onlyAdmin {
            if (bytes(gs.mowseWearDictionary[traitType][traitIndex].traitName).length == 0) revert CannotFindMowseWear(traitType, traitIndex);
            if (traitType >= EQUIPPED_WEARABLE_SLOTS) revert DictionaryInvalidTraitType(traitType);
            if (bytes(traitName).length == 0) revert DictionaryMissingTraitName(traitType);
    
            uint256 dictionaryIndex = gs.mowseWearDictionary[traitType][traitIndex].dictionaryIndex;
            _updateMowseWearDictionary(
                traitType,
                traitName,
                traitIndex,
                dictionaryIndex,
                nonTransferrable,
                width,
                height,
                transform,
                style,
                image,
                weight
            );
        }
    
        function _updateMowseWearDictionary(
            uint8 traitType,
            string memory traitName,
            uint16 traitIndex,
            uint256 dictionaryIndex,
            bool nonTransferrable,
            uint16 width,
            uint16 height,
            string memory transform,
            string memory style,
            string memory image,
            uint16 weight
        ) internal {
            MowseWearMetadata storage metadata = gs.mowseWearDictionary[traitType][traitIndex];
            metadata.traitType = traitType;
            metadata.traitName = traitName;
            metadata.traitIndex = traitIndex;
            metadata.dictionaryIndex = dictionaryIndex;
            metadata.nonTransferrable = nonTransferrable;
            metadata.baseDimensions.width = width;
            metadata.baseDimensions.height = height;
            metadata.baseDimensions.transform = transform;
            metadata.baseDimensions.style = style;
            metadata.baseDimensions.image = image;
            metadata.baseDimensions.weight = weight;
        }
    
        function tokenURI(
            uint256 mowseWearTokenId
        ) public view override(ERC721Metadata, IERC721Metadata) mowseWearMustExist(mowseWearTokenId) returns (string memory) {
            return MowseVisualizerFacet(gs.diamondAddress).getMowseWearTokenURI(mowseWearTokenId);
        }
    
        function mowseWearExists(uint256 mowseWearTokenId) external view returns (bool) {
            return _exists(mowseWearTokenId);
        }
    
        // HemMowseWear is a function that changes the stats of a mowsewear
        // NOTE: This changes the item rarity and skill points of the mowsewear
        function hemMowseWear(uint256 mowseWearTokenId) external ownsMowseWear(mowseWearTokenId) notPaused {
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
    
            uint256 costToHem = ((gs.mowsewears[mowseWearTokenId].alterCount * gs.mowseWearHemPrice) + gs.mowseWearHemPrice);
            // console.log("hemMowseWear", costToHem);
            // console.log(_ownerOf(mowseWearTokenId));
            // console.log(gs.mowsewears[mowseWearTokenId].isEquipped);
            if (gs.mowsewears[mowseWearTokenId].isEquipped) revert MowseWearMustBeUnequipped("MowseWear must be unequipped to hem");
            if (!_isApprovedOrOwner(address(this), mowseWearTokenId)) revert MowseWearNotApprovedToTransfer(mowseWearTokenId);
            if (!mowsegold.transferFrom(msg.sender, address(WithdrawalFacet(gs.diamondAddress)), costToHem))
                revert MowseWearPaymentNotSuccessful(mowseWearTokenId);
            if (gs.mowsewears[mowseWearTokenId].alterCount >= gs.mowsewears[mowseWearTokenId].maxAlterCount)
                revert MowseWearIsTooAltered(mowseWearTokenId);
    
            (uint256 _randomness, uint256 _randomSeed) = _getRandomSeed();
            // 5% chance to burn when hemming
            if (_randomness % 20 == 0) {
                // Burned. Oofies.
                emit MowseWearHemmed(msg.sender, mowseWearTokenId, false);
                _burn(mowseWearTokenId);
            } else {
                updateMowseWearSkillPoints(mowseWearTokenId, _randomness, _randomSeed, 0);
                gs.mowsewears[mowseWearTokenId].alterCount++;
                emit MowseWearHemmed(msg.sender, mowseWearTokenId, true);
            }
    
            RandomFortuneFacet(gs.diamondAddress).randomFortune(msg.sender);
        }
    
        function getDurabilityTimeByTokenId(uint256 mowseWearTokenId) external view returns (uint256) {
            return gs.mowsewears[mowseWearTokenId].durability;
        }
    
        function getHemPriceByTokenId(uint256 mowseWearTokenId) external view returns (uint256 hemPrice) {
            return ((gs.mowsewears[mowseWearTokenId].alterCount * gs.mowseWearHemPrice) + gs.mowseWearHemPrice);
        }
    
        function getBaseHemPrice() external view returns (uint256 hemPrice) {
            return gs.mowseWearHemPrice;
        }
    
        function setBaseHemPrice(uint256 newHemPrice) external onlyAdmin {
            gs.mowseWearHemPrice = newHemPrice;
        }
    
        // Only MowseWearAdmins can change svg filters (other approved contracts like paintbrushes/authorized users)
        function updateMowseWearFilter(uint256 mowseWearTokenId, string memory filter) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseWearAdminRole(msg.sender)) revert MissingAdminRole("Must have MowseWear Admin Role");
            gs.mowsewears[mowseWearTokenId].metadata.svgFilter = filter;
        }
    
        function _setupMowseWear(uint256 mowseWearTokenId, uint8 traitType, uint16 traitIndex, uint8 minRarity) internal {
            // get MowseWear from dictionary
            MowseWearMetadata memory baseMetadata = gs.mowseWearDictionary[traitType][traitIndex];
    
            gs.mowsewears[mowseWearTokenId].tokenId = mowseWearTokenId;
            gs.mowsewears[mowseWearTokenId].metadata.traitType = traitType;
            gs.mowsewears[mowseWearTokenId].metadata.traitName = baseMetadata.traitName;
            gs.mowsewears[mowseWearTokenId].metadata.traitIndex = traitIndex;
            gs.mowsewears[mowseWearTokenId].metadata.dictionaryIndex = baseMetadata.dictionaryIndex;
            gs.mowsewears[mowseWearTokenId].metadata.nonTransferrable = baseMetadata.nonTransferrable;
    
            // set base dimensions (There may be future updates to modify svg elements/filters)
            gs.mowsewears[mowseWearTokenId].metadata.baseDimensions = baseMetadata.baseDimensions;
            // gs.mowsewears[mowseWearTokenId].metadata.baseDimensions.width = baseMetadata.baseDimensions.width;
            // gs.mowsewears[mowseWearTokenId].metadata.baseDimensions.height = baseMetadata.baseDimensions.height;
            // gs.mowsewears[mowseWearTokenId].metadata.baseDimensions.transform = baseMetadata.baseDimensions.transform;
            // gs.mowsewears[mowseWearTokenId].metadata.baseDimensions.style = baseMetadata.baseDimensions.style;
            // gs.mowsewears[mowseWearTokenId].metadata.baseDimensions.image = baseMetadata.baseDimensions.image;
            // gs.mowsewears[mowseWearTokenId].metadata.baseDimensions.weight = baseMetadata.baseDimensions.weight;
    
            // These dimensions may be updated in the future
            gs.mowsewears[mowseWearTokenId].dimensions = baseMetadata.baseDimensions;
            // gs.mowsewears[mowseWearTokenId].dimensions.width = baseMetadata.baseDimensions.width;
            // gs.mowsewears[mowseWearTokenId].dimensions.height = baseMetadata.baseDimensions.height;
            // gs.mowsewears[mowseWearTokenId].dimensions.transform = baseMetadata.baseDimensions.transform;
            // gs.mowsewears[mowseWearTokenId].dimensions.style = baseMetadata.baseDimensions.style;
            // gs.mowsewears[mowseWearTokenId].dimensions.image = baseMetadata.baseDimensions.image;
            // gs.mowsewears[mowseWearTokenId].dimensions.weight = baseMetadata.baseDimensions.weight;
    
            // random number max 2147483647 bc bonus stats are int32
            // random number max 4294967295 bc bonus stats are uint32
            (uint256 _randomness, uint256 _randomSeed) = _getRandomSeed();
            uint256 itemRarity = updateMowseWearSkillPoints(mowseWearTokenId, _randomness, _randomSeed, minRarity);
    
            // Only set on mint
            gs.mowsewears[mowseWearTokenId].maxAlterCount = (_randomness % (itemRarity + 4)) + 1; // Max alter count increases with rarity, min 1
        }
    
        function _getRandomSeed() internal returns (uint256 randomness, uint256 randomSeed) {
            uint256 _randomness = uint256(
                keccak256(abi.encodePacked(gs.mowseWearPrngSeed, gs.prngNonce++, block.timestamp, msg.sender, blockhash(block.number - 1)))
            );
            uint256 _randomSeed = _randomness % 4294967295;
            return (_randomness, _randomSeed);
        }
    
        function updateMowseWearSkillPoints(
            uint256 mowseWearTokenId,
            uint256 _randomness,
            uint256 _randomSeed,
            uint8 minRarity
        ) internal returns (uint8) {
            gs.mowsewears[mowseWearTokenId].bonuses.randomness = _randomness;
            (uint8 itemRarity, MowseSkillTypes memory mowseSkillTypes) = _setupBonuses(_randomness, _randomSeed, minRarity);
            gs.mowsewears[mowseWearTokenId].bonuses.itemRarityIV = _randomSeed;
            gs.mowsewears[mowseWearTokenId].bonuses.itemRarity = itemRarity;
            gs.mowsewears[mowseWearTokenId].bonuses.baseSkillLevelBoosts[0] = mowseSkillTypes.charisma;
            gs.mowsewears[mowseWearTokenId].bonuses.baseSkillLevelBoosts[1] = mowseSkillTypes.constitution;
            gs.mowsewears[mowseWearTokenId].bonuses.baseSkillLevelBoosts[2] = mowseSkillTypes.dexterity;
            gs.mowsewears[mowseWearTokenId].bonuses.baseSkillLevelBoosts[3] = mowseSkillTypes.intelligence;
            gs.mowsewears[mowseWearTokenId].bonuses.baseSkillLevelBoosts[4] = mowseSkillTypes.luck;
            gs.mowsewears[mowseWearTokenId].bonuses.baseSkillLevelBoosts[5] = mowseSkillTypes.strength;
            gs.mowsewears[mowseWearTokenId].bonuses.baseSkillLevelBoosts[6] = mowseSkillTypes.wisdom;
    
            return itemRarity;
        }
    
        function _setupBonuses(
            uint256 _randomness,
            uint256 _randomSeed,
            uint8 minRarity
        ) internal returns (uint8 rarity, MowseSkillTypes memory skillTypes) {
            uint8 itemRarity;
            int32[SKILL_TYPE_NUM] memory bonusLevels;
            if (_randomSeed < 2576980377) {
                itemRarity = 0; // Common       // ~ 0.6
            } else if (_randomSeed < 3865470565) {
                itemRarity = 1; // Uncommon     // ~ 0.4
            } else if (_randomSeed < 4252017622) {
                itemRarity = 2; // Rare         // ~ 0.1
            } else if (_randomSeed < 4294537798) {
                itemRarity = 3; // Epic         // ~ 0.01
            } else if (_randomSeed < 4294967294) {
                itemRarity = 4; // Legendary    // ~ 0.0001
            } else if (_randomSeed == 4294967294) {
                itemRarity = 5; // 5 is Unique  // Ultimate rarity 1/4294967295 or 2.328 e-10
            }
            // override itemRarity if minRarity is input
            if (itemRarity < minRarity) {
                itemRarity = minRarity;
            }
            uint256 randomness = _randomness;
            // Minimum 3 bonuses, max 1024 + 2
            for (uint32 i = 0; i < (2 ** (2 * itemRarity) + 2); i++) {
                randomness >>= 2;
                if (randomness == 0) {
                    (uint256 newRandomness, ) = _getRandomSeed();
                    randomness = newRandomness;
                }
                uint256 bonusIndex = randomness % SKILL_TYPE_NUM;
                // 75% chance to have positive value, 25% negative
                uint256 bonusValue = randomness % 4;
                if (bonusValue < 3) {
                    bonusLevels[bonusIndex]++;
                } else {
                    bonusLevels[bonusIndex]--;
                }
            }
            MowseSkillTypes memory mowseSkillTypes;
            mowseSkillTypes.charisma = bonusLevels[0];
            mowseSkillTypes.constitution = bonusLevels[1];
            mowseSkillTypes.dexterity = bonusLevels[2];
            mowseSkillTypes.intelligence = bonusLevels[3];
            mowseSkillTypes.luck = bonusLevels[4];
            mowseSkillTypes.strength = bonusLevels[5];
            mowseSkillTypes.wisdom = bonusLevels[6];
    
            return (itemRarity, mowseSkillTypes);
        }
    
        function _updateMowseStats(uint256 mowseId) internal {
            int32[SKILL_TYPE_NUM] memory stats;
            for (uint32 i = 0; i < EQUIPPED_WEARABLE_SLOTS; i++) {
                // Needs to have a mowsewear equipped
                if (gs.mowses[mowseId].equippedMowseWearByTokenIds[i] != 0) {
                    for (uint32 j = 0; j < SKILL_TYPE_NUM; j++) {
                        stats[j] += gs.mowsewears[gs.mowses[mowseId].equippedMowseWearByTokenIds[i]].bonuses.baseSkillLevelBoosts[j];
                        stats[j] += gs.mowsewears[gs.mowses[mowseId].equippedMowseWearByTokenIds[i]].bonuses.additionalSkillLevelBoosts[j];
                    }
                }
            }
    
            gs.mowses[mowseId].skillLevelBoosts = stats;
        }
    
        function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(SolidStateERC721) {
            super._beforeTokenTransfer(from, to, tokenId);
            // Only allow Non-transferrable MowseWear to be minted but not transferred
            if (from != address(0)) {
                if (gs.mowsewears[tokenId].metadata.nonTransferrable) revert MowseWearNotTransferrable(tokenId);
            }
            if (gs.mowsewears[tokenId].isEquipped) revert MowseWearMustBeUnequipped("MowseWear must be unequipped to transfer");
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    // import "@openzeppelin/contracts/utils/Context.sol";
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    import "../libraries/SlothVerifiableDelay.sol";
    
    // import {GameStorage} from "../libraries/LibStorage.sol";
    
    import {MowseWearFacet} from "./MowseWearFacet.sol";
    import {AccessControlFacet} from "./AccessControlFacet.sol";
    import {GameStorageFacet} from "./GameStorageFacet.sol";
    import {MowseVisualizerFacet} from "./MowseVisualizerFacet.sol";
    import {WithdrawalFacet} from "./WithdrawalFacet.sol";
    
    import "../MowseLootbox.sol";
    import "../MowseGold.sol";
    import "./../libraries/LibStorage.sol";
    
    contract RandomFortuneFacet {
        using Strings for uint256;
        using Strings for uint64;
        using Strings for uint16;
        using Strings for address;
        GameStorage internal gs;
    
        event RandomFortune(address indexed player, uint256 indexed timestamp);
        event ClaimRandomFortune(address indexed player);
        
        error ContractPaused(string);
        error MissingAdminRole(string);
        error NoFortuneToClaim(string);
        error NoFortuneFound(address caller, uint256 timestamp);
        
        modifier notPaused() {
            if (gs.paused) revert ContractPaused("Contract is paused");
            _;
        }
    
        function hasRandomFortuneToClaim(address sender) external view returns (bool) {
            return gs.randomFortuneCount[sender] > 0;
        }
    
        function getRandomFortuneCount(address sender) external view returns (uint256) {
            return gs.randomFortuneCount[sender];
        }
    
        function randomFortune(address caller) external {
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Mowse Admin role");
    
            // first get vdf seed
            uint256 vdfSeed = uint256(keccak256(abi.encodePacked(caller, gs.randomFortuneNonce++, block.timestamp, blockhash(block.number - 1))));
            console.log("random fortune seed");
            console.log(block.timestamp);
            
            // 1/1000 chance to succeed
            if (vdfSeed % 1000 == 0) {
              gs.randomFortuneTimestampToSeed[caller][block.timestamp] = vdfSeed;
              gs.randomFortuneCount[caller]++;
    
              emit RandomFortune(caller, block.timestamp);
            }
        }
        
        function claimRandomFortune(address caller) external notPaused {
            if (gs.randomFortuneCount[caller] == 0) revert NoFortuneToClaim("No Fortune to Claim");
    
            emit ClaimRandomFortune(caller);
        }
    
        function _prove(uint256 proof, uint256 seed) internal view returns (bool) {
            return SlothVerifiableDelay.verify(proof, seed, gs.randomFortunePrime, gs.randomFortuneIterations);
        }
    
        function getRandomFortuneProofByTimestamp(address caller, uint256 timestamp) external view returns (uint256) {
            if (gs.randomFortuneTimestampToSeed[caller][timestamp] == 0) revert NoFortuneFound(caller, timestamp);
    
            return SlothVerifiableDelay.compute(gs.randomFortuneTimestampToSeed[caller][timestamp], gs.randomFortunePrime, gs.randomFortuneIterations);
        }
    
        function proveRandomFortuneByTimestamp(address caller, uint256 timestamp, uint256 proof) external view returns (bool) {
            if (gs.randomFortuneTimestampToSeed[caller][timestamp] == 0) revert NoFortuneFound(caller, timestamp);
    
            if (_prove(proof, gs.randomFortuneTimestampToSeed[caller][timestamp])) {
                return true;
            } else {
                return false;
            }
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.8;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    import {LibDiamond} from "../libraries/LibDiamond.sol";
    
    import {GameStorage} from "../libraries/LibStorage.sol";
    
    import {GameStorageFacet} from "./GameStorageFacet.sol";
    import {AccessControlFacet} from "./AccessControlFacet.sol";
    
    import "../MowseAvatar.sol";
    import "../MowseGold.sol";
    import "./../libraries/LibStorage.sol";
    
    contract WithdrawalFacet {
        using Strings for uint256;
        GameStorage internal gs;
    
        error MissingAdminRole(string);
        error ContractPaused(string);
        error DidNotReceiveFromMowseAvatar();
        error DidNotReceiveFromMowseGold();
        error DidNotSendToGameDeveloper(uint256);
        error DidNotSendToTeam();
        error DidNotSendToBackend();
        error DidNotSendToTreasury();
        error DidNotSendToDevRoyalty();
    
        modifier notPaused() {
            if (gs.paused) revert ContractPaused("Contract is paused");
            _;
        }
    
        function changeTeamAddress(address payable newTeamAddress) public {
            if (!AccessControlFacet(address(this)).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Missing Admin role");
            gs.teamAddress = newTeamAddress;
        }
    
        function changeBackendAddress(address payable newBackendAddress) public {
            if (!AccessControlFacet(address(this)).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Missing Admin role");
            gs.backendAddress = newBackendAddress;
        }
    
        function changeTreasuryAddress(address payable newTreasuryAddress) public {
            if (!AccessControlFacet(address(this)).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Missing Admin role");
            gs.treasuryAddress = newTreasuryAddress;
        }
    
        function changeTeamTax(uint256 newTax) public {
            if (!AccessControlFacet(address(this)).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Missing Admin role");
            gs.teamTaxBasisPoints = newTax;
        }
    
        function changeBackendTax(uint256 newTax) public {
            if (!AccessControlFacet(address(this)).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Missing Admin role");
            gs.backendTaxBasisPoints = newTax;
        }
    
        // withdraw calls the withdraw() method on other contracts that receive ether (MowseAvatar and MowseGold contracts) so that funds are sent to this contract.
        // Then distribute to respective parties
        function withdraw() public payable notPaused {
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
            MowseAvatar mowseavatar = MowseAvatar(gs.mowseAvatarContractAddress);
    
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Missing Admin role");
            console.log("Begin receiving from contracts", address(this).balance);
            // Withdraw all ETH from MowseAvatar and MowseGold contracts to this contract
            mowseavatar.withdraw();
            console.log("received from mowse avatar");
            mowsegold.withdraw();
            console.log("Finished receiving from other contracts", address(this).balance);
            uint256 devRoyaltyPayout = (address(this).balance * gs.devRoyaltyTaxBasisPoints) / 10000; // doublesharp
            uint256 developerPayout = (address(this).balance * gs.developerTaxBasisPoints) / 10000; // game devs
            uint256 teamPayout = (address(this).balance * gs.teamTaxBasisPoints) / 10000; // team
            uint256 backendPayout = (address(this).balance * gs.backendTaxBasisPoints) / 10000; // backend
            uint256 treasuryPayout = gs.activeMowseGamesCount > 0
                ? address(this).balance - devRoyaltyPayout - teamPayout - backendPayout - developerPayout
                : address(this).balance - teamPayout - backendPayout - developerPayout;
            console.log("Developer Payout", developerPayout);
            console.log("Team Payout", teamPayout);
            console.log("Backend Payout", backendPayout);
            console.log("Treasury Payout", treasuryPayout);
    
            // Pay game developers
            if (gs.activeMowseGamesCount > 0) {
                uint256 individualDeveloperPayout = developerPayout / gs.activeMowseGamesCount;
                for (uint256 i = 1; i < gs.mowseGamesCount + 1; i++) {
                    if (gs.mowsegames[i].active) {
                        console.log("Pay this dev", individualDeveloperPayout);
                        (bool sentToDeveloper, ) = gs.mowsegames[i].developerAddress.call{value: individualDeveloperPayout}("");
                        if (!sentToDeveloper) revert DidNotSendToGameDeveloper(i);
                    }
                }
            }
            console.log("Finished paying game devs");
            console.log(developerPayout + teamPayout + backendPayout + treasuryPayout);
    
            // Pay team, backend, and treasury
            (bool sentToDevRoyalty, ) = gs.devRoyaltyAddress.call{value: devRoyaltyPayout}("");
            if (!sentToDevRoyalty) revert DidNotSendToDevRoyalty();
            (bool sentToTeam, ) = gs.teamAddress.call{value: teamPayout}("");
            if (!sentToTeam) revert DidNotSendToTeam();
            (bool sentToBackend, ) = gs.backendAddress.call{value: backendPayout}("");
            if (!sentToBackend) revert DidNotSendToBackend();
            (bool sendToTreasury, ) = gs.treasuryAddress.call{value: treasuryPayout}("");
            if (!sendToTreasury) revert DidNotSendToTreasury();
    
            console.log("transfer all mgold to treasury");
            // Withdraw all MGOLD from this contract to the treasuryAddress
            uint256 mowseGoldBalance = mowsegold.balanceOf(address(this));
            mowsegold.transferFrom(address(this), gs.treasuryAddress, mowseGoldBalance);
            console.log("finished");
        }
    
        // withdrawFallback if for some reason withdraw does not work (too much gas from trying to send to too many wallets? idk)
        function withdrawFallback() public payable notPaused {
            MowseGold mowsegold = MowseGold(gs.mowseGoldContractAddress);
            MowseAvatar mowseavatar = MowseAvatar(gs.mowseAvatarContractAddress);
    
            if (!AccessControlFacet(gs.diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Missing Admin role");
            // Withdraw all ETH from MowseAvatar and MowseGold contracts to this contract
            mowseavatar.withdraw();
            mowsegold.withdraw();
    
            (bool sendToTreasury, ) = gs.treasuryAddress.call{value: address(this).balance}("");
            if (!sendToTreasury) revert DidNotSendToTreasury();
    
            // Withdraw all MGOLD from this contract to the treasuryAddress
            uint256 mowseGoldBalance = mowsegold.balanceOf(address(this));
            mowsegold.transferFrom(address(this), gs.treasuryAddress, mowseGoldBalance);
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    /******************************************************************************\
    * Author: Nick Mudge <nick@perfectabstractions.com> (https://twitter.com/mudgen)
    * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
    /******************************************************************************/
    
    interface IDiamondCut {
        enum FacetCutAction {Add, Replace, Remove}
        // Add=0, Replace=1, Remove=2
    
        struct FacetCut {
            address facetAddress;
            FacetCutAction action;
            bytes4[] functionSelectors;
        }
    
        /// @notice Add/replace/remove any number of functions and optionally execute
        ///         a function with delegatecall
        /// @param _diamondCut Contains the facet addresses and function selectors
        /// @param _init The address of the contract or facet to execute _calldata
        /// @param _calldata A function call, including function selector and arguments
        ///                  _calldata is executed with delegatecall on _init
        function diamondCut(
            FacetCut[] calldata _diamondCut,
            address _init,
            bytes calldata _calldata
        ) external;
    
        event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.12;
    
    interface IMowseBank {
        function getWalletAddress(uint256 _mowsepackId) external view returns (address);
    
        function getBalanceForMowse(uint256 mowseId) external view returns (uint256);
    
        function getBalanceForWallet(address wallet) external view returns (uint256);
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.12;
    
    import '@openzeppelin/contracts/interfaces/IERC20.sol';
    import '@openzeppelin/contracts/interfaces/IERC721.sol';
    import '@openzeppelin/contracts/interfaces/IERC1155.sol';
    
    interface IMowseWallet {
        /// @notice A structure to define arbitrary contract calls
        struct Call {
            address to;
            uint256 value;
            bytes data;
        }
    
        /// @notice A structure to define ERC20 transfer contents
        struct TransferERC20 {
            IERC20 token;
            uint256 amount;
        }
    
        /// @notice A structure to define ERC721 transfer contents
        struct TransferERC721 {
            IERC721 token;
            uint256[] tokenIds;
        }
    
        /// @notice A structure to define ERC1155 transfer contents
        struct TransferERC1155 {
            IERC1155 token;
            uint256[] ids;
            uint256[] amounts;
            bytes data;
        }
    
        /// @notice Emitted when the contract transfer ether
        event TransferredFantom(address indexed to, uint256 amount);
    
        /// @notice Emitted when an ERC20 token is withdrawn
        event TransferredERC20(address indexed token, uint256 amount);
    
        /// @notice Emitted when an ERC721 token is withdrawn
        event TransferredERC721(address indexed token, uint256[] tokenIds);
    
        /// @notice Emitted when an ERC1155 token is withdrawn
        event TransferredERC1155(address indexed token, uint256[] ids, uint256[] amounts, bytes data);
    }

    // SPDX-License-Identifier: MIT
    
    // Credit https://github.com/derekchiang/loot-name/blob/main/contracts/LootName.sol
    
    pragma solidity ^0.8.12;
    
    /// [MIT License]
    /// @title Base64
    /// @notice Provides a function for encoding some bytes in base64
    /// @author Brecht Devos <brecht@loopring.org>
    library Base64 {
        bytes internal constant TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    
        /// @notice Encodes some bytes to the base64 representation
        function encode(bytes memory data) internal pure returns (string memory) {
            uint256 len = data.length;
            if (len == 0) return '';
    
            // multiply by 4/3 rounded up
            uint256 encodedLen = 4 * ((len + 2) / 3);
    
            // Add some extra buffer at the end
            bytes memory result = new bytes(encodedLen + 32);
    
            bytes memory table = TABLE;
    
            assembly {
                let tablePtr := add(table, 1)
                let resultPtr := add(result, 32)
    
                for {
                    let i := 0
                } lt(i, len) {
    
                } {
                    i := add(i, 3)
                    let input := and(mload(add(data, i)), 0xffffff)
    
                    let out := mload(add(tablePtr, and(shr(18, input), 0x3F)))
                    out := shl(8, out)
                    out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF))
                    out := shl(8, out)
                    out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF))
                    out := shl(8, out)
                    out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF))
                    out := shl(224, out)
    
                    mstore(resultPtr, out)
    
                    resultPtr := add(resultPtr, 4)
                }
    
                switch mod(len, 3)
                case 1 {
                    mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
                }
                case 2 {
                    mstore(sub(resultPtr, 1), shl(248, 0x3d))
                }
    
                mstore(result, encodedLen)
            }
    
            return string(result);
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    /******************************************************************************\
    * Author: Nick Mudge <nick@perfectabstractions.com> (https://twitter.com/mudgen)
    * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
    /******************************************************************************/
    import { IDiamondCut } from "../interfaces/IDiamondCut.sol";
    
    // Remember to add the loupe functions from DiamondLoupeFacet to the diamond.
    // The loupe functions are required by the EIP2535 Diamonds standard
    
    error InitializationFunctionReverted(address _initializationContractAddress, bytes _calldata);
    
    library LibDiamond {
        bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
    
        struct FacetAddressAndPosition {
            address facetAddress;
            uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array
        }
    
        struct FacetFunctionSelectors {
            bytes4[] functionSelectors;
            uint256 facetAddressPosition; // position of facetAddress in facetAddresses array
        }
    
        struct DiamondStorage {
            // maps function selector to the facet address and
            // the position of the selector in the facetFunctionSelectors.selectors array
            mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;
            // maps facet addresses to function selectors
            mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
            // facet addresses
            address[] facetAddresses;
            // Used to query if a contract implements an interface.
            // Used to implement ERC-165.
            mapping(bytes4 => bool) supportedInterfaces;
            // owner of the contract
            address contractOwner;
        }
    
        function diamondStorage() internal pure returns (DiamondStorage storage ds) {
            bytes32 position = DIAMOND_STORAGE_POSITION;
            assembly {
                ds.slot := position
            }
        }
    
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        function setContractOwner(address _newOwner) internal {
            DiamondStorage storage ds = diamondStorage();
            address previousOwner = ds.contractOwner;
            ds.contractOwner = _newOwner;
            emit OwnershipTransferred(previousOwner, _newOwner);
        }
    
        function contractOwner() internal view returns (address contractOwner_) {
            contractOwner_ = diamondStorage().contractOwner;
        }
    
        function enforceIsContractOwner() internal view {
            require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner");
        }
    
        event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata);
    
        // Internal function version of diamondCut
        function diamondCut(
            IDiamondCut.FacetCut[] memory _diamondCut,
            address _init,
            bytes memory _calldata
        ) internal {
            for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) {
                IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action;
                if (action == IDiamondCut.FacetCutAction.Add) {
                    addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
                } else if (action == IDiamondCut.FacetCutAction.Replace) {
                    replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
                } else if (action == IDiamondCut.FacetCutAction.Remove) {
                    removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
                } else {
                    revert("LibDiamondCut: Incorrect FacetCutAction");
                }
            }
            emit DiamondCut(_diamondCut, _init, _calldata);
            initializeDiamondCut(_init, _calldata);
        }
    
        function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
            require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut");
            DiamondStorage storage ds = diamondStorage();        
            require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)");
            uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
            // add new facet address if it does not exist
            if (selectorPosition == 0) {
                addFacet(ds, _facetAddress);            
            }
            for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
                bytes4 selector = _functionSelectors[selectorIndex];
                address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
                require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists");
                addFunction(ds, selector, selectorPosition, _facetAddress);
                selectorPosition++;
            }
        }
    
        function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
            require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut");
            DiamondStorage storage ds = diamondStorage();
            require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)");
            uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
            // add new facet address if it does not exist
            if (selectorPosition == 0) {
                addFacet(ds, _facetAddress);
            }
            for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
                bytes4 selector = _functionSelectors[selectorIndex];
                address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
                require(oldFacetAddress != _facetAddress, "LibDiamondCut: Can't replace function with same function");
                removeFunction(ds, oldFacetAddress, selector);
                addFunction(ds, selector, selectorPosition, _facetAddress);
                selectorPosition++;
            }
        }
    
        function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
            require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut");
            DiamondStorage storage ds = diamondStorage();
            // if function does not exist then do nothing and return
            require(_facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)");
            for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
                bytes4 selector = _functionSelectors[selectorIndex];
                address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
                removeFunction(ds, oldFacetAddress, selector);
            }
        }
    
        function addFacet(DiamondStorage storage ds, address _facetAddress) internal {
            enforceHasContractCode(_facetAddress, "LibDiamondCut: New facet has no code");
            ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds.facetAddresses.length;
            ds.facetAddresses.push(_facetAddress);
        }    
    
    
        function addFunction(DiamondStorage storage ds, bytes4 _selector, uint96 _selectorPosition, address _facetAddress) internal {
            ds.selectorToFacetAndPosition[_selector].functionSelectorPosition = _selectorPosition;
            ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(_selector);
            ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress;
        }
    
        function removeFunction(DiamondStorage storage ds, address _facetAddress, bytes4 _selector) internal {        
            require(_facetAddress != address(0), "LibDiamondCut: Can't remove function that doesn't exist");
            // an immutable function is a function defined directly in a diamond
            require(_facetAddress != address(this), "LibDiamondCut: Can't remove immutable function");
            // replace selector with last selector, then delete last selector
            uint256 selectorPosition = ds.selectorToFacetAndPosition[_selector].functionSelectorPosition;
            uint256 lastSelectorPosition = ds.facetFunctionSelectors[_facetAddress].functionSelectors.length - 1;
            // if not the same then replace _selector with lastSelector
            if (selectorPosition != lastSelectorPosition) {
                bytes4 lastSelector = ds.facetFunctionSelectors[_facetAddress].functionSelectors[lastSelectorPosition];
                ds.facetFunctionSelectors[_facetAddress].functionSelectors[selectorPosition] = lastSelector;
                ds.selectorToFacetAndPosition[lastSelector].functionSelectorPosition = uint96(selectorPosition);
            }
            // delete the last selector
            ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop();
            delete ds.selectorToFacetAndPosition[_selector];
    
            // if no more selectors for facet address then delete the facet address
            if (lastSelectorPosition == 0) {
                // replace facet address with last facet address and delete last facet address
                uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1;
                uint256 facetAddressPosition = ds.facetFunctionSelectors[_facetAddress].facetAddressPosition;
                if (facetAddressPosition != lastFacetAddressPosition) {
                    address lastFacetAddress = ds.facetAddresses[lastFacetAddressPosition];
                    ds.facetAddresses[facetAddressPosition] = lastFacetAddress;
                    ds.facetFunctionSelectors[lastFacetAddress].facetAddressPosition = facetAddressPosition;
                }
                ds.facetAddresses.pop();
                delete ds.facetFunctionSelectors[_facetAddress].facetAddressPosition;
            }
        }
    
        function initializeDiamondCut(address _init, bytes memory _calldata) internal {
            if (_init == address(0)) {
                return;
            }
            enforceHasContractCode(_init, "LibDiamondCut: _init address has no code");        
            (bool success, bytes memory error) = _init.delegatecall(_calldata);
            if (!success) {
                if (error.length > 0) {
                    // bubble up error
                    /// @solidity memory-safe-assembly
                    assembly {
                        let returndata_size := mload(error)
                        revert(add(32, error), returndata_size)
                    }
                } else {
                    revert InitializationFunctionReverted(_init, _calldata);
                }
            }
        }
    
        function enforceHasContractCode(address _contract, string memory _errorMessage) internal view {
            uint256 contractSize;
            assembly {
                contractSize := extcodesize(_contract)
            }
            require(contractSize > 0, _errorMessage);
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.12;
    import "hardhat/console.sol";
    import {
        MowseDungeonSimplePlayer, 
        MowseDungeonSimpleMonster, 
        MowseDungeonBattleSimulation, 
        MowseDungeonBattleLog, 
        MowseDungeonBattleLogTypeEnum, 
        MowseDungeonBattleLogSourceEnum
    } from "../libraries/LibStorage.sol";
    
    library LibDungeon {
        function _int32ToUint256(int32 _stat) internal pure returns (uint256) {
            return uint256(int256(_stat));
        }
        function getNewSeed(uint256 seed, uint256 newSeed) internal pure returns (uint256) {
            return uint256(keccak256(abi.encodePacked(seed, newSeed)));
        }
        function updateNewSeed(uint256 seed, uint256 newSeed) internal pure returns (uint256) {
            if (newSeed >> 2 == 0) {
                newSeed = getNewSeed(seed, newSeed);
            } else {
                newSeed >>= 2;
            }
            return newSeed;
        }
    
        function _damageShieldAndHealth(uint256 _damage, uint256 _enemyHealth, uint256 _enemyShield) internal pure returns (uint256, uint256) {
            if (_damage > _enemyShield) {
                if (_damage - _enemyShield > _enemyHealth) {
                    _enemyHealth = 0;
                } else {
                    _enemyHealth = _enemyHealth - (_damage - _enemyShield);
                }
                _enemyShield = 0;
            } else {
                _enemyShield = _enemyShield - _damage;
            }
            return (_enemyHealth, _enemyShield);
        }
        // For true damage/pierce attacks
        function _damageHealth(uint256 _damage, uint256 _enemyHealth) internal pure returns (uint256) {
            if (_damage > _enemyHealth) {
                return 0;
            } else {
                return _enemyHealth - _damage;
            }
        }
        function _checkPlayerRevive(MowseDungeonSimplePlayer memory _player) internal pure returns (MowseDungeonSimplePlayer memory) {
            // Check if player has died
            if (_player.currentHealth == 0) {
                // Revive
                if (_player.statusEffects.revive > 0) {
                    _player.currentHealth = _player.maxHealth;
                    _player.statusEffects.revive = 0;
                }
                // LAST_HOPE
                if (_player.statusEffects.lastHope > 0) {
                    _player.currentHealth = 1;
                    _player.statusEffects.lastHope = 0;
                }
            }
            return _player;
        }
    
        function _checkMonsterRevive(MowseDungeonSimpleMonster memory _monster) internal pure returns (MowseDungeonSimpleMonster memory) {
            // Check if monster has died
            if (_monster.currentHealth == 0) {
                // Revive
                if (_monster.statusEffects.revive > 0) {
                    _monster.currentHealth = _monster.maxHealth;
                    _monster.statusEffects.revive = 0;
                }
                // LAST_HOPE
                if (_monster.statusEffects.lastHope > 0) {
                    _monster.currentHealth = 1;
                    _monster.statusEffects.lastHope = 0;
                }
            }
            return _monster;
        }
    
    function _applyStatusEffectsFromTrinketsToMonster(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory, MowseDungeonSimpleMonster memory) {
            if (_battleSimulation.player.trinkets.poisonDab > 0) {
                if (_monster.statusEffects.poisonResist == 0) {
                    _monster.statusEffects.poison += _battleSimulation.player.trinkets.poisonDab * (_battleSimulation.player.statusEffects.doubleUp + 1);
                    _battleSimulation = _addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_POISON, _battleSimulation.player.trinkets.poisonDab * (_battleSimulation.player.statusEffects.doubleUp + 1));
                }
            }
            if (_battleSimulation.player.trinkets.kindle > 0) {
                if (_monster.statusEffects.fireResist == 0) {
                    _monster.statusEffects.burn += _battleSimulation.player.trinkets.kindle * (_battleSimulation.player.statusEffects.doubleUp + 1);
                    _battleSimulation = _addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BURN, _battleSimulation.player.trinkets.kindle * (_battleSimulation.player.statusEffects.doubleUp + 1));
                }
            }
            if (_battleSimulation.player.trinkets.bloodLetter > 0) {
                _monster.statusEffects.bleed += _battleSimulation.player.trinkets.bloodLetter * (_battleSimulation.player.statusEffects.doubleUp + 1);
                _battleSimulation = _addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_BLEED, _battleSimulation.player.trinkets.bloodLetter * (_battleSimulation.player.statusEffects.doubleUp + 1));
            }
            if (_battleSimulation.player.trinkets.cursedRing > 0) {
                _monster.statusEffects.curse += _battleSimulation.player.trinkets.cursedRing * (_battleSimulation.player.statusEffects.doubleUp + 1);
                _battleSimulation = _addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_CURSE, _battleSimulation.player.trinkets.cursedRing * (_battleSimulation.player.statusEffects.doubleUp + 1));
            }
            if (_battleSimulation.player.trinkets.stunRod > 0) {
                if (_monster.statusEffects.stunResist == 0 && _monster.statusEffects.unstoppable == 0 && _monster.isBoss == false) {
                    _battleSimulation.seed = updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
                    if (_battleSimulation.seed % 100 < 20 + _battleSimulation.player.trinkets.stunRod * 20) {
                        _monster.statusEffects.stun++;
                        _battleSimulation = _addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STUN, 1);
                    }
                }
            }
            if (_battleSimulation.player.trinkets.silenceBell > 0) {
                if (_monster.statusEffects.unstoppable == 0 && _monster.isBoss == false) {
                    _battleSimulation.seed = updateNewSeed(_battleSimulation.dungeonId, _battleSimulation.seed);
                    if (_battleSimulation.seed % 100 < 20 + _battleSimulation.player.trinkets.silenceBell * 20) {
                        _monster.statusEffects.silence++;
                        _battleSimulation = _addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_SILENCE, 1);
                    }
                }
            }
            if (_battleSimulation.player.trinkets.strengthBelt > 0) {
                _battleSimulation.player.statusEffects.strengthen +=
                    _battleSimulation.player.trinkets.strengthBelt *
                    (_battleSimulation.player.statusEffects.doubleUp + 1);
                    _battleSimulation = _addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_STRENGTHEN, _battleSimulation.player.trinkets.strengthBelt * (_battleSimulation.player.statusEffects.doubleUp + 1));
            }
            if (_battleSimulation.player.trinkets.weakenedChain > 0) {
                _monster.statusEffects.weaken += _battleSimulation.player.trinkets.weakenedChain * (_battleSimulation.player.statusEffects.doubleUp + 1);
                _battleSimulation = _addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_WEAKEN, _battleSimulation.player.trinkets.weakenedChain * (_battleSimulation.player.statusEffects.doubleUp + 1));
            }
            if (_battleSimulation.player.trinkets.leech > 0) {
                _battleSimulation.player.currentHealth +=
                    ((_battleSimulation.player.tempAttack * (10 * _battleSimulation.player.trinkets.leech)) / 100) *
                    (_battleSimulation.player.statusEffects.doubleUp + 1);
                _battleSimulation = _addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.HEAL, MowseDungeonBattleLogSourceEnum.TRINKET_LEECH, MowseDungeonBattleLogSourceEnum.PLAYER, ((_battleSimulation.player.tempAttack * (10 * _battleSimulation.player.trinkets.leech)) / 100) * (_battleSimulation.player.statusEffects.doubleUp + 1));
    
                // Make sure health doesn't exceed maxHealth
                if (_battleSimulation.player.currentHealth > _battleSimulation.player.maxHealth) {
                    _battleSimulation.player.currentHealth = _battleSimulation.player.maxHealth;
                }
                _battleSimulation.achievementProgress.healthHealed +=
                    ((_battleSimulation.player.tempAttack * (10 * _battleSimulation.player.trinkets.leech)) / 100) *
                    (_battleSimulation.player.statusEffects.doubleUp + 1);
            }
    
            return (_battleSimulation, _monster);
        }
        function _monsterRetaliate(
            MowseDungeonBattleSimulation memory _battleSimulation,
            MowseDungeonSimpleMonster memory _monster,
            uint256 _monsterIndex
        ) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monster.statusEffects.counter > 0) {
                (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = _damageShieldAndHealth(
                    _monster.attack,
                    _battleSimulation.player.currentHealth,
                    _battleSimulation.player.shield
                );
                _battleSimulation.achievementProgress.damageTaken += _monster.attack;
                _battleSimulation = _addToBattleLogForPlayerFromMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, _monster.attack);
                _battleSimulation.player = _checkPlayerRevive(_battleSimulation.player);
            }
            if (_monster.statusEffects.thorns > 0) {
                (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = _damageShieldAndHealth(
                    5 * _monster.statusEffects.thorns,
                    _battleSimulation.player.currentHealth,
                    _battleSimulation.player.shield
                );
                _battleSimulation.achievementProgress.damageTaken += 5 * _monster.statusEffects.thorns;
                _battleSimulation = _addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_THORNS, MowseDungeonBattleLogSourceEnum.PLAYER, 5 * _monster.statusEffects.thorns);
                _battleSimulation.player = _checkPlayerRevive(_battleSimulation.player);
            }
            if (_monster.statusEffects.wrathfulReprisal > 0) {
                (_battleSimulation.player.currentHealth, _battleSimulation.player.shield) = _damageShieldAndHealth(
                    _battleSimulation.player.attack,
                    _battleSimulation.player.currentHealth,
                    _battleSimulation.player.shield
                );
                _battleSimulation = _addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.STATUS_EFFECT_WRATHFUL_REPRISAL, MowseDungeonBattleLogSourceEnum.PLAYER, _battleSimulation.player.attack);
                _battleSimulation.achievementProgress.damageTaken += _battleSimulation.player.attack;
                _battleSimulation.player = _checkPlayerRevive(_battleSimulation.player);
            }
            return _battleSimulation;
        }
        function _dealPlayerDamageToMonster(MowseDungeonBattleSimulation memory _battleSimulation, MowseDungeonSimpleMonster memory _monster, uint256 _monsterIndex) internal pure returns (MowseDungeonBattleSimulation memory, MowseDungeonSimpleMonster memory) {
            if (_monster.statusEffects.invincible == 0) {
                if (_battleSimulation.player.statusEffects.pierce > 0) {
                    _monster.currentHealth = _damageHealth(_battleSimulation.player.tempAttack, _monster.currentHealth);
                } else {
                    (_monster.currentHealth, _monster.shield) = _damageShieldAndHealth(
                        _battleSimulation.player.tempAttack,
                        _monster.currentHealth,
                        _monster.shield
                    );
                }
                _battleSimulation = _addToBattleLogForMonsterIndex(_battleSimulation, _monsterIndex, MowseDungeonBattleLogTypeEnum.DAMAGE, MowseDungeonBattleLogSourceEnum.PLAYER, _battleSimulation.player.tempAttack);
                _battleSimulation.achievementProgress.damageDealt += _battleSimulation.player.tempAttack;
                _monster = _checkMonsterRevive(_monster);
            }
            (_battleSimulation, _monster) = _applyStatusEffectsFromTrinketsToMonster(_battleSimulation, _monster, _monsterIndex);
            if (_monster.currentHealth > 0) {
                _battleSimulation = _monsterRetaliate(_battleSimulation, _monster, _monsterIndex);
            }
            return (_battleSimulation, _monster);
        }
    
        function _addToBattleLogForMonsterIndex(MowseDungeonBattleSimulation memory _battleSimulation, uint256 _monsterIndex, MowseDungeonBattleLogTypeEnum _logType, MowseDungeonBattleLogSourceEnum _source, uint256 _amount) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                _battleSimulation = _addToBattleLog(_battleSimulation, _logType, _source, MowseDungeonBattleLogSourceEnum.MONSTER_1, _amount);
            } else if (_monsterIndex == 1) {
                _battleSimulation = _addToBattleLog(_battleSimulation, _logType, _source, MowseDungeonBattleLogSourceEnum.MONSTER_2, _amount);
            } else {
                _battleSimulation = _addToBattleLog(_battleSimulation, _logType, _source, MowseDungeonBattleLogSourceEnum.MONSTER_3, _amount);
            }
            return _battleSimulation;
        }
        function _addToBattleLogForPlayerFromMonsterIndex(MowseDungeonBattleSimulation memory _battleSimulation, uint256 _monsterIndex, MowseDungeonBattleLogTypeEnum _logType, uint256 _amount) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_monsterIndex == 0) {
                _battleSimulation = _addToBattleLog(_battleSimulation, _logType, MowseDungeonBattleLogSourceEnum.MONSTER_1, MowseDungeonBattleLogSourceEnum.PLAYER, _amount);
            } else if (_monsterIndex == 1) {
                _battleSimulation = _addToBattleLog(_battleSimulation, _logType, MowseDungeonBattleLogSourceEnum.MONSTER_2, MowseDungeonBattleLogSourceEnum.PLAYER, _amount);
            } else {
                _battleSimulation = _addToBattleLog(_battleSimulation, _logType, MowseDungeonBattleLogSourceEnum.MONSTER_3, MowseDungeonBattleLogSourceEnum.PLAYER, _amount);
            }
            return _battleSimulation;
        }
        function _addStatusEffectToBattleLogForPlayer(MowseDungeonBattleSimulation memory _battleSimulation, MowseDungeonBattleLogSourceEnum _source, uint256 _amount) internal pure returns (MowseDungeonBattleSimulation memory) {
            _battleSimulation = _addToBattleLog(_battleSimulation, MowseDungeonBattleLogTypeEnum.APPLY_STATUS_EFFECT, _source, MowseDungeonBattleLogSourceEnum.PLAYER, _amount);
            return _battleSimulation;
        }
        function _addToBattleLog(MowseDungeonBattleSimulation memory _battleSimulation, MowseDungeonBattleLogTypeEnum _logType, MowseDungeonBattleLogSourceEnum _source, MowseDungeonBattleLogSourceEnum _target, uint256 _amount) internal pure returns (MowseDungeonBattleSimulation memory) {
            if (_battleSimulation.battleLogCount >= 100) return _battleSimulation;
            _battleSimulation.battleLog[_battleSimulation.battleLogCount++] = (MowseDungeonBattleLog({logType: _logType, source: _source, target: _target, amount: _amount}));
            return _battleSimulation;
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    import { LibDiamond } from "./LibDiamond.sol";
    
    uint256 constant SKILL_TYPE_NUM = 7;
    uint256 constant STAT_CHARISMA = 0;
    uint256 constant STAT_CONSTITUTION = 1;
    uint256 constant STAT_DEXTERITY = 2;
    uint256 constant STAT_INTELLIGENCE = 3;
    uint256 constant STAT_LUCK = 4;
    uint256 constant STAT_STRENGTH = 5;
    uint256 constant STAT_WISDOM = 6;
    uint256 constant EQUIPPED_WEARABLE_SLOTS = 12;
    bytes32 constant MOWSE_ADMIN_ROLE = keccak256('MOWSE_ADMIN_ROLE');
    bytes32 constant MOWSEWEAR_MINTER_ROLE = keccak256('MOWSEWEAR_MINTER_ROLE');
    bytes32 constant MOWSE_MINTER_ROLE = keccak256('MOWSE_MINTER_ROLE');
    bytes32 constant MOWSEWEAR_ADMIN_ROLE = keccak256('MOWSEWEAR_ADMIN_ROLE');
    bytes32 constant MOWSEGAME_ADMIN_ROLE = keccak256('MOWSEGAME_ADMIN_ROLE');
    bytes32 constant MOWSEBANK_CREATE2_SALT = keccak256('MOWSEBANK_CREATE2_SALT');
    bytes32 constant MOWSELOOTBOX_MINTER_ROLE = keccak256('MOWSELOOTBOX_MINTER_ROLE');
    bytes32 constant MOWSEJOB_ADMIN_ROLE = keccak256('MOWSEJOB_ADMIN_ROLE');
    
    struct GameStorage {
      address diamondAddress;
      address mowseAvatarContractAddress;
      address mowseGoldContractAddress;
      address mowseBankContractAddress;
      address mowseLootboxContractAddress;
    
      bool paused;
      // Mowse = id > 10,000
      uint256 mowseMintPrice;
      uint256 mowseTokenIdCounter; // Current tokenId of Mowse for minting purposes
      uint256 mowseWearTotalCount;  // Total Count of MowseWear minted
      uint256 mowseWearTokenIdCounter; // Current tokenId of MowseWear for minting purposes
      mapping(address => bool) isMowse; // maps Mowse address to bool if it is a mowse address or not
      mapping(address => uint256) mainMowse; // map address to mowseTokenId
      mapping(uint256 => Mowse) mowses; // map mowseAvatar tokenId to Mowse struct
      mapping(uint256 => MowseWear) mowsewears; // map mowsewear tokenId to MowseWear struct
      // mapping(uint256 => MowseLineage) mowselineages;
      mapping(uint8 => mapping(uint16 => MowseWearMetadata)) mowseWearDictionary; // map equippable wearable slot index to dictionary index to get unique mowsewear
      mapping(uint256 => MowseWearMetadata) mowseWearDictionaryByDictionaryIndex; // map dictionary index to mowsewear (for lootbox)
      uint256 mowseWearDictionaryCount; // Counts how many items are in the mowseWearDictionary (for lootbox)
      uint16[EQUIPPED_WEARABLE_SLOTS] mowseWearDictionaryTraitCount; // count of each trait (11 shirts, 10 eyeWear, etc), used for incrementing next traitIndex
      mapping(uint8 => mapping(uint16 => uint256)) mowseWearCountByTraitIndex;  // Counts how many mowsewear are minted per traitIndex (3 Blue Headbands)
      mapping(uint8 => uint256) mowseWearCountByTraitType;  // Counts how many shirts were minted
      mapping(uint16 => string) possibleTraitTypes; // mapping of all possible traitTypes to strings
      string[] initTraitNames;  // used in DiamondInit
    
      // MowseWear
      uint256 mowseWearHemPrice;
    
      // Initial merkel MGOLD aidrop
      // mapping(address => uint256) initialMowseGoldClaimList;  // Used for merkel airdrop
    
      // MowseLootbox
      // lootbox index is the loot pool index; [0-11] are trait type specific, [12] is general pool, 13+ are any other specific pools (weekly chests or seasonal chests)
      mapping(uint64 => MowseLootboxPool) mowselootboxes;  // map lootbox index (traitType + a few more) to MowseLootbox
      // count of current lootbox index
      uint64 mowseLootboxIndexCount;
      // Current tokenId of MowseLootbox for minting purposes
      uint256 mowseLootboxTokenIdCounter;
      // map tokenId to LootboxIndex to get the lootbox data
      mapping(uint256 => uint64) mowseLootboxIndexByTokenId;
      // large prime used for VDF
      uint256 mowseLootboxPrime;
      // iterations for VDF
      uint256 mowseLootboxIterations;
      // nonce for VDF
      uint256 mowseLootboxNonce;
      // mapping to get seed from tokenId if minted through mowseLootbox
      mapping(uint256 => uint256) mowseLootboxTokenIdToSeed;
    
      // random seed for pseudo-rng
      uint256 mowsePrngSeed;
      uint256 mowseJobPrngSeed;
      uint256 mowseWearPrngSeed;
      uint256 prngNonce;
      
      // DonationTree
      uint256 totalWishCount;
      uint256 donationTreePrime;
      uint256 donationTreeIterations;
      uint256 donationTreeNonce;
      mapping(uint256 => uint256) donationTreeWishCountToSeed;
      mapping(uint256 => uint256) mowseNextWish;
      mapping(address => uint256) nextWish;
      mapping(address => uint256) wishCount;
    
      // MowseGame
      mapping(uint256 => MowseGames) mowsegames;
      uint256 mowseGamesCount;
      uint256 activeMowseGamesCount;  // Needed 
      mapping(string => uint256) getMowseGameByName;
      uint8 maxFreeGameSubmits;
      // Normal base token payout
      uint256 baseGamePayout;
      // Pay for users' gas but it ain't free yo. After x number of submits, purchase MowseGold to continue forever
      mapping(address => uint256) freeMowseGameSubmits;
      mapping(address => bool) hasPurchasedMowseGold;
      // For suspicious gaming activity, temporary ban
      mapping(address => bool) mowseGameUserBanned;
    
      // MowseBank and MowseWallet
      address mowseBankWalletImplementation;
      mapping(address => uint256) mowseBankBalances;
      mapping(address => uint256) mowseBankTokenIdForWallet;
    
      // Withdrawal
      address payable devRoyaltyAddress;
      address payable teamAddress;
      address payable backendAddress;
      address payable treasuryAddress;
      uint256 devRoyaltyTaxBasisPoints;
      uint256 teamTaxBasisPoints;
      uint256 backendTaxBasisPoints;
      uint256 developerTaxBasisPoints;
    
      // For testnet purposes and rewarding testers
      uint256 testPatchCount;
      mapping(uint256 => TestPatch) testPatches;  // Maps patchVersion to testPatch  
    
      // GatherAndGarnishFacet
      uint256 gatherGarnishPrngSeed;
      uint256 gatherGarnishSeasonStart;
      uint256 gatherGarnishSeasonEnd;
      uint256 gatherGarnishSeasonCount;
      // Maps season count -> season reward (mowse wear dictionary index)
      mapping(uint256 => uint256) gatherGarnishSeasonReward;  // MowseWearDictionaryIndex
      // Maps player address -> season count -> stats
      mapping(address => mapping(uint256 => GatherAndGarnishPlayerStats)) gatherGarnishPlayerStats;
      // What day index did the first winner win
      uint256 gatherGarnishDayOfFirstWinner;
      // Maps season count -> first winners count index -> player address for season first winners
      mapping(uint256 => mapping(uint256 => address)) gatherGarnishSeasonFirstWinners;
      uint256 gatherGarnishSeasonFirstWinnersCount;
      // Maps season count -> winners count index -> player address for season winners
      mapping(uint256 => mapping(uint256 => address)) gatherGarnishSeasonWinners;
      uint256 gatherGarnishSeasonWinnersCount;
      // Maps season count -> winners address -> bool if they have won and claimed their rewards
      mapping(uint256 => mapping(address => bool)) gatherGarnishClaimedRewards;
      // Maps season count -> mainMowseId -> bool if the mowseId has received rewards for this season (to prevent transferring main mowse and claiming multiple exp rewards)
      mapping(uint256 => mapping(uint256 => bool)) gatherGarnishMainMowseClaimedRewards;
      // Maps season count -> winners address -> bool if they have won
      mapping(uint256 => mapping(address => bool)) gatherGarnishHasPlayerWon;
      // Maps season count -> day index -> scavenge hunt count
      mapping(uint256 => mapping(uint256 => GatherAndGarnishScavenge)) gatherGarnishScavenge;
      // Maps season count -> day index -> resource type [1-4, wood/rock/gem/food]
      mapping(uint256 => mapping(uint256 => uint256)) gatherGarnishDailyResource;
      // Maps season count -> day index -> investment resource type [1-4]
      mapping(uint256 => mapping(uint256 => uint256)) gatherGarnishInvestmentResource;
      // Maps season count -> pointsStructureCost
      mapping(uint256 => uint256[4]) gatherGarnishFirstPointsStructureCost;
      mapping(uint256 => uint256[4]) gatherGarnishSecondPointsStructureCost;
      mapping(uint256 => uint256[4]) gatherGarnishThirdPointsStructureCost;
    
      // MowseDungeonFacet
      uint256 statusEffectCount;
      // VDF for initial dungeon hash
      uint256 mowseDungeonPrime;
      uint256 mowseDungeonIterations;
      uint256 mowseDungeonNonce;
      // pRNG for smaller pseudo random actions
      uint256 mowseDungeonPrngSeed;
      // Maps mowseTokenId -> dungeonRunCount -> MowseDungeon
      mapping(uint256 => mapping(uint256 => MowseDungeon)) mowsedungeons;
      // Maps monsterIdCount -> mapping of statusEffects
      mapping(uint256 => mapping(MowseDungeonStatusEffectEnum => uint256)) mowseDungeonMonsterStatusEffects;
      // Count of Monster Ids
      // uint256 mowseDungeonMonsterIdCount;
      // Maps roomId -> MowseDungeonRoom
      // mapping(uint256 => MowseDungeonRoom) mowseDungeonRoom;
      // Count of rooms
      // uint256 mowseDungeonRoomCount;
      // Maps mowseTokenId -> Current Player stats
      mapping(uint256 => MowseDungeonPlayer) mowseDungeonPlayer;
      // Maps mowseTokenId -> AchievementEnum -> threshold
      mapping(uint256 => mapping(MowseDungeonAchievementsEnum => uint256)) mowseDungeonAchievementProgress;
      // Maps mowseTokenId -> Companion Enum -> Companion
      mapping(uint256 => mapping(MowseDungeonCompanionEnum => MowseDungeonCompanion)) mowseDungeonPlayerCompanion;
      // Maps mowseTokenId -> dungeonRunCount -> DungeonRewards
      mapping(uint256 => mapping(uint256 => MowseDungeonDungeonRewards)) mowseDungeonDungeonRewards;
      
      // Maps Monster Enum -> Monster for default values
      mapping(MowseDungeonMonsterEnum => MowseDungeonMonsterAttributes) mowseDungeonMonsterDefaults;
      uint256 mowseDungeonMonsterDefaultsCount;
      // Maps Room Enum -> Room for default values
      mapping(MowseDungeonRoomEnum => MowseDungeonRoom) mowseDungeonRoomDefaults;
      uint256 mowseDungeonRoomDefaultsCount;
      mapping(MowseDungeonTrinketEnum => MowseDungeonTrinket) mowseDungeonTrinketDefaults;
      // Keeps track of number of different trinkets exist
      uint256 mowseDungeonTrinketDefaultsCount;
      
      // Maps mowseTokenId to dungeon run count to vdf seed
      mapping(uint256 => mapping(uint256 => uint256)) mowseDungeonRunCountToSeed;
      // Maps mowseTokenId to dungeon run count
      mapping(uint256 => uint256) mowseDungeonRunCount;
    
      // RandomFortuneFacet
      mapping(address => uint256) randomFortuneCount;
      // large prime used for VDF
      uint256 randomFortunePrime;
      // iterations for VDF
      uint256 randomFortuneIterations;
      // nonce for VDF
      uint256 randomFortuneNonce;
      // mapping to get seed from timestamp
      mapping(address => mapping(uint256 => uint256)) randomFortuneTimestampToSeed;
    
      // MowseSpin
      // uint256 mowseSpinPrime;
      // uint256 mowseSpinIterations;
      // uint256 mowseSpinNonce;
      // // Maps mowseTokenId to current mowse spin game
      // mapping(uint256 => MowseSpinStorage) mowseSpinStorage;
      // // Maps mowseTokenId to player stats
      // mapping(uint256 => MowseSpinPlayer) mowseSpinPlayer;
    }
    
    // MowseSpin
    // struct MowseSpinStorage {
    //   uint256 mowseId;
    //   uint256 stageCount;
    //   uint256 spinCount;
    //   uint256 mowseSpinId;
    //   uint256 seed;
    //   uint256 recountTokens;
    //   uint256 trashTokens;
    //   uint256 coins;
    //   MowseSpinSimplePlayer player;
    //   MowseSpinPlayerAchievementsProgress achievementProgress;
    //   MowseSpinLog[] spinLog;
    //   uint256 spinLogCount;
    //   uint256 grid;
    // }
    
    // struct MowseSpinPlayer {
    //   uint256 mowseId;
    //   // Number of times a player has created a game
    //   uint256 spinRunCount;
    //   // MowseDungeonSimplePlayerAchievementsUnlocked achievements;
    //   // // Used for quickly checking if an achievement is unlocked
    //   // mapping(MowseDungeonAchievementsEnum => bool) achievementsUnlocked;
    //   // mapping(MowseDungeonAchievementsEnum => bool) achievementsClaimed;
    //   // MowseDungeonSimplePlayerAchievementsProgress achievementsProgress;
    // }
    
    enum MowseDungeonStatusEffectEnum {
      NONE,
      BLIND,      // 1/2 chance of missing attack
      EAGLE_EYE,   // double chance of hitting atttack
      POISON,     // Damage per turn, pre attack
      BURN,       // Damage per turn, post attack
      BLEED,      // Damage per turn, post attack
      CURSE,      // % Damage per turn, pre attack
      STUN,       // Skip damage step
      SILENCE,    // Disable special ability
      FREEZE,     // Skip damage step, +50% damage taken from fire damage
      STRENGTHEN, // Damage Up
      WEAKEN,     // Damage Down
      BULK,       // Defense Up
      FRAIL,      // Defense Down
      THORNS,     // Return damage on hit
      REGEN,      // Heal, pre attack
      SLEEP,      // Skip damage step, +150% damage taken, wakes up after damage taken
      DEATH,      // Kill player after x turns
      INVINCIBLE, // Player does not receive damage 
      LAST_HOPE,   // If player would die, set health to 1
      DODGE,      // Increase dodge chance
      HEAL_BLOCK,  // Disable healing
      CHARM,      // Skip damage step
      COUNTER,    // Basic attack after getting hit by a basic/special attack
      PIERCE,     // Attack through shield
      CLEAVE,     // Every attack hits all monsters
      REVIVE,     // After dying, revive with full health
      UNSTOPPABLE,// Debuffs cannot be applied (existing ones still damage)
      PETRIFY,    // Skip damage step, can't receive damage from enemy attack
      FIRE_RESIST, // Resist application of burn
      POISON_RESIST,
      FREEZE_RESIST,
      STUN_RESIST,
      SLEEP_RESIST,
      CHARGED,    // Used for boss abilities that take wind up time
      DOUBLE_UP,   // Apply double status effects
      RAMPAGE,    // Increase monster damage per turn
      LONE_SURVIVOR,// If all other monsters are dead, increase damage
      ROYAL_DECREE,// Cannot be directly targeted until all other monsters are dead
      WRATHFUL_REPRISAL// Counter attack based off player attack
    }
    enum MowseDungeonSpecialAbilityEnum {
      DEFEND,         // Apply thorns/counter/shield+5 to self
      POISON_GAS,      // Apply poison to enemy
      BOULDER_TOSS,    // Charge up and deal 3x damage + stun
      RAGE,           // Apply STRENGTHEN to self
      SUMMON_CLUCKO,   // Summon a Clucko in an empty monster slot
      THIN_STICK,      // 80% dodge chance
      BADMOUTH,       // Apply SILENCE, WEAKEN, FRAIL to enemy
      SUMMON_PUNK,     // Summon up to 2 WRAT_PUNK in empty monster slots
      CURSED_SLIME,    // Apply CURSE to enemy
      BLEEDING_SLIME,  // Apply BLEED to enemy
      IRON_GRASP,      // Charge up and double shield. Next turn add shield to damage
      IRON_PRAYER,     // Grant shield to monsters other than self
      JESTER_DANCE,    // Apply CURSE/BLEED/BURN/POISON/STUN/SLEEP/CHARM/freeze to enemy
      KNIGHTS_HONOR,   // Apply STRENGTHEN to self and shield to self
      QUEENS_GRACE,    // Apply REGEN,STRENGTHEN,BULK to all monsters
      KINGS_AUTHORITY, // Apply stun and charge up 2 turns, at end, deal damage equal to players coins
      ROYAL_DECREE,    // Summon up to 2 copper squire, then charge for 2 turns
      HOARDERS_STASH,  // Steal 25% of player's coins, add amount to current & max health
      WRATHFUL_REPRISAL,// Apply WRATHFUL_REPRISAL to self
      DESTRUCTIVE_ENVY,// Charge up 1 turn, copy player's attack
      SEDUCTIVE_GAZE,  // Apply CHARM +2 to player
      INSATIABLE_FEAST,// Charge 1 turn, Consume all debuffs from self and all buffs from player, heal for 10% per debuff
      ANTI_SONIC_SPEED,// Apply SLEEP to all players for 2 turns
      NONE
    }
    enum MowseDungeonCompanionEnum {
      NONE,
      DIAMOND_BACK,
      DEVILISH,       // Get Copper Squire Achievement
      WONDER_CHILD,     // Get Novice Achievement
      INVALID_COMPANION
    }
    enum MowseDungeonTrinketEnum {
      DAGGER,         // +10% attack
      SLINGSHOT,      // increase damage when enemy at max health
      PENDANT_OF_LIFE,  // +5 health pre attack
      LEECH,          // +10% lifesteal per stack
      POISON_DAB,      // +1 apply poison stack after attack
      KINDLE,         // +1 burn stack
      BLOOD_LETTER,    // +1 bleed stack
      CURSED_RING,     // +1 curse stack
      STUN_ROD,        // chance to apply +1 stun stack
      SILENCE_BELL,    // chance to apply +1 silence stack
      STRENGTH_BELT,   // +1 strengthen stack (2x damage)
      WEAKENED_CHAIN,  // +1 weaken stack (1/2 damage)
      EMBLEM_OF_INITIATIVE, // +1 action
      GOLD_RING,       // increase coin drop 10% + 10% per stack
      NONE
    }
    enum MowseDungeonRoomEnum {
      MERCHANT,             // Merchant
      COIN_LOOT_ROOM,
      TRINKET_LOOT_ROOM,
      FOREST_SLIME_1,       // 1 Green Slime
      FOREST_SLIME_2,       // 2 Green Slime
      FOREST_SLIME_3,       // 3 Green Slime
      FOREST_MUSHY,   // 2 Red Mushy
      FOREST_PEBBLE,   // 1 Pebble Golem 1 DUCKY
      FOREST_TOAD,   // 3 Spiny Toad
      FOREST_ROOSTA,         // 1 Roosta 2 Clucko
      FOREST_STICK,         // 3 Stick Bug
      FOREST_WRAT_PUNK,         // 2 WRAT_PUNK
      FOREST_WRAT_GANG_LEADER,           // 1 Wrat Gang Leader
      RAIN_OF_FROGS,          // 3 Spiny Toad
      STICKY_SITUATION,       // 2 Green Slime 1 Stick Bug
      MEDIEVAL_SLIME_INTRO,   // 1 Helmet Slime
      MEDIEVAL_SQUIRE_INTRO,  // 1 Copper Squire
      MEDIEVAL_PRIEST_INTRO,  // 2 Iron Priest
      MEDIEVAL_SLIME,        // 3 Helmet Slime
      MEDIEVAL_SQUIRE,       // 2 Copper Squire
      MEDIEVAL_JESTER,       // 2 Pyrite Jester
      MEDIEVAL_KNIGHT,       // 2 Steel Knight
      MEDIEVAL_KNIGHT_PRIEST, // 1 Steel Knight 1 Iron Priest
      MEDIEVAL_KNIGHT_SQUIRE,       // 1 Steel Knight 1 CopperSquire
      MEDIEVAL_MAIDEN,       // 1 Iron Maiden 2 Iron Priest
      MEDIEVAL_QUEEN,        // 1 Silver Queen 1 SteelKnight
      MEDIEVAL_KING,         // 1 Gold King 1 SteelKnight
      MEDIEVAL_ROYAL,        // 1 Silver Queen 1 Gold King
      BOSS_LIONS_PRIDE,
      BOSS_GOBLINS_GREED,
      BOSS_LICHS_WRATH,
      BOSS_WRATS_ENVY,
      BOSS_LUSTFUL_HEIRESS,
      BOSS_GLUTTONOUS_PIG,
      BOSS_SLOTHFUL_HEDGEHOG
    }
    enum MowseDungeonMonsterEnum {
      // Forest
      SPINY_TOAD,
      GREEN_SLIME,
      RED_MUSHY,
      PEBBLE_GOLEM,
      CLUCKO,
      ROOSTA,
      STICK_BUG,
      DUCKY,
      WRAT_PUNK,
      WRAT_GANG_LEADER,
      BLUE_SLIME,
      RED_SLIME,
      // Metal Medieval theme
      HELMET_SLIME,
      COPPER_SQUIRE,
      IRON_MAIDEN,
      IRON_PRIEST,
      PYRITE_JESTER,
      STEEL_KNIGHT,
      SILVER_QUEEN,
      GOLD_KING,
      // Bosses
      // 7 Deadly Sins
      LIONS_PRIDE,
      GOBLINS_GREED,
      LICHS_WRATH,
      WRATS_ENVY,
      LUSTFUL_HEIRESS,
      GLUTTONOUS_PIG,
      SLOTHFUL_HEDGEHOG,
      NONE
    }
    enum MowseDungeonClassEnum {
      // Base Classes
      WARRIOR,  // Basic 100% damage Special ability whirlwind (apply cleave to self)
      MAGE,     // Basic 60% Special ability fireball (2x base attack, apply burn to enemy)
      ARCHER,   // Basic 80% damage, Special ability Hawkshot (apply eagle eye, deal damage x # of times)
      ROGUE,    // Basic 50% x 2 apply poison Special ability backstab (apply dodge 2x)
      // Advanced Classes
      PALADIN,  // Basic 70% damage, Special ability Judgement Of Light (heal 30% max health + 1% per 10 int + 1% per 10 str)
      GUARDIAN,  // Basic 50% damage, gain shield Special ability shieldbash (gain extra shield, damage equal to shield)
      BERSERKER,// Basic 120% Special ability rage (deal basic attack damage, apply strengthen, lastHope, silence to self)
      CLERIC,   // Basic 40% Special ability heal (apply regen to self)
      NECROMANCER,// Basic 55% Special ability life tap (deal basic attack damage, if monster dies, permanently gain 10% attack)
      BARD,     // Basic 60% Special ability charm (apply charm to enemy)
      // T2 Classes
      INVALID_CLASS
    }
    enum MowseDungeonRoomTypeEnum {
      MONSTER,
      COIN_LOOT,
      TRINKET_LOOT,
      BOSS,
      MERCHANT,
      INVALID_ROOM
    }
    // Complete Achievements to unlock Companions
    enum MowseDungeonAchievementsEnum {
      NOVICE,           // Complete the forest-0 dungeon
      FOREST_MASTER,     // Complete the forest-10 dungeon
      COPPER_SQUIRE,   // Complete the medieval-0 dungeon
      IRON_MAIDEN,   // Complete the medieval-3 dungeon
      STEEL_KNIGHT,    // Complete the medieval-6 dungeon
      GOLD_KING,     // Complete the medieval-9 dungeon
      MASTER_OF_DEADLY_SINS,// Complete the medieval-10 dungeon
      UNTOUCHABLE,      // Defeat a boss without taking damage
      POISON_MASTER,      // Apply 99 stacks of poison in one dungeon
      LOOT_GOBLIN,       // Steal successfully more than 5 times in a single run
      COIN_GOBLIN,       // Have at least 500 coins in inventory
      ONE_MAN_ARMY,       // Deal 1000 damage in a single run
      LEARN_THE_ROPES,  // Complete any dungeon with all 4 base classes
      WARRIOR,          // Complete any dungeon with Warrior
      MAGE,             // Complete any dungeon with Mage
      ARCHER,           // Complete any dungeon with Archer
      ROGUE,            // Complete any dungeon with Rogue
      PALADIN,          // Complete any dungeon with Paladin
      GUARDIAN,         // Complete any dungeon with Guardian
      BERSERKER,        // Complete any dungeon with Berserker
      CLERIC,           // Complete any dungeon with Cleric
      NECROMANCER,      // Complete any dungeon with Necromancer
      BARD,             // Complete any dungeon with Bard
      INVALID_ACHIEVEMENT
    }
    enum MowseDungeonActionTypeEnum {
      BASIC_ATTACK,
      SPECIAL_ABILITY,
      STEAL,
      DEFEND,
      INVALID_ACTION
    }
    enum MowseDungeonBattleStatusEnum {
      VICTORY,
      DEFEAT,
      ONGOING
    }
    enum MowseDungeonDungeonTypeEnum {
      FOREST,
      MEDIEVAL
    }
    enum MowseDungeonBattleLogTypeEnum {
      INVALID_LOG_TYPE,
      DAMAGE,
      APPLY_STATUS_EFFECT,
      HEAL,
      SUMMON,
      STEAL_SUCCESS,
      STEAL_FAILURE
    }
    enum MowseDungeonBattleLogSourceEnum {
      PLAYER,
      MONSTER_1,
      MONSTER_2,
      MONSTER_3,
      TARGET_INDEX_MONSTER,  // Target a Monster according to targetIndex
      COMPANION,
      TRINKET_PENDANT_OF_LIFE,
      TRINKET_LEECH,
      STATUS_EFFECT_BLIND,
      STATUS_EFFECT_EAGLE_EYE,
      STATUS_EFFECT_POISON,
      STATUS_EFFECT_BURN,
      STATUS_EFFECT_BLEED,
      STATUS_EFFECT_CURSE,
      STATUS_EFFECT_STUN,
      STATUS_EFFECT_SILENCE,
      STATUS_EFFECT_FREEZE,
      STATUS_EFFECT_STRENGTHEN,
      STATUS_EFFECT_WEAKEN,
      STATUS_EFFECT_BULK,
      STATUS_EFFECT_FRAIL,
      STATUS_EFFECT_THORNS,
      STATUS_EFFECT_REGEN,
      STATUS_EFFECT_SLEEP,
      STATUS_EFFECT_DEATH,
      STATUS_EFFECT_INVINCIBLE,
      STATUS_EFFECT_LAST_HOPE,
      STATUS_EFFECT_DODGE,
      STATUS_EFFECT_HEAL_BLOCK,
      STATUS_EFFECT_CHARM,
      STATUS_EFFECT_COUNTER,
      STATUS_EFFECT_PIERCE,
      STATUS_EFFECT_CLEAVE,
      STATUS_EFFECT_REVIVE,
      STATUS_EFFECT_UNSTOPPABLE,
      STATUS_EFFECT_PETRIFY,
      STATUS_EFFECT_FIRE_RESIST,
      STATUS_EFFECT_POISON_RESIST,
      STATUS_EFFECT_FREEZE_RESIST,
      STATUS_EFFECT_STUN_RESIST,
      STATUS_EFFECT_SLEEP_RESIST,
      STATUS_EFFECT_CHARGED,
      STATUS_EFFECT_DOUBLE_UP,
      STATUS_EFFECT_RAMPAGE,
      STATUS_EFFECT_LONE_SURVIVOR,
      STATUS_EFFECT_ROYAL_DECREE,
      STATUS_EFFECT_WRATHFUL_REPRISAL
    }
    struct MowseDungeonSimpleTrinkets {
      uint256 dagger;
      uint256 slingshot;
      uint256 pendantOfLife;
      uint256 leech;
      uint256 poisonDab;
      uint256 kindle;
      uint256 bloodLetter;
      uint256 cursedRing;
      uint256 stunRod;
      uint256 silenceBell;
      uint256 strengthBelt;
      uint256 weakenedChain;
      uint256 emblemOfInitiative;
      uint256 goldRing;
    }
    struct MowseDungeonSimpleStatusEffect {
      uint256 blind;
      uint256 eagleEye;
      uint256 poison;
      uint256 burn;
      uint256 bleed;
      uint256 curse;
      uint256 stun;
      uint256 silence;
      uint256 freeze;
      uint256 strengthen;
      uint256 weaken;
      uint256 bulk;
      uint256 frail;
      uint256 thorns;
      uint256 regen;
      uint256 sleep;
      uint256 death;
      uint256 invincible;
      uint256 lastHope;
      uint256 dodge;
      uint256 healBlock;
      uint256 charm;
      uint256 counter;
      uint256 pierce;
      uint256 cleave;
      uint256 revive;
      uint256 unstoppable;
      uint256 petrify;
      uint256 fireResist;
      uint256 poisonResist;
      uint256 freezeResist;
      uint256 stunResist;
      uint256 sleepResist;
      uint256 charged;
      uint256 doubleUp;
      uint256 rampage;
      uint256 loneSurvivor;
      uint256 royalDecree;
      uint256 wrathfulReprisal;
    }
    struct MowseDungeonSimplePlayerAchievementsUnlocked {
      bool novice;
      bool forestMaster;
      bool copperSquire;
      bool ironMaiden;
      bool steelKnight;
      bool goldKing;
      bool masterOfDeadlySins;
      bool untouchable;
      bool poisonMaster;
      bool lootGoblin;
      bool coinGoblin;
      bool oneManArmy;
      bool learnTheRopes;
      bool warrior;
      bool mage;
      bool archer;
      bool rogue;
      bool paladin;
      bool guardian;
      bool berserker;
      bool cleric;
      bool necromancer;
      bool bard;
    }
    struct MowseDungeonSimplePlayerAchievementsProgress {
      uint256 damageDealt;
      uint256 damageTaken;
      uint256 healthHealed;
      uint256 burnDamageDealt;
      uint256 poisonDamageDealt;
      uint256 bleedDamageDealt;
      uint256 curseDamageDealt;
      uint256 trinketsStolen;
    }
    struct MowseDungeonBattleLog {
      MowseDungeonBattleLogTypeEnum logType;
      MowseDungeonBattleLogSourceEnum source;
      MowseDungeonBattleLogSourceEnum target;
      uint256 amount;
    }
    struct MowseDungeonBattleSimulation {
      MowseDungeonSimplePlayer player;
      MowseDungeonSimpleMonster monster1;
      MowseDungeonSimpleMonster monster2;
      MowseDungeonSimpleMonster monster3;
      uint256 dungeonId;
      uint256 seed;
      MowseDungeonActionTypeEnum actionType;
      uint8 targetIndex;
      uint256 actionsRun;
      MowseDungeonTrinketEnum[] stolenTrinkets;
      uint256 stolenTrinketCount;
      bool isStealSuccessful;
      MowseDungeonBattleStatusEnum status;
      MowseDungeonSimplePlayerAchievementsProgress achievementProgress;
      uint256 descentLevel;
      MowseDungeonBattleLog[] battleLog;
      uint256 battleLogCount;
    }
    struct MowseDungeonBattleResults {
      MowseDungeonBattleSimulation simulation;
      MowseDungeonPostBattleResults postBattleResults;
      bytes32 actionsRunHash;
      bytes32 postBattleResultsHash;
    }
    struct MowseDungeonPostBattleResults {
      uint256 mowseId;
      uint256 coins;
      MowseDungeonMonsterEnum monster1;
      MowseDungeonMonsterEnum monster2;
      MowseDungeonMonsterEnum monster3;
      MowseDungeonTrinketEnum[] stolenTrinkets;
      uint256 stolenTrinketCount;
      MowseDungeonBattleStatusEnum status;
      MowseDungeonSimplePlayerAchievementsProgress achievementProgress;
      uint256 descentLevel;
    }
    struct MowseDungeonDungeonRewards {
      bool hasRewards;
      uint256 mgoldMintAmount;
      uint256 xpGainedAmount;
      uint256 companionXpGained;
      uint256 completionPercentage;
      uint256 additionalFactor;
    }
    // Used for _performAction to calculate damage
    struct MowseDungeonSimplePlayer {
      uint256 mowseId;
      int32[SKILL_TYPE_NUM] stats;
      uint256 maxHealth;
      uint256 currentHealth;
      uint256 shield;
      uint256 attack;
      bool canHit;
      uint256 accuracy;
      uint256 tempAttack;
      uint256 damageMultiplier;
      uint256 action;
      uint256 numberOfHits;
      MowseDungeonClassEnum class;
      MowseDungeonSimpleStatusEffect statusEffects;
      MowseDungeonSimpleTrinkets trinkets;
      MowseDungeonCompanion companion;
      MowseDungeonDescentLevel descentLevel;
      // MowseDungeonSimplePlayerAchievementsUnlocked achievements;
      uint256 coins;
      uint256 currentDungeonRunCount;
      uint256 currentRoomIndex;
    }
    struct MowseDungeonPlayer {
      uint256 mowseId;
      int32[SKILL_TYPE_NUM] stats;
      uint256 maxHealth;
      uint256 currentHealth;
      uint256 shield;
      uint256 attack;
      // uint256 maxAction;  // Special Ability points
      uint256 action;
      // Maps StatusEffect to statusEffect value (ex: 1 (poison) => 2 is equal to 2 stacks of poison)
      mapping(MowseDungeonStatusEffectEnum => uint256) statusEffects;
      // Maps trinketIndex to MowseDungeonTrinketEnum
      mapping(MowseDungeonTrinketEnum => MowseDungeonTrinket) trinkets;
      // uint256 trinketCount;
      mapping(MowseDungeonCompanionEnum => MowseDungeonCompanion) companions;
      MowseDungeonCompanion currentCompanion;
      uint256 coins;
      MowseDungeonClassEnum class;
      // Number of times a player has run a dungeon
      uint256 dungeonRunCount;
      MowseDungeonSimplePlayerAchievementsUnlocked achievements;
      // Used for quickly checking if an achievement is unlocked
      mapping(MowseDungeonAchievementsEnum => bool) achievementsUnlocked;
      mapping(MowseDungeonAchievementsEnum => bool) achievementsClaimed;
      MowseDungeonSimplePlayerAchievementsProgress achievementsProgress;
      MowseDungeonDescentLevel descentLevel;
    }
    struct MowseDungeonPerformAction {
      MowseDungeonActionTypeEnum actionType;
      uint256 targetIndex;
    }
    // Used for _performAction to calculate damage
    struct MowseDungeonSimpleMonster {
      MowseDungeonMonsterEnum monsterType;
      uint256 monsterId;
      uint256 maxHealth;
      uint256 currentHealth;
      uint256 shield;
      uint256 attack;
      bool canHit;
      uint256 accuracy;
      uint256 tempAttack;
      uint256 damageMultiplier;
      MowseDungeonSpecialAbilityEnum specialAbility;
      bool isBoss;
      bool hasBeenStolen;
      bool usedSpecialAbility;
      MowseDungeonSimpleStatusEffect statusEffects;
      string image;
    }
    // Used for setting default monster values 
    struct MowseDungeonMonsterAttributes {
      bool isActive;
      MowseDungeonMonsterEnum monsterType;
      uint256 monsterId;
      uint256 maxHealth;
      uint256 shield;
      uint256 attack;
      MowseDungeonSpecialAbilityEnum specialAbility;
      MowseDungeonSimpleStatusEffect statusEffects;
      bool isBoss;
      uint256 coins;
      string name;
      string image;
    }
    struct MowseDungeonMonster {
      MowseDungeonMonsterEnum monsterType;
      uint256 monsterId;
      uint256 maxHealth;
      uint256 currentHealth;
      uint256 shield;
      uint256 attack;
      MowseDungeonSpecialAbilityEnum specialAbility;
      mapping(MowseDungeonStatusEffectEnum => uint256) statusEffects;
      // Bosses cannot be stunned, slept, etc.
      bool isBoss;
      // Maps monsterDropIndex to MowseDungeonTrinket
      mapping(uint256 => MowseDungeonTrinket) monsterDrops;
      uint256 monsterDropCount;
      uint256 coins;
      // Players can only use steal once per monster
      bool hasBeenStolen;
      string name;
    }
    // Trinkets are passive boosts to the player they can select after battle
    // Trinkets do not carry over between dungeon run, however, you can enhance trinkets so they are stronger the next run
    // Use MowseDungeonTrinketEnum id to conditionally apply statusEffects
    struct MowseDungeonTrinket {
      MowseDungeonTrinketEnum trinketId;
      uint256 level;
      // mapping(MowseDungeonStatusEffectEnum => uint256) statusEffects;
      uint256 health;
      uint256 shield;
      uint256 attack;
    }
    // Should always have 5 items
    struct MowseDungeonMerchantRoom {
      MowseDungeonMerchantItem[] items;
    }
    struct MowseDungeonMerchantItem {
      MowseDungeonTrinketEnum trinketId;
      MowseDungeonTrinket trinket;
      uint256 cost;
      bool hasPurchased;
    }
    // Unlike Trinkets, Companions carry over between dungeon runs
    // Companions can level up before a dungeon run
    struct MowseDungeonCompanion {
      MowseDungeonCompanionEnum companionId;
      bool isUnlocked;
      uint256 level;
      uint256 experience;
    }
    struct MowseDungeonRoom {
      MowseDungeonRoomEnum id;
      MowseDungeonRoomTypeEnum roomType;
      MowseDungeonMonsterEnum[] monsters;
    }
    struct MowseDungeon {
      uint256 id;   // Should be the VDF hash
      MowseDungeonRoom[] rooms;
      uint256 currentRoomIndex;
      bool active;
      MowseDungeonDungeonTypeEnum dungeonType;
      MowseDungeonMerchantRoom merchantRoom;
      uint256 descentLevel;
    }
    // 0 - normal
    // 1 - +25% hp/shield
    // 2 - +25% attack
    // 3 - +25% attack, -25% coin
    // 4 - +25% attack/hp/shield
    // 5 - +25% attack/hp/shield, items cost 10% more
    // 6 - +25% attack, +50% hp/shield
    // 7 - +25% attack, +50% hp/shield, -25% coins
    // 8 - +25% attack, +50% hp/shield, -25% coins, items cost 10% more
    // 9 - +100% hp/shield, rampage
    // 10 - +100% hp/shield, rampage, -25% coins, items cost 10% more
    struct MowseDungeonDescentLevel {
      uint256 forestDungeon;
      uint256 medievalDungeon;
    }
    
    // Gather and Garnish
    struct GatherAndGarnishScavenge {
      uint256 woodCount;
      uint256 rockCount;
      uint256 gemCount;
      uint256 foodCount;
    }
    struct GatherAndGarnishPlayerStats {
      // Maps season day index -> action index taken [1 - gather, 2 - predict, 3 - build, 4 - invest]
      mapping(uint256 => uint256) dailyAction;
      uint256 woodStorage;
      uint256 woodStorageLevel;
      uint256 rockStorage;
      uint256 rockStorageLevel;
      uint256 gemStorage;
      uint256 gemStorageLevel;
      uint256 foodStorage;
      uint256 foodStorageLevel;
      uint256 investmentCount;
      // Maps whether the player has built the first/second/third points structure
      mapping(uint256 => bool) hasBuiltPointsStructure;
      // Maps season day index -> resource type prediction [1-4, wood/rock/gem/food]
      mapping(uint256 => uint256) prediction;
      uint256 latestPredictionDay;
      uint256 seasonFirstWinnerIndex;
    }
    
    struct TestPatch {
      uint256 testPatchVersion;
      uint256 uniqueInteractionCount; // Unique address interactions
      address[] interactedAddresses;  // Addresses that interacted
      mapping(address => uint256) numberOfInteractions; // maps addresses -> number of times interacted
    }
    
    struct Mowse {
      uint256 tokenId; // Mowse asset tokenId counter, also used to check if exists
      address ownerAddress; // Owner Address
      // BaseTraits required on all Mowse
      uint16 generation;
      MowseWearMetadata baseBackgroundColor; // Index 0
      MowseWearMetadata baseSkinColor;       // Index 2
      MowseWearMetadata baseEarType;         // Index 3
      MowseWearMetadata baseBodyType;        // Index 5
      MowseWearMetadata baseMouth;           // Index 6
      MowseWearMetadata baseEyeType;         // Index 8
      // MowseJob
      int32[SKILL_TYPE_NUM] skillLevelBoosts;  // Additional Skill LEVEL modifiers
      int32[SKILL_TYPE_NUM] skillLevel; // Base Skill LEVEL, increases as ExperiencePoints caps [charisma, constitution, dexterity, intelligence, luck, strength, wisdom]
      // uint32[SKILL_TYPE_NUM] skillExperiencePoints; // Skill EXPERIENCE points
      // uint16[SKILL_TYPE_NUM] skillExperiencePointsBoosts; // Additional Skill EXPERIENCE points boosts
      uint8 primarySkillType;  // Primary skill preference from birth (should be 0-6), cannot change
      uint8 secondarySkillType;  // Secondary skill preference from birth (should be 0-6, can be the same as primary skill), cannot change
      uint8 profession; // Career (should be 0-7), can change. 
      // 0 = no profession
      // 1 = Agriculture and Food
      // 2 = Finance and Marketing
      // 3 = Art and Manufacturing
      // 4 = Science and Medicine
      // 5 = Military
      // 6 = Politics (Law and Government)
      // 7 = Nursing and Childcare (Family-focused)
      // MowseLife
      uint16 lifeLevel; // LIFE Experience level
      uint256 lifeExperiencePoints;  // Life Experience points (as life level goes up, skill level can be increased)
      uint256 prngNonce;  // Used in Mowse PRNG (level up to randomly distribute stat points and onMint)
    
      // MowseWear
      uint256[EQUIPPED_WEARABLE_SLOTS] equippedMowseWearByTokenIds;  // Currently equipped mowsewear tokenIds
      // [backgroundColor, backgroundFeature, skinColor, earType, shirt, bodyType, mouth, eyeBrow, eyeType, eyeWear, headWear, jewelry]
    
    }
    
    // 1 Background Color
    // 2 Background Feature
    // 3 Skin Color
    // 4 Ear Type
    // 5 Shirt
    // 6 Body Type
    // 7 Mouth
    // 8 Eye Brow Type
    // 9 Eye Type
    // 10 Eye Wear
    // 11 Head Wear
    // 12 Jewelry
    
    struct MowseWear {
      uint256 tokenId;  // MowseWear asset tokenId counter, also used to check if exists
      bool isEquipped;  // Quick way to know if a MowseWear is equipped
      uint256 equippedBy; // track which Mowse tokenId is equipped by
      uint256 durability;  // Durability time after stitching
      uint256 alterCount;  // Number of times a MowseWear has been altered (stitched or hemmed)
      uint256 maxAlterCount;  // Max number of times a MowseWear can be altered (stitched or hemmed)
      MowseWearMetadata metadata;
      MowseWearDimensions dimensions;
      MowseWearBonuses bonuses;
    }
    
    struct MowseWearMetadata {
      uint8 traitType;  // Type of MowseWear (backgroundColor, shirt, eyeWear, etc) (should be 0-11)
      string traitName; // MowseWear item name ("Blue Headband")
      uint16 traitIndex;  // MowseWear trait index (12th headband)
      uint256 dictionaryIndex;  // MowseWear dictionary index (12th item in dictionary)
      bool nonTransferrable;  // MowseWear can be traded or not (soulbound)
      string svgFilter; // Any overrides on the svgFilter
      MowseWearDimensions baseDimensions; // Base SVG Dimensions
    }
    
    struct MowseWearBonuses {
      uint256 randomness; // Random seed for MowseWear
      uint256 itemRarityIV;  // Item Rarity inherent value (pseudorandom number between 0-4294967294)
      uint8 itemRarity; // Item Rarity (common, uncommon, rare, epic, legendary, unique) (should be 0-5)
      int32[SKILL_TYPE_NUM] baseSkillLevelBoosts; // Base Skill LEVEL modifiers from IV
      int32[SKILL_TYPE_NUM] additionalSkillLevelBoosts;  // Additional Skill LEVEL modifiers from other factors (set bonuses?)
    }
    
    struct MowseSkillTypes {
      int32 charisma;
      int32 constitution;
      int32 dexterity;
      int32 intelligence;
      int32 luck;
      int32 strength;
      int32 wisdom;
    }
    
    struct MowseWearDimensions {
      uint16 width;
      uint16 height;
      string transform;
      string style;
      string image;
      uint16 weight;
    }
    
    struct MowseLootboxPool {
      bool active;  // Is lootbox active and can be minted from?
      uint256 price;  // Price per MowseGold (generic loot pool cheapest -> specific trait type more expensive -> event specific)
      string name;
      uint16 width; // Goes with image to create on-chain SVG
      uint16 height;  // Goes with image to create on-chain SVG
      string image;
      mapping(uint16 => uint256) lootPool;  // TraitTypes & TraitIndexes in the lootPool for minting
      uint16 itemCount; // Number of items in lootpool
      uint256 dateAdded;  // When the lootbox was added; Ex: Earlier lootboxes might have rarer items
      uint256 index;  // Index of the lootbox
    }
    // This struct is used by MowseLootboxFacet.getMowseLootboxData to return the lootPool mapping as well
    struct MowseLootboxPoolData {
      bool active;
      uint256 price;
      string name;
      string image;
      SimpleMowseLootboxPoolItem[] lootPool;
      uint16 itemCount;
      uint256 dateAdded;
      uint64 index;
    }
    struct SimpleMowseLootboxPoolItem {
      uint16 traitType;
      uint16 traitIndex;
      string traitName;
      string image;
    }
    
    struct MowseLineage {
      uint16 generation;
      uint256 parent1;
      uint256 parent2;
      uint256[] children;
    }
    
    struct MowseBankStruct {
      address _walletImplementation;
      mapping(address => uint256) balances;
      mapping(address => uint256) getTokenIdForWallet;
    }
    struct MowseGames {
      string name;
      bool active;  // Game is active or disabled (disable if game updates or something. idk. Bad developer)
      bytes32 gameHash;
      uint256 totalNumberOfSubmits;
      mapping(address => uint256) uniqueSubmitsByAddress;
      address[] uniqueAddressesList;
      uint256 uniqueAddressesCount;
      mapping(address => uint256[3]) dailySubmits;
      // Minimum score to pass to get the max payout MGOLD
      uint256 minScoreForMaxPayout;
      // For granting additional bonuses for special event days
      bool rewardBonus;
      // For paying game developers
      address payable developerAddress;
    }
    
    
    library LibStorage {
      function gameStorage() internal pure returns (GameStorage storage gs) {
        assembly {
          gs.slot := 0
        }
      }
      function skillTypeToString(uint16 x) internal pure returns (string memory skillTypeString) {
        require(x < SKILL_TYPE_NUM, "LibStorage: Invalid Skill Type");
    
        if (x == 0) return "charisma";
        if (x == 1) return "constitution";
        if (x == 2) return "dexterity";
        if (x == 3) return "intelligence";
        if (x == 4) return "luck";
        if (x == 5) return "strength";
        if (x == 6) return "wisdom";
      }
      function equippableWearableSlotToString(uint8 traitType) internal pure returns (string memory wearableSlotString) {
        require(traitType < EQUIPPED_WEARABLE_SLOTS, "LibStorage: Invalid Trait Type");
    
        if (traitType == 0) return "backgroundColor"; 
        if (traitType == 1) return "backgroundFeature"; 
        if (traitType == 2) return "skinColor"; 
        if (traitType == 3) return "earType"; 
        if (traitType == 4) return "shirt"; 
        if (traitType == 5) return "bodyType"; 
        if (traitType == 6) return "mouth"; 
        if (traitType == 7) return "eyeBrow"; 
        if (traitType == 8) return "eyeType"; 
        if (traitType == 9) return "eyeWear"; 
        if (traitType == 10) return "headWear"; 
        if (traitType == 11) return "jewelry";
      }
      function itemRarityToString(uint8 itemRarity) internal pure returns (string memory itemRarityString) {
        if (itemRarity == 0) return "common";
        if (itemRarity == 1) return "uncommon";
        if (itemRarity == 2) return "rare";
        if (itemRarity == 3) return "epic";
        if (itemRarity == 4) return "legendary";
        if (itemRarity == 5) return "unique";
      }
    }
    contract WithStorage {
      function skillTypeToString(uint16 skillType) internal pure returns (string memory) {
        return LibStorage.skillTypeToString(skillType);
      }
      function equippedWearableSlotToString(uint8 traitType) internal pure returns (string memory) {
        return LibStorage.equippableWearableSlotToString(traitType);
      }
      function itemRarityToString(uint8 itemRarity) internal pure returns (string memory) {
        return LibStorage.itemRarityToString(itemRarity);
      }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.12;
    import "@openzeppelin/contracts/utils/Strings.sol";
    
    library LibUtils {
        using Strings for uint16;
        using Strings for uint32;
    
        // Convert int to string
        function int16ToString(int16 i) internal pure returns (string memory) {
            if (i == 0) return "0";
            bool negative = i < 0;
            uint16 int16AsUint16 = uint16(abs16(i));
            return negative ? string(abi.encodePacked("-", int16AsUint16.toString())) : int16AsUint16.toString();
        }
    
        function int32ToString(int32 i) internal pure returns (string memory) {
            if (i == 0) return "0";
            bool negative = i < 0;
            uint32 int32AsUint32 = uint32(abs32(i));
            return negative ? string(abi.encodePacked("-", int32AsUint32.toString())) : int32AsUint32.toString();
        }
    
        function abs16(int16 i) internal pure returns (int16) {
            // If i = -32768, absolute value would overflow so set to -32767 first (For more MowseGame purposes this is fine)
            if (i == -32768) i = -32767;
            return i >= 0 ? i : -i;
        }
    
        function abs32(int32 i) internal pure returns (int32) {
            // If i = -2147483648, absolute value would overflow so set to -32767 first (For more MowseGame purposes this is fine)
            if (i == -2147483648) i = -2147483647;
            return i >= 0 ? i : -i;
        }
    
        function getFirstChar(string memory _originString) internal pure returns (string memory _firstChar) {
            bytes memory firstCharByte = new bytes(1);
            firstCharByte[0] = bytes(_originString)[0];
            return string(firstCharByte);
        }
    
        function substring(string memory str, uint startIndex, uint endIndex) internal pure returns (string memory) {
            bytes memory strBytes = bytes(str);
            bytes memory result = new bytes(endIndex - startIndex);
            for (uint i = startIndex; i < endIndex; i++) {
                result[i - startIndex] = strBytes[i];
            }
            return string(result);
        }
    
        function utfStringLength(string memory str) internal pure returns (uint length) {
            uint i = 0;
            bytes memory string_rep = bytes(str);
    
            while (i < string_rep.length) {
                if (string_rep[i] >> 7 == 0) i += 1;
                else if (string_rep[i] >> 5 == bytes1(uint8(0x6))) i += 2;
                else if (string_rep[i] >> 4 == bytes1(uint8(0xE))) i += 3;
                else if (string_rep[i] >> 3 == bytes1(uint8(0x1E)))
                    i += 4;
                    //For safety
                else i += 1;
    
                length++;
            }
        }
    
        function sqrt(uint256 y) internal pure returns (uint z) {
            if (y > 3) {
                z = y;
                uint x = y / 2 + 1;
                while (x < z) {
                    z = x;
                    x = (y / x + x) / 2;
                }
            } else if (y != 0) {
                z = 1;
            }
        }
    }

    // SPDX-License-Identifier: MIT
    // doublesharp
    pragma solidity ^0.8.12;
    
    import '@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol';
    
    library SetableCountersUpgradeable {
        function set(CountersUpgradeable.Counter storage counter, uint256 _value) internal {
            counter._value = _value;
        }
    }

    // SPDX-License-Identifier: MIT
    // https://eprint.iacr.org/2015/366.pdf
    
    pragma solidity ^0.8.12;
    
    // TODO: checking for quadratic residues has been removed, unclear of implications
    
    library SlothVerifiableDelay {
        /// @dev verify sloth result proof, starting from seed, over prime, for iterations
        /// @param _proof result
        /// @param _seed seed
        /// @param _prime prime
        /// @param _iterations number of iterations
        /// @return true if y is a quadratic residue modulo p
        function verify(
            uint256 _proof,
            uint256 _seed,
            uint256 _prime,
            uint256 _iterations
        ) internal pure returns (bool) {
            for (uint256 i; i < _iterations; ++i) {
                _proof = mulmod(_proof, _proof, _prime);
            }
    
            _seed %= _prime;
    
            if (_seed == _proof) return true;
    
            if (_prime - _seed == _proof) return true;
    
            return false;
        }
    
        /// @dev pow(base, exponent, modulus)
        /// @param base base
        /// @param exponent exponent
        /// @param modulus modulus
        function bexmod(
            uint256 base,
            uint256 exponent,
            uint256 modulus
        ) internal pure returns (uint256) {
            uint256 _result = 1;
            uint256 _base = base;
            for (; exponent > 0; exponent >>= 1) {
                if (exponent & 1 == 1) {
                    _result = mulmod(_result, _base, modulus);
                }
    
                _base = mulmod(_base, _base, modulus);
            }
    
            return _result;
        }
    
        /// @dev compute sloth starting from seed, over prime, for iterations
        /// @param _seed seed
        /// @param _prime prime
        /// @param _iterations number of iterations
        /// @return sloth result
        function compute(
            uint256 _seed,
            uint256 _prime,
            uint256 _iterations
        ) internal pure returns (uint256) {
            uint256 _exponent = (_prime + 1) >> 2;
            _seed %= _prime;
    
            for (uint256 i; i < _iterations; ++i) {
                _seed = bexmod(_seed, _exponent, _prime);
            }
    
            return _seed;
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.9;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol";
    
    import {RandomFortuneFacet} from "./facets/RandomFortuneFacet.sol";
    import {AccessControlFacet} from "./facets/AccessControlFacet.sol";
    import {MowseFacet} from "./facets/MowseFacet.sol";
    import {MowseWearFacet} from "./facets/MowseWearFacet.sol";
    import {WithdrawalFacet} from "./facets/WithdrawalFacet.sol";
    import {EQUIPPED_WEARABLE_SLOTS} from "./libraries/LibStorage.sol";
    
    import "./MowseBank.sol";
    import "./libraries/SetableCountersUpgradeable.sol";
    import "./MowseGold.sol";
    
    contract MowseAvatar is
        Initializable,
        ERC721Upgradeable,
        ERC721EnumerableUpgradeable,
        ERC721URIStorageUpgradeable,
        PausableUpgradeable,
        AccessControlUpgradeable,
        UUPSUpgradeable,
        IERC721ReceiverUpgradeable
    {
        using CountersUpgradeable for CountersUpgradeable.Counter;
        using SetableCountersUpgradeable for CountersUpgradeable.Counter;
        using StringsUpgradeable for uint256;
    
        bytes32 public constant MINTER_ROLE = keccak256("MOWSEAVATAR_MINTER_ROLE");
        bytes32 public constant UPGRADER_ROLE = keccak256("MOWSEAVATAR_UPGRADER_ROLE");
    
        uint256 public MOWSE_MINT_PRICE;
    
        address private owner;
        address public diamondAddress;
        uint256 public MOWSE_MINT_PRICE_MOWSE_GOLD;
    
        MowseBank public mowsebank;
        MowseGold public mowsegold;
    
        CountersUpgradeable.Counter private _mowseIds;
    
        error DidNotSendToTeam();
        error DidNotSendToBackend();
        error DidNotSendToTreasury();
        error DidNotSendToWithdrawalContract();
        error MowseGoldPaymentNotSuccessful(string);
        error MissingAdminRole(string);
    
        modifier mustExist(uint256 _tokenId) {
            require(_exists(_tokenId) || _tokenId == 0, "Mowse/must-exist");
            _;
        }
    
        /// @custom:oz-upgrades-unsafe-allow constructor
        constructor() {
            _disableInitializers();
        }
    
        function initialize(address _diamondAddress, address _mowseGoldAddress) public initializer {
            __ERC721_init("MowseAvatar", "MOWSE");
            __ERC721Enumerable_init();
            __ERC721URIStorage_init();
            __Pausable_init();
            __AccessControl_init();
            __UUPSUpgradeable_init();
            diamondAddress = _diamondAddress;
            mowsebank = new MowseBank(_diamondAddress);
            mowsegold = MowseGold(_mowseGoldAddress);
            owner = msg.sender;
    
            _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
            _grantRole(MINTER_ROLE, msg.sender);
            _grantRole(UPGRADER_ROLE, msg.sender);
    
            MOWSE_MINT_PRICE;
            // setMowseMintPrice(0.1 ether);  // DEV: 0.1 FTM to help test mowse functions
            setMowseMintPrice(10 ether);
            MOWSE_MINT_PRICE_MOWSE_GOLD = 1000 * 1e18;
        }
    
        // Increase price of MGOLD mint for every 1000 Mowse minted
        function getMintWithMGOLDPrice() public view returns (uint256) {
            return
                totalSupply() > 1000
                    ? MOWSE_MINT_PRICE_MOWSE_GOLD + (MOWSE_MINT_PRICE_MOWSE_GOLD * (totalSupply() / 1000))
                    : MOWSE_MINT_PRICE_MOWSE_GOLD;
        }
    
        function purchaseMowseWithMGOLD() external whenNotPaused {
            if (!mowsegold.transferFrom(msg.sender, address(WithdrawalFacet(diamondAddress)), getMintWithMGOLDPrice()))
                revert MowseGoldPaymentNotSuccessful("MGOLD Payment unsuccessful");
            _mint(msg.sender);
            MowseFacet(diamondAddress).mintMowse();
    
            RandomFortuneFacet(diamondAddress).randomFortune(msg.sender);
        }
    
        function mintMowse() external payable whenNotPaused {
            require(MOWSE_MINT_PRICE == msg.value, string(abi.encodePacked("Payment amount incorrect. Mowse cost ", MOWSE_MINT_PRICE.toString(), "FTM")));
            _mint(msg.sender);
            MowseFacet(diamondAddress).mintMowse();
    
            RandomFortuneFacet(diamondAddress).randomFortune(msg.sender);
        }
    
        function grantMinterRole(address _minter) public onlyRole(DEFAULT_ADMIN_ROLE) {
            _grantRole(MINTER_ROLE, _minter);
        }
    
        function revokeMinterRole(address _minter) public onlyRole(DEFAULT_ADMIN_ROLE) {
            _revokeRole(MINTER_ROLE, _minter);
        }
    
        // ONLY OWNER
        function freeMint(address to) public onlyRole(MINTER_ROLE) whenNotPaused returns (uint256) {
            
            // Mint ERC721 token
            _mint(to);
            // Call MowseFacet freeMint function to save state and setup Mowse
            MowseFacet(diamondAddress).freeMint();
    
            uint256 _mowseId = _mowseIds.current();
            return _mowseId;
        }
    
        function setMowseMintPrice(uint256 mintPrice) public onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused {
            MOWSE_MINT_PRICE = mintPrice;
        }
    
        function setMGOLDMintPrice(uint256 newPrice) public onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused {
            MOWSE_MINT_PRICE_MOWSE_GOLD = newPrice;
        }
    
        function pause() public onlyRole(DEFAULT_ADMIN_ROLE) {
            _pause();
        }
    
        function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) {
            _unpause();
        }
    
        // withdraw should only be called through WithdrawalFacet contract
        function withdraw() public payable {
            if (!AccessControlFacet(diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Mowse Admin Role");
    
            // Withdraw all MGOLD from this contract to the treasuryAddress
            uint256 mowseGoldBalance = mowsegold.balanceOf(address(this));
            mowsegold.transferFrom(address(this), address(WithdrawalFacet(diamondAddress)), mowseGoldBalance);
    
            (bool sentToWithdrawalContract, ) = address(WithdrawalFacet(diamondAddress)).call{value: address(this).balance}("");
            if (!sentToWithdrawalContract) revert DidNotSendToWithdrawalContract();
        }
    
        function exists(uint256 tokenId) external view returns (bool) {
            return _exists(tokenId);
        }
    
        // INTERNAL
        function _mint(address to) internal {
            _mowseIds.increment();
            uint256 _mowseId = _mowseIds.current();
            console.log('Minting Mowse with ID:', _mowseId);
            _safeMint(to, _mowseId);
        }
    
        // UUPS
        function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {}
    
        // The following functions are overrides required by Solidity.
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 tokenId,
            uint256 batchSize
        ) internal override(ERC721Upgradeable, ERC721EnumerableUpgradeable) {
            super._beforeTokenTransfer(from, to, tokenId, batchSize);
            // If transferring main mowse, unset it first
            if (MowseFacet(diamondAddress).getMainMowse(from) == tokenId) {
                MowseFacet(diamondAddress).unsetMainMowse(from);
            }
            // Unequip any mowsewear that the mowse itself does not own.
            // This can get messy if the owner of mowsewear is not the owner of the mowse
            if (from != address(0)) {
                MowseWearFacet(diamondAddress).unequipAllUnownedMowseWear(msg.sender, tokenId);
            }
            // Change owner Address so facets know if for some reason this contract changes
            MowseFacet(diamondAddress).changeOwnerAddress(tokenId, to);
        }
    
        function _burn(uint256 tokenId) internal override(ERC721Upgradeable, ERC721URIStorageUpgradeable) {
            super._burn(tokenId);
        }
    
        function tokenURI(uint256 tokenId) public view override(ERC721Upgradeable, ERC721URIStorageUpgradeable) returns (string memory) {
            // return super.tokenURI(tokenId);
    
            // Override to get tokenURI from MowseFacet
            return MowseFacet(diamondAddress).mowseTokenURI(tokenId);
        }
    
        function onERC721Received(address, address, uint256, bytes calldata) public pure override returns (bytes4) {
            return IERC721ReceiverUpgradeable.onERC721Received.selector;
        }
    
        function supportsInterface(
            bytes4 interfaceId
        ) public view override(ERC721Upgradeable, ERC721EnumerableUpgradeable, ERC721URIStorageUpgradeable, AccessControlUpgradeable) returns (bool) {
            return super.supportsInterface(interfaceId);
        }
    }

    // SPDX-License-Identifier: MIT
    // doublesharp
    pragma solidity ^0.8.12;
    
    import '@openzeppelin/contracts/interfaces/IERC721Enumerable.sol';
    
    import '@openzeppelin/contracts/proxy/Clones.sol';
    
    import '@openzeppelin/contracts/utils/Context.sol';
    import '@openzeppelin/contracts/utils/Strings.sol';
    
    import './interfaces/IMowseBank.sol';
    import './MowseWallet.sol';
    import { MOWSEBANK_CREATE2_SALT } from "./libraries/LibStorage.sol"; 
    import { MowseFacet } from "./facets/MowseFacet.sol";
    
    contract MowseBank is IMowseBank, Context {
        address public diamondAddress;
    
        modifier onlyMowseHolder(uint256 mowseId) {
            require(msg.sender == MowseFacet(diamondAddress).getOwnerAddress(mowseId), 'MowseWallet/only-mowse-holder');
            _;
        }
    
        modifier onlyMowseWallet() {
            require(MowseFacet(diamondAddress).getMowseBankTokenIdForWallet(_msgSender()) != 0, 'MowseWallet/only-mowse-wallet');
            _;
        }
    
        constructor(address _diamondAddress) {
            diamondAddress = _diamondAddress;
            }
    
        function getWalletAddress(uint256 mowseId) public view override returns (address) {
            return Clones.predictDeterministicAddress(MowseFacet(diamondAddress).getMowseBankWalletImplementation(), _salt(mowseId));
        }
    
        function getBalanceForMowse(uint256 mowseId) public view override returns (uint256) {
            return getBalanceForWallet(getWalletAddress(mowseId));
        }
    
        function getBalanceForWallet(address wallet) public view override returns (uint256) {
            return MowseFacet(diamondAddress).getMowseBankBalances(wallet) + wallet.balance;
        }
    
        // MOWSE HOLDERS
    
        event MowseBankForWallet(uint256 mowseId, address wallet, address bank);
    
        /// @notice Allows the owner of an ERC721 to execute arbitrary calls on behalf of the associated wallet.
        /// @dev The wallet will be counterfactually created, calls executed, then the contract destroyed.
        /// @param fromMowseId The mowse token id
        /// @param calls The array of call structs that define that target, amount of ether, and data.
        /// @return The array of call return values.
        function executeCalls(uint256 fromMowseId, MowseWallet.Call[] calldata calls)
            external
            onlyMowseHolder(fromMowseId)
            returns (bytes[] memory)
        {
            return _executeCalls(fromMowseId, calls);
        }
    
        function transfer(
            uint256 fromMowseId,
            address payable to,
            uint256 amount,
            MowseWallet.TransferERC20[] memory erc20transfers,
            MowseWallet.TransferERC721[] memory erc721transfers,
            MowseWallet.TransferERC1155[] memory erc1155transfers
        ) external onlyMowseHolder(fromMowseId) {
            _transfer(fromMowseId, to, amount, erc20transfers, erc721transfers, erc1155transfers);
        }
    
        // WALLET ONLY
    
        function _walletDeposit() external payable onlyMowseWallet {
            uint256 _balance = MowseFacet(diamondAddress).getMowseBankBalances(_msgSender());
            MowseFacet(diamondAddress).setMowseBankBalances(_msgSender(), _balance + msg.value);
        }
    
        function _walletHeldBalance() public view onlyMowseWallet returns (uint256) {
            // only return the balance from a wallet
            return MowseFacet(diamondAddress).getMowseBankBalances(_msgSender());
        }
    
        function _walletTransfer(address payable to, uint256 amount) external onlyMowseWallet {
            // only send from the caller address
            address _wallet = _msgSender();
            uint256 _balance = MowseFacet(diamondAddress).getMowseBankBalances(_wallet);
            require(_balance >= amount, 'MowseWallet/insufficient-balance');
            // decrement balance
            MowseFacet(diamondAddress).setMowseBankBalances(_wallet, _balance - amount);
            // transfer
            (bool success, ) = to.call{value: amount}('');
            require(success, 'MowseWallet/transfer-failed');
        }
    
        // INTERNAL
    
        function _executeCalls(uint256 mowseId, MowseWallet.Call[] calldata calls) internal returns (bytes[] memory) {
            MowseWallet wallet = _createWallet(mowseId);
            bytes[] memory result = wallet.executeCalls(calls);
            wallet.destroy();
            _removeWallet(wallet);
    
            return result;
        }
    
        function _transfer(
            uint256 fromMowseId,
            address payable to,
            uint256 amount,
            MowseWallet.TransferERC20[] memory erc20transfers,
            MowseWallet.TransferERC721[] memory erc721transfers,
            MowseWallet.TransferERC1155[] memory erc1155transfers
        ) internal {
            MowseWallet wallet = _createWallet(fromMowseId);
            wallet.transfer(to, amount, erc20transfers, erc721transfers, erc1155transfers);
            _removeWallet(wallet);
        }
    
        /// @notice Creates a MowseWallet for the given mowse token.
        /// @param mowseId The mowse token id
        /// @return The address of the newly created MowseWallet.
        function _createWallet(uint256 mowseId) internal returns (MowseWallet) {
            // get the create2 clone address
            address payable _address = payable(Clones.cloneDeterministic(MowseFacet(diamondAddress).getMowseBankWalletImplementation(), _salt(mowseId)));
            // get an instance of the wallet
            MowseWallet wallet = MowseWallet(_address);
            // cache while the wallet is alive so we can validate callbacks
            MowseFacet(diamondAddress).setMowseBankTokenIdForWallet(_address, mowseId);
            // return the clone instance
            return wallet;
        }
    
        /// @notice Removes a MowseWallet.
        /// @param wallet The MowseWallet
        function _removeWallet(MowseWallet wallet) internal {
            MowseFacet(diamondAddress).removeMowseBankTokenIdForWallet(address(wallet));
            // delete MowseFacet(diamondAddress).getMowseBankTokenIdForWallet(address(wallet));
        }
    
        /// @notice Computes the CREATE2 salt for the given token.
        /// @param mowseId The mowse token id
        /// @return A bytes32 value that is unique to that token.
        function _salt(uint256 mowseId) internal pure returns (bytes32) {
            return keccak256(abi.encodePacked(MOWSEBANK_CREATE2_SALT, mowseId));
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.9;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";
    
    import {RandomFortuneFacet} from "./facets/RandomFortuneFacet.sol";
    import {GameStorageFacet} from "./facets/GameStorageFacet.sol";
    import {AccessControlFacet} from "./facets/AccessControlFacet.sol";
    import {WithdrawalFacet} from "./facets/WithdrawalFacet.sol";
    import {MowseFacet} from "./facets/MowseFacet.sol";
    import {MowseJobFacet} from "./facets/MowseJobFacet.sol";
    
    import "./MowseBank.sol";
    
    contract MowseGold is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, AccessControlUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable {
        using StringsUpgradeable for uint256;
    
        bytes32 public constant MINTER_ROLE = keccak256("MOWSEGOLD_MINTER_ROLE");
        bytes32 public constant UPGRADER_ROLE = keccak256("MOWSEGOLD_UPGRADER_ROLE");
    
        uint256 public MOWSE_GOLD_MINT_PRICE;
        address private owner;
        address public diamondAddress;
    
        MowseBank mowsebank;
    
        error DidNotSendToTeam();
        error DidNotSendToBackend();
        error DidNotSendToTreasury();
    
        error DidNotSendToWithdrawalContract();
        error MissingAdminRole(string);
        error PaymentAmountIncorrect(string);
        error MustOwnMowse(string);
    
        /// @custom:oz-upgrades-unsafe-allow constructor
        constructor() {
            _disableInitializers();
        }
    
        function initialize(address _diamondAddress, address _mowseBankAddress) public initializer {
            __ERC20_init("MowseGold", "MGOLD");
            __ERC20Burnable_init();
            __AccessControl_init();
            __ERC20Permit_init("MowseGold");
            __UUPSUpgradeable_init();
    
            _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
            _grantRole(MINTER_ROLE, msg.sender);
            _grantRole(UPGRADER_ROLE, msg.sender);
    
            MOWSE_GOLD_MINT_PRICE;
            // DEV: 0.05 FTM to help test mgold minting
            // setMowseGoldMintPrice(0.01 ether);
            setMowseGoldMintPrice(5 ether);
    
            owner = msg.sender;
            diamondAddress = _diamondAddress;
            mowsebank = MowseBank(_mowseBankAddress);
        }
    
        function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
            _mint(to, amount);
        }
    
        function approveForMowse(uint256 mowseId) external {
            if (MowseFacet(diamondAddress).getOwnerAddress(mowseId) != msg.sender) revert MustOwnMowse("Must own Mowse");
            address mowseAddress = mowsebank.getWalletAddress(mowseId);
            approve(mowseAddress, 1000000 * 1e18);
        }
    
        // Every purchase of MowseGold at 5 ether could support up to 25 free transactions for other users!
        function purchaseMowseGold() external payable {
            if (MOWSE_GOLD_MINT_PRICE != msg.value)
                revert PaymentAmountIncorrect(
                    string(abi.encodePacked("Payment amount incorrect. 250 MowseGold costs ", MOWSE_GOLD_MINT_PRICE.toString(), "FTM"))
                );
            // _mint(msg.sender, 100000 * 1e18);  // DEV: 100000 MGOLD to help test mowse functions
            _mint(msg.sender, 250 * 1e18);
            GameStorageFacet(diamondAddress).setHasPurchasedMowseGold(msg.sender);
    
            // Grant a small amount of exp to main mowse
            uint256 mainMowse = MowseFacet(diamondAddress).getMainMowse(msg.sender);
            if (mainMowse != 0) {
                MowseJobFacet(diamondAddress).grantLifeExperience(mainMowse, 10);
            }
    
            RandomFortuneFacet(diamondAddress).randomFortune(msg.sender);
        }
    
        function grantMinterRole(address to) external onlyRole(DEFAULT_ADMIN_ROLE) {
            _grantRole(MINTER_ROLE, to);
        }
    
        function revokeMinterRole(address to) external onlyRole(DEFAULT_ADMIN_ROLE) {
            _revokeRole(MINTER_ROLE, to);
        }
    
        function setMowseGoldMintPrice(uint256 price) public onlyRole(DEFAULT_ADMIN_ROLE) {
            MOWSE_GOLD_MINT_PRICE = price;
        }
    
        function withdraw() public payable {
            if (!AccessControlFacet(diamondAddress).hasMowseAdminRole(msg.sender)) revert MissingAdminRole("Must have Mowse Admin Role");
    
            (bool sentToWithdrawalContract, ) = address(WithdrawalFacet(diamondAddress)).call{value: address(this).balance}("");
            if (!sentToWithdrawalContract) revert DidNotSendToWithdrawalContract();
        }
    
        function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {}
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.9;
    import "hardhat/console.sol";
    /*************************************************************************
    ___  ___                       
    |  \/  |                                          ___
    | .  . | _____      _____  ___           _  _  .-'   '-.
    | |\/| |/ _ \ \ /\ / / __|/ _ \         (.)(.)/         \   
    | |  | | (_) \ V  V /\__ \  __/          /@@             ;
    \_|  |_/\___/ \_/\_/ |___/\___|         o_\\-mm-......-mm`~~~~~~~~~~~~~~~~` 
                                   
    /*************************************************************************/
    
    import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";
    
    import { GameStorageFacet } from "./facets/GameStorageFacet.sol";
    import { AccessControlFacet } from "./facets/AccessControlFacet.sol";
    import { MowseFacet } from "./facets/MowseFacet.sol";
    import { MowseWearFacet } from "./facets/MowseWearFacet.sol";
    import { MowseLootboxFacet } from "./facets/MowseLootboxFacet.sol";
    
    import "./libraries/SetableCountersUpgradeable.sol";
    
    contract MowseLootbox is Initializable, ERC721Upgradeable, ERC721EnumerableUpgradeable, ERC721URIStorageUpgradeable, PausableUpgradeable, AccessControlUpgradeable, UUPSUpgradeable {
        using CountersUpgradeable for CountersUpgradeable.Counter;
        using SetableCountersUpgradeable for CountersUpgradeable.Counter;
        using StringsUpgradeable for uint256;
    
        bytes32 public constant MINTER_ROLE = keccak256("MOWSELOOTBOX_MINTER_ROLE");
        bytes32 public constant UPGRADER_ROLE = keccak256("MOWSELOOTBOX_UPGRADER_ROLE");
        
        address private owner;
        address public diamondAddress;
    
        CountersUpgradeable.Counter private _mowseLootboxIds;
    
        modifier mustExist(uint256 _tokenId) {
          require(_exists(_tokenId) || _tokenId == 0, 'MowseLootbox/must-exist');
          _;
        }
    
        /// @custom:oz-upgrades-unsafe-allow constructor
        constructor() {
            _disableInitializers();
        }
    
        function initialize(address _diamondAddress) initializer public {
            __ERC721_init("MowseLootbox", "MLOOTBOX");
            __ERC721Enumerable_init();
            __ERC721URIStorage_init();
            __Pausable_init();
            __AccessControl_init();
            __UUPSUpgradeable_init();
            diamondAddress = _diamondAddress;
            owner = msg.sender;
    
            _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
            _grantRole(MINTER_ROLE, msg.sender);
            _grantRole(UPGRADER_ROLE, msg.sender);
        }
        function openMowseLootbox(uint256 tokenId) external {
          MowseLootboxFacet(diamondAddress).openMowseLootbox(tokenId, msg.sender);
          
          // After opening, burn token
          _burn(tokenId);
        }
        // For minting new Mowse Lootbox Tokens
        function purchaseMowseLootbox(uint64 index) external {
          console.log('purchaseMowseLootbox', msg.sender);
          MowseLootboxFacet(diamondAddress).purchaseMowseLootbox(index, msg.sender);
          console.log('finished purchaseMowseLootbox');
          _mint(msg.sender);
          console.log('Mint the token to the sender');
        }
        function exists(uint256 tokenId) external view returns (bool) {
          return _exists(tokenId);
        }
    
        // ONLY OWNER
        function freeMint(address to, uint64 lootboxIndex) public {
          MowseLootboxFacet(diamondAddress).freeMowseLootbox(lootboxIndex, to);
          // Mint ERC721 token
          _mint(to);
        }
        function pause() public onlyRole(DEFAULT_ADMIN_ROLE) {
          _pause();
        }
        function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) {
          _unpause();
        }
    
        // INTERNAL
        function _mint(address to) internal {
          _mowseLootboxIds.increment();
          uint256 _lootboxId = _mowseLootboxIds.current();
          console.log('mint this lootbox', _lootboxId);
          _safeMint(to, _lootboxId);
        }
    
        // UUPS
        function _authorizeUpgrade(address newImplementation)
            internal
            onlyRole(UPGRADER_ROLE)
            override
        {}
    
        // The following functions are overrides required by Solidity.
        function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
            internal
            override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
        {
            super._beforeTokenTransfer(from, to, tokenId, batchSize);
        }
        function _burn(uint256 tokenId)
            internal
            override(ERC721Upgradeable, ERC721URIStorageUpgradeable)
        {
            super._burn(tokenId);
        }
        function tokenURI(uint256 tokenId)
            public
            view
            override(ERC721Upgradeable, ERC721URIStorageUpgradeable)
            returns (string memory)
        {
            // return super.tokenURI(tokenId);
    
            // Override to get tokenURI from MowseFacet
            return MowseLootboxFacet(diamondAddress).mowseLootboxTokenURI(tokenId);
        }
        function supportsInterface(bytes4 interfaceId)
            public
            view
            override(ERC721Upgradeable, ERC721EnumerableUpgradeable, ERC721URIStorageUpgradeable, AccessControlUpgradeable)
            returns (bool)
        {
            return super.supportsInterface(interfaceId);
        }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.12;
    
    import '@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol';
    import '@openzeppelin/contracts/utils/Context.sol';
    import '@openzeppelin/contracts/utils/Strings.sol';
    
    import './interfaces/IMowseWallet.sol';
    import './MowseBank.sol';
    
    contract MowseWallet is IMowseWallet, Context, ERC721Holder {
        using Strings for uint256;
        using Strings for bool;
    
        address private immutable bank;
    
        // only the bank can call functions on the wallet
        modifier onlyBank() {
            require(bank == _msgSender(), 'MowseWallet/only-bank');
            _;
        }
    
        // called in the implementation
        constructor(address _bank) {
            // bank = _msgSender();
            bank = _bank;
        }
    
        /// @notice Transfers tokens to another address
        /// @param to The address receiving all tokens
        /// @param ftmAmount The amount of FTM to send
        /// @param erc20 Array of ERC20 token addresses whose entire balance should be transferred
        /// @param erc721 Array of TransferERC721 structs whose tokens should be transferred
        /// @param erc1155 Array of TransferERC1155 structs whose tokens should be transferred
        function transfer(
            address payable to,
            uint256 ftmAmount,
            TransferERC20[] memory erc20,
            TransferERC721[] memory erc721,
            TransferERC1155[] memory erc1155
        ) external onlyBank {
            require(to != address(0), 'MowseWallet/non-zero-to');
            _transferFantom(to, ftmAmount);
            _transferERC20(to, erc20);
            _transferERC721(to, erc721);
            _transferERC1155(to, erc1155);
            // self destruct
            _destroy();
        }
    
        /// @notice Executes calls on behalf of this contract.
        /// @param calls The array of calls to be executed.
        /// @return An array of the return values for each of the calls
        function executeCalls(Call[] calldata calls) external onlyBank returns (bytes[] memory) {
            bytes[] memory response = new bytes[](calls.length);
            for (uint256 i = 0; i < calls.length; i++) {
                response[i] = _executeCall(calls[i].to, calls[i].value, calls[i].data);
            }
            // self destruct from bank
            return response;
        }
    
        /// @notice Destroys this contract via  call from the bank, when we need a response
        function destroy() external onlyBank {
            _destroy();
        }
    
        // INTERNAL
    
        /// @notice Destroys this contract using `selfdestruct`
        function _destroy() internal {
            // transfer funds to bank
            MowseBank _bank = MowseBank(bank);
            if (address(this).balance > 0) {
                _bank._walletDeposit{value: address(this).balance}();
            }
            // remove the bytecode
            selfdestruct(payable(address(0)));
        }
    
        /// @notice Transfers fantom held by the contract to another account
        /// @param to The account to transfer fantom to
        /// @param amount The amount of fantom to transfer
        function _transferFantom(address payable to, uint256 amount) internal {
            if (amount > 0) {
                // bank instance
                MowseBank _bank = MowseBank(bank);
                // make sure there is cumulatively enough balance
                require(amount <= _bank.getBalanceForWallet(address(this)), 'MowseWallet/insufficient-balance');
                // figure out the amount to transfer from the bank
                uint256 _bankBalance = _bank._walletHeldBalance();
                uint256 _amountFromBank = amount < _bankBalance ? amount : _bankBalance;
                _bank._walletTransfer(to, _amountFromBank);
                // if there is any left over transfer it from this contract
                if (_amountFromBank < amount) {
                    to.transfer(amount - _amountFromBank);
                }
    
                emit TransferredFantom(to, amount);
            }
        }
    
        /// @notice Executes a call to another contract
        /// @param to The address to call
        /// @param value The Fantom to pass along with the call
        /// @param data The call data
        /// @return The return data from the call
        function _executeCall(
            address to,
            uint256 value,
            bytes memory data
        ) internal returns (bytes memory) {
            (bool succeeded, bytes memory returnValue) = to.call{value: value}(data);
            require(succeeded, string(returnValue));
            return returnValue;
        }
    
        /// @notice Transfers the entire balance of ERC20s to an account
        /// @param to The recipient of the transfers
        /// @param transfers An array of ERC20 tokens to transfer out.  The balance of each will be transferred.
        function _transferERC20(address to, TransferERC20[] memory transfers) internal {
            for (uint256 i = 0; i < transfers.length; i++) {
                transfers[i].token.transfer(to, transfers[i].amount);
    
                emit TransferredERC20(address(transfers[i].token), transfers[i].amount);
            }
        }
    
        /// @notice Transfers ERC721 tokens to an account
        /// @param to The recipient of the transfers
        /// @param transfers An array of TransferERC721 structs that each include the ERC721 token to transfer and the corresponding token ids.
        function _transferERC721(address to, TransferERC721[] memory transfers) internal {
            for (uint256 i = 0; i < transfers.length; i++) {
                for (uint256 tokenIndex = 0; tokenIndex < transfers[i].tokenIds.length; tokenIndex++) {
                    transfers[i].token.transferFrom(address(this), to, transfers[i].tokenIds[tokenIndex]);
                }
    
                emit TransferredERC721(address(transfers[i].token), transfers[i].tokenIds);
            }
        }
    
        /// @notice Transfers ERC1155 tokens to an account
        /// @param to The recipient of the transfers
        /// @param transfers An array of WithdrawERC1155 structs that each include the ERC1155 to transfer and it's corresponding token ids and amounts.
        function _transferERC1155(address to, TransferERC1155[] memory transfers) internal {
            for (uint256 i = 0; i < transfers.length; i++) {
                transfers[i].token.safeBatchTransferFrom(
                    address(this),
                    to,
                    transfers[i].ids,
                    transfers[i].amounts,
                    transfers[i].data
                );
    
                emit TransferredERC1155(
                    address(transfers[i].token),
                    transfers[i].ids,
                    transfers[i].amounts,
                    transfers[i].data
                );
            }
        }
    
        receive() external payable {}
    }

    // SPDX-License-Identifier: MIT
    pragma solidity >=0.4.22 <0.9.0;
    
    library console {
        address constant CONSOLE_ADDRESS =
            0x000000000000000000636F6e736F6c652e6c6f67;
    
        function _sendLogPayloadImplementation(bytes memory payload) internal view {
            address consoleAddress = CONSOLE_ADDRESS;
            /// @solidity memory-safe-assembly
            assembly {
                pop(
                    staticcall(
                        gas(),
                        consoleAddress,
                        add(payload, 32),
                        mload(payload),
                        0,
                        0
                    )
                )
            }
        }
    
        function _castToPure(
          function(bytes memory) internal view fnIn
        ) internal pure returns (function(bytes memory) pure fnOut) {
            assembly {
                fnOut := fnIn
            }
        }
    
        function _sendLogPayload(bytes memory payload) internal pure {
            _castToPure(_sendLogPayloadImplementation)(payload);
        }
    
        function log() internal pure {
            _sendLogPayload(abi.encodeWithSignature("log()"));
        }
        function logInt(int256 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(int256)", p0));
        }
    
        function logUint(uint256 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0));
        }
    
        function logString(string memory p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string)", p0));
        }
    
        function logBool(bool p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool)", p0));
        }
    
        function logAddress(address p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address)", p0));
        }
    
        function logBytes(bytes memory p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes)", p0));
        }
    
        function logBytes1(bytes1 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0));
        }
    
        function logBytes2(bytes2 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0));
        }
    
        function logBytes3(bytes3 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0));
        }
    
        function logBytes4(bytes4 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0));
        }
    
        function logBytes5(bytes5 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0));
        }
    
        function logBytes6(bytes6 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0));
        }
    
        function logBytes7(bytes7 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0));
        }
    
        function logBytes8(bytes8 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0));
        }
    
        function logBytes9(bytes9 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0));
        }
    
        function logBytes10(bytes10 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0));
        }
    
        function logBytes11(bytes11 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0));
        }
    
        function logBytes12(bytes12 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0));
        }
    
        function logBytes13(bytes13 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0));
        }
    
        function logBytes14(bytes14 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0));
        }
    
        function logBytes15(bytes15 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0));
        }
    
        function logBytes16(bytes16 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0));
        }
    
        function logBytes17(bytes17 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0));
        }
    
        function logBytes18(bytes18 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0));
        }
    
        function logBytes19(bytes19 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0));
        }
    
        function logBytes20(bytes20 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0));
        }
    
        function logBytes21(bytes21 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0));
        }
    
        function logBytes22(bytes22 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0));
        }
    
        function logBytes23(bytes23 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0));
        }
    
        function logBytes24(bytes24 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0));
        }
    
        function logBytes25(bytes25 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0));
        }
    
        function logBytes26(bytes26 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0));
        }
    
        function logBytes27(bytes27 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0));
        }
    
        function logBytes28(bytes28 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0));
        }
    
        function logBytes29(bytes29 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0));
        }
    
        function logBytes30(bytes30 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0));
        }
    
        function logBytes31(bytes31 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0));
        }
    
        function logBytes32(bytes32 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0));
        }
    
        function log(uint256 p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0));
        }
    
        function log(string memory p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string)", p0));
        }
    
        function log(bool p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool)", p0));
        }
    
        function log(address p0) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address)", p0));
        }
    
        function log(uint256 p0, uint256 p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256)", p0, p1));
        }
    
        function log(uint256 p0, string memory p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string)", p0, p1));
        }
    
        function log(uint256 p0, bool p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool)", p0, p1));
        }
    
        function log(uint256 p0, address p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address)", p0, p1));
        }
    
        function log(string memory p0, uint256 p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1));
        }
    
        function log(string memory p0, string memory p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1));
        }
    
        function log(string memory p0, bool p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1));
        }
    
        function log(string memory p0, address p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1));
        }
    
        function log(bool p0, uint256 p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256)", p0, p1));
        }
    
        function log(bool p0, string memory p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1));
        }
    
        function log(bool p0, bool p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1));
        }
    
        function log(bool p0, address p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1));
        }
    
        function log(address p0, uint256 p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256)", p0, p1));
        }
    
        function log(address p0, string memory p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1));
        }
    
        function log(address p0, bool p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1));
        }
    
        function log(address p0, address p1) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1));
        }
    
        function log(uint256 p0, uint256 p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256)", p0, p1, p2));
        }
    
        function log(uint256 p0, uint256 p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string)", p0, p1, p2));
        }
    
        function log(uint256 p0, uint256 p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool)", p0, p1, p2));
        }
    
        function log(uint256 p0, uint256 p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address)", p0, p1, p2));
        }
    
        function log(uint256 p0, string memory p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256)", p0, p1, p2));
        }
    
        function log(uint256 p0, string memory p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string)", p0, p1, p2));
        }
    
        function log(uint256 p0, string memory p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool)", p0, p1, p2));
        }
    
        function log(uint256 p0, string memory p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address)", p0, p1, p2));
        }
    
        function log(uint256 p0, bool p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256)", p0, p1, p2));
        }
    
        function log(uint256 p0, bool p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string)", p0, p1, p2));
        }
    
        function log(uint256 p0, bool p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool)", p0, p1, p2));
        }
    
        function log(uint256 p0, bool p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address)", p0, p1, p2));
        }
    
        function log(uint256 p0, address p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256)", p0, p1, p2));
        }
    
        function log(uint256 p0, address p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string)", p0, p1, p2));
        }
    
        function log(uint256 p0, address p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool)", p0, p1, p2));
        }
    
        function log(uint256 p0, address p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address)", p0, p1, p2));
        }
    
        function log(string memory p0, uint256 p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256)", p0, p1, p2));
        }
    
        function log(string memory p0, uint256 p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string)", p0, p1, p2));
        }
    
        function log(string memory p0, uint256 p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool)", p0, p1, p2));
        }
    
        function log(string memory p0, uint256 p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address)", p0, p1, p2));
        }
    
        function log(string memory p0, string memory p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256)", p0, p1, p2));
        }
    
        function log(string memory p0, string memory p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2));
        }
    
        function log(string memory p0, string memory p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2));
        }
    
        function log(string memory p0, string memory p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2));
        }
    
        function log(string memory p0, bool p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256)", p0, p1, p2));
        }
    
        function log(string memory p0, bool p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2));
        }
    
        function log(string memory p0, bool p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2));
        }
    
        function log(string memory p0, bool p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2));
        }
    
        function log(string memory p0, address p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256)", p0, p1, p2));
        }
    
        function log(string memory p0, address p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2));
        }
    
        function log(string memory p0, address p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2));
        }
    
        function log(string memory p0, address p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2));
        }
    
        function log(bool p0, uint256 p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256)", p0, p1, p2));
        }
    
        function log(bool p0, uint256 p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string)", p0, p1, p2));
        }
    
        function log(bool p0, uint256 p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool)", p0, p1, p2));
        }
    
        function log(bool p0, uint256 p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address)", p0, p1, p2));
        }
    
        function log(bool p0, string memory p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256)", p0, p1, p2));
        }
    
        function log(bool p0, string memory p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2));
        }
    
        function log(bool p0, string memory p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2));
        }
    
        function log(bool p0, string memory p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2));
        }
    
        function log(bool p0, bool p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256)", p0, p1, p2));
        }
    
        function log(bool p0, bool p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2));
        }
    
        function log(bool p0, bool p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2));
        }
    
        function log(bool p0, bool p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2));
        }
    
        function log(bool p0, address p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256)", p0, p1, p2));
        }
    
        function log(bool p0, address p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2));
        }
    
        function log(bool p0, address p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2));
        }
    
        function log(bool p0, address p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2));
        }
    
        function log(address p0, uint256 p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256)", p0, p1, p2));
        }
    
        function log(address p0, uint256 p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string)", p0, p1, p2));
        }
    
        function log(address p0, uint256 p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool)", p0, p1, p2));
        }
    
        function log(address p0, uint256 p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address)", p0, p1, p2));
        }
    
        function log(address p0, string memory p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256)", p0, p1, p2));
        }
    
        function log(address p0, string memory p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2));
        }
    
        function log(address p0, string memory p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2));
        }
    
        function log(address p0, string memory p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2));
        }
    
        function log(address p0, bool p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256)", p0, p1, p2));
        }
    
        function log(address p0, bool p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2));
        }
    
        function log(address p0, bool p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2));
        }
    
        function log(address p0, bool p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2));
        }
    
        function log(address p0, address p1, uint256 p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256)", p0, p1, p2));
        }
    
        function log(address p0, address p1, string memory p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2));
        }
    
        function log(address p0, address p1, bool p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2));
        }
    
        function log(address p0, address p1, address p2) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2));
        }
    
        function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, uint256 p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, string memory p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, bool p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,address)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,string)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,bool)", p0, p1, p2, p3));
        }
    
        function log(uint256 p0, address p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, uint256 p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, string memory p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, bool p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3));
        }
    
        function log(string memory p0, address p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, uint256 p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, string memory p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, bool p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3));
        }
    
        function log(bool p0, address p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, uint256 p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, string memory p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, bool p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, uint256 p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, uint256 p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, uint256 p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, uint256 p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, string memory p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, string memory p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, string memory p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, string memory p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, bool p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, bool p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, bool p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, bool p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, address p2, uint256 p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint256)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, address p2, string memory p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, address p2, bool p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3));
        }
    
        function log(address p0, address p1, address p2, address p3) internal pure {
            _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3));
        }
    
    }

    Please enter a contract address above to load the contract details and source code.

    Context size (optional):