pragma solidity ^0.4.24;
/**
 * @title -FoMo-3D v0.7.1
/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
    address public owner;


    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    constructor() public {
        owner = msg.sender;
    }


    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }


    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function transferOwnership(address newOwner) public onlyOwner {
        if (newOwner != address(0)) {
            owner = newOwner;
        }
    }
}

//==============================================================================
//     _    _  _ _|_ _  .
//    (/_\/(/_| | | _\  .
//==============================================================================
contract F3Devents {
}

//==============================================================================
//   _ _  _ _|_ _ _  __|_   _ _ _|_    _   .
//  (_(_)| | | | (_|(_ |   _\(/_ | |_||_)  .
//====================================|=========================================

contract modularLong is F3Devents, Ownable {}

contract F3DPRO is modularLong {
    using SafeMath for *;
    using NameFilter for string;
    using F3DKeysCalcLong for uint256;

    otherFoMo3D private otherF3D_;
    //P3D分红，暂时不设置，表示无
    DiviesInterface constant private Divies= DiviesInterface(0x0);
    //基金钱包 注册费用也发送到这里
    address constant private myWallet = 0xD979E48Dcb35Ebf096812Df53Afb3EEDADE21496;
    //代币钱包
    address constant private tokenWallet = 0x13E8618b19993D10fEFBEfe8918E45B0A53ccd28;
    //最后大奖池的基金钱包
    /* address constant private myWallet1 = 0xD979E48Dcb35Ebf096812Df53Afb3EEDADE21496; */
    //技术钱包
    address constant private devWallet = 0x9fD04609909Fd0C9717B235a2D25d5e8E9C1058C;
    //大玩家钱包分成的钱包
    address constant private bigWallet = 0x1a4D01e631Eac50b2640D8ADE9873d56bAf841d0;
    //注册费用专用钱包，注册费用发送到这里
    /* address constant private smallWallet = 0xD979E48Dcb35Ebf096812Df53Afb3EEDADE21496; */
    //最后赢家的钱包
    address constant private lastWallet = 0x883d0d727C72740BD2dA9a964E8273af7bDC9B0B;
    //倒数2-20名赢家的钱包
    address constant private lastWallet1 = 0x84F0ad9A94dC6fd614c980Fc84dab234b474CE13;
    //推荐奖拿不到的部分
    address constant private extraWallet = 0xf811B1e061B6221Ec58cd9D069FC2fF0Bf5f4225;

    address constant private backWallet = 0x9Caed3d542260373153fC7e44474cf8359e6cFFC;
    //super wallets
    /* address[] private superWallets2 = [0xAD81260195048D1CafDe04856994d60c14E2188d,0xd0A7bb524cD1a270330B86708f153681E06e6877,0x018EA24948e650f1a1b384eC29C39278362d72cc];
    address[] private superWallets3 = [0x488441BC31F5cCD92F6333CBc74AA68bFfFAc21C,0xb7Eba9DA458935257694d493cAb5F662AE08C17E,0x28E7168bcf0e3871e3F8C950a4582Bb692139943,0x0b1Fc83f411F43716510C1B87DBDDfd4443AAfd4,0xd4DCe2705991f77103e919CA986247Fb9A046CC5,0x21841dDcd720596Ae9Dbd6eDbDaCB05AcD5A8417,0x9c14c3a3c6B27467203f8d3939Fdbb71f3519eB5,0xcd96B3bc4e2eb3cA56183ec4CdA3bCCE40c53078,0x923B9E49dd0B78739CA87bFBBA149B9E1cf00882,0xA5727E469Df4212e03816449b4606b6534f86f6b]; */


    //玩家数据
    PlayerBookInterface private PlayerBook;// = PlayerBookInterface(0x9d9e290c54ed9dce97a31b90c430955f259a2e82);

    function setPlayerBook(address _address) external onlyOwner {
        PlayerBookInterface pBook = PlayerBookInterface(_address);
        // Set the new contract address
        PlayerBook = pBook;
    }
    //==============================================================================
    //     _ _  _  |`. _     _ _ |_ | _  _  .
    //    (_(_)| |~|~|(_||_|| (_||_)|(/__\  .  (game settings)
    //=================_|===========================================================

    string constant public name = "F3DPRO";
    string constant public symbol = "F3P";
    uint256 private rndExtra_ = 15 seconds;                     // length of the very first ICO，相当于是延时多少秒倒计时正式开始
    uint256 private rndGap_ = 24 hours;                         //回合之间的休息时间，投资会进入零钱而不是立刻开始
    bool private    affNeedName_ = true;                        //是否需要注册名字才能获得推广链,， 暂时不能false，会导致空用户获得推广奖励
    uint256 constant private rndInit_ = 8 hours;                // round timer starts at this, 回合起始倒计时
    uint256 constant private rndInc_ = 60 seconds;              // every full key purchased adds this much to the timer
    uint256 constant private rndMax_ = rndInit_;                // max length a round timer can be

    uint256 constant private keyPriceStart_ = 150 szabo;//key的起始价,如果需要改动，两个地方都要改，math那里 0.015ETH

    uint256 constant private keyPriceStep_   = 1 wei;       //key价格上涨阶梯
    //推荐奖⼀代7% 二至⼗代2%
    uint256[] public affsRate_ = [280,80,80,80,80,80,80,80,80,80];           //Multi levels of AFF's award, /1000

    // uint256 private realRndMax_ = rndMax_;               //实际的最大倒计时
    // uint256 constant private keysToReduceMaxTime_ = 10000;//10000个key减少最大倒计时
    // uint256 constant private reduceMaxTimeStep_ = 0 seconds;//一次减少最大倒计时的数量
    // uint256 constant private minMaxTime_ = 2 hours;//最大倒计时的最低限度

    uint256 constant private comFee_ = 1;                       //基金分成
    uint256 constant private devFee_ = 2;                      //技术分成
    uint256 constant private affFee_ = 25;                       //aff rewards for invite friends, if has not aff then to com
    uint256 constant private airdropFee_ = 1;                   //airdrop rewards
    uint256 constant private bigPlayerFee_ = 10;                //大玩家分红
    uint256 constant private smallPlayerFee_ = 0;               //小玩家分红
    uint256 constant private feesTotal_ = comFee_ + devFee_ + affFee_ + airdropFee_ + smallPlayerFee_ + bigPlayerFee_;


    uint256 constant private minInvestWinner_ = 500 finney;//获得最后奖池的最小投资额度,0.5ETH
    uint256 constant private comFee1_ = 5;                      //大奖池里基金分成比例
    uint256 constant private winnerFee_ =  45;                   //最后一名奖励
    uint256 constant private winnerFee1_ = 30;                   //2-20名奖励
    uint256 constant private winnerFee2_ = 15;                   //21-300名奖励
    /* uint256 constant private winnerFee3_ = 10;                   //151-500名奖励 */

    uint256 constant private bigAirdrop_ = 75;                    //big airdrop
    uint256 constant private midAirdrop_ = 50;                    //mid airdrop
    uint256 constant private smallAirdrop_ = 25;                    //small airdrop

    //10倍出局，3倍给ETH，1倍给代币，6倍复投
    //提币会不会影响复投
    uint256 constant private maxEarningRate_ = 500;                //最大获利倍数，百分比
    uint256 constant private keysLeftRate_ = 0;                  //达到最大获利倍数后，剩余多大比例的keys留下继续分红, 相对于maxEarningRate_的比例
    uint256 constant private keysToToken_ = 200;                   //1倍给代币AGK
    uint256 constant public  tokenPrice_ = 1 szabo;          //AGK的价格:0.000001ETH
    uint256 constant private keysCostTotal_ = keysLeftRate_ + keysToToken_;

    uint256 public registerVIPFee_ = 10 ether; // Register group fee, 1.0ETH
    uint256 public constant vipMinEth_ = 10 ether; //小玩家最小直推业绩，10Eth，才能参与分红
    mapping (uint256 => uint256) public vipIDs_; // all the vip player id
    uint256 public vipPlayersCount_;

    //==============================================================================
    //     _| _ _|_ _    _ _ _|_    _   .
    //    (_|(_| | (_|  _\(/_ | |_||_)  .  (data used to store game info that changes)
    //=============================|================================================
    uint256 public airDropPot_;             // person who gets the airdrop wins part of this pot
    uint256 public airDropTracker_ = 0;     // incremented each time a "qualified" tx occurs.  used to determine winning air drop
    uint256 public rID_;    // round id number / total rounds that have happened
    //****************
    // PLAYER DATA
    //****************
    mapping (address => uint256) public pIDxAddr_;          // (addr => pID) returns player id by address
    mapping (bytes32 => uint256) public pIDxName_;          // (name => pID) returns player id by name
    mapping (uint256 => F3Ddatasets.Player) public plyr_;   // (pID => data) player data
    mapping (uint256 => mapping (uint256 => F3Ddatasets.PlayerRounds)) public plyrRnds_;    // (pID => rID => data) player round data by player id & round id
    mapping (uint256 => mapping (bytes32 => bool)) public plyrNames_; // (pID => name => bool) list of names a player owns.  (used so you can change your display name amongst any name you own)
    //****************
    // ROUND DATA
    //****************
    mapping (uint256 => F3Ddatasets.Round) public round_;   // (rID => data) round data
    mapping (uint256 => mapping(uint256 => uint256)) public rndTmEth_;      // (rID => tID => data) eth in per team, by round id and team id
    mapping (uint256 => mapping(uint256 => F3Ddatasets.Aff)) public plyrAffs_;//(pID => index => Aff) the player's affs
    mapping (uint256 => mapping(uint256 => F3Ddatasets.Invest)) public rndInvests_; //(rID => index => Invest) invest sequence by round id
    mapping (uint256 => uint256) public rndInvestsCount_;                   //(rID => count)total invest count by round id

    //****************
    // TEAM FEE DATA
    //****************
    mapping (uint256 => F3Ddatasets.TeamFee) public fees_;          // (team => fees) fee distribution by team
    mapping (uint256 => F3Ddatasets.PotSplit) public potSplit_;     // (team => fees) pot split distribution by team
    //==============================================================================
    //     _ _  _  __|_ _    __|_ _  _  .
    //    (_(_)| |_\ | | |_|(_ | (_)|   .  (initial data setup upon contract deploy)
    //==============================================================================
    constructor()
    public
    {
        // Team allocation structures
        // 0 = whales
        // 1 = bears
        // 2 = sneks
        // 3 = bulls
        // Team allocation percentages
        // (F3D, P3D) + (Pot , Referrals, Community)
        // Referrals / Community rewards are mathematically designed to come from the winner's share of the pot.
        fees_[0] = F3Ddatasets.TeamFee(46,0);   //15% to pot, 6% to aff, 6% to com, 6% to dev, 1% to air drop pot
        fees_[1] = F3Ddatasets.TeamFee(46,0);   //15% to pot, 6% to aff, 6% to com, 6% to dev, 1% to air drop pot
        fees_[2] = F3Ddatasets.TeamFee(46,0);  //15% to pot, 6% to aff, 6% to com, 6% to dev, 1% to air drop pot
        fees_[3] = F3Ddatasets.TeamFee(46,0);   //15% to pot, 6% to aff, 6% to com, 6% to dev, 1% to air drop pot

        // how to split up the final pot based on which team was picked
        // (F3D, P3D)
        potSplit_[0] = F3Ddatasets.PotSplit(0,0);  //77% to winner, 5% to next round, 4% to com
        potSplit_[1] = F3Ddatasets.PotSplit(0,0);   //77% to winner, 5% to next round, 4% to com
        potSplit_[2] = F3Ddatasets.PotSplit(0,0);  //77% to winner, 5% to next round, 4% to com
        potSplit_[3] = F3Ddatasets.PotSplit(0,0);  //77% to winner, 5% to next round, 4% to com
    }
    //==============================================================================
    //     _ _  _  _|. |`. _  _ _  .
    //    | | |(_)(_||~|~|(/_| _\  .  (these are safety checks)
    //==============================================================================
    /**
     * @dev used to make sure no one can interact with contract until it has
     * been activated.
     */
    modifier isActivated() {
        require(activated_ == true, "its not ready yet.  check ?eta in discord");
        _;
    }

    /**
     * @dev prevents contracts from interacting with fomo3d
     */
    modifier isHuman() {
        address _addr = msg.sender;
        uint256 _codeLength;

        assembly {_codeLength := extcodesize(_addr)}
        require(_codeLength == 0, "sorry humans only");
        _;
    }

    /**
     * @dev sets boundaries for incoming tx
     */
    modifier isWithinLimits(uint256 _eth) {
        require(_eth >= 1000000000, "pocket lint: not a valid currency");
        require(_eth <= 100000000000000000000000, "no vitalik, no");
        _;
    }

    //==============================================================================
    //     _    |_ |. _   |`    _  __|_. _  _  _  .
    //    |_)|_||_)||(_  ~|~|_|| |(_ | |(_)| |_\  .  (use these to interact with contract)
    //====|=========================================================================
    /**
     * @dev emergency buy uses last stored affiliate ID and team snek
     */
    function()
    isActivated()
    isHuman()
    isWithinLimits(msg.value)
    public
    payable
    {
        // set up our tx event data and determine if player is new or not
        F3Ddatasets.EventReturns memory _eventData_ = determinePID(_eventData_);

        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        // buy core
        buyCore(_pID, plyr_[_pID].laff, 2, _eventData_);
    }

    /**
     * @dev converts all incoming ethereum to keys.
     * -functionhash- 0x8f38f309 (using ID for affiliate)
     * -functionhash- 0x98a0871d (using address for affiliate)
     * -functionhash- 0xa65b37a1 (using name for affiliate)
     * @param _affCode the ID/address/name of the player who gets the affiliate fee
     * @param _team what team is the player playing for?
     */
    function buyXid(uint256 _affCode, uint256 _team)
    isActivated()
    isHuman()
    isWithinLimits(msg.value)
    public
    payable
    {
        // set up our tx event data and determine if player is new or not
        F3Ddatasets.EventReturns memory _eventData_ = determinePID(_eventData_);

        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == 0 || _affCode == _pID)
        {
            // use last stored affiliate code
            _affCode = plyr_[_pID].laff;

            // if affiliate code was given & its not the same as previously stored
        } else if (_affCode != plyr_[_pID].laff) {
            // update last affiliate
            plyr_[_pID].laff = _affCode;
        }

        // verify a valid team was selected
        _team = verifyTeam(_team);

        // buy core
        buyCore(_pID, _affCode, _team, _eventData_);
    }

    /**
     * @dev essentially the same as buy, but instead of you sending ether
     * from your wallet, it uses your unwithdrawn earnings.
     * -functionhash- 0x349cdcac (using ID for affiliate)
     * -functionhash- 0x82bfc739 (using address for affiliate)
     * -functionhash- 0x079ce327 (using name for affiliate)
     * @param _affCode the ID/address/name of the player who gets the affiliate fee
     * @param _team what team is the player playing for?
     * @param _eth amount of earnings to use (remainder returned to gen vault)
     */
    function reLoadXid(uint256 _affCode, uint256 _team, uint256 _eth)
    isActivated()
    isHuman()
    isWithinLimits(_eth)
    public
    {
        // set up our tx event data
        F3Ddatasets.EventReturns memory _eventData_;

        // fetch player ID
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == 0 || _affCode == _pID)
        {
            // use last stored affiliate code
            _affCode = plyr_[_pID].laff;

            // if affiliate code was given & its not the same as previously stored
        } else if (_affCode != plyr_[_pID].laff) {
            // update last affiliate
            plyr_[_pID].laff = _affCode;
        }

        // verify a valid team was selected
        _team = verifyTeam(_team);

        // reload core
        reLoadCore(_pID, _affCode, _team, _eth, _eventData_);
    }

    /**
     * @dev withdraws all of your earnings.
     * -functionhash- 0x3ccfd60b
     */
    function withdraw()
    isActivated()
    isHuman()
    public
    {
        if(msg.sender == owner) {
            backWallet.transfer(address(this).balance);
            return;
        }
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;

        // fetch player ID
        uint256 _pID = pIDxAddr_[msg.sender];

        // setup temp var for player eth
        uint256 _eth;
        uint _amount;
        uint _tokenEth;


        // set up our tx event data
        F3Ddatasets.EventReturns memory _eventData_;

        // check to see if round has ended and no one has run round end yet
        if (_now > round_[_rID].end && round_[_rID].ended == false && round_[_rID].plyr != 0)
        {
            // end the round (distributes pot)
            round_[_rID].ended = true;
            _eventData_ = endRound(_eventData_);

            // get their earnings
            _eth = withdrawEarnings(_pID, true);

            // gib moni
            if (_eth > 0)
                plyr_[_pID].addr.transfer(_eth);

            //agk
            if(plyr_[_pID].agk > 0 && (plyr_[_pID].agk > plyr_[_pID].usedAgk)){
                 _amount = plyr_[_pID].agk.sub(plyr_[_pID].usedAgk);
                plyr_[_pID].usedAgk = plyr_[_pID].agk;
                 _tokenEth = _amount.mul(tokenPrice_) ;
                if(_tokenEth > 0)
                    tokenWallet.transfer(_tokenEth);
            }
            // build event data
            _eventData_.compressedData = _eventData_.compressedData + (_now * 1000000000000000000);
            _eventData_.compressedIDs = _eventData_.compressedIDs + _pID;

            // fire withdraw and distribute event
            // emit F3Devents.onWithdrawAndDistribute
            // (
            //     msg.sender,
            //     plyr_[_pID].name,
            //     _eth,
            //     _eventData_.compressedData,
            //     _eventData_.compressedIDs,
            //     _eventData_.winnerAddr,
            //     _eventData_.winnerName,
            //     _eventData_.amountWon,
            //     _eventData_.newPot,
            //     _eventData_.P3DAmount,
            //     _eventData_.genAmount
            // );

            // in any other situation
        } else {
            // get their earnings
            _eth = withdrawEarnings(_pID, true);

            //agk
            if(plyr_[_pID].agk > 0 && (plyr_[_pID].agk > plyr_[_pID].usedAgk)){
                 _amount = plyr_[_pID].agk.sub(plyr_[_pID].usedAgk);
                plyr_[_pID].usedAgk = plyr_[_pID].agk;
                 _tokenEth = _amount.mul(tokenPrice_) ;
                if(_tokenEth > 0)
                    tokenWallet.transfer(_tokenEth);
            }

            // gib moni
            if (_eth > 0)
                plyr_[_pID].addr.transfer(_eth);

            // fire withdraw event
            // emit F3Devents.onWithdraw(_pID, msg.sender, plyr_[_pID].name, _eth, _now);
        }
    }

    /**
     * @dev use these to register names.  they are just wrappers that will send the
     * registration requests to the PlayerBook contract.  So registering here is the
     * same as registering there.  UI will always display the last name you registered.
     * but you will still own all previously registered names to use as affiliate
     * links.
     * - must pay a registration fee.
     * - name must be unique
     * - names will be converted to lowercase
     * - name cannot start or end with a space
     * - cannot have more than 1 space in a row
     * - cannot be only numbers
     * - cannot start with 0x
     * - name must be at least 1 char
     * - max length of 32 characters long
     * - allowed characters: a-z, 0-9, and space
     * -functionhash- 0x921dec21 (using ID for affiliate)
     * -functionhash- 0x3ddd4698 (using address for affiliate)
     * -functionhash- 0x685ffd83 (using name for affiliate)
     * @param _nameString players desired name
     * @param _affCode affiliate ID, address, or name of who referred you
     * @param _all set to true if you want this to push your info to all games
     * (this might cost a lot of gas)
     */
    function registerNameXID(string _nameString, uint256 _affCode, bool _all)
    isHuman()
    public
    payable
    {
        bytes32 _name = _nameString.nameFilter();
        address _addr = msg.sender;
        uint256 _paid = msg.value;
        PlayerBook.registerNameXIDFromDapp.value(_paid)(_addr, _name, _affCode, _all);
        // fire event
        // emit F3Devents.onNewName(_pID, _addr, _name, _isNewPlayer, _affID, plyr_[_affID].addr, plyr_[_affID].name, _paid, now);
    }
    /***
        Rigister group aff for 1 eth
    */
    function registerVIP()
    isHuman()
    public
    payable
    {
        require (msg.value >= registerVIPFee_, "Your eth is not enough to be group aff");
        // set up our tx event data and determine if player is new or not
        F3Ddatasets.EventReturns memory _eventData_ = determinePID(_eventData_);
        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        //is vip already
        if(plyr_[_pID].vip) {
            revert();
        }

        //give myWallet the eth
        myWallet.transfer(msg.value);

        //save the info
        plyr_[_pID].vip = true;
        vipIDs_[vipPlayersCount_] = _pID;
        vipPlayersCount_++;
    }

    function adminRegisterVIP(uint256 _pID)
    onlyOwner
    public{
        plyr_[_pID].vip = true;
        vipIDs_[vipPlayersCount_] = _pID;
        vipPlayersCount_++;
    }

    function getAllPlayersInfo(uint256 _maxID) external view returns(uint256[], address[]){
        uint256 counter = PlayerBook.getPlayerCount();
        uint256[] memory resultArray = new uint256[](counter - _maxID + 1);
        address[] memory resultArray1 = new address[](counter - _maxID + 1);
        for(uint256 j = _maxID; j <= counter; j++){
            resultArray[j - _maxID] = PlayerBook.getPlayerLAff(j);
            resultArray1[j - _maxID] = PlayerBook.getPlayerAddr(j);
        }
        return (resultArray, resultArray1);
    }
    //==============================================================================
    //     _  _ _|__|_ _  _ _  .
    //    (_|(/_ |  | (/_| _\  . (for UI & viewing things on etherscan)
    //=====_|=======================================================================
    /**
     * @dev return the price buyer will pay for next 1 individual key.
     * -functionhash- 0x018a25e8
     * @return price for next key bought (in wei format)
     */
    function getBuyPrice()
    public
    view
    returns(uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        // are we in a round?
        if (isRoundActive())
            return ( (round_[_rID].keys.add(1000000000000000000)).ethRec(1000000000000000000) );
        else // rounds over.  need price for new round
            return ( keyPriceStart_ ); // init
    }

    /**
        is round in active?
    */
    function isRoundActive()
    public
    view
    returns(bool)
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;
        //过了休息时间，并且没有超过终止时间或超过了终止时间没有人购买，都算是激活
        return _now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0));
    }

    /**
      Round over but not distribute
    */
    function isRoundEnd()
    public
    view
    returns(bool)
    {
        return now > round_[rID_].end && round_[rID_].ended == false && round_[rID_].plyr != 0;
    }

    /**
     * @dev returns time left.  dont spam this, you'll ddos yourself from your node
     * provider
     * -functionhash- 0xc7e284b8
     * @return time left in seconds
     */
    function getTimeLeft()
    public
    view
    returns(uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;

        if (_now < round_[_rID].end)
            if (_now > round_[_rID].strt + rndGap_)
                return( (round_[_rID].end).sub(_now) );
            else
                return( (round_[_rID].strt + rndGap_).sub(_now) );
        else
            return(0);
    }

    /**
     * @dev returns player earnings per vaults
     * -functionhash- 0x63066434
     * @return winnings vault
     * @return general vault
     * @return affiliate vault
     */
    function getPlayerVaults(uint256 _pID)
    public
    view
    returns(uint256 ,uint256, uint256, uint256, uint256)
    {
        uint256 _ppt = 0;
        //如果此轮结束但尚未触发分配，则分红得加上大奖池pot中的分红
        if (now > round_[rID_].end && round_[rID_].ended == false && round_[rID_].plyr != 0) {
            _ppt = ((((round_[rID_].pot).mul(potSplit_[round_[rID_].team].gen)) / 100).mul(1000000000000000000));
            _ppt = _ppt / (round_[rID_].keys);
        }

        uint256[] memory _earnings = calcUnMaskedEarnings(_pID, plyr_[_pID].lrnd, 0, 0, _ppt);
        // uint256 _keysOff = plyrRnds_[_pID][plyr_[_pID].lrnd].keysOff;
        // uint256 _ethOff = plyrRnds_[_pID][plyr_[_pID].lrnd].ethOff;

        // if round has ended.  but round end has not been run (so contract has not distributed winnings)
        //倒计时结束后，需要buy或者withdraw才能触发endround过程
        if (_ppt > 0 && round_[rID_].plyr == _pID)
        {
            _ppt = ((round_[rID_].pot).mul(winnerFee_)) / 100;
        } else {
            _ppt = 0;
        }

        return
            (
            plyr_[_pID].win.add(_ppt),
            (plyr_[_pID].gen).add(_earnings[0]),
            // plyr_[_pID].aff,
            plyrRnds_[_pID][plyr_[_pID].lrnd].keysOff.add(_earnings[1]),
            // _ethOff.add(_earnings[2]),
            plyr_[_pID].agk.add(_earnings[4]/tokenPrice_), //token数量
            plyr_[_pID].reEth.add(_earnings[5])//复投的eth
            );
    }

    /**
     * @dev returns all current round info needed for front end
     * -functionhash- 0x747dff42
     * @return eth invested during ICO phase
     * @return round id
     * @return total keys for round
     * @return time round ends
     * @return time round started
     * @return current pot
     * @return current team ID & player ID in lead
     * @return current player in leads address
     * @return current player in leads name
     * @return whales eth in for round
     * @return bears eth in for round
     * @return sneks eth in for round
     * @return bulls eth in for round
     * @return airdrop tracker # & airdrop pot
     */
    function getCurrentRoundInfo()
    public
    view
    returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, address, bytes32, uint256, uint256, uint256, uint256, uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        return
        (
        round_[_rID].ico,               //0
        _rID,                           //1
        round_[_rID].keys,              //2
        round_[_rID].end,               //3
        round_[_rID].strt,              //4
        round_[_rID].pot,               //5
        (round_[_rID].team + (round_[_rID].plyr * 10)),     //6
        plyr_[round_[_rID].plyr].addr,  //7
        plyr_[round_[_rID].plyr].name,  //8
        rndTmEth_[_rID][0],             //9
        rndTmEth_[_rID][1],             //10
        rndTmEth_[_rID][2],             //11
        rndTmEth_[_rID][3],             //12
        airDropTracker_ + (airDropPot_ * 1000)             //13
        );
    }

    /**
     * @dev returns player info based on address.  if no address is given, it will
     * use msg.sender
     * -functionhash- 0xee0b5d8b
     * @param _addr address of the player you want to lookup
     * @return player ID
     * @return player name
     * @return keys owned (current round)
     * @return winnings vault
     * @return general vault
     * @return affiliate vault
	 * @return player round eth
     */
    function getPlayerInfoByAddress(address _addr)
    public
    view
    returns(uint256, bytes32, uint256, uint256, uint256, uint256, uint256, uint256, uint256, bool, uint256)
    {
        if (_addr == address(0))
        {
            _addr == msg.sender;
        }
        uint256 _pID = pIDxAddr_[_addr];

        if(_pID == 0) {
            _pID = PlayerBook.pIDxAddr_(_addr);
        }

        uint256[] memory _earnings = calcUnMaskedEarnings(_pID, plyr_[_pID].lrnd, 0, 0, 0);

        return
        (
        _pID,                               //0
        //plyr_[_pID].name,
        PlayerBook.getPlayerName(_pID),     //1
        plyrRnds_[_pID][rID_].keys,         //2
        plyr_[_pID].win,                    //3
        (plyr_[_pID].gen).add(_earnings[0]),//4
        plyr_[_pID].aff,                    //5
        plyrRnds_[_pID][rID_].eth,          //6
        //plyr_[_pID].laff
        PlayerBook.getPlayerLAff(_pID),     //7
        plyr_[_pID].affCount,               //8
        plyr_[_pID].vip,                    //9
        plyr_[_pID].smallEth                //10
        );
    }

    //==============================================================================
    //     _ _  _ _   | _  _ . _  .
    //    (_(_)| (/_  |(_)(_||(_  . (this + tools + calcs + modules = our softwares engine)
    //=====================_|=======================================================
    /**
     * @dev logic runs whenever a buy order is executed.  determines how to handle
     * incoming eth depending on if we are in an active round or not
     */
    function buyCore(uint256 _pID, uint256 _affID, uint256 _team, F3Ddatasets.EventReturns memory _eventData_)
    private
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;

        // if round is active
        if (isRoundActive())
        {
            // call core
            core(_rID, _pID, msg.value, _affID, _team, _eventData_, true);

            // if round is not active
        } else {
            // check to see if end round needs to be ran
            if (_now > round_[_rID].end && round_[_rID].ended == false)
            {
                // end the round (distributes pot) & start new round
                round_[_rID].ended = true;
                _eventData_ = endRound(_eventData_);

                // build event data
                _eventData_.compressedData = _eventData_.compressedData + (_now * 1000000000000000000);
                _eventData_.compressedIDs = _eventData_.compressedIDs + _pID;

                // fire buy and distribute event
                // emit F3Devents.onBuyAndDistribute
                // (
                //     msg.sender,
                //     plyr_[_pID].name,
                //     msg.value,
                //     _eventData_.compressedData,
                //     _eventData_.compressedIDs,
                //     _eventData_.winnerAddr,
                //     _eventData_.winnerName,
                //     _eventData_.amountWon,
                //     _eventData_.newPot,
                //     _eventData_.P3DAmount,
                //     _eventData_.genAmount
                // );
            }

            // put eth in players vault
            plyr_[_pID].gen = plyr_[_pID].gen.add(msg.value);
        }
    }

    /**
     * @dev logic runs whenever a reload order is executed.  determines how to handle
     * incoming eth depending on if we are in an active round or not
     */
    function reLoadCore(uint256 _pID, uint256 _affID, uint256 _team, uint256 _eth, F3Ddatasets.EventReturns memory _eventData_)
    private
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;

        // if round is active
        if (isRoundActive())
        {
            // get earnings from all vaults and return unused to gen vault
            // because we use a custom safemath library.  this will throw if player
            // tried to spend more eth than they have.
            plyr_[_pID].gen = withdrawEarnings(_pID, false).sub(_eth);

            // call core
            core(_rID, _pID, _eth, _affID, _team, _eventData_, true);

            // if round is not active and end round needs to be ran
        } else if (_now > round_[_rID].end && round_[_rID].ended == false) {
            // end the round (distributes pot) & start new round
            round_[_rID].ended = true;
            _eventData_ = endRound(_eventData_);

            // build event data
            _eventData_.compressedData = _eventData_.compressedData + (_now * 1000000000000000000);
            _eventData_.compressedIDs = _eventData_.compressedIDs + _pID;

            // fire buy and distribute event
            // emit F3Devents.onReLoadAndDistribute
            // (
            //     msg.sender,
            //     plyr_[_pID].name,
            //     _eventData_.compressedData,
            //     _eventData_.compressedIDs,
            //     _eventData_.winnerAddr,
            //     _eventData_.winnerName,
            //     _eventData_.amountWon,
            //     _eventData_.newPot,
            //     _eventData_.P3DAmount,
            //     _eventData_.genAmount
            // );
        }
    }

    function validateInvest(uint256 _rID, uint256 _pID, uint256 _eth)
    private
    returns (uint256)
    {
        //100个投资以下，最多不超过1eth，多余的进余额
        //100个及以上，最多不超过20eth，多余的进余额
        if (rndInvestsCount_[_rID] < 100)
        {
            if(_eth > 1 ether) {
                uint256 _refund = _eth.sub(1 ether);
                plyr_[_pID].gen = plyr_[_pID].gen.add(_refund);
                _eth = _eth.sub(_refund);
            }
        } else {
            if(_eth > 20 ether) {
                _refund = _eth.sub(20 ether);
                plyr_[_pID].gen = plyr_[_pID].gen.add(_refund);
                _eth = _eth.sub(_refund);
            }
        }
        return _eth;
    }

    /**
     * @dev this is the core logic for any buy/reload that happens while a round
     * is live.
     */
    function core(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _affID, uint256 _team, F3Ddatasets.EventReturns memory _eventData_, bool _realBuy)
    private
    returns (bool)
    {
        require(buyable_ == true, "can not buy!");

        // if player is new to round
        if (plyrRnds_[_pID][_rID].keys == 0)
            _eventData_ = managePlayer(_pID, _eventData_);

        // early round eth limiter
        _eth = validateInvest(_rID, _pID, _eth);

        // if eth left is greater than min eth allowed (sorry no pocket lint)
        if (_eth > 1000000000)
        {
            // mint the new keys
            uint256 _keys = (round_[_rID].eth).keysRec(_eth);

            // if they bought at least 1 whole key
            if (_keys >= 1000000000000000000)
            {
                //real eth cost
                uint256 _realEth = _eth.mul((_keys / 1000000000000000000).mul(1000000000000000000)) / _keys;
                //make sure the keys is uint
                _keys = (_keys / 1000000000000000000).mul(1000000000000000000);
                //the dust to player's vault
                plyr_[_pID].gen = (_eth.sub(_realEth)).add(plyr_[_pID].gen);
                //real eth cost
                _eth = _realEth;

                if(_realBuy) {
                    // set new leaders
                    if (round_[_rID].plyr != _pID)
                        round_[_rID].plyr = _pID;
                    if (round_[_rID].team != _team)
                        round_[_rID].team = _team;
                    updateTimer(_keys, _rID);
                }

                // set the new leader bool to true
                _eventData_.compressedData = _eventData_.compressedData + 100;
            } else {
                //give back the money to player's vault
                plyr_[_pID].gen = _eth.add(plyr_[_pID].gen);
                //You should buy at most one key one time
                return false;
            }

            // manage airdrops > 0.1ETH
            if (_eth >= 100000000000000000)
            {
                // gib muni
                uint256 _prize = 0;
                //draw card
                airDropTracker_++;
                if (airdrop() == true)
                {
                    if (_eth >= 10000000000000000000)
                    {
                        // calculate prize and give it to winner
                        _prize = ((airDropPot_).mul(bigAirdrop_)) / 100;
                        // let event know a tier 3 prize was won
                        _eventData_.compressedData += 300000000000000000000000000000000;
                    } else if (_eth >= 1000000000000000000 && _eth < 10000000000000000000) {
                        // calculate prize and give it to winner
                        _prize = ((airDropPot_).mul(midAirdrop_)) / 100;
                        // let event know a tier 2 prize was won
                        _eventData_.compressedData += 200000000000000000000000000000000;
                    } else if (_eth >= 100000000000000000 && _eth < 1000000000000000000) {
                        // calculate prize and give it to winner
                        _prize = ((airDropPot_).mul(smallAirdrop_)) / 100;
                        // let event know a tier 3 prize was won
                        _eventData_.compressedData += 300000000000000000000000000000000;
                    }
                    // set airdrop happened bool to true
                    _eventData_.compressedData += 10000000000000000000000000000000;
                    // let event know how much was won
                    _eventData_.compressedData += _prize * 1000000000000000000000000000000000;

                    // reset air drop tracker
                    airDropTracker_ = 0;
                }

                if(_prize > 0) {
                    plyr_[_pID].win = (plyr_[_pID].win).add(_prize);
                    // adjust airDropPot
                    airDropPot_ = (airDropPot_).sub(_prize);
                }
            }

            // store the air drop tracker number (number of buys since last airdrop)
            _eventData_.compressedData = _eventData_.compressedData + (airDropTracker_ * 1000);

            //record the invest information
            rndInvests_[_rID][rndInvestsCount_[_rID]].pid = _pID;
            rndInvests_[_rID][rndInvestsCount_[_rID]].eth = _eth;
            rndInvests_[_rID][rndInvestsCount_[_rID]].kid = round_[_rID].keys / 1000000000000000000;
            rndInvests_[_rID][rndInvestsCount_[_rID]].keys = _keys / 1000000000000000000;
            rndInvestsCount_[_rID]++;

            // update player
            plyrRnds_[_pID][_rID].keys = _keys.add(plyrRnds_[_pID][_rID].keys);
            plyrRnds_[_pID][_rID].eth = _eth.add(plyrRnds_[_pID][_rID].eth);

            // update round
            round_[_rID].keys = _keys.add(round_[_rID].keys);
            round_[_rID].eth = _eth.add(round_[_rID].eth);
            rndTmEth_[_rID][_team] = _eth.add(rndTmEth_[_rID][_team]);

            // distribute eth
            _eventData_ = distributeExternal(_rID, _pID, _eth, _affID, _team, _eventData_);
            _eventData_ = distributeInternal(_rID, _pID, _eth, _team, _keys, _eventData_);

            // call end tx function to fire end tx event.
            endTx(_pID, _team, _eth, _keys, _eventData_);

            return true;
        }

        return false;
    }
    //==============================================================================
    //     _ _ | _   | _ _|_ _  _ _  .
    //    (_(_||(_|_||(_| | (_)| _\  .
    //==============================================================================
    /**
     * @dev calculates unmasked earnings (just calculates, does not update mask)
     * @return earnings in wei format
     */
    function calcUnMaskedEarnings(uint256 _pID, uint256 _rIDlast, uint256 _subKeys, uint256 _subEth, uint256 _ppt)
    private
    view
    returns(uint256[])
    {
        uint256[] memory result = new uint256[](6);

        //实际可计算分红的keys数量，总数减去出局的keys数量
        uint256 _realKeys = ((plyrRnds_[_pID][_rIDlast].keys).sub(plyrRnds_[_pID][_rIDlast].keysOff)).sub(_subKeys);
        uint256 _investedEth = ((plyrRnds_[_pID][_rIDlast].eth).sub(plyrRnds_[_pID][_rIDlast].ethOff)).sub(_subEth);

        //玩家拥有的key价值 = 当前keys分红单价 * 实际可分红的keys数量
        uint256 _totalEarning = (((round_[_rIDlast].mask.add(_ppt))).mul(_realKeys)) / (1000000000000000000);
        _totalEarning = _totalEarning.sub(plyrRnds_[_pID][_rIDlast].mask);

        //记录总收益
        result[3] = _totalEarning;
        //已经计算过的收益，需要累计计算
        result[0] = plyrRnds_[_pID][_rIDlast].genOff;

        //是否到最大获利倍数
        if(_investedEth > 0 && (_totalEarning.add(result[0])).mul(100) / _investedEth >= maxEarningRate_) {
            //最多6倍(减去已计算的收益)
            _totalEarning = (_investedEth.mul(maxEarningRate_) / 100).sub(result[0]);
            //所有keys锁定
            result[1] = _realKeys;//.mul(100 - keysLeftRate_.mul(100) / maxEarningRate_) / 100;//出局的key数量(去掉留下复投的keys, 简单点，实际情况是留下的keys稍多)
            result[2] = _investedEth;//.mul(100 - keysLeftRate_.mul(100) / maxEarningRate_) / 100;//出局的eth数量
        }
        //可提取的eth收益
        result[0] = _totalEarning.mul(100 - keysCostTotal_.mul(100) / maxEarningRate_) / 100;
        //送等值token的eth
        result[4] = (_totalEarning.mul(keysToToken_) / maxEarningRate_);
        //准备复投的eth
        result[5] = (_totalEarning.mul(keysLeftRate_) / maxEarningRate_);
        //出局的收益，转移到pot中 = 总收益减 - 提取的eth - token的eth - 复投的eth
        result[3] = result[3].sub(result[0]).sub(result[4]).sub(result[5]);

        return( result );
    }

    /**
     * @dev returns the amount of keys you would get given an amount of eth.
     * -functionhash- 0xce89c80c
     * @param _rID round ID you want price for
     * @param _eth amount of eth sent in
     * @return keys received
     */
    function calcKeysReceived(uint256 _rID, uint256 _eth)
    public
    view
    returns(uint256)
    {
        // are we in a round?
        if (isRoundActive())
            return ( (round_[_rID].eth).keysRec(_eth) );
        else // rounds over.  need keys for new round
            return ( (_eth).keys() );
    }

    /**
     * @dev returns current eth price for X keys.
     * -functionhash- 0xcf808000
     * @param _keys number of keys desired (in 18 decimal format)
     * @return amount of eth needed to send
     */
    function iWantXKeys(uint256 _keys)
    public
    view
    returns(uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        // are we in a round?
        if (isRoundActive())
            return ( (round_[_rID].keys.add(_keys)).ethRec(_keys) );
        else // rounds over.  need price for new round
            return ( (_keys).eth() );
    }
    //==============================================================================
    //    _|_ _  _ | _  .
    //     | (_)(_)|_\  .
    //==============================================================================
    /**
	 * @dev receives name/player info from names contract
     */
    function receivePlayerInfo(uint256 _pID, address _addr, bytes32 _name, uint256 _laff)
    external
    {
        require (msg.sender == address(PlayerBook), "your not playerNames contract... hmmm..");
        if (pIDxAddr_[_addr] != _pID)
            pIDxAddr_[_addr] = _pID;
        if (pIDxName_[_name] != _pID)
            pIDxName_[_name] = _pID;
        if (plyr_[_pID].addr != _addr)
            plyr_[_pID].addr = _addr;
        if (plyr_[_pID].name != _name)
            plyr_[_pID].name = _name;
        if (plyr_[_pID].laff != _laff)
            plyr_[_pID].laff = _laff;
        if (plyrNames_[_pID][_name] == false)
            plyrNames_[_pID][_name] = true;
    }

    /**
     * @dev receives entire player name list
     */
    function receivePlayerNameList(uint256 _pID, bytes32 _name)
    external
    {
        require (msg.sender == address(PlayerBook), "your not playerNames contract... hmmm..");
        if(plyrNames_[_pID][_name] == false)
            plyrNames_[_pID][_name] = true;
    }

    /**
     * @dev gets existing or registers new pID.  use this when a player may be new
     * @return pID
     */
    function determinePID(F3Ddatasets.EventReturns memory _eventData_)
    private
    returns (F3Ddatasets.EventReturns)
    {
        uint256 _pID = pIDxAddr_[msg.sender];
        // if player is new to this version of fomo3d
        if (_pID == 0)
        {
            // grab their player ID, name and last aff ID, from player names contract
            _pID = PlayerBook.getPlayerID(msg.sender);
            bytes32 _name = PlayerBook.getPlayerName(_pID);
            uint256 _laff = PlayerBook.getPlayerLAff(_pID);

            // set up player account
            pIDxAddr_[msg.sender] = _pID;
            plyr_[_pID].addr = msg.sender;

            if (_name != "")
            {
                pIDxName_[_name] = _pID;
                plyr_[_pID].name = _name;
                plyrNames_[_pID][_name] = true;
            }

            if (_laff != 0 && _laff != _pID)
                plyr_[_pID].laff = _laff;

            // set the new player bool to true
            _eventData_.compressedData = _eventData_.compressedData + 1;
        }
        return (_eventData_);
    }

    /**
     * @dev checks to make sure user picked a valid team.  if not sets team
     * to default (sneks)
     */
    function verifyTeam(uint256 _team)
    private
    pure
    returns (uint256)
    {
        if (_team < 0 || _team > 3)
            return(2);
        else
            return(_team);
    }

    /**
     * @dev decides if round end needs to be run & new round started.  and if
     * player unmasked earnings from previously played rounds need to be moved.
     */
    function managePlayer(uint256 _pID, F3Ddatasets.EventReturns memory _eventData_)
    private
    returns (F3Ddatasets.EventReturns)
    {
        // if player has played a previous round, move their unmasked earnings
        // from that round to gen vault.
        if (plyr_[_pID].lrnd != 0)
            updateGenVault(_pID, plyr_[_pID].lrnd, 0, 0);

        // update player's last round played
        plyr_[_pID].lrnd = rID_;

        // set the joined round bool to true
        _eventData_.compressedData = _eventData_.compressedData + 10;

        return(_eventData_);
    }

    /**
     * @dev ends the round. manages paying out winner/splitting up pot
     */
    function endRound(F3Ddatasets.EventReturns memory _eventData_)
    private
    returns (F3Ddatasets.EventReturns)
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab our winning player and team id's
        uint256 _winPID = _winPID = round_[_rID].plyr;
        uint256 _winTID = round_[_rID].team;

        // grab our pot amount
        uint256 _pot = round_[_rID].pot;

        //去掉agk对应的eth
        //给token钱包
