Files
prestonbane/src/engine/gameManager/CombatManager.java
T

684 lines
29 KiB
Java
Raw Normal View History

2024-04-01 17:25:56 -04:00
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
2024-03-17 15:56:47 -05:00
package engine.gameManager;
import engine.job.JobContainer;
import engine.job.JobScheduler;
import engine.jobs.AttackJob;
import engine.jobs.DeferredPowerJob;
import engine.mbEnums;
2024-03-17 15:56:47 -05:00
import engine.net.client.ClientConnection;
import engine.net.client.msg.TargetedActionMsg;
import engine.net.client.msg.UpdateStateMsg;
import engine.objects.*;
import engine.powers.DamageShield;
import engine.powers.effectmodifiers.AbstractEffectModifier;
2024-06-22 19:50:53 -05:00
import engine.powers.effectmodifiers.WeaponProcEffectModifier;
2024-03-17 15:56:47 -05:00
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
2024-03-23 18:33:32 -04:00
import java.util.EnumSet;
2024-03-17 15:56:47 -05:00
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
2024-05-28 19:28:41 -05:00
import static java.lang.Math.pow;
2024-03-23 19:22:14 -04:00
public enum CombatManager {
2024-04-28 11:44:28 -05:00
// MB Dev notes:
// Class implements all combat mechanics for Magicbane.
//
// Combat initiates in "combatCycle" to determine which hands will be swung, and attack is processed based on that.
// Handles all combat for AbstractCharacter including player characters and mobiles.
// Controls toggling of combat and sit/stand for all AbstractCharacters
//
//
2024-03-23 19:22:14 -04:00
COMBAT_MANAGER;
2024-05-28 19:12:00 -05:00
public static final int COMBAT_BLOCK_ANIMATION = 298;
public static final int COMBAT_PARRY_ANIMATION = 299;
public static final int COMBAT_DODGE_ANIMATION = 300;
2024-03-23 19:22:14 -04:00
2024-03-17 15:56:47 -05:00
public static void combatCycle(AbstractCharacter attacker, AbstractWorldObject target) {
2024-03-23 18:33:32 -04:00
2024-03-17 15:56:47 -05:00
//early exit checks
2024-03-23 18:33:32 -04:00
if (attacker == null || target == null || !attacker.isAlive() || !target.isAlive())
2024-03-17 15:56:47 -05:00
return;
2024-04-21 12:56:41 -04:00
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
2024-04-13 19:44:03 -05:00
if (((Mob) attacker).nextAttackTime > System.currentTimeMillis())
return;
2024-03-23 18:33:32 -04:00
switch (target.getObjectType()) {
2024-03-17 15:56:47 -05:00
case Building:
2024-04-21 12:56:41 -04:00
if (!((Building) target).isVulnerable())
2024-03-17 15:56:47 -05:00
return;
break;
case PlayerCharacter:
case Mob:
2024-03-23 18:33:32 -04:00
PlayerBonuses bonuses = ((AbstractCharacter) target).getBonuses();
2024-07-21 21:55:08 -04:00
if (bonuses != null && bonuses.getBool(mbEnums.ModType.ImmuneToAttack, mbEnums.EffectSourceType.None))
2024-03-17 15:56:47 -05:00
return;
break;
case NPC:
return;
}
Item mainWeapon = attacker.charItemManager.getEquipped().get(mbEnums.EquipSlotType.RHELD);
Item offWeapon = attacker.charItemManager.getEquipped().get(mbEnums.EquipSlotType.LHELD);
2024-03-23 18:33:32 -04:00
if (mainWeapon == null && offWeapon == null) {
2024-03-17 15:56:47 -05:00
//no weapons equipped, punch with both fists
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
2024-04-21 12:56:41 -04:00
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter))
2024-04-13 19:12:09 -05:00
processAttack(attacker, target, mbEnums.EquipSlotType.LHELD);
2024-04-21 11:21:40 -05:00
return;
}
if (mainWeapon != null && offWeapon == null) {
//swing right hand only
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
return;
}
2024-05-17 20:34:26 -05:00
if (mainWeapon == null && offWeapon != null && !offWeapon.template.item_skill_used.equals("Block")) {
2024-04-21 11:21:40 -05:00
//swing left hand only
processAttack(attacker, target, mbEnums.EquipSlotType.LHELD);
return;
}
2024-05-17 20:34:26 -05:00
if (mainWeapon == null && offWeapon != null && offWeapon.template.item_skill_used.equals("Block")) {
2024-03-17 15:56:47 -05:00
//no weapon equipped with a shield, punch with one hand
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
2024-04-21 11:21:40 -05:00
return;
}
2024-05-17 20:34:26 -05:00
if (mainWeapon != null && offWeapon != null && offWeapon.template.item_skill_used.equals("Block")) {
2024-03-17 15:56:47 -05:00
//one weapon equipped with a shield, swing with one hand
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
2024-04-21 11:21:40 -05:00
return;
}
2024-05-17 20:34:26 -05:00
if (mainWeapon != null && offWeapon != null && !offWeapon.template.item_skill_used.equals("Block")) {
2024-03-17 15:56:47 -05:00
//two weapons equipped, swing both hands
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
2024-04-21 12:56:41 -04:00
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter))
2024-04-21 11:21:40 -05:00
processAttack(attacker, target, mbEnums.EquipSlotType.LHELD);
2024-03-17 15:56:47 -05:00
}
}
public static void processAttack(AbstractCharacter attacker, AbstractWorldObject target, mbEnums.EquipSlotType slot) {
2024-03-23 18:33:32 -04:00
2024-05-12 13:36:47 -04:00
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) {
2024-04-21 13:19:50 -05:00
if (!attacker.isCombat())
return;
2024-05-26 21:41:59 -05:00
//check if this slot is on attack timer, if timer has passed clear it, else early exit
2024-06-11 13:40:42 -04:00
if (attacker.getTimers() != null && attacker.getTimers().containsKey("Attack" + slot.name()))
if (attacker.getTimers().get("Attack" + slot.name()).timeToExecutionLeft() <= 0)
attacker.getTimers().remove("Attack" + slot.name());
2024-05-26 21:41:59 -05:00
else
return;
2024-04-21 13:19:50 -05:00
}
2024-05-26 21:41:59 -05:00
2024-03-23 18:33:32 -04:00
// check if character is in range to attack target
2024-03-17 15:56:47 -05:00
PlayerBonuses bonus = attacker.getBonuses();
2024-03-23 18:33:32 -04:00
2024-03-17 15:56:47 -05:00
float rangeMod = 1.0f;
float attackRange = MBServerStatics.NO_WEAPON_RANGE;
2024-03-23 18:33:32 -04:00
2024-03-18 10:01:29 -04:00
Item weapon = attacker.charItemManager.getEquipped(slot);
2024-03-23 18:33:32 -04:00
2024-03-17 15:56:47 -05:00
if (weapon != null) {
if (bonus != null)
2024-07-21 21:55:08 -04:00
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.EffectSourceType.None);
2024-03-17 15:56:47 -05:00
2024-05-27 19:50:52 -05:00
attackRange += weapon.template.item_weapon_max_range * rangeMod;
2024-03-17 15:56:47 -05:00
}
2024-03-23 18:33:32 -04:00
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
2024-03-23 18:33:32 -04:00
if (((Mob) attacker).isSiege())
2024-03-17 15:56:47 -05:00
attackRange = 300;
float distanceSquared = attacker.loc.distanceSquared(target.loc);
2024-03-23 18:33:32 -04:00
boolean inRange = false;
2024-05-27 19:59:35 -05:00
if (AbstractCharacter.IsAbstractCharacter(target)) {
2024-06-11 13:40:42 -04:00
attackRange += ((AbstractCharacter) target).calcHitBox();
2024-05-27 19:59:35 -05:00
} else {
}
2024-05-26 21:49:36 -05:00
2024-06-11 13:40:42 -04:00
if (attackRange > 15 && attacker.isMoving()) {
2024-05-26 21:49:36 -05:00
//cannot shoot bow while moving;
return;
}
2024-04-21 12:56:41 -04:00
switch (target.getObjectType()) {
case PlayerCharacter:
2024-04-21 12:56:41 -04:00
attackRange += ((PlayerCharacter) target).getCharacterHeight() * 0.5f;
if (distanceSquared <= attackRange * attackRange)
inRange = true;
break;
case Mob:
2024-04-21 12:56:41 -04:00
attackRange += ((AbstractCharacter) target).calcHitBox();
if (distanceSquared <= attackRange * attackRange)
inRange = true;
break;
case Building:
2024-06-11 13:40:42 -04:00
if (attackRange > 15) {
2024-05-22 20:49:33 -05:00
float rangeSquared = (attackRange + target.getBounds().getHalfExtents().x) * (attackRange + target.getBounds().getHalfExtents().x);
//float distanceSquared = attacker.loc.distanceSquared(target.loc);
2024-06-11 13:40:42 -04:00
if (distanceSquared < rangeSquared) {
inRange = true;
break;
}
2024-06-11 13:40:42 -04:00
} else {
float locX = target.loc.x - target.getBounds().getHalfExtents().x;
float locZ = target.loc.z - target.getBounds().getHalfExtents().y;
float sizeX = (target.getBounds().getHalfExtents().x + attackRange) * 2;
float sizeZ = (target.getBounds().getHalfExtents().y + attackRange) * 2;
Rectangle2D.Float rect = new Rectangle2D.Float(locX, locZ, sizeX, sizeZ);
if (rect.contains(new Point2D.Float(attacker.loc.x, attacker.loc.z)))
inRange = true;
break;
}
}
2024-04-13 19:44:03 -05:00
//get delay for the auto attack job
long delay = 5000;
if (weapon != null) {
float wepSpeed = (int) (weapon.template.item_weapon_wepspeed);
2024-04-13 19:44:03 -05:00
2024-07-21 21:55:08 -04:00
if (weapon.getBonusPercent(mbEnums.ModType.WeaponSpeed, mbEnums.EffectSourceType.None) != 0f) //add weapon speed bonus
wepSpeed *= (1 + weapon.getBonus(mbEnums.ModType.WeaponSpeed, mbEnums.EffectSourceType.None));
2024-04-13 19:44:03 -05:00
2024-07-21 21:55:08 -04:00
if (attacker.getBonuses() != null && attacker.getBonuses().getFloatPercentAll(mbEnums.ModType.AttackDelay, mbEnums.EffectSourceType.None) != 0f) //add effects speed bonus
wepSpeed *= (1 + attacker.getBonuses().getFloatPercentAll(mbEnums.ModType.AttackDelay, mbEnums.EffectSourceType.None));
2024-04-13 19:44:03 -05:00
if (wepSpeed < 10)
wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects.
delay = (long)wepSpeed * 100L;
2024-04-13 19:44:03 -05:00
}
2024-04-21 12:56:41 -04:00
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
((Mob) attacker).nextAttackTime = System.currentTimeMillis() + delay;
2024-04-08 20:37:27 -05:00
if (inRange) {
2024-03-17 15:56:47 -05:00
2024-07-21 19:46:06 -05:00
if(attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)){
if(!attacker.getTimestamps().contains(slot.name()+"Attack")){
attacker.getTimestamps().put(slot.name()+"Attack", System.currentTimeMillis() - 1000);
} else if(System.currentTimeMillis() < attacker.getTimestamps().get(slot.name()+"Attack") + delay){
setAutoAttackJob(attacker,slot,delay);
return;
}
}
2024-04-09 21:23:29 -05:00
//handle retaliate
2024-04-21 12:56:41 -04:00
if (AbstractCharacter.IsAbstractCharacter(target)) {
if (((AbstractCharacter) target).combatTarget == null || !((AbstractCharacter) target).combatTarget.isAlive()) {
((AbstractCharacter) target).combatTarget = attacker;
if (target.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && ((AbstractCharacter) target).isCombat())
2024-04-13 19:44:03 -05:00
combatCycle((AbstractCharacter) target, attacker);
2024-04-09 21:23:29 -05:00
}
}
2024-05-28 19:28:41 -05:00
// take stamina away from attacker if its not a mob
if (weapon != null && !attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) {
//check if Out of Stamina
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) {
if (attacker.getStamina() < (weapon.template.item_wt / 3f)) {
//set auto attack job
setAutoAttackJob(attacker, slot, delay);
return;
}
}
2024-04-08 20:37:27 -05:00
float stam = weapon.template.item_wt / 3f;
stam = (stam < 1) ? 1 : stam;
attacker.modifyStamina(-(stam), attacker, true);
} else
attacker.modifyStamina(1, attacker, true);
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
//cancel things that are cancelled by an attack
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
attacker.cancelOnAttackSwing();
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
//declare relevant variables
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
int min = attacker.minDamageHandOne;
int max = attacker.maxDamageHandOne;
int atr = attacker.atrHandOne;
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
//get the proper stats based on which slot is attacking
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (slot == mbEnums.EquipSlotType.LHELD) {
min = attacker.minDamageHandTwo;
max = attacker.maxDamageHandTwo;
atr = attacker.atrHandTwo;
}
2024-03-23 18:33:32 -04:00
//apply weapon powers before early exit for miss or passives
DeferredPowerJob dpj = null;
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) {
dpj = ((PlayerCharacter) attacker).getWeaponPower();
if (dpj != null) {
dpj.attack(target, attackRange);
if (dpj.getPower() != null && (dpj.getPowerToken() == -1851459567 || dpj.getPowerToken() == -1851489518))
((PlayerCharacter) attacker).setWeaponPower(dpj);
}
}
2024-04-08 20:37:27 -05:00
int def = 0;
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (AbstractCharacter.IsAbstractCharacter(target))
def = ((AbstractCharacter) target).defenseRating;
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
//calculate hit chance based off ATR and DEF
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
int hitChance;
if (def == 0)
def = 1;
2024-04-21 13:21:00 -04:00
float dif = atr * 1f / def;
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (dif <= 0.8f)
hitChance = 4;
else
hitChance = ((int) (450 * (dif - 0.8f)) + 4);
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (target.getObjectType() == mbEnums.GameObjectType.Building)
hitChance = 100;
2024-05-28 18:55:54 -05:00
int passiveAnim = getPassiveAnimation(mbEnums.PassiveType.None); // checking for a miss due to ATR vs Def
2024-04-08 20:37:27 -05:00
if (ThreadLocalRandom.current().nextInt(100) > hitChance) {
TargetedActionMsg msg = new TargetedActionMsg(attacker, target, 0f, passiveAnim);
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
if (target.getObjectType() == mbEnums.GameObjectType.PlayerCharacter)
DispatchManager.dispatchMsgToInterestArea(target, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
2024-05-29 18:39:24 -05:00
else
DispatchManager.sendToAllInRange(attacker, msg);
//we need to send the animation even if the attacker misses
2024-06-11 13:40:42 -04:00
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
2024-05-29 18:39:24 -05:00
DispatchManager.sendToAllInRange(target, cmm);
2024-03-17 15:56:47 -05:00
2024-04-21 11:14:10 -05:00
//set auto attack job
2024-04-21 12:56:41 -04:00
setAutoAttackJob(attacker, slot, delay);
2024-04-08 20:37:27 -05:00
return;
}
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
//calculate passive chances only if target is AbstractCharacter
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (EnumSet.of(mbEnums.GameObjectType.PlayerCharacter, mbEnums.GameObjectType.NPC, mbEnums.GameObjectType.Mob).contains(target.getObjectType())) {
mbEnums.PassiveType passiveType = mbEnums.PassiveType.None;
int hitRoll = ThreadLocalRandom.current().nextInt(100);
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
float dodgeChance = ((AbstractCharacter) target).getPassiveChance("Dodge", attacker.getLevel(), true);
float blockChance = ((AbstractCharacter) target).getPassiveChance("Block", attacker.getLevel(), true);
float parryChance = ((AbstractCharacter) target).getPassiveChance("Parry", attacker.getLevel(), true);
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
// Passive chance clamped at 75
2024-03-23 18:39:03 -04:00
2024-04-08 20:37:27 -05:00
dodgeChance = Math.max(0, Math.min(75, dodgeChance));
blockChance = Math.max(0, Math.min(75, blockChance));
parryChance = Math.max(0, Math.min(75, parryChance));
2024-03-23 18:39:03 -04:00
2024-04-08 20:37:27 -05:00
if (hitRoll < dodgeChance)
passiveType = mbEnums.PassiveType.Dodge;
else if (hitRoll < blockChance)
passiveType = mbEnums.PassiveType.Block;
else if (hitRoll < parryChance)
passiveType = mbEnums.PassiveType.Parry;
2024-03-23 18:33:32 -04:00
2024-04-21 12:56:41 -04:00
if (!passiveType.equals(mbEnums.PassiveType.None)) {
2024-05-28 18:55:54 -05:00
passiveAnim = getPassiveAnimation(passiveType);
2024-04-08 20:37:27 -05:00
TargetedActionMsg msg = new TargetedActionMsg(attacker, passiveAnim, target, passiveType.value);
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
if (target.getObjectType() == mbEnums.GameObjectType.PlayerCharacter)
DispatchManager.dispatchMsgToInterestArea(target, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
2024-03-17 15:56:47 -05:00
//we need to send the animation even if the attacker misses
2024-06-11 13:40:42 -04:00
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
DispatchManager.sendToAllInRange(target, cmm);
2024-04-21 11:14:10 -05:00
//set auto attack job
2024-04-21 12:56:41 -04:00
setAutoAttackJob(attacker, slot, delay);
2024-04-08 20:37:27 -05:00
return;
}
2024-03-17 15:56:47 -05:00
}
2024-07-10 19:45:38 -05:00
//check for proccing
checkForProc(attacker,target,weapon);
2024-04-08 20:37:27 -05:00
//calculate the base damage
int damage = ThreadLocalRandom.current().nextInt(min, max + 1);
2024-04-13 19:44:03 -05:00
if (damage == 0) {
2024-04-21 11:14:10 -05:00
//set auto attack job
2024-04-21 12:56:41 -04:00
setAutoAttackJob(attacker, slot, delay);
2024-04-08 20:37:27 -05:00
return;
2024-04-13 19:44:03 -05:00
}
2024-06-11 13:40:42 -04:00
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob) && ((Mob) attacker).isPet())
2024-05-28 19:28:41 -05:00
calculatePetDamage(attacker);
2024-04-08 20:37:27 -05:00
//get the damage type
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
mbEnums.DamageType damageType;
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (attacker.charItemManager.getEquipped().get(slot) == null) {
damageType = mbEnums.DamageType.CRUSHING;
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
if (((Mob) attacker).isSiege())
damageType = mbEnums.DamageType.SIEGE;
} else {
damageType = (mbEnums.DamageType) attacker.charItemManager.getEquipped().get(slot).template.item_weapon_damage.keySet().toArray()[0];
}
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
//get resists
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
Resists resists;
2024-03-17 15:56:47 -05:00
2024-04-21 12:56:41 -04:00
if (!AbstractCharacter.IsAbstractCharacter(target))
2024-04-08 20:37:27 -05:00
resists = ((Building) target).getResists(); //this is a building
else
resists = ((AbstractCharacter) target).getResists(); //this is a character
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (AbstractCharacter.IsAbstractCharacter(target)) {
AbstractCharacter absTarget = (AbstractCharacter) target;
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
//check damage shields
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
PlayerBonuses bonuses = absTarget.getBonuses();
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
if (bonuses != null) {
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
ConcurrentHashMap<AbstractEffectModifier, DamageShield> damageShields = bonuses.getDamageShields();
float total = 0;
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
for (DamageShield ds : damageShields.values()) {
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
//get amount to damage back
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
float amount;
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (ds.usePercent())
amount = damage * ds.getAmount() / 100;
else
amount = ds.getAmount();
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
//get resisted damage for damagetype
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (resists != null)
amount = resists.getResistedDamage(absTarget, attacker, ds.getDamageType(), amount, 0);
total += amount;
}
2024-03-17 15:56:47 -05:00
2024-04-08 20:37:27 -05:00
if (total > 0) {
//apply Damage back
attacker.modifyHealth(-total, absTarget, true);
TargetedActionMsg cmm = new TargetedActionMsg(attacker, attacker, total, 0);
DispatchManager.sendToAllInRange(target, cmm);
2024-04-08 20:37:27 -05:00
}
2024-03-17 15:56:47 -05:00
}
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (resists != null) {
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
//check for damage type immunities
2024-03-23 18:33:32 -04:00
2024-04-13 19:44:03 -05:00
if (resists.immuneTo(damageType)) {
2024-04-21 11:14:10 -05:00
//set auto attack job
//we need to send the animation even if the attacker misses
2024-06-11 13:40:42 -04:00
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
DispatchManager.sendToAllInRange(target, cmm);
2024-04-21 12:56:41 -04:00
setAutoAttackJob(attacker, slot, delay);
2024-04-13 19:44:03 -05:00
return;
}
2024-04-08 20:37:27 -05:00
//calculate resisted damage including fortitude
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
damage = (int) resists.getResistedDamage(attacker, (AbstractCharacter) target, damageType, damage, 0);
}
2024-03-17 15:56:47 -05:00
}
2024-04-08 20:37:27 -05:00
//remove damage from target health
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (damage > 0) {
2024-03-23 18:33:32 -04:00
2024-04-08 20:37:27 -05:00
if (AbstractCharacter.IsAbstractCharacter(target))
((AbstractCharacter) target).modifyHealth(-damage, attacker, true);
else
2024-05-22 20:49:33 -05:00
((Building) target).modifyHealth(-damage, attacker);
2024-03-23 18:33:32 -04:00
2024-05-28 18:55:54 -05:00
int attackAnim = getSwingAnimation(null, null, slot);
2024-04-08 20:37:27 -05:00
if (attacker.charItemManager.getEquipped().get(slot) != null) {
2024-05-12 13:36:47 -04:00
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) {
2024-04-21 12:21:34 -05:00
DeferredPowerJob weaponPower = ((PlayerCharacter) attacker).getWeaponPower();
2024-05-29 18:39:24 -05:00
attackAnim = getSwingAnimation(weapon.template, weaponPower, slot);
2024-05-12 13:36:47 -04:00
} else {
2024-05-29 18:39:24 -05:00
attackAnim = getSwingAnimation(weapon.template, null, slot);
2024-04-21 12:21:34 -05:00
}
2024-04-08 20:37:27 -05:00
}
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) damage, attackAnim);
DispatchManager.sendToAllInRange(target, cmm);
2024-03-28 20:25:40 -05:00
}
2024-03-29 19:11:47 -05:00
}
2024-04-21 12:43:23 -05:00
2024-04-21 11:14:10 -05:00
//set auto attack job
2024-04-21 12:56:41 -04:00
setAutoAttackJob(attacker, slot, delay);
2024-03-23 18:33:32 -04:00
2024-03-17 15:56:47 -05:00
}
public static void toggleCombat(boolean toggle, ClientConnection origin) {
2024-03-23 18:33:32 -04:00
PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin);
if (playerCharacter == null)
2024-03-17 15:56:47 -05:00
return;
2024-03-23 18:33:32 -04:00
playerCharacter.setCombat(toggle);
2024-03-17 15:56:47 -05:00
if (!toggle) // toggle is move it to false so clear combat target
2024-03-23 18:33:32 -04:00
playerCharacter.setCombatTarget(null); //clear last combat target
2024-03-17 15:56:47 -05:00
UpdateStateMsg rwss = new UpdateStateMsg();
2024-03-23 18:33:32 -04:00
rwss.setPlayer(playerCharacter);
DispatchManager.dispatchMsgToInterestArea(playerCharacter, rwss, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
2024-03-17 15:56:47 -05:00
}
public static void toggleSit(boolean toggle, ClientConnection origin) {
2024-03-23 18:33:32 -04:00
PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin);
if (playerCharacter == null)
2024-03-17 15:56:47 -05:00
return;
2024-03-23 18:33:32 -04:00
playerCharacter.setSit(toggle);
2024-03-17 15:56:47 -05:00
UpdateStateMsg rwss = new UpdateStateMsg();
2024-03-23 18:33:32 -04:00
rwss.setPlayer(playerCharacter);
DispatchManager.dispatchMsgToInterestArea(playerCharacter, rwss, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
2024-03-17 15:56:47 -05:00
}
2024-03-23 18:33:32 -04:00
2024-03-17 15:56:47 -05:00
public static void handleRetaliate(AbstractCharacter target, AbstractCharacter attacker) {
2024-03-23 18:33:32 -04:00
//Called when character takes damage.
2024-03-17 15:56:47 -05:00
if (attacker == null || target == null)
return;
if (attacker.equals(target))
return;
if (target.isMoving() && target.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter))
2024-03-17 15:56:47 -05:00
return;
if (!target.isAlive() || !attacker.isAlive())
return;
boolean isCombat = target.isCombat();
//If target in combat and has no target, then attack back
2024-03-23 18:33:32 -04:00
if (isCombat && target.combatTarget == null)
2024-03-17 15:56:47 -05:00
target.setCombatTarget(attacker);
}
2024-05-28 18:55:54 -05:00
public static int getSwingAnimation(ItemTemplate wb, DeferredPowerJob dpj, mbEnums.EquipSlotType slot) {
//No weapon, return default animation
if (wb == null)
return 75;
2024-03-23 18:33:32 -04:00
2024-04-21 12:56:41 -04:00
int token;
2024-03-17 15:56:47 -05:00
2024-04-21 11:46:45 -05:00
if (dpj != null) {
2024-04-21 12:56:41 -04:00
2024-03-17 15:56:47 -05:00
token = (dpj.getPower() != null) ? dpj.getPower().getToken() : 0;
2024-04-21 11:46:45 -05:00
if (token == 563721004) //kick animation
return 79;
2024-05-28 18:55:54 -05:00
}
//Item has no equipment slots and should not try to return an animation, return default instead
2024-06-11 13:40:42 -04:00
if (wb.item_eq_slots_or == null || wb.item_eq_slots_or.isEmpty()) {
2024-05-28 18:55:54 -05:00
return 75;
}
2024-04-21 12:56:41 -04:00
2024-05-28 18:55:54 -05:00
//declare variables
int anim;
int random;
//Item can only be equipped in one slot, return animation for that slot
2024-06-11 13:40:42 -04:00
if (wb.item_eq_slots_or.size() == 1) {
2024-05-28 18:55:54 -05:00
if (wb.item_eq_slots_or.iterator().next().equals(mbEnums.EquipSlotType.RHELD)) {
anim = wb.weapon_attack_anim_right.get(0)[0];
if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_right.size());
anim = wb.weapon_attack_anim_right.get(random)[0];
}
2024-06-11 13:40:42 -04:00
} else {
anim = wb.weapon_attack_anim_left.get(0)[0];
2024-05-28 18:55:54 -05:00
if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_left.size());
anim = wb.weapon_attack_anim_left.get(random)[0];
2024-04-21 12:43:23 -05:00
}
}
2024-05-28 18:55:54 -05:00
return anim;
2024-03-17 15:56:47 -05:00
}
2024-05-28 18:55:54 -05:00
//Item can be equipped in either hand, and should have animation sets for each hand
if (slot.equals(mbEnums.EquipSlotType.RHELD)) {
anim = wb.weapon_attack_anim_right.get(0)[0];
if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_right.size());
anim = wb.weapon_attack_anim_right.get(random)[0];
}
2024-06-11 13:40:42 -04:00
} else {
2024-05-28 18:55:54 -05:00
anim = wb.weapon_attack_anim_left.get(0)[0];
if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_left.size());
anim = wb.weapon_attack_anim_left.get(random)[0];
}
}
return anim;
}
2024-04-21 12:56:41 -04:00
2024-06-11 13:40:42 -04:00
public static int getPassiveAnimation(mbEnums.PassiveType passiveType) {
switch (passiveType) {
2024-05-28 18:55:54 -05:00
case Block:
2024-05-28 19:12:00 -05:00
return COMBAT_BLOCK_ANIMATION;
2024-05-28 18:55:54 -05:00
case Parry:
2024-05-28 19:12:00 -05:00
return COMBAT_PARRY_ANIMATION;
2024-05-28 19:09:46 -05:00
case Dodge:
2024-05-28 19:12:00 -05:00
return COMBAT_DODGE_ANIMATION;
2024-05-28 18:55:54 -05:00
default:
return 0;
}
2024-03-17 15:56:47 -05:00
}
2024-04-21 11:14:10 -05:00
2024-04-21 12:56:41 -04:00
public static void setAutoAttackJob(AbstractCharacter attacker, mbEnums.EquipSlotType slot, long delay) {
2024-04-21 11:14:10 -05:00
//calculate next allowed attack and update the timestamp
2024-05-26 20:31:22 -05:00
2024-06-11 13:40:42 -04:00
if (attacker.getTimestamps().containsKey("Attack" + slot.name()) && attacker.getTimestamps().get("Attack" + slot.name()) > System.currentTimeMillis())
2024-05-26 20:31:22 -05:00
return;
2024-04-21 11:14:10 -05:00
attacker.getTimestamps().put("Attack" + slot.name(), System.currentTimeMillis() + delay);
//handle auto attack job creation
ConcurrentHashMap<String, JobContainer> timers = attacker.getTimers();
if (timers != null) {
AttackJob aj = new AttackJob(attacker, slot.ordinal(), true);
JobContainer job;
job = JobScheduler.getInstance().scheduleJob(aj, (System.currentTimeMillis() + delay)); // offset 1 millisecond so no overlap issue
timers.put("Attack" + slot.name(), job);
2024-04-21 11:14:10 -05:00
} else
Logger.error("Unable to find Timers for Character " + attacker.getObjectUUID());
}
2024-06-11 13:40:42 -04:00
2024-05-28 19:28:41 -05:00
public static int calculatePetDamage(AbstractCharacter agent) {
//damage calc for pet
float range;
float damage;
float min = 40;
float max = 60;
2024-07-21 21:55:08 -04:00
float dmgMultiplier = 1 + agent.getBonuses().getFloatPercentAll(mbEnums.ModType.MeleeDamageModifier, mbEnums.EffectSourceType.None);
2024-05-28 19:28:41 -05:00
double minDmg = getMinDmg(min, agent);
double maxDmg = getMaxDmg(max, agent);
dmgMultiplier += agent.getLevel() * 0.1f;
range = (float) (maxDmg - minDmg);
damage = min + ((ThreadLocalRandom.current().nextFloat() * range) + (ThreadLocalRandom.current().nextFloat() * range)) / 2;
return (int) (damage * dmgMultiplier);
}
2024-06-11 13:40:42 -04:00
2024-05-28 19:28:41 -05:00
public static double getMinDmg(double min, AbstractCharacter agent) {
int primary = agent.getStatStrCurrent();
int secondary = agent.getStatDexCurrent();
int focusLevel = 0;
int masteryLevel = 0;
return min * (pow(0.0048 * primary + .049 * (primary - 0.75), 0.5) + pow(0.0066 * secondary + 0.064 * (secondary - 0.75), 0.5) + +0.01 * (focusLevel + masteryLevel));
}
2024-06-11 13:40:42 -04:00
2024-05-28 19:28:41 -05:00
public static double getMaxDmg(double max, AbstractCharacter agent) {
int primary = agent.getStatStrCurrent();
int secondary = agent.getStatDexCurrent();
int focusLevel = 0;
int masteryLevel = 0;
return max * (pow(0.0124 * primary + 0.118 * (primary - 0.75), 0.5) + pow(0.0022 * secondary + 0.028 * (secondary - 0.75), 0.5) + 0.0075 * (focusLevel + masteryLevel));
}
2024-06-22 19:50:53 -05:00
public static void checkForProc(AbstractCharacter attacker, AbstractWorldObject target, Item weapon){
if(weapon == null) // cant proc without a weapon
return;
for(Effect eff : weapon.effects.values()){
for(AbstractEffectModifier mod : eff.getEffectsBase().getModifiers()){
if(mod.modType.equals(mbEnums.ModType.WeaponProc))
if(ThreadLocalRandom.current().nextInt(0,101) < 6)
((WeaponProcEffectModifier)mod).applyProc(attacker,target);
}
}
}
2024-03-17 15:56:47 -05:00
}