package com.asteria.game.character.combat; import static com.asteria.game.character.combat.CombatConstants.PRAYER_ACCURACY_REDUCTION; import static com.asteria.game.character.combat.CombatConstants.PRAYER_DAMAGE_REDUCTION; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; import plugin.combat.DefaultMagicCombatStrategy; import plugin.combat.DefaultMeleeCombatStrategy; import plugin.combat.DefaultRangedCombatStrategy; import com.asteria.Server; import com.asteria.game.NodeType; import com.asteria.game.World; import com.asteria.game.character.CharacterNode; import com.asteria.game.character.Hit; import com.asteria.game.character.MovementQueue; import com.asteria.game.character.combat.effect.CombatEffect; import com.asteria.game.character.combat.effect.CombatEffectType; import com.asteria.game.character.combat.magic.CombatSpells; import com.asteria.game.character.combat.magic.CombatWeaken; import com.asteria.game.character.combat.prayer.CombatPrayer; import com.asteria.game.character.combat.ranged.CombatRangedAmmo; import com.asteria.game.character.combat.weapon.FightStyle; import com.asteria.game.character.npc.Npc; import com.asteria.game.character.player.Player; import com.asteria.game.character.player.content.WeaponInterface; import com.asteria.game.character.player.skill.Skills; import com.asteria.game.item.Item; import com.asteria.game.item.container.Equipment; import com.asteria.game.location.Position; import com.asteria.utility.RandomGen; /** * A collection of utility methods and constants related to combat. * * @author lare96 <http://github.com/lare96> */ public final class Combat { /** * The attack stab bonus identifier. */ public static final int ATTACK_STAB = 0; /** * The attack slash bonus identifier. */ public static final int ATTACK_SLASH = 1; /** * The attack crush bonus identifier. */ public static final int ATTACK_CRUSH = 2; /** * The attack magic bonus identifier. */ public static final int ATTACK_MAGIC = 3; /** * The attack ranged bonus identifier. */ public static final int ATTACK_RANGED = 4; /** * The defence stab bonus identifier. */ public static final int DEFENCE_STAB = 5; /** * The defence slash bonus identifier. */ public static final int DEFENCE_SLASH = 6; /** * The defence crush bonus identifier. */ public static final int DEFENCE_CRUSH = 7; /** * The defence magic bonus identifier. */ public static final int DEFENCE_MAGIC = 8; /** * The defence ranged bonus identifier. */ public static final int DEFENCE_RANGED = 9; /** * The strength bonus identifier. */ public static final int BONUS_STRENGTH = 10; /** * The prayer bonus identifier. */ public static final int BONUS_PRAYER = 11; /** * The names of all the bonuses in their exact identified slots. */ public static final String[] BONUS_NAMES = { "Stab", "Slash", "Crush", "Magic", "Range", "Stab", "Slash", "Crush", "Magic", "Range", "Strength", "Prayer" }; /** * The hash collection of all the NPCs mapped to their combat strategies. */ public static final Map<Integer, CombatStrategy> STRATEGIES = new HashMap<>(); /** * The random generator instance that will generate random numbers. */ private static RandomGen random = new RandomGen(); /** * The default constructor. * * @throws UnsupportedOperationException * if this class is instantiated. */ private Combat() { throw new UnsupportedOperationException("This class cannot be instantiated!"); } /** * Applies combat prayer accuracy and damage reductions before executing the * {@link CombatSessionAttack}. * * @param data * the data for this combat session. */ protected static void applyPrayerEffects(CombatSessionData data) { if (!data.isCheckAccuracy()) { return; } if (data.getVictim().getType() != NodeType.PLAYER) { return; } if (Combat.isFullVeracs(data.getAttacker())) { if (Server.DEBUG && data.getAttacker().getType() == NodeType.PLAYER) ((Player) data.getAttacker()).getMessages().sendMessage( "[DEBUG]: Chance of opponents prayer cancelling hit " + "[0/" + PRAYER_ACCURACY_REDUCTION + "]"); return; } Player player = (Player) data.getVictim(); if (CombatPrayer.isActivated(player, Combat.getProtectingPrayer(data.getType()))) { switch (data.getAttacker().getType()) { case PLAYER: for (CombatHit h : data.getHits()) { int hit = h.getHit().getDamage(); double mod = Math.abs(1 - PRAYER_DAMAGE_REDUCTION); h.setHit(new Hit((int) (hit * mod), h.getHit().getType())); if (Server.DEBUG) player.getMessages().sendMessage( "[DEBUG]: Damage " + "reduced by opponents prayer [" + (hit - h.getHit().getDamage()) + "]"); mod = Math.round(random.get().nextDouble() * 100.0) / 100.0; if (Server.DEBUG) player .getMessages() .sendMessage( "[DEBUG]: Chance " + "of opponents prayer cancelling hit [" + mod + "/" + PRAYER_ACCURACY_REDUCTION + "]"); if (mod <= PRAYER_ACCURACY_REDUCTION) { h.setAccurate(false); } } break; case NPC: Arrays.stream(data.getHits()).filter(Objects::nonNull).forEach(h -> h.setAccurate(false)); break; default: throw new IllegalStateException("Invalid character node " + "type!"); } } } /** * Handles the distribution of experience for the amount of damage dealt in * this combat session attack. * * @param builder * the combat builder for this combat session. * @param data * the data for this combat session. * @param counter * the total amount of damage dealt. */ protected static void handleExperience(CombatBuilder builder, CombatSessionData data, int counter) { if (builder.getCharacter().getType() == NodeType.PLAYER) { if (data.getExperience().length == 0 && data.getType() != CombatType.MAGIC) { return; } Player player = (Player) builder.getCharacter(); double exp = 0; double hitpointsExp = 0; if (data.getType() == CombatType.MAGIC) { exp = (counter * 4d) + builder.getCharacter().getCurrentlyCasting().baseExperience(); hitpointsExp = (exp / 3d); Skills.experience(player, exp, Skills.MAGIC); Skills.experience(player, hitpointsExp, Skills.HITPOINTS); return; } exp = ((counter * 4d) / data.getExperience().length); hitpointsExp = (exp / 3d); for (int amount : data.getExperience()) { Skills.experience(player, exp, amount); } Skills.experience(player, hitpointsExp, Skills.HITPOINTS); } } /** * Deals the damage contained within {@code data} to all {@code characters} * within {@code radius} of {@code position}. This method also executes * {@code action} for every character. Please note that this only accounts * for local characters. * * @param attacker * the attacker in this combat session. * @param victims * the characters that will be attempted to be hit. * @param position * the position that the radius will be calculated from. * @param radius * the radius of the damage. * @param hits * the amount of hits to calculate. * @param type * the combat type the attacker is using. * @param checkAccuracy * determines if accuracy should be calculated for hits. * @param action * the action to execute for each victim. */ public static <E extends CharacterNode> void damageCharactersWithin(CharacterNode attacker, Iterable<E> victims, Position position, int radius, int hits, CombatType type, boolean checkAccuracy, Consumer<E> action) { for (E c : victims) { if (c == null) continue; if (!c.getPosition().withinDistance(position, radius) || c.equals(attacker) || c .equals(attacker.getCombatBuilder().getVictim()) || c.getCurrentHealth() <= 0 || c.isDead()) continue; CombatSessionData data = new CombatSessionData(attacker, c, hits, type, checkAccuracy); c.getCombatBuilder().getDamageCache().add(attacker, data.attack()); if (action != null) action.accept(c); } } /** * Deals the damage contained within {@code data} to all {@code characters} * within {@code radius} of {@code position}. This method also executes * {@code action} for every character. Please note that this only accounts * for local characters. * * @param attacker * the attacker in this combat session. * @param victims * the characters that will be attempted to be hit. * @param position * the position that the radius will be calculated from. * @param radius * the radius of the damage. * @param hits * the amount of hits to calculate. * @param type * the combat type the attacker is using. * @param checkAccuracy * determines if accuracy should be calculated for hits. */ public static void damageCharactersWithin(CharacterNode attacker, Iterable<? extends CharacterNode> victims, Position position, int radius, int hits, CombatType type, boolean checkAccuracy) { damageCharactersWithin(attacker, victims, position, radius, hits, type, checkAccuracy, null); } /** * Deals the damage contained within {@code data} to all {@link Player}s * within {@code radius} of {@code position}. Please note that this only * accounts for local characters. * * @param attacker * the attacker in this combat session. * @param position * the position that the radius will be calculated from. * @param radius * the radius of the damage. * @param hits * the amount of hits to calculate. * @param type * the combat type the attacker is using. * @param checkAccuracy * determines if accuracy should be calculated for hits. * @param action * the action to execute for each victim. */ public static void damagePlayersWithin(CharacterNode attacker, Position position, int radius, int hits, CombatType type, boolean checkAccuracy, Consumer<Player> action) { damageCharactersWithin(attacker, () -> World.getLocalPlayers(attacker), position, radius, hits, type, checkAccuracy, action); } /** * Deals the damage contained within {@code data} to all {@link Player}s * within {@code radius} of {@code position}. This method also executes * {@code action} for every character. Please note that this only accounts * for local characters. * * @param attacker * the attacker in this combat session. * @param position * the position that the radius will be calculated from. * @param radius * the radius of the damage. * @param hits * the amount of hits to calculate. * @param type * the combat type the attacker is using. * @param checkAccuracy * determines if accuracy should be calculated for hits. */ public static void damagePlayersWithin(CharacterNode attacker, Position position, int radius, int hits, CombatType type, boolean checkAccuracy) { damagePlayersWithin(attacker, position, radius, hits, type, checkAccuracy, null); } /** * Deals the damage contained within {@code data} to all {@link Npc}s within * {@code radius} of {@code position}. This method also executes * {@code action} for every character. Please note that this only accounts * for local characters. * * @param attacker * the attacker in this combat session. * @param position * the position that the radius will be calculated from. * @param radius * the radius of the damage. * @param hits * the amount of hits to calculate. * @param type * the combat type the attacker is using. * @param checkAccuracy * determines if accuracy should be calculated for hits. * @param action * the action to execute for each victim. */ public static void damageNpcsWithin(CharacterNode attacker, Position position, int radius, int hits, CombatType type, boolean checkAccuracy, Consumer<Npc> action) { damageCharactersWithin(attacker, () -> World.getLocalNpcs(attacker), position, radius, hits, type, checkAccuracy, action); } /** * Deals the damage contained within {@code data} to all {@link Npc}s within * {@code radius} of {@code position}. This method also executes * {@code action} for every character. Please note that this only accounts * for local characters. * * @param attacker * the attacker in this combat session. * @param position * the position that the radius will be calculated from. * @param radius * the radius of the damage. * @param hits * the amount of hits to calculate. * @param type * the combat type the attacker is using. * @param checkAccuracy * determines if accuracy should be calculated for hits. */ public static void damageNpcsWithin(CharacterNode attacker, Position position, int radius, int hits, CombatType type, boolean checkAccuracy) { damageNpcsWithin(attacker, position, radius, hits, type, checkAccuracy, null); } /** * Gets the corresponding combat prayer to {@code type}. * * @param type * the combat type to get the prayer for. * @return the corresponding combat prayer. * @throws IllegalArgumentException * if the combat type is invalid. */ public static CombatPrayer getProtectingPrayer(CombatType type) { switch (type) { case MELEE: return CombatPrayer.PROTECT_FROM_MELEE; case MAGIC: return CombatPrayer.PROTECT_FROM_MAGIC; case RANGED: return CombatPrayer.PROTECT_FROM_MISSILES; default: throw new IllegalArgumentException("Invalid combat type: " + type); } } /** * Determines the combat strategy for {@code npc}. * * @param npc * the npc to determine the combat strategy for. * @return */ public static CombatStrategy determineStrategy(int npc) { CombatStrategy combat = STRATEGIES.get(npc); if (combat == null) return Combat.newDefaultMeleeStrategy(); return combat; } /** * Determines which spell {@code npc} will use when they have the * {@link DefaultMagicCombatStrategy} combat strategy. * * @param npc * the npc that needs a spell. * @return the spell that the npc will cast. */ public static CombatSpells prepareSpellCast(Npc npc) { switch (npc.getId()) { case 13: case 172: case 174: return random.random(new CombatSpells[] { CombatSpells.WEAKEN, CombatSpells.FIRE_STRIKE, CombatSpells.EARTH_STRIKE, CombatSpells.WATER_STRIKE }); default: return CombatSpells.FIRE_STRIKE; } } /** * Determines which ammo {@code npc} will use when they have the * {@link DefaultRangedCombatStrategy} combat strategy. * * @param npc * the npc that needs a spell. * @return the spell that the npc will cast. */ public static CombatRangedAmmo prepareRangedAmmo(Npc npc) { switch (npc.getId()) { case 688: return CombatRangedAmmo.BRONZE_ARROW; default: return CombatRangedAmmo.STEEL_ARROW; } } /** * Determines if {@code character} is wearing full veracs. * * @param character * the character to determine this for. * @return {@code true} if this character is wearing full veracs, * {@code false} otherwise. */ public static boolean isFullVeracs(CharacterNode character) { return character.getType() == NodeType.NPC ? ((Npc) character).getDefinition().getName().equals("Verac the Defiled") : ((Player) character).getEquipment().containsAll(4753, 4757, 4759, 4755); } /** * Determines if {@code character} is wearing full dharoks. * * @param character * the character to determine this for. * @return {@code true} if this character is wearing full dharoks, * {@code false} otherwise. */ public static boolean isFullDharoks(CharacterNode character) { return character.getType() == NodeType.NPC ? ((Npc) character).getDefinition().getName().equals("Dharok the Wretched") : ((Player) character).getEquipment().containsAll(4716, 4720, 4722, 4718); } /** * Determines if {@code character} is wearing full karils. * * @param character * the character to determine this for. * @return {@code true} if this character is wearing full karils, * {@code false} otherwise. */ public static boolean isFullKarils(CharacterNode character) { return character.getType() == NodeType.NPC ? ((Npc) character).getDefinition().getName().equals("Karil the Tainted") : ((Player) character).getEquipment().containsAll(4732, 4736, 4738, 4734); } /** * Determines if {@code character} is wearing full ahrims. * * @param character * the character to determine this for. * @return {@code true} if this character is wearing full ahrims, * {@code false} otherwise. */ public static boolean isFullAhrims(CharacterNode character) { return character.getType() == NodeType.NPC ? ((Npc) character).getDefinition().getName().equals("Ahrim the Blighted") : ((Player) character).getEquipment().containsAll(4708, 4712, 4714, 4710); } /** * Determines if {@code character} is wearing full torags. * * @param character * the character to determine this for. * @return {@code true} if this character is wearing full torags, * {@code false} otherwise. */ public static boolean isFullTorags(CharacterNode character) { return character.getType() == NodeType.NPC ? ((Npc) character).getDefinition().getName().equals("Torag the Corrupted") : ((Player) character).getEquipment().containsAll(4745, 4749, 4751, 4747); } /** * Determines if {@code character} is wearing full guthans. * * @param character * the character to determine this for. * @return {@code true} if this character is wearing full guthans, * {@code false} otherwise. */ public static boolean isFullGuthans(CharacterNode character) { return character.getType() == NodeType.NPC ? ((Npc) character).getDefinition().getName().equals("Guthan the Infested") : ((Player) character).getEquipment().containsAll(4724, 4728, 4730, 4726); } /** * Determines if {@code player} is wielding a crystal bow. * * @param player * the player to determine this for. * @return {@code true} if the player is wielding a crystal bow, * {@code false} otherwise. */ public static boolean isCrystalBow(Player player) { Item item = player.getEquipment().get(Equipment.WEAPON_SLOT); if (item == null) return false; return item.getDefinition().getName().toLowerCase().contains("crystal bow"); } /** * Determines if {@code player} has any arrows equipped. * * @param player * the player to determine this for. * @return {@code true} if the player has any arrows equipped, {@code false} * otherwise. */ public static boolean isArrows(Player player) { Item item; if ((item = player.getEquipment().get(Equipment.ARROWS_SLOT)) == null) { return false; } return !(!item.getDefinition().getName().endsWith("arrow") && !item.getDefinition().getName().endsWith("arrow(p)") && !item .getDefinition().getName().endsWith("arrow(p+)") && !item.getDefinition().getName().endsWith("arrow(p++)")); } /** * Determines if {@code player} has any bolts equipped. * * @param player * the player to determine this for. * @return {@code true} if the player has any bolts equipped, {@code false} * otherwise. */ public static boolean isBolts(Player player) { Item item; if ((item = player.getEquipment().get(Equipment.ARROWS_SLOT)) == null) { return false; } return !(!item.getDefinition().getName().endsWith("bolts") && !item.getDefinition().getName().endsWith("bolts(p)") && !item .getDefinition().getName().endsWith("bolts(p+)") && !item.getDefinition().getName().endsWith("bolts(p++)")); } /** * Gets the ranged distance based on {@code weapon}. * * @param weapon * the weapon you have equipped. * @return the ranged distance. * @throws IllegalArgumentException * if the weapon interface type is invalid. */ public static int getRangedDistance(WeaponInterface weapon) { switch (weapon) { case DART: case THROWNAXE: return 4; case KNIFE: case JAVELIN: return 5; case CROSSBOW: case LONGBOW: return 8; case SHORTBOW: return 7; default: throw new IllegalArgumentException("Invalid weapon interface type!"); } } /** * Gets the delay for the specified {@code type}. * * @param type * the combat type to retrieve the delay for. * @return the delay for the combat type. * @throws IllegalArgumentException * if the combat type is invalid. */ public static int getDelay(CombatType type) { switch (type) { case MELEE: return 1; case RANGED: return 2; case MAGIC: return 3; default: throw new IllegalArgumentException("Invalid combat type!"); } } /** * Applies the {@code effect} in any context. * * @param effect * the effect that must be applied. * @return {@code true} if it was successfully applied, {@code false} * otherwise. */ public static boolean effect(CharacterNode character, CombatEffectType effect) { return CombatEffect.EFFECTS.get(effect).start(character); } /** * Calculates the combat level difference for wilderness player vs. player * combat. * * @param combatLevel * the combat level of the first person. * @param otherCombatLevel * the combat level of the other person. * @return the combat level difference. */ public static int combatLevelDifference(int combatLevel, int otherCombatLevel) { if (combatLevel > otherCombatLevel) { return (combatLevel - otherCombatLevel); } else if (otherCombatLevel > combatLevel) { return (otherCombatLevel - combatLevel); } else { return 0; } } /** * Calculates a pseudo-random hit for {@code character} based on * {@code victim} and {@code type}. * * @param character * the character this hit is being calculated for. * @param victim * the victim of this hit that will be used as a factor. * @param type * the character's combat type that will be used as a factor. * @return the generated hit, will most likely return a different result if * called on two different occasions even with the same arguments. * @throws IllegalArgumentException * if the combat type is invalid. */ public static Hit calculateRandomHit(CharacterNode character, CharacterNode victim, CombatType type) { switch (type) { case MELEE: return new Hit(random.inclusive(1, Combat.calculateMaxMeleeHit(character, victim))); case RANGED: return new Hit(random.inclusive(1, Combat.calculateMaxRangedHit(character, victim))); case MAGIC: if (Server.DEBUG && character.getType() == NodeType.PLAYER) ((Player) character).getMessages().sendMessage( "[DEBUG]: " + "Maximum hit this turn is [" + character.getCurrentlyCasting().maximumHit() + "]."); return new Hit(random.inclusive(0, character.getCurrentlyCasting().maximumHit())); default: throw new IllegalArgumentException("Invalid combat type!"); } } /** * Determines if {@code attacker}'s attack will be successful. * * @param attacker * the attacker that this will be determined for. * @param victim * the victim of the attacker. * @param type * the combat type used by the attacker. * @return {@code true} if the hit was accurate, {@code false} otherwise. */ public static boolean isAccurate(CharacterNode attacker, CharacterNode victim, CombatType type) { boolean veracEffect = false; if (type == CombatType.MELEE) { if (Combat.isFullVeracs(attacker)) { if (random.get().nextInt(8) == 3) { veracEffect = true; } } } double prayerMod = 1; double equipmentBonus = 1; double specialBonus = 1; int styleBonus = 0; int bonusType = -1; if (attacker.getType() == NodeType.PLAYER) { Player player = (Player) attacker; equipmentBonus = type == CombatType.MAGIC ? player.getBonus()[Combat.ATTACK_MAGIC] : player.getBonus()[player.getFightType() .getBonus()]; bonusType = player.getFightType().getCorrespondingBonus(); if (type == CombatType.MELEE) { if (CombatPrayer.isActivated(player, CombatPrayer.CLARITY_OF_THOUGHT)) { prayerMod = 1.05; } else if (CombatPrayer.isActivated(player, CombatPrayer.IMPROVED_REFLEXES)) { prayerMod = 1.10; } else if (CombatPrayer.isActivated(player, CombatPrayer.INCREDIBLE_REFLEXES)) { prayerMod = 1.15; } } else if (type == CombatType.RANGED) { // XXX: Ranged prayers here. } else if (type == CombatType.MAGIC) { // XXX: Magic prayers here. } if (player.getFightType().getStyle() == FightStyle.ACCURATE) { styleBonus = 3; } else if (player.getFightType().getStyle() == FightStyle.CONTROLLED) { styleBonus = 1; } if (player.isSpecialActivated()) { specialBonus = player.getCombatSpecial().getAccuracy(); } } double attackCalc = Math.floor(equipmentBonus + attacker.getBaseAttack(type)) + 8; attackCalc *= prayerMod; attackCalc += styleBonus; if (equipmentBonus < -67) { attackCalc = random.get().nextInt(8) == 0 ? attackCalc : 0; } attackCalc *= specialBonus; equipmentBonus = 1; prayerMod = 1; styleBonus = 0; if (victim.getType() == NodeType.PLAYER) { Player player = (Player) victim; if (bonusType == -1) { equipmentBonus = type == CombatType.MAGIC ? player.getBonus()[Combat.DEFENCE_MAGIC] : player.getSkills()[Skills.DEFENCE] .getLevel(); } else { equipmentBonus = type == CombatType.MAGIC ? player.getBonus()[Combat.DEFENCE_MAGIC] : player.getBonus()[bonusType]; } if (CombatPrayer.isActivated(player, CombatPrayer.THICK_SKIN)) { prayerMod = 1.05; } else if (CombatPrayer.isActivated(player, CombatPrayer.ROCK_SKIN)) { prayerMod = 1.10; } else if (CombatPrayer.isActivated(player, CombatPrayer.STEEL_SKIN)) { prayerMod = 1.15; } if (player.getFightType().getStyle() == FightStyle.DEFENSIVE) { styleBonus = 3; } else if (player.getFightType().getStyle() == FightStyle.CONTROLLED) { styleBonus = 1; } } double defenceCalc = Math.floor(equipmentBonus + victim.getBaseDefence(type)) + 8; defenceCalc *= prayerMod; defenceCalc += styleBonus; if (equipmentBonus < -67) { defenceCalc = random.get().nextInt(8) == 0 ? defenceCalc : 0; } if (veracEffect) { defenceCalc = 0; } double A = Math.floor(attackCalc); double D = Math.floor(defenceCalc); double hitSucceed = A < D ? (A - 1.0) / (2.0 * D) : 1.0 - (D + 1.0) / (2.0 * A); hitSucceed = hitSucceed >= 1.0 ? 0.99 : hitSucceed <= 0.0 ? 0.01 : hitSucceed; if (attacker.getType() == NodeType.PLAYER && Server.DEBUG) { ((Player) attacker) .getMessages() .sendMessage( "[DEBUG]: Your roll " + "[" + (Math.round(attackCalc * 1000.0) / 1000.0) + "] : " + "Victim's roll [" + (Math .round(defenceCalc * 1000.0) / 1000.0) + "] : Chance to hit [" + (100 * Math.round(hitSucceed * 1000.0) / 1000.0) + "%]"); } return hitSucceed >= random.get().nextDouble(); } /** * Calculates the maximum hit that can be dealt using melee for * {@code character}. * * @param character * the character to calculate the max hit for. * @param victim * the victim being attacked. * @return the maximum hit this character can deal. */ private static int calculateMaxMeleeHit(CharacterNode character, CharacterNode victim) { int maxHit = 0; if (character.getType() == NodeType.NPC) { Npc npc = (Npc) character; maxHit = npc.getDefinition().getMaxHit(); if (npc.getWeakenedBy() == CombatWeaken.STRENGTH_LOW || npc.getWeakenedBy() == CombatWeaken.STRENGTH_HIGH) maxHit -= (int) ((npc.getWeakenedBy().getRate()) * (maxHit)); return maxHit; } Player player = (Player) character; double specialMultiplier = 1; double prayerMultiplier = 1; // TODO: void melee = 1.2, slayer helm = 1.15, salve amulet = 1.15, // salve amulet(e) = 1.2 double otherBonusMultiplier = 1; int strengthLevel = player.getSkills()[Skills.STRENGTH].getLevel(); int attackLevel = player.getSkills()[Skills.ATTACK].getLevel(); int combatStyleBonus = 0; if (CombatPrayer.isActivated(player, CombatPrayer.BURST_OF_STRENGTH)) { prayerMultiplier = 1.05; } else if (CombatPrayer.isActivated(player, CombatPrayer.SUPERHUMAN_STRENGTH)) { prayerMultiplier = 1.1; } else if (CombatPrayer.isActivated(player, CombatPrayer.ULTIMATE_STRENGTH)) { prayerMultiplier = 1.15; } // else if // (CombatPrayer.isPrayerActivated(player,CombatPrayer.CHIVALRY)) { // prayerMultiplier = 1.18; // } else if // (CombatPrayer.isPrayerActivated(player,CombatPrayer.PIETY)) { // prayerMultiplier = 1.23; // } switch (player.getFightType().getStyle()) { case AGGRESSIVE: combatStyleBonus = 3; break; case CONTROLLED: combatStyleBonus = 1; break; default: combatStyleBonus = 0; break; } // if (CombatFactory.fullVoid(player)) { // otherBonusMultiplier = 1.1; // } if (strengthLevel <= 10 || attackLevel <= 10) { otherBonusMultiplier = 1.8; } int effectiveStrengthDamage = (int) ((strengthLevel * prayerMultiplier * otherBonusMultiplier) + combatStyleBonus); double baseDamage = 1.3 + (effectiveStrengthDamage / 10) + (player.getBonus()[Combat.BONUS_STRENGTH] / 80) + ((effectiveStrengthDamage * player .getBonus()[Combat.BONUS_STRENGTH]) / 640); if (player.isSpecialActivated()) { specialMultiplier = player.getCombatSpecial().getStrength(); } maxHit = (int) (baseDamage * specialMultiplier); if (Combat.isFullDharoks(player)) { maxHit += (player.getSkills()[Skills.HITPOINTS].getRealLevel() - player.getSkills()[Skills.HITPOINTS].getLevel()) * 0.35; } if (Server.DEBUG) player.getMessages().sendMessage("[DEBUG]: Maximum hit this turn " + "is [" + maxHit + "]."); return maxHit; } /** * Calculates the maximum hit that can be dealt using ranged for * {@code character}. * * @param character * the character to calculate the max hit for. * @param victim * the victim being attacked. * @return the maximum hit this character can deal. */ private static int calculateMaxRangedHit(CharacterNode character, CharacterNode victim) { int maxHit = 0; if (character.getType() == NodeType.NPC) { Npc npc = (Npc) character; maxHit = npc.getDefinition().getMaxHit(); return maxHit; } Player player = (Player) character; double specialMultiplier = 1; double prayerMultiplier = 1; double otherBonusMultiplier = 1; int rangedStrength = player.getRangedAmmo().getStrength(); int rangeLevel = player.getSkills()[Skills.RANGED].getLevel(); int combatStyleBonus = 0; switch (player.getFightType().getStyle()) { case ACCURATE: combatStyleBonus = 3; break; default: break; } // if (fullVoidRange(character)) { // otherBonusMultiplier = 1.1; // } int effectiveRangeDamage = (int) ((rangeLevel * prayerMultiplier * otherBonusMultiplier) + combatStyleBonus); double baseDamage = 1.3 + (effectiveRangeDamage / 10) + (rangedStrength / 80) + ((effectiveRangeDamage * rangedStrength) / 640); if (player.isSpecialActivated()) { specialMultiplier = player.getCombatSpecial().getStrength(); } maxHit = (int) (baseDamage * specialMultiplier); if (Server.DEBUG) player.getMessages().sendMessage("[DEBUG]: Maximum hit this turn " + "is [" + maxHit + "]."); return maxHit; } /** * Determines if the character within {@code builder} is close enough to * it's victim to attack. * * @param builder * the builder that will be checked. * @return {@code true} if the character is close enough, {@code false} * otherwise. */ public static boolean checkAttackDistance(CombatBuilder builder) { Position attacker = builder.getCharacter().getPosition(); Position victim = builder.getVictim().getPosition(); int distance = builder.getStrategy().attackDistance(builder.getCharacter()); MovementQueue movement = builder.getCharacter().getMovementQueue(); MovementQueue otherMovement = builder.getVictim().getMovementQueue(); if (!movement.isMovementDone() && !otherMovement.isMovementDone() && !movement.isLockMovement() && !builder.getCharacter() .isFrozen()) { distance += 1; // XXX: Might have to change this back to 1 or even remove it, not // sure what it's like on actual runescape. Are you allowed to // attack when the character is trying to run away from you? if (movement.isRunning()) { distance += 2; } } return attacker.withinDistance(victim, distance); } /** * A static factory method that constructs the default melee combat strategy * implementation. * * @return the default melee combat strategy implementation. */ public static CombatStrategy newDefaultMeleeStrategy() { return new DefaultMeleeCombatStrategy(); } /** * A static factory method that constructs the default magic combat strategy * implementation. * * @return the default magic combat strategy implementation. */ public static CombatStrategy newDefaultMagicStrategy() { return new DefaultMagicCombatStrategy(); } /** * A static factory method that constructs the default ranged combat * strategy implementation. * * @return the default ranged combat strategy implementation. */ public static CombatStrategy newDefaultRangedStrategy() { return new DefaultRangedCombatStrategy(); } }