Unified dragonfire protection logic

Fixed KBD's attack range resulting in only certain spots allowing him to melee
This commit is contained in:
Ceikry
2023-04-18 08:44:03 +00:00
committed by Ryan
parent 4ded3b90de
commit cb92ad0577
5 changed files with 673 additions and 660 deletions
@@ -17,205 +17,193 @@ import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics; import core.game.world.update.flag.context.Graphics;
import core.tools.RandomFunction; import core.tools.RandomFunction;
import static core.api.ContentAPIKt.calculateDragonfireMaxHit;
/** /**
* Handles dragonfire combat. * Handles dragonfire combat.
* @author Emperor * @author Emperor
*/ */
public class DragonfireSwingHandler extends CombatSwingHandler { public class DragonfireSwingHandler extends CombatSwingHandler {
/** /**
* If the NPC has to be in melee range. * If the NPC has to be in melee range.
*/ */
private boolean meleeRange; private boolean meleeRange;
/** /**
* The maximum hit. * The maximum hit.
*/ */
private int maximumHit; private int maximumHit;
/** /**
* The attack data. * The attack data.
*/ */
private SwitchAttack attack; private SwitchAttack attack;
/** /**
* IF the dragon attack is firey or icey. * IF the dragon attack is firey or icey.
*/ */
private boolean fire; private boolean fire;
/** /**
* Constructs a new {@code DragonfireSwingHandler} {@code Object}. * Constructs a new {@code DragonfireSwingHandler} {@code Object}.
* @param meleeRange If the NPC has to be in melee range. * @param meleeRange If the NPC has to be in melee range.
* @param maximumHit The maximum hit. * @param maximumHit The maximum hit.
* @param fire if firey. * @param fire if firey.
*/ */
public DragonfireSwingHandler(boolean meleeRange, int maximumHit, SwitchAttack attack, boolean fire) { public DragonfireSwingHandler(boolean meleeRange, int maximumHit, SwitchAttack attack, boolean fire) {
super(CombatStyle.MAGIC); super(CombatStyle.MAGIC);
this.meleeRange = meleeRange; this.meleeRange = meleeRange;
this.maximumHit = maximumHit; this.maximumHit = maximumHit;
this.attack = attack; this.attack = attack;
this.fire = fire; this.fire = fire;
} }
/** /**
* Gets the switch attack instance for a dragonfire attack. * Gets the switch attack instance for a dragonfire attack.
* @param meleeRange If the attack is melee range. * @param meleeRange If the attack is melee range.
* @param maximumHit The maximum hit. * @param maximumHit The maximum hit.
* @param animation The animation. * @param animation The animation.
* @param startGraphic The start graphic. * @param startGraphic The start graphic.
* @param endGraphic The end graphic. * @param endGraphic The end graphic.
* @param projectile The projectile. * @param projectile The projectile.
* @return The switch attack instance. * @return The switch attack instance.
*/ */
public static SwitchAttack get(boolean meleeRange, int maximumHit, Animation animation, Graphics startGraphic, Graphics endGraphic, Projectile projectile) { public static SwitchAttack get(boolean meleeRange, int maximumHit, Animation animation, Graphics startGraphic, Graphics endGraphic, Projectile projectile) {
SwitchAttack attack = new SwitchAttack(null, animation, startGraphic, endGraphic, projectile).setUseHandler(true); SwitchAttack attack = new SwitchAttack(null, animation, startGraphic, endGraphic, projectile).setUseHandler(true);
attack.setHandler(new DragonfireSwingHandler(meleeRange, maximumHit, attack, true)); attack.setHandler(new DragonfireSwingHandler(meleeRange, maximumHit, attack, true));
return attack; return attack;
} }
/** /**
* Gets the switch attack instance for a dragonfire attack. * Gets the switch attack instance for a dragonfire attack.
* @param meleeRange If the attack is melee range. * @param meleeRange If the attack is melee range.
* @param maximumHit The maximum hit. * @param maximumHit The maximum hit.
* @param animation The animation. * @param animation The animation.
* @param startGraphic The start graphic. * @param startGraphic The start graphic.
* @param endGraphic The end graphic. * @param endGraphic The end graphic.
* @param projectile The projectile. * @param projectile The projectile.
* @return The switch attack instance. * @return The switch attack instance.
*/ */
public static SwitchAttack get(boolean meleeRange, int maximumHit, Animation animation, Graphics startGraphic, Graphics endGraphic, Projectile projectile, boolean fire) { public static SwitchAttack get(boolean meleeRange, int maximumHit, Animation animation, Graphics startGraphic, Graphics endGraphic, Projectile projectile, boolean fire) {
SwitchAttack attack = new SwitchAttack(null, animation, startGraphic, endGraphic, projectile).setUseHandler(true); SwitchAttack attack = new SwitchAttack(null, animation, startGraphic, endGraphic, projectile).setUseHandler(true);
attack.setHandler(new DragonfireSwingHandler(meleeRange, maximumHit, attack, fire)); attack.setHandler(new DragonfireSwingHandler(meleeRange, maximumHit, attack, fire));
return attack; return attack;
} }
@Override @Override
public InteractionType canSwing(Entity entity, Entity victim) { public InteractionType canSwing(Entity entity, Entity victim) {
if (meleeRange) { if (meleeRange) {
return CombatStyle.MELEE.getSwingHandler().canSwing(entity, victim); return CombatStyle.MELEE.getSwingHandler().canSwing(entity, victim);
} }
return CombatStyle.MAGIC.getSwingHandler().canSwing(entity, victim); return CombatStyle.MAGIC.getSwingHandler().canSwing(entity, victim);
} }
@Override @Override
public int swing(Entity entity, Entity victim, BattleState state) { public int swing(Entity entity, Entity victim, BattleState state) {
int max = calculateHit(entity, victim, 1.0); int max = calculateHit(entity, victim, 1.0);
int hit = RandomFunction.random(max); int hit = RandomFunction.random(max + 1);
assert state != null; assert state != null;
state.setMaximumHit(max); state.setMaximumHit(max);
state.setStyle(CombatStyle.MAGIC); state.setStyle(CombatStyle.MAGIC);
state.setEstimatedHit(hit); state.setEstimatedHit(hit);
if (meleeRange) { if (meleeRange) {
return 1; return 1;
} }
int ticks = 2 + (int) Math.floor(entity.getLocation().getDistance(victim.getLocation()) * 0.5); int ticks = 2 + (int) Math.floor(entity.getLocation().getDistance(victim.getLocation()) * 0.5);
entity.setAttribute("fireBreath", GameWorld.getTicks() + (ticks + 2)); entity.setAttribute("fireBreath", GameWorld.getTicks() + (ticks + 2));
return ticks; return ticks;
} }
@Override @Override
public void visualize(Entity entity, Entity victim, BattleState state) { public void visualize(Entity entity, Entity victim, BattleState state) {
entity.visualize(attack.getAnimation(), attack.getStartGraphic()); entity.visualize(attack.getAnimation(), attack.getStartGraphic());
if (attack.getProjectile() != null) { if (attack.getProjectile() != null) {
attack.getProjectile().copy(entity, victim, 5).send(); attack.getProjectile().copy(entity, victim, 5).send();
} }
} }
@Override @Override
public void visualizeImpact(Entity entity, Entity victim, BattleState state) { public void visualizeImpact(Entity entity, Entity victim, BattleState state) {
if (entity instanceof NPC && victim instanceof Player) { if (entity instanceof NPC && victim instanceof Player) {
Player p = (Player) victim; Player p = (Player) victim;
p.getPacketDispatch().sendMessage(getDragonfireMessage(victim.getAttribute("fire_resistance", 0), !fire ? "icy breath" : "fiery breath")); Item shield = p.getEquipment().get(EquipmentContainer.SLOT_SHIELD);
Item shield = p.getEquipment().get(EquipmentContainer.SLOT_SHIELD); if (shield != null && (shield.getId() == 11283 || shield.getId() == 11284)) {
if (shield != null && (shield.getId() == 11283 || shield.getId() == 11284)) { if (shield.getId() == 11284) {
if (shield.getId() == 11284) { p.getEquipment().replace(new Item(11283), EquipmentContainer.SLOT_SHIELD);
p.getEquipment().replace(new Item(11283), EquipmentContainer.SLOT_SHIELD); shield = p.getEquipment().get(EquipmentContainer.SLOT_SHIELD);
shield = p.getEquipment().get(EquipmentContainer.SLOT_SHIELD); shield.setCharge(0);
shield.setCharge(0); }
} if (shield.getCharge() < 1000) {
if (shield.getCharge() < 1000) { shield.setCharge(shield.getCharge() + 20);
shield.setCharge(shield.getCharge() + 20); EquipmentContainer.updateBonuses(p);
EquipmentContainer.updateBonuses(p); p.getPacketDispatch().sendMessage("Your dragonfire shield glows more brightly.");
p.getPacketDispatch().sendMessage("Your dragonfire shield glows more brightly.");
p.faceLocation(entity.getCenterLocation()); p.faceLocation(entity.getCenterLocation());
victim.visualize(Animation.create(6695), Graphics.create(1163)); victim.visualize(Animation.create(6695), Graphics.create(1163));
} else { } else {
p.getPacketDispatch().sendMessage("Your dragonfire shield is already fully charged."); p.getPacketDispatch().sendMessage("Your dragonfire shield is already fully charged.");
} }
return; return;
} }
} }
if (!fire && victim.getAttribute("freeze_immunity", -1) < GameWorld.getTicks() && RandomFunction.random(4) == 2) { if (!fire && victim.getAttribute("freeze_immunity", -1) < GameWorld.getTicks() && RandomFunction.random(4) == 2) {
victim.getStateManager().set(EntityState.FROZEN, 16); victim.getStateManager().set(EntityState.FROZEN, 16);
victim.graphics(Graphics.create(502)); victim.graphics(Graphics.create(502));
} }
Graphics graphic = attack != null ? attack.getEndGraphic() : null; Graphics graphic = attack != null ? attack.getEndGraphic() : null;
victim.visualize(victim.getProperties().getDefenceAnimation(), graphic); victim.visualize(victim.getProperties().getDefenceAnimation(), graphic);
} }
@Override @Override
public void impact(Entity entity, Entity victim, BattleState state) { public void impact(Entity entity, Entity victim, BattleState state) {
assert state != null; assert state != null;
int hit = state.getEstimatedHit(); int hit = state.getEstimatedHit();
if (hit > -1) { if (hit > -1) {
victim.getImpactHandler().handleImpact(entity, hit, CombatStyle.MAGIC, state); victim.getImpactHandler().handleImpact(entity, hit, CombatStyle.MAGIC, state);
} }
hit = state.getSecondaryHit(); hit = state.getSecondaryHit();
if (hit > -1) { if (hit > -1) {
victim.getImpactHandler().handleImpact(entity, hit, CombatStyle.MAGIC, state); victim.getImpactHandler().handleImpact(entity, hit, CombatStyle.MAGIC, state);
} }
} }
@Override @Override
public void adjustBattleState(Entity entity, Entity victim, BattleState state) { public void adjustBattleState(Entity entity, Entity victim, BattleState state) {
if (victim.isPlayer() && !fire) { if (victim.isPlayer() && !fire) {
Item item = victim.asPlayer().getEquipment().get(EquipmentContainer.SLOT_SHIELD); Item item = victim.asPlayer().getEquipment().get(EquipmentContainer.SLOT_SHIELD);
if (item != null && (item.getId() == 2890 || item.getId() == 9731) && state.getEstimatedHit() > 10) { if (item != null && (item.getId() == 2890 || item.getId() == 9731) && state.getEstimatedHit() > 10) {
state.setEstimatedHit(RandomFunction.random(10)); state.setEstimatedHit(RandomFunction.random(10));
} }
} }
CombatStyle style = state.getStyle(); CombatStyle style = state.getStyle();
super.adjustBattleState(entity, victim, state); super.adjustBattleState(entity, victim, state);
state.setStyle(style); state.setStyle(style);
} }
@Override @Override
protected int getFormattedHit(Entity entity, Entity victim, BattleState state, int hit) { protected int getFormattedHit(Entity entity, Entity victim, BattleState state, int hit) {
return formatHit(victim, hit); return formatHit(victim, hit);
} }
@Override @Override
public int calculateAccuracy(Entity entity) { public int calculateAccuracy(Entity entity) {
return 4000; return 4000;
} }
@Override @Override
public int calculateHit(Entity entity, Entity victim, double modifier) { public int calculateHit(Entity entity, Entity victim, double modifier) {
int damage = maximumHit; return calculateDragonfireMaxHit(victim, maximumHit, !fire, 0, true);
if (victim instanceof Player) { }
int val = victim.getDragonfireProtection(fire);
if ((val & 0x2) != 0) {
damage *= 0.1;
}
if ((val & 0x4) != 0) {
damage *= 0.1;
}
if ((val & 0x8) != 0) {
damage *= 0.6;
}
}
return damage;
}
@Override @Override
public int calculateDefence(Entity victim, Entity attacker) { public int calculateDefence(Entity victim, Entity attacker) {
return CombatStyle.MAGIC.getSwingHandler().calculateDefence(victim, attacker); return CombatStyle.MAGIC.getSwingHandler().calculateDefence(victim, attacker);
} }
@Override @Override
public double getSetMultiplier(Entity e, int skillId) { public double getSetMultiplier(Entity e, int skillId) {
return 1.0; return 1.0;
} }
} }
@@ -31,19 +31,6 @@ class SkeletalWyvernBehavior : NPCBehavior(*Tasks.SKELETAL_WYVERN.ids) {
private val SHIELDS = intArrayOf(Items.DRAGONFIRE_SHIELD_11283, Items.DRAGONFIRE_SHIELD_11285, Items.ELEMENTAL_SHIELD_2890, Items.MIND_SHIELD_9731) private val SHIELDS = intArrayOf(Items.DRAGONFIRE_SHIELD_11283, Items.DRAGONFIRE_SHIELD_11285, Items.ELEMENTAL_SHIELD_2890, Items.MIND_SHIELD_9731)
override fun beforeAttackFinalized(self: NPC, victim: Entity, state: BattleState) {
if (victim !is Player) return
if (state.style != CombatStyle.MAGIC) return
val shield = getItemFromEquipment(victim, EquipmentSlot.SHIELD)
val hasShieldProtection = shield != null && shield.id in SHIELDS
if (!hasShieldProtection) return
state.estimatedHit = RandomFunction.random(11)
if (victim.location.getDistance(self.location) >= 5 && victim.prayer.get(PrayerType.PROTECT_FROM_MAGIC))
state.estimatedHit = 0
}
override fun getSwingHandlerOverride(self: NPC, original: CombatSwingHandler): CombatSwingHandler { override fun getSwingHandlerOverride(self: NPC, original: CombatSwingHandler): CombatSwingHandler {
val victim = self.properties.combatPulse.getVictim() ?: return original val victim = self.properties.combatPulse.getVictim() ?: return original
if (victim !is Player) return original if (victim !is Player) return original
@@ -25,6 +25,8 @@ import core.plugin.Initializable;
import core.tools.RandomFunction; import core.tools.RandomFunction;
import content.global.handlers.item.equipment.special.DragonfireSwingHandler; import content.global.handlers.item.equipment.special.DragonfireSwingHandler;
import static core.api.ContentAPIKt.calculateDragonfireMaxHit;
/** /**
* Represents the Elvarg npc. * Represents the Elvarg npc.
@@ -34,311 +36,295 @@ import content.global.handlers.item.equipment.special.DragonfireSwingHandler;
@Initializable @Initializable
public final class ElvargNPC extends AbstractNPC { public final class ElvargNPC extends AbstractNPC {
/** /**
* The NPC ids of NPCs using this plugin. * The NPC ids of NPCs using this plugin.
*/ */
private static final int[] ID = { 742 }; private static final int[] ID = { 742 };
/** /**
* Represents the animations to use. * Represents the animations to use.
*/ */
private static final Animation[] ANIMATIONS = new Animation[] { new Animation(6654), new Animation(6655) }; private static final Animation[] ANIMATIONS = new Animation[] { new Animation(6654), new Animation(6655) };
/** /**
* Represents the combat swing handler. * Represents the combat swing handler.
*/ */
private final CombatSwingHandler combatHandler = new ElvargCombatSwingHandler(); private final CombatSwingHandler combatHandler = new ElvargCombatSwingHandler();
/** /**
* Constructs a new {@code ElvargNPC} {@code Object}. * Constructs a new {@code ElvargNPC} {@code Object}.
*/ */
public ElvargNPC() { public ElvargNPC() {
super(0, null); super(0, null);
} }
/** /**
* Constructs a new {@code ElvargNPC} {@code Object}. * Constructs a new {@code ElvargNPC} {@code Object}.
* @param id The NPC id. * @param id The NPC id.
* @param location The location. * @param location The location.
*/ */
private ElvargNPC(int id, Location location) { private ElvargNPC(int id, Location location) {
super(id, location); super(id, location);
} }
@Override @Override
public AbstractNPC construct(int id, Location location, Object... objects) { public AbstractNPC construct(int id, Location location, Object... objects) {
return new ElvargNPC(id, location); return new ElvargNPC(id, location);
} }
@Override @Override
public void commenceDeath(Entity killer) { public void commenceDeath(Entity killer) {
final Direction direction = Direction.getLogicalDirection(getLocation(), killer.getLocation()); final Direction direction = Direction.getLogicalDirection(getLocation(), killer.getLocation());
GameWorld.getPulser().submit(new Pulse(1, this) { GameWorld.getPulser().submit(new Pulse(1, this) {
@Override @Override
public boolean pulse() { public boolean pulse() {
faceLocation(getCenterLocation().transform(direction.getStepX() * 3, direction.getStepY() * 3, 0)); faceLocation(getCenterLocation().transform(direction.getStepX() * 3, direction.getStepY() * 3, 0));
return true; return true;
} }
}); });
setDirection(direction); setDirection(direction);
} }
@Override @Override
public void finalizeDeath(final Entity killer) { public void finalizeDeath(final Entity killer) {
super.finalizeDeath(killer); super.finalizeDeath(killer);
final Scenery object = new Scenery(25202, getLocation(), getRotation()); final Scenery object = new Scenery(25202, getLocation(), getRotation());
SceneryBuilder.add(object); SceneryBuilder.add(object);
killer.faceLocation(object.getCenterLocation()); killer.faceLocation(object.getCenterLocation());
killer.lock(); killer.lock();
GameWorld.getPulser().submit(new Pulse(1) { GameWorld.getPulser().submit(new Pulse(1) {
int counter = 0; int counter = 0;
@Override @Override
public boolean pulse() { public boolean pulse() {
counter++; counter++;
if (counter == 1) { if (counter == 1) {
killer.animate(ANIMATIONS[0]); killer.animate(ANIMATIONS[0]);
} else if (counter == 4) { } else if (counter == 4) {
final Player player = ((Player) killer); final Player player = ((Player) killer);
SceneryBuilder.replace(object, object.transform(25203)); SceneryBuilder.replace(object, object.transform(25203));
if (!player.getInventory().add(DragonSlayer.ELVARG_HEAD)) { if (!player.getInventory().add(DragonSlayer.ELVARG_HEAD)) {
GroundItemManager.create(DragonSlayer.ELVARG_HEAD, player); GroundItemManager.create(DragonSlayer.ELVARG_HEAD, player);
} }
killer.animate(ANIMATIONS[1]); killer.animate(ANIMATIONS[1]);
killer.unlock(); killer.unlock();
} else if (counter == 12) { } else if (counter == 12) {
SceneryBuilder.remove(RegionManager.getObject(object.getLocation())); SceneryBuilder.remove(RegionManager.getObject(object.getLocation()));
return true; return true;
} }
return false; return false;
} }
}); });
} }
@Override @Override
public CombatSwingHandler getSwingHandler(boolean swing) { public CombatSwingHandler getSwingHandler(boolean swing) {
return combatHandler; return combatHandler;
} }
@Override @Override
public int[] getIds() { public int[] getIds() {
return ID; return ID;
} }
/** /**
* Method used to get the rotation. * Method used to get the rotation.
* @return the rotation. * @return the rotation.
*/ */
public int getRotation() { public int getRotation() {
switch (getDirection()) { switch (getDirection()) {
case EAST: case EAST:
return 3; return 3;
case NORTH: case NORTH:
return 2; return 2;
case WEST: case WEST:
return 1; return 1;
case SOUTH: case SOUTH:
return 0; return 0;
default: default:
break; break;
} }
return 0; return 0;
} }
@Override @Override
public boolean isAttackable(Entity entity, CombatStyle style, boolean message) { public boolean isAttackable(Entity entity, CombatStyle style, boolean message) {
if (!(entity instanceof Player)) { if (!(entity instanceof Player)) {
return super.isAttackable(entity, style, message); return super.isAttackable(entity, style, message);
} }
final Player player = (Player) entity; final Player player = (Player) entity;
if (player.getQuestRepository().getQuest("Dragon Slayer").getStage(player) == 40 && (player.getInventory().containsItem(DragonSlayer.ELVARG_HEAD))) { if (player.getQuestRepository().getQuest("Dragon Slayer").getStage(player) == 40 && (player.getInventory().containsItem(DragonSlayer.ELVARG_HEAD))) {
if(message) { if(message) {
player.getPacketDispatch().sendMessage("You have already slain the dragon. Now you just need to return to Oziach for"); player.getPacketDispatch().sendMessage("You have already slain the dragon. Now you just need to return to Oziach for");
player.getPacketDispatch().sendMessage("your reward!"); player.getPacketDispatch().sendMessage("your reward!");
} }
return false; return false;
} }
if (player.getQuestRepository().getQuest("Dragon Slayer").getStage(player) > 40) { if (player.getQuestRepository().getQuest("Dragon Slayer").getStage(player) > 40) {
if(message) { if(message) {
player.getPacketDispatch().sendMessage("You have already slain Elvarg."); player.getPacketDispatch().sendMessage("You have already slain Elvarg.");
} }
return false; return false;
} }
return super.isAttackable(entity, style, message); return super.isAttackable(entity, style, message);
} }
@Override @Override
public int getDragonfireProtection(boolean fire) { public int getDragonfireProtection(boolean fire) {
return 0x2 | 0x4 | 0x8; return 0x2 | 0x4 | 0x8;
} }
/** /**
* Handles the Elvarg combat swing handler. * Handles the Elvarg combat swing handler.
* @author Emperor * @author Emperor
*/ */
static class ElvargCombatSwingHandler extends CombatSwingHandler { static class ElvargCombatSwingHandler extends CombatSwingHandler {
/** /**
* The style. * The style.
*/ */
private CombatStyle style = CombatStyle.RANGE; private CombatStyle style = CombatStyle.RANGE;
/** /**
* The melee attack animation. * The melee attack animation.
*/ */
private static final Animation MELEE_ATTACK = new Animation(80, Priority.HIGH); private static final Animation MELEE_ATTACK = new Animation(80, Priority.HIGH);
/** /**
* The fire type. * The fire type.
*/ */
private final FireType fireType = FireType.FIERY_BREATH; private final FireType fireType = FireType.FIERY_BREATH;
/** /**
* The DragonFire Attack */ * The DragonFire Attack */
private static final DragonfireSwingHandler DRAGONFIRE = new DragonfireSwingHandler(false, 60, null, true); private static final DragonfireSwingHandler DRAGONFIRE = new DragonfireSwingHandler(false, 60, null, true);
/** /**
* Constructs a new {@Code ElvargCombatSwingHandler} {@Code CombatStyle}. * Constructs a new {@Code ElvargCombatSwingHandler} {@Code CombatStyle}.
* The Combat style. * The Combat style.
*/ */
public ElvargCombatSwingHandler() { public ElvargCombatSwingHandler() {
super(CombatStyle.RANGE); super(CombatStyle.RANGE);
} }
@Override @Override
public void adjustBattleState(Entity entity, Entity victim, BattleState state) { public void adjustBattleState(Entity entity, Entity victim, BattleState state) {
if (style == CombatStyle.RANGE) { if (style == CombatStyle.RANGE) {
fireType.getTask().exec(victim, entity); fireType.getTask().exec(victim, entity);
state.setStyle(null); state.setStyle(null);
DRAGONFIRE.adjustBattleState(entity, victim, state); DRAGONFIRE.adjustBattleState(entity, victim, state);
state.setStyle(CombatStyle.RANGE); state.setStyle(CombatStyle.RANGE);
return; return;
} }
style.getSwingHandler().adjustBattleState(entity, victim, state); style.getSwingHandler().adjustBattleState(entity, victim, state);
} }
@Override @Override
public int calculateAccuracy(Entity entity) { public int calculateAccuracy(Entity entity) {
// If in melee combat, calculate attackers accuracy level. // If in melee combat, calculate attackers accuracy level.
if (style == CombatStyle.MELEE) { if (style == CombatStyle.MELEE) {
return style.getSwingHandler().calculateAccuracy(entity); return style.getSwingHandler().calculateAccuracy(entity);
} }
// Else calculate attackers accuracy based on their magic level. // Else calculate attackers accuracy based on their magic level.
return CombatStyle.MAGIC.getSwingHandler().calculateAccuracy(entity); return CombatStyle.MAGIC.getSwingHandler().calculateAccuracy(entity);
} }
@Override @Override
public int calculateDefence(Entity victim, Entity attacker) { public int calculateDefence(Entity victim, Entity attacker) {
// If in melee combat, calculate players defense level. // If in melee combat, calculate players defense level.
if (style == CombatStyle.MELEE) { if (style == CombatStyle.MELEE) {
return style.getSwingHandler().calculateDefence(victim, attacker); return style.getSwingHandler().calculateDefence(victim, attacker);
} }
// Else calculate players defense against attack based on their magic level. // Else calculate players defense against attack based on their magic level.
return CombatStyle.MAGIC.getSwingHandler().calculateDefence(victim, attacker); return CombatStyle.MAGIC.getSwingHandler().calculateDefence(victim, attacker);
} }
@Override @Override
public int calculateHit(Entity entity, Entity victim, double modifier) { public int calculateHit(Entity entity, Entity victim, double modifier) {
if (style == CombatStyle.MELEE) { if (style == CombatStyle.MELEE) {
return style.getSwingHandler().calculateHit(entity, victim, modifier); return style.getSwingHandler().calculateHit(entity, victim, modifier);
} }
int maxDamage = 60; // Max Possible hit without shield protection
int finalDamage = maxDamage;
if (victim instanceof Player) { return calculateDragonfireMaxHit(victim, 60, false, 3, true);
int val = victim.getDragonfireProtection(true); // DragonFireShield Protection }
if ((val & 0x2) != 0) { // 0x2 - Anti Fire potion used? @Override
finalDamage -= 0.22 * maxDamage; // = 20% protection public InteractionType canSwing(Entity entity, Entity victim) {
} if (!isProjectileClipped(entity, victim, false)) {
if ((val & 0x4) != 0) { // 0x4 - Anti Dragon Fire Shield equipped? return InteractionType.NO_INTERACT;
finalDamage -= 0.78 * maxDamage; // Should always equal 13 as that is the max hit possible with the shield equipped, 80% protection }
} if (victim.getCenterLocation().withinDistance(entity.getCenterLocation(), getCombatDistance(entity, victim, 9)) && super.canSwing(entity, victim) != InteractionType.NO_INTERACT) {
if ( (val & 0x2) == 0 && (val & 0x4) == 0 && (val & 0x8) != 0) { // 0x8 - Magic Prayer Protection, should not stack with the others entity.getWalkingQueue().reset();
finalDamage -= 0.6 * maxDamage; // = 36 return InteractionType.STILL_INTERACT;
} }
} return InteractionType.NO_INTERACT;
}
return finalDamage; @Override
} public ArmourSet getArmourSet(Entity e) {
return style.getSwingHandler().getArmourSet(e);
}
@Override @Override
public InteractionType canSwing(Entity entity, Entity victim) { public double getSetMultiplier(Entity e, int skillId) {
if (!isProjectileClipped(entity, victim, false)) { return style.getSwingHandler().getSetMultiplier(e, skillId);
return InteractionType.NO_INTERACT; }
}
if (victim.getCenterLocation().withinDistance(entity.getCenterLocation(), getCombatDistance(entity, victim, 9)) && super.canSwing(entity, victim) != InteractionType.NO_INTERACT) {
entity.getWalkingQueue().reset();
return InteractionType.STILL_INTERACT;
}
return InteractionType.NO_INTERACT;
}
@Override @Override
public ArmourSet getArmourSet(Entity e) { public void impact(Entity entity, Entity victim, BattleState state) {
return style.getSwingHandler().getArmourSet(e); style.getSwingHandler().impact(entity, victim, state);
} }
@Override @Override
public double getSetMultiplier(Entity e, int skillId) { public int swing(Entity entity, Entity victim, BattleState state) {
return style.getSwingHandler().getSetMultiplier(e, skillId); style = CombatStyle.RANGE;
} int hit = 0;
int ticks = 1;
@Override if (victim.getCenterLocation().withinDistance(entity.getCenterLocation(), getCombatDistance(entity, victim, 1)) ) {
public void impact(Entity entity, Entity victim, BattleState state) { if ( RandomFunction.random(10) < 7 ){
style.getSwingHandler().impact(entity, victim, state); style = CombatStyle.MELEE;
} }
} else {
ticks += (int) Math.ceil(entity.getLocation().getDistance(victim.getLocation()) * 0.3);
}
@Override state.setStyle(style);
public int swing(Entity entity, Entity victim, BattleState state) {
style = CombatStyle.RANGE;
int hit = 0;
int ticks = 1;
if (victim.getCenterLocation().withinDistance(entity.getCenterLocation(), getCombatDistance(entity, victim, 1)) ) { if (isAccurateImpact(entity, victim)) {
if ( RandomFunction.random(10) < 7 ){ int max = calculateHit(entity, victim, 1.0);
style = CombatStyle.MELEE; state.setMaximumHit(max);
} hit = RandomFunction.random(max + 1);
} else { }
ticks += (int) Math.ceil(entity.getLocation().getDistance(victim.getLocation()) * 0.3); state.setEstimatedHit(hit);
} return ticks;
}
state.setStyle(style); @Override
public void visualize(Entity entity, Entity victim, BattleState state) {
switch (style) {
case MELEE:
entity.animate(MELEE_ATTACK);
break;
case RANGE:
Projectile.ranged(entity, victim, 450, 20, 36, 50, 15).send();
entity.animate(fireType.getAnimation());
break;
default:
break;
}
}
if (isAccurateImpact(entity, victim)) { @Override
int max = calculateHit(entity, victim, 1.0); public void visualizeImpact(Entity entity, Entity victim, BattleState state) {
state.setMaximumHit(max); if (style != CombatStyle.MELEE) {
hit = RandomFunction.random(max); DRAGONFIRE.visualizeImpact(entity, victim, state);
} } else {
state.setEstimatedHit(hit); style.getSwingHandler().visualizeImpact(entity, victim, state);
return ticks; }
} }
@Override }
public void visualize(Entity entity, Entity victim, BattleState state) {
switch (style) {
case MELEE:
entity.animate(MELEE_ATTACK);
break;
case RANGE:
Projectile.ranged(entity, victim, 450, 20, 36, 50, 15).send();
entity.animate(fireType.getAnimation());
break;
default:
break;
}
}
@Override
public void visualizeImpact(Entity entity, Entity victim, BattleState state) {
if (style != CombatStyle.MELEE) {
DRAGONFIRE.visualizeImpact(entity, victim, state);
} else {
style.getSwingHandler().visualizeImpact(entity, victim, state);
}
}
}
} }
@@ -20,6 +20,8 @@ import core.plugin.Initializable;
import core.plugin.Plugin; import core.plugin.Plugin;
import core.tools.RandomFunction; import core.tools.RandomFunction;
import static core.api.ContentAPIKt.calculateDragonfireMaxHit;
/** /**
* Represents the King Black Dragon NPC. * Represents the King Black Dragon NPC.
* @author Emperor * @author Emperor
@@ -28,236 +30,216 @@ import core.tools.RandomFunction;
@Initializable @Initializable
public final class KingBlackDragonNPC extends AbstractNPC { public final class KingBlackDragonNPC extends AbstractNPC {
/** /**
* The default spawn location. * The default spawn location.
*/ */
private static final Location DEFAULT_SPAWN = Location.create(2273, 4698, 0); private static final Location DEFAULT_SPAWN = Location.create(2273, 4698, 0);
/** /**
* The combat swing handler. * The combat swing handler.
*/ */
private CombatSwingHandler combatHandler = new KBDCombatSwingHandler(); private CombatSwingHandler combatHandler = new KBDCombatSwingHandler();
public KingBlackDragonNPC() { public KingBlackDragonNPC() {
super(-1,null); super(-1,null);
} }
@Override @Override
public void finalizeDeath(Entity killer) { public void finalizeDeath(Entity killer) {
super.finalizeDeath(killer); super.finalizeDeath(killer);
BossKillCounter.addtoKillcount((Player) killer, this.getId()); BossKillCounter.addtoKillcount((Player) killer, this.getId());
} }
/** /**
* Constructs a new {@code KingBlackDragonNPC} {@code Object}. * Constructs a new {@code KingBlackDragonNPC} {@code Object}.
* @param id the id. * @param id the id.
* @param l the l. * @param l the l.
*/ */
public KingBlackDragonNPC(int id, Location l) { public KingBlackDragonNPC(int id, Location l) {
super(id, l); super(id, l);
} }
@Override @Override
public void init() { public void init() {
super.init(); super.init();
configureBossData(); configureBossData();
} }
@Override @Override
public AbstractNPC construct(int id, Location location, Object... objects) { public AbstractNPC construct(int id, Location location, Object... objects) {
return new KingBlackDragonNPC(id, location); return new KingBlackDragonNPC(id, location);
} }
@Override @Override
public int getDragonfireProtection(boolean fire) { public int getDragonfireProtection(boolean fire) {
return 0x2 | 0x4 | 0x8; return 0x2 | 0x4 | 0x8;
} }
@Override @Override
public int[] getIds() { public int[] getIds() {
return new int[] { 50 }; return new int[] { 50 };
} }
@Override @Override
public CombatSwingHandler getSwingHandler(boolean swing) { public CombatSwingHandler getSwingHandler(boolean swing) {
return combatHandler; return combatHandler;
} }
@Override @Override
public Plugin<Object> newInstance(Object arg) throws Throwable { public Plugin<Object> newInstance(Object arg) throws Throwable {
return super.newInstance(arg); return super.newInstance(arg);
} }
/** /**
* Handles the King Black Dragon's combat swings. * Handles the King Black Dragon's combat swings.
* @author Emperor * @author Emperor
*/ */
static class KBDCombatSwingHandler extends CombatSwingHandler { static class KBDCombatSwingHandler extends CombatSwingHandler {
/** /**
* The style. * The style.
*/
private CombatStyle style = CombatStyle.RANGE;
/**
* Dragonfire.
*/
private static final DragonfireSwingHandler DRAGONFIRE = new DragonfireSwingHandler(false, 56, null, true);
/**
* The melee attack animation.
*/
private static final Animation MELEE_ATTACK = new Animation(80, Priority.HIGH);
/**
* The fire type.
*/
private FireType fireType = FireType.FIERY_BREATH;
/**
* Constructs a new {@code KBDCombatSwingHandler} {@Code Object}.
*/ */
public KBDCombatSwingHandler() { private CombatStyle style = CombatStyle.RANGE;
super(CombatStyle.RANGE);
}
@Override /**
public void adjustBattleState(Entity entity, Entity victim, BattleState state) { * Dragonfire.
if (style == CombatStyle.RANGE) { */
fireType.getTask().exec(victim, entity); private static final DragonfireSwingHandler DRAGONFIRE = new DragonfireSwingHandler(false, 56, null, true);
state.setStyle(null);
DRAGONFIRE.adjustBattleState(entity, victim, state);
state.setStyle(CombatStyle.RANGE);
return;
}
style.getSwingHandler().adjustBattleState(entity, victim, state);
}
@Override /**
public int calculateAccuracy(Entity entity) { * The melee attack animation.
if (style == CombatStyle.MELEE) { */
return style.getSwingHandler().calculateAccuracy(entity); private static final Animation MELEE_ATTACK = new Animation(80, Priority.HIGH);
}
return CombatStyle.MAGIC.getSwingHandler().calculateAccuracy(entity);
}
@Override /**
public int calculateDefence(Entity victim, Entity attacker) { * The fire type.
if (style == CombatStyle.MELEE) { */
return style.getSwingHandler().calculateDefence(victim, attacker); private FireType fireType = FireType.FIERY_BREATH;
}
return CombatStyle.MAGIC.getSwingHandler().calculateDefence(victim, attacker);
}
@Override /**
public int calculateHit(Entity entity, Entity victim, double modifier) { * Constructs a new {@code KBDCombatSwingHandler} {@Code Object}.
if (style == CombatStyle.MELEE) { */
return style.getSwingHandler().calculateHit(entity, victim, modifier); public KBDCombatSwingHandler() {
} super(CombatStyle.RANGE);
int max = 56; }
int damage = max;
if (victim instanceof Player) {
int val = victim.getDragonfireProtection(true);
if ((val & 0x2) != 0) {
damage *= 0.5;
}
if ((val & 0x4) != 0) {
damage -= (int) (damage * 0.85);
}
if ((val & 0x8) != 0) {
damage *= 0.6;
}
if (damage < 3) {
damage = 3;
}
}
if (fireType != FireType.FIERY_BREATH) {
damage += 11;
}
return damage;
}
@Override @Override
public InteractionType canSwing(Entity entity, Entity victim) { public void adjustBattleState(Entity entity, Entity victim, BattleState state) {
if (!isProjectileClipped(entity, victim, false)) { if (style == CombatStyle.RANGE) {
return InteractionType.NO_INTERACT; fireType.getTask().exec(victim, entity);
} state.setStyle(null);
if (victim.getCenterLocation().withinDistance(entity.getCenterLocation(), getCombatDistance(entity, victim, 9)) && super.canSwing(entity, victim) == InteractionType.STILL_INTERACT) { DRAGONFIRE.adjustBattleState(entity, victim, state);
entity.getWalkingQueue().reset(); state.setStyle(CombatStyle.RANGE);
return InteractionType.STILL_INTERACT; return;
} }
return InteractionType.NO_INTERACT; style.getSwingHandler().adjustBattleState(entity, victim, state);
} }
@Override @Override
public ArmourSet getArmourSet(Entity e) { public int calculateAccuracy(Entity entity) {
return style.getSwingHandler().getArmourSet(e); if (style == CombatStyle.MELEE) {
} return style.getSwingHandler().calculateAccuracy(entity);
}
return CombatStyle.MAGIC.getSwingHandler().calculateAccuracy(entity);
}
@Override @Override
public double getSetMultiplier(Entity e, int skillId) { public int calculateDefence(Entity victim, Entity attacker) {
return style.getSwingHandler().getSetMultiplier(e, skillId); if (style == CombatStyle.MELEE) {
} return style.getSwingHandler().calculateDefence(victim, attacker);
}
return CombatStyle.MAGIC.getSwingHandler().calculateDefence(victim, attacker);
}
@Override @Override
public void impact(Entity entity, Entity victim, BattleState state) { public int calculateHit(Entity entity, Entity victim, double modifier) {
style.getSwingHandler().impact(entity, victim, state); if (style == CombatStyle.MELEE) {
} return style.getSwingHandler().calculateHit(entity, victim, modifier);
}
return calculateDragonfireMaxHit(victim, 56, false, fireType != FireType.FIERY_BREATH ? 10 : 0, true);
}
@Override @Override
public int swing(Entity entity, Entity victim, BattleState state) { public InteractionType canSwing(Entity entity, Entity victim) {
style = CombatStyle.RANGE; if (!isProjectileClipped(entity, victim, false)) {
int hit = 0; return InteractionType.NO_INTERACT;
int ticks = 1; }
if (victim.getCenterLocation().withinDistance(entity.getCenterLocation(), getCombatDistance(entity, victim, 1)) && RandomFunction.random(10) < 7) { if (victim.getCenterLocation().withinMaxnormDistance(entity.getCenterLocation(), getCombatDistance(entity, victim, 9)) && super.canSwing(entity, victim) == InteractionType.STILL_INTERACT) {
style = CombatStyle.MELEE; entity.getWalkingQueue().reset();
} else { return InteractionType.STILL_INTERACT;
ticks += (int) Math.ceil(entity.getLocation().getDistance(victim.getLocation()) * 0.3); }
} return InteractionType.NO_INTERACT;
fireType = FireType.values()[RandomFunction.random(FireType.values().length)]; }
state.setStyle(style);
if (isAccurateImpact(entity, victim)) {
int max = calculateHit(entity, victim, 1.0);
state.setMaximumHit(max);
hit = RandomFunction.random(max);
}
state.setEstimatedHit(hit);
((NPC) entity).getAggressiveHandler().setPauseTicks(2);
return ticks;
}
@Override @Override
public void visualize(Entity entity, Entity victim, BattleState state) { public ArmourSet getArmourSet(Entity e) {
switch (style) { return style.getSwingHandler().getArmourSet(e);
case MELEE: }
entity.animate(MELEE_ATTACK);
break;
case RANGE:
Projectile.ranged(entity, victim, fireType.getProjectileId(), 20, 36, 50, 15).send();
entity.animate(fireType.getAnimation());
break;
default:
break;
}
}
@Override @Override
public void visualizeImpact(Entity entity, Entity victim, BattleState state) { public double getSetMultiplier(Entity e, int skillId) {
if (style != CombatStyle.MELEE) { return style.getSwingHandler().getSetMultiplier(e, skillId);
DRAGONFIRE.visualizeImpact(entity, victim, state); }
} else {
style.getSwingHandler().visualizeImpact(entity, victim, state);
}
}
/** @Override
* Gets the fire type. public void impact(Entity entity, Entity victim, BattleState state) {
* @return the type. style.getSwingHandler().impact(entity, victim, state);
*/ }
public FireType getFireType() {
return fireType;
}
} @Override
public int swing(Entity entity, Entity victim, BattleState state) {
style = CombatStyle.RANGE;
int hit = 0;
int ticks = 1;
if (victim.getCenterLocation().withinMaxnormDistance(entity.getCenterLocation(), getCombatDistance(entity, victim, 1)) && RandomFunction.random(10) < 7) {
style = CombatStyle.MELEE;
} else {
ticks += (int) Math.ceil(entity.getLocation().getDistance(victim.getLocation()) * 0.3);
}
fireType = FireType.values()[RandomFunction.random(FireType.values().length)];
state.setStyle(style);
if (isAccurateImpact(entity, victim)) {
int max = calculateHit(entity, victim, 1.0);
state.setMaximumHit(max);
hit = RandomFunction.random(max + 1);
}
state.setEstimatedHit(hit);
((NPC) entity).getAggressiveHandler().setPauseTicks(2);
return ticks;
}
@Override
public void visualize(Entity entity, Entity victim, BattleState state) {
switch (style) {
case MELEE:
entity.animate(MELEE_ATTACK);
break;
case RANGE:
Projectile.ranged(entity, victim, fireType.getProjectileId(), 20, 36, 50, 15).send();
entity.animate(fireType.getAnimation());
break;
default:
break;
}
}
@Override
public void visualizeImpact(Entity entity, Entity victim, BattleState state) {
if (style != CombatStyle.MELEE) {
DRAGONFIRE.visualizeImpact(entity, victim, state);
} else {
style.getSwingHandler().visualizeImpact(entity, victim, state);
}
}
/**
* Gets the fire type.
* @return the type.
*/
public FireType getFireType() {
return fireType;
}
}
} }
+70
View File
@@ -20,6 +20,7 @@ import core.game.node.entity.player.link.TeleportManager
import core.game.node.entity.player.link.audio.Audio import core.game.node.entity.player.link.audio.Audio
import core.game.node.entity.player.link.emote.Emotes import core.game.node.entity.player.link.emote.Emotes
import core.game.node.entity.player.link.quest.QuestRepository import core.game.node.entity.player.link.quest.QuestRepository
import core.game.node.entity.player.link.prayer.PrayerType;
import core.game.node.entity.skill.Skills import core.game.node.entity.skill.Skills
import content.data.skill.SkillingTool import content.data.skill.SkillingTool
import content.global.skill.slayer.Tasks import content.global.skill.slayer.Tasks
@@ -2462,4 +2463,73 @@ fun decantContainer (container: core.game.container.Container) : Pair<List<Item>
return Pair<List<Item>,List<Item>>(toRemove, toAdd) return Pair<List<Item>,List<Item>>(toRemove, toAdd)
} }
/**
* Checks whether the user has a valid anti-dragonfire shield equipped.
* @param player the player whose shield we are checking.
* @param wyvern an optional parameter (default false) whether or not we are checking against wyvern fire, which ignores normal anti-dragon-shields, but accepts elemental/mind shields, etc.
* @return whether or not the player is protected by their shield.
* @see <a href=https://runescape.wiki/w/Dragonfire?oldid=2068032>Source</a>
**/
fun hasDragonfireShieldProtection(player: Player, wyvern: Boolean = false): Boolean {
val shield = getItemFromEquipment(player, EquipmentSlot.SHIELD) ?: return false
return when (shield.id) {
Items.ELEMENTAL_SHIELD_2890, Items.MIND_SHIELD_9731 -> wyvern
Items.ANTI_DRAGON_SHIELD_1540 -> !wyvern
Items.DRAGONFIRE_SHIELD_11283, Items.DRAGONFIRE_SHIELD_11284 -> true
else -> false
}
}
/**
* Calculates the expected max hit of dragonfire after checking/applying valid protections.
* @param entity the entity whose protection we are checking.
* @param maxDamage the max damage the dragonfire can do without protection.
* @param wyvern an optional parameter (default false) that defines whether or not this is wyvern fire. Wyvern fire ignores anti-fire potions and regular anti-dragon shield.
* @param unprotectableDamage an optional parameter (default 0) that defines how much damage cannot be protected against. Think of it as a minimum-max-hit.
* @param sendMessage an optional parameter (default false) whether or not to send a message notifying the player of the damage being blocked.
* @return the maximum amount of damage that can be done to the player.
* @see <a href=https://runescape.wiki/w/Dragonfire?oldid=2068032>Source</a>
**/
fun calculateDragonfireMaxHit(entity: Entity, maxDamage: Int, wyvern: Boolean = false, unprotectableDamage: Int = 0, sendMessage: Boolean = false): Int {
val hasShield: Boolean
val hasPotion: Boolean
val hasPrayer: Boolean
if (entity is Player) {
hasShield = hasDragonfireShieldProtection(entity, wyvern)
hasPotion = !wyvern && getAttribute(entity, "fire:immune", 0) >= getWorldTicks()
hasPrayer = entity.prayer.get(PrayerType.PROTECT_FROM_MAGIC)
if (sendMessage) {
var message = "You are horribly burnt by the ${if (wyvern) "icy" else "fiery"} breath."
if (hasShield && hasPotion)
message = "Your potion and shield fully absorb the ${if (wyvern) "icy" else "fiery"} breath."
else if (hasShield)
message = "Your shield absorbs most of the ${if (wyvern) "icy" else "fiery"} breath."
else if (hasPotion)
message = "Your potion absorbs some of the fiery breath."
else if (hasPrayer)
message = "Your prayer absorbs some of the ${if (wyvern) "icy" else "fiery"} breath."
sendMessage(entity, message)
}
} else {
val dragonfireTokens = entity.getDragonfireProtection(!wyvern)
hasShield = dragonfireTokens and 0x2 != 0
hasPotion = dragonfireTokens and 0x4 != 0
hasPrayer = dragonfireTokens and 0x8 != 0
}
var effectiveDamage = maxDamage.toDouble()
if (hasPrayer && !hasShield && !hasPotion)
effectiveDamage -= 0.6 * maxDamage
else {
if (hasShield)
effectiveDamage -= 0.8 * maxDamage
if (hasPotion)
effectiveDamage -= 0.2 * maxDamage
}
return Math.max(unprotectableDamage, effectiveDamage.toInt())
}
private class ContentAPI private class ContentAPI