You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							427 lines
						
					
					
						
							17 KiB
						
					
					
				
			
		
		
	
	
							427 lines
						
					
					
						
							17 KiB
						
					
					
				| package engine.gameManager; | |
|  | |
| import engine.Enum; | |
| import engine.job.JobContainer; | |
| import engine.job.JobScheduler; | |
| import engine.jobs.AttackJob; | |
| import engine.jobs.DeferredPowerJob; | |
| import engine.net.DispatchMessage; | |
| 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; | |
| import engine.server.MBServerStatics; | |
| import org.pmw.tinylog.Logger; | |
|  | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import java.util.concurrent.ThreadLocalRandom; | |
|  | |
| public class FinalCombatManager { | |
|     public static void combatCycle(AbstractCharacter attacker, AbstractWorldObject target) { | |
|         //early exit checks | |
|         if(attacker == null || target == null || !attacker.isAlive() || !target.isAlive()) | |
|             return; | |
|  | |
|         switch(target.getObjectType()){ | |
|             case Building: | |
|                 if(((Building)target).isVulnerable() == false) | |
|                     return; | |
|                 break; | |
|             case PlayerCharacter: | |
|             case Mob: | |
|                 PlayerBonuses bonuses = ((AbstractCharacter)target).getBonuses(); | |
|                 if(bonuses != null && bonuses.getBool(Enum.ModType.ImmuneToAttack, Enum.SourceType.NONE)) | |
|                     return; | |
|                 break; | |
|             case NPC: | |
|                 return; | |
|         } | |
|  | |
|         Item mainWeapon = attacker.charItemManager.getEquipped().get(Enum.EquipSlotType.RHELD); | |
|         Item offWeapon = attacker.charItemManager.getEquipped().get(Enum.EquipSlotType.LHELD); | |
|         if(mainWeapon == null && offWeapon == null){ | |
|             //no weapons equipped, punch with both fists | |
|             processAttack(attacker,target,Enum.EquipSlotType.RHELD); | |
|             processAttack(attacker,target,Enum.EquipSlotType.LHELD); | |
|         }else if(mainWeapon == null && offWeapon != null && offWeapon.template.item_skill_required.containsKey("Block")){ | |
|             //no weapon equipped with a shield, punch with one hand | |
|             processAttack(attacker,target,Enum.EquipSlotType.RHELD); | |
|         }else if(mainWeapon != null && offWeapon != null && offWeapon.template.item_skill_required.containsKey("Block")){ | |
|             //one weapon equipped with a shield, swing with one hand | |
|             processAttack(attacker,target,Enum.EquipSlotType.RHELD); | |
|         }else if(mainWeapon != null && offWeapon != null && offWeapon.template.item_skill_required.containsKey("Block") == false){ | |
|             //two weapons equipped, swing both hands | |
|             processAttack(attacker,target,Enum.EquipSlotType.RHELD); | |
|             processAttack(attacker,target,Enum.EquipSlotType.LHELD); | |
|         } else if(mainWeapon == null && offWeapon != null && offWeapon.template.item_skill_required.containsKey("Block") == false){ | |
|             //swing left hand only | |
|             processAttack(attacker,target,Enum.EquipSlotType.LHELD); | |
|         } | |
|     } | |
|  | |
|     public static void processAttack(AbstractCharacter attacker, AbstractWorldObject target, Enum.EquipSlotType slot){ | |
|         //check if character can even attack yet | |
|         if(attacker.getTimestamps().containsKey("Attack" + slot.name())) | |
|             if(System.currentTimeMillis() < attacker.getTimestamps().get("Attack" + slot.name())) | |
|                 return; | |
|  | |
|         //check if character is in range to attack target | |
|         PlayerBonuses bonus = attacker.getBonuses(); | |
|         float rangeMod = 1.0f; | |
|         float attackRange = MBServerStatics.NO_WEAPON_RANGE; | |
|         Item weapon = attacker.charItemManager.getEquipped(slot); | |
|         if (weapon != null) { | |
|             if (bonus != null) | |
|                 rangeMod += bonus.getFloatPercentAll(Enum.ModType.WeaponRange, Enum.SourceType.NONE); | |
|  | |
|             attackRange = weapon.template.item_weapon_max_range * rangeMod; | |
|         } | |
|         if(attacker.getObjectType().equals(Enum.GameObjectType.Mob)) | |
|             if(((Mob)attacker).isSiege()) | |
|                 attackRange = 300; | |
|  | |
|         float distanceSquared = attacker.loc.distanceSquared(target.loc); | |
|         if(distanceSquared > attackRange * attackRange) | |
|             return; | |
|  | |
|         //take stamina away from attacker | |
|         if (weapon == null) | |
|             attacker.modifyStamina(-0.5f, attacker, true); | |
|         else { | |
|             float stam = weapon.template.item_wt / 3f; | |
|             stam = (stam < 1) ? 1 : stam; | |
|             attacker.modifyStamina(-(stam), attacker, true); | |
|         } | |
|  | |
|         //cancel things that are cancelled by an attack | |
|         attacker.cancelOnAttackSwing(); | |
|  | |
|         //declare relevant variables | |
|         int min = attacker.minDamageHandOne; | |
|         int max = attacker.maxDamageHandOne; | |
|         int atr = attacker.atrHandOne; | |
|  | |
|         //get the proper stats based on which slot is attacking | |
|         if(slot == Enum.EquipSlotType.LHELD){ | |
|             min = attacker.minDamageHandTwo; | |
|             max = attacker.maxDamageHandTwo; | |
|             atr = attacker.atrHandTwo; | |
|         } | |
|         int def = 0; | |
|         if(AbstractCharacter.IsAbstractCharacter(target)) | |
|             def = ((AbstractCharacter)target).defenseRating; | |
|  | |
|         //calculate hit chance based off ATR and DEF | |
|         int hitChance; | |
|         float dif = atr / def; | |
|         if (dif <= 0.8f) | |
|             hitChance = 4; | |
|         else | |
|             hitChance = ((int) (450 * (dif - 0.8f)) + 4); | |
|         if (target.getObjectType() == Enum.GameObjectType.Building) | |
|             hitChance = 100; | |
|  | |
|         int passiveAnim = getSwingAnimation(attacker.charItemManager.getEquipped().get(slot).template, null, true); | |
|  | |
|         if(ThreadLocalRandom.current().nextInt(100) > hitChance) { | |
|             TargetedActionMsg msg = new TargetedActionMsg(attacker, target, 0f, passiveAnim); | |
|  | |
|             if (target.getObjectType() == Enum.GameObjectType.PlayerCharacter) | |
|                 DispatchMessage.dispatchMsgToInterestArea(target, msg, Enum.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); | |
|             else | |
|                 DispatchMessage.sendToAllInRange(attacker, msg); | |
|  | |
|             return; | |
|         } | |
|  | |
|         //calculate passive chances only if target is AbstractCharacter | |
|         if(AbstractCharacter.IsAbstractCharacter(target)){ | |
|             int hitRoll = ThreadLocalRandom.current().nextInt(100); | |
|             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); | |
|             if(hitRoll > dodgeChance || hitRoll > parryChance || hitRoll > blockChance){ | |
|                 TargetedActionMsg msg = new TargetedActionMsg(attacker, passiveAnim, target, MBServerStatics.COMBAT_SEND_BLOCK); | |
|  | |
|                 if (target.getObjectType() == Enum.GameObjectType.PlayerCharacter) | |
|                     DispatchMessage.dispatchMsgToInterestArea(target, msg, Enum.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); | |
|                 else | |
|                     DispatchMessage.sendToAllInRange(attacker, msg); | |
|  | |
|                 return; | |
|             } | |
|         } | |
|  | |
|         //calculate the base damage | |
|         int damage = ThreadLocalRandom.current().nextInt(min,max + 1); | |
|         if(damage == 0) | |
|             return; | |
|  | |
|         //get the damage type | |
|         Enum.SourceType damageType; | |
|         if(attacker.charItemManager.getEquipped().get(slot) == null) { | |
|             damageType = Enum.SourceType.CRUSHING; | |
|             if(attacker.getObjectType().equals(Enum.GameObjectType.Mob)) | |
|                 if(((Mob)attacker).isSiege()) | |
|                     damageType = Enum.SourceType.SIEGE; | |
|         }else { | |
|             damageType = (Enum.SourceType) attacker.charItemManager.getEquipped().get(slot).template.item_weapon_damage.keySet().toArray()[0]; | |
|         } | |
|  | |
|         //get resists | |
|         Resists resists; | |
|         if(AbstractCharacter.IsAbstractCharacter(target) == false){ | |
|             //this is a building | |
|             resists = ((Building) target).getResists(); | |
|         }else{ | |
|             //this is a character | |
|             resists = ((AbstractCharacter) target).getResists(); | |
|         } | |
|  | |
|         if(AbstractCharacter.IsAbstractCharacter(target)) { | |
|             AbstractCharacter absTarget = (AbstractCharacter) target; | |
|             //check damage shields | |
|             PlayerBonuses bonuses = absTarget.getBonuses(); | |
|  | |
|             if (bonuses != null) { | |
|  | |
|                 ConcurrentHashMap<AbstractEffectModifier, DamageShield> 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 | |
|                     if (resists != null) | |
|                         amount = resists.getResistedDamage(absTarget, attacker, ds.getDamageType(), amount, 0); | |
|                     total += amount; | |
|                 } | |
|  | |
|                 if (total > 0) { | |
|                     //apply Damage back | |
|                     attacker.modifyHealth(-total, absTarget, true); | |
|                     TargetedActionMsg cmm = new TargetedActionMsg(attacker, attacker, total, 0); | |
|                     DispatchMessage.sendToAllInRange(target, cmm); | |
|                 } | |
|             } | |
|             if (resists != null) { | |
|                 //check for damage type immunities | |
|                 if (resists.immuneTo(damageType)) | |
|                     return; | |
|                 //calculate resisted damage including fortitude | |
|                 damage = (int) resists.getResistedDamage(attacker,(AbstractCharacter)target,damageType,damage,0); | |
|             } | |
|         } | |
|  | |
|         //remove damage from target health | |
|         if(damage > 0){ | |
|             if(AbstractCharacter.IsAbstractCharacter(target)){ | |
|                 ((AbstractCharacter)target).modifyHealth(-damage, attacker, true); | |
|             }else{ | |
|                 ((Building)target).setCurrentHitPoints(target.getCurrentHitpoints() - damage); | |
|             } | |
|             TargetedActionMsg cmm = new TargetedActionMsg(attacker,target, (float) damage,0); | |
|             DispatchMessage.sendToAllInRange(target, cmm); | |
|         } | |
|  | |
|         //calculate next allowed attack and update the timestamp | |
|         long delay = 20 * 100; | |
|         if (weapon != null){ | |
|             int wepSpeed = (int) (weapon.template.item_weapon_wepspeed); | |
|  | |
|             if (weapon.getBonusPercent(Enum.ModType.WeaponSpeed, Enum.SourceType.NONE) != 0f) //add weapon speed bonus | |
|                 wepSpeed *= (1 + weapon.getBonus(Enum.ModType.WeaponSpeed, Enum.SourceType.NONE)); | |
|  | |
|             if (attacker.getBonuses() != null && attacker.getBonuses().getFloatPercentAll(Enum.ModType.AttackDelay, Enum.SourceType.NONE) != 0f) //add effects speed bonus | |
|                 wepSpeed *= (1 + attacker.getBonuses().getFloatPercentAll(Enum.ModType.AttackDelay, Enum.SourceType.NONE)); | |
|  | |
|             if (wepSpeed < 10) | |
|                 wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects. | |
|  | |
|             delay = wepSpeed * 100; | |
|         } | |
|         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, (delay + 1)); // offset 1 millisecond so no overlap issue | |
|             timers.put("Attack" + slot, job); | |
|         } else { | |
|             Logger.error("Unable to find Timers for Character " + attacker.getObjectUUID()); | |
|         } | |
|     } | |
|  | |
|     public static void toggleCombat(boolean toggle, ClientConnection origin) { | |
|  | |
|         PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); | |
|         if (pc == null) | |
|             return; | |
|  | |
|         pc.setCombat(toggle); | |
|         if (!toggle) // toggle is move it to false so clear combat target | |
|             pc.setCombatTarget(null); //clear last combat target | |
|  | |
|         UpdateStateMsg rwss = new UpdateStateMsg(); | |
|         rwss.setPlayer(pc); | |
|         DispatchMessage.dispatchMsgToInterestArea(pc, rwss, Enum.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); | |
|     } | |
|  | |
|     public static void toggleSit(boolean toggle, ClientConnection origin) { | |
|  | |
|         PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); | |
|         if (pc == null) | |
|             return; | |
|  | |
|         pc.setSit(toggle); | |
|         UpdateStateMsg rwss = new UpdateStateMsg(); | |
|         rwss.setPlayer(pc); | |
|         DispatchMessage.dispatchMsgToInterestArea(pc, rwss, Enum.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); | |
|     } | |
|  | |
|     //Called when character takes damage. | |
|     public static void handleRetaliate(AbstractCharacter target, AbstractCharacter attacker) { | |
|  | |
|         if (attacker == null || target == null) | |
|             return; | |
|  | |
|         if (attacker.equals(target)) | |
|             return; | |
|  | |
|         if (target.isMoving() && target.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) | |
|             return; | |
|  | |
|         if (!target.isAlive() || !attacker.isAlive()) | |
|             return; | |
|  | |
|         boolean isCombat = target.isCombat(); | |
|  | |
|         //If target in combat and has no target, then attack back | |
|         if(isCombat && target.combatTarget == null) | |
|             target.setCombatTarget(attacker); | |
|     } | |
|  | |
|     public static int getSwingAnimation(ItemTemplate wb, DeferredPowerJob dpj, boolean mainHand) { | |
|         int token = 0; | |
|  | |
|         if (dpj != null) | |
|             token = (dpj.getPower() != null) ? dpj.getPower().getToken() : 0; | |
|  | |
|         if (token == 563721004) //kick animation | |
|             return 79; | |
|  | |
|         if (wb == null) | |
|             return 75; | |
|  | |
|         ItemTemplate template = wb; | |
|  | |
|         if (mainHand) { | |
|             if (template.weapon_attack_anim_right.size() > 0) { | |
|  | |
|                 int animation; | |
|  | |
|                 int random = ThreadLocalRandom.current().nextInt(template.weapon_attack_anim_right.size()); | |
|  | |
|                 try { | |
|                     animation = template.weapon_attack_anim_right.get(random)[0]; | |
|                     return animation; | |
|                 } catch (Exception e) { | |
|                     Logger.error(e.getMessage()); | |
|                     return template.weapon_attack_anim_right.get(0)[0]; | |
|                 } | |
|  | |
|             } else if (template.weapon_attack_anim_left.size() > 0) { | |
|  | |
|                 int animation; | |
|                 int random = ThreadLocalRandom.current().nextInt(template.weapon_attack_anim_left.size()); | |
|  | |
|                 try { | |
|                     animation = template.weapon_attack_anim_left.get(random)[0]; | |
|                     return animation; | |
|                 } catch (Exception e) { | |
|                     Logger.error(e.getMessage()); | |
|                     return template.weapon_attack_anim_right.get(0)[0]; | |
|                 } | |
|             } | |
|         } else { | |
|             if (template.weapon_attack_anim_left.size() > 0) { | |
|                 int animation; | |
|                 int random = ThreadLocalRandom.current().nextInt(template.weapon_attack_anim_left.size()); | |
|  | |
|                 try { | |
|                     animation = template.weapon_attack_anim_left.get(random)[0]; | |
|                     return animation; | |
|                 } catch (Exception e) { | |
|                     Logger.error(e.getMessage()); | |
|                     return template.weapon_attack_anim_right.get(0)[0]; | |
|  | |
|                 } | |
|             } else if (template.weapon_attack_anim_left.size() > 0) { | |
|  | |
|                 int animation; | |
|                 int random = ThreadLocalRandom.current().nextInt(template.weapon_attack_anim_left.size()); | |
|  | |
|                 try { | |
|                     animation = template.weapon_attack_anim_left.get(random)[0]; | |
|                     return animation; | |
|                 } catch (Exception e) { | |
|                     Logger.error(e.getMessage()); | |
|                     return template.weapon_attack_anim_right.get(0)[0]; | |
|  | |
|                 } | |
|  | |
|             } | |
|         } | |
|  | |
|         String required = template.item_skill_used; | |
|         String mastery = wb.item_skill_mastery_used; | |
|  | |
|         if (required.equals("Unarmed Combat")) | |
|             return 75; | |
|         else if (required.equals("Sword")) { | |
|  | |
|             if (ItemTemplate.isTwoHanded(template)) | |
|                 return 105; | |
|             else | |
|                 return 98; | |
|  | |
|         } else if (required.equals("Staff") || required.equals("Pole Arm")) { | |
|             return 85; | |
|         } else if (required.equals("Spear")) { | |
|             return 92; | |
|         } else if (required.equals("Hammer") || required.equals("Axe")) { | |
|             if (ItemTemplate.isTwoHanded(template)) { | |
|                 return 105; | |
|             } else if (mastery.equals("Throwing")) { | |
|                 return 115; | |
|             } else { | |
|                 return 100; | |
|             } | |
|         } else if (required.equals("Dagger")) { | |
|             if (mastery.equals("Throwing")) { | |
|                 return 117; | |
|             } else { | |
|                 return 81; | |
|             } | |
|         } else if (required.equals("Crossbow")) { | |
|             return 110; | |
|         } else if (required.equals("Bow")) { | |
|             return 109; | |
|         } else if (ItemTemplate.isTwoHanded(template)) { | |
|             return 105; | |
|         } else { | |
|             return 100; | |
|         } | |
|     } | |
| } |