@reldens/skills
Version:
185 lines (171 loc) • 8.29 kB
JavaScript
/**
*
* Reldens - Skills - Attack
*
* This class provides a skill with damage calculation properties and methods.
*
*/
const Skill = require('../skill');
const SkillsEvents = require('../skills-events');
const SkillsConst = require('../constants');
const { ErrorManager, sc, Logger} = require('@reldens/utils');
class Attack extends Skill
{
constructor(props)
{
super(props);
this.type = SkillsConst.SKILL.TYPE.ATTACK;
if(!sc.get(props, 'affectedProperty', false)){
ErrorManager.error('Missing skill affectedProperty to which the result damage will be applied.');
}
// the affected property is the one to which the damage calculation will have effect:
this.affectedProperty = props.affectedProperty;
this.allowEffectBelowZero = sc.get(props, 'allowEffectBelowZero', false);
// hit damage value at 100%:
this.hitDamage = sc.hasOwn(props, 'hitDamage') ? props.hitDamage : 0;
this.applyDirectDamage = sc.hasOwn(props, 'applyDirectDamage') ? props.applyDirectDamage : false;
// all properties are obtained from the skill owner and the target to calculate the damage:
this.attackProperties = sc.hasOwn(props, 'attackProperties') ? props.attackProperties : [];
this.defenseProperties = sc.hasOwn(props, 'defenseProperties') ? props.defenseProperties : [];
// aim and dodge properties are used to determine if the skill could fail:
this.aimProperties = sc.hasOwn(props, 'aimProperties') ? props.aimProperties : [];
this.dodgeProperties = sc.hasOwn(props, 'dodgeProperties') ? props.dodgeProperties : [];
// dodgeFullEnabled will make the attack fail if: dodge > ownerAim * dodgeOverAimSuccess
this.dodgeFullEnabled = sc.hasOwn(props, 'dodgeFullEnabled') ? props.dodgeFullEnabled : true;
this.dodgeOverAimSuccess = sc.hasOwn(props, 'dodgeOverAimSuccess') ? props.dodgeOverAimSuccess : 1;
// specify if the damage or the critical will be affected by the aim and dodge properties:
this.damageAffected = sc.hasOwn(props, 'damageAffected') ? props.damageAffected : false;
this.criticalAffected = sc.hasOwn(props, 'criticalAffected') ? props.criticalAffected : false;
// properties total calculator operators:
// example: {
// {key: 'propName1', op: ModifierConst.OPS.INC},
// {key: 'propName2', op: ModifierConst.OPS.DEC}
// }
this.propertiesTotalOperators = sc.get(props, 'propertiesTotalOperators', {});
}
async runSkillLogic()
{
this.lastState = null;
if(!this.validateRange(this.target)){
// out of range, the owner or the target could move away
this.lastState = SkillsConst.SKILL_STATES.OUT_OF_RANGE;
return false;
}
this.lastState = SkillsConst.SKILL_STATES.APPLYING_DAMAGE;
return await this.applyDamageTo(this.target);
}
async applyDamageTo(target)
{
if(!target){
this.lastState = SkillsConst.SKILL_STATES.TARGET_NOT_AVAILABLE;
return false;
}
// @NOTE: both owner and target properties values has to be calculated at the time the skill is executed, that
// is why we are getting the target always as parameter.
// check if the skill can be dodged:
let ownerAim = this.getPropertiesTotal(this.owner, this.aimProperties);
if(false === ownerAim){
Logger.debug('Missing owner aim properties.', this.owner);
return false;
}
let targetDodge = this.getPropertiesTotal(target, this.dodgeProperties);
if(false === targetDodge){
Logger.debug('Missing target dodge properties.', target);
return false;
}
if(this.dodgeFullEnabled && targetDodge > (ownerAim * this.dodgeOverAimSuccess)){
this.lastState = SkillsConst.SKILL_STATES.DODGED;
return false;
}
let affectedPropertyValue = this.getAffectedPropertyValue(target);
if(!this.allowEffectBelowZero && 0 >= affectedPropertyValue){
return false;
}
// dodge proportion:
let dodgeAimDiff = this.getDiffProportion(ownerAim, targetDodge);
let damage = this.applyDirectDamage
? this.hitDamage // 100%
: this.calculateProportionDamage(target, this.hitDamage, targetDodge, ownerAim, dodgeAimDiff);
// critical calculation:
damage = damage + this.calculateCriticalDamage(damage, targetDodge, ownerAim, dodgeAimDiff);
// avoid getting below 0:
let modifiedValue = (!this.allowEffectBelowZero && affectedPropertyValue < damage)
? 0
: affectedPropertyValue - damage;
this.setAffectedPropertyValue(target, modifiedValue);
this.lastState = SkillsConst.SKILL_STATES.APPLIED_DAMAGE;
await this.fireEvent(SkillsEvents.SKILL_ATTACK_APPLY_DAMAGE, this, target, damage, modifiedValue);
}
calculateCriticalDamage(damage, targetDodge, ownerAim, dodgeAimDiff)
{
let criticalValue = this.getCriticalDiff(damage);
if(!this.criticalAffected || targetDodge > ownerAim){
return criticalValue;
}
this.lastState = SkillsConst.SKILL_STATES.APPLIED_CRITICAL_DAMAGE;
// calculate dodge proportion over critical damage and remove it from the critical total:
return criticalValue - (Math.floor((criticalValue * dodgeAimDiff / 100)));
}
calculateProportionDamage(target, damage, targetDodge, ownerAim, dodgeAimDiff)
{
let ownerAtk = this.getPropertiesTotal(this.owner, this.attackProperties);
let targetDef = this.getPropertiesTotal(target, this.defenseProperties);
// atk and def calculation to affect the hit damage:
let diff = ownerAtk - targetDef;
if(diff > 0){
let p = diff < targetDef ? (diff * 100 / targetDef) : 99;
p = p > 99 ? 99 : p; // maximum modifier percentage to add.
let additionalDamage = Math.ceil((p * damage / 100));
damage = damage + additionalDamage;
}
if(diff < 0){
let p = -diff < ownerAtk ? (-diff * 100 / ownerAtk) : 99;
p = p > 99 ? 99 : p; // maximum modifier percentage to remove.
let reduceDamage = Math.floor((p * damage / 100));
damage = damage - reduceDamage;
}
if(this.damageAffected && targetDodge > ownerAim){
// calculate dodge proportion over damage and remove it from the total:
let damageProportion = Math.floor((damage * dodgeAimDiff / 100));
damage = damage - damageProportion;
}
return damage;
}
getPropertiesTotal(object, propertiesArray)
{
if(!object){
Logger.warning('Missing object to get properties total.');
return false;
}
if(0 === propertiesArray.length){
return 0;
}
try {
let totalValue = 0;
for(let prop of propertiesArray){
let propValue = this.propertyManager.getPropertyValue(object, prop);
totalValue = sc.hasOwn(this.propertiesTotalOperators, prop)
? this.calculator.calculateNewValue(totalValue, this.propertiesTotalOperators[prop], propValue)
: totalValue + propValue;
}
return totalValue;
} catch (error) {
Logger.debug(error.message);
return false;
}
}
getDiffProportion(totalValue, upProportionValue)
{
let propertiesDiff = upProportionValue - totalValue;
return ((propertiesDiff * 100) / totalValue);
}
getAffectedPropertyValue(target)
{
return this.propertyManager.getPropertyValue(target, this.affectedProperty);
}
setAffectedPropertyValue(target, newValue)
{
return this.propertyManager.setOwnerProperty(target, this.affectedProperty, newValue);
}
}
module.exports = Attack;