diff --git a/src/engine/gameManager/CombatSystem.java b/src/engine/gameManager/CombatSystem.java new file mode 100644 index 00000000..e388b837 --- /dev/null +++ b/src/engine/gameManager/CombatSystem.java @@ -0,0 +1,329 @@ +package engine.gameManager; + +import engine.Enum; +import engine.jobs.DeferredPowerJob; +import engine.net.DispatchMessage; +import engine.net.client.msg.TargetedActionMsg; +import engine.objects.*; +import engine.powers.DamageShield; +import engine.powers.effectmodifiers.AbstractEffectModifier; +import engine.powers.effectmodifiers.WeaponProcEffectModifier; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class CombatSystem { + + public static void attemptCombat(AbstractCharacter source, AbstractWorldObject target, boolean mainhand){ + + //1. source or target doesn't exist, early exit + if(source == null || target == null) + return; + + //2. source or target is dead, early exit + if(!source.isAlive() || !target.isAlive()) + return; + + //3. make sure if target is a building to ensure that it is damageable + if(target.getObjectType().equals(Enum.GameObjectType.Building)){ + Building building = (Building)target; + if(building.assetIsProtected() || building.getProtectionState().equals(Enum.ProtectionState.NPC)) + return; + } + + //after thought: make sure target is in range of source + if(!inRange(source,target,mainhand)) + return; + + //4. apply any weapon powers and then clear the weapon power memory for the player + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter)source; + if(pc.getWeaponPower() != null){ + pc.getWeaponPower().attack(target,pc.getRange()); + pc.setWeaponPower(null); + } + } + + //5. make sure if target is AbstractCharacter to check for defense trigger and passive trigger + if(AbstractCharacter.IsAbstractCharacter(target)) { + int atr; + int def; + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter)source; + if(pc.combatStats == null) + pc.combatStats = new PlayerCombatStats(pc); + atr = (int) pc.combatStats.atrHandOne; + if(!mainhand) + atr =(int) pc.combatStats.atrHandTwo; + + def = pc.combatStats.defense; + } else { + atr = (int) ((source.getAtrHandOne() + source.getAtrHandTwo()) * 0.5f); + def = source.defenseRating; + } + + if(!LandHit(atr,def)) + return; + + if(source.getBonuses() != null) + if(!source.getBonuses().getBool(Enum.ModType.IgnorePassiveDefense, Enum.SourceType.None)) + if(triggerPassive(source,target)) + return; + } + + //commence actual combat management + + //6. check for any procs + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter)source; + if(pc.getCharItemManager() != null && pc.getCharItemManager().getEquipped() != null){ + Item weapon = pc.getCharItemManager().getEquipped(1); + if(!mainhand) + weapon = pc.getCharItemManager().getEquipped(2); + if(weapon != null){ + if(weapon.effects != null){ + for (Effect eff : weapon.effects.values()){ + for(AbstractEffectModifier mod : eff.getEffectModifiers()){ + if(mod.modType.equals(Enum.ModType.WeaponProc)){ + int procChance = ThreadLocalRandom.current().nextInt(0,101); + if (procChance <= MBServerStatics.PROC_CHANCE) { + try { + ((WeaponProcEffectModifier) mod).applyProc(source, target); + }catch(Exception e){ + Logger.error(eff.getName() + " Failed To Cast Proc"); + } + } + } + } + } + } + } + } + } + + //7. configure damage amounts and type + Enum.DamageType damageType = Enum.DamageType.Crush; + int min = 0; + int max = 0; + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) source; + if(mainhand){ + min = pc.combatStats.minDamageHandOne; + max = pc.combatStats.maxDamageHandOne; + }else{ + min = pc.combatStats.minDamageHandTwo; + max = pc.combatStats.maxDamageHandTwo; + } + }else if (source.getObjectType().equals(Enum.GameObjectType.Mob)) { + Mob mob = (Mob) source; + min = (int) mob.mobBase.getDamageMin(); + max = (int) mob.mobBase.getDamageMax(); + } + + int damage = ThreadLocalRandom.current().nextInt(min,max + 1); + + if(source.getBonuses() != null){ + damage *= 1 + source.getBonuses().getFloatPercentAll(Enum.ModType.MeleeDamageModifier, Enum.SourceType.None); + } + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) { + PlayerCharacter pc = (PlayerCharacter) source; + damage *= pc.ZergMultiplier; + } + + //8. configure the attack message to be sent to the clients + int animation = 0; + ItemBase wb = null; + if(source.getCharItemManager() != null && source.getCharItemManager().getEquipped() != null) { + Item weapon = source.getCharItemManager().getEquipped(1); + if (!mainhand) + weapon = source.getCharItemManager().getEquipped(2); + + if(weapon != null && weapon.getItemBase().getAnimations() != null && !weapon.getItemBase().getAnimations().isEmpty()){ + animation = weapon.getItemBase().getAnimations().get(0); + wb = weapon.getItemBase(); + damageType = wb.getDamageType(); + } + } + + //9. reduce damage from resists and apply damage shields + if(AbstractCharacter.IsAbstractCharacter(target)){ + AbstractCharacter abs = (AbstractCharacter) target; + damage = (int) abs.getResists().getResistedDamage(source, abs,damageType,damage,1); + handleDamageShields(source,abs,damage); + } + + + + sendCombatMessage(source, target, 0f, wb, null, mainhand, animation); + + //if attacker is player, set last attack timestamp + if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) + updateAttackTimers((PlayerCharacter) source, target); + + //10. cancel all effects that cancel on attack + source.cancelOnAttack(); + } + + public static boolean LandHit(int ATR, int DEF){ + + int roll = ThreadLocalRandom.current().nextInt(101); + + float chance = PlayerCombatStats.getHitChance(ATR,DEF); + return chance >= roll; + } + + private static void sendCombatMessage(AbstractCharacter source, AbstractWorldObject target, float damage, ItemBase wb, DeferredPowerJob dpj, boolean mainHand, int swingAnimation) { + + if (dpj != null) + if (PowersManager.AnimationOverrides.containsKey(dpj.getAction().getEffectID())) + swingAnimation = PowersManager.AnimationOverrides.get(dpj.getAction().getEffectID()); + + if (source.getObjectType() == Enum.GameObjectType.PlayerCharacter) + for (Effect eff : source.getEffects().values()) + if (eff.getPower() != null && (eff.getPower().getToken() == 429506943 || eff.getPower().getToken() == 429408639 || eff.getPower().getToken() == 429513599 || eff.getPower().getToken() == 429415295)) + swingAnimation = 0; + + TargetedActionMsg cmm = new TargetedActionMsg(source, target, damage, swingAnimation); + DispatchMessage.sendToAllInRange(target, cmm); + } + + private static void updateAttackTimers(PlayerCharacter pc, AbstractWorldObject target) { + + //Set Attack Timers + + if (target.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) + pc.setLastPlayerAttackTime(); + } + + public static void handleDamageShields(AbstractCharacter ac, AbstractCharacter target, float damage) { + + if (ac == null || target == null) + return; + + PlayerBonuses bonuses = target.getBonuses(); + + if (bonuses != null) { + + ConcurrentHashMap damageShields = bonuses.getDamageShields(); + float total = 0; + + for (DamageShield ds : damageShields.values()) { + + //get amount to damage back + + float amount; + + if (ds.usePercent()) + amount = damage * ds.getAmount() / 100; + else + amount = ds.getAmount(); + + //get resisted damage for damagetype + + Resists resists = ac.getResists(); + + if (resists != null) { + amount = resists.getResistedDamage(target, ac, ds.getDamageType(), amount, 0); + } + total += amount; + } + + if (total > 0) { + + //apply Damage back + + ac.modifyHealth(-total, target, true); + + TargetedActionMsg cmm = new TargetedActionMsg(ac, ac, total, 0); + DispatchMessage.sendToAllInRange(target, cmm); + + } + } + } + + public static boolean inRange(AbstractCharacter source, AbstractWorldObject target, boolean mainhand){ + + if(source == null || target == null) + return false; + + float distanceSquared = source.loc.distanceSquared(target.loc); + + float rangeSquared = 16.0f; + + if(source.getCharItemManager() != null && source.getCharItemManager().getEquipped() != null){ + Item weapon = source.getCharItemManager().getEquipped(1); + if(!mainhand) + weapon = source.getCharItemManager().getEquipped(2); + if(weapon != null) + rangeSquared = weapon.getItemBase().getRange() * weapon.getItemBase().getRange(); + } + + if(source.getBonuses() != null){ + rangeSquared *= 1 + source.getBonuses().getFloatPercentAll(Enum.ModType.WeaponRange, Enum.SourceType.None); + } + + return distanceSquared <= rangeSquared; + } + + public static boolean triggerPassive(AbstractCharacter source, AbstractWorldObject target) { + boolean passiveFired = false; + + if (!AbstractCharacter.IsAbstractCharacter(target)) + return false; + + AbstractCharacter tarAc = (AbstractCharacter) target; + //Handle Block passive + if (testPassive(source, tarAc, "Block")) { + sendPassiveDefenseMessage(source, null, target, MBServerStatics.COMBAT_SEND_DODGE, null, true); + return true; + } + + //Handle Parry passive + if (testPassive(source, tarAc, "Parry")) { + sendPassiveDefenseMessage(source, null, target, MBServerStatics.COMBAT_SEND_DODGE, null, true); + return true; + } + + //Handle Dodge passive + if (testPassive(source, tarAc, "Dodge")) { + sendPassiveDefenseMessage(source, null, target, MBServerStatics.COMBAT_SEND_DODGE, null, true); + return true; + } + return false; + } + + private static void sendPassiveDefenseMessage(AbstractCharacter source, ItemBase wb, AbstractWorldObject target, int passiveType, DeferredPowerJob dpj, boolean mainHand) { + + int swingAnimation = 75; + + if (dpj != null) + if (PowersManager.AnimationOverrides.containsKey(dpj.getAction().getEffectID())) + swingAnimation = PowersManager.AnimationOverrides.get(dpj.getAction().getEffectID()); + + TargetedActionMsg cmm = new TargetedActionMsg(source, swingAnimation, target, passiveType); + DispatchMessage.sendToAllInRange(target, cmm); + } + private static boolean testPassive(AbstractCharacter source, AbstractCharacter target, String type) { + + if(target.getBonuses() != null) + if(target.getBonuses().getBool(Enum.ModType.Stunned, Enum.SourceType.None)) + return false; + + float chance = target.getPassiveChance(type, source.getLevel(), true); + + if (chance == 0f) + return false; + + //max 75% chance of passive to fire + + if (chance > 75f) + chance = 75f; + + int roll = ThreadLocalRandom.current().nextInt(1,100); + + return roll < chance; + + } +}