package com.rs.game.player.actions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.rs.Settings;
import com.rs.cache.loaders.AnimationDefinitions;
import com.rs.cache.loaders.ClientScriptMap;
import com.rs.cache.loaders.GeneralRequirementMap;
import com.rs.cache.loaders.ItemDefinitions;
import com.rs.game.Animation;
import com.rs.game.EffectsManager;
import com.rs.game.EffectsManager.Effect;
import com.rs.game.EffectsManager.EffectType;
import com.rs.game.Entity;
import com.rs.game.Graphics;
import com.rs.game.Hit;
import com.rs.game.Hit.HitLook;
import com.rs.game.Projectile;
import com.rs.game.Region;
import com.rs.game.World;
import com.rs.game.WorldTile;
import com.rs.game.item.Item;
import com.rs.game.npc.NPC;
import com.rs.game.npc.dungeonnering.ShadowForgerIhlakhizan;
import com.rs.game.npc.familiar.Familiar;
import com.rs.game.npc.fightkiln.HarAken;
import com.rs.game.npc.fightkiln.HarAkenTentacle;
import com.rs.game.npc.glacior.Glacyte;
import com.rs.game.npc.godwars.GodWarMinion;
import com.rs.game.npc.godwars.armadyl.GodwarsArmadylFaction;
import com.rs.game.npc.godwars.armadyl.KreeArra;
import com.rs.game.npc.godwars.bandos.GeneralGraardor;
import com.rs.game.npc.godwars.bandos.GodwarsBandosFaction;
import com.rs.game.npc.godwars.saradomin.CommanderZilyana;
import com.rs.game.npc.godwars.saradomin.GodwarsSaradominFaction;
import com.rs.game.npc.godwars.zammorak.KrilTstsaroth;
import com.rs.game.npc.godwars.zaros.Nex;
import com.rs.game.npc.godwars.zaros.NexMinion;
import com.rs.game.npc.godwars.zaros.ZarosMinion;
import com.rs.game.npc.others.ClueNPC;
import com.rs.game.npc.others.GuthixBlessing;
import com.rs.game.npc.others.MirrorBackSpider;
import com.rs.game.npc.pest.PestPortal;
import com.rs.game.npc.qbd.QueenBlackDragon;
import com.rs.game.player.ActionBar;
import com.rs.game.player.ActionBar.Shortcut;
import com.rs.game.player.CombatDefinitions;
import com.rs.game.player.Equipment;
import com.rs.game.player.Player;
import com.rs.game.player.Skills;
import com.rs.game.player.content.Combat;
import com.rs.game.player.content.ItemConstants;
import com.rs.game.player.content.Magic;
import com.rs.game.player.content.Slayer;
import com.rs.game.player.controllers.Wilderness;
import com.rs.game.tasks.WorldTask;
import com.rs.game.tasks.WorldTasksManager;
import com.rs.utils.MapAreas;
import com.rs.utils.Utils;

public class PlayerCombatNew extends Action {

	private Entity target;
	private Shortcut nextAbility;

	public void setNextAbility(Shortcut ability) {
		nextAbility = ability;
	}

	public PlayerCombatNew(Entity target) {
		this.target = target;
	}

	@Override
	public boolean start(Player player) {
		if (!process(player))
			return false;
		player.setNextFaceEntity(target);
		return true;
	}

	@Override
	public boolean process(Player player) {
		// checks if target is ingame and not ded
		if (player.isDead() || player.hasFinished() || player.isCantWalk())
			return false;
		if (target.isDead() || target.hasFinished()) {
			// fixes fact u had already started attacked and then target died
			int mainHandDelay = (int) (player.getCombatDefinitions().getMainHandDelay() - Utils.currentWorldCycle());
			int offHandDelay = (int) (player.getCombatDefinitions().getOffHandDelay() - Utils.currentWorldCycle());
			if (mainHandDelay > 0 || offHandDelay > 0) {

				player.setNextAnimation(new Animation(-1));
				// doesnt work <.
				/*
				 * if(player.getCombatDefinitions().getType(Equipment. SLOT_WEAPON) != Combat.MELEE_TYPE ||
				 * (player.getCombatDefinitions().getType(Equipment.SLOT_SHIELD) != Combat.MELEE_TYPE &&
				 * player.getEquipment().hasOffHand() && !player.getEquipment().hasShield())) {
				 * World.sendProjectileNew(player, target, -1, 0, 0, 0, 0, 0, 0);
				 * 
				 * }
				 */

			}
			return false;
		}
		// checks if player appears in viewport
		if (!player.withinDistance(target, 16))
			return false;
		// checks if can interact with that target
		if (target instanceof Player) {
			Player p2 = (Player) target;
			if (!player.isCanPvp() || !p2.isCanPvp())
				return false;
		} else {
			NPC n = (NPC) target;
			if (n.isCantInteract())
				return false;
			// checks if familiar and if so if can attack
			if (n instanceof Familiar) {
				Familiar familiar = (Familiar) n;
				if (!familiar.canAttack(player))
					return false;
			} else {
				// if npc has been defined not to be attackable outside of its
				// path, prevents it
				if (!n.canBeAttackFromOutOfArea() && !MapAreas.isAtArea(n.getMapAreaNameHash(), player))
					return false;
				// checks if is slayer creature and if so, if player has lvl to
				// attack it
				int slayerLevel = Slayer.getLevelRequirement(n.getName());
				if (slayerLevel > player.getSkills().getLevel(Skills.SLAYER)) {
					player.getPackets().sendEntityMessage(1, 0xFFFFFF, player, "You need a slayer level of " + slayerLevel + " to know how to wound this monster.");
					return false;
				}
				// checks for npc exeptions that cant be attacked
				if (isAttackExeption(player, n))
					return false;
			}
		}
		// if player is frozen and under, stops attacking, else stands waiting
		if (player.isStunned() || player.isBound())
			return !Utils.colides(player, target);
		// colission check
		if (Utils.colides(player, target) && !target.hasWalkSteps()) {
			player.resetWalkSteps();
			return player.calcFollow(target, true);
		}
		// diagonal check
		if (hasMeleeHand(player) && Math.abs(player.getX() - target.getX()) == 1 && Math.abs(player.getY() - target.getY()) == 1 && !target.hasWalkSteps() && target.getSize() == 1) {
			player.resetWalkSteps();
			if (!player.addWalkSteps(target.getX(), player.getY(), 1)) {
				player.resetWalkSteps();
				player.addWalkSteps(player.getX(), target.getY(), 1);
			}
			return true;
		}
		// range check
		if (!Utils.isOnRange(player, target, getAttackRange(player)) || !player.clipedProjectile(target, hasMeleeHand(player) && !checkAttackPathAsRange(target))) {
			if (!player.hasWalkSteps() || target.hasWalkSteps()) {
				player.resetWalkSteps();
				player.calcFollow(target, player.getRun() ? 2 : 1, true, true);
			}
		} else
			player.resetWalkSteps();
		return true;
	}

	/*
	 * those mobs are above water/lava so melee wont be able to reach, therefore check as if range
	 */
	private static boolean checkAttackPathAsRange(Entity target) {
		return target instanceof ShadowForgerIhlakhizan || target instanceof PestPortal || target instanceof NexMinion || target instanceof HarAken || target instanceof HarAkenTentacle || target instanceof QueenBlackDragon;
	}

	private boolean isAttackExeption(Player player, NPC n) {
		if (n.getId() == 879) {
			if (player.getEquipment().getWeaponId() != 2402) {
				player.getPackets().sendGameMessage("I'd better wield Silverlight first.");
				return true;
			}
		} else if (n.getId() >= 14084 && n.getId() <= 14139) {
			int weaponId = player.getEquipment().getWeaponId();
			if (!((weaponId >= 13117 && weaponId <= 13146) || (weaponId >= 21580 && weaponId <= 21582))) {
				player.getPackets().sendGameMessage("I'd better wield a silver weapon first.");
				return true;
			}
		} else if (n instanceof GodwarsArmadylFaction) {
			if (hasMeleeHand(player)) {
				player.getPackets().sendGameMessage("The Aviansie is flying too high for you to attack using melee.");
				return true;
			}
		} else if (n.getId() == 14301 || n.getId() == 14302 || n.getId() == 14303 || n.getId() == 14304) {
			Glacyte glacyte = (Glacyte) n;
			if (glacyte.getGlacor().getTargetIndex() != -1 && player.getIndex() != glacyte.getGlacor().getTargetIndex()) {
				player.getPackets().sendGameMessage("This isn't your target.");
				return true;
			}
		} else if (n.getId() == 1007 || n.getId() == 1264 || n.getId() == 5144 || n.getId() == 5145) {
			ClueNPC npc = (ClueNPC) n;
			if (npc.getTarget() != player) {
				player.getPackets().sendGameMessage("This isn't your target.");
				return true;
			}
		} else if (n.getId() == 16980) {
			GuthixBlessing blessing = (GuthixBlessing) n;
			if (!player.isCanPvp()) {
				player.getPackets().sendGameMessage("You cannot fight other people's followers here.");
				return true;
			} else if (blessing.getTarget() == player) {
				player.getPackets().sendGameMessage("You cannot fight your own follower.");
				return true;
			}
		}
		return false;
	}

	private static boolean hasMeleeHand(Player player) {
		return player.getEquipment().getItem(Equipment.SLOT_WEAPON) == null || (player.getCombatDefinitions().getType(Equipment.SLOT_WEAPON) == Combat.MELEE_TYPE || (player.getEquipment().hasOffHand() && player.getCombatDefinitions().getType(Equipment.SLOT_SHIELD) == Combat.MELEE_TYPE));
	}

	private static int calculateAttackRange(Player player, boolean mainHand) {
		int weaponType = player.getCombatDefinitions().getType(mainHand ? Equipment.SLOT_WEAPON : Equipment.SLOT_SHIELD);
		Item item = player.getEquipment().getItem(mainHand ? Equipment.SLOT_WEAPON : Equipment.SLOT_SHIELD);
		if (item == null || !mainHand && player.getEquipment().hasShield())
			return mainHand ? 0 : -1;
		if (weaponType == Combat.MAGIC_TYPE)
			return 8;
		int id = item.getId();
		if (weaponType == Combat.MELEE_TYPE) {
			if (mainHand)
				if (item.getName().contains("halberd") || item.getName().contains("polearm"))
					return 1;
			return 0;
		}
		int style = player.getCombatDefinitions().getStyle(!mainHand);
		int speed = getAttackSpeed(player, mainHand);
		if (style == Combat.THROWN_STYLE) {
			if (speed == 5)
				return 7;
			else if (speed == 6) {// darts, thrownaxe
				if (id == 13879 || id == 13953)
					return 7;
				else if (id == 21364)// sagie
					return 9;
				return 4;
			} else if (speed == 4) {// knives, javelin
				if (id == 30574 || id == 30575)// death lotus darts
					return 6;
				return 5;
			}
		} else if (style == Combat.ARROW_STYLE || style == Combat.BOLT_STYLE) {
			if (speed == 4 || speed == 5 || speed == 12)
				return 7;
			else if (speed == 6) {
				if (id == 24338 || id == 24339 || id == 18331 || id == 18332 || id == 29634)// royal
					// crossbow
					// &
					// slighted
					// bows
					return 9;
				return 8;
			}
		}
		return 0;
	}

	private static int getAttackRange(Player player) {
		int mainHandRange = calculateAttackRange(player, true);
		int offHandRange = calculateAttackRange(player, false);
		if (offHandRange == -1)
			return mainHandRange;
		else if (mainHandRange > offHandRange)
			return offHandRange;
		else if (offHandRange > mainHandRange)
			return mainHandRange;
		return mainHandRange;
	}

	@Override
	public int processWithDelay(Player player) {
		int mainHandDelay = (int) (player.getCombatDefinitions().getMainHandDelay() - Utils.currentWorldCycle());
		if (nextAbility == null && mainHandDelay > 0) {
			if (!player.getEquipment().hasOffHand())
				return 0;
			int offHandDelay = (int) (player.getCombatDefinitions().getOffHandDelay() - Utils.currentWorldCycle());
			if (offHandDelay > 0)
				return 0;
		}
		if (player.getActionbar().isComboOn() || player.isStunned() || (player.getActionbar().hasQueuedShortcut()

				&& player.getVarsManager().getBitValue(ActionBar.REGENERATION_VARBIT_ID) == 0

				&& nextAbility == null))
			return 0;

		// player is walking to atm
		if (!isWithinDistance(player, target)) // doesnt let u attack when u
			// under / while walking out,
			// remove this check if u want
			return 0;
		if (!player.getControlerManager().keepCombating(target))
			return -1;
		return attack(player, nextAbility != null || mainHandDelay <= 0);
	}

	public static boolean isWithinDistance(Player player, Entity target) {
		// player is walking to atm
		if (!player.clipedProjectile(target, hasMeleeHand(player) && !checkAttackPathAsRange(target)) || !Utils.isOnRange(player, target,
				getAttackRange(player)
						// correct extra distance for walk. no glitches
						// this way ^^. even if frozen
						+ (player.hasWalkSteps() && target.hasWalkSteps() ? (player.getRun() && target.getRun() ? 2 : 1) : 0))
				|| Utils.colides(player, target)) // doesnt let u attack when u
			// under / while walking out,
			// remove this check if u want
			return false;
		return true;
	}

	private int attack(Player player, boolean mainHand) {
		switch (player.getCombatDefinitions().getType(mainHand ? Equipment.SLOT_WEAPON : Equipment.SLOT_SHIELD)) {
		case Combat.MAGIC_TYPE:
			return attackMagic(player, mainHand);
		case Combat.RANGE_TYPE:
			return attackRange(player, mainHand);
		case Combat.MELEE_TYPE:
		default:
			return attackMelee(player, mainHand);
		}

	}

	private static interface SpellEffect {

