# Calculating Enchantment Protection Factor (EPF)

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

1. ### MuqsitChicken

Messages:
1,549
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.

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 *= 1 - \$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(\$damage, self::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.