pragma solidity ^0.4.25;

contract FOMOEvents {
    // fired whenever a player registers a name
    event onNewName
    (
        uint256 indexed playerID,
        address indexed playerAddress,
        bytes32 indexed playerName,
        bool isNewPlayer,
        uint256 affiliateID,
        address affiliateAddress,
        bytes32 affiliateName,
        uint256 amountPaid,
        uint256 timeStamp
    );

    // fired at end of buy or reload
    event onEndTx
    (
        uint256 compressedData,
        uint256 compressedIDs,
        bytes32 playerName,
        address playerAddress,
        uint256 ethIn,
        uint256 keysBought,
        address winnerAddr,
        bytes32 winnerName,
        uint256 amountWon,
        uint256 newPot,
        uint256 tokenAmount,
        uint256 genAmount,
        uint256 potAmount,
        uint256 seedAdd
    );

    // fired whenever theres a withdraw
    event onWithdraw
    (
        uint256 indexed playerID,
        address playerAddress,
        bytes32 playerName,
        uint256 ethOut,
        uint256 timeStamp
    );

    // fired whenever a withdraw forces end round to be ran
    event onWithdrawAndDistribute
    (
        address playerAddress,
        bytes32 playerName,
        uint256 ethOut,
        uint256 compressedData,
        uint256 compressedIDs,
        address winnerAddr,
        bytes32 winnerName,
        uint256 amountWon,
        uint256 newPot,
        uint256 tokenAmount,
        uint256 genAmount,
        uint256 seedAdd
    );

    // fired whenever a player tries a buy after round timer
    // hit zero, and causes end round to be ran.
    event onBuyAndDistribute
    (
        address playerAddress,
        bytes32 playerName,
        uint256 ethIn,
        uint256 compressedData,
        uint256 compressedIDs,
        address winnerAddr,
        bytes32 winnerName,
        uint256 amountWon,
        uint256 newPot,
        uint256 tokenAmount,
        uint256 genAmount,
        uint256 seedAdd
    );

    // fired whenever a player tries a reload after round timer
    // hit zero, and causes end round to be ran.
    event onReLoadAndDistribute
    (
        address playerAddress,
        bytes32 playerName,
        uint256 compressedData,
        uint256 compressedIDs,
        address winnerAddr,
        bytes32 winnerName,
        uint256 amountWon,
        uint256 newPot,
        uint256 tokenAmount,
        uint256 genAmount,
        uint256 seedAdd
    );

    // fired whenever an affiliate is paid
    event onAffiliatePayout
    (
        uint256 indexed affiliateID,
        address affiliateAddress,
        bytes32 affiliateName,
        uint256 indexed roundID,
        uint256 indexed buyerID,
        uint256 amount,
        uint256 timeStamp
    );
}

//==============================================================================
//   _ _  _ _|_ _ _  __|_   _ _ _|_    _   .
//  (_(_)| | | | (_|(_ |   _\(/_ | |_||_)  .
//====================================|=========================================

