/*
                                                  .:^~~!!!!~^:.                                                                             
                                              .:!7???777777???77!^:.          .:^~~!!7777!!~~^.                                             
                                            :!7?77777777777777777??7!^.  .:~!77??77777777777???7!:                                          
                                          :7?77777777777777777777777??7~^7?7777777777777777777777?!.                                        
                                        :!77777777777!!~~~~~~~~~~~~~!77?7^!77777777777777777777777?7.                                       
                                       ~777777777!~~~~!777777777777!~~~!77^7777777777777!!!!!!!!!777!.                                      
                                     .!?777777!~~~!77777777777777777777~^~~~77!!~~~~^^^^^^~~~~~~~~~~~:                                      
                                     !?777777!777777777777777777777777777~~^777777777777777777777!!!!!^:.                                   
                                    ^?777777777777777777777777!!!!~~!!!!!!~:7777777777777777777777777777!!^.                                
                                   :7777777777777777777777!!~!!!777!!!!!!!7!^!?7777777777777777777!!~~~~!!!!!!^.                            
                              .:^~^!7777777777777777777!!~!!7!!~~~~!!!77!!!!~.^777777777777777!!~~~~~~!!!!!!~~~^.                           
                           .~77??!~77777777777777!!!~~~~~~~^^^^^~~~~~~~~~~~!!!!!~!7777777!!!~~~~~~!!777!!7J5GBBGGY!.                        
                          ~777777^77777777777777!~~~~~!~^^~!?5G#&&&&&&&&#BPY7~~~!!~77~!!!!~~~~!!!!77?J5B&@@@@@@@@@@&G^                      
                         !?77777!~7777777777777!~!!77~^!YB&@@@@@@@@@@@@@@@@@@&G?~~~!77!~~~~!77?YPB&&@@@@@@@@&57?B@@@@@Y                     
                        ~?7777777777777777777777777775&@@@@@@@@@@@@@@@@B!~7G@@@@&GJ~^~7J5GB&&@@@@@@@@@@@@@@@!  ^~@@@@@&.                    
                       ~?77777777777777777777777777~5@@@@@@@@@@@@@@@@@&   ~ &@@@@@&7Y@@@@@@@@@@@@@@@@@@@@@@@@GYP&@&&BJ.                     
                      ~?77777777777777777777777777777!?5B&&@@@@@@@@@@@@B?7?B@&BPJ?7!^JJ55PGB###&&&&&&&&&##BGP5YJ?7!~:                       
                     ~?777!~7^7777777777777777777777777!!777?JY5PPGB##BBG5Y?77!77!~~~~~777!!!!!!!777!!!!!!!7777~:.                          
                    ^?7777^!?~~!77777777777777777777777!~!!!!!!!!!!!!!!777777!!~~~~!!77777777777777777777777~:                              
                   ^?777!~!7!!~!777777777777777777777777777!!!!!~~~!!!!!!!~~~~~~!!77777!~~!!!!777!!!!!!!~~.                                 
                  .777!~~!~~!77!777777777777777777777777777777777777!!!!!~~!~~~~!!777777777!!^^~!!!!777777~                                 
                  !77!!77~777777777777777777!!~~~~~~~~~~~~~!!!!!~!!!!!!777!~!77777777777777777777777777!!~~~~.                              
                 .7777777777777777777777!~~!!777777777777777!!!!!!~!!!!!!~~77777777777777777777!!!!~~~!!!77??!                              
                 ~?7777777777777777777!~~777????777777777????????777777!!!!!!~~~~~~~~~~~~~~~~!!!!!7777777??7~.     !.                       
                 !777777777777777777!~!777?7!^:..        ..::^^~!!77?????????77777777777777?????????7777!~:        :                        
                .777777777777777777~~777?7:                         ..::^^~!!!77777!!!!!!!!~~~~^^::...                                      
                :77777777777777777^!7777^                                                                                ^!.                
                :?777777777777777^7777?:                                                                  ..:::::.      .^:                 
                :?777777777~!!!7^!777?^                            ......                              :~77????7777!~:                      
                .7777777!~^!7^7~~777?^                        :^!777??77777!~::.                   .:~7??7!~::^!7777??~                     
                 ~?77777!!!~!!7^7777!                      .~777777!!~~!!77????77!~^::........:^~!7??7!^..       .....                      
                 .77777777777?!~777?:                    .!?7777^.         .:^~!7777???77777????77~^.                                       
                  ~?7777777777~~777?.                   .77777!                  ..:^~!!!77!~^:.       ..                                   
                  .77777777777~~777?.                   777777                                         ^!.                                  
                   ^?777777777!^777?~                  :?7777~                                                                              
                    ~?777777777^77777^                 :!!!!!^                                       :.                                     
                     ~777777777!^7777?7:.      ..:^~!777777!!!~:..                                                                          
                    .::~77777777!~!7777?77!!!77?????7777777777???777!~^^:.................::::^^^~~^:.                                      
                 :~7777~^^~!777777~~!!7777777777!!!!~~~!~!!~~!!!!7777?????????????????????????????77!^                                      
             .^!7?77777777!~~~~!!7777!!!!~~~~~!!!!77777777~^777!!~~~~!!!!!!!!!!!!!!!!!!!!!!!~~:...                                          
          :!7??7777777777777777!!!~~!!!!!777777777777777~~7^77!!!~^~77777!!!!!!!7777777!!^:.                    ::     .                    
           ~?77777777777777777777777777!!!77777777777!~~777:~~!77~!777777777777!!!!~:..                       .~?: ..^!^                    
            :7?777777777777777777777777777777777777!~~777777777!~!?77777!~~!!!!!!!7!.                       :~777!77?7:                     
              ~7777777777777777777777777777777!!~~~!77777777777~~!7777777777777777777!^.                .^~7?777777777:.                    
               .!?777777777777777777777777!!~~!!77777777777777777!^~777777777777777777?7!^.         .^~77?777777777????7!:                  
                 :!?777777777777777777!~~~!777777777777777!!~~~~!!!7777777777777777777777?7!: ..:~!77?777777777777!^:..                     
                   :7?7777777777777777777777777777777777~~!777777777777777777777777777777777?~~???777777777777?!.                           
                     :!?7777777777777777777777777777777^!7777777777777777777777777777777777777!^77777777777777^                             
                       .~7?77777777777777777777777777?~^?77777777777777777777777777777777777777!^77777777777?:                              
                         .^!7?777777777777777777777777:77777777777777777777777777777777777777777!^7777777777:                               
                            .^!7?77777777777777777777^!777777777777777777777777777777777777777777!^777777?7.                                
                               .^!7??7777777777777777~77777777777777777777777777777777777777777777!~7777?~                                  
                                  .:~77??777777777777777777777777777777777777777777777777777777777?^!77~:                                   
                                      .:~!7???777777777777777777777777777777777777777777777777??77!^..                                      
                                           .:~!77???777777777777777777777777777777777777???77!~^..                                          
                                                ..:^~!!777777777777777777777777777777!!~^:..                                                
                                                        ..::^^~~~~~~~~~~~~~~~~^^::...    

                ##########   ########### ########### ########### ########### ###########  ##   ##   ##   ##   ##  
                ###     ###  ###         ###         ###         ###         ###         #### #### #### #### #### 
                ###     ###  ###         ###         ###         ###         ###         #### #### #### #### #### 
                ##########   #######     #######     #######     #######     #######      ##   ##   ##   ##   ## 
                ###     ###  ###         ###         ###         ###         ###                     
                ###      ### ###         ###         ###         ###         ###          ##   ##   ##   ##   ##  
                ###      ### ########### ########### ########### ########### ###########  ##   ##   ##   ##   ## 
                                                                                      
Tokenomics

Total Supply: 1.000.000.000 Tokens
Buy tax: 2%
Sell tax: 3%

Max buy: 1% of total supply / 10.000.000 REEEEE

-------------------------------------------------------------

Deposits & Withdrawals to/from games don't have fees.
You can only deposit/withdraw to your registered address.

What it's all about @ https://reeeee.net

telegram: https://t.me/ReeeeeToken

*/

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }
}