		void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell);

	}

	private static final SpellEffect SMOKE_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			target.getEffectsManager().startEffect(new Effect(EffectType.SMOKE_EFFECT, 17, mainTarget ? 0.95 : 0.97));
		}
	};

	private static final SpellEffect SHADOW_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			target.getEffectsManager().startEffect(new Effect(EffectType.SHADOW_EFFECT, 17, mainTarget ? 0.95 : 0.97));

		}
	};

	private static final SpellEffect BLOOD_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			player.heal((int) (hit.getDamage() * (mainTarget ? 0.05 : 0.03)));
		}
	};

	private static final SpellEffect ICE_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			if (hit.getDamage() > 0) {
				int delay = target instanceof NPC ? 17 : spell == CombatSpell.ICE_BARRAGE ? 16 : spell == CombatSpell.ICE_RUSH ? 12 : 8;
				EffectsManager.addBoundEffect(target, delay, true, 5);
			}
		}
	};

	private static final SpellEffect CONFUSE_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			target.getEffectsManager().startEffect(new Effect(EffectType.CONFUSE_EFFECT, 100, mainTarget ? 0.95 : 0.97));
		}
	};

	private static final SpellEffect CURSE_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			target.getEffectsManager().startEffect(new Effect(EffectType.CURSE_EFFECT, 100, mainTarget ? 1.05 : 1.03));
		}
	};

	private static final SpellEffect WEAKEN_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			target.getEffectsManager().startEffect(new Effect(EffectType.WEAKEN_EFFECT, 100, mainTarget ? 0.95 : 0.97));
		}
	};

	private static final SpellEffect STAGGER_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			target.getEffectsManager().startEffect(new Effect(EffectType.STAGGER_EFFECT, 100, mainTarget ? 0.90 : 0.93));
		}
	};

	private static final SpellEffect VULNERABILITY_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			target.getEffectsManager().startEffect(new Effect(EffectType.VULNERABILITY_EFFECT, 100, mainTarget ? 1.10 : 1.07));
		}
	};

	private static final SpellEffect ENFEEBLE_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			target.getEffectsManager().startEffect(new Effect(EffectType.ENFEEBLE_EFFECT, 100, mainTarget ? 0.90 : 0.93));
		}
	};

	private static final SpellEffect BIND_EFFECT = new SpellEffect() {
		@Override
		public void execute(Player player, Entity target, Hit hit, boolean mainTarget, CombatSpell spell) {
			if (!target.isBoundImmune()) {
				int delay = spell == CombatSpell.ENTANGLE ? 66 : spell == CombatSpell.SNARE ? 50 : 33;
				if (target instanceof Player)
					delay /= 2;
				EffectsManager.addBoundEffect(target, delay, true, 5);
			}
		}
	};

	// handle spell here(requires emote and project height/angle) and special
	// effects if any
	private static enum CombatSpell {
		// standart spells damage
		AIR_STRIKE(14, 14221, 30, 30, 99, null, false), WATER_STRIKE(16, 14220, 30, 30, 90, null, false), EARTH_STRIKE(18, 14222, 35, 35, 90, null, false), FIRE_STRIKE(21, 14223, 30, 30, 90, null, false), AIR_BOLT(23, 14221, 30, 30, 90, null, false), WATER_BOLT(37, 14220, 30, 30, 90, null, false), EARTH_BOLT(30, 14222, 35, 35, 90, null, false), FIRE_BOLT(33, 14223, 30, 30, 90, null, false), AIR_BLAST(37, 14221, 30, 30, 90, null, false), WATER_BLAST(40, 14220, 30, 30, 90, null, false), EARTH_BLAST(46, 14222, 35, 35, 90, null, false), FIRE_BLAST(51, 14223, 30, 30, 90, null, false), AIR_WAVE(58, 14221, 30, 30, 90, null, false), WATER_WAVE(61, 14220, 30, 30, 90, null, false), EART_WAVE(65, 14222, 35, 35, 90, null, false), FIRE_WAVE(68, 14223, 30, 30, 90, null, false), AIR_SURGE(73, 14221, 30, 30, 90, null, false), WATER_SURGE(78, 14220, 30, 30, 90, null, false), EARTH_SURGE(76, 14222, 35, 35, 90, null, false), FIRE_SURGE(80, 14223, 30, 30, 90, null, false), DIVINE_STORM(54, 10546, 18, 18, 90, null, false), ARMADYL_STORM(69, 10546, 18, 18, 90, null, false), SLAYER_DART(44, 1978, 35, 35, 5, null, false)
		// ancient spells
		, SMOKE_RUSH(81, 1978, 25, 25, 90, SMOKE_EFFECT, false), SHADOW_RUSH(82, 1978, 25, 25, 90, SHADOW_EFFECT, false), BLOOD_RUSH(84, 1978, 22, 22, 90, BLOOD_EFFECT, false), ICE_RUSH(85, 1978, 22, 22, 90, ICE_EFFECT, false), SMOKE_BURST(87, 1979, 25, 25, 90, SMOKE_EFFECT, true), SHADOW_BURST(88, 1979, 25, 25, 90, SHADOW_EFFECT, true), BLOOD_BURST(90, 1979, 22, 22, 90, BLOOD_EFFECT, true), ICE_BURST(91, 1979, 22, 22, 90, ICE_EFFECT, true), SMOKE_BLITZ(93, 1978, 25, 25, 90, SMOKE_EFFECT, false), SHADOW_BLITZ(94, 1978, 25, 25, 90, SHADOW_EFFECT, false), BLOOD_BLITZ(96, 1978, 22, 22, 90, BLOOD_EFFECT, false), ICE_BLITZ(97, 1978, 22, 22, 90, ICE_EFFECT, false), SMOKE_BARRAGE(99, 1979, 25, 25, 90, SMOKE_EFFECT, true), SHADOW_BARRAGE(100, 1979, 25, 25, 90, SHADOW_EFFECT, true), BLOOD_BARRAGE(102, 1979, 22, 22, 90, BLOOD_EFFECT, true), ICE_BARRAGE(103, 1979, 22, 22, 90, ICE_EFFECT, true)
		// standart spells curses
		, CONFUSE(15, 711, 18, 18, 90, CONFUSE_EFFECT, false), WEAKEN(20, 716, 18, 18, 90, WEAKEN_EFFECT, false), CURSE(24, 711, 18, 18, 90, CURSE_EFFECT, false), VULNERABILITY(63, 729, 18, 18, 90, VULNERABILITY_EFFECT, false), ENFEEBLE(66, 729, 18, 18, 90, ENFEEBLE_EFFECT, false), STAGGER(71, 729, 18, 18, 90, STAGGER_EFFECT, false)
		// stndart spells binds
		, BIND(25, 710, 18, 18, 90, BIND_EFFECT, false), SNARE(43, 710, 18, 18, 90, BIND_EFFECT, false), ENTANGLE(70, 710, 18, 18, 90, BIND_EFFECT, false)
		// all books
		, POLYPORE_STRIKE(162, 15448, 50, 32, 90, null, false);

		// 0 - bothands. 1 - hand1. 2 - hand2.
		private Animation attackAnim;
		private int id, pStartHeight, pEndHeight, pAngle;
		private SpellEffect effect;
		private boolean multi;

		private CombatSpell(int id, int attackAnim, int pStartHeight, int pEndHeight, int pAngle, SpellEffect effect, boolean multi) {
			this.id = id;
			this.attackAnim = new Animation(attackAnim);
			this.pStartHeight = pStartHeight;
			this.pEndHeight = pEndHeight;
			this.pAngle = pAngle;
			this.effect = effect;
			this.multi = multi;
		}

		private static CombatSpell getCombatSpell(int id) {
			for (CombatSpell spell : CombatSpell.values())
				if (spell.id == id)
					return spell;
			return null;
		}
	}

	private void resetSpell(Player player, boolean manualCast) {
		if (manualCast) {
			player.getCombatDefinitions().resetSpells(false);
			// manual casting no longer resets combat from what im aware of
			// player.getActionManager().forceStop();
		}
	}

	private int attackMagic(final Player player, boolean mainHand) {
		int spellId = player.getCombatDefinitions().getSpellId();
		if (spellId == 0) {
			player.getPackets().sendGameMessage("You need to set a spell in your magic book to use magic.");
			return -1;
		}

		boolean manualCast = spellId >= 256;
		if (manualCast)
			spellId -= 256;
		// no need to check lvl. the single fact he could select spell means he
		// has lvl
		GeneralRequirementMap spellData = Magic.getSpellData(spellId);

		if (spellId == 44) { // slayer dart exeption required

			Item weapon = player.getEquipment().getItem(mainHand ? Equipment.SLOT_WEAPON : Equipment.SLOT_SHIELD);
			if (!Magic.hasStaffOfLight(weapon.getId()) && weapon.getId() != 30793 && weapon.getId() != 30796 && weapon.getId() != 4170) {
				// TODO real message
				player.getPackets().sendGameMessage("You can't cast this spell with this weapon.");
				resetSpell(player, manualCast); // cant reset spell earlier cuz
				// else it gets damage for wrong
				// spell
				return -1;
			}
		} else if (spellId == 162) { // polypore strike exeption required
			Item weapon = player.getEquipment().getItem(mainHand ? Equipment.SLOT_WEAPON : Equipment.SLOT_SHIELD);
			if (weapon.getId() != 22494 && weapon.getId() != 22496) {
				player.getPackets().sendGameMessage("You can't cast this spell with this weapon.");
				resetSpell(player, manualCast); // cant reset spell earlier cuz
				// else it gets damage for wrong
				// spell
				return -1;
			}
			if (weapon.getId() == 22494) {
				player.getCharges().addCharges(22496, ItemConstants.getItemDefaultCharges(22496), Equipment.SLOT_WEAPON);
				player.getEquipment().getItem(Equipment.SLOT_WEAPON).setId(22496);
				player.getEquipment().refresh(Equipment.SLOT_WEAPON);
			}
			player.getCharges().addCharges(22496, -1, Equipment.SLOT_WEAPON);
		}

		// not having runes no longer resets spell, instead just stops combat
		if (!Magic.checkRunes(player, spellData, (nextAbility == null || player.getEffectsManager().hasActiveEffect(EffectType.METAMORPHISIS)))) {
			resetSpell(player, manualCast); // cant reset spell earlier cuz else
			// it gets damage for wrong spell
			return -1;
		}
		if (nextAbility != null)
			return handleAbilityAttack(player);
		if (mainHand && player.getCombatDefinitions().isUsingSpecialAttack())
			return handleSpecialAttack(player);

		int pStartHeight;
		int pEndHeight;
		int pAngle;
		boolean multi;
		Animation attackAnim;
		final SpellEffect effect;
		final Hit hit = getHit(player, mainHand, Combat.MAGIC_TYPE);

		final CombatSpell spell = CombatSpell.getCombatSpell(spellId);

		if (spell == null) { // shouldnt happen unless spell not coded
			attackAnim = new Animation(getAttackAnimation(player, mainHand));
			pStartHeight = 40;
			pEndHeight = 36;
			pAngle = 90;
			effect = null;
			multi = false;
		} else {
			attackAnim = spell.attackAnim;
			pStartHeight = spell.pStartHeight;
			pEndHeight = spell.pEndHeight;
			pAngle = spell.pAngle;
			effect = spell.effect;
			multi = spell.multi;
		}
		AnimationDefinitions attackAnimDefs = attackAnim.getDefinitions();
		if (player.getCombatDefinitions().getCombatMode() != CombatDefinitions.LEGACY_COMBAT_MODE)
			attackAnim = new Animation(spellData.getIntValue(player.getEquipment().hasTwoHandedWeapon() ? 2919 : 2914));
		Integer startGfx = attackAnimDefs.clientScriptData == null ? null : (Integer) attackAnimDefs.clientScriptData.get(2920);

		int projectileGfx = spellData.getIntValue(2940);
		int projectileDelay;
		int projectileTime;
		int hitDelay;

		int spellBook = Magic.getSpellBook(spellData);

		if (spellBook == 1) { // ancient spells exeption required sadly
			if (spell == CombatSpell.ICE_RUSH || spell == CombatSpell.ICE_BURST || spell == CombatSpell.ICE_BLITZ) {
				if (spell == CombatSpell.ICE_BLITZ)
					startGfx = 366;
				projectileTime = 60;
				hitDelay = 2;
			} else {
				projectileTime = 0;
				hitDelay = 1;
			}
			projectileDelay = 0;
			int type = Magic.getSpellType(spellData);

			// rldTile from, WorldTile to, int graphicId, int startHeight, int
			// endHeight, int startTime, int speed, int slope, int angle) {
			// if (type == 1 || type == 3) //means its smoke or shadow
			if (spell != CombatSpell.ICE_BLITZ)
				World.sendProjectileNew(player, target, projectileGfx, pStartHeight, pEndHeight, 51, 5, 0, pAngle);
		} else {
			projectileDelay = Magic.isAutoCastSpell(spellData) ? 55 : 15;// attackAnimDefs.getEmoteClientCycles()
			// -
			// (spell
			// ==
			// CombatSpell.POLYPORE_STRIKE
			// ? 60
			// :
			// 35);
			projectileTime = World.sendProjectileNew(player, target, projectileGfx, pStartHeight, pEndHeight, projectileDelay, 5, 0, pAngle).getEndTime();
			hitDelay = Utils.projectileTimeToCycles(projectileTime);
			if (spell == CombatSpell.FIRE_SURGE) { // exeption required for
				// those spells as data not
				// in cache for them or diff
				World.sendProjectileNew(player, target, 2736, pStartHeight, pEndHeight, projectileDelay, 5, 20, 90).getEndTime();
				World.sendProjectileNew(player, target, 2736, pStartHeight, pEndHeight, projectileDelay, 5, 110, 90).getEndTime();

			} else if (spell == CombatSpell.WATER_SURGE)
				startGfx++;
			else if (spell == CombatSpell.EARTH_STRIKE)
				startGfx--;
			else if (spell == CombatSpell.EARTH_BLAST)
				startGfx++;
			else if (spell == CombatSpell.EART_WAVE)
				startGfx += 2;
			else if (spell == CombatSpell.EARTH_SURGE)
				startGfx += 3;
			else if (spell == CombatSpell.CONFUSE)
				startGfx = 102;
			else if (spell == CombatSpell.WEAKEN)
				startGfx = 105;
			else if (spell == CombatSpell.CURSE)
				startGfx = 110;
			else if (spell == CombatSpell.VULNERABILITY)
				startGfx = 167;
			else if (spell == CombatSpell.ENFEEBLE)
				startGfx = 170;
			else if (spell == CombatSpell.STAGGER)
				startGfx = 173;
			else if (spell == CombatSpell.BIND || spell == CombatSpell.SNARE || spell == CombatSpell.ENTANGLE)
				startGfx = 177;
			else if (spell == CombatSpell.POLYPORE_STRIKE)
				startGfx = 2034;
		}

		player.setNextAnimation(attackAnim);
		if (startGfx != null)
			player.setNextGraphics(new Graphics(startGfx));

		if (hit.getDamage() == -1) { // splash
			target.setNextGraphics(new Graphics(85, projectileTime, 96));
			// playSound(227, player, target);
			delayHit(target, hitDelay, hit); // had to write 2x. this one non
			// multi as splash
		} else { // process spell
			if (Magic.getSpellDamage(spellData) == 0)
				hit.setDamage(-1); // no damage spell, but not splash either
			int targetGfx = spellData.getIntValue(2933);
			Entity[] targets = !multi ? new Entity[] { target } : getMultiAttackTargets(player);
			for (final Entity target : targets) {
				if (targetGfx != 0)
					target.setNextGraphics(new Graphics(targetGfx, projectileTime, spellBook == 1 ? 0 : 96));
				final Hit hitTarget = target == this.target ? hit : getHit(player, target, mainHand, Combat.MAGIC_TYPE, 1, 1, false);
				delayHit(target, hitDelay, hitTarget);
				if (effect != null) {
					WorldTasksManager.schedule(new WorldTask() {
						@Override
						public void run() {
							if (player.hasFinished() || target.isDead() || target.hasFinished() || (target instanceof NPC && ((NPC) target).isCantInteract()))
								return;
							effect.execute(player, target, hitTarget, target == PlayerCombatNew.this.target, spell);
						}
					}, projectileTime == 0 ? 0 : (Utils.projectileTimeToCycles(projectileTime) - 1));
				}
			}
		}
		resetSpell(player, manualCast); // cant reset spell earlier cuz else it
		// gets damage for wrong spell
		if (player.getCombatDefinitions().getCombatMode() != CombatDefinitions.LEGACY_COMBAT_MODE)
			addAdrenaline(player, mainHand ? player.getEquipment().hasTwoHandedWeapon() ? 3 : 2 : 1);
		setWeaponDelay(player, mainHand);
		return 0;
	}

	public interface MultiAttack {
		public boolean attack();
	}

	public void attackTarget(Entity[] targets, MultiAttack perform) {
		Entity realTarget = target;
		for (Entity t : targets) {
			target = t;
			if (!perform.attack())
				break;
		}
		target = realTarget;
	}

	private boolean checkAmmo(Player player, boolean mainHand) {
		ItemDefinitions defs = player.getEquipment().getItem(mainHand ? Equipment.SLOT_WEAPON : Equipment.SLOT_SHIELD).getDefinitions();
		if (defs.getCSOpcode(2940) != 0) // no need for ammo
			return true;
		Item ammo = player.getEquipment().getItem(Equipment.SLOT_ARROWS);
		if (ammo == null) {
			player.getPackets().sendEntityMessage(1, 0xFFFFFF, player, "You have no ammo equipped.");
			return false;
		}
		int attackStyle = player.getCombatDefinitions().getStyle(!mainHand);
		boolean result;
		switch (attackStyle) {
		case Combat.ARROW_STYLE:
			result = ammo.getName().contains("arrow");
			break;
		case Combat.BOLT_STYLE:
			// handcannon exeption
			result = defs.id == 15241 ? ammo.getId() == 15243 : ammo.getName().contains("bolt");
			break;
		case Combat.THROWN_STYLE: // throwables not supposed to use ammo
		default:
			result = false;
			break;
		}
		if (result)
			result = defs.getCSOpcode(750) >= ammo.getDefinitions().getCSOpcode(750);
		if (!result)
			player.getPackets().sendEntityMessage(1, 0xFFFFFF, player, "You can't use that ammunition with your weapon.");
		return result;

	}

	private int attackRange(Player player, boolean mainHand) {
		if (!checkAmmo(player, mainHand))
			return -1;
		if (nextAbility != null)
			return handleAbilityAttack(player);
		if (mainHand && player.getCombatDefinitions().isUsingSpecialAttack())
			return handleSpecialAttack(player);

		Animation attackAnim = new Animation(getAttackAnimation(player, mainHand));

		int projectileDelay;// =
		// attackAnim.getDefinitions().getEmoteClientCycles();

		// 8=Arrow, 9=Bolt, 10=Thrown
		int attackStyle = player.getCombatDefinitions().getStyle(!mainHand);
		ItemDefinitions defs = player.getEquipment().getItem(mainHand ? Equipment.SLOT_WEAPON : Equipment.SLOT_SHIELD).getDefinitions();

		int projectileGfx = defs.getCSOpcode(2940);
		if (projectileGfx == 0) { // uses ammo
			ItemDefinitions ammoDefs = player.getEquipment().getItem(Equipment.SLOT_ARROWS).getDefinitions();
			projectileGfx = ammoDefs.getCSOpcode(2940);
		}

		// Load graphics no longer exist on rs.
		// Graphics loadGfx = getLoadWeaponGraphic(defs.id,
		// defs.getCSOpcode(2940) != 0 ? -1 :
		// player.getEquipment().getItem(Equipment.SLOT_ARROWS).getId());
		player.setNextAnimation(attackAnim);
		/*
		 * if (loadGfx != null) player.setNextGraphics(loadGfx);
		 */

		switch (attackStyle) {
		case Combat.ARROW_STYLE:

			projectileDelay = 55;

			// projectileDelay -= 15;
			if (Combat.hasDarkbow(player)) { // darkbow exeption
				int slope = Utils.random(5);
				int delay = Utils.projectileTimeToCycles(World.sendProjectileNew(player, target, projectileGfx, 40, 41, projectileDelay, 5, slope, 5).getEndTime()) - 1;
				World.sendProjectileNew(player, target, projectileGfx, 40, 41, projectileDelay, 5, slope + 5, 5);
				delayHit(delay, getHit(player, mainHand, Combat.RANGE_TYPE));
				delayHit(delay + 1, getHit(player, mainHand, Combat.RANGE_TYPE));
				removeAmmo(player, 2, delay);
			} else {
				int delay = Utils.projectileTimeToCycles(World.sendProjectileNew(player, target, projectileGfx, 40, 41, projectileDelay, 5, Utils.random(5), 5).getEndTime()) - 1;
				// Exceptions
				String name = defs.getName().toLowerCase();
				if (name.contains("zaryte") || name.contains("crystal"))
					player.getCharges().addCharges(defs.id, -1, Equipment.SLOT_WEAPON);
				delayHit(delay, getHit(player, mainHand, Combat.RANGE_TYPE));
				if (defs.getCSOpcode(2940) == 0) // crystal bow doesnt use ammo
					// for instance
					removeAmmo(player, 1, defs.getName().contains("bow") ? delay : -1);
			}
			break;
		case Combat.BOLT_STYLE:

			projectileDelay = defs.getEquipLookHideSlot() == 5 ? 55 : !mainHand ? 40 : 15;

			if (defs.id == 15241 && Utils.random(player.getSkills().getLevel(Skills.FIREMAKING) << 1) == 0) {// handcannon
				// exeption
				player.setNextAnimation(new Animation(12175));
				player.setNextGraphics(new Graphics(2140));
				player.getEquipment().getItems().set(3, null);
				player.getEquipment().refresh(3);
				player.getAppearence().generateAppearenceData();
				player.applyHit(new Hit(player, Utils.random(1500) + 100, HitLook.REGULAR_DAMAGE));
				setWeaponDelay(player, mainHand);
				return 0;
			}
			// projectileDelay -= 20;
			int delay = Utils.projectileTimeToCycles(World.sendProjectileNew(player, target, projectileGfx, 40, 41, projectileDelay, 5, Utils.random(5), 5).getEndTime()) - 1;
			delayHit(delay, getHit(player, mainHand, Combat.RANGE_TYPE));
			if (defs.getCSOpcode(2940) == 0)
				removeAmmo(player, 1, defs.getName().contains("crossbow") ? delay : -1);
			break;
		case Combat.THROWN_STYLE:
			projectileDelay = 35;
			delay = World.sendProjectileNew(player, target, projectileGfx, 40, 41, projectileDelay, 5, Utils.random(5), 5).getEndTime() - 1;
			boolean usingChinchompa = defs.id == 10033 || defs.id == 10034;
			if (usingChinchompa) { // Chinchompa exeption
				for (Entity target : getMultiAttackTargets(player))
					delayHit(target, Utils.projectileTimeToCycles(delay), getHit(player, mainHand, Combat.RANGE_TYPE));
			} else
				delayHit(Utils.projectileTimeToCycles(delay), getHit(player, mainHand, Combat.RANGE_TYPE));
			boolean deleteAmmo = defs.id == 732 || usingChinchompa;
			removeAmmo(player, attackStyle == Combat.THROWN_STYLE ? mainHand ? -1 : -2 : 1, !deleteAmmo ? delay : -1);
		default:
			break;
		}
		if (player.getCombatDefinitions().getCombatMode() != CombatDefinitions.LEGACY_COMBAT_MODE)
			addAdrenaline(player, mainHand ? player.getEquipment().hasTwoHandedWeapon() ? 3 : 2 : 1);
		setWeaponDelay(player, mainHand);
		return 0;
	}

	// quantity -1 = weapon. -2 offhand weapon
	private void removeAmmo(final Player player, final int quantity, int delay) {
		if (nextAbility != null)// Abilities don't remove ammo ^.^
			return;
		final int ammoId = quantity == -1 ? player.getEquipment().getWeaponId() : quantity == -2 ? player.getEquipment().getShieldId() : player.getEquipment().getAmmoId();
		player.getEquipment().removeAmmo(ammoId, quantity);
		if (delay != -1)
			WorldTasksManager.schedule(new WorldTask() {

				@Override
				public void run() {
					World.updateGroundItem(new Item(ammoId, quantity == -1 || quantity == -2 ? 1 : quantity), target.getMiddleWorldTile(), player);
				}
			}, delay);
	}

	private int attackMelee(Player player, boolean mainHand) {
		if (nextAbility != null)
			return handleAbilityAttack(player);
		if (mainHand && player.getCombatDefinitions().isUsingSpecialAttack())
			return handleSpecialAttack(player);

		Animation attackAnim = new Animation(getAttackAnimation(player, mainHand));
		// so that emote end matches cycle end and mark
		/*
		 * attackAnim.setDelay(60-attackAnim.getDefinitions(). getEmoteClientCycles());
		 * System.out.println("d: "+attackAnim.getSpeed());
		 */

		/*
		 * int animClientCycles = attackAnim.getDefinitions().getEmoteClientCycles(); int worldCycleDelay =
		 * animClientCycles / 60; int clientCycleDelay = ((worldCycleDelay + 1) * 60 - animClientCycles) - 10; if
		 * (clientCycleDelay < 0) //some weaps are faster than cycle lol, those few dont need delay(such as new ags
		 * emote) clientCycleDelay = 0;
		 * 
		 * attackAnim.setDelay(clientCycleDelay);
		 */

		// rs downgraded combat delay <..<.
		player.setNextAnimation(attackAnim);

		if (player.getCombatDefinitions().getCombatMode() != CombatDefinitions.LEGACY_COMBAT_MODE)
			addAdrenaline(player, mainHand ? player.getEquipment().hasTwoHandedWeapon() ? 3 : 2 : 1);

		delayHit(/* worldCycleDelay */0, getHit(player, mainHand, Combat.MELEE_TYPE));
		setWeaponDelay(player, mainHand);
		return 0;
	}

	public static void addAdrenaline(Player player, int amt) {
		player.getCombatDefinitions().increaseSpecialAttack(amt);
	}

	public int handleAbilityAttack(final Player player) {
		int abilityId = nextAbility.getId(player);
		GeneralRequirementMap data = GeneralRequirementMap.getMap(ClientScriptMap.getMap(ActionBar.CS_DATA_ID[nextAbility.getType() - 1]).getIntValue(abilityId == 200 ? 2 : abilityId));

		// 2915, 6706

		int currentWeapon = player.getEquipment().getWeaponId();

		// 2919 - mainhand
		// 2914 - offhand

		boolean twoHWep = player.getEquipment().hasTwoHandedWeapon();
		ClientScriptMap map = ClientScriptMap.getMap(data.getIntValue(2915));
		final ItemDefinitions def = ItemDefinitions.getItemDefinitions(currentWeapon);
		int emoteId = data.getIntValue(twoHWep ? 2919 : 2914);
		if (emoteId == 0)
			emoteId = map.getIntValue(currentWeapon == -1 ? 0 : def.getCSOpcode(686));
		if ((nextAbility.getType() == ActionBar.MAGIC_ABILITY_SHORTCUT && nextAbility.getId(player) == 164) || nextAbility.getType() == ActionBar.RANGED_ABILITY_SHORTCUT && nextAbility.getId(player) == 14)
			emoteId = -1;
		int projectileId = def.getCSOpcode(2940);
		if (projectileId == 0)
			projectileId = data.getIntValue(2940);
		final int targetGFX = data.getIntValue(2933);

		Animation attackAnim = new Animation(emoteId);
		AnimationDefinitions defs = attackAnim.getDefinitions();

		player.setNextAnimation(attackAnim);

		Integer startGfx = defs.clientScriptData == null ? null : (Integer) defs.clientScriptData.get(2920);
		if (startGfx != null) {
			Integer startGfxHeight = (Integer) defs.clientScriptData.get(4339);
			player.setNextGraphics(new Graphics(startGfx, 0, startGfxHeight == null ? 0 : startGfxHeight));
		}

		// if (Settings.DEBUG)
		// System.out.println("Emote: " +emoteId+ ", Projectile: " +
		// projectileId + ", Target GFX: " + targetGFX + ", StartGFX: "+startGfx
		// != null ? startGfx : 0);

		switch (nextAbility.getType()) {
		case ActionBar.MAGIC_ABILITY_SHORTCUT:
			switch (abilityId) {
			case 1:// WRACK
				delayHit(target, 0, getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, target.getEffectsManager().hasActiveEffect(EffectType.STUNNED) ? 1.25 : 0.94, false, true));
				target.setNextGraphics(new Graphics(targetGFX, 0, 130));
				break;
			case 6:// DRAGON'S BREATH
				Projectile projectile = World.sendProjectileNew(player, target, projectileId, 22, 40, 15, 5, 0, 10);
				int angle = (int) Math.round(Math.toDegrees(Math.atan2((player.getX() * 2 + player.getSize()) - (target.getX() * 2 + target.getSize()), (player.getY() * 2 + player.getSize()) - (target.getY() * 2 + target.getSize()))) / 45d) & 0x7;
				for (Entity t : getMultiAttackTargets(player, target, Utils.getDistance(player, target), 9)) {
					int nextAngle = (int) Math.round(Math.toDegrees(Math.atan2((player.getX() * 2 + player.getSize()) - (t.getX() * 2 + t.getSize()), (player.getY() * 2 + player.getSize()) - (t.getY() * 2 + t.getSize()))) / 45d) & 0x7;
					if (nextAngle != angle)
						continue;
					t.getEffectsManager().startEffect(new Effect(EffectType.PROTECTION_DISABLED, 8));
					boolean riderAmulet = player.getEquipment().getAmuletId() == 30929;
					if (riderAmulet && Utils.random(100) < 5) {
						int minumumDamage = getMaxHit(player, t, true, Combat.MAGIC_TYPE, 1.0, true), damage = (int) ((minumumDamage * 0.10) / 3);
						t.getEffectsManager().startEffect(new Effect(EffectType.COMBUST, 10, HitLook.MAGIC_DAMAGE, new Graphics(targetGFX), damage, 4, new WorldTile(t)));
					}
					delayHit(t, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, t, true, Combat.MAGIC_TYPE, 1.0, riderAmulet ? 1.98 : 1.88, false, true));
					break;
				}
				break;
			case 165:// SONIC WAVE
				projectile = World.sendProjectileNew(player, target, projectileId, 32, 40, 40, 5, 0, 10);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 1.57, false, true));
				player.getEffectsManager().startEffect(new Effect(EffectType.MODIFY_ACCURACY, 15, 0.10));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime() / 2, 130));
				break;
			case 3:// IMPACT
			case 171:// DEEP IMPACT
				projectile = World.sendProjectileNew(player, target, projectileId, 32, 37, 15, 5, 0, 10);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, abilityId == 170 ? 2.0 : target.isStunImmune() ? 1.88 : 1.0, false, true));
				target.setStunDelay(abilityId == 170 ? 8 : 5);
				angle = (int) Math.round(Math.toDegrees(Math.atan2((player.getX() * 2 + player.getSize()) - (target.getX() * 2 + target.getSize()), (player.getY() * 2 + player.getSize()) - (target.getY() * 2 + target.getSize()))) / 45d) & 0x7;
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime() / 2, 130, angle, true));
				target.setNextGraphics(new Graphics(3488, projectile.getEndTime(), 92));
				break;
			case 166:// CONCENTRATED BLAST
				player.getEffectsManager().startEffect(new Effect(EffectType.CONCENTRATED_BLAST, 10, projectileId, targetGFX));
				break;
			case 4:// CHAIN
				projectile = World.sendProjectileNew(player, target, projectileId, 22, 39, 15, 5, 0, 10);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 1.0, false, true));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 130));
				for (Entity e : getMultiAttackTargets(player, target, 9, 3)) {
					if (e == target)
						continue;
					Projectile proj = World.sendProjectileNew(target, e, projectileId, 22, 39, projectile.getEndTime() + 15, 1, 0, 10);
					e.setNextGraphics(new Graphics(targetGFX, proj.getEndTime(), 130));
					delayHit(e, Utils.projectileTimeToCycles(proj.getEndTime()), getHit(player, e, true, Combat.MAGIC_TYPE, 1.0, 1.0, false, true));
				}
				break;
			case 5:// COMBUST
				projectile = World.sendProjectileNew(player, target, projectileId, 22, 39, 15, 5, 0, 10);
				if (Combat.getHitChance(player, target, player.getCombatDefinitions().getStyle(false), true) < Utils.random(100)) {
					delayHit(target, 0, new Hit(player, 0, HitLook.MISSED));
					break;
				}
				final int minumumDamage = getMaxHit(player, target, true, Combat.MAGIC_TYPE, 1.0, true), maximumDamage = getMaxHit(player, target, true, Combat.MAGIC_TYPE, 1.0 + Utils.random(.88), true), damage = Utils.random(minumumDamage, maximumDamage) / 5;
				target.setNextGraphics(new Graphics(targetGFX, 0, 130));
				WorldTasksManager.schedule(new WorldTask() {

					@Override
					public void run() {
						target.getEffectsManager().startEffect(new Effect(EffectType.COMBUST, 10, HitLook.MAGIC_DAMAGE, new Graphics(targetGFX), damage, 2, player, new WorldTile(target)));
					}
				});
				break;
			case 12:// OMNIPOWER
				projectile = World.sendProjectileNew(player, target, projectileId, 32, 40, 80, 5, 0, 10);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()), getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 2.0 + Utils.random(2.0), false, true));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 130));
				break;
			case 9:// WILD MAGIC
				projectile = World.sendProjectileNew(player, target, projectileId, 32, 42, 40, 5, 0, 10);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 0.5 + Utils.random(1.75), false, true));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 130));
				projectile = World.sendProjectileNew(player, target, projectileId, 32, 39, 45, 3, 0, 10);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()), getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 0.5 + Utils.random(1.75), false, true));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 130));
				break;
			case 7:// ASPHYXIATE
				player.getEffectsManager().startEffect(new Effect(EffectType.ASPHYXIATE, 10, projectileId, targetGFX));
				break;
			case 11:// TSUNAMI
				angle = (int) Math.round(Math.toDegrees(Math.atan2((player.getX() * 2 + player.getSize()) - (target.getX() * 2 + target.getSize()), (player.getY() * 2 + player.getSize()) - (target.getY() * 2 + target.getSize()))) / 45d) & 0x7;
				World.sendGraphics(player, new Graphics(3560, 0, 0, (angle + 4) & 0x7, true), new WorldTile(player));
				projectile = World.sendProjectileNew(player, target, projectileId, 22, 40, 15, 5, 0, 10);
				for (Entity t : getMultiAttackTargets(player, target, 5, 12, true)) {
					int nextAngle = (int) Math.round(Math.toDegrees(Math.atan2((player.getX() * 2 + player.getSize()) - (t.getX() * 2 + t.getSize()), (player.getY() * 2 + player.getSize()) - (t.getY() * 2 + t.getSize()))) / 45d) & 0x7;
					if (nextAngle != angle)
						continue;
					delayHit(t, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, t, true, Combat.MAGIC_TYPE, 1.0, 2.0 + Utils.random(1.0), false, true));
				}
				player.getEffectsManager().startEffect(new Effect(EffectType.ADRENALINE_GAIN_FOR_CRIT, 50, 10));
				break;
			case 164:// SUNSHINE
				player.getEffectsManager().startEffect(new Effect(EffectType.SUNSHINE, 50, new WorldTile(player)));
				return -1;

			}
			break;
		case ActionBar.RANGED_ABILITY_SHORTCUT:
			if (projectileId == 0) {
				projectileId = ClientScriptMap.getMap(6722).getIntValue(ItemDefinitions.getItemDefinitions(currentWeapon).getCSOpcode(21));
				if (projectileId == 0)
					projectileId = ClientScriptMap.getMap(6722).getDefaultIntValue();
			}
			int startTime = twoHWep ? 44 : 32;
			boolean usingChin = def.id == 10033 || def.id == 10034;
			switch (abilityId) {
			case 1:// PIERCING SHOT
				Projectile projectile = World.sendProjectileNew(player, target, projectileId, 41, 38, startTime, 5, 0, 95);
				Entity[] targets = usingChin ? getMultiAttackTargets(player) : new Entity[] { target };
				for (Entity t : targets)
					delayHit(t, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, t, true, Combat.RANGE_TYPE, 1.0, t.getEffectsManager().hasActiveEffect(EffectType.STUNNED) ? 1.25 : 0.94, false, true));
				break;
			case 2:// BINDING SHOT
			case 18:// TIGHT_BINDINGS
				targets = usingChin ? getMultiAttackTargets(player) : new Entity[] { target };
				for (Entity t : targets) {
					projectile = World.sendProjectileNew(t == target ? player : target, t, projectileId, 12, 39, 29, 5, 0, 5);
					delayHit(t, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, t, true, Combat.RANGE_TYPE, 1.0, abilityId == 18 ? 2.0 : t.isStunImmune() ? 1.88D : 1.0D, false, true));
					t.getEffectsManager().startEffect(new Effect(EffectType.BINDING_SHOT, t instanceof Player ? 16 : 33));
					t.setStunDelay(5);// Three seconds
					t.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime() / 2, 92));
				}
				break;
			case 6:// RICOCHET
				projectile = World.sendProjectileNew(player, target, projectileId, 41, 42, startTime, 5, 0, 10);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.RANGE_TYPE, 1.0D, 1.0D, false, true));
				for (Entity e : getMultiAttackTargets(player, target, 9, 3)) {
					if (e == target)
						continue;
					Projectile proj = World.sendProjectileNew(target, e, projectileId, 41, 42, projectile.getEndTime() + 15, 5, 0, 10);
					e.setNextGraphics(new Graphics(targetGFX, proj.getEndTime(), 130));
					delayHit(e, Utils.projectileTimeToCycles(proj.getEndTime()) - 1, getHit(player, e, true, Combat.RANGE_TYPE, 1.0D, 1.0D, false, true));
				}
				break;
			case 15:// DAZING SHOT
				projectile = World.sendProjectileNew(player, target, projectileId, 38, 40, 44, 5, 0, 130);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.RANGE_TYPE, 1.0D, 1.57D, false, true));
				target.getEffectsManager().startEffect(new Effect(EffectType.MODIFY_ACCURACY, 15, -0.10));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 130));
				break;
			case 4:// SNIPE
				player.getEffectsManager().startEffect(new Effect(EffectType.SNIPE, 5, projectileId, targetGFX, usingChin));
				break;
			case 5:// FRAGMENTATION SHOT
				targets = usingChin ? getMultiAttackTargets(player) : new Entity[] { target };
				projectile = World.sendProjectileNew(player, target, projectileId, 41, 40, startTime, 5, 0, 95);
				if (Combat.getHitChance(player, target, player.getCombatDefinitions().getStyle(false), true) < Utils.random(100)) {
					delayHit(target, 0, new Hit(player, 0, HitLook.MISSED));
					break;
				}
				for (Entity t : targets) {
					final int minumumDamage = getMaxHit(player, t, true, Combat.RANGE_TYPE, 1.0D, true), maximumDamage = getMaxHit(player, t, true, Combat.RANGE_TYPE, 1.0D + Utils.random(.88D), true), damage = Utils.random(minumumDamage, maximumDamage) / 5;
					WorldTasksManager.schedule(new WorldTask() {

						@Override
						public void run() {
							t.getEffectsManager().startEffect(new Effect(EffectType.FRAGMENTATION, 10, HitLook.RANGE_DAMAGE, new Graphics(3574), damage, 2, player, new WorldTile(target)));
						}
					});
				}
				break;
			case 16:// NEEDLE_STRIKE
				targets = usingChin ? getMultiAttackTargets(player) : new Entity[] { target };
				for (Entity t : targets) {
					projectile = World.sendProjectileNew(player, t, projectileId, 41, 40, 35, 5, 0, 78);
					delayHit(t, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, t, true, Combat.RANGE_TYPE, 1.0D, 1.57D, false, true));
					player.getEffectsManager().startEffect(new Effect(EffectType.INCREASE_CRIT_CHANCE, 3, 10));
					t.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 130));
				}
				break;
			case 7:// SNAPSHOT
				projectile = World.sendProjectileNew(player, target, projectileId, 32, 42, startTime, 5, 0, 5);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.0 + Utils.random(0.20), false, true));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 130));
				projectile = World.sendProjectileNew(player, target, 65535, 32, 39, startTime + 5, 3, 0, 5);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()), getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.0 + Utils.random(1.10), false, true));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 130));
				break;
			case 8:// RAPID FIRE
				player.getEffectsManager().startEffect(new Effect(EffectType.RAPID_FIRE, 10, emoteId, projectileId, startTime, usingChin));
				break;
			case 9:// BOMBARDMENT
				projectile = World.sendProjectileNew(player, target, 65535, 41, 40, 60, 2, 0, 5);
				for (Entity e : getMultiAttackTargets(player, target, 1, 9))
					delayHit(e, Utils.projectileTimeToCycles(projectile.getEndTime()) + 1, getHit(player, e, true, Combat.RANGE_TYPE, 1.0, 2.19, false, true));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 130));
				break;
			case 10:// INCENRARY SHOT
				projectile = World.sendProjectileNew(player, target, projectileId, 38, 39, 44, 5, 0, 130);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) + 4, getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 2.5 + Utils.random(1.0), false, true));
				target.setNextGraphics(new Graphics(targetGFX, projectile.getEndTime(), 0, 0, true));
				target.setNextGraphics(new Graphics(targetGFX + 1, 200, 0, 0, true));
				target.getEffectsManager().startEffect(new Effect(EffectType.INCENDIARY_SHOT, 5));
				player.getEffectsManager().startEffect(new Effect(EffectType.ADRENALINE_GAIN_FOR_CRIT, 50, 10));
				break;
			case 11:// UNLOAD
				player.getEffectsManager().startEffect(new Effect(EffectType.UNLOAD, 10, projectileId, 0.0, usingChin));
				break;
			case 12:// DEADSHOT
				player.setNextGraphics(new Graphics(3519));
				targets = usingChin ? getMultiAttackTargets(player) : new Entity[] { target };
				for (Entity t : targets) {
					projectile = World.sendProjectileNew(t == target ? player : target, t, projectileId, 41, 39, 70, 5, 0, 5);
					Hit hit = getHit(player, t, true, Combat.RANGE_TYPE, 1.0, 1.88, false, true);
					delayHit(t, Utils.projectileTimeToCycles(projectile.getEndTime()) + 1, hit);
					if (hit.getDamage() > 0) {
						int minDamage = getMaxHit(player, t, true, Combat.RANGE_TYPE, 1.0, true);
						int maxDamage = getMaxHit(player, t, true, Combat.RANGE_TYPE, 3.13, true);
						final int constantDmg = Utils.random(minDamage, maxDamage) / 5;
						WorldTasksManager.schedule(new WorldTask() {

							@Override
							public void run() {
								t.getEffectsManager().startEffect(new Effect(EffectType.DEADSHOT, 10, HitLook.RANGE_DAMAGE, new Graphics(3527), constantDmg, 2, player));
							}
						}, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1);
					}
				}
				break;
			case 14:// DEATH'S SWIFTNESS
				player.getEffectsManager().startEffect(new Effect(EffectType.DEATHS_SWIFTNESS, 50, new WorldTile(player)));
				return -1;
			}
			break;
		case ActionBar.MELEE_ABILITY_SHORTCUT:
			switch (abilityId) {
			case 1:// SLICE
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.10, false, true));
				break;
			case 6:// BACKHAND
			case 16:// FORCEFUL BACKAHND
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, abilityId == 16 ? 2.0 : 1.0, false, true));
				target.setBoundDelay(5);
				target.setNextGraphics(new Graphics(3488, 0, 92));
				break;
			case 4:// HAVOC
			case 5:// SMASH
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.25, false, true));
				target.getEffectsManager().startEffect(new Effect(EffectType.PROTECTION_DISABLED, 8));// 5
				// Seconds
				break;
			case 3:// SEVER
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.88, false, true));
				target.getEffectsManager().startEffect(new Effect(EffectType.MODIFY_DAMAGE, 8, -0.10));
				target.getEffectsManager().startEffect(new Effect(EffectType.SEVER, 8));
				break;
			case 7:// SLAUGHTER
				if (Combat.getHitChance(player, target, player.getCombatDefinitions().getStyle(false), true) < Utils.random(100)) {
					delayHit(target, 0, new Hit(player, 0, HitLook.MISSED));
					break;
				}
				WorldTasksManager.schedule(new WorldTask() {

					@Override
					public void run() {
						if (player.isStunned())// One of the logistics to avoid
							// it is to insta stun.
							return;
						int minumumDamage = getMaxHit(player, target, true, Combat.MELEE_TYPE, 1.0, true), maximumDamage = getMaxHit(player, target, true, Combat.MELEE_TYPE, 2.50, true), damage = Utils.random(minumumDamage, maximumDamage) / 5;
						target.getEffectsManager().startEffect(new Effect(EffectType.SLAUGHTER, 10, HitLook.MELEE_DAMAGE, new Graphics(3464), damage, 2, player, new WorldTile(target)));
					}
				});
				break;
			case 8:// FLURRY
				player.getEffectsManager().startEffect(new Effect(EffectType.FLURRY, 10, true));
				break;
			case 10:// OVERPOWER
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 2.0 + Utils.random(2.0), false, true));
				break;
			case 12:// METEOR STRIKE
				int angle = (int) Math.round(Math.toDegrees(Math.atan2((player.getX() * 2 + player.getSize()) - (target.getX() * 2 + target.getSize()), (player.getY() * 2 + player.getSize()) - (target.getY() * 2 + target.getSize()))) / 45d) & 0x7;
				World.sendGraphics(player, new Graphics(3583, 0, 0, angle, true), new WorldTile(player));
				player.getEffectsManager().startEffect(new Effect(EffectType.ADRENALINE_GAIN_FOR_CRIT, 50, 10));
				delayHit(target, 2, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 2.5 + Utils.random(1.0), false, true));
				break;
			case 9:// HURRICANE
				for (Entity t : getMultiAttackTargets(player, target, 1, 9, true))
					delayHit(t, 0, getHit(player, t, true, Combat.MELEE_TYPE, 1.0, 2.19, false, true));
				break;
			case 11:// MASSACRE
				WorldTasksManager.schedule(new WorldTask() {

					@Override
					public void run() {
						int minumumDamage = getMaxHit(player, target, true, Combat.MELEE_TYPE, 1.0, true), maximumDamage = getMaxHit(player, target, true, Combat.MELEE_TYPE, 3.13, true), damage = Utils.random(minumumDamage, maximumDamage) / 5;
						target.getEffectsManager().startEffect(new Effect(EffectType.MASSACRE, 10, HitLook.MELEE_DAMAGE, null, damage, 2, player));
					}
				});
				delayHit(target, 1, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.88, false, true));
				break;
			case 14:// BALANCED_STRIKE
				double playerPercent = player.getHitpoints() / (double) player.getMaxHitpoints(), targetPercent = target.getHitpoints() / (double) target.getMaxHitpoints();
				int damage = 0;
				boolean isTargetHealing = playerPercent > targetPercent;
				if (playerPercent != targetPercent) {
					damage = (int) (isTargetHealing ? ((targetPercent / playerPercent) * player.getHitpoints()) : ((playerPercent / targetPercent) * target.getHitpoints()));
					if (damage > 12000)// Damage cap on RS :D
						damage = 12000; // TODO 12000 if blood tendrils only
					// 10000 otherwise.
					if (damage > 0) {
						if (isTargetHealing)
							target.heal(damage);
						else
							player.heal(damage / 2);
					}
				}
				Hit hit = new Hit(player, damage, HitLook.MELEE_DAMAGE);
				hit.setAbilityMark();
				delayHit(isTargetHealing ? player : target, 1, hit);
				break;
			}
			break;
		case ActionBar.STRENGTH_ABILITY_SHORTCUT:
			switch (abilityId) {
			case 2:// KICK
			case 13:// STOMP
			case 200:// BARGE KICK EXCEPTION
				int angle = (int) Math.round(Math.toDegrees(Math.atan2((player.getX() * 2 + player.getSize()) - (target.getX() * 2 + target.getSize()), (player.getY() * 2 + player.getSize()) - (target.getY() * 2 + target.getSize()))) / 45d) & 0x7;
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.0, abilityId == 13 ? 2.0 : 1.0, false, true);
				delayHit(target, 0, hit);
				if (hit.getDamage() > 0 && abilityId != 200) {
					if (!target.isStunImmune() && !(target instanceof NPC && ((NPC) target).isCantFollowUnderCombat()))
						if (!target.addWalkSteps(target.getX() - player.getX() + target.getX(), target.getY() - player.getY() + target.getY(), 1))
							target.setDirection(angle);
					target.setStunDelay(5);// Three seconds
				}
				break;
			case 3:// PUNISH
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, target.isStunned() ? 1.25 : 0.94, false, true));
				break;
			case 5:// DECIMATE
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, target instanceof Player && ((Player) target).getEquipment().hasShield() ? 2.44 : 1.88, false, true));
				break;
			case 1:// DISMEMBER
				WorldTasksManager.schedule(new WorldTask() {

					@Override
					public void run() {
						int minumumDamage = getMaxHit(player, target, true, Combat.MELEE_TYPE, 1.0, true), maximumDamage = getMaxHit(player, target, true, Combat.MELEE_TYPE, 1.88, true), damage = Utils.random(minumumDamage, maximumDamage) / 5;
						target.getEffectsManager().startEffect(new Effect(EffectType.DISMEMBER, 10, HitLook.MELEE_DAMAGE, new Graphics(3465, 0, 94), damage, 2, player));
					}
				});
				break;
			case 4:// FURY
				player.getEffectsManager().startEffect(new Effect(EffectType.FURY, 8, 0.0));
				break;
			case 6:// CLEAVE
				angle = (int) Math.round(Math.toDegrees(Math.atan2((player.getX() * 2 + player.getSize()) - (target.getX() * 2 + target.getSize()), (player.getY() * 2 + player.getSize()) - (target.getY() * 2 + target.getSize()))) / 45d) & 0x7;
				for (Entity t : getMultiAttackTargets(player, target, 1, 9, true)) {
					int nextAngle = (int) Math.round(Math.toDegrees(Math.atan2((player.getX() * 2 + player.getSize()) - (t.getX() * 2 + t.getSize()), (player.getY() * 2 + player.getSize()) - (t.getY() * 2 + t.getSize()))) / 45d) & 0x7;
					if (nextAngle != angle)
						continue;
					delayHit(t, 0, getHit(player, t, true, Combat.MELEE_TYPE, 1.0, 1.88, false, true));
				}
				break;
			case 7:// ASSAULT
				player.getEffectsManager().startEffect(new Effect(EffectType.ASSAULT, 10, 0.0));
				break;
			case 8:// QUAKE
				for (Entity t : getMultiAttackTargets(player, target, 1, 9, true)) {
					hit = getHit(player, t, true, Combat.MELEE_TYPE, 1.0, 1.88, false, true);
					// TODO camera shake
					if (t instanceof Player)
						((Player) t).getSkills().drainLevel(Skills.DEFENCE, hit.getDamage() / 200);
					delayHit(t, 2, hit);
				}
				break;
			case 9:// DESTROY
				player.getEffectsManager().startEffect(new Effect(EffectType.DESTROY, 10, attackAnim, startGfx));
				target.setBoundDelay(10);
				break;
			case 12:// PULVERISE
				target.getEffectsManager().startEffect(new Effect(EffectType.PULVERISE, 50));
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 2.5 + Utils.random(1.0), false, true));
				if (target instanceof NPC) {
					WorldTasksManager.schedule(new WorldTask() {

						@Override
						public void run() {
							if (target.isDead())
								addAdrenaline(player, 50);
						}
					}, 1);
				}
				break;
			case 11:// FRENZY
				player.getEffectsManager().startEffect(new Effect(EffectType.FRENZY, 10, 0.0));
				break;
			}
			break;
		case ActionBar.DEFENCE_ABILITY_SHORTCUT:
			switch (abilityId) {
			case 3:// PROVOKE
				player.setNextAnimation(new Animation(18130));
				player.getEffectsManager().startEffect(new Effect(EffectType.PROVOKE, 17, target));
				target.getEffectsManager().startEffect(new Effect(EffectType.PROVOKE, 17, player));
				if (target instanceof NPC)
					((NPC) target).setTarget(player);
				return -1;// Leave combat
			case 6:// BASH
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.0, false, true);
				if (hit.getDamage() > 0) {
					int damage = hit.getDamage() + player.getEquipment().getItem(Equipment.SLOT_SHIELD).getDefinitions().getArmor();
					hit.setDamage(Utils.random(hit.getDamage(), damage));// Best
					// way
					// I
					// could
					// think
					// of
					// doing
					// it.
				}
				delayHit(target, 0, hit);
				break;
			case 8:// DEBILITATE
				hit = getHit(player, target, true, player.getCombatDefinitions().getType(Equipment.SLOT_WEAPON), 1.0, 1.0, false, true);
				if (hit.getDamage() > 0) {
					int tier = player.getEquipment().hasShield() ? player.getEquipment().getItem(Equipment.SLOT_SHIELD).getDefinitions().getCSOpcode(750) : 0;
					target.getEffectsManager().startEffect(new Effect(EffectType.DEBILITATE, tier <= 10 ? 8 : tier <= 30 ? 12 : tier <= 69 ? 15 : tier <= 99 ? 16 : 8));
				}
				delayHit(target, 0, hit);
				break;
			case 13:// NATURAL INSTINCT
				player.getEffectsManager().startEffect(new Effect(EffectType.NATURAL_INSTINCT, 33, target));
				return -1;
			}
			break;
		case ActionBar.HEAL_ABILITY_SHORTCUT:
			switch (abilityId) {
			case 10:// SACRIFICE
				Hit hit = getHit(player, target, true, player.getCombatDefinitions().getType(Equipment.SLOT_WEAPON), 1.0, 1.0, false, true);
				delayHit(0, hit);
				final int remainingHP = target.getHitpoints(); // hit damage
				// could be >
				// than remaining
				// HP
				WorldTasksManager.schedule(new WorldTask() {

					@Override
					public void run() {
						player.heal((int) (target.isDead() ? remainingHP : hit.getDamage() * 0.25), 0, 0, true);
					}
				}, 1);
				target.setNextGraphics(new Graphics(4502, 0, 130));
				break;
			case 15:// SIPHON
				if (!target.getEffectsManager().hasActiveEffect(EffectType.SIPHON_IMMUNITY)) {
					Player playerTarget = (Player) target;
					int adrenaline = (int) (playerTarget.getCombatDefinitions().getSpecialAttackPercentage() * 0.10);
					if (adrenaline > 0) {
						playerTarget.getCombatDefinitions().desecreaseSpecialAttack(adrenaline);
						player.getCombatDefinitions().increaseSpecialAttack(adrenaline);
					}
				}
				player.getEffectsManager().startEffect(new Effect(EffectType.SIPHON_IMMUNITY, 10));
				break;
			}
			break;
		}

		nextAbility = null;
		setWeaponAbilityDelay(player);
		return 0;
	}

	public int handleSpecialAttack(final Player player) {
		Item weapon = player.getEquipment().getItem(Equipment.SLOT_WEAPON);

		if (weapon == null || !weapon.getDefinitions().hasSpecialAttack()) {
			player.getPackets().sendGameMessage("You can only do that with a weapon that can perform a special attack.");
			player.getCombatDefinitions().desecreaseSpecialAttack(0); // turns
			// spec
			// off if
			// somehow
			// this
			// happened
			return 0;
		}

		int specAmt = weapon.getDefinitions().getSpecialAmmount();
		if (player.getCombatDefinitions().hasRingOfVigour())
			specAmt *= 0.9;// This special attack requires 60% adrenaline before
		// it can be used.

		int combatMode = player.getCombatDefinitions().getCombatMode();

		if (player.getCombatDefinitions().getSpecialAttackPercentage() < specAmt) {
			player.getPackets().sendGameMessage("This special attack requires " + specAmt + "% " + (combatMode == CombatDefinitions.LEGACY_COMBAT_MODE ? "special attack energy" : "adrenaline") + " before it can be used.");
			player.getCombatDefinitions().desecreaseSpecialAttack(0); // turns
			// spec
			// off
			return 0;
		}

		final SpecialAttack spec = SpecialAttack.getSpecialAttack(weapon.getId());

		if (spec == SpecialAttack.DRAGON_CLAWS) { // dragon claw exeption, no
			// other specs have exeption
			Item offhand = player.getEquipment().getItem(Equipment.SLOT_SHIELD);
			if (offhand == null || offhand.getId() != 25555 && offhand.getId() != 25557 && offhand.getId() != 25952) {
				player.getPackets().sendEntityMessage(1, 0xFFFFFF, player, "Off-hand dragon claw must be equipped to use this special attack.");
				player.getCombatDefinitions().desecreaseSpecialAttack(0); // turns
				// spec
				// off
				return 0;
			}
		} else if (spec == SpecialAttack.RUNE_CLAWS) {
			Item offhand = player.getEquipment().getItem(Equipment.SLOT_SHIELD);
			if (offhand == null || offhand.getId() != 25945 && offhand.getId() != 25947) {
				player.getPackets().sendEntityMessage(1, 0xFFFFFF, player, "Off-hand rune claw must be equipped to use this special attack.");
				player.getCombatDefinitions().desecreaseSpecialAttack(0); // turns
				// spec
				// off
				return 0;
			}
		}
		player.getCombatDefinitions().desecreaseSpecialAttack(specAmt);
		// addAdrenaline(player, -specAmt);
		if (spec != null) {
			AnimationDefinitions defs = spec.attackAnim.getDefinitions();
			player.setNextAnimation(spec.attackAnim);
			Integer startGfx = defs.clientScriptData == null ? null : (Integer) defs.clientScriptData.get(2920);
			if (startGfx != null) {
				Integer startGfxHeight = (Integer) defs.clientScriptData.get(4339/* 4090 */);
				player.setNextGraphics(new Graphics(startGfx, 0, startGfxHeight == null ? 0 : startGfxHeight));
			}
			if (spec.effect != null) {
				Integer targetGfx = defs.clientScriptData == null ? null : (Integer) defs.clientScriptData.get(2933);
				Entity[] targets = !spec.multi ? new Entity[] { target } : getMultiAttackTargets(player);
				for (final Entity target : targets) {
					int time = spec.effect.executeAttack(player, target, target == PlayerCombatNew.this.target, spec);
					if (targetGfx != null)
						target.setNextGraphics(new Graphics(targetGfx, time, 93));
				}
			}

		}
		setWeaponDelay(player, true);
		setWeaponDelay(player, false);
		return 0;
	}

	private static enum SpecialAttack {

		DRAGON_DAGGER(new int[] { 1215, 13465 }, 23954, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.15, 1.15, true));
				delayHit(target, 1, getHit(player, target, true, Combat.MELEE_TYPE, 1.15, 1.15, true));
				return 0;
			}

		}, false)

		, DRAGON_SCIMITAR(new int[] { 4587, 13477 }, 23935, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.15, 1.0, true);
				delayHit(target, 0, hit);
				if (target instanceof Player && hit.getDamage() > 0)
					player.getEffectsManager().startEffect(new Effect(EffectType.PROTECTION_DISABLED, 8));
				return 0;
			}

		}, false)

		, DRAGON_LONGSWORD(new int[] { 1305 }, 23929, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, Utils.random(1.0, 2.5), true));
				return 0;
			}

		}, false)

		, DRAGON_MACE(new int[] { 1434 }, 23952, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 0.95, Utils.random(1.0, 3.0), true));
				return 0;
			}

		}, false)

		, GRANITE_MAUL(new int[] { 4153, 13445 }, 1667, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.25, true));
				return 0;
			}

		}, false)

		, DRAGON_CLAWS(new int[] { 14484, 14486, 23695 }, 24010, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit[] hits = new Hit[4];
				for (int i = 0; i < hits.length; i++)
					hits[i] = i == 0 || hits[i - 1].getDamage() == 0 ? getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.0, true) : new Hit(player, hits[i - 1].getDamage() / 2 + (i == 3 ? 1 : 0), HitLook.MELEE_DAMAGE);
				delayHit(target, 0, hits[0], hits[1]);
				delayHit(target, 1, hits[2], hits[3]);
				return 0;
			}
		}, false)

		, DRAGON_HALBERD(new int[] { 3204, 13478 }, 23962, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Entity t = player.getCurrentFaceEntity();
				if (t == null)
					return 0;
				boolean inPath = mainTarget;
				if (!inPath) {
					byte[] dirs = Utils.getDirection(Utils.getAngle(t.getX() - player.getX(), t.getY() - player.getY()));
					for (int distance = -1; distance < 2; distance++) {
						WorldTile tile = new WorldTile(new WorldTile(player.getX() + (dirs[1] * distance) + dirs[0], player.getY() + (dirs[0] * distance) + dirs[1], player.getPlane()));
						if (target.getX() == tile.getX() && target.getY() == tile.getY() && tile.getPlane() == target.getPlane()) {
							inPath = true;
							break;
						}
					}
				}
				if (inPath)
					delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.0, true));
				return 0;
			}
		}, true)

		, DRAGON_PICKAXE(new int[] { 15259 }, 23963, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.0, true);
				delayHit(target, 0, hit);
				if (hit.getDamage() > 0) {
					if (target instanceof Player) {
						Player p2 = (Player) target;
						p2.getSkills().drainLevel(Skills.ATTACK, (int) (p2.getSkills().getLevel(Skills.ATTACK) * .05));
						p2.getSkills().drainLevel(Skills.RANGE, (int) (p2.getSkills().getLevel(Skills.RANGE) * .05));
						p2.getSkills().drainLevel(Skills.MAGIC, (int) (p2.getSkills().getLevel(Skills.MAGIC) * .05));
					}
				}
				return 0;
			}
		}, true)

		, DRAGON_HATCHET(new int[] { 6739, 13470 }, 23965, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.0, true);
				delayHit(target, 0, hit);
				if (hit.getDamage() > 0) {
					if (target instanceof Player) {
						Player p2 = (Player) target;
						p2.getSkills().drainLevel(Skills.ATTACK, (int) (p2.getSkills().getLevel(Skills.ATTACK) * .1));
						p2.getSkills().drainLevel(Skills.DEFENCE, (int) (p2.getSkills().getLevel(Skills.DEFENCE) * .1));
					}
				}
				return 0;
			}
		}, true)

		, DRAGON_2H(new int[] { 7158, 13430 }, 7078, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.0, true));
				return 0;
			}
		}, true)

		, RUNE_CLAWS(new int[] { 3101, 13764 }, 23956, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.15, 1.20, true));
				return 0;
			}
		}, false)

		, BRACKISH_BLADE(new int[] { 20671, 20673 }, 24004, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 2.0, 1.0, true);
				delayHit(target, 0, hit);
				int damage = hit.getDamage();
				if (damage > 0) {
					int boost = damage / 57;
					if (player.getSkills().getLevel(Skills.ATTACK) <= player.getSkills().getLevelForXp(Skills.ATTACK))
						player.getSkills().set(Skills.ATTACK, player.getSkills().getLevel(Skills.ATTACK) + boost);
					if (player.getSkills().getLevel(Skills.STRENGTH) <= player.getSkills().getLevelForXp(Skills.STRENGTH))
						player.getSkills().set(Skills.STRENGTH, player.getSkills().getLevel(Skills.STRENGTH) + boost);
					if (player.getSkills().getLevel(Skills.DEFENCE) <= player.getSkills().getLevelForXp(Skills.DEFENCE))
						player.getSkills().set(Skills.DEFENCE, player.getSkills().getLevel(Skills.DEFENCE) + boost);
				}
				return 0;
			}
		}, false)

		, BONE_DAGGER(new int[] { 8872, 8874, 8876, 8878 }, 24008, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, Utils.random(0.5, 2.0), true));
				return 0;
			}
		}, false)

		, DARKLIGHT(new int[] { 6746, 8281 }, 24005, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.0, true);
				delayHit(target, 0, hit);
				if (hit.getDamage() > 0) {
					if (target instanceof Player) {
						Player p2 = (Player) target;
						p2.getSkills().drainLevel(Skills.STRENGTH, (int) (p2.getSkills().getLevel(Skills.STRENGTH) * .05));
						p2.getSkills().drainLevel(Skills.DEFENCE, (int) (p2.getSkills().getLevel(Skills.DEFENCE) * .05));
					}
				}
				return 0;
			}
		}, false)

		, ANCIENT_MACE(new int[] { 11061, 22406 }, 24003, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.ALL_TYPE, 1.10, 1.0, true);
				if (hit.getDamage() > 0) {
					if (target instanceof Player) {
						Player p2 = (Player) target;
						int targetPrayerPoints = p2.getPrayer().getPrayerpoints();
						int drainedPoints = (int) (hit.getDamage() * .1);
						if (targetPrayerPoints > 0) {
							if (drainedPoints > targetPrayerPoints)
								drainedPoints = targetPrayerPoints;
							p2.getPrayer().drainPrayer(drainedPoints);
							player.getPrayer().restorePrayer(drainedPoints);
						}
					}
				}
				delayHit(target, 0, hit);
				return 0;
			}
		}, false)

		, BARRELCHEST_ANCHOR(new int[] { 10887 }, 24005, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.25, Utils.random(0.75, 1.5), true);
				delayHit(target, 0, hit);
				int damage = hit.getDamage();
				if (damage > 0) {
					if (target instanceof Player) {
						Player p2 = (Player) target;
						int skill = Combat.COMBAT_SKILLS[Utils.random(Combat.COMBAT_SKILLS.length)];
						if (skill == Skills.STRENGTH)// Can't drain strength.
							skill = Skills.ATTACK;
						p2.getSkills().drainLevel(skill, (int) (damage * .1));
					}
				}
				return 0;
			}
		}, false)

		, ABYSSAL_VINE_WHIP(new int[] { 21371, 21372, 21373, 21374, 21375 }, 23930, new SpecialAttackEffect() {

			private final Graphics VINE = new Graphics(478);

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.10, 1.0, true));
				final WorldTile vineTile = new WorldTile(target);
				WorldTasksManager.schedule(new WorldTask() {

					private int cycles;

					@Override
					public void run() {
						cycles++;
						if (cycles == 10) {
							stop();
							return;
						}
						World.sendGraphics(player, VINE, vineTile);
						for (Entity t : getMultiAttackTargets(player, target, 1, 3))
							if (t.getX() == vineTile.getX() && t.getY() == vineTile.getY() && t.getPlane() == vineTile.getPlane())
								delayHit(t, 0, getHit(player, t, true, Combat.MELEE_TYPE, 1.25, 0.33, true));
					}
				}, 0, 3);
				return 0;
			}

		}, false)

		, ABYSSAL_WHIP(new int[] { 4151, 13444, 14661, 15441, 15442, 15443, 15444, 23691 }, 23928, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.10, 1.0, true);
				delayHit(target, 0, hit);
				if (hit.getDamage() > 0 && target instanceof Player) {
					Player p2 = (Player) target;
					player.setRunEnergy(player.getRunEnergy() + p2.getRunEnergy());
					p2.setRunEnergy(0);
				}
				return 0;
			}

		}, false)

		, KORASI(new int[] { 19784 }, 24007, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 1.5, true));
				target.setNextGraphics(new Graphics(2795, 20, 0));
				return 0;
			}

		}, true)

		, VESTA_LONGSWORD(new int[] { 13899, 13901 }, 23998, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.35, Utils.random(1.0, 2.5), true));
				return 0;
			}

		}, false)

		, VESTA_SPEAR(new int[] { 13905, 13907 }, 23997, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Entity t = player.getCurrentFaceEntity();
				if (t == null)
					return 0;
				boolean inPath = mainTarget;

				byte[] dirs = Utils.getDirection(Utils.getAngle(t.getX() - player.getX(), t.getY() - player.getY()));
				for (int distance = -1; distance < 2; distance++) {
					if (distance == 0)
						continue;
					WorldTile tile = new WorldTile(new WorldTile(player.getX() + (dirs[1] * distance) + dirs[0], player.getY() + (dirs[0] * distance), player.getPlane()));
					if (target.getX() == tile.getX() && target.getY() == tile.getY() && tile.getPlane() == target.getPlane()) {
						inPath = true;
						break;
					}
				}
				if (inPath)
					delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.0, true));
				player.getEffectsManager().startEffect(new Effect(EffectType.VESTA_IMMUNITY, 17));
				return 0;
			}

		}, true)

		, STATIUS_WARHAMMER(new int[] { 13902, 13904 }, 24000, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.00, 1.5, true);
				delayHit(target, 0, hit);
				if (hit.getDamage() > 0) {
					if (target instanceof Player) {
						Player p2 = (Player) target;
						p2.getSkills().set(Skills.DEFENCE, (int) (player.getSkills().getLevelForXp(Skills.DEFENCE) * .7));
					}
				}
				return 0;
			}

		}, false)

		, ARMADYL_GODSWORD(new int[] { 11694, 13450, 23679 }, 23932, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.10, 1.5, true));
				return 0;
			}

		}, false)

		, BANDOS_GODSWORD(new int[] { 11696, 13451, 23680, 31526, 31524 }, 23934, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.1, true);
				delayHit(target, 0, hit);
				if (hit.getDamage() > 0 && target instanceof Player)
					((Player) target).getSkills().drainLevel(Combat.COMBAT_SKILLS[Utils.random(Combat.COMBAT_SKILLS.length)], (int) (hit.getDamage() * 0.1));
				return 0;
			}

		}, false)

		, SARADOMIN_GODSWORD(new int[] { 11698, 13452, 23681 }, 23933, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1, true);
				delayHit(target, 0, hit);
				int damage = hit.getDamage();
				if (damage > 0) {
					player.getPrayer().restorePrayer((int) (damage * .25));
					player.heal((int) (damage * .5));
				}
				return 0;
			}

		}, false)

		, ZAMORAK_GODSWORD(new int[] { 11700, 13453, 23682 }, 23912, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.75, true);
				delayHit(target, 0, hit);
				int damage = hit.getDamage();
				if (damage > 0) {
					EffectsManager.addBoundEffect(target, 16, true, 3);
					target.setNextGraphics(new Graphics(2104, 30, 0));
				}
				return 0;
			}

		}, false)

		, SARADOMIN_SWORD(new int[] { 11730, 13461, 23690 }, 23937, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MELEE_TYPE, 1.0, 1.10, true));
				delayHit(target, 0, getHit(player, target, true, Combat.MAGIC_TYPE, 1.25, 1.00, true));
				return 0;
			}

		}, false)

		, KEENBLADE(new int[] { 23042 }, 23913, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				delayHit(target, 0, getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, Utils.random(1.0, 1.5), true));
				return 0;
			}

		}, false)

		, DARK_BOW(new int[] { 11235, 13405, 15701, 15702, 15703, 15704 }, 23941, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				ItemDefinitions ammoDefs = player.getEquipment().getItem(Equipment.SLOT_ARROWS).getDefinitions();
				boolean bestArrows = ammoDefs.getName().contains("Dragon") || ammoDefs.getName().contains("Dark");
				Projectile projectile = World.sendProjectileNew(player, target, bestArrows ? 1099 : 1101, 41, 37, 55, 7, Utils.random(5), 15);
				int hitDelay = Utils.projectileTimeToCycles(projectile.getEndTime());
				World.sendProjectileNew(player, target, bestArrows ? 1099 : 1101, 41, 40, 55, 5, Utils.random(5), 15);
				target.setNextGraphics(new Graphics(bestArrows ? 1100 : 1103, projectile.getEndTime(), projectile.getEndHeight()));
				delayHit(target, hitDelay - 1, getHit(player, target, true, Combat.RANGE_TYPE, 1.00, 1.5, true));
				delayHit(target, hitDelay - 1, getHit(player, target, true, Combat.RANGE_TYPE, 1.00, 1.5, true));
				return projectile.getEndTime();
			}
		}, false)

		, SEERCUL(new int[] { 6724, 13529 }, 23943, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 3490, 41, 37, 55, 5, Utils.random(5), 15);
				Hit hit = getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.0, true);
				int damage = hit.getDamage();
				if (damage > 0 && target instanceof Player)
					((Player) target).getSkills().drainLevel(Skills.MAGIC, damage / 30);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, hit);
				return projectile.getEndTime();
			}

		}, false)

		, MAGIC_SHORTBOW(new int[] { 861, 13528 }, 23967, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 249, 41, 38, 35, 5, Utils.random(5), 15);
				delayHit(target, Utils.projectileTimeToCycles(World.sendProjectileNew(player, target, 249, 41, 37, 15, 5, Utils.random(5), 15).getEndTime()) - 1, getHit(player, target, true, Combat.RANGE_TYPE, 0.85, 1.25, true));
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.RANGE_TYPE, 0.85, 1.25, true));
				return 0;
			}

		}, false)

		, MAGIC_COMPOSITE_BOW(new int[] { 10284, 13543 }, 23942, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 249, 41, 39, 50, 5, Utils.random(5), 15);

				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()), getHit(player, target, true, Combat.RANGE_TYPE, 1.25, 1.25, true));
				return projectile.getEndTime();
			}

		}, false)

		, MORRIGAN_JAVELIN(new int[] { 13879 }, 23919, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.0, true);
				final int dmg = hit.getDamage();
				int hitDelay = Utils.projectileTimeToCycles(World.sendProjectileNew(player, target, 3491, 52, 40, 36, 5, Utils.random(5), 137).getEndTime());
				if (dmg >= 4)
					WorldTasksManager.schedule(new WorldTask() {

						@Override
						public void run() {
							EffectsManager.startBleedEffect(target, 16, 4, (int) (dmg * 0.25));
						}

					}, hitDelay);
				delayHit(target, hitDelay - 1, hit);
				return hitDelay;
			}

		}, false)

		, MORRIGAN_AXE(new int[] { 13883 }, 23999, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 1839, 41, 40, 41, 5, Utils.random(5), 4);
				Hit hit = getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.20, true);
				if (hit.getDamage() > 0 && target instanceof Player)
					((Player) target).getEffectsManager().startEffect(new Effect(EffectType.MORRIGAN_AXE, 100));
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()), hit);
				return projectile.getEndTime();
			}

		}, false)

		, ZANIKS_CROSSBOW(new int[] { 14684, 14686 }, 24012, new SpecialAttackEffect() {

			@Override
			public int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 2001, 41, 40, 80, 5, Utils.random(5), 0);

				boolean godAffinity = target instanceof NexMinion || target instanceof GodWarMinion || target instanceof Nex || target instanceof ZarosMinion || target instanceof GeneralGraardor || target instanceof CommanderZilyana || target instanceof KreeArra || target instanceof KrilTstsaroth || target instanceof GodwarsArmadylFaction || target instanceof GodwarsBandosFaction || target instanceof GodwarsSaradominFaction || target instanceof GodwarsBandosFaction;
				addCombatDelay(player, 7);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.RANGE_TYPE, 1.0, godAffinity ? 1.5 : 1.0, true));
				return projectile.getEndTime();
			}

		}, false)

		, HAND_CANNON(new int[] { 15241 }, 12175, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				WorldTasksManager.schedule(new WorldTask() {

					@Override
					public void run() {
						Projectile projectile = World.sendProjectileNew(player, target, 2143, 24, 40, 0, 5, Utils.random(5), 4);
						addCombatDelay(player, 7);
						player.setNextAnimation(new Animation(24011));
						delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.RANGE_TYPE, 1.45, 1.50, true));
					}
				}, 3);
				return 0;
			}

		}, false)

		, RUNE_THROWING_AXE(new int[] { 805 }, 23958, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				List<Entity> targets = new ArrayList<Entity>(3);
				targets.addAll(Arrays.asList(getMultiAttackTargets(player, target, 3, 3)));
				if (targets.size() == 2)
					targets.add(target);

				int startDelay = 19;
				Entity next = player;
				for (int idx = 0; idx < targets.size(); idx++) {
					Entity t = targets.get(idx);
					if (t.isDead())
						continue;
					Projectile projectile = World.sendProjectileNew(next, t, 258, idx == 0 ? 41 : 25, idx == 0 ? 41 : 25, startDelay, 2, Utils.random(5), idx == 0 ? 10 : 5);
					startDelay += projectile.getEndTime() - (idx * 35);
					delayHit(t, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.0, true));
					next = t;
				}
				return 0;
			}

		}, false)

		, QUICKBOW(new int[] { 23043 }, 23968, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 2682, 19, 40, 15, 5, Utils.random(5), 4);
				World.sendProjectileNew(player, target, 2682, 19, 40, 40, 5, Utils.random(5), 4);
				Hit hit = getHit(player, target, true, Combat.RANGE_TYPE, 1.25, Utils.random(0.5, 0.75), true);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()), hit, hit);
				return projectile.getEndTime();
			}

		}, false)

		, SARADOMIN_BOW(new int[] { 19143, 19145 }, 23943, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 3490, 41, 40, 55, 5, Utils.random(5), 5);
				Hit hit = getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.0, true);
				int damage = hit.getDamage();
				if (damage > 10)
					EffectsManager.startHealEffect(player, damage / 5);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, hit);
				return projectile.getEndTime();
			}

		}, false)

		, ZAMORAK_BOW(new int[] { 19149, 19151 }, 23943, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 3490, 41, 40, 55, 5, Utils.random(5), 5);
				Hit hit = getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.0, true);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, hit);
				delayHit(target, Utils.projectileTimeToCycles(World.sendProjectileNew(player, target, 3490, 41, 40, 35, 5, Utils.random(5), 5).getEndTime()) - 1, getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.0, true));
				return projectile.getEndTime();
			}
		}, false)

		, GUTHIX_BOW(new int[] { 19146, 19148 }, 23943, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 3490, 41, 40, 55, 5, Utils.random(5), 5);
				Hit hit = getHit(player, target, true, Combat.RANGE_TYPE, 1.0, 1.35, true);
				int damage = hit.getDamage();
				if (damage > 10)
					EffectsManager.startHealEffect(player, damage / 10);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, hit);
				return projectile.getEndTime();
			}

		}, false)

		, ARMADYL_BATTLESTAFF(new int[] { 21777, 21863 }, -1, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				GeneralRequirementMap spellData = Magic.getSpellData(player.getCombatDefinitions().getSpellId());
				if (spellData == null)
					return 0;
				final int targetGfx = spellData.getIntValue(2933), spellBook = Magic.getSpellBook(spellData);
				addCombatDelay(player, 10);
				WorldTasksManager.schedule(new WorldTask() {

					int cycle;

					@Override
					public void run() {
						player.setNextAnimation(new Animation(10546));
						cycle++;
						if (target.isDead() || cycle == 5) {
							stop();
							return;
						}
						Projectile projectile = World.sendProjectileNew(player, target, 1019, 22, 22, 60, 4, Utils.random(5), 5);
						Hit hit = getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 0.6, true);
						delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()) - 1, hit);
						if (targetGfx != 0)
							target.setNextGraphics(new Graphics(targetGfx, projectile.getEndTime(), spellBook == 1 ? 96 : 0));

					}
				}, 0, 0);
				return 0;
			}
		}, false)

		, ZURIELS_STAFF(new int[] { 13867, 13869 }, 24001, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 1.35, true);
				if (hit.getDamage() > 0) {
					target.getEffectsManager().startEffect(new Effect(EffectType.ADRENALINE_GAIN_DECREASE, 17));
					CONFUSE_EFFECT.execute(player, target, hit, mainTarget, null);
				}
				delayHit(target, Utils.projectileTimeToCycles(60), hit);
				return 60;
			}
		}, false)

		, SARADOMIN_STAFF(new int[] { 2417 }, 23925, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 1.35, true);
				if (hit.getDamage() > 0 && target instanceof Player) {
					Player p2 = (Player) target;
					p2.getSkills().drainLevel(Skills.MAGIC, (int) (p2.getSkills().getLevel(Skills.MAGIC) * 0.05));
				}
				delayHit(target, Utils.projectileTimeToCycles(60), hit);
				return 60;
			}
		}, false)

		, GUTHIX_STAFF(new int[] { 2416 }, 23923, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 1.35, true);
				if (hit.getDamage() > 0 && target instanceof Player) {
					Player p2 = (Player) target;
					p2.getSkills().drainLevel(Skills.DEFENCE, (int) (p2.getSkills().getLevel(Skills.DEFENCE) * 0.05));
				}
				delayHit(target, Utils.projectileTimeToCycles(60), hit);
				return 60;
			}
		}, false)

		, ZAMORAK_STAFF(new int[] { 2415 }, 23921, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				Hit hit = getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, 1.35, true);
				if (hit.getDamage() > 0 && target instanceof Player) {
					Player p2 = (Player) target;
					p2.getSkills().drainLevel(Skills.MAGIC, (int) (p2.getSkills().getLevel(Skills.MAGIC) * 0.05));
				}
				delayHit(target, Utils.projectileTimeToCycles(60), hit);
				return 60;
			}
		}, false)

		, IBAN_STAFF(new int[] { 1409 }, 23996, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 88, 22, 22, 15, 7, Utils.random(5), 90);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()), getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, Utils.random(1.0, 1.50), true));
				return projectile.getEndTime();
			}
		}, false)

		, PENANCE_TRIDENT(new int[] { 1409 }, 23996, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				player.getEffectsManager().startEffect(new Effect(EffectType.INCREASE_CRIT_CHANCE, 3, 10));// 10%
				// crit
				// chance
				delayHit(target, Utils.projectileTimeToCycles(60), getHit(player, target, true, Combat.MAGIC_TYPE, 1.0, Utils.random(.25, 1.55), true));
				return 60;
			}
		}, false)

		, MINDSPIKE(new int[] { 23044, 23045, 23046, 23047 }, 14223, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				Projectile projectile = World.sendProjectileNew(player, target, 2731, 30, 30, 55, 5, Utils.random(5), 90);
				delayHit(target, Utils.projectileTimeToCycles(projectile.getEndTime()), getHit(player, target, true, Combat.MAGIC_TYPE, 1.25, Utils.random(1.0, 1.40), true));
				return projectile.getEndTime();
			}
		}, false)

		, NOXIOUS_WEAPONS(new int[] { 31725, 31727, 31729, 31731, 31733, 31735, 33330, 33331, 33333, 33334, 33336, 33337, 33396, 33397, 33399, 33400, 33402, 33403, 33462, 33463, 33465, 33466, 33468, 33469 }, 24153, new SpecialAttackEffect() {

			@Override
			public int executeAttack(final Player player, final Entity target, boolean mainTarget, SpecialAttack attack) {
				player.setNextGraphics(new Graphics(5005));
				player.getEffectsManager().startEffect(new Effect(EffectType.MIRRORBACK_SPIDER, 17, new MirrorBackSpider(19487, Utils.getFreeTile(player, 1), -1, true)));
				return 60;
			}
		}, false)

		;

		private int[] weaponIds;
		private Animation attackAnim;
		private SpecialAttackEffect effect;
		private boolean multi;

		private SpecialAttack(int[] weaponIds, int attackAnimId, SpecialAttackEffect effect, boolean multi) {
			this.weaponIds = weaponIds;
			attackAnim = new Animation(attackAnimId);
			this.effect = effect;
			this.multi = multi;
		}

		private static SpecialAttack getSpecialAttack(int id) {
			for (SpecialAttack spec : SpecialAttack.values())
				for (int weaponId : spec.weaponIds)
					if (weaponId == id)
						return spec;
			return null;
		}
	}

	private static interface SpecialAttackEffect {

		int executeAttack(Player player, Entity target, boolean mainTarget, SpecialAttack attack);

	}

	private Hit getHit(Player player, boolean mainHand, int attackType) {
		return getHit(player, target, mainHand, attackType, 1, 1, false);
	}

	private static Hit getHit(Player player, Entity target, boolean mainHand, int attackType, double accuracyMultiplier, double damageMultiplier, boolean spec) {
		return getHit(player, target, mainHand, attackType, accuracyMultiplier, damageMultiplier, spec, false);
	}

	private static Hit getHit(Player player, Entity target, boolean mainHand, int attackType, double accuracyMultiplier, double damageMultiplier, boolean spec, boolean ability) {
		return getHit(player, target, mainHand, attackType, accuracyMultiplier, damageMultiplier, spec, ability, false);
	}

	// if crit. add 25% of max hit
	public static Hit getHit(Player player, Entity target, boolean mainHand, int attackType, double accuracyMultiplier, double damageMultiplier, boolean spec, boolean ability, boolean ignoreDefence) {
		EffectsManager effects = player.getEffectsManager();
		if (effects.hasActiveEffect(EffectType.MODIFY_ACCURACY)) {
			accuracyMultiplier += (double) effects.getEffectForType(EffectType.MODIFY_ACCURACY).getArguments()[0];
			effects.removeEffect(EffectType.MODIFY_ACCURACY);
		}
		if (effects.hasActiveEffect(EffectType.MODIFY_DAMAGE))
			damageMultiplier += (double) effects.getEffectForType(EffectType.MODIFY_DAMAGE).getArguments()[0];

		if (!ignoreDefence) {
			double hitChance = Combat.getHitChance(player, target, player.getCombatDefinitions().getStyle(!mainHand), mainHand) * accuracyMultiplier;
			if (!Settings.HOSTED)
				System.out.println("Hit chance: " + hitChance + " %");
			if (hitChance < Utils.random(100))
				return new Hit(player, 0, attackType == Combat.MELEE_TYPE ? HitLook.MELEE_DAMAGE : attackType == Combat.RANGE_TYPE ? HitLook.RANGE_DAMAGE : attackType == Combat.MAGIC_TYPE ? HitLook.MAGIC_DAMAGE : HitLook.REGULAR_DAMAGE);
		}
		if (attackType == Combat.MAGIC_TYPE && player.getEffectsManager().hasActiveEffect(EffectType.METAMORPHISIS))
			damageMultiplier += 0.75;// DMG increases by 75%
		int maxHit = getMaxHit(player, target, mainHand, attackType, damageMultiplier, ability);
		Hit hit = new Hit(player, (int) (Utils.random(spec || ability ? maxHit * 0.33 : 0, maxHit) + 1), attackType == Combat.MELEE_TYPE ? HitLook.MELEE_DAMAGE : attackType == Combat.RANGE_TYPE ? HitLook.RANGE_DAMAGE : attackType == Combat.MAGIC_TYPE ? HitLook.MAGIC_DAMAGE : HitLook.REGULAR_DAMAGE);
		if (Utils.random(100) <= (effects.hasActiveEffect(EffectType.INCREASE_CRIT_CHANCE) ? (int) effects.getEffectForType(EffectType.INCREASE_CRIT_CHANCE).getArguments()[0] : 5)) { // 5%
			// chance
			// crit
			hit.setCriticalMark();
			hit.setDamage((int) (hit.getDamage() + maxHit * 0.25));
		} else if (ability)
			hit.setAbilityMark();
		if (attackType == Combat.MELEE_TYPE && effects.hasActiveEffect(EffectType.BERSERK))
			hit.setDamage(hit.getDamage() * 2);
		else if (attackType == Combat.MAGIC_TYPE && effects.hasActiveEffect(EffectType.SUNSHINE) && player.withinDistance((WorldTile) effects.getEffectForType(EffectType.SUNSHINE).getArguments()[0], 3))
			hit.setDamage((int) (hit.getDamage() * 1.5));
		else if (attackType == Combat.RANGE_TYPE && effects.hasActiveEffect(EffectType.DEATHS_SWIFTNESS) && player.withinDistance((WorldTile) effects.getEffectForType(EffectType.DEATHS_SWIFTNESS).getArguments()[0], 3))
			hit.setDamage((int) (hit.getDamage() * 1.5));
		if (target.getEffectsManager().hasActiveEffect(EffectType.ANTICIPATION))
			hit.setDamage((int) (hit.getDamage() * 0.90));
		if (effects.hasActiveEffect(EffectType.PROVOKE) && target != (Entity) effects.getEffectForType(EffectType.PROVOKE).getArguments()[0])
			hit.setDamage((int) (hit.getDamage() * 0.5));

		if (!ability && Combat.fullDharokEquipped(player)) {
			double maxhp = player.getMaxHitpoints();
			double hp = maxhp - player.getHitpoints();
			if (hp > 0) {
				// nerfed dharok effect up to 50% boost max for now, and setted
				// bk to what it was
				double multiplier = 1 + ((hp / maxhp));
				hit.setDamage((int) (hit.getDamage() * multiplier));
			}
		}

		// small boost added after all multipliers, including crit it seems
		int weaponType = player.getCombatDefinitions().getType(mainHand ? Equipment.SLOT_WEAPON : Equipment.SLOT_SHIELD);
		int skill = weaponType == Combat.MAGIC_TYPE ? Skills.MAGIC : weaponType == Combat.RANGE_TYPE ? Skills.RANGE : Skills.STRENGTH;
		int boost = (player.getSkills().getLevel(skill) - player.getSkills().getLevelForXp(skill));
		if (boost > 0)
			hit.setDamage(hit.getDamage() + Utils.random(boost * 4, boost * 8));
		return hit;
	}

	public static int getMaxHit(Player player, Entity target, boolean mainHand, int attackType, double damageMultiplier, boolean ability) {
		int maxHit = (int) (getMaxHit(player, mainHand, ability) * damageMultiplier * player.getPrayer().getCombatDamageMultiplier(attackType));

		// 25% nerf
		if (target instanceof Player)
			maxHit *= 0.75;

		if (target instanceof Player) {
			maxHit *= ((Player) target).getPrayer().getEnemyCombatDamageMultiplier(attackType);

			maxHit *= (1d - ((double) ((Player) target).getSkills().getLevelForXp(Skills.DEFENCE)) * 0.001);

		}
		// System.out.println("maxhit: " + maxHit + ", with crit: " + (maxHit *
		// 1.25));
		// if spec dmg 25-100% else 0-100%
		EffectsManager effects = player.getEffectsManager();
		if (!effects.isEmpty()) {
			if (effects.hasActiveEffect(EffectType.SHADOW_EFFECT))
				maxHit *= (double) effects.getEffectForType(EffectType.SHADOW_EFFECT).getArguments()[0];
			if (effects.hasActiveEffect(EffectType.WEAKEN_EFFECT))
				maxHit *= (double) effects.getEffectForType(EffectType.WEAKEN_EFFECT).getArguments()[0];
			if (effects.hasActiveEffect(EffectType.ENFEEBLE_EFFECT))
				maxHit *= (double) effects.getEffectForType(EffectType.ENFEEBLE_EFFECT).getArguments()[0];
			if (effects.hasActiveEffect(EffectType.REVENGE))
				maxHit *= (double) effects.getEffectForType(EffectType.REVENGE).getArguments()[0];
		}
		if (target.getEffectsManager().hasActiveEffect(EffectType.CURSE_EFFECT))
			maxHit *= (double) target.getEffectsManager().getEffectForType(EffectType.CURSE_EFFECT).getArguments()[0];
		if (target.getEffectsManager().hasActiveEffect(EffectType.VULNERABILITY_EFFECT))
			maxHit *= (double) target.getEffectsManager().getEffectForType(EffectType.VULNERABILITY_EFFECT).getArguments()[0];
		return maxHit;
	}

	// TODO: Prayers increase your ability damage by a set percentage, which can
	// be found on the Prayer page. Prayers do not affect the damage of bleed
	// abilities.
	/*
	 * TODO Black mask, Focus sight, Hexcrest, Full slayer helmet and Slayer helmet boost your ability damage by
	 * 12.5% when on a Slayer task. Salve amulet and Salve amulet (e) boosts your ability damage by respectively
	 * 15% and 20% against undead creatures. Ferocious ring boosts ability damage by 4% when on a task in Kuradal's
	 * Dungeon. Balmung boosts ability damage by 45% against Dagannoths. Bane ammunition increases ability damage
	 * by 18.75% against appropriate monsters. TokKul-Zo increases ability damage by 10% against TzHaar creatures.
	 * Keris increases damage by 33% against Kalphites, and occasionally increases it by 200%. Brine sabre and
	 * Brackish blade increase damage by 40% against 'rum'-pumped crabs. Silverlight and Darklight double ability
	 * damage against demons. Using the portal to the King Black Dragon in the Lava Maze increases ability damage
	 * by 10%. Ancient Magicks increase damage by 100% against Muspahs.
	 */

	// legacy seems to be autoattack * 2. (1.75 now since rs nerfed it later)
	// eoc seems to be about 1.25(rs buffed it lol)
	private static int getMaxHit(Player player, boolean mainHand, boolean ability) {

		int combatMode = player.getCombatDefinitions().getCombatMode();
		double dmgMultiplier = combatMode == CombatDefinitions.LEGACY_COMBAT_MODE ? 1.75 : combatMode == CombatDefinitions.MOMENTUM_COMBAT_MODE ? 1.5 : 1.00;

		return (int) ((ability ? player.getCombatDefinitions().getAbilitiesDamage() : player.getCombatDefinitions().getHandDamage(!mainHand)) * dmgMultiplier * (mainHand ? 1 : 0.5) * 0.1); // x
		// 0.1
		// to
		// reduce
		// dmg
		// by
		// 10
		// as
		// method
		// return
		// x10
	}

	private void delayHit(int delay, final Hit... hits) {
		delayHit(target, delay, hits);
	}

	public static void delayHit(final Entity target, int delay, final Hit... hits) {
		addAttackedByDelay(hits[0].getSource(), target); // called separately
		// since some spells
		// dont do dmg
		WorldTasksManager.schedule(new WorldTask() {

			@Override
			public void run() {
				for (Hit hit : hits) {
					Player player = (Player) hit.getSource();
					if (player.hasFinished() || target.isDead() || target.hasFinished())
						return;
					if (target instanceof NPC) {
						NPC n = (NPC) target;
						if (n.isCantInteract()) // if npc stoped being
							// interactable before the hit
							// reached
							return;
					}
					if (hit.isCriticalHit())
						if (player.getEffectsManager().hasActiveEffect(EffectType.ADRENALINE_GAIN_FOR_CRIT))
							player.getCombatDefinitions().increaseSpecialAttack((int) player.getEffectsManager().getEffectForType(EffectType.ADRENALINE_GAIN_FOR_CRIT).getArguments()[0]);
					// hit delay is 0 anyway but ye
					if (hit.getLook() != HitLook.MAGIC_DAMAGE) {
						// Fuck poison I don't understand it u can do it -.-
						/*
						 * Effect pois = player.getEffectsManager().getEffectForType( EffectType.WEAPON_POISON); if
						 * (pois != null && Utils.random(8) == 0) { Effect anti =
						 * target.getEffectsManager().getEffectForType( EffectType.ANTIPOISON); if (anti != null) {
						 * anti.setCycle(anti.getCycle() - 16); target.getEffectsManager().refreshBuffEffect(anti);
						 * } else { EffectsManager.makePoisoned(target, getMaxHit(player, )); } }
						 */
						target.setNextGraphics(new Graphics(hit.getDelay() == 0 ? 3466 : 3465, hit.getDelay(), 100 + hit.getDamage() == 0 ? 2 : 11));
					}
					target.setNextAnimationNoPriority(new Animation(Combat.getDefenceEmote(target), hit.getDelay()));
					if (player.getPrayer().usingPrayer(1, 24))
						target.sendSoulSplit(hit, player);
					if (!(hit.getDamage() == -1))
						target.applyHit(hit); // also reduces damage if needed,
					// autorelatie
					autoRelatie(player, target);
				}
			}
		}, delay);
	}

	public static void autoRelatie(Player player, Entity target) {
		if (target instanceof Player) {
			Player p2 = (Player) target;
			p2.closeInterfaces();
			if (p2.getCombatDefinitions().isAutoRelatie() && !p2.getActionManager().hasSkillWorking() && !p2.hasWalkSteps() && !p2.isLocked() && !p2.getEmotesManager().isDoingEmote())
				p2.getActionManager().setAction(new PlayerCombatNew(player));
		} else {
			NPC n = (NPC) target;
			if (!n.isUnderCombat() || n.canBeAttackedByAutoRelatie())
				n.setTarget(player);
		}
	}

	private static void playSoundEffect(int soundId, Player player, Entity target) {
		if (soundId == -1)
			return;
		player.getPackets().sendSoundEffect(soundId);
		if (target instanceof Player) {
			Player p2 = (Player) target;
			p2.getPackets().sendSoundEffect(soundId);
		}
	}

	private static void addAttackedByDelay(Entity player, Entity target) {
		target.setAttackedBy(player);
		target.setAttackedByDelay(Utils.currentTimeMillis() + 6000); // 8seconds
		player.setAttackingDelay(Utils.currentTimeMillis() + 6000);
	}

	public static void addAttackingDelay(Entity player) {
		player.setAttackingDelay(Utils.currentTimeMillis() + 6000);
	}

	public static void setWeaponAbilityDelay(Player player) {
		setWeaponDelay(player, true);
		setWeaponDelay(player, false);
		// delay furhter when using ability
		player.getCombatDefinitions().setMainHandDelay(player.getCombatDefinitions().getMainHandDelay() + 3);
		player.getCombatDefinitions().setOffHandDelay(player.getCombatDefinitions().getOffHandDelay() + 3);
	}

	public static void setWeaponDelay(Player player, boolean mainHand) {
		long delay = getAttackSpeed(player, mainHand);
		long nextHitMin = 2;
		if (player.getCombatDefinitions().getType(Equipment.SLOT_WEAPON) != player.getCombatDefinitions().getType(Equipment.SLOT_SHIELD) && player.getEquipment().hasOffHand() && !player.getEquipment().hasShield())
			nextHitMin += 1;
		if (player.getEffectsManager().hasActiveEffect(EffectType.ADRENALINE_GAIN_DECREASE)) {// Reduces
			// speed
			// by
			// two
			delay *= 2;
			nextHitMin *= 2;
		}
		long worldCycle = Utils.currentWorldCycle();
		delay += worldCycle;
		nextHitMin += worldCycle;
		if (mainHand) {
			if (player.getCombatDefinitions().getMainHandDelay() < delay)
				player.getCombatDefinitions().setMainHandDelay(delay);
			if (player.getCombatDefinitions().getOffHandDelay() < nextHitMin)
				player.getCombatDefinitions().setOffHandDelay(nextHitMin);
		} else {
			if (player.getCombatDefinitions().getMainHandDelay() < delay)
				player.getCombatDefinitions().setOffHandDelay(delay);
			if (player.getCombatDefinitions().getMainHandDelay() < nextHitMin)
				player.getCombatDefinitions().setMainHandDelay(nextHitMin);
		}

	}

	public static void addCombatDelay(Player player, int delay) {
		int time = (int) (Utils.currentWorldCycle() + delay);
		if (player.getCombatDefinitions().getMainHandDelay() < time)
			player.getCombatDefinitions().setMainHandDelay(time);
		if (player.getCombatDefinitions().getOffHandDelay() < time)
			player.getCombatDefinitions().setOffHandDelay(time);
	}

	public static int getAttackAnimation(Player player, boolean mainHand) {
		Item item = getWeapon(player, mainHand);
		boolean legacy = player.getCombatDefinitions().getCombatMode() == CombatDefinitions.LEGACY_COMBAT_MODE;
		if (item == null)
			return mainHand ? (legacy ? 422 : 18224) : -1;
		return item.getDefinitions()
				.getCombatOpcode(mainHand ? (legacy ? 4385 : 2914) : (legacy ? 4389 : 2831)/* 4369 : 4373 */);
	}

	public static int getAttackSpeed(Player player, boolean mainHand) {
		Item item = getWeapon(player, mainHand);
		if (item == null)
			return 4;
		return item.getDefinitions().getAttackSpeed();
	}

	private static Item getWeapon(Player player, boolean mainHand) {
		return player.getEquipment().getItem(mainHand ? Equipment.SLOT_WEAPON : Equipment.SLOT_SHIELD);
	}

	@Override
	public void stop(Player player) {
		player.setNextFaceEntity(null);
		player.getActionbar().removeBuffs();
	}

	/*
	 * not in cache sadly
	 * 
	 * private Graphics getLoadWeaponGraphic(int weaponId, int arrowId) { //TODO knives //darts if (weaponId ==
	 * 806) return new Graphics(232, 0, 96); if (weaponId == 807) return new Graphics(233, 0, 96); if (weaponId ==
	 * 808) return new Graphics(234, 0, 96); if (weaponId == 3093) return new Graphics(273, 0, 96); if (weaponId ==
	 * 809) return new Graphics(235, 0, 96); if (weaponId == 810) return new Graphics(236, 0, 96); if (weaponId ==
	 * 811) return new Graphics(237, 0, 96); if (weaponId == 11230) return new Graphics(1123, 0, 96); return null;
	 * }
	 */

	public Entity[] getMultiAttackTargets(Player player) {
		return getMultiAttackTargets(player, target, 1, 9, false);
	}

	public static Entity[] getMultiAttackTargets(Player player, Entity target) {
		return getMultiAttackTargets(player, target, 1, 9, false);
	}

	public static Entity[] getMultiAttackTargets(Player player, Entity target, int maxDistance, int maxAmtTargets) {
		return getMultiAttackTargets(player, target, maxDistance, maxAmtTargets, false);
	}

	public static Entity[] getMultiAttackTargets(Player player, Entity target, int maxDistance, int maxAmtTargets, boolean usePlayerLoc) {
		List<Entity> possibleTargets = new ArrayList<Entity>();
		possibleTargets.add(target);
		y: for (int regionId : target.getMapRegionsIds()) {
			Region region = World.getRegion(regionId);
			if (target instanceof Player) {
				List<Integer> playerIndexes = region.getPlayerIndexes();
				if (playerIndexes == null)
					continue;
				for (int playerIndex : playerIndexes) {
					Player p2 = World.getPlayers().get(playerIndex);
					if (p2 == null || p2 == player || p2 == target || p2.isDead() || !p2.hasStarted() || p2.hasFinished() || !p2.isCanPvp() || !p2.withinDistance(usePlayerLoc ? player : target, maxDistance) || !player.getControlerManager().canHit(p2) || !player.clipedProjectile(p2, false) || (p2.getControlerManager().getControler() instanceof Wilderness && !((Wilderness) p2.getControlerManager().getControler()).isMulti()))
						continue;
					possibleTargets.add(p2);
					if (possibleTargets.size() == maxAmtTargets)
						break y;
				}
			} else {
				List<Integer> npcIndexes = region.getNPCsIndexes();
				if (npcIndexes == null)
					continue;
				for (int npcIndex : npcIndexes) {
					NPC n = World.getNPCs().get(npcIndex);
					if (n == null || n == target || n == player.getFamiliar() || n.isDead() || n.hasFinished() || !n.withinDistance(usePlayerLoc ? player : target, maxDistance) || !n.getDefinitions().hasAttackOption() || !player.getControlerManager().canHit(n) || !player.clipedProjectile(n, false))
						continue;
					possibleTargets.add(n);
					if (possibleTargets.size() == maxAmtTargets)
						break y;
				}
			}
		}
		return possibleTargets.toArray(new Entity[possibleTargets.size()]);
	}

	public Entity getTarget() {
		return target;
	}
}
