diff --git a/src/engine/gameManager/CombatManager.java b/src/engine/gameManager/CombatManager.java index 7f0126ef..20ceeb07 100644 --- a/src/engine/gameManager/CombatManager.java +++ b/src/engine/gameManager/CombatManager.java @@ -215,6 +215,22 @@ public enum CombatManager { wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects. delay = (long)wepSpeed * 100L; + } else if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) { + + //unarmed mob, use the natural attack speed from its rune + + float naturalSpeed = getNaturalAttackSpeed((Mob) attacker, slot); + + if (naturalSpeed > 0) { + + if (attacker.getBonuses() != null && attacker.getBonuses().getFloatPercentAll(mbEnums.ModType.AttackDelay, mbEnums.SourceType.None) != 0f) //add effects speed bonus + naturalSpeed *= (1 + attacker.getBonuses().getFloatPercentAll(mbEnums.ModType.AttackDelay, mbEnums.SourceType.None)); + + if (naturalSpeed < 10) + naturalSpeed = 10; + + delay = (long) naturalSpeed * 100L; + } } if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) @@ -646,6 +662,25 @@ public enum CombatManager { } + public static float getNaturalAttackSpeed(Mob mob, mbEnums.EquipSlotType slot) { + + // Returns the rune natural attack speed (x100 ms) for this hand, or 0 if the rune has none. + + ItemTemplate rune = ItemTemplate.templates.get(mob.loadID); + int hand = slot.equals(mbEnums.EquipSlotType.LHELD) ? 2 : 1; + + if (rune == null || rune.rune_natural_attacks.isEmpty()) + return 0; + + for (float[] attack : rune.rune_natural_attacks) + if (attack.length > 3 && (int) attack[3] == hand) + return attack[0]; + + // No entry for this hand, fall back to the first natural attack + + return rune.rune_natural_attacks.get(0)[0]; + } + public static int calculatePetDamage(AbstractCharacter agent) { //damage calc for pet float range; diff --git a/src/engine/objects/ItemTemplate.java b/src/engine/objects/ItemTemplate.java index b336fb25..8022ab74 100644 --- a/src/engine/objects/ItemTemplate.java +++ b/src/engine/objects/ItemTemplate.java @@ -138,6 +138,10 @@ public class ItemTemplate { public final EnumSet rune_group_role_set = EnumSet.noneOf(mbEnums.MobBehaviourType.class); public boolean rune_renderable = false; public int rune_natural_power_attack; + // [AI] One float[] per rune_naturalattacks entry. Layout inferred from game data, not yet verified in IDA: + // [0] attack speed (x100 ms), [1] range, [2] ?, [3] hand (1 = right, 2 = left), [4] weapon/ammo template id, + // [5] projectile speed?, [6] swing anim, [7] damage type index, [8] min damage, [9] max damage + public final ArrayList rune_natural_attacks = new ArrayList<>(); public final HashMap rune_sparse_data = new HashMap<>(); public final HashMap rune_skill_adj = new HashMap<>(); public final EnumSet rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class); @@ -554,6 +558,21 @@ public class ItemTemplate { rune_renderable = jsonObject.getBoolean("rune_renderable"); rune_natural_power_attack = jsonObject.getInt("rune_natural_power_attack"); + if (jsonObject.has("rune_naturalattacks")) { + + JSONArray natural_attacks_json = jsonObject.getJSONArray("rune_naturalattacks"); + + for (Object attackEntry : natural_attacks_json) { + JSONArray attackArray = (JSONArray) attackEntry; + float[] attack = new float[attackArray.length()]; + + for (int i = 0; i < attackArray.length(); i++) + attack[i] = attackArray.getFloat(i); + + rune_natural_attacks.add(attack); + } + } + JSONObject rune_sparse_json = jsonObject.getJSONObject("rune_sparse_data"); for (String sparseType : rune_sparse_json.keySet()) {