UNPKG

@reldens/skills

Version:
203 lines (189 loc) 8.93 kB
/** * * 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 { Logger, sc } = require('@reldens/utils'); class Attack extends Skill { constructor(props) { super(props); this.type = SkillsConst.SKILL.TYPE.ATTACK; let affectedProperty = sc.get(props, 'affectedProperty', false); if(!affectedProperty){ Logger.error('Missing skill affectedProperty to which the result damage will be applied.'); this.isReady = false; } // the affected property is the one to which the damage calculation will have effect: this.affectedProperty = affectedProperty; this.allowEffectBelowZero = sc.get(props, 'allowEffectBelowZero', false); // hit damage value at 100%: this.hitDamage = sc.get(props, 'hitDamage', 0); this.applyDirectDamage = sc.get(props, 'applyDirectDamage', false); // all properties are obtained from the skill owner and the target to calculate the damage: this.attackProperties = sc.get(props, 'attackProperties', []); this.defenseProperties = sc.get(props, 'defenseProperties', []); // aim and dodge properties are used to determine if the skill could fail: this.aimProperties = sc.get(props, 'aimProperties', []); this.dodgeProperties = sc.get(props, 'dodgeProperties', []); // dodgeFullEnabled will make the attack fail if: dodge > ownerAim * dodgeOverAimSuccess this.dodgeFullEnabled = sc.get(props, 'dodgeFullEnabled', true); this.dodgeOverAimSuccess = sc.get(props, 'dodgeOverAimSuccess', 1); // specify if the damage or the critical will be affected by the aim and dodge properties: this.damageAffected = sc.get(props, 'damageAffected', false); this.criticalAffected = sc.get(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.target || !this.owner){ this.lastState = SkillsConst.SKILL_STATES.TARGET_NOT_AVAILABLE; return false; } if(!this.isInRange(this.owner.getPosition(), this.target.getPosition())){ // 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); return true; } calculateCriticalDamage(damage, targetDodge, ownerAim, dodgeAimDiff) { let criticalValue = this.getCriticalDiff(damage); // if criticalAffected is false OR dodge is NOT greater than aim, return full critical value // if criticalAffected is true AND dodge > aim, apply dodge reduction to critical if(!this.criticalAffected || targetDodge <= ownerAim){ return criticalValue; } this.lastState = SkillsConst.SKILL_STATES.APPLIED_CRITICAL_DAMAGE; // calculate the 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 a dodge proportion over damage and remove it from the total: let damageProportion = Math.floor((damage * dodgeAimDiff / 100)); damage = damage - damageProportion; } return damage; } getPropertiesTotal(objectInstance, propertiesArray) { if(!objectInstance){ 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(objectInstance, 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) { // prevent division by zero when totalValue (for example, "aim") is 0: if(0 === totalValue){ // if both "aim" and "dodge" are 0, no advantage either way so we return 0% reduction: if(0 === upProportionValue){ return 0; } // if aim is 0 but dodge > 0, target has maximum advantage so we return 100% reduction: return 100; } let propertiesDiff = upProportionValue - totalValue; return ((propertiesDiff * 100) / totalValue); } getAffectedPropertyValue(target) { return this.propertyManager.getPropertyValue(target, this.affectedProperty); } setAffectedPropertyValue(target, value) { return this.propertyManager.setOwnerProperty(target, this.affectedProperty, value); } } module.exports = Attack;