// Contract: CryptagionLaboratory
// Target function: function collectDonations(address payable _shareholder1,address payable _shareholder2,uint _divamount) external onlyOwner
        //emit the event 	
		emit NewEthogen(newNftTokenId, _dtypeID, _rarity);
    }
    
//mint a specific ethogen
	function mintOwner(address _to, uint16 _dtypeID, uint8 _rarity) external onlyOwner {
    	 require (checkmintcaps(_dtypeID,_rarity), "Cap reached");
		 _mintEthogen(_to, _dtypeID, _rarity);
	}	    
    
//************************* SET FEES ******************************************
//set the upgradefee
	function setUpgradeFee(uint _fee) external onlyOwner {
		upgradefee = _fee;
	}
//set the silver fee
	function setSilverpackFee(uint _fee) external onlyOwner {
		silverpackfee = _fee;
	}
//set the legendary fee
	function setLegendarypackFee(uint _fee) external onlyOwner {
		legendarypackfee = _fee;
	}
//set the sickpack fee
	function setSickpackFee(uint _fee) external onlyOwner {
		sickpackfee = _fee;
	}	
//set the userxpgainbase
	function setUserXPgainBase(uint16 _newval) external onlyOwner {
		userxpgainbase = _newval;
	}	
//set the sickbonusbase
	function setSickBonusBase(uint16 _newval) external onlyOwner {
		sickbonusbase = _newval;
	}	
//set the sickfeebase
	function setSickFeeBase(uint16 _newval) external onlyOwner {
		sickfeebase = _newval;
	}	
//set the sickburndivisor
	function setSickBurnDivisor(uint8 _newval) external onlyOwner {
		sickburndivisor = _newval;
	}	
//set the xpDivisor
	function setXpDivisor(uint16 _newval) external onlyOwner {
		xpDivisor = _newval;
	}	

//************************* PAYABLES ******************************************
//buy a pack
	function buyCardPack(uint8 _packtype) external payable {
		require((msg.value == silverpackfee || msg.value == 10 * silverpackfee || msg.value == 100 * silverpackfee || msg.value == sickpackfee || msg.value == legendarypackfee ) ," Fee must match one listed");

        uint16 sickgain;
        uint16 xpgain;
        
        if(msg.value == silverpackfee && _packtype == 1){
            //first uncom
    		mintRandom(13,2,2);
    		mintRandom(17,1,1);

    		if(founderpackcap1 > 0){
    		    
    		    mintRandom(3,1,1);
    		    sickgain = sickbonusbase * 2;
                xpgain = userxpgainbase * 2;
    		    
    		    founderpackcap1 --;
    		    
    		}
            else{
    		    sickgain = sickbonusbase;
        	    xpgain = userxpgainbase;
            }

        }
        else if(msg.value == silverpackfee * 10 && _packtype == 2){
            //first rare
    		mintRandom(7,3,3);
    		mintRandom(4,2,2);

            if(founderpackcap2 > 0){
                
                mintRandom(13,0,2);
    		    sickgain = sickbonusbase * 5;
        	    xpgain = userxpgainbase * 3;

    		    founderpackcap2 --;
    		}
            else{
                sickgain = sickbonusbase * 2;
                xpgain = userxpgainbase * 2;
            }

        }
        else if(msg.value == silverpackfee * 100 && _packtype == 3){
        
            //first epic
    		mintRandom(24,4,4);
    		mintRandom(12,0,3);

            if(founderpackcap3 > 0){
                
    		    mintRandom(23,0,2);
    		    sickgain = sickbonusbase * 10;
                xpgain = userxpgainbase * 6;
 
    		    founderpackcap3 --;
    		}
            else{
    		    sickgain = sickbonusbase * 5;
                xpgain = userxpgainbase * 4;
            }
        }
        else if(msg.value == sickpackfee && _packtype == 200){
        
    		mintRandom(9,0,2);
    		sickgain = sickbonusbase * 10;
            xpgain = userxpgainbase * 2;
        }
        else if(msg.value == legendarypackfee && _packtype == 250){
        
    		mintRandom(7,5,5);
      		mintRandom(11,4,4);
    		mintRandom(5,3,3);

    		sickgain = sickbonusbase * 20;
            xpgain = userxpgainbase * 10;
        }
        else if(msg.value == legendarypackfee && _packtype == 253){
        
    		mintRandom(13,5,5);
    		sickgain = sickbonusbase * 10;
            xpgain = userxpgainbase * 5;
        }
        else{
             revert("Wrong Amount ");
        }
        //give Sick, burn Sick, gain XP
  		sicktoken.transfer(msg.sender,sickgain * DEC18);
		sicktoken.burn((sickgain * DEC18 ) / sickburndivisor);
    	useraddxp(uint16(xpgain));
    }