abstract contract Ownable is Context {
    address private _owner;

    error Ownable_CallerIsNotTheOwner();
    error Ownable_NewOwnerIsTheZeroAddress();

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor() {
        _transferOwnership(_msgSender());
    }

    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    function owner() public view virtual returns (address) {
        return _owner;
    }

    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) revert Ownable_CallerIsNotTheOwner();
    }

    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) revert Ownable_NewOwnerIsTheZeroAddress();
        _transferOwnership(newOwner);
    }

    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

library EnumerableSet {
    struct Set {
        bytes32[] _values;
        mapping(bytes32 => uint256) _indexes;
    }

    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    function _remove(Set storage set, bytes32 value) private returns (bool) {
        uint256 valueIndex = set._indexes[value];
        if (valueIndex != 0) {
            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;
            if (lastIndex != toDeleteIndex) {
                bytes32 lastValue = set._values[lastIndex];
                set._values[toDeleteIndex] = lastValue;
                set._indexes[lastValue] = valueIndex;
            }
            set._values.pop();
            delete set._indexes[value];
            return true;
        } else {
            return false;
        }
    }

    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    struct AddressSet {
        Set _inner;
    }

    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

library SafeMath {
    error SafeMath_AdditionOverflow();
    error SafeMath_MultiplicationOverflow();
    error SafeMath_SubtractionUnderflow();
    error SafeMath_DivisionByZero();

    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        if (c < a) {
            revert SafeMath_AdditionOverflow();
        }
        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b > a) {
            revert SafeMath_SubtractionUnderflow();
        }
        uint256 c = a - b;
        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        if(c / a != b) {
            revert SafeMath_MultiplicationOverflow();
        }
        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b <= 0) {
            revert SafeMath_DivisionByZero();
        }
        uint256 c = a / b;
        return c;
    }
}

