Calculating Enchantment Protection Factor (EPF)

Discussion in 'Resources' started by Muqsit, Jul 29, 2017.

  1. Muqsit

    Muqsit Wither Skeleton Verified

    Messages:
    1,487
    GitHub:
    muqsit
    ...with the current PocketMine API.
    NOTE: The name of this thread *might* change if I continue to explore enchantments other than Protection*. This calculation is limited to what I've understood from https://minecraft.gamepedia.com/Armor#Enchantments, so feel free to comment any miscalculations that I've done.

    NOTE: You can copy, paste or modify this code as you like. I do not impose any license on this work. This is just my understanding of a minecraft wiki page.

    Understanding Enchantment Protection Factor:
    Enchantment Protection Factor is how much damage a Protection* enchantment can reduce. Every Protection* enchantment in Minecraft is given a constant "modifier" value which is directly propotional to the armor's enchantment protection factor by level. "Modifier" is the same thing as a multiplier.
    xs.PNG
    As you can see, the EPF level is incremented by "Type Modifier" as the enchantment level increases.
    Or in terms of PHP and PocketMine's API:
    PHP:
    const EPF_MODIFIERES = [
        
    Enchantment::TYPE_ARMOR_PROTECTION => 1,
        
    Enchantment::TYPE_ARMOR_FIRE_PROTECTION => 2,
        
    Enchantment::TYPE_ARMOR_EXPLOSION_PROTECTION => 2,
        
    Enchantment::TYPE_ARMOR_PROJECTILE_PROTECTION => 2,
        
    Enchantment::TYPE_ARMOR_FALL_PROTECTION => 3
    ];

    static public function 
    getEPFByEnchantment(Enchantment $enchant) : int{
        return 
    EPF_MODIFIERS[$enchant->getId()] * $enchant->getLevel();
    }
    How to calculate damage using this value?
    "When a player or mob wearing armor is subjected to damage, the EPFs of all applicable enchantments are added together, capped at 20, and then damage is reduced as damage = damage * (1 - cappedEPF / 25)"
    That's from the Minecraft Wiki.

    For this, you'll need to first find out the damage done, for which we will be listening to EntityDamageEvent with the HIGHEST priority (because we want to calculate our damage based on the final result - after all the plugins have modified the outcome).
    We also do not want to do such arguably heavy calculations if the event has already been cancelled. For this we will be ignoring the event if it's cancelled.

    NOTE: *Ignore this if you are creating a totally new plugin/script to handle EntityDamageEvent*. If your plugin already is listening to EntityDamageEvent, then execute the upcoming calculations at the end of the event.

    PHP:
    /**
     * @param EntityDamageEvent $event
     * @priority HIGHEST
     * @ignoreCancelled true
     */
    public function onEntityDamage(EntityDamageEvent $event){
    }
    Next we'll need to get the final damage and use the formula provided on the Minecraft Wiki page to reduce the damage based on armor protection. EntityDamageEvent::getFinalDamage() gives you the final damage done to the entity. This (currently) includes damage from entities (value: > 0), damage reduction from armor (value: <= 0), damage from effects (currently: strength, weakness and resistance) and if done, damage modified by plugins (citation).
    cappedEPF is the total sum of EPF from all armor items capped at 20, meaning if the total EPF is greater than 20, it is set back to 20.
    PHP:
    /** @var Items[] $armorItems */
    $epf = [];
    /** @var Armor $armor except if $armor is a skull or a pumpkin pie */
    foreach($armorItems as $armor){
        if(
    $armor->hasEnchantments()){
            
    /** @var ListTag $en */
            
    foreach($armor->getNamedTag()->ench as $en){
                if(isset(
    self::EPF_MODIFIERES[$id $en->id->getValue()])){
                    
    $modifier self::EPF_MODIFIERS[$id];
                    if(!isset(
    $epf[$modifier])){
                        
    $epf[$modifier] = $modifier $en->lvl->getValue();//yes the tag is named "lvl" and not "level". Just wanted you to know I haven't created an abbreviation here!
                    
    }else{
                        
    $epf[$modifier] += $modifier $en->lvl->getValue();//"the EPFs of all applicable enchantments are added together"
                    
    }
                }
            }
        }
    }

    $damage $event->getFinalDamage();
    $epf array_sum($epf);//again, "the EPFs of all applicable enchantments are added together"
    $cappedEPF min(20$epf);//If $epf > 20, set $epf to 20
    $damage *= $cappedEPF 25;//Using the formula from Minecraft Wiki.
    WAIT! Do not use EntityDamageEvent::setDamage() without the 2nd argument.
    The second argument of EntityDamageEvent specifies which modification you are setting the damage for. Currently, there are 5 modifiers in PocketMine, the default value of parameter 2 is EntityDamageEvent::MODIFIER_BASE.

    Why not use Item::getEnchantments() instead of Item::getNamedTag()->ench?
    Good question. PocketMine currently supports only 3 enchantments. And if our enchantment isn't any of these 3 enchantments, then this enchantment won't be included in Item::getEnchantments(). Item::getNamedTag()->ench, on the other hand, will list out all enchantments as it checks the item's NBT.
    PHP:
    const MODIFIER_BASE 0;
    const 
    MODIFIER_ARMOR 1;
    const 
    MODIFIER_STRENGTH 2;
    const 
    MODIFIER_WEAKNESS 3;
    const 
    MODIFIER_RESISTANCE 4;
    For example: executing EntityDamageEvent::setDamage(0, EntityDamageEvent::MODIFIER_WEAKNESS) will make the weakness effect not do any harm to EntityDamageEvent::getEntity().
    Another example: EntityDamageEvent::setDamage(0, EntityDamageEvent::MODIFIER_ARMOR) will not give the player the static armor resistance. So even if the player is covered in diamond armor from head to toe, the damage done will be the same as the damage done to a player wearing no armor piece.

    But which modifier is applicable for EPF? I'd go with MODIFIER_BASE. But since we are modifying the event's final damage, this modifier won't be that effective. We will need to declare our own modifier with a value that isn't currently being used in EntityDamageEvent, and then set the damage to that modifier.
    PHP:
    const MODIFIER_EPF 5;

    $event->setDamage($damageself::MODIFIER_EPF);
    Isn't the function going to 'add' the damage rather than 'subtract'?
    No, $damage is a negative value.

    That is it, all protection enchantments must now be working. If you find any errors in this thread, do let me know.
     
    Last edited: Nov 10, 2017
    OnTheVerge and Teamblocket like this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.