diff --git a/Server/src/main/content/global/handlers/item/equipment/special/ChinchompaSwingHandler.java b/Server/src/main/content/global/handlers/item/equipment/special/ChinchompaSwingHandler.java index 34b52b9c0..a0801835e 100644 --- a/Server/src/main/content/global/handlers/item/equipment/special/ChinchompaSwingHandler.java +++ b/Server/src/main/content/global/handlers/item/equipment/special/ChinchompaSwingHandler.java @@ -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 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; diff --git a/Server/src/main/content/global/handlers/item/equipment/special/PowerstabSpecialHandler.java b/Server/src/main/content/global/handlers/item/equipment/special/PowerstabSpecialHandler.java index 172ca036c..ba3ff8ed0 100644 --- a/Server/src/main/content/global/handlers/item/equipment/special/PowerstabSpecialHandler.java +++ b/Server/src/main/content/global/handlers/item/equipment/special/PowerstabSpecialHandler.java @@ -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()); diff --git a/Server/src/main/content/global/handlers/item/equipment/special/SpearWallSpecialHandler.java b/Server/src/main/content/global/handlers/item/equipment/special/SpearWallSpecialHandler.java index c83d2fd5d..2595b0479 100644 --- a/Server/src/main/content/global/handlers/item/equipment/special/SpearWallSpecialHandler.java +++ b/Server/src/main/content/global/handlers/item/equipment/special/SpearWallSpecialHandler.java @@ -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()); diff --git a/Server/src/main/content/global/handlers/item/equipment/special/SweepSpecialHandler.java b/Server/src/main/content/global/handlers/item/equipment/special/SweepSpecialHandler.java index 3ef8ce0f7..6c4b9cedc 100644 --- a/Server/src/main/content/global/handlers/item/equipment/special/SweepSpecialHandler.java +++ b/Server/src/main/content/global/handlers/item/equipment/special/SweepSpecialHandler.java @@ -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()); diff --git a/Server/src/main/content/global/skill/summoning/familiar/Familiar.java b/Server/src/main/content/global/skill/summoning/familiar/Familiar.java index b85c1ec16..b386a54d6 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/Familiar.java +++ b/Server/src/main/content/global/skill/summoning/familiar/Familiar.java @@ -274,46 +274,56 @@ public abstract class Familiar extends NPC implements Plugin { @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 diff --git a/Server/src/main/core/game/node/entity/Entity.java b/Server/src/main/core/game/node/entity/Entity.java index 9699be83d..3576742f7 100644 --- a/Server/src/main/core/game/node/entity/Entity.java +++ b/Server/src/main/core/game/node/entity/Entity.java @@ -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. + * + *

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.

+ * + *

The default implementation returns {@code true}, allowing the attack to proceed. + * Subclasses should override this method to implement entity-specific attack restrictions.

+ * + *

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.

+ * + * @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; diff --git a/Server/src/main/core/game/node/entity/combat/spell/CombatSpell.java b/Server/src/main/core/game/node/entity/combat/spell/CombatSpell.java index 537262cf9..30cf2f238 100644 --- a/Server/src/main/core/game/node/entity/combat/spell/CombatSpell.java +++ b/Server/src/main/core/game/node/entity/combat/spell/CombatSpell.java @@ -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) { diff --git a/Server/src/main/core/game/node/entity/npc/NPC.java b/Server/src/main/core/game/node/entity/npc/NPC.java index 2ce5481cc..bcd5c4f70 100644 --- a/Server/src/main/core/game/node/entity/npc/NPC.java +++ b/Server/src/main/core/game/node/entity/npc/NPC.java @@ -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; diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java index dfe357ff0..30d32e9f2 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -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; diff --git a/Server/src/main/core/game/world/map/zone/MapZone.java b/Server/src/main/core/game/world/map/zone/MapZone.java index 630d06194..de20b4b08 100644 --- a/Server/src/main/core/game/world/map/zone/MapZone.java +++ b/Server/src/main/core/game/world/map/zone/MapZone.java @@ -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. + * + *

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.

+ * + *

The default implementation returns {@code true}, allowing the attack to proceed. + * Subclasses should override this method to implement zone-specific attack restrictions.

+ *

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)}

+ * + * @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;