//*************************  SICK PAYABLE ******************************************
// buy user xp with sicktoken
	function buyXPwithSick(uint16 _sicktokens) external {
    
        require (_sicktokens > 0 && _sicktokens <= 32000 && (users[msg.sender].xp + _sicktokens) <= 32000 , "No_Max SICK");
    
        sicktoken.transferFrom(address(msg.sender), address(this), _sicktokens * DEC18);
        useraddxp(_sicktokens);
    }

// buy small pack with SICK
	function buyPackwithSick(uint8 _pack) external {
            
            uint16 xpgain;
            uint16 sickfee;
            
            if(_pack == 1){
                
                sickfee = sickfeebase;
                xpgain = sickfeebase/2;
    
    		    mintRandom(14,0,2);
            }
            else{
                sickfee = sickfeebase * 10;
                xpgain = sickfeebase * 5;
                
                mintRandom(12,4,4);
            }

           sicktoken.transferFrom(address(msg.sender), address(this), sickfee * DEC18);
           useraddxp(xpgain);
           mintRandom(13,2,2);
    }

// mutate with SICK
	function mutate(uint _ethogenID, uint16 _rand) external onlyOwnerOf(_ethogenID){

            sicktoken.transferFrom(address(msg.sender), address(this), sickfeebase * 5 * DEC18);
    		sicktoken.burn((sickfeebase * DEC18) / sickburndivisor);
        	useraddxp(sickfeebase);
           
            ethogenstats[_ethogenID].mutation = uint8(uint(keccak256(abi.encodePacked(block.number + _tokenIdTracker.current() + _rand, msg.sender))) % 255  + 1);
     }

//************************* EXTERNAL ETHOGEN UPDATES ******************************************

// boost an ethogen with SickToken
	function boostStat(uint _ethogenID,uint8 _sicktokens,uint8 _stat) external onlyOwnerOf(_ethogenID){
    
        uint8 finalfee;

        if (_stat == 1){
             if(uint16(ethogenstats[_ethogenID].boostinfect) + uint16(_sicktokens) > 255){
                
                 finalfee = uint8(255 - ethogenstats[_ethogenID].boostinfect);
                 ethogenstats[_ethogenID].boostinfect  = 255;
             }
             else{
                 ethogenstats[_ethogenID].boostinfect  = uint8(ethogenstats[_ethogenID].boostinfect + _sicktokens);
                 finalfee = _sicktokens;
             }  
        }
        else if (_stat == 2){
              if(uint16(ethogenstats[_ethogenID].boostmort) + uint16(_sicktokens) > 255){
                 finalfee = uint8(255 - ethogenstats[_ethogenID].boostmort);
                 ethogenstats[_ethogenID].boostmort  = 255;
             }
             else{
                 ethogenstats[_ethogenID].boostmort  = uint8(ethogenstats[_ethogenID].boostmort + _sicktokens);
                 finalfee = _sicktokens;
             }  
        }
        else if (_stat == 3){
             if(uint16(ethogenstats[_ethogenID].boostresist) + uint16(_sicktokens) > 255){
                 finalfee = uint8(255 - ethogenstats[_ethogenID].boostresist);
                 ethogenstats[_ethogenID].boostresist  = 255;
             }
             else{
                 ethogenstats[_ethogenID].boostresist  = uint8(ethogenstats[_ethogenID].boostresist + _sicktokens);
                 finalfee = _sicktokens;
             }  
        }
        else if (_stat == 4){
              if(uint16(ethogenstats[_ethogenID].booststealth) + uint16(_sicktokens) > 255){
                 finalfee = uint8(255 - ethogenstats[_ethogenID].booststealth);
                 ethogenstats[_ethogenID].booststealth  = 255;
             }
             else{
                 ethogenstats[_ethogenID].booststealth  = uint8(ethogenstats[_ethogenID].booststealth + _sicktokens);
                 finalfee = _sicktokens;
             }  
        }
        //take some SICK for the boost
        if(finalfee > 0){sicktoken.transferFrom(address(msg.sender), address(this), finalfee * DEC18);}
    }

