// Contract: CryptagionLaboratory
// Target function: function incubateout(uint8 _whichone) external
baseresist, uint8 _basestealth, uint8 _classtype,uint8 _legendarycap) public onlyOwner{

    	require (_legendarycap <=20, " Max is 20");
    	require (_basemort <=maxBaseStat && _baseinfect <=maxBaseStat && _basestealth <=maxBaseStat && _baseresist <=maxBaseStat, " Max Base Stat");
	
	    //proportion of mint caps in relation to legendary
        uint16 commoncap = uint16(_legendarycap) * 150;
	    uint16 uncommoncap = uint16(_legendarycap) * 70;
		uint16 rarecap = uint16(_legendarycap) * 20;
		uint8 epiccap = uint8(_legendarycap) * 5;
	
        dtypes.push(dtype(commoncap,uncommoncap,rarecap,epiccap,_legendarycap,_baseinfect,_basemort,_baseresist,  _basestealth, _classtype,_legendarycap));
	}
//overwrite ethogen type in case of f**k ups
	function overwriteType (uint8 _baseinfect, uint8 _basemort, uint8 _baseresist, uint8 _basestealth, uint8 _classtype,uint _overwriteindex) external onlyOwner{

       require (_basemort <=maxBaseStat && _baseinfect <=maxBaseStat && _basestealth <=maxBaseStat && _baseresist <=maxBaseStat, " Max Base Stat");

       dtypes[_overwriteindex].baseresist = _baseresist;
       dtypes[_overwriteindex].basestealth = _basestealth;
       dtypes[_overwriteindex].baseinfect = _baseinfect;
       dtypes[_overwriteindex].basemort = _basemort;
       dtypes[_overwriteindex].classtype = _classtype;
	}

//************************* THE MINT ******************************************
//mint random card with weighted rarity chances if _raritymin is 0, otherwise set the rarity
	function mintRandom(uint _randomadd,uint8 _raritymin, uint8 _raritymax) private {

        uint randNonce = 3 + _randomadd;
        uint16 randomtype;
    	uint8 randomweight;
        uint8 finalrarity;

        //200 tries to get one that's not capped out.. just prevent endless loop
        while (randNonce < _randomadd + 200){
            
    	    randomtype = uint16(uint(keccak256(abi.encodePacked(block.number, msg.sender, randNonce))) % dtypes.length );
            randNonce++;

    	    if(_raritymin == 0){
        	    
        	    randomweight = uint8(uint(keccak256(abi.encodePacked(block.number + _tokenIdTracker.current() , msg.sender, randNonce))) % 100  + 1);

                if(_raritymax == 2)
                {
                    if (randomweight < 66){finalrarity = 1;}
                    else {finalrarity = 2;}
                    
                }
                else if(_raritymax == 3)
                {
                    if (randomweight < 60){finalrarity = 1;}
                    else if (randomweight < 90){finalrarity = 2;}
                    else {finalrarity = 3;}
                }
    	    }
            else{
                finalrarity = _raritymin;
            }
    	    //particular mintcap?
    	    if(checkmintcaps(randomtype,finalrarity)){
    	             _mintEthogen(msg.sender, randomtype, finalrarity);
    	            return;
    	    }
            randNonce++;
        }
        //massive fail       
        revert("No Cap, Try again")  ;
	}

//mint new ethogen
	function _mintEthogen(address _to, uint16 _dtypeID, uint8 _rarity) private {

	    uint16 tmpfactor;

        //set base stats based on rarity
		if(_rarity == 1){tmpfactor = 1000;}
		else if(_rarity == 2){tmpfactor = 1030;}
		else if(_rarity == 3){tmpfactor = 1100;}
		else if(_rarity == 4){tmpfactor = 1180;}
		else if(_rarity == 5){tmpfactor = 1275;}
		else {revert("Bad Rarity");}

        //the real minting
        uint256 newNftTokenId = _tokenIdTracker.current();
       
        _mint(_to, newNftTokenId);
  
        //percents increase based on rarity
        uint8 finalinfect =  uint8((uint32(dtypes[_dtypeID].baseinfect) * uint32(tmpfactor))/1000);
        uint8 finalmort =  uint8((uint32(dtypes[_dtypeID].basemort) * uint32(tmpfactor))/1000);
        uint8 finalresist =  uint8((uint32(dtypes[_dtypeID].baseresist) * uint32(tmpfactor))/1000);
        uint8 finalstealth =  uint8((uint32(dtypes[_dtypeID].basestealth) * uint32(tmpfactor))/1000);
        
        //main Ethogen
        ethogens.push(Ethogen(block.timestamp,block.number,_dtypeID, 0, _rarity, 0,finalinfect,finalmort,finalresist,finalstealth));

        //random shizen
        uint8 randommutation = uint8(uint(keccak256(abi.encodePacked(block.number + _tokenIdTracker.current() + 11, msg.sender))) % 255  + 1);
        uint8 randomtrait = uint8(uint(keccak256(abi.encodePacked(block.timestamp + _tokenIdTracker.current() + 5, msg.sender))) % 255  + 1);
        uint8 randomspecial = uint8(uint(keccak256(abi.encodePacked(block.timestamp + _tokenIdTracker.current() + 3, msg.sender))) % 255  + 1);

        //EthogenStats 1:1 with Ethogens
        ethogenstats.push(EthogenStats(1,1,1,1,0,0,0,0,randommutation,randomtrait,randomspecial));

        _tokenIdTracker.increment();
		
        //decrement specific value in mintcaps mapping and raritycaps array
		if(_rarity == 1){dtypes[_dtypeID].raritycap1--;}
		if(_rarity == 2){dtypes[_dtypeID].raritycap2--;}
		if(_rarity == 3){dtypes[_dtypeID].raritycap3--;}
		if(_rarity == 4){dtypes[_dtypeID].raritycap4--;}
		if(_rarity == 5){dtypes[_dtypeID].raritycap5--;}

        //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;
	}