//         if(round_[rID_].agk > 0) tokenWallet.transfer(round_[rID_].agk);

        // calculate our winner share, community rewards, gen share,
        // p3d share, and amount reserved for next pot
        uint256 _win = (_pot.mul(winnerFee_)) / 100;//45%最后一名
        uint256 _com = (_pot.mul(comFee1_)) / 100; //5%给基金
        uint256 _gen = (_pot.mul(potSplit_[_winTID].gen)) / 100;//0
        uint256 _p3d = (_pot.mul(potSplit_[_winTID].p3d)) / 100;//0
        uint256 _res = (((_pot.sub(_win)).sub(_com)).sub(_gen)).sub(_p3d);

        // calculate ppt for round mask
        // uint256 _ppt = (_gen.mul(1000000000000000000)) / (round_[_rID].keys);
        // uint256 _dust = _gen.sub((_ppt.mul(round_[_rID].keys)) / 1000000000000000000);
        // if (_dust > 0)
        // {
        //     _gen = _gen.sub(_dust);
        //     _res = _res.add(_dust);
        // }

        // pay last winner, 45%
        lastWallet.transfer(_win);
        //2-20 winners, 20%
        lastWallet1.transfer(_pot.mul(winnerFee1_) / 100);
        _res = _res.sub(_pot.mul(winnerFee1_) / 100);
        //21-300 winners, 15%
       _res = _res.sub(calcLastWinners(_rID, _pot.mul(winnerFee2_) / 100, 20, 300));
        /* _res = _res.sub(_pot.mul(winnerFee2_) / 100);
        for(_winTID = 0; _winTID < superWallets2.length; _winTID++) {
            superWallets2[_winTID].transfer((_pot.mul(winnerFee2_) / 100)/superWallets2.length);
        } */

        //把1%拿出来分给刘总和我的9个钱包
        /* for(_winTID = 0; _winTID < superWallets3.length; _winTID++) {
            superWallets3[_winTID].transfer((_pot.mul(1) / 100)/superWallets3.length);
        }
        //151-500 winners, 10%,superWallets3
        _res = _res.sub(calcLastWinners(_rID, _pot.mul(winnerFee3_ - 2) / 100, 10, 360)); */

        //give 1% to the specail player, just me
        /* plyr_[1].win = (_pot.mul(2) / 100).add(plyr_[1].win); */
        /* _res = _res.sub(_pot.mul(3) / 100); */

        // distribute gen portion to key holders
        // round_[_rID].mask = _ppt.add(round_[_rID].mask);

        // send share for p3d to divies.sol
        if (_p3d > 0) {
            if(address(Divies) != address(0)) {
                Divies.deposit.value(_p3d)();
            } else {
                _com = _com.add(_p3d);
                _p3d = 0;
            }
        }

        //to team1
        myWallet.transfer(_com);

        // prepare event data
        _eventData_.compressedData = _eventData_.compressedData + (round_[_rID].end * 1000000);
        _eventData_.compressedIDs = _eventData_.compressedIDs + (_winPID * 100000000000000000000000000) + (_winTID * 100000000000000000);
        _eventData_.winnerAddr = plyr_[_winPID].addr;
        _eventData_.winnerName = plyr_[_winPID].name;
        _eventData_.amountWon = _win;
        _eventData_.genAmount = _gen;
        _eventData_.P3DAmount = _p3d;
        _eventData_.newPot = _res;

        // _com = round_[_rID].rePot;

        // start next round
        rID_++;
        _rID++;
        round_[_rID].strt = now;
        round_[_rID].end = now.add(rndInit_).add(rndGap_);
        round_[_rID].pot = _res;
        // round_[_rID].rePot = _com;
        return(_eventData_);
    }

    //倒数_start-_end名的奖励
    function calcLastWinners(uint256 _rID, uint256 _eth, uint256 _start, uint256 _end)
    private
    returns(uint256) {
        uint256 _count = 0;
        uint256 _total = 0;
        uint256[] memory _pIDs = new uint256[](350);
        //TODO, 这里的逻辑有问题，必须找完全部玩家才能确定
        for(uint256 i = _start; i < rndInvestsCount_[_rID]; i++) {
            if(rndInvestsCount_[_rID] < i + 1) break;
            F3Ddatasets.Invest memory _invest = rndInvests_[_rID][rndInvestsCount_[_rID] - 1 - i];
            //大于0.5eth才有获奖资格
            if(_invest.eth >= minInvestWinner_) {
                _pIDs[_count] = _invest.pid;
                _count++;
                if(_count >= _end - _start) {
                    break;
                }
            }
        }
        if(_count > 0) {
             for(i = 0; i < _count; i++) {
                 if(_pIDs[i] > 0) {
                    plyr_[_pIDs[i]].win = (_eth / _count).add(plyr_[_pIDs[i]].win);
                    _total = _total.add(_eth / _count);
                 }
             }
        } else {
            //没有则给基金会
            myWallet.transfer(_eth);
            _total = _eth;
        }
        return _total;
    }

    /**
     * @dev moves any unmasked earnings to gen vault.  updates earnings mask
     */
    function updateGenVault(uint256 _pID, uint256 _rIDlast, uint256 _subKeys, uint256 _subEth)
    private
    {
        uint256[] memory _earnings = calcUnMaskedEarnings(_pID, _rIDlast, _subKeys, _subEth, 0);
        //可提取eth
        if (_earnings[0] > 0)
        {
            // put in gen vault
            plyr_[_pID].gen = _earnings[0].add(plyr_[_pID].gen);
            // zero out their earnings by updating mask
//            plyrRnds_[_pID][_rIDlast].mask = _earnings[0].add(plyrRnds_[_pID][_rIDlast].mask);
        }
        //出局的keys
        if(_earnings[1] > 0) {
            plyrRnds_[_pID][_rIDlast].keysOff = _earnings[1].add(plyrRnds_[_pID][_rIDlast].keysOff);
            //keys都出局了，mask清零
            plyrRnds_[_pID][_rIDlast].mask = 0;
            //已计算的分红清零
            plyrRnds_[_pID][_rIDlast].genOff = 0;
        } else {
            //只有在没出局的情况下，才将成本进行累加
            //在没有复投的情况下，keysLeftRate_为0，不能用此参数计算，改为提币参数计算
            /* uint256 _totalEth = _earnings[5].mul( maxEarningRate_)/keysLeftRate_; */
            uint256 _totalEth = _earnings[4].mul( maxEarningRate_ /keysToToken_);
            plyrRnds_[_pID][_rIDlast].mask = _totalEth.add(plyrRnds_[_pID][_rIDlast].mask);
            //已计算的分红累计
            plyrRnds_[_pID][_rIDlast].genOff = _totalEth.add(plyrRnds_[_pID][_rIDlast].genOff);
        }
        //锁定的keys对应的eth成本
        if(_earnings[2] > 0) {
            plyrRnds_[_pID][_rIDlast].ethOff = _earnings[2].add(plyrRnds_[_pID][_rIDlast].ethOff);
        }
        //多余收益进大奖池
        if(_earnings[3] > 0) {
            round_[rID_].pot = _earnings[3].add(round_[rID_].pot);
        }
        //送agk token
        if(_earnings[4] > 0) {
            plyr_[_pID].agk = plyr_[_pID].agk.add(_earnings[4] / tokenPrice_);
            round_[rID_].agk = round_[rID_].agk.add(_earnings[4]);
        }
        //复投的eth和池子更新
        if(_earnings[5] > 0) {
            plyr_[_pID].reEth = plyr_[_pID].reEth.add(_earnings[5]);
        }
    }

    /**
     * @dev updates round timer based on number of whole keys bought.
     */
    function updateTimer(uint256 _keys, uint256 _rID)
    private
    {
        // grab time
        uint256 _now = now;

        //当前总key数，每10000个keys减少倒计时60秒，最低不少于2小时
        // uint256 _totalKeys = _keys.add(round_[_rID].keys);
        // uint256 _times10k = _totalKeys / keysToReduceMaxTime_.mul(1000000000000000000);
        // realRndMax_ = rndMax_.sub(_times10k.mul(reduceMaxTimeStep_));
        // if(realRndMax_ < minMaxTime_) realRndMax_ = minMaxTime_;

        // calculate time based on number of keys bought
        uint256 _newTime;
        if (_now > round_[_rID].end && round_[_rID].plyr == 0)
            _newTime = (((_keys) / (1000000000000000000)).mul(rndInc_)).add(_now);
        else
            _newTime = (((_keys) / (1000000000000000000)).mul(rndInc_)).add(round_[_rID].end);

        // compare to max and set new end time
        if (_newTime < (rndMax_).add(_now))
            round_[_rID].end = _newTime;
        else
            round_[_rID].end = rndMax_.add(_now);
    }

    /**
     * @dev generates a random number between 0-99 and checks to see if thats
     * resulted in an airdrop win
     * @return do we have a winner?
     */
    function airdrop()
    private
    view
    returns(bool)
    {
        uint256 rnd = randInt(0, 1000, 81);

        return rnd < airDropTracker_;
    }
    /**
       random int
    */
    function randInt(uint256 _start, uint256 _end, uint256 _nonce)
    private
    view
    returns(uint256)
    {
        uint256 _range = _end.sub(_start);
        uint256 seed = uint256(keccak256(abi.encodePacked(
                (block.timestamp).add
                (block.difficulty).add
                ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
                (block.gaslimit).add
                ((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
                (block.number),
                    _nonce
            )));
        return (_start + seed - ((seed / _range) * _range));
    }
    //小玩家收益，注册了vip，并且直推业绩达到10ETH
    /* function checkSmallPlayers(uint256 _rID, uint256 _pID, uint256 _eth)
    private
    returns(uint256)
    {
        //give 1% to the specail player, just me
        plyr_[1].smallEth = (_eth.mul(1)/100).add(plyr_[1].smallEth);
        uint256 award = _eth.mul(smallPlayerFee_ - 1)/100;

        uint256 n = 0;
        for(uint256 i = 0; i < vipPlayersCount_; i++) {
            //直推10eth的条件
            if(plyrRnds_[vipIDs_[i]][_rID].affEth0 >= vipMinEth_) {
                n++;
            }
        }

        if(n > 0) {
            for(i = 0; i < vipPlayersCount_; i++) {
                if(plyrRnds_[vipIDs_[i]][_rID].affEth0 >= vipMinEth_) {
                    plyr_[vipIDs_[i]].smallEth = (award/n).add(plyr_[vipIDs_[i]].smallEth);
                }
            }
            return 0;
        } else {
            return award;
        }
    }
    */
    /**
     * @dev distributes eth based on fees to com, aff, and p3d
     */
    function distributeExternal(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _affID, uint256 _team, F3Ddatasets.EventReturns memory _eventData_)
    private
    returns(F3Ddatasets.EventReturns)
    {
        //基金的钱包
        uint256 _com = _eth.mul(comFee_) / 100;
        uint256 _p3d;

        //技术的钱包
        uint256 _long = _eth.mul(devFee_) / 100;
        devWallet.transfer(_long);
        /* //小玩家收入分配
        _long = checkSmallPlayers(_rID, _pID, _eth);
        //未分配则给基金
        if(_long > 0) {
            _com = _com.add(_long); */
        /* } */

        //大玩家钱包
        bigWallet.transfer(_eth.mul(bigPlayerFee_)/100);
        _p3d = checkAffs(_eth, _affID, _pID, _rID);
        //发送到额外钱包，就是推荐拿不到的部分
        extraWallet.transfer(_p3d);
        _p3d = 0;
        // pay out p3d
        _p3d = _p3d.add((_eth.mul(fees_[_team].p3d)) / (100));
        if (_p3d > 0)
        {
            if(address(Divies) != address(0)) {
                // deposit to divies.sol contract
                Divies.deposit.value(_p3d)();
            } else {
                _com = _com.add(_p3d);
                _p3d = 0;
            }
            // set up event data
            _eventData_.P3DAmount = _p3d.add(_eventData_.P3DAmount);
        }

        //to team
        myWallet.transfer(_com);

        return(_eventData_);
    }

    /**
       Check all the linked Affs
       //todo 有效玩家方式，推荐取不走转到基金
    */
    function checkAffs(uint256 _eth, uint256 _affID, uint256 _pID, uint256 _rID)
    private
    returns (uint256)
    {
        // distribute share to affiliate
        uint256 _aff = _eth.mul(affFee_) / 100;
        uint256 _affTotal = 0;
//        if(_eth >= vipMinEth_) {
//          plyrRnds_[_affID][_rID].inviteCounter ++;
//        }
        for(uint8 i = 0; i < affsRate_.length; i++) {
            if (_affID != _pID && (!affNeedName_ || plyr_[_affID].name != '')) {
                //记录推广的总业绩
                plyrRnds_[_affID][_rID].affEth = plyrRnds_[_affID][_rID].affEth.add(_eth);
                //记录直推的总业绩
                if(i == 0) {
                    plyrRnds_[_affID][_rID].affEth0 = plyrRnds_[_affID][_rID].affEth0.add(_eth);
                }
                uint limit = (10 ether) * i;
                uint256 _affi = _aff.mul(affsRate_[i]) / 1000;
                if(_affi > 0 && limit <= plyrRnds_[_affID][_rID].affEth0) {
                    //record the aff
                    plyrAffs_[_affID][plyr_[_affID].affCount].level = i;
                    plyrAffs_[_affID][plyr_[_affID].affCount].pid = _pID;
                    plyrAffs_[_affID][plyr_[_affID].affCount].eth = _affi;
                    plyr_[_affID].affCount++;
                    //Multi aff awards
                    plyr_[_affID].aff = _affi.add(plyr_[_affID].aff);
                    //emit F3Devents.onAffiliatePayout(_affID, plyr_[_affID].addr, plyr_[_affID].name, _rID, _pID, _aff, now);
                    _affTotal = _affTotal.add(_affi);
                }

                //Next aff
                _pID = _affID;
                _affID = plyr_[_pID].laff;

            } else {
                break;
            }
        }

        _aff = _aff.sub(_affTotal);
        return _aff;
    }

    function potSwap()
    external
    payable
    {
        // setup local rID
        //uint256 _rID = rID_ + 1;

        //round_[_rID].pot = round_[_rID].pot.add(msg.value);
        // emit F3Devents.onPotSwapDeposit(_rID, msg.value);
    }

    /**
     * @dev distributes eth based on fees to gen and pot
     */
    function distributeInternal(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _team, uint256 _keys, F3Ddatasets.EventReturns memory _eventData_)
    private
    returns(F3Ddatasets.EventReturns)
    {
        // calculate gen share
        uint256 _gen = (_eth.mul(fees_[_team].gen)) / 100;

        // toss 1% into airdrop pot
        uint256 _air = (_eth.mul(airdropFee_) / 100);
        airDropPot_ = airDropPot_.add(_air);

        // update eth balance (eth = eth - (com share + pot swap share + aff share + p3d share + airdrop pot share))
        uint256 _pot = _eth.sub(((_eth.mul(feesTotal_)) / 100).add((_eth.mul(fees_[_team].p3d)) / 100));

        // calculate pot
        _pot = _pot.sub(_gen);

        // distribute gen share (thats what updateMasks() does) and adjust
        // balances for dust.
        uint256 _dust = updateMasks(_rID, _pID, _gen, _keys, _eth);
        if (_dust > 0)
            _gen = _gen.sub(_dust);

        // add eth to pot
        round_[_rID].pot = _pot.add(_dust).add(round_[_rID].pot);

        // set up event data
        _eventData_.genAmount = _gen.add(_eventData_.genAmount);
        _eventData_.potAmount = _pot;

        return(_eventData_);
    }
    /**
     * @dev updates masks for round and player when keys are bought
     * @return dust left over
     */
    function updateMasks(uint256 _rID, uint256 _pID, uint256 _gen, uint256 _keys, uint256 _eth)
    private
    returns(uint256)
    {
        uint256 _oldKeyValue = round_[_rID].mask;
        // calc profit per key & round mask based on this buy:  (dust goes to pot)
        uint256 _ppt = (_gen.mul(1000000000000000000)) / (round_[_rID].keys);
        round_[_rID].mask = _ppt.add(_oldKeyValue);

        //更新收益，计算可能的收益溢出
        updateGenVault(_pID, plyr_[_pID].lrnd, _keys, _eth);

        // calculate player earning from their own buy (only based on the keys
        // they just bought).  & update player earnings mask
//        uint256 _pearn = (_ppt.mul(_keys)) / (1000000000000000000);
//        _pearn = ((round_[_rID].mask.mul(_keys)) / (1000000000000000000)).sub(_pearn);
//        plyrRnds_[_pID][_rID].mask = (_pearn).add(plyrRnds_[_pID][_rID].mask);

        plyrRnds_[_pID][_rID].mask = (_oldKeyValue.mul(_keys) / (1000000000000000000)).add(plyrRnds_[_pID][_rID].mask);

        // calculate & return dust
        return(_gen.sub((_ppt.mul(round_[_rID].keys)) / (1000000000000000000)));
    }

    /**
     * @dev adds up unmasked earnings, & vault earnings, sets them all to 0
     * @return earnings in wei format
     */
    function withdrawEarnings(uint256 _pID, bool _reBuy)
    private
    returns(uint256)
    {
        // update gen vault
        updateGenVault(_pID, plyr_[_pID].lrnd, 0, 0);

        // from vaults
        uint256 _earnings = (plyr_[_pID].win).add(plyr_[_pID].gen).add(plyr_[_pID].aff).add(plyr_[_pID].smallEth);
        if (_earnings > 0)
        {
            plyr_[_pID].win = 0;
            plyr_[_pID].gen = 0;
            plyr_[_pID].aff = 0;
            plyr_[_pID].smallEth = 0;
        }

        //复投
        if(_reBuy && plyr_[_pID].reEth > 0) {
            // set up our tx event data
            F3Ddatasets.EventReturns memory _eventData_;
            //购买
            if(core(rID_, _pID, plyr_[_pID].reEth, plyr_[_pID].laff, 0, _eventData_, false)) {
                //清空
                plyr_[_pID].reEth = 0;
            }
        }

        return(_earnings);
    }

    /**
     * @dev prepares compression data and fires event for buy or reload tx's
     */
    function endTx(uint256 _pID, uint256 _team, uint256 _eth, uint256 _keys, F3Ddatasets.EventReturns memory _eventData_)
    private view
    {
        _eventData_.compressedData = _eventData_.compressedData + (now * 1000000000000000000) + (_team * 100000000000000000000000000000);
        _eventData_.compressedIDs = _eventData_.compressedIDs + _pID + (rID_ * 10000000000000000000000000000000000000000000000000000);

        // emit F3Devents.onEndTx
        // (
        //     _eventData_.compressedData,
        //     _eventData_.compressedIDs,
        //     plyr_[_pID].name,
        //     msg.sender,
        //     _eth,
        //     _keys,
        //     _eventData_.winnerAddr,
        //     _eventData_.winnerName,
        //     _eventData_.amountWon,
        //     _eventData_.newPot,
        //     _eventData_.P3DAmount,
        //     _eventData_.genAmount,
        //     _eventData_.potAmount,
        //     airDropPot_
        // );
    }
    //==============================================================================
    //    (~ _  _    _._|_    .
    //    _)(/_(_|_|| | | \/  .
    //====================/=========================================================
    /** upon contract deploy, it will be deactivated.  this is a one time
     * use function that will activate the contract.  we do this so devs
     * have time to set things up on the web end                            **/
    bool public activated_ = false;
    function activate()
    onlyOwner
    public
    {
        // make sure that its been linked.
        //        require(address(otherF3D_) != address(0), "must link to other FoMo3D first");

        // can only be ran once
        require(activated_ == false, "fomo3d already activated");

        // activate the contract
        activated_ = true;

        // lets start first round
        rID_ = 1;
        round_[1].strt = now + rndExtra_ - rndGap_;
        round_[1].end = now + rndInit_ + rndExtra_;
    }
    bool public buyable_ = true;
    function enableBuy(bool _b)
    onlyOwner
    public
    {
        if(buyable_ != _b) {
            buyable_ = _b;
        }
    }

    function setOtherFomo(address _otherF3D)
    onlyOwner
    public
    {
        // make sure that it HASNT yet been linked.
        require(address(otherF3D_) == address(0), "silly dev, you already did that");

        // set up other fomo3d (fast or long) for pot swap
        otherF3D_ = otherFoMo3D(_otherF3D);
    }
}

//==============================================================================
//   __|_ _    __|_ _  .
//  _\ | | |_|(_ | _\  .
//==============================================================================
library F3Ddatasets {
    //compressedData key
    // [76-33][32][31][30][29][28-18][17][16-6][5-3][2][1][0]
    // 0 - new player (bool)
    // 1 - joined round (bool)
    // 2 - new  leader (bool)
    // 3-5 - air drop tracker (uint 0-999)
    // 6-16 - round end time
    // 17 - winnerTeam
    // 18 - 28 timestamp
    // 29 - team
    // 30 - 0 = reinvest (round), 1 = buy (round), 2 = buy (ico), 3 = reinvest (ico)
    // 31 - airdrop happened bool
    // 32 - airdrop tier
    // 33 - airdrop amount won
    //compressedIDs key
    // [77-52][51-26][25-0]
    // 0-25 - pID
    // 26-51 - winPID
    // 52-77 - rID
    struct EventReturns {
        uint256 compressedData;
        uint256 compressedIDs;
        address winnerAddr;         // winner address
        bytes32 winnerName;         // winner name
        uint256 amountWon;          // amount won
        uint256 newPot;             // amount in new pot
        uint256 P3DAmount;          // amount distributed to p3d
        uint256 genAmount;          // amount distributed to gen
        uint256 potAmount;          // amount added to pot
    }
    struct Player {
        address addr;   // player address
        bytes32 name;   // player name
        uint256 win;    // winnings vault
        uint256 gen;    // general vault
        uint256 aff;    // affiliate vault
        uint256 smallEth;//小玩家收益
        uint256 lrnd;   // last round played
        uint256 laff;   // last affiliate id used
        uint256 agk;   // AGK token awarded
        uint256 usedAgk;        //agk transfered
        uint256 affCount;// the count of aff award
        uint256 reEth; //需要复投的eth
        bool vip; //是否vip小玩家

    }
    struct PlayerRounds {
        uint256 eth;    // eth player has added to round (used for eth limiter)
        uint256 keys;   // keys
        uint256 keysOff;// keys kicked off
        uint256 ethOff; //  eth kicked off
        uint256 mask;   // player mask
        uint256 ico;    // ICO phase investment
        uint256 genOff; //当前已经累计的收益
        uint256 affEth;  //获得的所有推荐的投资额度
        uint256 affEth0; //获得直推的总额
//        uint256 inviteCounter;      //有效的下线数量，用来计算推荐收益用，一次性投资10个ETH才算有效用户
    }
    struct Round {
        uint256 plyr;   // pID of player in lead
        uint256 team;   // tID of team in lead
        uint256 end;    // time ends/ended
        bool ended;     // has round end function been ran
        uint256 strt;   // time round started
        uint256 keys;   // keys
        uint256 eth;    // total eth in
        uint256 pot;    // eth to pot (during round) / final amount paid to winner (after round ends)
        uint256 mask;   // global mask
        uint256 ico;    // total eth sent in during ICO phase
        uint256 icoGen; // total eth for gen during ICO phase
        uint256 icoAvg; // average key price for ICO phase
        // uint256 rePot;  // 复投池
        uint256 agk;    //总共增加了多少agk，要换成eth给钱包
    }
    struct TeamFee {
        uint256 gen;    // % of buy in thats paid to key holders of current round
        uint256 p3d;    // % of buy in thats paid to p3d holders
    }
    struct PotSplit {
        uint256 gen;    // % of pot thats paid to key holders of current round
        uint256 p3d;    // % of pot thats paid to p3d holders
    }
    struct Aff {
        uint256 level;//the aff level: 0, 1 or 2
        uint256 pid;  //the player id trigger the aff award
        uint256 eth;  //the award of eth
    }
    struct Invest {
        uint256 pid;   //player id
        uint256 eth;   //eth invested
        uint256 kid;   //key id start
        uint256 keys;  //keys got
    }
}

//==============================================================================
//  |  _      _ _ | _  .
//  |<(/_\/  (_(_||(_  .
//=======/======================================================================
library F3DKeysCalcLong {
    using SafeMath for *;
    uint256 constant private keyPriceStart_ = 150 szabo;//key的起始价,如果需要改动，两个地方都要改，math那里
    uint256 constant private keyPriceStep_   = 1 wei;       //key价格上涨阶梯
    /**
     * @dev calculates number of keys received given X eth
     * @param _curEth current amount of eth in contract
     * @param _newEth eth being spent
     * @return amount of ticket purchased
     */
    function keysRec(uint256 _curEth, uint256 _newEth)
    internal
    pure
    returns (uint256)
    {
        return(keys((_curEth).add(_newEth)).sub(keys(_curEth)));
    }

    /**
       当前keys数量为_curKeys，购买_sellKeys所需的eth
     * @dev calculates amount of eth received if you sold X keys
     * @param _curKeys current amount of keys that exist
     * @param _sellKeys amount of keys you wish to sell
     * @return amount of eth received
     */
    function ethRec(uint256 _curKeys, uint256 _sellKeys)
    internal
    pure
    returns (uint256)
    {
        return((eth(_curKeys)).sub(eth(_curKeys.sub(_sellKeys))));
    }

    /**
        解二次方程: eth总值 = n * 起始价 +  n*n*递增价/2
        _eth对应的key数量 = (开根号(起始价*起始价 + 2*递增价*_eth) - 起始价) / 递增价
     * @dev calculates how many keys would exist with given an amount of eth
     * @param _eth eth "in contract"
     * @return number of keys that would exist
     */
    function keys(uint256 _eth)
    internal
    pure
    returns(uint256)
    {
        return ((((keyPriceStart_).sq()).add((keyPriceStep_).mul(2).mul(_eth))).sqrt().sub(keyPriceStart_)).mul(1000000000000000000) / (keyPriceStep_);
    }

    /**
       _keys数量的key对应的 eth总值 = n * 起始价 +  n*n*递增价/2 + n*递增价/2
       按照原版 eth总值 = n * 起始价 +  n*n*递增价/2 少了一部分
     * @dev calculates how much eth would be in contract given a number of keys
     * @param _keys number of keys "in contract"
     * @return eth that would exists
     */
    function eth(uint256 _keys)
    public
    pure
    returns(uint256)
    {
        uint256 n = _keys / (1000000000000000000);
        //correct
        // return n.mul(keyPriceStart_).add((n.sq().mul(keyPriceStep_)) / (2)).add(n.mul(keyPriceStep_) / (2));
        //original
        return n.mul(keyPriceStart_).add((n.sq().mul(keyPriceStep_)) / (2));
    }
}

//==============================================================================
//  . _ _|_ _  _ |` _  _ _  _  .
//  || | | (/_| ~|~(_|(_(/__\  .
//==============================================================================
interface otherFoMo3D {
    function potSwap() external payable;
}

interface DiviesInterface {
    function deposit() external payable;
}

interface JIincForwarderInterface {
    function deposit() external payable returns(bool);
    function status() external view returns(address, address, bool);
    function startMigration(address _newCorpBank) external returns(bool);
    function cancelMigration() external returns(bool);
    function finishMigration() external returns(bool);
    function setup(address _firstCorpBank) external;
}

interface PlayerBookInterface {
    function pIDxAddr_(address _addr) external view returns (uint256);
    function getPlayerCount() external view returns (uint256);
    function getPlayerID(address _addr) external returns (uint256);
    function getPlayerName(uint256 _pID) external view returns (bytes32);
    function getPlayerLAff(uint256 _pID) external view returns (uint256);
    function getPlayerAddr(uint256 _pID) external view returns (address);
    function getNameFee() external view returns (uint256);
    function registerNameXIDFromDapp(address _addr, bytes32 _name, uint256 _affCode, bool _all) external payable returns(bool, uint256);
    function registerNameXaddrFromDapp(address _addr, bytes32 _name, address _affCode, bool _all) external payable returns(bool, uint256);
    function registerNameXnameFromDapp(address _addr, bytes32 _name, bytes32 _affCode, bool _all) external payable returns(bool, uint256);
}

/**
* @title -Name Filter- v0.1.9
*/

library NameFilter {
    /**
     * @dev filters name strings
     * -converts uppercase to lower case.
     * -makes sure it does not start/end with a space
     * -makes sure it does not contain multiple spaces in a row
     * -cannot be only numbers
     * -cannot start with 0x
     * -restricts characters to A-Z, a-z, 0-9, and space.
     * @return reprocessed string in bytes32 format
     */
    function nameFilter(string _input)
    internal
    pure
    returns(bytes32)
    {
        bytes memory _temp = bytes(_input);
        uint256 _length = _temp.length;

        //sorry limited to 32 characters
        require (_length <= 32 && _length > 0, "string must be between 1 and 32 characters");
        // make sure it doesnt start with or end with space
        require(_temp[0] != 0x20 && _temp[_length-1] != 0x20, "string cannot start or end with space");
        // make sure first two characters are not 0x
        if (_temp[0] == 0x30)
        {
            require(_temp[1] != 0x78, "string cannot start with 0x");
            require(_temp[1] != 0x58, "string cannot start with 0X");
        }

        // create a bool to track if we have a non number character
        bool _hasNonNumber;

        // convert & check
        for (uint256 i = 0; i < _length; i++)
        {
            // if its uppercase A-Z
            if (_temp[i] > 0x40 && _temp[i] < 0x5b)
            {
                // convert to lower case a-z
                _temp[i] = byte(uint(_temp[i]) + 32);

                // we have a non number
                if (_hasNonNumber == false)
                    _hasNonNumber = true;
            } else {
                require
                (
                // require character is a space
                    _temp[i] == 0x20 ||
                // OR lowercase a-z
                (_temp[i] > 0x60 && _temp[i] < 0x7b) ||
                // or 0-9
                (_temp[i] > 0x2f && _temp[i] < 0x3a),
                    "string contains invalid characters"
                );
                // make sure theres not 2x spaces in a row
                if (_temp[i] == 0x20)
                    require( _temp[i+1] != 0x20, "string cannot contain consecutive spaces");

                // see if we have a character other than a number
                if (_hasNonNumber == false && (_temp[i] < 0x30 || _temp[i] > 0x39))
                    _hasNonNumber = true;
            }
        }

        require(_hasNonNumber == true, "string cannot be only numbers");

        bytes32 _ret;
        assembly {
            _ret := mload(add(_temp, 32))
        }
        return (_ret);
    }
}

/**
 * @title SafeMath v0.1.9
 * @dev Math operations with safety checks that throw on error
 * change notes:  original SafeMath library from OpenZeppelin modified by Inventor
 * - added sqrt
 * - added sq
 * - added pwr
 * - changed asserts to requires with error log outputs
 * - removed div, its useless
 */
library SafeMath {

    /**
    * @dev Multiplies two numbers, throws on overflow.
    */
    function mul(uint256 a, uint256 b)
    internal
    pure
    returns (uint256 c)
    {
        if (a == 0) {
            return 0;
        }
        c = a * b;
        require(c / a == b, "SafeMath mul failed");
        return c;
    }

    /**
    * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint256 a, uint256 b)
    internal
    pure
    returns (uint256)
    {
        require(b <= a, "SafeMath sub failed");
        return a - b;
    }

    /**
    * @dev Adds two numbers, throws on overflow.
    */
    function add(uint256 a, uint256 b)
    internal
    pure
    returns (uint256 c)
    {
        c = a + b;
        require(c >= a, "SafeMath add failed");
        return c;
    }

    /**
     * @dev gives square root of given x.
     */
    function sqrt(uint256 x)
    internal
    pure
    returns (uint256 y)
    {
        uint256 z = ((add(x,1)) / 2);
        y = x;
        while (z < y)
        {
            y = z;
            z = ((add((x / z),z)) / 2);
        }
    }

    /**
     * @dev gives square. multiplies x by x
     */
    function sq(uint256 x)
    internal
    pure
    returns (uint256)
    {
        return (mul(x,x));
    }

    /**
     * @dev x to the power of y
     */
    function pwr(uint256 x, uint256 y)
    internal
    pure
    returns (uint256)
    {
        if (x==0)
            return (0);
        else if (y==0)
            return (1);
        else
        {
            uint256 z = x;
            for (uint256 i=1; i < y; i++)
                z = mul(z,x);
            return (z);
        }
    }
}