//************************* INCUBATE ******************************************
//lock token in incubator for upgrade fee
	function incubatein1(uint _ethogenID) external payable onlyOwnerOf(_ethogenID){
		
		require (users[msg.sender].incubating1 == 0 , " Full");
		require(msg.value == upgradefee);
		
	    users[msg.sender].incubating1date = uint(block.timestamp);
        users[msg.sender].incubating1 = _ethogenID;

	}
	function incubatein2(uint _ethogenID) external payable onlyOwnerOf(_ethogenID){
		
		require (users[msg.sender].incubating2 == 0 , " Full");
		require (users[msg.sender].level >= 50 , " Level 50");
		require(msg.value == upgradefee);
		
	    users[msg.sender].incubating2date = uint(block.timestamp);
        users[msg.sender].incubating2 = _ethogenID;

	}
	function incubatein3(uint _ethogenID) external payable onlyOwnerOf(_ethogenID){
		
		require (users[msg.sender].incubating3 == 0 , " Full");
		require(msg.value == upgradefee*3);
		
	    users[msg.sender].incubating3date = uint(block.timestamp);
        users[msg.sender].incubating3 = _ethogenID;
	}

//take out of incubator
	function incubateout(uint8 _whichone) external {
		
		uint ethogenID;
		uint16 xpgained;
		
		if(_whichone == 1){
		    if(users[msg.sender].incubating1 == 0){revert("Empty");}
		    ethogenID =  users[msg.sender].incubating1;
		    users[msg.sender].incubating1 = 0;
            xpgained = uint16((block.timestamp - users[msg.sender].incubating1date)/xpDivisor); 
		}	
		else if(_whichone == 2){
		    if(users[msg.sender].incubating2 == 0){revert("Empty");}
		    ethogenID =  users[msg.sender].incubating2;
		    users[msg.sender].incubating2 = 0;
            xpgained = uint16((block.timestamp - users[msg.sender].incubating2date)/xpDivisor); 
		}	
		else if(_whichone == 3){
		    if(users[msg.sender].incubating3 == 0){revert("Empty");}
		    ethogenID =  users[msg.sender].incubating3;
		    users[msg.sender].incubating3 = 0;
            xpgained = uint16((block.timestamp - users[msg.sender].incubating3date) * 3 /xpDivisor); 
		}	

        if(ownerOf(ethogenID) == msg.sender){
            addxp(ethogenID,xpgained);
        }	
    }

