Added target validity checking for combat interactions and special cases
This commit is contained in:
+9
-6
@@ -13,6 +13,7 @@ import core.game.world.map.RegionManager;
|
||||
import core.game.world.update.flag.context.Graphics;
|
||||
import core.tools.RandomFunction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -54,13 +55,15 @@ public final class ChinchompaSwingHandler extends RangeSwingHandler {
|
||||
entity.getProperties().getCombatPulse().stop();
|
||||
return -1;
|
||||
}
|
||||
@SuppressWarnings("rawtypes")
|
||||
List list = victim instanceof NPC ? RegionManager.getSurroundingNPCs(victim, 14, entity) : RegionManager.getSurroundingPlayers(victim, 14, entity);
|
||||
BattleState[] targets = new BattleState[list.size()];
|
||||
|
||||
// ! Initial capacity of 14, but has dynamic size? i.e. we are not restricting to a max of 14 targets hit here now
|
||||
List<Entity> targetCandidates = new ArrayList<>(14);
|
||||
targetCandidates.addAll(RegionManager.getSurroundingNPCs(victim, 14, entity));
|
||||
targetCandidates.addAll(RegionManager.getSurroundingPlayers(victim, 14, entity));
|
||||
BattleState[] targets = new BattleState[targetCandidates.size()];
|
||||
int count = 0;
|
||||
for (Object o : list) {
|
||||
Entity e = (Entity) o;
|
||||
if (canSwing(entity, e) != InteractionType.NO_INTERACT) {
|
||||
for (Entity e : targetCandidates) {
|
||||
if (canSwing(entity, e) != InteractionType.NO_INTERACT && e.isAttackable(entity, CombatStyle.RANGE, false)) {
|
||||
BattleState s = targets[count++] = new BattleState(entity, e);
|
||||
s.setStyle(CombatStyle.RANGE);
|
||||
int hit = 0;
|
||||
|
||||
+4
-4
@@ -49,7 +49,7 @@ public final class PowerstabSpecialHandler extends MeleeSwingHandler implements
|
||||
|
||||
@Override
|
||||
public void impact(Entity entity, Entity victim, BattleState state) {
|
||||
if (state.getTargets() != null) {
|
||||
if (state != null && state.getTargets() != null) {
|
||||
for (BattleState s : state.getTargets()) {
|
||||
if (s != null) {
|
||||
s.getVictim().getImpactHandler().handleImpact(entity, s.getEstimatedHit(), CombatStyle.MELEE, s);
|
||||
@@ -81,13 +81,13 @@ public final class PowerstabSpecialHandler extends MeleeSwingHandler implements
|
||||
int count = 0;
|
||||
for (Object o : list) {
|
||||
Entity e = (Entity) o;
|
||||
if (CombatStyle.RANGE.getSwingHandler().canSwing(entity, e) != InteractionType.NO_INTERACT) {
|
||||
if (CombatStyle.RANGE.getSwingHandler().canSwing(entity, e) != InteractionType.NO_INTERACT && e.isAttackable(entity, CombatStyle.RANGE, false)) {
|
||||
BattleState s = targets[count++] = new BattleState(entity, e);
|
||||
int hit = 0;
|
||||
if (isAccurateImpact(entity, e)) {
|
||||
hit = RandomFunction.random(calculateHit(entity, e, 1.0) + 1);
|
||||
}
|
||||
s.setStyle(CombatStyle.MELEE);
|
||||
s.setStyle(CombatStyle.MELEE);
|
||||
s.setEstimatedHit(hit);
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public final class PowerstabSpecialHandler extends MeleeSwingHandler implements
|
||||
|
||||
@Override
|
||||
public void visualizeImpact(Entity entity, Entity victim, BattleState state) {
|
||||
if (state.getTargets() != null) {
|
||||
if (state != null && state.getTargets() != null) {
|
||||
for (BattleState s : state.getTargets()) {
|
||||
if (s != null) {
|
||||
s.getVictim().animate(victim.getProperties().getDefenceAnimation());
|
||||
|
||||
+6
-6
@@ -49,7 +49,7 @@ public final class SpearWallSpecialHandler extends MeleeSwingHandler implements
|
||||
|
||||
@Override
|
||||
public void impact(Entity entity, Entity victim, BattleState state) {
|
||||
if (state.getTargets() != null) {
|
||||
if (state != null && state.getTargets() != null) {
|
||||
for (BattleState s : state.getTargets()) {
|
||||
if (s != null) {
|
||||
s.getVictim().getImpactHandler().handleImpact(entity, s.getEstimatedHit(), CombatStyle.MELEE, s);
|
||||
@@ -82,11 +82,11 @@ public final class SpearWallSpecialHandler extends MeleeSwingHandler implements
|
||||
int count = 0;
|
||||
for (Object o : list) {
|
||||
Entity e = (Entity) o;
|
||||
if (CombatStyle.RANGE.getSwingHandler().canSwing(entity, e) != InteractionType.NO_INTERACT) {
|
||||
if (CombatStyle.RANGE.getSwingHandler().canSwing(entity, e) != InteractionType.NO_INTERACT && e.isAttackable(entity, CombatStyle.RANGE, false)) {
|
||||
BattleState s = targets[count++] = new BattleState(entity, e);
|
||||
int hit = 0;
|
||||
hit = RandomFunction.random(calculateHit(entity, e, 1.0) + 1);
|
||||
s.setStyle(CombatStyle.MELEE);
|
||||
s.setStyle(CombatStyle.MELEE);
|
||||
s.setEstimatedHit(hit);
|
||||
}
|
||||
}
|
||||
@@ -96,13 +96,13 @@ public final class SpearWallSpecialHandler extends MeleeSwingHandler implements
|
||||
|
||||
@Override
|
||||
public void visualize(Entity entity, Entity victim, BattleState state) {
|
||||
playGlobalAudio(entity.getLocation(), Sounds.CLEAVE_2529);
|
||||
entity.visualize(ANIMATION, GRAPHIC);
|
||||
playGlobalAudio(entity.getLocation(), Sounds.CLEAVE_2529);
|
||||
entity.visualize(ANIMATION, GRAPHIC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visualizeImpact(Entity entity, Entity victim, BattleState state) {
|
||||
if (state.getTargets() != null) {
|
||||
if (state != null && state.getTargets() != null) {
|
||||
for (BattleState s : state.getTargets()) {
|
||||
if (s != null) {
|
||||
s.getVictim().animate(victim.getProperties().getDefenceAnimation());
|
||||
|
||||
+7
-4
@@ -64,7 +64,7 @@ public final class SweepSpecialHandler extends MeleeSwingHandler implements Plug
|
||||
BattleState[] targets = getTargets(entity, victim, state);
|
||||
state.setTargets(targets);
|
||||
for (BattleState s : targets) {
|
||||
s.setStyle(CombatStyle.MELEE);
|
||||
s.setStyle(CombatStyle.MELEE);
|
||||
int hit = 0;
|
||||
if (isAccurateImpact(entity, s.getVictim(), CombatStyle.MELEE)) {
|
||||
hit = RandomFunction.random(calculateHit(entity, s.getVictim(), 1.1) + 1);
|
||||
@@ -90,7 +90,7 @@ public final class SweepSpecialHandler extends MeleeSwingHandler implements Plug
|
||||
*/
|
||||
private BattleState[] getTargets(Entity entity, Entity victim, BattleState state) {
|
||||
if (!entity.getProperties().isMultiZone() || !victim.getProperties().isMultiZone()) {
|
||||
return new BattleState[] { state };
|
||||
return new BattleState[]{state};
|
||||
}
|
||||
Location vl = victim.getLocation();
|
||||
int x = vl.getX();
|
||||
@@ -102,6 +102,9 @@ public final class SweepSpecialHandler extends MeleeSwingHandler implements Plug
|
||||
if (n instanceof Familiar) {
|
||||
continue;
|
||||
}
|
||||
if (!n.isAttackable(entity, CombatStyle.MELEE, false)) {
|
||||
continue;
|
||||
}
|
||||
if (n.getLocation().equals(vl.transform(dir.getStepY(), dir.getStepX(), 0)) || n.getLocation().equals(vl.transform(-dir.getStepY(), -dir.getStepX(), 0))) {
|
||||
l.add(new BattleState(entity, n));
|
||||
if (l.size() >= 3) {
|
||||
@@ -127,7 +130,7 @@ public final class SweepSpecialHandler extends MeleeSwingHandler implements Plug
|
||||
|
||||
@Override
|
||||
public void impact(Entity entity, Entity victim, BattleState state) {
|
||||
if (state.getTargets() != null) {
|
||||
if (state != null && state.getTargets() != null) {
|
||||
for (BattleState s : state.getTargets()) {
|
||||
if (s != null) {
|
||||
s.getVictim().getImpactHandler().handleImpact(entity, s.getEstimatedHit(), CombatStyle.MELEE, s);
|
||||
@@ -146,7 +149,7 @@ public final class SweepSpecialHandler extends MeleeSwingHandler implements Plug
|
||||
|
||||
@Override
|
||||
public void visualizeImpact(Entity entity, Entity victim, BattleState state) {
|
||||
if (state.getTargets() != null) {
|
||||
if (state != null && state.getTargets() != null) {
|
||||
for (BattleState s : state.getTargets()) {
|
||||
if (s != null) {
|
||||
s.getVictim().animate(victim.getProperties().getDefenceAnimation());
|
||||
|
||||
@@ -274,46 +274,56 @@ public abstract class Familiar extends NPC implements Plugin<Object> {
|
||||
|
||||
@Override
|
||||
public boolean isAttackable(Entity entity, CombatStyle style, boolean message) {
|
||||
if (isInvalidTarget(entity, style, message)) return false;
|
||||
return super.isAttackable(entity, style, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueAttack(Entity target, CombatStyle style, boolean message) {
|
||||
return !isInvalidTarget(target, style, message);
|
||||
}
|
||||
|
||||
private boolean isInvalidTarget(Entity entity, CombatStyle style, boolean message) {
|
||||
if (entity == owner) {
|
||||
if (message) {
|
||||
owner.getPacketDispatch().sendMessage("You can't just betray your own familiar like that!");
|
||||
sendMessage(owner, "You can't just betray your own familiar like that!");
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (entity instanceof Player) {
|
||||
if (!owner.isAttackable(entity, style, message)) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!getProperties().isMultiZone()) {
|
||||
if (entity instanceof Player && !((Player) entity).getProperties().isMultiZone()) {
|
||||
if (message) {
|
||||
((Player) entity).getPacketDispatch().sendMessage("You have to be in multicombat to attack a player's familiar.");
|
||||
sendMessage((Player) entity, "You have to be in multicombat to attack a player's familiar.");
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (entity instanceof Player) {
|
||||
if (message) {
|
||||
((Player) entity).getPacketDispatch().sendMessage("This familiar is not in the a multicombat zone.");
|
||||
sendMessage((Player) entity, "This familiar is not in the a multicombat zone.");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (entity instanceof Player) {
|
||||
if (!((Player) entity).getSkullManager().isWilderness()) {
|
||||
if (message) {
|
||||
((Player) entity).getPacketDispatch().sendMessage("You have to be in the wilderness to attack a player's familiar.");
|
||||
sendMessage((Player) entity, "You have to be in the wilderness to attack a player's familiar.");
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (!owner.getSkullManager().isWilderness()) {
|
||||
if (message) {
|
||||
((Player) entity).getPacketDispatch().sendMessage("This familiar's owner is not in the wilderness.");
|
||||
sendMessage((Player) entity, "This familiar's owner is not in the wilderness.");
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.isAttackable(entity, style, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -490,9 +490,24 @@ public abstract class Entity extends Node {
|
||||
|
||||
/**
|
||||
* Checks if an entity can continue its attack.
|
||||
* @param target the target.
|
||||
* @param style the style.
|
||||
* @return {@code True} if so.
|
||||
*
|
||||
* <p>This method is called during attack validation to determine if the attacking entity should be
|
||||
* allowed to continue attacking the target. It's invoked by
|
||||
* {@link core.game.world.map.zone.ZoneMonitor#continueAttack(Node, CombatStyle, boolean)}
|
||||
* as part of the attack validation chain.</p>
|
||||
*
|
||||
* <p>The default implementation returns {@code true}, allowing the attack to proceed.
|
||||
* <b>Subclasses should override this method to implement entity-specific attack restrictions.</b></p>
|
||||
*
|
||||
* <p>This method is distinct from {@link #isAttackable(Entity, CombatStyle, boolean)}, which
|
||||
* checks if this entity can be attacked by another entity. The {@code continueAttack} method
|
||||
* checks if this entity can attack the given target.</p>
|
||||
*
|
||||
* @param target the target entity being attacked
|
||||
* @param style the combat style being used
|
||||
* @param message whether to send a message to the player explaining why the attack cannot continue
|
||||
*
|
||||
* @return {@code true} if the entity can continue attacking the target
|
||||
*/
|
||||
public boolean continueAttack(Entity target, CombatStyle style, boolean message) {
|
||||
return true;
|
||||
|
||||
@@ -127,7 +127,7 @@ public abstract class CombatSpell extends MagicSpell {
|
||||
if (e == target || e == entity) {
|
||||
continue;
|
||||
}
|
||||
if (CombatStyle.MAGIC.getSwingHandler().canSwing(entity, e) == InteractionType.NO_INTERACT) {
|
||||
if (CombatStyle.MAGIC.getSwingHandler().canSwing(entity, e) == InteractionType.NO_INTERACT || !e.isAttackable(entity, CombatStyle.MAGIC, false)) {
|
||||
continue;
|
||||
}
|
||||
if (e instanceof Familiar) {
|
||||
|
||||
@@ -400,15 +400,29 @@ public class NPC extends Entity {
|
||||
return false;
|
||||
}
|
||||
if (task != null && entity instanceof Player && task.levelReq > entity.getSkills().getLevel(Skills.SLAYER)) {
|
||||
if(message) {
|
||||
((Player) entity).getPacketDispatch().sendMessage("You need a higher slayer level to know how to wound this monster.");
|
||||
}
|
||||
if(message) {
|
||||
((Player) entity).getPacketDispatch().sendMessage("You need a higher slayer level to know how to wound this monster.");
|
||||
}
|
||||
}
|
||||
if (!behavior.canBeAttackedBy(this, entity, style, message))
|
||||
return false;
|
||||
return super.isAttackable(entity, style, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueAttack(Entity target, CombatStyle style, boolean message) {
|
||||
if (isInvisible()) {
|
||||
return false;
|
||||
}
|
||||
if (task != null && target instanceof Player && task.levelReq > target.getSkills().getLevel(Skills.SLAYER)) {
|
||||
if (message) {
|
||||
((Player) target).getPacketDispatch().sendMessage("You need a higher slayer level to know how to wound this monster.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return behavior.canBeAttackedBy(this, target, style, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDragonfireProtection(boolean fire) {
|
||||
return 0;
|
||||
|
||||
@@ -766,6 +766,30 @@ public class Player extends Entity {
|
||||
return super.isAttackable(entity, style, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueAttack(Entity target, CombatStyle style, boolean message) {
|
||||
if (target instanceof NPC
|
||||
&& !((NPC) target).getDefinition().hasAction("attack")
|
||||
&& !((NPC) target).isIgnoreAttackRestrictions(this)) {
|
||||
return false;
|
||||
}
|
||||
if (target instanceof Player) {
|
||||
Player p = (Player) target;
|
||||
if (p.getSkullManager().isWilderness() && skullManager.isWilderness()) {
|
||||
if (GameWorld.getSettings() == null || !GameWorld.getSettings().getWild_pvp_enabled()) {
|
||||
return false;
|
||||
}
|
||||
if (p.getSkullManager().hasWildernessProtection()) {
|
||||
return false;
|
||||
}
|
||||
return !skullManager.hasWildernessProtection();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPoisonImmune() {
|
||||
return timers.getTimer("poison:immunity") != null;
|
||||
|
||||
@@ -130,12 +130,25 @@ public abstract class MapZone implements Zone {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the entity is able to continue attacking the target.
|
||||
* @param e the attacking entity.
|
||||
* @param target The target.
|
||||
* @param style The combat style used.
|
||||
* @param message If a message should be send.
|
||||
* @return {@code True} if so.
|
||||
* Checks if the entity is able to continue attacking the target within this zone.
|
||||
*
|
||||
* <p>This method is called during attack validation to determine if zone-specific rules
|
||||
* allow the attack to proceed. It's invoked by
|
||||
* {@link core.game.world.map.zone.ZoneMonitor#continueAttack(Node, CombatStyle, boolean)}
|
||||
* for each zone the attacking entity is in.</p>
|
||||
*
|
||||
* <p>The default implementation returns {@code true}, allowing the attack to proceed.
|
||||
* <b>Subclasses should override this method to implement zone-specific attack restrictions.</b></p>
|
||||
* <p>This method is part of the attack validation chain:
|
||||
* {@link Entity#isAttackable(Entity, CombatStyle, boolean)} ->
|
||||
* {@link core.game.world.map.zone.ZoneMonitor#continueAttack(Node, CombatStyle, boolean)} ->
|
||||
* {@code MapZone.continueAttack()} + {@link Entity#continueAttack(Entity, CombatStyle, boolean)}</p>
|
||||
*
|
||||
* @param e the attacking entity
|
||||
* @param target the target node being attacked
|
||||
* @param style the combat style being used
|
||||
* @param message whether to send a message to the player explaining why the attack cannot continue
|
||||
* @return {@code true} if the zone allows the attack to continue
|
||||
*/
|
||||
public boolean continueAttack(Entity e, Node target, CombatStyle style, boolean message) {
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user