library CryptoUtils {
   
    /* CLASSES */
    uint256 internal constant WARRIOR = 0;
    uint256 internal constant ARCHER = 1;
    uint256 internal constant MAGE = 2;
    /* RARITIES */
    uint256 internal constant COMMON = 1;
    uint256 internal constant UNCOMMON = 2;
    uint256 internal constant RARE = 3;
    uint256 internal constant MYTHIC = 4;
    uint256 internal constant LEGENDARY = 5;
    uint256 internal constant UNIQUE = 6;
    /* LIMITS */
    uint256 internal constant CLASS_MECHANICS_MAX = 3;
    uint256 internal constant RARITY_MAX = 6;
    /*@dev range used for rarity chance computation */
    uint256 internal constant RARITY_CHANCE_RANGE = 10000000;
    uint256 internal constant POINTS_TO_LEVEL = 10;
    /* ATTRIBUTE MASKS */
    /*@dev range 0-9999 */
    uint256 internal constant UNIQUE_MASK_0 = 1;
    /*@dev range 0-9 */
    uint256 internal constant RARITY_MASK_1 = UNIQUE_MASK_0 * 10000;
    /*@dev range 0-999 */
    uint256 internal constant CLASS_VIEW_MASK_2 = RARITY_MASK_1 * 10;
    /*@dev range 0-999 */
    uint256 internal constant BODY_COLOR_MASK_3 = CLASS_VIEW_MASK_2 * 1000;
    /*@dev range 0-999 */
    uint256 internal constant EYES_MASK_4 = BODY_COLOR_MASK_3 * 1000;
    /*@dev range 0-999 */
    uint256 internal constant MOUTH_MASK_5 = EYES_MASK_4 * 1000;
    /*@dev range 0-999 */
    uint256 internal constant HEIR_MASK_6 = MOUTH_MASK_5 * 1000;
    /*@dev range 0-999 */
    uint256 internal constant HEIR_COLOR_MASK_7 = HEIR_MASK_6 * 1000;
    /*@dev range 0-999 */
    uint256 internal constant ARMOR_MASK_8 = HEIR_COLOR_MASK_7 * 1000;
    /*@dev range 0-999 */
    uint256 internal constant WEAPON_MASK_9 = ARMOR_MASK_8 * 1000;
    /*@dev range 0-999 */
    uint256 internal constant HAT_MASK_10 = WEAPON_MASK_9 * 1000;
    /*@dev range 0-99 */
    uint256 internal constant RUNES_MASK_11 = HAT_MASK_10 * 1000;
    /*@dev range 0-99 */
    uint256 internal constant WINGS_MASK_12 = RUNES_MASK_11 * 100;
    /*@dev range 0-99 */
    uint256 internal constant PET_MASK_13 = WINGS_MASK_12 * 100;
    /*@dev range 0-99 */
    uint256 internal constant BORDER_MASK_14 = PET_MASK_13 * 100;
    /*@dev range 0-99 */
    uint256 internal constant BACKGROUND_MASK_15 = BORDER_MASK_14 * 100;
    /*@dev range 0-99 */
    uint256 internal constant INTELLIGENCE_MASK_16 = BACKGROUND_MASK_15 * 100;
    /*@dev range 0-99 */
    uint256 internal constant AGILITY_MASK_17 = INTELLIGENCE_MASK_16 * 100;
    /*@dev range 0-99 */
    uint256 internal constant STRENGTH_MASK_18 = AGILITY_MASK_17 * 100;
    /*@dev range 0-9 */
    uint256 internal constant CLASS_MECH_MASK_19 = STRENGTH_MASK_18 * 100;
    /*@dev range 0-999 */
    uint256 internal constant RARITY_BONUS_MASK_20 = CLASS_MECH_MASK_19 * 10;
    /*@dev range 0-9 */
    uint256 internal constant SPECIALITY_MASK_21 = RARITY_BONUS_MASK_20 * 1000;
    /*@dev range 0-99 */
    uint256 internal constant DAMAGE_MASK_22 = SPECIALITY_MASK_21 * 10;
    /*@dev range 0-99 */
    uint256 internal constant AURA_MASK_23 = DAMAGE_MASK_22 * 100;
    /*@dev 20 decimals left */
    uint256 internal constant BASE_MASK_24 = AURA_MASK_23 * 100;
    
    
    /* SPECIAL PERKS */
    uint256 internal constant MINER_PERK = 1;
    
    
    /* PARAM INDEXES */
    uint256 internal constant BODY_COLOR_MAX_INDEX_0 = 0;
    uint256 internal constant EYES_MAX_INDEX_1 = 1;
    uint256 internal constant MOUTH_MAX_2 = 2;
    uint256 internal constant HAIR_MAX_3 = 3;
    uint256 internal constant HEIR_COLOR_MAX_4 = 4;
    uint256 internal constant ARMOR_MAX_5 = 5;
    uint256 internal constant WEAPON_MAX_6 = 6;
    uint256 internal constant HAT_MAX_7 = 7;
    uint256 internal constant RUNES_MAX_8 = 8;
    uint256 internal constant WINGS_MAX_9 = 9;
    uint256 internal constant PET_MAX_10 = 10;
    uint256 internal constant BORDER_MAX_11 = 11;
    uint256 internal constant BACKGROUND_MAX_12 = 12;
    uint256 internal constant UNIQUE_INDEX_13 = 13;
    uint256 internal constant LEGENDARY_INDEX_14 = 14;
    uint256 internal constant MYTHIC_INDEX_15 = 15;
    uint256 internal constant RARE_INDEX_16 = 16;
    uint256 internal constant UNCOMMON_INDEX_17 = 17;
    uint256 internal constant UNIQUE_TOTAL_INDEX_18 = 18;
    
     /* PACK PVP DATA LOGIC */
    //pvp data
    uint256 internal constant CLASS_PACK_0 = 1;
    uint256 internal constant RARITY_BONUS_PACK_1 = CLASS_PACK_0 * 10;
    uint256 internal constant RARITY_PACK_2 = RARITY_BONUS_PACK_1 * 1000;
    uint256 internal constant EXPERIENCE_PACK_3 = RARITY_PACK_2 * 10;
    uint256 internal constant INTELLIGENCE_PACK_4 = EXPERIENCE_PACK_3 * 1000;
    uint256 internal constant AGILITY_PACK_5 = INTELLIGENCE_PACK_4 * 100;
    uint256 internal constant STRENGTH_PACK_6 = AGILITY_PACK_5 * 100;
    uint256 internal constant BASE_DAMAGE_PACK_7 = STRENGTH_PACK_6 * 100;
    uint256 internal constant PET_PACK_8 = BASE_DAMAGE_PACK_7 * 100;
    uint256 internal constant AURA_PACK_9 = PET_PACK_8 * 100;
    uint256 internal constant WARRIOR_ID_PACK_10 = AURA_PACK_9 * 100;
    uint256 internal constant PVP_CYCLE_PACK_11 = WARRIOR_ID_PACK_10 * 10**10;
    uint256 internal constant RATING_PACK_12 = PVP_CYCLE_PACK_11 * 10**10;
    uint256 internal constant PVP_BASE_PACK_13 = RATING_PACK_12 * 10**10;//NB rating must be at the END!
    
    //tournament data
    uint256 internal constant HP_PACK_0 = 1;
    uint256 internal constant DAMAGE_PACK_1 = HP_PACK_0 * 10**12;
    uint256 internal constant ARMOR_PACK_2 = DAMAGE_PACK_1 * 10**12;
    uint256 internal constant DODGE_PACK_3 = ARMOR_PACK_2 * 10**12;
    uint256 internal constant PENETRATION_PACK_4 = DODGE_PACK_3 * 10**12;
    uint256 internal constant COMBINE_BASE_PACK_5 = PENETRATION_PACK_4 * 10**12;
    
    /* MISC CONSTANTS */
    uint256 internal constant MAX_ID_SIZE = 10000000000;
    int256 internal constant PRECISION = 1000000;
    
    uint256 internal constant BATTLES_PER_CONTENDER = 10;//10x100
    uint256 internal constant BATTLES_PER_CONTENDER_SUM = BATTLES_PER_CONTENDER * 100;//10x100
    
    uint256 internal constant LEVEL_BONUSES = 98898174676155504541373431282523211917151413121110;
    
    //ucommon bonuses
    uint256 internal constant BONUS_NONE = 0;
    uint256 internal constant BONUS_HP = 1;
    uint256 internal constant BONUS_ARMOR = 2;
    uint256 internal constant BONUS_CRIT_CHANCE = 3;
    uint256 internal constant BONUS_CRIT_MULT = 4;
    uint256 internal constant BONUS_PENETRATION = 5;
    //rare bonuses
    uint256 internal constant BONUS_STR = 6;
    uint256 internal constant BONUS_AGI = 7;
    uint256 internal constant BONUS_INT = 8;
    uint256 internal constant BONUS_DAMAGE = 9;
    
    //bonus value database, 
    uint256 internal constant BONUS_DATA = 16060606140107152000;
    //pets database
    uint256 internal constant PETS_DATA = 287164235573728325842459981692000;
    
    uint256 internal constant PET_AURA = 2;
    uint256 internal constant PET_PARAM_1 = 1;
    uint256 internal constant PET_PARAM_2 = 0;

    /* GETTERS */
	function getUniqueValue(uint256 identity) internal pure returns(uint256){
		return identity % RARITY_MASK_1;
	}

    function getRarityValue(uint256 identity) internal pure returns(uint256){
        return (identity % CLASS_VIEW_MASK_2) / RARITY_MASK_1;
    }

	function getClassViewValue(uint256 identity) internal pure returns(uint256){
		return (identity % BODY_COLOR_MASK_3) / CLASS_VIEW_MASK_2;
	}

	function getBodyColorValue(uint256 identity) internal pure returns(uint256){
        return (identity % EYES_MASK_4) / BODY_COLOR_MASK_3;
    }

    function getEyesValue(uint256 identity) internal pure returns(uint256){
        return (identity % MOUTH_MASK_5) / EYES_MASK_4;
    }

    function getMouthValue(uint256 identity) internal pure returns(uint256){
        return (identity % HEIR_MASK_6) / MOUTH_MASK_5;
    }

    function getHairValue(uint256 identity) internal pure returns(uint256){
        return (identity % HEIR_COLOR_MASK_7) / HEIR_MASK_6;
    }

    function getHairColorValue(uint256 identity) internal pure returns(uint256){
        return (identity % ARMOR_MASK_8) / HEIR_COLOR_MASK_7;
    }

    function getArmorValue(uint256 identity) internal pure returns(uint256){
        return (identity % WEAPON_MASK_9) / ARMOR_MASK_8;
    }

    function getWeaponValue(uint256 identity) internal pure returns(uint256){
        return (identity % HAT_MASK_10) / WEAPON_MASK_9;
    }

    function getHatValue(uint256 identity) internal pure returns(uint256){
        return (identity % RUNES_MASK_11) / HAT_MASK_10;
    }

    function getRunesValue(uint256 identity) internal pure returns(uint256){
        return (identity % WINGS_MASK_12) / RUNES_MASK_11;
    }

    function getWingsValue(uint256 identity) internal pure returns(uint256){
        return (identity % PET_MASK_13) / WINGS_MASK_12;
    }

    function getPetValue(uint256 identity) internal pure returns(uint256){
        return (identity % BORDER_MASK_14) / PET_MASK_13;
    }

	function getBorderValue(uint256 identity) internal pure returns(uint256){
		return (identity % BACKGROUND_MASK_15) / BORDER_MASK_14;
	}

	function getBackgroundValue(uint256 identity) internal pure returns(uint256){
		return (identity % INTELLIGENCE_MASK_16) / BACKGROUND_MASK_15;
	}

    function getIntelligenceValue(uint256 identity) internal pure returns(uint256){
        return (identity % AGILITY_MASK_17) / INTELLIGENCE_MASK_16;
    }

    function getAgilityValue(uint256 identity) internal pure returns(uint256){
        return ((identity % STRENGTH_MASK_18) / AGILITY_MASK_17);
    }

    function getStrengthValue(uint256 identity) internal pure returns(uint256){
        return ((identity % CLASS_MECH_MASK_19) / STRENGTH_MASK_18);
    }

    function getClassMechValue(uint256 identity) internal pure returns(uint256){
        return (identity % RARITY_BONUS_MASK_20) / CLASS_MECH_MASK_19;
    }

    function getRarityBonusValue(uint256 identity) internal pure returns(uint256){
        return (identity % SPECIALITY_MASK_21) / RARITY_BONUS_MASK_20;
    }

    function getSpecialityValue(uint256 identity) internal pure returns(uint256){
        return (identity % DAMAGE_MASK_22) / SPECIALITY_MASK_21;
    }
    
    function getDamageValue(uint256 identity) internal pure returns(uint256){
        return (identity % AURA_MASK_23) / DAMAGE_MASK_22;
    }

    function getAuraValue(uint256 identity) internal pure returns(uint256){
        return ((identity % BASE_MASK_24) / AURA_MASK_23);
    }

    /* SETTERS */
    function _setUniqueValue0(uint256 value) internal pure returns(uint256){
        require(value < RARITY_MASK_1);
        return value * UNIQUE_MASK_0;
    }

    function _setRarityValue1(uint256 value) internal pure returns(uint256){
        require(value < (CLASS_VIEW_MASK_2 / RARITY_MASK_1));
        return value * RARITY_MASK_1;
    }

    function _setClassViewValue2(uint256 value) internal pure returns(uint256){
        require(value < (BODY_COLOR_MASK_3 / CLASS_VIEW_MASK_2));
        return value * CLASS_VIEW_MASK_2;
    }

    function _setBodyColorValue3(uint256 value) internal pure returns(uint256){
        require(value < (EYES_MASK_4 / BODY_COLOR_MASK_3));
        return value * BODY_COLOR_MASK_3;
    }

    function _setEyesValue4(uint256 value) internal pure returns(uint256){
        require(value < (MOUTH_MASK_5 / EYES_MASK_4));
        return value * EYES_MASK_4;
    }

    function _setMouthValue5(uint256 value) internal pure returns(uint256){
        require(value < (HEIR_MASK_6 / MOUTH_MASK_5));
        return value * MOUTH_MASK_5;
    }

    function _setHairValue6(uint256 value) internal pure returns(uint256){
        require(value < (HEIR_COLOR_MASK_7 / HEIR_MASK_6));
        return value * HEIR_MASK_6;
    }

    function _setHairColorValue7(uint256 value) internal pure returns(uint256){
        require(value < (ARMOR_MASK_8 / HEIR_COLOR_MASK_7));
        return value * HEIR_COLOR_MASK_7;
    }

    function _setArmorValue8(uint256 value) internal pure returns(uint256){
        require(value < (WEAPON_MASK_9 / ARMOR_MASK_8));
        return value * ARMOR_MASK_8;
    }

    function _setWeaponValue9(uint256 value) internal pure returns(uint256){
        require(value < (HAT_MASK_10 / WEAPON_MASK_9));
        return value * WEAPON_MASK_9;
    }

    function _setHatValue10(uint256 value) internal pure returns(uint256){
        require(value < (RUNES_MASK_11 / HAT_MASK_10));
        return value * HAT_MASK_10;
    }

    function _setRunesValue11(uint256 value) internal pure returns(uint256){
        require(value < (WINGS_MASK_12 / RUNES_MASK_11));
        return value * RUNES_MASK_11;
    }

    function _setWingsValue12(uint256 value) internal pure returns(uint256){
        require(value < (PET_MASK_13 / WINGS_MASK_12));
        return value * WINGS_MASK_12;
    }

    function _setPetValue13(uint256 value) internal pure returns(uint256){
        require(value < (BORDER_MASK_14 / PET_MASK_13));
        return value * PET_MASK_13;
    }

    function _setBorderValue14(uint256 value) internal pure returns(uint256){
        require(value < (BACKGROUND_MASK_15 / BORDER_MASK_14));
        return value * BORDER_MASK_14;
    }

    function _setBackgroundValue15(uint256 value) internal pure returns(uint256){
        require(value < (INTELLIGENCE_MASK_16 / BACKGROUND_MASK_15));
        return value * BACKGROUND_MASK_15;
    }

    function _setIntelligenceValue16(uint256 value) internal pure returns(uint256){
        require(value < (AGILITY_MASK_17 / INTELLIGENCE_MASK_16));
        return value * INTELLIGENCE_MASK_16;
    }

    function _setAgilityValue17(uint256 value) internal pure returns(uint256){
        require(value < (STRENGTH_MASK_18 / AGILITY_MASK_17));
        return value * AGILITY_MASK_17;
    }

    function _setStrengthValue18(uint256 value) internal pure returns(uint256){
        require(value < (CLASS_MECH_MASK_19 / STRENGTH_MASK_18));
        return value * STRENGTH_MASK_18;
    }

    function _setClassMechValue19(uint256 value) internal pure returns(uint256){
        require(value < (RARITY_BONUS_MASK_20 / CLASS_MECH_MASK_19));
        return value * CLASS_MECH_MASK_19;
    }

    function _setRarityBonusValue20(uint256 value) internal pure returns(uint256){
        require(value < (SPECIALITY_MASK_21 / RARITY_BONUS_MASK_20));
        return value * RARITY_BONUS_MASK_20;
    }

    function _setSpecialityValue21(uint256 value) internal pure returns(uint256){
        require(value < (DAMAGE_MASK_22 / SPECIALITY_MASK_21));
        return value * SPECIALITY_MASK_21;
    }
    
    function _setDamgeValue22(uint256 value) internal pure returns(uint256){
        require(value < (AURA_MASK_23 / DAMAGE_MASK_22));
        return value * DAMAGE_MASK_22;
    }

    function _setAuraValue23(uint256 value) internal pure returns(uint256){
        require(value < (BASE_MASK_24 / AURA_MASK_23));
        return value * AURA_MASK_23;
    }
    
    /* WARRIOR IDENTITY GENERATION */
    function _computeRunes(uint256 _rarity) internal pure returns (uint256){
        return _rarity > UNCOMMON ? _rarity - UNCOMMON : 0;// 1 + _random(0, max, hash, WINGS_MASK_12, RUNES_MASK_11) : 0;
    }

    function _computeWings(uint256 _rarity, uint256 max, uint256 hash) internal pure returns (uint256){
        return _rarity > RARE ?  1 + _random(0, max, hash, PET_MASK_13, WINGS_MASK_12) : 0;
    }

    function _computePet(uint256 _rarity, uint256 max, uint256 hash) internal pure returns (uint256){
        return _rarity > MYTHIC ? 1 + _random(0, max, hash, BORDER_MASK_14, PET_MASK_13) : 0;
    }

    function _computeBorder(uint256 _rarity) internal pure returns (uint256){
        return _rarity >= COMMON ? _rarity - 1 : 0;
    }

    function _computeBackground(uint256 _rarity) internal pure returns (uint256){
        return _rarity;
    }
    
    function _unpackPetData(uint256 index) internal pure returns(uint256){
        return (PETS_DATA % (1000 ** (index + 1)) / (1000 ** index));
    }
    
    function _getPetBonus1(uint256 _pet) internal pure returns(uint256) {
        return (_pet % (10 ** (PET_PARAM_1 + 1)) / (10 ** PET_PARAM_1));
    }
    
    function _getPetBonus2(uint256 _pet) internal pure returns(uint256) {
        return (_pet % (10 ** (PET_PARAM_2 + 1)) / (10 ** PET_PARAM_2));
    }
    
    function _getPetAura(uint256 _pet) internal pure returns(uint256) {
        return (_pet % (10 ** (PET_AURA + 1)) / (10 ** PET_AURA));
    }
    
    function _getBattleBonus(uint256 _setBonusIndex, uint256 _currentBonusIndex, uint256 _petData, uint256 _warriorAuras, uint256 _petAuras) internal pure returns(int256) {
        int256 bonus = 0;
        if (_setBonusIndex == _currentBonusIndex) {
            bonus += int256(BONUS_DATA % (100 ** (_setBonusIndex + 1)) / (100 ** _setBonusIndex)) * PRECISION;
        }
        //add pet bonuses
        if (_setBonusIndex == _getPetBonus1(_petData)) {
            bonus += int256(BONUS_DATA % (100 ** (_setBonusIndex + 1)) / (100 ** _setBonusIndex)) * PRECISION / 2;
        }
        if (_setBonusIndex == _getPetBonus2(_petData)) {
            bonus += int256(BONUS_DATA % (100 ** (_setBonusIndex + 1)) / (100 ** _setBonusIndex)) * PRECISION / 2;
        }
        //add warrior aura bonuses
        if (isAuraSet(_warriorAuras, uint8(_setBonusIndex))) {//warriors receive half bonuses from auras
            bonus += int256(BONUS_DATA % (100 ** (_setBonusIndex + 1)) / (100 ** _setBonusIndex)) * PRECISION / 2;
        }
        //add pet aura bonuses
        if (isAuraSet(_petAuras, uint8(_setBonusIndex))) {//pets receive full bonues from auras
            bonus += int256(BONUS_DATA % (100 ** (_setBonusIndex + 1)) / (100 ** _setBonusIndex)) * PRECISION;
        }
        return bonus;
    }
    
    function _computeRarityBonus(uint256 _rarity, uint256 hash) internal pure returns (uint256){
        if (_rarity == UNCOMMON) {
            return 1 + _random(0, BONUS_PENETRATION, hash, SPECIALITY_MASK_21, RARITY_BONUS_MASK_20);
        }
        if (_rarity == RARE) {
            return 1 + _random(BONUS_PENETRATION, BONUS_DAMAGE, hash, SPECIALITY_MASK_21, RARITY_BONUS_MASK_20);
        }
        if (_rarity >= MYTHIC) {
            return 1 + _random(0, BONUS_DAMAGE, hash, SPECIALITY_MASK_21, RARITY_BONUS_MASK_20);
        }
        return BONUS_NONE;
    }

    function _computeAura(uint256 _rarity, uint256 hash) internal pure returns (uint256){
        if (_rarity >= MYTHIC) {
            return 1 + _random(0, BONUS_DAMAGE, hash, BASE_MASK_24, AURA_MASK_23);
        }
        return BONUS_NONE;
    }
    
	function _computeRarity(uint256 _reward, uint256 _unique, uint256 _legendary, 
	    uint256 _mythic, uint256 _rare, uint256 _uncommon) internal pure returns(uint256){
	        
        uint256 range = _unique + _legendary + _mythic + _rare + _uncommon;
        if (_reward >= range) return COMMON; // common
        if (_reward >= (range = (range - _uncommon))) return UNCOMMON;
        if (_reward >= (range = (range - _rare))) return RARE;
        if (_reward >= (range = (range - _mythic))) return MYTHIC;
        if (_reward >= (range = (range - _legendary))) return LEGENDARY;
        if (_reward < range) return UNIQUE;
        return COMMON;
    }
    
    function _computeUniqueness(uint256 _rarity, uint256 nextUnique) internal pure returns (uint256){
        return _rarity == UNIQUE ? nextUnique : 0;
    }
    
    /* identity packing */
    /* @returns bonus value which depends on speciality value,
     * if speciality == 1 (miner), then bonus value will be equal 4,
     * otherwise 1
     */
    function _getBonus(uint256 identity) internal pure returns(uint256){
        return getSpecialityValue(identity) == MINER_PERK ? 4 : 1;
    }
    

    function _computeAndSetBaseParameters16_18_22(uint256 _hash) internal pure returns (uint256, uint256){
        uint256 identity = 0;

        uint256 damage = 35 + _random(0, 21, _hash, AURA_MASK_23, DAMAGE_MASK_22);
        
        uint256 strength = 45 + _random(0, 26, _hash, CLASS_MECH_MASK_19, STRENGTH_MASK_18);
        uint256 agility = 15 + (125 - damage - strength);
        uint256 intelligence = 155 - strength - agility - damage;
        (strength, agility, intelligence) = _shuffleParams(strength, agility, intelligence, _hash);
        
        identity += _setStrengthValue18(strength);
        identity += _setAgilityValue17(agility);
		identity += _setIntelligenceValue16(intelligence);
		identity += _setDamgeValue22(damage);
        
        uint256 classMech = strength > agility ? (strength > intelligence ? WARRIOR : MAGE) : (agility > intelligence ? ARCHER : MAGE);
        return (identity, classMech);
    }
    
    function _shuffleParams(uint256 param1, uint256 param2, uint256 param3, uint256 _hash) internal pure returns(uint256, uint256, uint256) {
        uint256 temp = param1;
        if (_hash % 2 == 0) {
            temp = param1;
            param1 = param2;
            param2 = temp;
        }
        if ((_hash / 10 % 2) == 0) {
            temp = param2;
            param2 = param3;
            param3 = temp;
        }
        if ((_hash / 100 % 2) == 0) {
            temp = param1;
            param1 = param2;
            param2 = temp;
        }
        return (param1, param2, param3);
    }
    
    
    /* RANDOM */
    function _random(uint256 _min, uint256 _max, uint256 _hash, uint256 _reminder, uint256 _devider) internal pure returns (uint256){
        return ((_hash % _reminder) / _devider) % (_max - _min) + _min;
    }

    function _random(uint256 _min, uint256 _max, uint256 _hash) internal pure returns (uint256){
        return _hash % (_max - _min) + _min;
    }

    function _getTargetBlock(uint256 _targetBlock) internal view returns(uint256){
        uint256 currentBlock = block.number;
        uint256 target = currentBlock - (currentBlock % 256) + (_targetBlock % 256);
        if (target >= currentBlock) {
            return (target - 256);
        }
        return target;
    }
    
    function _getMaxRarityChance() internal pure returns(uint256){
        return RARITY_CHANCE_RANGE;
    }
    
    function generateWarrior(uint256 _heroIdentity, uint256 _heroLevel, uint256 _targetBlock, uint256 specialPerc, uint32[19] memory params) internal view returns (uint256) {
        _targetBlock = _getTargetBlock(_targetBlock);
        
        uint256 identity;
        uint256 hash = uint256(keccak256(block.blockhash(_targetBlock), _heroIdentity, block.coinbase, block.difficulty));
        //0 _heroLevel produces warriors of COMMON rarity
        uint256 rarityChance = _heroLevel == 0 ? RARITY_CHANCE_RANGE : 
        	_random(0, RARITY_CHANCE_RANGE, hash) / (_heroLevel * _getBonus(_heroIdentity)); // 0 - 10 000 000
        uint256 rarity = _computeRarity(rarityChance, 
            params[UNIQUE_INDEX_13],params[LEGENDARY_INDEX_14], params[MYTHIC_INDEX_15], params[RARE_INDEX_16], params[UNCOMMON_INDEX_17]);
            
        uint256 classMech;
        
        // start
        (identity, classMech) = _computeAndSetBaseParameters16_18_22(hash);
        
        identity += _setUniqueValue0(_computeUniqueness(rarity, params[UNIQUE_TOTAL_INDEX_18] + 1));
        identity += _setRarityValue1(rarity);
        identity += _setClassViewValue2(classMech); // 1 to 1 with classMech
        
        identity += _setBodyColorValue3(1 + _random(0, params[BODY_COLOR_MAX_INDEX_0], hash, EYES_MASK_4, BODY_COLOR_MASK_3));
        identity += _setEyesValue4(1 + _random(0, params[EYES_MAX_INDEX_1], hash, MOUTH_MASK_5, EYES_MASK_4));
        identity += _setMouthValue5(1 + _random(0, params[MOUTH_MAX_2], hash, HEIR_MASK_6, MOUTH_MASK_5));
        identity += _setHairValue6(1 + _random(0, params[HAIR_MAX_3], hash, HEIR_COLOR_MASK_7, HEIR_MASK_6));
        identity += _setHairColorValue7(1 + _random(0, params[HEIR_COLOR_MAX_4], hash, ARMOR_MASK_8, HEIR_COLOR_MASK_7));
        identity += _setArmorValue8(1 + _random(0, params[ARMOR_MAX_5], hash, WEAPON_MASK_9, ARMOR_MASK_8));
        identity += _setWeaponValue9(1 + _random(0, params[WEAPON_MAX_6], hash, HAT_MASK_10, WEAPON_MASK_9));
        identity += _setHatValue10(_random(0, params[HAT_MAX_7], hash, RUNES_MASK_11, HAT_MASK_10));//removed +1
        
        identity += _setRunesValue11(_computeRunes(rarity));
        identity += _setWingsValue12(_computeWings(rarity, params[WINGS_MAX_9], hash));
        identity += _setPetValue13(_computePet(rarity, params[PET_MAX_10], hash));
        identity += _setBorderValue14(_computeBorder(rarity)); // 1 to 1 with rarity
        identity += _setBackgroundValue15(_computeBackground(rarity)); // 1 to 1 with rarity
        
        identity += _setClassMechValue19(classMech);

        identity += _setRarityBonusValue20(_computeRarityBonus(rarity, hash));
        identity += _setSpecialityValue21(specialPerc); // currently only miner (1)
        
        identity += _setAuraValue23(_computeAura(rarity, hash));
        // end
        return identity;
    }
    
	function _changeParameter(uint256 _paramIndex, uint32 _value, uint32[19] storage parameters) internal {
		//we can change only view parameters, and unique count in max range <= 100
		require(_paramIndex >= BODY_COLOR_MAX_INDEX_0 && _paramIndex <= UNIQUE_INDEX_13);
		//we can NOT set pet, border and background values,
		//those values have special logic behind them
		require(
		    _paramIndex != RUNES_MAX_8 && 
		    _paramIndex != PET_MAX_10 && 
		    _paramIndex != BORDER_MAX_11 && 
		    _paramIndex != BACKGROUND_MAX_12
		);
		//value of bodyColor, eyes, mouth, hair, hairColor, armor, weapon, hat must be < 1000
		require(_paramIndex > HAT_MAX_7 || _value < 1000);
		//value of wings,  must be < 100
		require(_paramIndex > BACKGROUND_MAX_12 || _value < 100);
		//check that max total number of UNIQUE warriors that we can emit is not > 100
		require(_paramIndex != UNIQUE_INDEX_13 || (_value + parameters[UNIQUE_TOTAL_INDEX_18]) <= 100);
		
		parameters[_paramIndex] = _value;
    }
    
	function _recordWarriorData(uint256 identity, uint32[19] storage parameters) internal {
        uint256 rarity = getRarityValue(identity);
        if (rarity == UNCOMMON) { // uncommon
            parameters[UNCOMMON_INDEX_17]--;
            return;
        }
        if (rarity == RARE) { // rare
            parameters[RARE_INDEX_16]--;
            return;
        }
        if (rarity == MYTHIC) { // mythic
            parameters[MYTHIC_INDEX_15]--;
            return;
        }
        if (rarity == LEGENDARY) { // legendary
            parameters[LEGENDARY_INDEX_14]--;
            return;
        }
        if (rarity == UNIQUE) { // unique
            parameters[UNIQUE_INDEX_13]--;
            parameters[UNIQUE_TOTAL_INDEX_18] ++;
            return;
        }
    }
    
    function _validateIdentity(uint256 _identity, uint32[19] memory params) internal pure returns(bool){
        uint256 rarity = getRarityValue(_identity);
        require(rarity <= UNIQUE);
        
        require(
            rarity <= COMMON ||//common 
            (rarity == UNCOMMON && params[UNCOMMON_INDEX_17] > 0) ||//uncommon
            (rarity == RARE && params[RARE_INDEX_16] > 0) ||//rare
            (rarity == MYTHIC && params[MYTHIC_INDEX_15] > 0) ||//mythic
            (rarity == LEGENDARY && params[LEGENDARY_INDEX_14] > 0) ||//legendary
            (rarity == UNIQUE && params[UNIQUE_INDEX_13] > 0)//unique
        );
        require(rarity != UNIQUE || getUniqueValue(_identity) > params[UNIQUE_TOTAL_INDEX_18]);
        
        //check battle parameters
        require(
            getStrengthValue(_identity) < 100 &&
            getAgilityValue(_identity) < 100 &&
            getIntelligenceValue(_identity) < 100 &&
            getDamageValue(_identity) <= 55
        );
        require(getClassMechValue(_identity) <= MAGE);
        require(getClassMechValue(_identity) == getClassViewValue(_identity));
        require(getSpecialityValue(_identity) <= MINER_PERK);
        require(getRarityBonusValue(_identity) <= BONUS_DAMAGE);
        require(getAuraValue(_identity) <= BONUS_DAMAGE);
        
        //check view
        require(getBodyColorValue(_identity) <= params[BODY_COLOR_MAX_INDEX_0]);
        require(getEyesValue(_identity) <= params[EYES_MAX_INDEX_1]);
        require(getMouthValue(_identity) <= params[MOUTH_MAX_2]);
        require(getHairValue(_identity) <= params[HAIR_MAX_3]);
        require(getHairColorValue(_identity) <= params[HEIR_COLOR_MAX_4]);
        require(getArmorValue(_identity) <= params[ARMOR_MAX_5]);
        require(getWeaponValue(_identity) <= params[WEAPON_MAX_6]);
        require(getHatValue(_identity) <= params[HAT_MAX_7]);
        require(getRunesValue(_identity) <= params[RUNES_MAX_8]);
        require(getWingsValue(_identity) <= params[WINGS_MAX_9]);
        require(getPetValue(_identity) <= params[PET_MAX_10]);
        require(getBorderValue(_identity) <= params[BORDER_MAX_11]);
        require(getBackgroundValue(_identity) <= params[BACKGROUND_MAX_12]);
        
        return true;
    }
    
    /* UNPACK METHODS */
    //common
    function _unpackClassValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % RARITY_PACK_2 / CLASS_PACK_0);
    }
    
    function _unpackRarityBonusValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % RARITY_PACK_2 / RARITY_BONUS_PACK_1);
    }
    
    function _unpackRarityValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % EXPERIENCE_PACK_3 / RARITY_PACK_2);
    }
    
    function _unpackExpValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % INTELLIGENCE_PACK_4 / EXPERIENCE_PACK_3);
    }

    function _unpackLevelValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % INTELLIGENCE_PACK_4) / (EXPERIENCE_PACK_3 * POINTS_TO_LEVEL);
    }
    
    function _unpackIntelligenceValue(uint256 packedValue) internal pure returns(int256){
        return int256(packedValue % AGILITY_PACK_5 / INTELLIGENCE_PACK_4);
    }
    
    function _unpackAgilityValue(uint256 packedValue) internal pure returns(int256){
        return int256(packedValue % STRENGTH_PACK_6 / AGILITY_PACK_5);
    }
    
    function _unpackStrengthValue(uint256 packedValue) internal pure returns(int256){
        return int256(packedValue % BASE_DAMAGE_PACK_7 / STRENGTH_PACK_6);
    }

    function _unpackBaseDamageValue(uint256 packedValue) internal pure returns(int256){
        return int256(packedValue % PET_PACK_8 / BASE_DAMAGE_PACK_7);
    }
    
    function _unpackPetValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % AURA_PACK_9 / PET_PACK_8);
    }
    
    function _unpackAuraValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % WARRIOR_ID_PACK_10 / AURA_PACK_9);
    }
    //
    //pvp unpack
    function _unpackIdValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % PVP_CYCLE_PACK_11 / WARRIOR_ID_PACK_10);
    }
    
    function _unpackCycleValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % RATING_PACK_12 / PVP_CYCLE_PACK_11);
    }
    
    function _unpackRatingValue(uint256 packedValue) internal pure returns(uint256){
        return (packedValue % PVP_BASE_PACK_13 / RATING_PACK_12);
    }
    
    //max cycle skip value cant be more than 1000000000
    function _changeCycleValue(uint256 packedValue, uint256 newValue) internal pure returns(uint256){
        newValue = newValue > 1000000000 ? 1000000000 : newValue;
        return packedValue - (_unpackCycleValue(packedValue) * PVP_CYCLE_PACK_11) + newValue * PVP_CYCLE_PACK_11;
    }
    
    function _packWarriorCommonData(uint256 _identity, uint256 _experience) internal pure returns(uint256){
        uint256 packedData = 0;
        packedData += getClassMechValue(_identity) * CLASS_PACK_0;
        packedData += getRarityBonusValue(_identity) * RARITY_BONUS_PACK_1;
        packedData += getRarityValue(_identity) * RARITY_PACK_2;
        packedData += _experience * EXPERIENCE_PACK_3;
        packedData += getIntelligenceValue(_identity) * INTELLIGENCE_PACK_4;
        packedData += getAgilityValue(_identity) * AGILITY_PACK_5;
        packedData += getStrengthValue(_identity) * STRENGTH_PACK_6;
        packedData += getDamageValue(_identity) * BASE_DAMAGE_PACK_7;
        packedData += getPetValue(_identity) * PET_PACK_8;
        
        return packedData;
    }
    
    function _packWarriorPvpData(uint256 _identity, uint256 _rating, uint256 _pvpCycle, uint256 _warriorId, uint256 _experience) internal pure returns(uint256){
        uint256 packedData = _packWarriorCommonData(_identity, _experience);
        packedData += _warriorId * WARRIOR_ID_PACK_10;
        packedData += _pvpCycle * PVP_CYCLE_PACK_11;
        //rating MUST have most significant value!
        packedData += _rating * RATING_PACK_12;
        return packedData;
    }
    
    /* TOURNAMENT BATTLES */
    
    
    function _packWarriorIds(uint256[] memory packedWarriors) internal pure returns(uint256){
        uint256 packedIds = 0;
        uint256 length = packedWarriors.length;
        for(uint256 i = 0; i < length; i ++) {
            packedIds += (MAX_ID_SIZE ** i) * _unpackIdValue(packedWarriors[i]);
        }
        return packedIds;
    }

    function _unpackWarriorId(uint256 packedIds, uint256 index) internal pure returns(uint256){
        return (packedIds % (MAX_ID_SIZE ** (index + 1)) / (MAX_ID_SIZE ** index));
    }
    
    function _packCombinedParams(int256 hp, int256 damage, int256 armor, int256 dodge, int256 penetration) internal pure returns(uint256) {
        uint256 combinedWarrior = 0;
        combinedWarrior += uint256(hp) * HP_PACK_0;
        combinedWarrior += uint256(damage) * DAMAGE_PACK_1;
        combinedWarrior += uint256(armor) * ARMOR_PACK_2;
        combinedWarrior += uint256(dodge) * DODGE_PACK_3;
        combinedWarrior += uint256(penetration) * PENETRATION_PACK_4;
        return combinedWarrior;
    }
    
    function _unpackProtectionParams(uint256 combinedWarrior) internal pure returns 
    (int256 hp, int256 armor, int256 dodge){
        hp = int256(combinedWarrior % DAMAGE_PACK_1 / HP_PACK_0);
        armor = int256(combinedWarrior % DODGE_PACK_3 / ARMOR_PACK_2);
        dodge = int256(combinedWarrior % PENETRATION_PACK_4 / DODGE_PACK_3);
    }
    
    function _unpackAttackParams(uint256 combinedWarrior) internal pure returns(int256 damage, int256 penetration) {
        damage = int256(combinedWarrior % ARMOR_PACK_2 / DAMAGE_PACK_1);
        penetration = int256(combinedWarrior % COMBINE_BASE_PACK_5 / PENETRATION_PACK_4);
    }
    
    function _combineWarriors(uint256[] memory packedWarriors) internal pure returns (uint256) {
        int256 hp;
        int256 damage;
		int256 armor;
		int256 dodge;
		int256 penetration;
		
		(hp, damage, armor, dodge, penetration) = _computeCombinedParams(packedWarriors);
        return _packCombinedParams(hp, damage, armor, dodge, penetration);
    }
    
    function _computeCombinedParams(uint256[] memory packedWarriors) internal pure returns 
    (int256 totalHp, int256 totalDamage, int256 maxArmor, int256 maxDodge, int256 maxPenetration){
        uint256 length = packedWarriors.length;
        
        int256 hp;
		int256 armor;
		int256 dodge;
		int256 penetration;
		
		uint256 warriorAuras;
		uint256 petAuras;
		(warriorAuras, petAuras) = _getAurasData(packedWarriors);
		
		uint256 packedWarrior;
        for(uint256 i = 0; i < length; i ++) {
            packedWarrior = packedWarriors[i];
            
            totalDamage += getDamage(packedWarrior, warriorAuras, petAuras);
            
            penetration = getPenetration(packedWarrior, warriorAuras, petAuras);
            maxPenetration = maxPenetration > penetration ? maxPenetration : penetration;
			(hp, armor, dodge) = _getProtectionParams(packedWarrior, warriorAuras, petAuras);
            totalHp += hp;
            maxArmor = maxArmor > armor ? maxArmor : armor;
            maxDodge = maxDodge > dodge ? maxDodge : dodge;
        }
    }
    
    function _getAurasData(uint256[] memory packedWarriors) internal pure returns(uint256 warriorAuras, uint256 petAuras) {
        uint256 length = packedWarriors.length;
        
        warriorAuras = 0;
        petAuras = 0;
        
        uint256 packedWarrior;
        for(uint256 i = 0; i < length; i ++) {
            packedWarrior = packedWarriors[i];
            warriorAuras = enableAura(warriorAuras, (_unpackAuraValue(packedWarrior)));
            petAuras = enableAura(petAuras, (_getPetAura(_unpackPetData(_unpackPetValue(packedWarrior)))));
        }
        warriorAuras = filterWarriorAuras(warriorAuras, petAuras);
        return (warriorAuras, petAuras);
    }
    
    // Get bit value at position
    function isAuraSet(uint256 aura, uint256 auraIndex) internal pure returns (bool) {
        return aura & (uint256(0x01) << auraIndex) != 0;
    }
    
    // Set bit value at position
    function enableAura(uint256 a, uint256 n) internal pure returns (uint256) {
        return a | (uint256(0x01) << n);
    }
    
    //switch off warrior auras that are enabled in pets auras, pet aura have priority
    function filterWarriorAuras(uint256 _warriorAuras, uint256 _petAuras) internal pure returns(uint256) {
        return (_warriorAuras & _petAuras) ^ _warriorAuras;
    }
  
    function _getTournamentBattles(uint256 _numberOfContenders) internal pure returns(uint256) {
        return (_numberOfContenders * BATTLES_PER_CONTENDER / 2);
    }
    
    function getTournamentBattleResults(uint256[] memory combinedWarriors, uint256 _targetBlock) internal view returns (uint32[] memory results){
        uint256 length = combinedWarriors.length;
        results = new uint32[](length);
		
		int256 damage1;
		int256 penetration1;
		
		uint256 hash;
		
		uint256 randomIndex;
		uint256 exp = 0;
		uint256 i;
		uint256 result;
        for(i = 0; i < length; i ++) {
            (damage1, penetration1) = _unpackAttackParams(combinedWarriors[i]);
            while(results[i] < BATTLES_PER_CONTENDER_SUM) {
                //if we just started generate new random source
                //or regenerate if we used all data from it
                if (exp == 0 || exp > 73) {
                    hash = uint256(keccak256(block.blockhash(_getTargetBlock(_targetBlock - i)), uint256(damage1) + now));
                    exp = 0;
                }
                //we do not fight with self if there are other warriors
                randomIndex = (_random(i + 1 < length ? i + 1 : i, length, hash, 1000 * 10**exp, 10**exp));
                result = getTournamentBattleResult(damage1, penetration1, combinedWarriors[i],
                    combinedWarriors[randomIndex], hash % (1000 * 10**exp) / 10**exp);
                results[result == 1 ? i : randomIndex] += 101;//icrement battle count 100 and +1 win
                results[result == 1 ? randomIndex : i] += 100;//increment only battle count 100 for loser
                if (results[randomIndex] >= BATTLES_PER_CONTENDER_SUM) {
                    if (randomIndex < length - 1) {
                        _swapValues(combinedWarriors, results, randomIndex, length - 1);
                    }
                    length --;
                }
                exp++;
            }
        }
        //filter battle count from results
        length = combinedWarriors.length;
        for(i = 0; i < length; i ++) {
            results[i] = results[i] % 100;
        }
        
        return results;
    }
    
    function _swapValues(uint256[] memory combinedWarriors, uint32[] memory results, uint256 id1, uint256 id2) internal pure {
        uint256 temp = combinedWarriors[id1];
        combinedWarriors[id1] = combinedWarriors[id2];
        combinedWarriors[id2] = temp;
        temp = results[id1];
        results[id1] = results[id2];
        results[id2] = uint32(temp);
    }

    function getTournamentBattleResult(int256 damage1, int256 penetration1, uint256 combinedWarrior1, 
        uint256 combinedWarrior2, uint256 randomSource) internal pure returns (uint256)
    {
        int256 damage2;
		int256 penetration2;
        
		(damage2, penetration2) = _unpackAttackParams(combinedWarrior1);

		int256 totalHp1 = getCombinedTotalHP(combinedWarrior1, penetration2);
		int256 totalHp2 = getCombinedTotalHP(combinedWarrior2, penetration1);
        
        return _getBattleResult(damage1 * getBattleRandom(randomSource, 1) / 100, damage2 * getBattleRandom(randomSource, 10) / 100, totalHp1, totalHp2, randomSource);
    }
    /* COMMON BATTLE */
    
    function _getBattleResult(int256 damage1, int256 damage2, int256 totalHp1, int256 totalHp2, uint256 randomSource)  internal pure returns (uint256){
		totalHp1 = (totalHp1 * (PRECISION * PRECISION) / damage2);
		totalHp2 = (totalHp2 * (PRECISION * PRECISION) / damage1);
		//if draw, let the coin decide who wins
		if (totalHp1 == totalHp2) return randomSource % 2 + 1;
		return totalHp1 > totalHp2 ? 1 : 2;       
    }
    
    function getCombinedTotalHP(uint256 combinedData, int256 enemyPenetration) internal pure returns(int256) {
        int256 hp;
		int256 armor;
		int256 dodge;
		(hp, armor, dodge) = _unpackProtectionParams(combinedData);
        
        return _getTotalHp(hp, armor, dodge, enemyPenetration);
    }
    
    function getTotalHP(uint256 packedData, uint256 warriorAuras, uint256 petAuras, int256 enemyPenetration) internal pure returns(int256) {
        int256 hp;
		int256 armor;
		int256 dodge;
		(hp, armor, dodge) = _getProtectionParams(packedData, warriorAuras, petAuras);
        
        return _getTotalHp(hp, armor, dodge, enemyPenetration);
    }
    
    function _getTotalHp(int256 hp, int256 armor, int256 dodge, int256 enemyPenetration) internal pure returns(int256) {
        int256 piercingResult = (armor - enemyPenetration) < -(75 * PRECISION) ? -(75 * PRECISION) : (armor - enemyPenetration);
        int256 mitigation = (PRECISION - piercingResult * PRECISION / (PRECISION + piercingResult / 100) / 100);
        
        return (hp * PRECISION / mitigation + (hp * dodge / (100 * PRECISION)));
    }
    
    function _applyLevelBonus(int256 _value, uint256 _level) internal pure returns(int256) {
        _level -= 1;
        return int256(uint256(_value) * (LEVEL_BONUSES % (100 ** (_level + 1)) / (100 ** _level)) / 10);
    }
    
    function _getProtectionParams(uint256 packedData, uint256 warriorAuras, uint256 petAuras) internal pure returns(int256 hp, int256 armor, int256 dodge) {
        uint256 rarityBonus = _unpackRarityBonusValue(packedData);
        uint256 petData = _unpackPetData(_unpackPetValue(packedData));
        int256 strength = _unpackStrengthValue(packedData) * PRECISION + _getBattleBonus(BONUS_STR, rarityBonus, petData, warriorAuras, petAuras);
        int256 agility = _unpackAgilityValue(packedData) * PRECISION + _getBattleBonus(BONUS_AGI, rarityBonus, petData, warriorAuras, petAuras);
        
        hp = 100 * PRECISION + strength + 7 * strength / 10 + _getBattleBonus(BONUS_HP, rarityBonus, petData, warriorAuras, petAuras);//add bonus hp
        hp = _applyLevelBonus(hp, _unpackLevelValue(packedData));
		armor = (strength + 8 * strength / 10 + agility + _getBattleBonus(BONUS_ARMOR, rarityBonus, petData, warriorAuras, petAuras));//add bonus armor
		dodge = (2 * agility / 3);
    }
    
    function getDamage(uint256 packedWarrior, uint256 warriorAuras, uint256 petAuras) internal pure returns(int256) {
        uint256 rarityBonus = _unpackRarityBonusValue(packedWarrior);
        uint256 petData = _unpackPetData(_unpackPetValue(packedWarrior));
        int256 agility = _unpackAgilityValue(packedWarrior) * PRECISION + _getBattleBonus(BONUS_AGI, rarityBonus, petData, warriorAuras, petAuras);
        int256 intelligence = _unpackIntelligenceValue(packedWarrior) * PRECISION + _getBattleBonus(BONUS_INT, rarityBonus, petData, warriorAuras, petAuras);
		
		int256 crit = (agility / 5 + intelligence / 4) + _getBattleBonus(BONUS_CRIT_CHANCE, rarityBonus, petData, warriorAuras, petAuras);
		int256 critMultiplier = (PRECISION + intelligence / 25) + _getBattleBonus(BONUS_CRIT_MULT, rarityBonus, petData, warriorAuras, petAuras);
        
        int256 damage = int256(_unpackBaseDamageValue(packedWarrior) * 3 * PRECISION / 2) + _getBattleBonus(BONUS_DAMAGE, rarityBonus, petData, warriorAuras, petAuras);
        
		return (_applyLevelBonus(damage, _unpackLevelValue(packedWarrior)) * (PRECISION + crit * critMultiplier / (100 * PRECISION))) / PRECISION;
    }

    function getPenetration(uint256 packedWarrior, uint256 warriorAuras, uint256 petAuras) internal pure returns(int256) {
        uint256 rarityBonus = _unpackRarityBonusValue(packedWarrior);
        uint256 petData = _unpackPetData(_unpackPetValue(packedWarrior));
        int256 agility = _unpackAgilityValue(packedWarrior) * PRECISION + _getBattleBonus(BONUS_AGI, rarityBonus, petData, warriorAuras, petAuras);
        int256 intelligence = _unpackIntelligenceValue(packedWarrior) * PRECISION + _getBattleBonus(BONUS_INT, rarityBonus, petData, warriorAuras, petAuras);
		
		return (intelligence * 2 + agility + _getBattleBonus(BONUS_PENETRATION, rarityBonus, petData, warriorAuras, petAuras));
    }
    
    /* BATTLE PVP */
    
    //@param randomSource must be >= 1000
    function getBattleRandom(uint256 randmSource, uint256 _step) internal pure returns(int256){
        return int256(100 + _random(0, 11, randmSource, 100 * _step, _step));
    }
    
    uint256 internal constant NO_AURA = 0;
    
    function getPVPBattleResult(uint256 packedData1, uint256 packedData2, uint256 randmSource) internal pure returns (uint256){
        uint256 petAura1 = _computePVPPetAura(packedData1);
        uint256 petAura2 = _computePVPPetAura(packedData2);
        
        uint256 warriorAura1 = _computePVPWarriorAura(packedData1, petAura1);
        uint256 warriorAura2 = _computePVPWarriorAura(packedData2, petAura2);
        
		int256 damage1 = getDamage(packedData1, warriorAura1, petAura1) * getBattleRandom(randmSource, 1) / 100;
        int256 damage2 = getDamage(packedData2, warriorAura2, petAura2) * getBattleRandom(randmSource, 10) / 100;

		int256 totalHp1;
		int256 totalHp2;
		(totalHp1, totalHp2) = _computeContendersTotalHp(packedData1, warriorAura1, petAura1, packedData2, warriorAura1, petAura1);
        
        return _getBattleResult(damage1, damage2, totalHp1, totalHp2, randmSource);
    }
    
    function _computePVPPetAura(uint256 packedData) internal pure returns(uint256) {
        return enableAura(NO_AURA, _getPetAura(_unpackPetData(_unpackPetValue(packedData))));
    }
    
    function _computePVPWarriorAura(uint256 packedData, uint256 petAuras) internal pure returns(uint256) {
        return filterWarriorAuras(enableAura(NO_AURA, _unpackAuraValue(packedData)), petAuras);
    }
    
    function _computeContendersTotalHp(uint256 packedData1, uint256 warriorAura1, uint256 petAura1, uint256 packedData2, uint256 warriorAura2, uint256 petAura2) 
    internal pure returns(int256 totalHp1, int256 totalHp2) {
		int256 enemyPenetration = getPenetration(packedData2, warriorAura2, petAura2);
		totalHp1 = getTotalHP(packedData1, warriorAura1, petAura1, enemyPenetration);
		enemyPenetration = getPenetration(packedData1, warriorAura1, petAura1);
		totalHp2 = getTotalHP(packedData2, warriorAura1, petAura1, enemyPenetration);
    }
    
    function getRatingRange(uint256 _pvpCycle, uint256 _pvpInterval, uint256 _expandInterval) internal pure returns (uint256){
        return 50 + (_pvpCycle * _pvpInterval / _expandInterval * 25);
    }
    
    function isMatching(int256 evenRating, int256 oddRating, int256 ratingGap) internal pure returns(bool) {
        return evenRating <= (oddRating + ratingGap) && evenRating >= (oddRating - ratingGap);
    }
    
    function sort(uint256[] memory data) internal pure {
       quickSort(data, int(0), int(data.length - 1));
    }
    
    function quickSort(uint256[] memory arr, int256 left, int256 right) internal pure {
        int256 i = left;
        int256 j = right;
        if(i==j) return;
        uint256 pivot = arr[uint256(left + (right - left) / 2)];
        while (i <= j) {
            while (arr[uint256(i)] < pivot) i++;
            while (pivot < arr[uint256(j)]) j--;
            if (i <= j) {
                (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]);
                i++;
                j--;
            }
        }
        if (left < j)
            quickSort(arr, left, j);
        if (i < right)
            quickSort(arr, i, right);
    }
    
    function _swapPair(uint256[] memory matchingIds, uint256 id1, uint256 id2, uint256 id3, uint256 id4) internal pure {
        uint256 temp = matchingIds[id1];
        matchingIds[id1] = matchingIds[id2];
        matchingIds[id2] = temp;
        
        temp = matchingIds[id3];
        matchingIds[id3] = matchingIds[id4];
        matchingIds[id4] = temp;
    }
    
    function _swapValues(uint256[] memory matchingIds, uint256 id1, uint256 id2) internal pure {
        uint256 temp = matchingIds[id1];
        matchingIds[id1] = matchingIds[id2];
        matchingIds[id2] = temp;
    }
    
    function _getMatchingIds(uint256[] memory matchingIds, uint256 _pvpInterval, uint256 _skipCycles, uint256 _expandInterval) 
    internal pure returns(uint256 matchingCount) 
    {
        matchingCount = matchingIds.length;
        if (matchingCount == 0) return 0;
        
        uint256 warriorId;
        uint256 index;
        //sort matching ids
        quickSort(matchingIds, int256(0), int256(matchingCount - 1));
        //find pairs
        int256 rating1;
        uint256 pairIndex = 0;
        int256 ratingRange;
        for(index = 0; index < matchingCount; index++) {
            //get packed value
            warriorId = matchingIds[index];
            //unpack rating 1
            rating1 = int256(_unpackRatingValue(warriorId));
            ratingRange = int256(getRatingRange(_unpackCycleValue(warriorId) + _skipCycles, _pvpInterval, _expandInterval));
            
            if (index > pairIndex && //check left neighbor
            isMatching(rating1, int256(_unpackRatingValue(matchingIds[index - 1])), ratingRange)) {
                //move matched pairs to the left
                //swap pairs
                _swapPair(matchingIds, pairIndex, index - 1, pairIndex + 1, index);
                //mark last pair position
                pairIndex += 2;
            } else if (index + 1 < matchingCount && //check right neighbor
            isMatching(rating1, int256(_unpackRatingValue(matchingIds[index + 1])), ratingRange)) {
                //move matched pairs to the left
                //swap pairs
                _swapPair(matchingIds, pairIndex, index, pairIndex + 1, index + 1);
                //mark last pair position
                pairIndex += 2;
                //skip next iteration
                index++;
            }
        }
        
        matchingCount = pairIndex;
    }

    function _getPVPBattleResults(uint256[] memory matchingIds, uint256 matchingCount, uint256 _targetBlock) internal view {
        uint256 exp = 0;
        uint256 hash = 0;
        uint256 result = 0;
        for (uint256 even = 0; even < matchingCount; even += 2) {
            if (exp == 0 || exp > 73) {
                hash = uint256(keccak256(block.blockhash(_getTargetBlock(_targetBlock)), hash));
                exp = 0;
            }
                
            //compute battle result 1 = even(left) id won, 2 - odd(right) id won
            result = getPVPBattleResult(matchingIds[even], matchingIds[even + 1], hash % (1000 * 10**exp) / 10**exp);
            require(result > 0 && result < 3);
            exp++;
            //if odd warrior won, swap his id with even warrior,
            //otherwise do nothing,
            //even ids are winning ids! odds suck!
            if (result == 2) {
                _swapValues(matchingIds, even, even + 1);
            }
        }
    }
    
    function _getLevel(uint256 _levelPoints) internal pure returns(uint256) {
        return _levelPoints / POINTS_TO_LEVEL;
    }
    
}
function filterWarriorAuras(uint256 _warriorAuras, uint256 _petAuras) internal pure returns(uint256) {
    return (_warriorAuras & _petAuras) ^ _warriorAuras;
}

//switch off warrior auras that are enabled in pets auras, pet aura have priority