//*************************  XP AND LEVELS ******************************************
//add xp check Max
	function addxp(uint _ethogenID,uint16 _xp) private onlyOwnerOf(_ethogenID){
        
        if(_xp > 32000){_xp=32000;}
        if(ethogens[_ethogenID].xp + _xp > 32000){
            ethogens[_ethogenID].xp = 32000;
        }
        else{
            ethogens[_ethogenID].xp += _xp;
        }

	    uint8 newlevel = calclevel(ethogens[_ethogenID].xp);
	    uint8 newmod = uint8(5 + (newlevel * 2) + (10 *ethogens[_ethogenID].rarity));
	    
	    ethogens[_ethogenID].level = newlevel;

        uint8 debuff = uint8(newlevel/4);
        uint8 debuff2 = uint8(newlevel/8);

        ethogenstats[_ethogenID].modmort = newmod;
        ethogenstats[_ethogenID].modinfect = newmod;
        ethogenstats[_ethogenID].modstealth = newmod;
        ethogenstats[_ethogenID].modresist = newmod;

	    if (dtypes[ethogens[_ethogenID].dtype].classtype < 20){ ethogenstats[_ethogenID].modmort = newmod - 1;ethogenstats[_ethogenID].modstealth = newmod - debuff2;}
        else if (dtypes[ethogens[_ethogenID].dtype].classtype < 30){ethogenstats[_ethogenID].modinfect = newmod - debuff;ethogenstats[_ethogenID].modresist = newmod - 1;}
        else if (dtypes[ethogens[_ethogenID].dtype].classtype < 40){ethogenstats[_ethogenID].modstealth = newmod - debuff;ethogenstats[_ethogenID].modinfect = newmod - 1;}
        else if (dtypes[ethogens[_ethogenID].dtype].classtype < 50){ethogenstats[_ethogenID].modresist = newmod - debuff;ethogenstats[_ethogenID].modmort = newmod - debuff2;}
        else if (dtypes[ethogens[_ethogenID].dtype].classtype < 60){ethogenstats[_ethogenID].modresist = newmod - 1;ethogenstats[_ethogenID].modinfect = newmod - debuff2;}
        else if (dtypes[ethogens[_ethogenID].dtype].classtype < 70){ethogenstats[_ethogenID].modmort = newmod - debuff;ethogenstats[_ethogenID].modresist = newmod - debuff2;}
    }

//add xp check Max
	function useraddxp(uint16 _xp) private {
        
        if(_xp > 32000){_xp=32000;}
         //max xp 32k
        if(users[msg.sender].xp + _xp > 32000){
            users[msg.sender].xp = 32000;
        }
        else{
             users[msg.sender].xp += _xp; 
        }
		users[msg.sender].level = calclevel(users[msg.sender].xp);
     }
	
//*************************  GETTERS ******************************************
//showfees
	function getfees() external view returns (uint,uint,uint,uint) {
		return(upgradefee,silverpackfee,sickpackfee,legendarypackfee);
	}
//show founder caps
	function getfoundercaps() external view returns (uint16,uint8,uint8) {
		return(founderpackcap1,founderpackcap2,founderpackcap3);
	}
//show bases
	function getbasenumbers() external view returns (uint16,uint16,uint16,uint8,uint16) {
		return(userxpgainbase,sickbonusbase,sickfeebase,sickburndivisor,xpDivisor);
	}

//************************* PRIVATE ******************************************
//check if cap is reached
    function checkmintcaps(uint16 _dtypeID,uint8 _rarity) private view returns (bool){
        
        if(_rarity == 1 && dtypes[_dtypeID].raritycap1<=0){return false;}
        if(_rarity == 2 && dtypes[_dtypeID].raritycap2<=0){return false;}
        if(_rarity == 3 && dtypes[_dtypeID].raritycap3<=0){return false;}
        if(_rarity == 4 && dtypes[_dtypeID].raritycap4<=0){return false;}
        if(_rarity == 5 && dtypes[_dtypeID].raritycap5<=0){return false;}

        return true;
    }
//square root yo
    function sqrt(uint x) internal pure returns (uint y) {
        uint z = (x + 1) / 2;
        y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
    }   
//get level from xp
   function calclevel(uint16 _xp) internal pure returns (uint8) {
       return uint8(sqrt(5*uint32(_xp)) / 4);
   }