contract FFEIF is FOMOEvents {
    using SafeMath for *;
    using NameFilter for string;
   
    PlayerBookInterface  private PlayerBook;

//==============================================================================
//     _ _  _  |`. _     _ _ |_ | _  _  .
//    (_(_)| |~|~|(_||_|| (_||_)|(/__\  .  (game settings)
//=================_|===========================================================
    PoEIF public PoEIFContract;
    address private admin = msg.sender;
    string constant public name = "Fomo Forever EIF";
    string constant public symbol = "FFEIF";
    uint256 private rndExtra_ = 1 minutes;     // length of the very first ICO
    uint256 public rndGap_ = 1 minutes;        // length of ICO phase, set to 1 year for EOS.
    uint256 public rndInit_ = 60 minutes;      // round timer starts at this
    uint256 public rndInc_ = 1 seconds;        // every full FFEIF purchased adds this much to the timer
    uint256 public rndIncDivisor_ = 1;         // divides the above by this amount (useful for less than 1 second per FFEIF when setting shorter rounds)
	
	uint256 public potSeedRate = 100;		   // pot increase per hour from seedingPot (divided into seedingPot) - default 100 means 1% per hour is added
	uint256 public potNextSeedTime = 0;        // time for the next increase (increases by an hour each time it is processed)		
	uint256 public seedingPot  = 0;            // this keeps track of the amount left to feed the pot - initially has nothing but can be fed by anyone
	uint256 public seedingThreshold = 0 ether; // for the first x ETH (or however it is set) of each round, all pot increases go to the seeding rather than the actual pot
	uint256 public seedingDivisor = 2;         // after the threshold, the pot amount is divided by this and added to the seedingPot i.e. half - a higher divisor means less goes into seeding
	uint256 public seedRoundEnd = 1;           // if nextRoundPercentage will also add to seeding pot as normal or instead fully goes to the pot
	
	uint256 public linearPrice = 75000000000000;            // if non-zero will set a fixed price - otherwise normal fomo scaling which is fairly linear still
	
	uint256 public multPurchase = 0;	      // number of FFEIF needed to purchase for the multiplier to go up.... 0 means it has to be a qualifying purchase of the multiplier amount of FFEIF i.e. enough to make the user the winner if nobody else buys enough afterwards...
	uint256 public multAllowLast = 1;         // Whether the current winner (last qualifying purchaser) can increase the multiplier - this could be used as a tactic to win
	uint256 public multLinear = 2;            // multiplier increases linearly, if not the multiplier is multiplied rather than added to each time a sufficient amount is purchased
	                                          // NOTE: If the value is 2 then it is linear until multStart is reached.....
	
	uint256 public maxMult = 1000000;		  // The maximum value of multiplier (remember it is 10 times the real value);			
	uint256 public multInc_ = 0;              // every purchase (any amount greater than multPurchase) adds this much to the multiplier but divided by 10 - a value of 10 means a real increase of 1 per purchase
											  // NOTE: For non-linear increases, this real value is added to 1 and multiplied - i.e. 10 would mean it doubles each time
											  // NOTE2: If multInc_ is 0, the amount of FFEIF purchased is used as the value (which is still ten times more than the real value)
	uint256 public multIncFactor_ = 10;		  // Further factor to multiply multInc_ by before processing (this is for finer adjustments when multInc_ is set to 0)
	uint256 public multLastChange = now;      // stores timestamp of the last increase/decrease so the next decay can by calculated - note that for non-linear decay, the timestamp only changes to the next minute or multiple of minutes that have passed
	uint256 public multDecayPerMinute = 1;    // every minute the multiplier reduces by this amount (isn't X10 this time so is the true decrease per minute  - i.e. 1)
											  // NOTE: For non-linear decay, a value of 1 means it halves every minute (1 is added to the amount and divided rather than subtracted)
	uint256 public multStart = 24 hours;       // the max time that should be left for the multiplier to increase through more purchases - won't increase when timer is above this
	uint256 public multCurrent = 10;	      // the current multiplier times 10 - defaults to a real value of 1 and this is also the minimum allowed
	
    uint256 public rndMax_ = 24 hours;      // max length a round timer can be
    uint256 public earlyRoundLimit = 1e18;        // limit of 1ETH per player (not per purchase) until earlyRoundLimitUntil
    uint256 public earlyRoundLimitUntil = 100e18; // when limiter ends
    
    uint256 public divPercentage = 65;        // max 75% and can be configured - potPercentage is implied from the rest - 5% each for PoEIF and EIF is fixed
    uint256 public affFee = 5;                // aff fee default is 5% and max is 15%
    uint256 public potPercentage = 20;        // 90 - divPercentage - affFee  (seeding divisor applies to this)
    
    uint256 public divPotPercentage = 15;     // max 50% and can be configured - winnerPercentage is implied from the rest - 5% each for PoEIF and EIF is fixed
    uint256 public nextRoundPercentage = 25;  // max 40% and is the amount carried to the next round (since it is going to the pot, the seeding divisor ratio also applies to this if seedRoundEnd is 1)
    uint256 public winnerPercentage = 50;     // 90 - divPotPercentage - nextRoundPercentatge
    
    uint256 public fundEIF = 0;               // the EasyInvestForever accumulated fund yet to be sent (5% of incoming ETH)
    uint256 public totalEIF = 0;              // total EasyInvestForever fund already sent
    uint256 public seedDonated = 0;           // total sent to seedingPot payable function (doesn't keep track of msg.sender)
    address public FundEIF = 0x0111E8A755a4212E6E1f13e75b1EABa8f837a213; // Usual fund address to send EIF funds too - updateable
    


//==============================================================================
//     _| _ _|_ _    _ _ _|_    _   .
//    (_|(_| | (_|  _\(/_ | |_||_)  .  (data used to store game info that changes)
//=============================|================================================
    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 => FFEIFDatasets.Player) public plyr_;   // (pID => data) player data
    mapping (uint256 => mapping (uint256 => FFEIFDatasets.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 => FFEIFDatasets.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
//****************
// TEAM FEE DATA
//****************
    mapping (uint256 => FFEIFDatasets.TeamFee) public fees_;          // (team => fees) fee distribution by team
    mapping (uint256 => FFEIFDatasets.PotSplit) public potSplit_;     // (team => fees) pot split distribution by team
//==============================================================================
//     _ _  _  __|_ _    __|_ _  _  .
//    (_(_)| |_\ | | |_|(_ | (_)|   .  (initial data setup upon contract deploy)
//==============================================================================
    constructor()
        public
    {
        PoEIFContract = PoEIF(0xFfB8ccA6D55762dF595F21E78f21CD8DfeadF1C8);
        PlayerBook = PlayerBookInterface(0xd80e96496cd0B3F95bB4941b1385023fBCa1E6Ba);
        
    }

//==============================================================================
//     _  _ _  _  .
//    | |(/_|/\|  .  (these are for multiplier and new functions)
//==============================================================================
    
function updateFundAddress(address _newAddress)
        onlyAdmin()
        public
    {
        FundEIF = _newAddress;
    }


    /**
     * @dev calculates number of FFEIF 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
        view
        returns (uint256)
    {
        return(keys((_curEth).add(_newEth)).sub(keys(_curEth)));
    }

    /**
     * @dev calculates amount of eth received if you sold X FFEIF
     * @param _curKeys current amount of FFEIF that exist
     * @param _sellKeys amount of FFEIF you wish to sell
     * @return amount of eth received
     */
    function ethRec(uint256 _curKeys, uint256 _sellKeys)
        internal
        view
        returns (uint256)
    {
        return((eth(_curKeys)).sub(eth(_curKeys.sub(_sellKeys))));
    }

    /**
     * @dev calculates how many FFEIF would exist with given an amount of eth
     * @param _eth eth "in contract"
     * @return number of FFEIF that would exist
     */
    function keys(uint256 _eth)
        internal
        view
        returns(uint256)
    {
        if (linearPrice==0)
        {return ((((((_eth).mul(1000000000000000000)).mul(312500000000000000000000000)).add(5624988281256103515625000000000000000000000000000000000000000000)).sqrt()).sub(74999921875000000000000000000000)) / (156250000);}
        else
        {return 1e18.mul(_eth) / linearPrice;}
    }

    /**
     * @dev calculates how much eth would be in contract given a number of FFEIF
     * @param _keys number of FFEIF "in contract"
     * @return eth that would exists
     */
    function eth(uint256 _keys)
        internal
        view
        returns(uint256)
    {
         if (linearPrice==0)
        {return ((78125000).mul(_keys.sq()).add(((149999843750000).mul(_keys.mul(1000000000000000000))) / (2))) / ((1000000000000000000).sq());}
        else
        {return _keys.mul(linearPrice)/1e18;}
    }


function payFund() public {   //Registration fee goes to EIF - function must be called manually so enough gas is sent
    if(!FundEIF.call.value(fundEIF)()) {
        revert();
    }
    totalEIF = totalEIF.add(fundEIF); fundEIF = 0; 
}


function calcMult(uint256 keysBought, bool validIncrease) internal returns (bool)
{
    uint256 _now = now; //make local variable
	uint256 secondsPassed = _now - multLastChange;
	
	//stores boolean for whether time left has reached the threshold for multiplier to be active - may still be active in special case
	bool thresholdReached = (multStart > round_[rID_].end - _now);
	
	// work out if linear and update the last change time depending on this 
	// multLastChange updates all the time even when multiplier inactive or at minimum and doesn't change
	bool currentlyLinear = false;
	if (multLinear == 1 || (multLinear == 2 && !thresholdReached)) { currentlyLinear = true; multLastChange = _now;}
	else  multLastChange = multLastChange.add((secondsPassed/60).mul(60)); //updates every 60 seconds only
	// includes special case is where it changes from linear to non-linear when multLinear is set to 2 and thresholdReached
	
	//first apply the decay even when not active
	if (multCurrent >= 10) {
	    if (currentlyLinear) multCurrent = (multCurrent.mul(10).sub(multDecayPerMinute.mul(secondsPassed).mul(100)/60))/10; else multCurrent = multCurrent / (1+(multDecayPerMinute.mul(secondsPassed)/60));
		if (multCurrent < 10) multCurrent = 10;
	}
	
	
	// returnValue true if enough bought to be new winner - note that it is set before the increase but after the decay 
	bool returnValue = ((keysBought / 1e17) >= multCurrent);
	
	// now increase the multiplier if active
	if ((thresholdReached || multLinear == 2) && validIncrease) {
	    uint256 wholeKeysBought = keysBought / 1e18;
	    uint256 actualMultInc = multIncFactor_.mul(wholeKeysBought);
	    if (multInc_ != 0) actualMultInc = multInc_;
	    
	    // have enough FFEIF/keys been bought to increase multiplier
	    if ((wholeKeysBought >= multPurchase && multPurchase > 0) || ((wholeKeysBought >= (multCurrent / 10)) && multPurchase == 0) ) {  
	    //now apply increase
	        if (currentlyLinear) multCurrent = multCurrent.add(actualMultInc); else multCurrent = multCurrent.mul((1+(actualMultInc/10)));
	        if (multCurrent > maxMult) multCurrent = maxMult;
	    }
    }
	
	return returnValue;
	
}


function viewMult() public view returns (uint256) // since multiplier only increases when keys are bought, this will only estimate the decay
{
    uint256 _now = now; //make local variable
	uint256 secondsPassed = _now - multLastChange; 
	
	//stores boolean for whether time left has reached the threshold for multiplier to be active - may still be active in special case
	bool thresholdReached = (multStart > round_[rID_].end - _now);
	
	// work out if linear
	bool currentlyLinear = false;
	if (multLinear == 1 || (multLinear == 2 && !thresholdReached)) currentlyLinear = true;
	// includes special case is where it changes from linear to non-linear when multLinear is set to 2 and thresholdReached
	
	//first apply the decay even when not active
	uint256 _multCurrent = multCurrent; //create local 
	if (_multCurrent >= 10) {
	    if (currentlyLinear) _multCurrent = (_multCurrent.mul(10).sub(multDecayPerMinute.mul(secondsPassed).mul(100)/60))/10; else
	        {
	            //make approximation for display reasons 
	            uint256 proportion = secondsPassed % 60;
	            _multCurrent = _multCurrent / (1+(multDecayPerMinute.mul(secondsPassed)/60));
	            uint256 _multCurrent2 = multCurrent / (1+(multDecayPerMinute.mul(secondsPassed+60)/60));
	            _multCurrent = _multCurrent - proportion.mul(_multCurrent - _multCurrent2)/60;
	        }
	}
	
	
    if (_multCurrent < 10) _multCurrent = 10;	
    return _multCurrent;
}

function viewPot() public view returns (uint256) // shows pot in realtime including proportion of seeding pot (so it rises every second)
{
    uint256 _now = now;
    uint256 _pot = round_[rID_].pot;
    uint256 _seedingPot = seedingPot;
    uint256 _potSeedRate = potSeedRate;
    uint256 _potNextSeedTime = potNextSeedTime;
    
    // emulate seeding to also make sure potNextSeedTime is not before _now
    while (_potNextSeedTime<now) {_pot = _pot.add(_seedingPot/_potSeedRate); _seedingPot = _seedingPot.sub(_seedingPot/_potSeedRate); _potNextSeedTime += 3600;}
    
    //time left for hourly real update
    uint256 timeLeft = potNextSeedTime - _now;
    
   // return calculated estimate - the usually negligible sub-hour extra is not actually won at round end (waste of gas to implement) but is for display reasons
   return ((3600-timeLeft).mul(_seedingPot/_potSeedRate)/3600 ).add(_pot);
    
}


uint numElements = 0;
uint256[] varvalue;
string[] varname;

function insert(string _var, uint256 _value) internal  {
    if(numElements == varvalue.length) {
        varvalue.length ++; varname.length ++;
    }
    varvalue[numElements] = _value;
    varname[numElements] = _var;
	numElements++;
}


function setStore(string _variable, uint256 _value) public  {  // we used uint256 for everything here

    // add any new configuration change to array list - admin only and ignore the dummy values sent by endround function
    if (keccak256(bytes(_variable))!=keccak256("endround") && msg.sender == admin) insert(_variable,_value);
    
    // if round has ended, update all variables and reset array index afterwards to effectively clear it
    if (round_[rID_].ended || activated_ == false)  {
       //now loop through all elements 
    	for (uint i=0; i<numElements; i++) {
    	   bytes32 _varname = keccak256(bytes(varname[i])); 
	   if (_varname==keccak256('rndGap_')) rndGap_=varvalue[i]; else 
	   if (_varname==keccak256('rndInit_')) rndInit_=varvalue[i]; else
	   if (_varname==keccak256('rndInc_')) rndInc_=varvalue[i]; else
	   if (_varname==keccak256('rndIncDivisor_')) rndIncDivisor_=varvalue[i]; else
	   if (_varname==keccak256('potSeedRate')) potSeedRate=varvalue[i]; else
	   if (_varname==keccak256('potNextSeedTime')) potNextSeedTime=varvalue[i]; else
	   if (_varname==keccak256('seedingThreshold')) seedingThreshold=varvalue[i]; else
	   if (_varname==keccak256('seedingDivisor')) seedingDivisor=varvalue[i]; else
	   if (_varname==keccak256('seedRoundEnd')) seedRoundEnd=varvalue[i]; else
	   if (_varname==keccak256('linearPrice')) linearPrice=varvalue[i]; else
	   if (_varname==keccak256('multPurchase')) multPurchase=varvalue[i]; else
	   if (_varname==keccak256('multAllowLast')) multAllowLast=varvalue[i]; else
	   if (_varname==keccak256('maxMult')) maxMult=varvalue[i]; else
	   if (_varname==keccak256('multInc_')) multInc_=varvalue[i]; else
	   if (_varname==keccak256('multIncFactor_')) multIncFactor_=varvalue[i]; else
	   if (_varname==keccak256('multLastChange')) multLastChange=varvalue[i]; else
	   if (_varname==keccak256('multDecayPerMinute')) multDecayPerMinute=varvalue[i]; else
	   if (_varname==keccak256('multStart')) multStart=varvalue[i]; else
	   if (_varname==keccak256('multCurrent')) multCurrent=varvalue[i]; else
	   if (_varname==keccak256('rndMax_')) rndMax_=varvalue[i]; else
	   if (_varname==keccak256('earlyRoundLimit')) earlyRoundLimit=varvalue[i]; else
	   if (_varname==keccak256('earlyRoundLimitUntil')) earlyRoundLimitUntil=varvalue[i]; else
	   if (_varname==keccak256('divPercentage')) {divPercentage=varvalue[i]; if (divPercentage>75) divPercentage=75;} else
	   if (_varname==keccak256('divPotPercentage')) {divPotPercentage=varvalue[i]; if (divPotPercentage>50) divPotPercentage=50;} else
	   if (_varname==keccak256('nextRoundPercentage')) {nextRoundPercentage=varvalue[i]; if (nextRoundPercentage>40) nextRoundPercentage=40;} else
	   if (_varname==keccak256('affFee')) {affFee=varvalue[i]; if (affFee>15) affFee=15;}
		}
		//clear elements by resetting index
		numElements = 0;
		// recalculate pot and winner percentages - assume SafeMath not needed due to max values being enforced
		winnerPercentage = 90 - divPotPercentage - nextRoundPercentage;
		potPercentage = 90 - divPercentage - affFee;  
		// reset multiplier to minimum
		multCurrent = 10;
		// finally update legacy arrays
		fees_[0] = FFEIFDatasets.TeamFee(divPercentage,10);   // rest to aff (affFee) and potPercentage
        potSplit_[0] = FFEIFDatasets.PotSplit(divPotPercentage,10);  // also winnerPercentage to winner, nextRoundPercentage to next round
    
	}
}

    
    
//==============================================================================
//     _ _  _  _|. |`. _  _ _  .
//    | | |(_)(_||~|~|(/_| _\  .  (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);
        // quick one-line hack to feed pot from seedingPot - adds one hour each time so may loop a few times if contract has been dormant
        while (potNextSeedTime<now)  {round_[rID_].pot = round_[rID_].pot.add(seedingPot/potSeedRate); seedingPot = seedingPot.sub(seedingPot/potSeedRate); potNextSeedTime += 3600; }
        _;
    }

    /**
     * @dev prevents other contracts from interacting with this one
     */
    modifier isHuman() {
        address _addr = msg.sender;
        uint256 _codeLength;
        require (msg.sender == tx.origin);
        assembly {_codeLength := extcodesize(_addr)}
        require(_codeLength == 0);
        
        _;
    }

    /**
     * @dev sets boundaries for incoming tx
     */
    modifier isWithinLimits(uint256 _eth) {
        require(_eth >= 1000000000);
        require(_eth <= 100000000000000000000000);
        _;
    }

 modifier onlyAdmin()
    {
        require(msg.sender == admin);
        _;
    }

//==============================================================================
//     _    |_ |. _   |`    _  __|_. _  _  _  .
//    |_)|_||_)||(_  ~|~|_|| |(_ | |(_)| |_\  .  (use these to interact with contract)
//====|=========================================================================
    /**
     * @dev emergency buy uses last stored affiliate ID
     */
    function()
        isActivated()
        isHuman()
        isWithinLimits(msg.value)
        public
        payable
    {
        // set up our tx event data and determine if player is new or not
        FFEIFDatasets.EventReturns memory _eventData_ = determinePID(_eventData_);

        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        // buy core
        buyCore(_pID, plyr_[_pID].laff, _eventData_);
    }
    
    
    function seedDeposit()
        isWithinLimits(msg.value)
        public
        payable
    {
        // add to seedingPot
        seedingPot = seedingPot.add(msg.value);
        seedDonated = seedDonated.add(msg.value);
    }

    /**
     * @dev converts all incoming ethereum to FFEIF.
     * -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
     */
    function buyXid(uint256 _affCode)
        isActivated()
        isHuman()
        isWithinLimits(msg.value)
        public
        payable
    {
        // set up our tx event data and determine if player is new or not
        FFEIFDatasets.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;
        }

        // buy core
        buyCore(_pID, _affCode, _eventData_);
    }

    function buyXaddr(address _affCode)
        isActivated()
        isHuman()
        isWithinLimits(msg.value)
        public
        payable
    {
        // set up our tx event data and determine if player is new or not
        FFEIFDatasets.EventReturns memory _eventData_ = determinePID(_eventData_);

        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        uint256 _affID;
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == address(0) || _affCode == msg.sender)
        {
            // use last stored affiliate code
            _affID = plyr_[_pID].laff;

        // if affiliate code was given
        } else {
            // get affiliate ID from aff Code
            _affID = pIDxAddr_[_affCode];

            // if affID is not the same as previously stored
            if (_affID != plyr_[_pID].laff)
            {
                // update last affiliate
                plyr_[_pID].laff = _affID;
            }
        }
        // buy core
        buyCore(_pID, _affID, _eventData_);
    }

    function buyXname(bytes32 _affCode)
        isActivated()
        isHuman()
        isWithinLimits(msg.value)
        public
        payable
    {
        // set up our tx event data and determine if player is new or not
        FFEIFDatasets.EventReturns memory _eventData_ = determinePID(_eventData_);

        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        uint256 _affID;
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == '' || _affCode == plyr_[_pID].name)
        {
            // use last stored affiliate code
            _affID = plyr_[_pID].laff;

        // if affiliate code was given
        } else {
            // get affiliate ID from aff Code
            _affID = pIDxName_[_affCode];

            // if affID is not the same as previously stored
            if (_affID != plyr_[_pID].laff)
            {
                // update last affiliate
                plyr_[_pID].laff = _affID;
            }
        }

        // buy core
        buyCore(_pID, _affID, _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 _eth amount of earnings to use (remainder returned to gen vault)
     */
    function reLoadXid(uint256 _affCode, uint256 _eth)
        isActivated()
        isHuman()
        isWithinLimits(_eth)
        public
    {
        // set up our tx event data
        FFEIFDatasets.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;
        }

        // reload core
        reLoadCore(_pID, _affCode,  _eth, _eventData_);
    }

    function reLoadXaddr(address _affCode, uint256 _eth)
        isActivated()
        isHuman()
        isWithinLimits(_eth)
        public
    {
        // set up our tx event data
        FFEIFDatasets.EventReturns memory _eventData_;

        // fetch player ID
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        uint256 _affID;
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == address(0) || _affCode == msg.sender)
        {
            // use last stored affiliate code
            _affID = plyr_[_pID].laff;

        // if affiliate code was given
        } else {
            // get affiliate ID from aff Code
            _affID = pIDxAddr_[_affCode];

            // if affID is not the same as previously stored
            if (_affID != plyr_[_pID].laff)
            {
                // update last affiliate
                plyr_[_pID].laff = _affID;
            }
        }

        // reload core
        reLoadCore(_pID, _affID, _eth, _eventData_);
    }

    function reLoadXname(bytes32 _affCode, uint256 _eth)
        isActivated()
        isHuman()
        isWithinLimits(_eth)
        public
    {
        // set up our tx event data
        FFEIFDatasets.EventReturns memory _eventData_;

        // fetch player ID
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        uint256 _affID;
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == '' || _affCode == plyr_[_pID].name)
        {
            // use last stored affiliate code
            _affID = plyr_[_pID].laff;

        // if affiliate code was given
        } else {
            // get affiliate ID from aff Code
            _affID = pIDxName_[_affCode];

            // if affID is not the same as previously stored
            if (_affID != plyr_[_pID].laff)
            {
                // update last affiliate
                plyr_[_pID].laff = _affID;
            }
        }

        // reload core
        reLoadCore(_pID, _affID, _eth, _eventData_);
    }

    /**
     * @dev withdraws all of your earnings.
     * -functionhash- 0x3ccfd60b
     */
    function withdraw()
        isActivated()
        isHuman()
        public
    {
        // 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;

        // check to see if round has ended and no one has run endround yet
        if (_now > round_[_rID].end && round_[_rID].ended == false && round_[_rID].plyr != 0)
        {
            // set up our tx event data
            FFEIFDatasets.EventReturns memory _eventData_;

            // end the round (distributes pot)
            round_[_rID].ended = true;
            _eventData_ = endRound(_eventData_);

            // get their earnings
            _eth = withdrawEarnings(_pID);

            // gib moni
            if (_eth > 0)
                plyr_[_pID].addr.transfer(_eth);

            // build event data
            _eventData_.compressedData = _eventData_.compressedData + (_now * 1000000000000000000);
            _eventData_.compressedIDs = _eventData_.compressedIDs + _pID;

            // fire withdraw and distribute event
            emit FOMOEvents.onWithdrawAndDistribute
            (
                msg.sender,
                plyr_[_pID].name,
                _eth,
                _eventData_.compressedData,
                _eventData_.compressedIDs,
                _eventData_.winnerAddr,
                _eventData_.winnerName,
                _eventData_.amountWon,
                _eventData_.newPot,
                _eventData_.tokenAmount,
                _eventData_.genAmount,
                _eventData_.seedAdd
            );

        // in any other situation
        } else {
            // get their earnings
            _eth = withdrawEarnings(_pID);

            // gib moni
            if (_eth > 0)
                plyr_[_pID].addr.transfer(_eth);

            // fire withdraw event
            emit FOMOEvents.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;
        (bool _isNewPlayer, uint256 _affID) = PlayerBook.registerNameXIDFromDapp.value(_paid)(_addr, _name, _affCode, _all);

        uint256 _pID = pIDxAddr_[_addr];

        // fire event
        emit FOMOEvents.onNewName(_pID, _addr, _name, _isNewPlayer, _affID, plyr_[_affID].addr, plyr_[_affID].name, _paid, now);
    }

    function registerNameXaddr(string _nameString, address _affCode, bool _all)
        isHuman()
        public
        payable
    {
        bytes32 _name = _nameString.nameFilter();
        address _addr = msg.sender;
        uint256 _paid = msg.value;
        (bool _isNewPlayer, uint256 _affID) = PlayerBook.registerNameXaddrFromDapp.value(msg.value)(msg.sender, _name, _affCode, _all);

        uint256 _pID = pIDxAddr_[_addr];

        // fire event
        emit FOMOEvents.onNewName(_pID, _addr, _name, _isNewPlayer, _affID, plyr_[_affID].addr, plyr_[_affID].name, _paid, now);
    }

    function registerNameXname(string _nameString, bytes32 _affCode, bool _all)
        isHuman()
        public
        payable
    {
        bytes32 _name = _nameString.nameFilter();
        address _addr = msg.sender;
        uint256 _paid = msg.value;
        (bool _isNewPlayer, uint256 _affID) = PlayerBook.registerNameXnameFromDapp.value(msg.value)(msg.sender, _name, _affCode, _all);

        uint256 _pID = pIDxAddr_[_addr];

        // fire event
        emit FOMOEvents.onNewName(_pID, _addr, _name, _isNewPlayer, _affID, plyr_[_affID].addr, plyr_[_affID].name, _paid, now);
    }
//==============================================================================
//     _  _ _|__|_ _  _ _  .
//    (_|(/_ |  | (/_| _\  . (for UI & viewing things on etherscan)
//=====_|=======================================================================
    /**
     * @dev return the price buyer will pay for next 1 individual FFEIF.
     * -functionhash- 0x018a25e8
     * @return price for next FFEIF bought (in wei format)
     */
    function getBuyPrice()
        public
        view
        returns(uint256)
    {
        //starting key/FFEIF price
        uint256 _startingPrice = 75000000000000;
        if (linearPrice != 0) _startingPrice = linearPrice;
        
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;

        // are we in a round?
        if (_now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0)))
            return ( ethRec((round_[_rID].keys.add(1000000000000000000)),1000000000000000000) );
        else // rounds over.  need price for new round
            return ( _startingPrice ); // init
    }

    /**
     * @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)
    {
        // setup local rID
        uint256 _rID = rID_;

        // if round has ended.  but endround has not been run (so contract has not distributed winnings)
        if (now > round_[_rID].end && round_[_rID].ended == false && round_[_rID].plyr != 0)
        {
            // if player is winner
            if (round_[_rID].plyr == _pID)
            {
                return
                (
                    (plyr_[_pID].win).add( ((round_[_rID].pot).mul(winnerPercentage)) / 100 ),
                    (plyr_[_pID].gen).add(  getPlayerVaultsHelper(_pID, _rID).sub(plyrRnds_[_pID][_rID].mask)   ),
                    plyr_[_pID].aff
                );
            // if player is not the winner
            } else {
                return
                (
                    plyr_[_pID].win,
                    (plyr_[_pID].gen).add(  getPlayerVaultsHelper(_pID, _rID).sub(plyrRnds_[_pID][_rID].mask)  ),
                    plyr_[_pID].aff
                );
            }

        // if round is still going on, or round has ended and endround has been ran
        } else {
            return
            (
                plyr_[_pID].win,
                (plyr_[_pID].gen).add(calcUnMaskedEarnings(_pID, plyr_[_pID].lrnd)),
                plyr_[_pID].aff
            );
        }
    }

    /**
     * solidity hates stack limits.  this lets us avoid that hate
     */
    function getPlayerVaultsHelper(uint256 _pID, uint256 _rID)
        private
        view
        returns(uint256)
    {
        return(  ((((round_[_rID].mask).add(((((round_[_rID].pot).mul(potSplit_[round_[_rID].team].gen)) / 100).mul(1000000000000000000)) / (round_[_rID].keys))).mul(plyrRnds_[_pID][_rID].keys)) / 1000000000000000000)  );
    }

    /**
     * @dev returns all current round info needed for front end
     * -functionhash- 0x747dff42
     * @return eth invested during ICO phase
     * @return round id
     * @return total FFEIF 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 team eth in for round
     */
    function getCurrentRoundInfo()
        public
        view
        returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, address, bytes32, 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
            
        );
    }

    /**
     * @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 FFEIF 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)
    {
        // setup local rID
        uint256 _rID = rID_;

        if (_addr == address(0))
        {
            _addr = msg.sender;
        }
        uint256 _pID = pIDxAddr_[_addr];

        return
        (
            _pID,                               //0
            plyr_[_pID].name,                   //1
            plyrRnds_[_pID][_rID].keys,         //2
            plyr_[_pID].win,                    //3
            (plyr_[_pID].gen).add(calcUnMaskedEarnings(_pID, plyr_[_pID].lrnd)),       //4
            plyr_[_pID].aff,                    //5
            plyrRnds_[_pID][_rID].eth           //6
        );
    }

//==============================================================================
//     _ _  _ _   | _  _ . _  .
//    (_(_)| (/_  |(_)(_||(_  . (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, FFEIFDatasets.EventReturns memory _eventData_)
        private
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;

        // if round is active
        if (_now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0)))
        {
            // call core
            core(_rID, _pID, msg.value, _affID, 0, _eventData_);

        // if round is not active
        } else {
            // check to see if endround needs to be run
            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 FOMOEvents.onBuyAndDistribute
                (
                    msg.sender,
                    plyr_[_pID].name,
                    msg.value,
                    _eventData_.compressedData,
                    _eventData_.compressedIDs,
                    _eventData_.winnerAddr,
                    _eventData_.winnerName,
                    _eventData_.amountWon,
                    _eventData_.newPot,
                    _eventData_.tokenAmount,
                    _eventData_.genAmount,
                    _eventData_.seedAdd
                );
            }

            // 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 _eth, FFEIFDatasets.EventReturns memory _eventData_)
        private
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;

        // if round is active
        if (_now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0)))
        {
            // 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).sub(_eth);

            // call core
            core(_rID, _pID, _eth, _affID, 0, _eventData_);

        // 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 FOMOEvents.onReLoadAndDistribute
            (
                msg.sender,
                plyr_[_pID].name,
                _eventData_.compressedData,
                _eventData_.compressedIDs,
                _eventData_.winnerAddr,
                _eventData_.winnerName,
                _eventData_.amountWon,
                _eventData_.newPot,
                _eventData_.tokenAmount,
                _eventData_.genAmount,
                _eventData_.seedAdd
            );
        }
    }

    /**
     * @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, FFEIFDatasets.EventReturns memory _eventData_)
        private
    {
        // if player is new to round
        if (plyrRnds_[_pID][_rID].keys == 0)
            _eventData_ = managePlayer(_pID, _eventData_);

        // early round eth limiter
        if (round_[_rID].eth < earlyRoundLimitUntil && plyrRnds_[_pID][_rID].eth.add(_eth) > earlyRoundLimit)
        {
            uint256 _availableLimit = (earlyRoundLimit).sub(plyrRnds_[_pID][_rID].eth);
            uint256 _refund = _eth.sub(_availableLimit);
            plyr_[_pID].gen = plyr_[_pID].gen.add(_refund);
            _eth = _availableLimit;
        }

        // above minimum amount for normal buys
        if (_eth > 1000000000)
        {

            // mint the new FFEIF
            uint256 _keys = keysRec(round_[_rID].eth,_eth);
            
            // calculate the new multiplier and returns true if new winner (they bought enough)
            bool newWinner = calcMult(_keys, multAllowLast==1 || round_[_rID].plyr != _pID);

            // if they bought at least 1 whole FFEIF
            if (_keys >= 1000000000000000000)
            {
                updateTimer(_keys, _rID);

                if (newWinner) {
                    // set new leaders
                    if (round_[_rID].plyr != _pID)
                    round_[_rID].plyr = _pID;
                    if (round_[_rID].team != _team)
                    round_[_rID].team = _team;

                    // set the new leader bool to true
                    _eventData_.compressedData = _eventData_.compressedData + 100;
                }
            }

            // 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][0] = _eth.add(rndTmEth_[_rID][0]);

            // distribute eth
            _eventData_ = distributeExternal(_rID, _pID, _eth, _affID, 0, _eventData_);
            _eventData_ = distributeInternal(_rID, _pID, _eth, 0, _keys, _eventData_);

            // call end tx function to fire end tx event.
            endTx(_pID, 0, _eth, _keys, _eventData_);
        }
    }
//==============================================================================
//     _ _ | _   | _ _|_ _  _ _  .
//    (_(_||(_|_||(_| | (_)| _\  .
//==============================================================================
    /**
     * @dev calculates unmasked earnings (just calculates, does not update mask)
     * @return earnings in wei format
     */
    function calcUnMaskedEarnings(uint256 _pID, uint256 _rIDlast)
        private
        view
        returns(uint256)
    {
        return(  (((round_[_rIDlast].mask).mul(plyrRnds_[_pID][_rIDlast].keys)) / (1000000000000000000)).sub(plyrRnds_[_pID][_rIDlast].mask)  );
    }

    /**
     * @dev returns the amount of FFEIF 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)
    {
        // grab time
        uint256 _now = now;

        // are we in a round?
        if (_now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0)))
            return keysRec(round_[_rID].eth,_eth);
        else // rounds over.  need FFEIF for new round
            return keys(_eth);
    }

    /**
     * @dev returns current eth price for X FFEIF.
     * -functionhash- 0xcf808000
     * @param _keys number of FFEIF 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_;

        // grab time
        uint256 _now = now;

        // are we in a round?
        if (_now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0)))
            return ethRec(round_[_rID].keys.add(_keys),_keys);
        else // rounds over.  need price for new round
            return eth(_keys);
    }
//==============================================================================
//    _|_ _  _ | _  .
//     | (_)(_)|_\  .
//==============================================================================
    /**
     * @dev receives name/player info from names contract
     */
    function receivePlayerInfo(uint256 _pID, address _addr, bytes32 _name, uint256 _laff)
        external
    {
        require (msg.sender == address(PlayerBook));
        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));
        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(FFEIFDatasets.EventReturns memory _eventData_)
        private
        returns (FFEIFDatasets.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 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, FFEIFDatasets.EventReturns memory _eventData_)
        private
        returns (FFEIFDatasets.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);

        // 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(FFEIFDatasets.EventReturns memory _eventData_)
        private
        returns (FFEIFDatasets.EventReturns)
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab our winning player and team id's
        uint256 _winPID = round_[_rID].plyr;
        uint256 _winTID = round_[_rID].team;

        // grab our pot amount
        uint256 _pot = round_[_rID].pot;

        // calculate our winner share, community rewards, gen share,
        // tokenholder share, and amount reserved for next pot
        uint256 _win = _pot.mul(winnerPercentage) / 100;  // to winner
        uint256 _gen = _pot.mul(potSplit_[_winTID].gen) / 100;  // divs to FFEIF buyers
        uint256 _PoEIF = _pot.mul(potSplit_[_winTID].poeif) / 100;  // to PoEIF/EIF smart contracts
        uint256 _res = _pot.sub(_win).sub(_gen).sub(_PoEIF);  // amount for next round


        // 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 our winner
        plyr_[_winPID].win = _win.add(plyr_[_winPID].win);

        // community rewards

        
        
        // send ETH to PoEIF holders and to EIF contract (50% each) 
        address(PoEIFContract).call.value(_PoEIF.sub((_PoEIF / 2)))(bytes4(keccak256("donateDivs()"))); 
        fundEIF = fundEIF.add(_PoEIF / 2);

        // distribute gen portion to FFEIF holders
        round_[_rID].mask = _ppt.add(round_[_rID].mask);
        
        uint256 _actualPot = _res;
        // send appropriate portion of _res to seeding pot
        if (seedRoundEnd==1) {
            // stick half into seedingPot and the remainder is the amount actually added to pot - seedingDivisor default is 2 which means half goes into seedingPot
            _actualPot = _res.sub(_res/seedingDivisor);
            // if seedingThreshold exceeds eth in the round then put all into seeding pot, else just half
            if (seedingThreshold > rndTmEth_[_rID][0]) {seedingPot = seedingPot.add(_res); _actualPot = 0;} else seedingPot = seedingPot.add(_res/seedingDivisor);
        }

        // 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_.tokenAmount = _PoEIF;
        _eventData_.newPot = _actualPot;
        _eventData_.seedAdd = _res - _actualPot;   
             

        // start next round
        setStore("endround",0); // update any config changes
        rID_++;
        _rID++;
        round_[_rID].strt = now;
        round_[_rID].end = now.add(rndInit_).add(rndGap_);
        round_[_rID].pot += _actualPot;

        return(_eventData_);
    }

    /**
     * @dev moves any unmasked earnings to gen vault.  updates earnings mask
     */
    function updateGenVault(uint256 _pID, uint256 _rIDlast)
        private
    {
        uint256 _earnings = calcUnMaskedEarnings(_pID, _rIDlast);
        if (_earnings > 0)
        {
            // put in gen vault
            plyr_[_pID].gen = _earnings.add(plyr_[_pID].gen);
            // zero out their earnings by updating mask
            plyrRnds_[_pID][_rIDlast].mask = _earnings.add(plyrRnds_[_pID][_rIDlast].mask);
        }
    }

    /**
     * @dev updates round timer based on number of whole FFEIF bought.
     */
    function updateTimer(uint256 _keys, uint256 _rID)
        private
    {
        // grab time
        uint256 _now = now;

        // calculate time based on number of FFEIF bought
        uint256 _newTime;
        if (_now > round_[_rID].end && round_[_rID].plyr == 0)
            _newTime = (((_keys) / (1000000000000000000)).mul(rndInc_)/rndIncDivisor_).add(_now);
        else
            _newTime = (((_keys) / (1000000000000000000)).mul(rndInc_)/rndIncDivisor_).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 distributes eth based on fees to aff and PoEIF
     */
    function distributeExternal(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _affID, uint256 _team, FFEIFDatasets.EventReturns memory _eventData_)
        private
        returns(FFEIFDatasets.EventReturns)
    {
        uint256 _PoEIF;
     
        // distribute share to affiliate (default 5%)
        uint256 _aff = _eth.mul(affFee) / 100;

        // decide what to do with affiliate share of fees
        // affiliate must not be self, and must have a name registered
        if (_affID != _pID && plyr_[_affID].name != '') {
            plyr_[_affID].aff = _aff.add(plyr_[_affID].aff);
            emit FOMOEvents.onAffiliatePayout(_affID, plyr_[_affID].addr, plyr_[_affID].name, _rID, _pID, _aff, now);
        } else {
            _PoEIF = _aff;
        }

        // pay out poeif
        _PoEIF = _PoEIF.add((_eth.mul(fees_[_team].poeif)) / 100);
        if (_PoEIF > 0)
        {
            // deposit to divies contract
            uint256 _EIFamount = _PoEIF / 2;
            
            address(PoEIFContract).call.value(_PoEIF.sub(_EIFamount))(bytes4(keccak256("donateDivs()")));

            fundEIF = fundEIF.add(_EIFamount);

            // set up event data
            _eventData_.tokenAmount = _PoEIF.add(_eventData_.tokenAmount);
        }

        return(_eventData_);
    }

    /**
     * @dev distributes eth based on fees to gen and pot
     */
    function distributeInternal(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _team, uint256 _keys, FFEIFDatasets.EventReturns memory _eventData_)
        private
        returns(FFEIFDatasets.EventReturns)
    {
        // calculate gen share
        uint256 _gen = _eth.mul(fees_[_team].gen) / 100;

        // update eth balance (eth = eth - (aff share + poeif share))
        _eth = _eth.sub(((_eth.mul(affFee)) / 100).add((_eth.mul(fees_[_team].poeif)) / 100));

        // calculate pot
        uint256 _pot = _eth.sub(_gen);
        // stick half into seedingPot and the remainder is the amount actually added to pot - seedingDivisor default is 2 which means half goes into seedingPot
        uint256 _actualPot = _pot.sub(_pot/seedingDivisor);
        
        // if seedingThreshold exceeds eth in the round then put all into seeding pot, else just half
        if (seedingThreshold > rndTmEth_[_rID][0]) {seedingPot = seedingPot.add(_pot); _actualPot = 0;} else seedingPot = seedingPot.add(_pot/seedingDivisor);

        // distribute gen share (thats what updateMasks() does) and adjust
        // balances for dust.
        uint256 _dust = updateMasks(_rID, _pID, _gen, _keys);
        if (_dust > 0)
            _gen = _gen.sub(_dust);

        // add eth to pot
        round_[_rID].pot = _actualPot.add(_dust).add(round_[_rID].pot);

        // set up event data - pot amount excludes seeding
        _eventData_.genAmount = _gen.add(_eventData_.genAmount);
        _eventData_.potAmount = _actualPot;
        _eventData_.seedAdd = _pot - _actualPot;

        return(_eventData_);
    }

    /**
     * @dev updates masks for round and player when FFEIF are bought
     * @return dust left over
     */
    function updateMasks(uint256 _rID, uint256 _pID, uint256 _gen, uint256 _keys)
        private
        returns(uint256)
    {
        /* MASKING NOTES
            earnings masks are a tricky thing for people to wrap their minds around.
            the basic thing to understand here.  is were going to have a global
            tracker based on profit per share for each round, that increases in
            relevant proportion to the increase in share supply.

            the player will have an additional mask that basically says "based
            on the rounds mask, my shares, and how much i've already withdrawn,
            how much is still owed to me?"
        */

        // calc profit per FFEIF & round mask based on this buy:  (dust goes to pot)
        uint256 _ppt = (_gen.mul(1000000000000000000)) / (round_[_rID].keys);
        round_[_rID].mask = _ppt.add(round_[_rID].mask);

        // calculate player earning from their own buy (only based on the FFEIF
        // they just bought).  & update player earnings mask
        uint256 _pearn = (_ppt.mul(_keys)) / (1000000000000000000);
        plyrRnds_[_pID][_rID].mask = (((round_[_rID].mask.mul(_keys)) / (1000000000000000000)).sub(_pearn)).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)
        private
        returns(uint256)
    {
        // update gen vault
        updateGenVault(_pID, plyr_[_pID].lrnd);

        // from vaults
        uint256 _earnings = (plyr_[_pID].win).add(plyr_[_pID].gen).add(plyr_[_pID].aff);
        if (_earnings > 0)
        {
            plyr_[_pID].win = 0;
            plyr_[_pID].gen = 0;
            plyr_[_pID].aff = 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, FFEIFDatasets.EventReturns memory _eventData_)
        private
    {
        _eventData_.compressedData = _eventData_.compressedData + (now * 1000000000000000000) + (_team * 100000000000000000000000000000);
        _eventData_.compressedIDs = _eventData_.compressedIDs + _pID + (rID_ * 10000000000000000000000000000000000000000000000000000);

       emit FOMOEvents.onEndTx
        (
            _eventData_.compressedData,
            _eventData_.compressedIDs,
            plyr_[_pID].name,
            msg.sender,
            _eth,
            _keys,
            _eventData_.winnerAddr,
            _eventData_.winnerName,
            _eventData_.amountWon,
            _eventData_.newPot,
            _eventData_.tokenAmount,
            _eventData_.genAmount,
            _eventData_.potAmount,
            _eventData_.seedAdd
        );
    }
//==============================================================================
//    (~ _  _    _._|_    .
//    _)(/_(_|_|| | | \/  .
//====================/=========================================================
    /** 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()
        public
    {
        // only team just can activate
        require(msg.sender == admin, "Only admin can activate");


        // can only be ran once
        require(activated_ == false, "FFEIF already activated");

        // update any configuration changes
        setStore("endround",0);
        
        // activate the contract
        activated_ = true;

        // lets start first round
        rID_ = 1;
            round_[1].strt = now + rndExtra_ - rndGap_;
            round_[1].end = now + rndInit_ + rndExtra_;
            
        //set potNextSeedTime to current block timestamp plus one hour 
        potNextSeedTime = now + 3600;
    }
    
    
    function removeAdmin()   //stops any further updates happening to make it completely autonomous
        public
    {
        require(msg.sender == admin, "Only admin can remove himself");
        admin =  address(0);  // dummy 0x000... address 
    }
    
    
}
 
//==============================================================================
//   __|_ _    __|_ _  .
//  _\ | | |_|(_ | _\  .
//==============================================================================
library FFEIFDatasets {
    
    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 tokenAmount;        // amount distributed to PoEIF tokenholders and EIF
        uint256 genAmount;          // amount distributed to gen
        uint256 potAmount;          // amount added to pot
        uint256 seedAdd;            // amount added to seedingPot
        
    }
    struct Player {
        address addr;   // player address
        bytes32 name;   // player name
        uint256 win;    // winnings vault
        uint256 gen;    // general vault
        uint256 aff;    // affiliate vault
        uint256 lrnd;   // last round played
        uint256 laff;   // last affiliate id used
    }
    struct PlayerRounds {
        uint256 eth;    // eth player has added to round (used for eth limiter)
        uint256 keys;   // FFEIF/keys
        uint256 mask;   // player mask
        uint256 ico;    // ICO phase investment
    }
    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;   // FFEIF/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 FFEIF price for ICO phase
    }
    struct TeamFee {
        uint256 gen;    // % of buy in thats paid to FFEIF holders of current round
        uint256 poeif;  // % of buy in thats paid to PoEIF holders and EIF
    }
    struct PotSplit {
        uint256 gen;    // % of pot thats paid to FFEIF holders of current round
        uint256 poeif;  // % of pot thats paid to PoEIF holders and EIF
    }
}




//==============================================================================
//  . _ _|_ _  _ |` _  _ _  _  .
//  || | | (/_| ~|~(_|(_(/__\  .
//==============================================================================

//Define the PoEIF token for the sending 5% divs
contract PoEIF 
{
    function donateDivs() public payable;
}

interface PlayerBookInterface {
    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);
}


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);
        // make sure it doesnt start with or end with space
        require(_temp[0] != 0x20 && _temp[_length-1] != 0x20);
        // make sure first two characters are not 0x
        if (_temp[0] == 0x30)
        {
            require(_temp[1] != 0x78);
            require(_temp[1] != 0x58);
        }

        // 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));
                // make sure theres not 2x spaces in a row
                if (_temp[i] == 0x20)
                    require( _temp[i+1] != 0x20);

                // see if we have a character other than a number
                if (_hasNonNumber == false && (_temp[i] < 0x30 || _temp[i] > 0x39))
                    _hasNonNumber = true;
            }
        }

        require(_hasNonNumber == true);

        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);
        }
    }
}