abstract contract ReentrancyGuard {
    error ReentrancyGuard_ReentrantCall();

    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        if (_status == _ENTERED) revert ReentrancyGuard_ReentrantCall();
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        _status = _NOT_ENTERED;
    }
}

interface IUniswapV2Factory {
    function createPair(address tokenA, address tokenB) external returns (address pair);
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}

interface IUniswapV2Router02 {
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function factory() external pure returns (address);
    function WETH() external pure returns (address);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
}

library UniswapV2Library {
    error UniswapV2Library_IDENTICAL_ADDRESSES();
    error UniswapV2Library_ZERO_ADDRESS();

    function sortTokens(address tokenA, address tokenB) internal pure returns(address token0, address token1) {
        if (tokenA == tokenB) revert UniswapV2Library_IDENTICAL_ADDRESSES();
        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        if (token0 == address(0)) revert UniswapV2Library_ZERO_ADDRESS();
    }

    function pairFor(address factory, address tokenA, address tokenB) internal pure returns(address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(uint160(uint(keccak256(
            abi.encodePacked(hex"ff", factory, keccak256(abi.encodePacked(token0, token1)), 
            hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f"
        )))));
    }
}

contract REEEEE is IERC20, Context, Ownable, ReentrancyGuard {
    using EnumerableSet for EnumerableSet.AddressSet;
    using SafeMath for uint256;

    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    mapping(address => address) private _players;
    EnumerableSet.AddressSet private ExcludedFromFee;
    
    struct Games {
        bool active;
        address game;
        uint256 id;
        string name;
    }
    mapping(address => Games) private games;
    EnumerableSet.AddressSet private gamesList;
    
    IUniswapV2Router02 private router;
    address private _router;
    address private pair;
    address private _pair;
    address private _owner;

    string constant NAME = "REEEEE!";
    string constant SYMBOL = "REEEEE";
    uint8 constant DECIMALS = 18;
    uint256 immutable _totalSupply = 1000000000 * 10**DECIMALS;

    // all games total deposits and withdrawals
    struct Total {
        uint256 deposited;
        uint256 withdrawn;
    }
    Total private total = Total(0, 0);

    struct ContractConf {
        bool tradingEnabled;
        bool autoSwapFees;
        uint16 buyTax;
        uint16 sellTax;
        address payable vaultAddress;
        address payable devAddress;
        uint256 maxBuyPercent;
        uint256 maxBuyAmount;
        uint256 maxWalletPercent;
        uint256 maxWalletAmount;
        uint256 swapThreshold;
    }
    ContractConf private conf = ContractConf({
        tradingEnabled: false,
        autoSwapFees: false,
        buyTax: 200,
        sellTax: 300,
        vaultAddress: payable(0xCc6b8e1F58f7D8b988E0d59F011B86d150Da00b2),
        devAddress: payable(0xF89c3f44AE28a026BBACa5aE8FC75b9301cD849e),
        maxBuyPercent: 100,
        maxBuyAmount: 10000000 * 10**DECIMALS,
        maxWalletPercent: 100,
        maxWalletAmount: 10000000 * 10**DECIMALS,
        swapThreshold: 10000000 * 10**DECIMALS
    });

    bool inSwap;

    modifier lockTheSwap() {
        inSwap = true;
        _;
        inSwap = false;
    }

    error BalanceToLow(address account, uint256 balance, uint256 amount);
    error DepositBalanceToLow(address account, uint256 balance, uint256 amount);
    error Exists(bool exists);
    error InvalidGameId();
    error NotAuthorized();
    error NotAGameAddress();
    error NotPlayersAccount();
    error OverMaxBuy(uint256 maxBuyAmount);
    error OverMaxWallet(uint256 maxWalletAmount);
    error SameValue();
    error TaxTooHight(uint16 maxTotalTax);
    error TradingDisabled();
    error ZeroAddress();
    error ZeroAmount();
    error InvalidValue();
    
    event AccountExcludedFromFee(address account, bool excluded);
    event Burned(address indexed from, uint256 amount);
    event Deposited(address indexed account, uint256 amount);
    event MaxBuySet(uint256 maxPercent, uint256 maxAmount);
    event DisableMaxBuyAtSet(uint256 amount);
    event SentEth(address to, uint256 amount);
    event SwappedTokensForEth(address account, uint256 amountTokens, uint256 amountEthers);
    event TaxesSet(uint16 buyTax, bool buyTaxEnabled, uint16 sellTax, bool sellTaxEnabled);
    event TradingEnabled();
    event VaultAddressSet(address vaultAddress);
    event DevAddressSet(address devAddress);
    event Withdrawn(address indexed account, uint256 amount);
    event WithdrawnERC20(address account, uint256 amount, address token);
    
    /// UniSwap 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
    constructor(
        address router_
    ) payable {
        _owner = address(owner());

        ExcludedFromFee.add(owner());
        ExcludedFromFee.add(address(this));
        ExcludedFromFee.add(conf.vaultAddress);
        ExcludedFromFee.add(conf.devAddress);

        router = IUniswapV2Router02(router_);
        _router = address(router);
        pair = IUniswapV2Factory(router.factory()).createPair(address(this), router.WETH());
        _pair = address(pair);

        _balances[_msgSender()] = _totalSupply;
        emit Transfer(address(0), _msgSender(), _totalSupply);
    }

    receive() external payable {}
    fallback() external payable {}

    function enableTrading() external onlyOwner {
        if (conf.tradingEnabled) revert();
        conf.tradingEnabled = true;
    }

    function name() public pure returns(
        string memory
    ) {
        return NAME;
    }

    function symbol() public pure returns(
        string memory
    ) {
        return SYMBOL;
    }

    function decimals() public pure returns(
        uint8
    ) {
        return DECIMALS;
    }

    function totalSupply() public view override returns(
        uint256
    ) {
        return _totalSupply;
    }

    function balanceOf(
        address address_
    ) public view override returns(uint256) {
        return _balances[address_];
    }

    function transfer(
        address recipient_,
        uint256 amount_
    ) public override returns(bool) {
        _transfer(_msgSender(), recipient_, amount_);
        return true;
    }

    function allowance(
        address owner_,
        address spender_
    ) public view override returns(uint256) {
        return _allowances[owner_][spender_];
    }

    function approve(
        address spender_,
        uint256 amount_
    ) public override returns(bool) {
        _approve(_msgSender(), spender_, amount_);
        return true;
    }

    function _approve(
        address owner_,
        address spender_,
        uint256 amount_
    ) internal virtual {
        if (owner_ == address(0)) revert ZeroAddress();
        if (spender_ == address(0)) revert ZeroAddress();
        _allowances[owner_][spender_] = amount_;
        emit Approval(owner_, spender_, amount_);
    }

    function increaseAllowance(
        address spender, 
        uint256 addedValue
    ) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    function decreaseAllowance(
        address spender, 
        uint256 subtractedValue
    ) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue));
        return true;
    }

    function transferFrom(
        address sender_,
        address recipient_,
        uint256 amount_
    ) public override returns(bool) {
        _transfer(sender_, recipient_, amount_);
         _approve(sender_, _msgSender(), _allowances[sender_][_msgSender()].sub(amount_));
        return true;
    }

    function _transfer(
        address from_,
        address to_,
        uint256 amount_
    ) private {
        if (from_ == address(0)) revert ZeroAddress();
        if (to_ == address(0)) revert ZeroAddress();
        
        if (amount_ == 0) {
            emit Transfer(from_, to_, 0);
            return;
        }

        if (amount_ > _balances[from_]) {
            revert BalanceToLow(from_, amount_, _balances[from_]);
        }

        if (ExcludedFromFee.contains(from_) 
            || ExcludedFromFee.contains(to_)
            || _msgSender() == _owner
        ) {
            _transferBalance(from_, to_, amount_, false);
            return;
        }

        if (!conf.tradingEnabled 
            && !ExcludedFromFee.contains(from_)
            && !ExcludedFromFee.contains(to_)
        ) {
            revert TradingDisabled();
        }

        bool buy = from_ == _pair && to_ != _router;
        bool sell = to_ == _pair && from_ != _router;
        bool trx = (!buy && !sell) || inSwap;
         

        if (trx) {
            _transferBalance(from_, to_, amount_, false);
            return;
        }

        uint256 fee = 0;
        if (buy && conf.buyTax > 0) {
            fee = amount_.mul(conf.buyTax).div(1e4);
        } else 
        if (sell && conf.sellTax > 0) {
            fee = amount_.mul(conf.sellTax).div(1e4);
        }

        if (fee > 0) {
            _transferBalance(from_, address(this), fee, false);
            amount_ = amount_.sub(fee);
        }

        if (buy 
            && conf.maxBuyPercent > 0 
            && amount_ > conf.maxBuyAmount
        ) {
            revert OverMaxBuy(conf.maxBuyAmount);
        }

        if ((buy || trx) 
            && conf.maxWalletPercent > 0
            && to_ != address(this)
            && to_ != _pair
        ) {
            uint256 newBalance = _balances[to_].add(amount_);
            if(newBalance > conf.maxWalletAmount) {
                revert OverMaxWallet(conf.maxWalletAmount);
            }
        }

        if (_balances[address(this)] > conf.swapThreshold
            && sell 
            && conf.autoSwapFees
        ) {
            _swapAndSend();
        }

        _transferBalance(from_, to_, amount_, false);
    }

    function setVaultAddress(
        address address_
    ) external onlyOwner {
        if (address_ == address(0)) {
            revert ZeroAddress();
        }
        if (conf.vaultAddress != address_) {
            conf.vaultAddress = payable(address_);
        } else {
            revert SameValue();
        }
        emit VaultAddressSet(address_);
    }

    function setDevAddress(
        address address_
    ) external onlyOwner {
        if (address_ == address(0)) {
            revert ZeroAddress();
        }
        if (conf.devAddress != address_) {
            conf.devAddress = payable(address_);
        } else {
            revert SameValue();
        }
        emit VaultAddressSet(address_);
    }

    // 0 to disable
    function setTaxes(
        uint16 buyTax_,  
        uint16 sellTax_
    ) external onlyOwner returns(
        bool
    ) {
        uint16 totalTax = buyTax_ + sellTax_;
        if (totalTax > 5) {
            revert TaxTooHight(5);
        }
        emit TaxesSet(buyTax_, buyTax_ > 0, sellTax_, sellTax_ > 0);
        conf.buyTax = buyTax_ * 100;
        conf.sellTax = sellTax_ * 100;
        return true;
    }

    function setMaxBuy(
        uint8 percent_
    ) external onlyOwner returns(
        uint256
    ) {
        if (percent_ == conf.maxBuyPercent) {
            revert SameValue();
        }
        // zero means disable
        if (percent_ != 0) {
            // we don't allow to be set below 1%
            if (percent_ < 1 || percent_ > 100) {
                revert InvalidValue();
            }
        }
        conf.maxBuyPercent = percent_ * 100;
        conf.maxBuyAmount = _totalSupply.mul(conf.maxBuyPercent).div(1e4);
        emit MaxBuySet(conf.maxBuyPercent, conf.maxBuyAmount);
        return conf.maxBuyAmount;
    }

    function setMaxWallet(
        uint8 percent_
    ) external onlyOwner returns(
        uint256
    ) {
        if (percent_ == conf.maxWalletPercent) {
            revert SameValue();
        }
        // zero means disable
        if (percent_ != 0) {
            // we don't allow to be set below 1%
            if (percent_ < 1 || percent_ > 100) {
                revert InvalidValue();
            }
        }
        conf.maxWalletPercent = percent_ * 100;
        conf.maxWalletAmount = _totalSupply.mul(conf.maxWalletPercent).div(1e4);
        emit MaxBuySet(conf.maxWalletPercent, conf.maxWalletAmount);
        return conf.maxWalletAmount;
    }

    function transferBalance(
        address from_,
        address to_,
        uint256 amount_
    ) external returns(
        uint256, 
        uint256
    ) {
        if (_msgSender() != _owner) {
            if (!gamesList.contains(_msgSender())) {
                revert NotAuthorized();
            }
        }
        return _transferBalance(from_, to_, amount_, true);
    }

    function _transferBalance(
        address from_,
        address to_,
        uint256 amount_,
        bool check_
    ) private returns(
        uint256, uint256
    ) {
        if (check_ && amount_ > _balances[from_]) {
            revert BalanceToLow(from_, _balances[from_], amount_);
        }
        unchecked {
            _balances[from_] -= amount_;
            _balances[to_] += amount_;   
        }
        emit Transfer(from_, to_, amount_);
        return (_balances[from_], _balances[to_]);
    }

    function toggleExcludedFromFee(
        address address_
    ) external onlyOwner returns(
        bool
    ) {
        if (ExcludedFromFee.contains(address_)) {
            ExcludedFromFee.remove(address_);
        } else {
            ExcludedFromFee.add(address_);
        }
        emit AccountExcludedFromFee(address_, ExcludedFromFee.contains(address_));
        return ExcludedFromFee.contains(address_);
    }

    function swapTokensToEth() external onlyOwner {
        _swapAndSend();
    }

    function _swapAndSend() private {
        uint256 balance = _balances[address(this)];
        _swapTokensToEth();
        _sendEth(address(this).balance);
        emit SwappedTokensForEth(conf.vaultAddress, balance, address(this).balance);
    }

    function _swapTokensToEth() private lockTheSwap {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = router.WETH();
        _approve(address(this), _router, _balances[address(this)]);
        router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            _balances[address(this)], 
            0, 
            path, 
            address(this), 
            block.timestamp
        );
    }

    function sendEth(
        uint256 amount_
    ) external onlyOwner returns(
        bool success
    ) {
        return _sendEth(amount_);
    }

    function _sendEth(
        uint256 amount_
    ) private returns (
        bool success
    ) {
        if (address(this).balance < amount_) return false;
        uint256 amount = amount_.div(2);
        conf.vaultAddress.transfer(amount);
        emit SentEth(conf.vaultAddress, amount);
        conf.devAddress.transfer(amount_.sub(amount));
        emit SentEth(conf.devAddress, amount_.sub(amount));
        return true;
    }

    function getContractConf() external view returns(
        ContractConf memory
    ) {
        return conf;
    }

    function withdrawTokenERC20(
        address token_,
        uint256 amount_
    ) external onlyOwner returns(bool success) {
        IERC20 token = IERC20(token_);
        token.approve(address(this), amount_);
        success = token.transferFrom(address(this), owner(), amount_);
        emit WithdrawnERC20(address(this), amount_, token_);
        return success;
    }

    /**
     * @dev this function can only be called by the owner, this contract or a game contract
     * 
     * If you are a player and you want to deposit, you need to call the deposit function from the game contract
     * or initiate the deposit from the dapp
     * 
     * @param player_ players wallet address
     * @param account_ deposit wallet address
     * @param amount_ amount to deposit
     * @return (balance wallet, balance deposit wallet)
     * @notice players deposit wallet is stored on first deposit
     */
    function deposit(
        address player_,
        address account_,
        uint256 amount_
    ) external nonReentrant returns(
        uint256, uint256
    ) {
        if (player_ == address(0) || account_ == address(0)) {
            revert ZeroAddress();
        }
        if (amount_ == 0) {
            revert ZeroAmount();
        }
        if (amount_ > _balances[player_]) {
            revert BalanceToLow(player_, _balances[player_], amount_);
        }
        if (_msgSender() != owner() && _msgSender() != address(this)) {
            if (!gamesList.contains(address(_msgSender()))) {
                revert NotAuthorized();
            }
        }
        if (_players[player_] == address(0)) {
            _players[player_] = account_;
        }
        // no fee on transfer to deposit wallet
        _transferBalance(player_, account_, amount_, false);
        total.deposited = total.deposited.add(amount_);
        emit Deposited(player_, amount_);
        return (_balances[player_], _balances[account_]);
    }

    /*****
     * @dev withdraw from deposit wallet can be initiated by owner, this contract or game contract
     * 
     * If you are a player and you want to withdraw, you need to call the deposit function from the game contract
     * or initiate the withdraw from the dapp
     * 
     * @param player_ address of players wallet
     * @param account_ address of deposit wallet
     * @param amount_ amount to withdraw
     * @return (balance wallet, balance deposit wallet)
     * @notice account_ must be the deposit wallet of player_
     * @notice amount_ must be less or equal to balance of deposit wallet
     * @notice transfer: deposit wallet ---> player wallet
     * @notice withdraw to non player wallet is not possible
     */
    function withdraw(
        address player_, 
        address account_,
        uint256 amount_
    ) external nonReentrant returns(
        uint256, uint256
    ) {
        if (player_ == address(0) || account_ == address(0)) {
            revert ZeroAddress();
        }
        if (_players[player_] != account_) {
            revert NotPlayersAccount();
        }
        if (amount_ == 0) {
            revert ZeroAmount();
        }
        if (amount_ > _balances[account_]) {
            revert DepositBalanceToLow(player_, _balances[account_], amount_);
        }
        if (_msgSender() != owner() 
            && _msgSender() != address(this)
            && !gamesList.contains(address(_msgSender()))) {
            revert NotAGameAddress();
        }
        // no fee on withdraw from ingame deposit wallet
        _transferBalance(account_, player_, amount_, false);
        total.withdrawn = total.withdrawn.add(amount_);
        emit Withdrawn(player_, amount_);
        return (_balances[player_], _balances[account_]);
    }

    function addGame(
        address address_,
        string memory name_
    ) external onlyOwner returns(
        bool
    ) {
        if (gamesList.contains(address_)) {
            revert Exists(true);
        }
        ExcludedFromFee.add(address_);
        gamesList.add(address_);
        games[address_] = Games({
            id: gamesList.length(),
            game: address_,
            name: name_,
            active: false
        });
        return true;
    }

    function removeGame(
        address address_
    ) external onlyOwner returns (bool success) {
        success = false;
        if (gamesList.contains(address_)) {
            success = gamesList.remove(address_);
            ExcludedFromFee.remove(address_);
        } else {
            revert NotAGameAddress();
        }
        delete games[address_];
        return success;
    }

    function toggleGame(
        address address_
    ) external onlyOwner returns(
        bool
    ) {
        if (!gamesList.contains(address_)) {
            revert Exists(false);
        }
        games[address_].active = !games[address_].active;
        return games[address_].active;
    }

    function setGame(
        uint256 id_,
        address address_,
        string memory name_,
        bool active_
    ) external onlyOwner returns(
        bool
    ) {
        if (id_ > gamesList.length()) {
            revert InvalidGameId();
        }
        if (!gamesList.contains(address_)) {
            revert Exists(false);
        }
        games[address_] = Games({
            id: id_,
            game: address_,
            name: name_,
            active: active_
        });
        return true;
    }

    function getGame(address address_) external view returns (Games memory) {
        return games[address_];
    }

    function getGamesList() external view returns(
        address[] memory
    ) {
        return EnumerableSet.values(gamesList);
    }

    function transferToMain(address from_) external {
        if (!gamesList.contains(_msgSender())) {
            revert NotAuthorized();
        }
        _transferBalance(from_, address(this), _balances[from_], true);
    }

    function getPlayerBalances(
        address player_, 
        address account_
    ) external view returns (
        uint256, 
        uint256
    ) {
        return (_balances[player_], _balances[account_]);
    }

    function getTotals() external view returns(
        Total memory
    ) {
        return total;
    }

    function getMaxBuyAmount() external view returns (
        uint256
    ) {
        return conf.maxBuyAmount;
    }

    function maxBuyAmountEnabled() external view returns (
        bool
    ) {
        return conf.maxBuyAmount > 0;
    }

    function getMaxWalletAmount() external view returns (
        uint256
    ) {
        return conf.maxWalletAmount;
    }

    function maxWalletAmountEnabled() external view returns (
        bool
    ) {
        return conf.maxWalletAmount > 0;
    }
}