package net.scapeemulator.game.model.player; import net.scapeemulator.game.GameServer; import net.scapeemulator.game.dispatcher.button.ButtonDispatcher; import net.scapeemulator.game.model.World; import net.scapeemulator.game.model.mob.Animation; import net.scapeemulator.game.model.mob.Mob; import net.scapeemulator.game.model.mob.combat.AttackStyle; import net.scapeemulator.game.model.mob.combat.AttackType; import net.scapeemulator.game.model.mob.combat.CombatHandler; import net.scapeemulator.game.model.mob.combat.DelayedRangeHit; import net.scapeemulator.game.model.mob.combat.Hits.Hit; import net.scapeemulator.game.model.player.Equipment; import net.scapeemulator.game.model.player.EquipmentDefinition; import net.scapeemulator.game.model.player.Item; import net.scapeemulator.game.model.player.Player; import net.scapeemulator.game.model.player.EquipmentDefinition.WeaponClass; import net.scapeemulator.game.model.player.interfaces.CombatTabHandler; import net.scapeemulator.game.model.player.skills.Skill; import net.scapeemulator.game.model.player.skills.magic.AutoCastHandler; import net.scapeemulator.game.model.player.skills.magic.DamageSpell; import net.scapeemulator.game.model.player.skills.magic.EffectMobSpell; import net.scapeemulator.game.model.player.skills.magic.SpellType; import net.scapeemulator.game.model.player.skills.prayer.HeadIcon; import net.scapeemulator.game.model.player.skills.ranged.Arrow; import net.scapeemulator.game.model.player.skills.ranged.Bolt; import net.scapeemulator.game.model.player.skills.ranged.Bow; import net.scapeemulator.game.model.player.skills.ranged.Crossbow; import net.scapeemulator.game.model.player.skills.ranged.Thrown; import net.scapeemulator.game.msg.impl.CreateProjectileMessage; import net.scapeemulator.game.msg.impl.inter.InterfaceTextMessage; import net.scapeemulator.game.msg.impl.inter.InterfaceVisibleMessage; public class PlayerCombatHandler extends CombatHandler<Player> { static { ButtonDispatcher.getInstance().bind(new CombatTabHandler()); } private Item weapon; private EquipmentDefinition weaponDefinition; private WeaponClass weaponClass; public PlayerCombatHandler(Player player) { super(player); } public boolean canAttack(Mob target) { if (!target.alive() || mob.actionsBlocked()) { return false; } { // TODO - if not multicombat Hit hit = mob.getHits().getMostRecentCombatHit(); if (hit != null) { if (hit.getSource() != target) { if (GameServer.getInstance().getTickTimestamp() - hit.getTimestamp() <= 8) { mob.sendMessage("You are already under attack!"); return false; } } } hit = target.getHits().getMostRecentCombatHit(); if (hit != null) { if (hit.getSource() != mob) { if (GameServer.getInstance().getTickTimestamp() - hit.getTimestamp() <= 8) { mob.sendMessage("Someone is already fighting that."); return false; } } } } // TODO end if not multicombat return true; } public boolean attack() { if (combatDelay > 0) { return false; } int damage = 0; boolean shouldHit = shouldHit(); switch (getNextAttackType()) { case MAGIC: if (!nextSpell.getRequirements().hasRequirementsDisplayOne(mob)) { if (autoCast == nextSpell) { // We don't have the requirements, cancel our auto cast and switch back to melee styles weaponChanged(); } return true; } // Removes runes, gives base XP nextSpell.getRequirements().fulfillAll(mob); if (nextSpell.getType() == SpellType.DAMAGE) { if (shouldHit) { damage = 1 + (int) (Math.random() * ((DamageSpell) nextSpell).getMaxHit()); if (target.getHeadIcon() == HeadIcon.MAGIC) { damage *= 0.6; } if (damage > target.getCurrentHitpoints()) { damage = target.getCurrentHitpoints(); } nextSpell.cast(mob, target, damage); addMagicExperience(damage); } else { nextSpell.cast(mob, target, 0); } } else if (nextSpell.getType() == SpellType.EFFECT_MOB) { ((EffectMobSpell) nextSpell).cast(mob, target, shouldHit); } combatDelay = 5; nextSpell = autoCast; return true; case RANGE: // Calculate damage dealt if (shouldHit) { damage = !shouldHit ? 0 : 1 + (int) (Math.random() * getRangeMaxHit()); damage = target.getHeadIcon() == HeadIcon.RANGED ? damage *= 0.6 : damage; damage = damage > target.getCurrentHitpoints() ? target.getCurrentHitpoints() : damage; } // Find ammo; if null set id to -1 for crystal arrow data Item ammo = mob.getEquipment().get(Equipment.AMMO) != null ? mob.getEquipment().get(Equipment.AMMO) : new Item(-1, 0); // Check if using bow Bow bow = Bow.forId(weapon.getId()); if (bow != null) { Arrow arrow = Arrow.forId(ammo.getId()); if (!bow.validAmmo(arrow)) { mob.sendMessage("You do not have any valid arrows for your bow!"); mob.stopAction(); return true; } if (bow == Bow.CRYSTAL_BOW) { mob.playSpotAnimation(Arrow.CRYSTAL.getDrawbackGraphic(false)); CreateProjectileMessage cpm = Arrow.CRYSTAL.createProjectile(mob, target); World.getWorld().createGlobalProjectile(mob.getPosition(), cpm); World.getWorld().getTaskScheduler().schedule(new DelayedRangeHit(mob, target, cpm.getDurationTicks(), damage, 0, 0)); } else { boolean twoArrows = (bow == Bow.DARK_BOW); if (twoArrows && ammo.getAmount() < 2) { mob.sendMessage("You need at least two arrows to use the Dark Bow."); mob.stopAction(); return true; } mob.getEquipment().remove(new Item(ammo.getId(), twoArrows ? 2 : 1)); mob.playSpotAnimation(arrow.getDrawbackGraphic(twoArrows)); CreateProjectileMessage cpm = arrow.createProjectile(mob, target); World.getWorld().createGlobalProjectile(mob.getPosition(), cpm); boolean shouldDrop = (int) (Math.random() * 2) == 0; World.getWorld().getTaskScheduler() .schedule(new DelayedRangeHit(mob, target, cpm.getDurationTicks(), damage, ammo.getId(), shouldDrop ? 1 : 0)); } mob.playAnimation(weaponDefinition.getAnimation(attackStyle, getRawAttackType())); combatDelay = weaponDefinition.getSpeed() - (attackStyle == AttackStyle.RAPID ? 1 : 0); addRangeExperience(damage); return true; } Crossbow crossbow = Crossbow.forId(weapon.getId()); if (crossbow != null) { Bolt bolt = Bolt.forId(ammo.getId()); if (!crossbow.validAmmo(bolt)) { mob.sendMessage("You do not have any valid bolts for your crossbow!"); mob.stopAction(); return true; } CreateProjectileMessage cpm = bolt.createProjectile(mob, target); World.getWorld().createGlobalProjectile(mob.getPosition(), cpm); boolean shouldDrop = (int) (Math.random() * 2) == 0; World.getWorld().getTaskScheduler() .schedule(new DelayedRangeHit(mob, target, cpm.getDurationTicks(), damage, ammo.getId(), shouldDrop ? 1 : 0)); mob.getEquipment().remove(new Item(ammo.getId(), 1)); mob.playAnimation(weaponDefinition.getAnimation(attackStyle, getRawAttackType())); combatDelay = weaponDefinition.getSpeed() - (attackStyle == AttackStyle.RAPID ? 1 : 0); addRangeExperience(damage); return true; } Thrown thrown = Thrown.forId(weapon.getId()); if (thrown != null) { } break; default: mob.playAnimation(weaponDefinition.getAnimation(attackStyle, getRawAttackType())); damage = !shouldHit ? 0 : 1 + (int) (Math.random() * getMeleeMaxHit()); damage = target.getHeadIcon() == HeadIcon.MELEE ? damage *= 0.6 : damage; damage = damage > target.getCurrentHitpoints() ? target.getCurrentHitpoints() : damage; hitTarget(damage); if (damage > 0) { target.playAnimation(target.getCombatHandler().getDefendAnimation()); addMeleeExperience(damage); } combatDelay = weaponDefinition.getSpeed(); break; } return true; } private void addMeleeExperience(int damage) { double xp = damage * 1.33; mob.getSkillSet().addExperience(Skill.HITPOINTS, xp); switch (attackStyle) { case ACCURATE: mob.getSkillSet().addExperience(Skill.ATTACK, damage * 4); return; case AGGRESSIVE: mob.getSkillSet().addExperience(Skill.STRENGTH, damage * 4); return; case DEFENSIVE: mob.getSkillSet().addExperience(Skill.DEFENCE, damage * 4); return; case SHARED: mob.getSkillSet().addExperience(Skill.DEFENCE, xp); mob.getSkillSet().addExperience(Skill.ATTACK, xp); mob.getSkillSet().addExperience(Skill.STRENGTH, xp); return; default: // Should never happen... return; } } private void addRangeExperience(int damage) { double xp = damage * 1.33; mob.getSkillSet().addExperience(Skill.HITPOINTS, xp); switch (attackStyle) { case ACCURATE: case RAPID: mob.getSkillSet().addExperience(Skill.RANGED, damage * 4); return; case DEFENSIVE: mob.getSkillSet().addExperience(Skill.RANGED, damage * 2); mob.getSkillSet().addExperience(Skill.DEFENCE, damage * 2); return; default: // Should never happen... return; } } private void addMagicExperience(int damage) { mob.getSkillSet().addExperience(Skill.HITPOINTS, damage * 1.33); mob.getSkillSet().addExperience(Skill.MAGIC, damage * 2); } @Override public boolean shouldRetaliate() { if (target == null && mob.getSettings().isAutoRetaliating() && noRetaliate < 1) { return true; } return false; } @Override public Animation getDefendAnimation() { return weaponDefinition.getAnimation(attackStyle, getRawAttackType()); } @Override public int getAttackRange() { switch (getNextAttackType()) { case MAGIC: return 8; case RANGE: return weaponDefinition.getRange() + (attackStyle == AttackStyle.DEFENSIVE ? 1 : 0); default: return weaponDefinition.getRange(); } } @Override public int attackRoll() { int level = 0; int equipmentBonus = mob.getCombatBonuses().getAttackBonus(getNextAttackType()); switch (getNextAttackType()) { case MAGIC: level = mob.getSkillSet().getCurrentLevel(Skill.MAGIC); level *= mob.getPrayers().getPrayerBonus(Skill.MAGIC); break; case RANGE: level = mob.getSkillSet().getCurrentLevel(Skill.RANGED); level *= mob.getPrayers().getPrayerBonus(Skill.RANGED); if (attackStyle == AttackStyle.ACCURATE) { level += 3; } break; case SLASH: case CRUSH: case STAB: level = mob.getSkillSet().getCurrentLevel(Skill.ATTACK); level *= mob.getPrayers().getPrayerBonus(Skill.ATTACK); if (attackStyle == AttackStyle.ACCURATE) { level += 3; } else if (attackStyle == AttackStyle.SHARED) { level += 1; } break; } level += 8; level *= 1.0; // TODO void bonus return level * (64 + equipmentBonus); } @Override public int defenceRoll(AttackType other) { int level = mob.getSkillSet().getCurrentLevel(Skill.DEFENCE); level *= mob.getPrayers().getPrayerBonus(Skill.DEFENCE); if (attackStyle == AttackStyle.DEFENSIVE) { level += 3; } else if (attackStyle == AttackStyle.SHARED) { level += 1; } level += 8; if (other == AttackType.MAGIC) { level *= 0.3; level += mob.getSkillSet().getCurrentLevel(Skill.MAGIC) * 0.7; } return level * (64 + mob.getCombatBonuses().getDefenceBonus(other)); } /** * Calculates the players maximum melee hit taking into account active prayers, equipment strength bonus, strength level (including boosts from * potions), special attacks, attack style, and extra bonuses such as a full void melee set. * * @return the players flat maximum hit, not randomized */ private int getMeleeMaxHit() { int strLevel = mob.getSkillSet().getCurrentLevel(Skill.STRENGTH); strLevel *= mob.getPrayers().getPrayerBonus(Skill.STRENGTH); if (attackStyle == AttackStyle.AGGRESSIVE) { strLevel += 3; } else if (attackStyle == AttackStyle.SHARED) { strLevel += 1; } strLevel += 8; strLevel *= 1.0; // TODO other bonus (slayer helm 1.15, void 1.2) int damage = (int) (0.5 + ((strLevel * (64 + mob.getCombatBonuses().getStrengthBonus())) / 640.0)); damage *= 1.0; // TODO special attack bonuses return damage; } /** * Calculates the players maximum range hit taking into account active prayers, equipment range strength bonus, range level (including boosts from * potions), special attacks and bolts, attack style, and extra bonuses such as the full void ranger set. * * @return the players flat maximum hit, not randomized */ private int getRangeMaxHit() { int level = mob.getSkillSet().getCurrentLevel(Skill.RANGED); level *= mob.getPrayers().getPrayerBonus(Skill.RANGED); if (attackStyle == AttackStyle.ACCURATE) { level += 3; } level += 8; level *= 1.0; // TODO other bonus (slayer helm 1.15, void 1.2) int damage = (int) (0.5 + ((level * (64 + mob.getCombatBonuses().getRangeStrengthBonus())) / 640.0)); damage *= 1.0; // TODO special attack bonuses return damage; } /** * Handles a player click onto the attack tab for things such as style switching, auto retaliating, and auto casting. The tabId parameter is cross * checked with players active tab to avoid packet editing. * * @param tabId the attack tab the player clicked on * @param childId the button the player clicked on */ public void attackTabClick(int tabId, int childId) { if (tabId != weaponClass.getTabId()) { return; } if (tabId == WeaponClass.MAGIC_STAFF.getTabId()) { childId -= 1; if (childId == 3) { mob.sendMessage("Defensive autocasting has not been enabled."); return; } else if (childId == 4) { AutoCastHandler.openSpellSelection(mob); return; } else if (childId <= 2) { if (autoCast != null) { mob.setInterfaceText(90, 11, "Spell"); mob.send(new InterfaceVisibleMessage(90, 83, true)); mob.send(new InterfaceVisibleMessage(90, autoCast.getAutoCastConfig(), false)); autoCast = null; } } } else { childId -= 2; } if (childId < 4 && childId >= 0) { switch (tabId) { case 75: case 78: childId = (childId == 0 ? 0 : 4 - childId); break; case 76: case 77: case 79: childId = (childId == 0 ? 0 : 3 - childId); break; } AttackStyle newStyle = weaponClass.getAttackStyle(childId); if (newStyle != null) { attackStyle = newStyle; setRawAttackType(weaponClass.getAttackType(childId)); mob.getSettings().setAttackStyle(childId); } } else { mob.getSettings().toggleAutoRetaliating(); } } /** * Only called at login, initiates the attack tab and attack styles. */ public void init() { weapon = mob.getEquipment().get(Equipment.WEAPON); String name = "Unarmed"; weaponClass = WeaponClass.UNARMED; combatDelay = 5; weaponDefinition = EquipmentDefinition.UNARMED; if (weapon != null) { name = weapon.getDefinition().getName(); weaponDefinition = weapon.getEquipmentDefinition(); weaponClass = weaponDefinition.getWeaponClass(); } int tab = weaponClass.getTabId(); mob.getInterfaceSet().openAttackTab(tab); mob.send(new InterfaceTextMessage(tab, 0, name)); attackStyle = weaponClass.getAttackStyle(mob.getSettings().getAttackStyle()); setRawAttackType(weaponClass.getAttackType(mob.getSettings().getAttackStyle())); mob.getSettings().setAttackStyle(mob.getSettings().getAttackStyle()); } public void restoreTab() { mob.getInterfaceSet().openAttackTab(weaponClass.getTabId()); weapon = mob.getEquipment().get(Equipment.WEAPON); String name = "Unarmed"; if (weapon != null) { name = weapon.getDefinition().getName(); } mob.send(new InterfaceTextMessage(weaponClass.getTabId(), 0, name)); } /** * Called whenever the players weapon changes, reassigns our weapon variables and attack style variables. Searches for style match to avoid * accidental XP gain switching. */ public void weaponChanged() { autoCast = null; nextSpell = null; weapon = mob.getEquipment().get(Equipment.WEAPON); String name = "Unarmed"; weaponClass = WeaponClass.UNARMED; combatDelay = 5; if (weaponClass == WeaponClass.MAGIC_STAFF) { mob.setInterfaceText(90, 11, "Spell"); mob.send(new InterfaceVisibleMessage(90, 83, true)); mob.send(new InterfaceVisibleMessage(90, autoCast.getAutoCastConfig(), false)); } weaponDefinition = EquipmentDefinition.UNARMED; if (weapon != null) { name = weapon.getDefinition().getName(); weaponDefinition = weapon.getEquipmentDefinition(); weaponClass = weaponDefinition.getWeaponClass(); combatDelay = weaponDefinition.getSpeed(); } int tab = weaponClass.getTabId(); mob.getInterfaceSet().openAttackTab(tab); mob.send(new InterfaceTextMessage(tab, 0, name)); if (attackStyle != null) { // Check for an exact match first for (int i = 0; i < 4; i++) { if (weaponClass.getAttackStyle(i) == attackStyle && weaponClass.getAttackType(i) == getRawAttackType()) { attackStyle = weaponClass.getAttackStyle(i); setRawAttackType(weaponClass.getAttackType(i)); mob.getSettings().setAttackStyle(i); return; } } // Check for at least an XP type match for (int i = 0; i < 4; i++) { if (weaponClass.getAttackStyle(i) == attackStyle) { attackStyle = weaponClass.getAttackStyle(i); setRawAttackType(weaponClass.getAttackType(i)); mob.getSettings().setAttackStyle(i); return; } } } // No match, just go for the default style attackStyle = weaponClass.getAttackStyle(0); setRawAttackType(weaponClass.getAttackType(0)); mob.getSettings().setAttackStyle(0); } }