multihit wip2

This commit is contained in:
Player Name 2026-07-27 10:15:20 +02:00
parent eb3c64d61a
commit 4716a0b74d
14 changed files with 103 additions and 114 deletions

View file

@ -6,15 +6,15 @@ import core.game.node.entity.combat.equipment.Ammunition;
import core.game.node.entity.combat.equipment.RangeWeapon;
import core.game.node.entity.combat.equipment.Weapon;
import core.game.node.entity.combat.equipment.WeaponInterface;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.node.entity.skill.Skills;
import core.game.world.map.RegionManager;
import core.game.world.update.flag.context.Graphics;
import core.tools.RandomFunction;
import java.util.List;
import static core.game.node.entity.combat.MultihitTargetsKt.findMultihitTargets;
/**
* Handles a combat swing using red chinchompas.
* @author Emperor
@ -55,25 +55,17 @@ public final class ChinchompaSwingHandler extends RangeSwingHandler {
return -1;
}
// https://oldschool.runescape.wiki/w/Chinchompa - shit source but it's better than what was here before
List<? extends Entity> targetCandidates;
if (victim instanceof Player) {
targetCandidates = RegionManager.getSurroundingPlayers(victim, 9);
} else {
targetCandidates = RegionManager.getSurroundingNPCs(victim, 11);
}
List<Entity> targetCandidates = findMultihitTargets(victim.getLocation(), entity, victim, entity.getSwingHandler(false), CombatStyle.RANGE);
BattleState[] targets = new BattleState[targetCandidates.size()];
int count = 0;
for (Entity e : targetCandidates) {
if (e != entity && 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;
if (isAccurateImpact(entity, e, CombatStyle.RANGE)) {
hit = RandomFunction.random(calculateHit(entity, e, 1.0) + 1);
}
s.setEstimatedHit(hit);
BattleState s = targets[count++] = new BattleState(entity, e);
s.setStyle(CombatStyle.RANGE);
int hit = 0;
if (isAccurateImpact(entity, e, CombatStyle.RANGE)) {
hit = RandomFunction.random(calculateHit(entity, e, 1.0) + 1);
}
s.setEstimatedHit(hit);
}
state.setTargets(targets);
Companion.useAmmo(entity, state, null);

View file

@ -19,6 +19,7 @@ import core.tools.RandomFunction;
import org.rs09.consts.Sounds;
import static core.api.ContentAPIKt.playGlobalAudio;
import static core.game.node.entity.combat.MultihitTargetsKt.findMultihitTargets;
/**
* Handles the Powerstab special attack.
@ -75,19 +76,17 @@ public final class PowerstabSpecialHandler extends MeleeSwingHandler implements
if (!multi) {
return super.swing(entity, victim, state);
}
List<? extends Entity> list = victim instanceof NPC ? RegionManager.getSurroundingNPCs(entity, 9) : RegionManager.getSurroundingPlayers(entity, 9);
List<Entity> list = findMultihitTargets(entity.getLocation(), entity, victim, entity.getSwingHandler(false), CombatStyle.MELEE);
BattleState[] targets = new BattleState[list.size()];
int count = 0;
for (Entity e : list) {
if (e != entity && 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.setEstimatedHit(hit);
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.setEstimatedHit(hit);
}
state.setTargets(targets);
return 1;

View file

@ -19,6 +19,7 @@ import core.tools.RandomFunction;
import org.rs09.consts.Sounds;
import static core.api.ContentAPIKt.playGlobalAudio;
import static core.game.node.entity.combat.MultihitTargetsKt.findMultihitTargets;
/**
* Handles Vesta's Spear special attack - Spear Wall.
@ -76,17 +77,14 @@ public final class SpearWallSpecialHandler extends MeleeSwingHandler implements
if (!multi) {
return super.swing(entity, victim, state);
}
List<? extends Entity> list = victim instanceof NPC ? RegionManager.getSurroundingNPCs(entity, 9) : RegionManager.getSurroundingPlayers(entity, 9);
List<Entity> list = findMultihitTargets(entity.getLocation(), entity, victim, entity.getSwingHandler(false), CombatStyle.MELEE);
BattleState[] targets = new BattleState[list.size()];
int count = 0;
for (Entity e : list) {
if (e != entity && 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.setEstimatedHit(hit);
}
BattleState s = targets[count++] = new BattleState(entity, e);
int hit = RandomFunction.random(calculateHit(entity, e, 1.0) + 1);
s.setStyle(CombatStyle.MELEE);
s.setEstimatedHit(hit);
}
state.setTargets(targets);
return 1;

View file

@ -24,6 +24,7 @@ import core.tools.RandomFunction;
import org.rs09.consts.Sounds;
import static core.api.ContentAPIKt.playGlobalAudio;
import static core.game.node.entity.combat.MultihitTargetsKt.findMultihitTargets;
/**
* Handles the Dragon halberd special attack.
@ -100,14 +101,9 @@ public final class SweepSpecialHandler extends MeleeSwingHandler implements Plug
Direction dir = Direction.getDirection(x - entity.getLocation().getX(), y - entity.getLocation().getY());
List<BattleState> l = new ArrayList<>(20);
l.add(new BattleState(entity, victim));
for (Entity n : victim instanceof NPC ? RegionManager.getSurroundingNPCs(victim, 9) : RegionManager.getSurroundingPlayers(victim, 9)) {
if (n == entity || n == victim) {
continue;
}
if (n instanceof Familiar) {
continue;
}
if (!n.isAttackable(entity, CombatStyle.MELEE, false)) {
List<Entity> list = findMultihitTargets(entity.getLocation(), entity, victim, entity.getSwingHandler(false), CombatStyle.MELEE);
for (Entity n : list) {
if (n == victim) {
continue;
}
if (n.getLocation().equals(vl.transform(dir.getStepY(), dir.getStepX(), 0)) || n.getLocation().equals(vl.transform(-dir.getStepY(), -dir.getStepX(), 0))) {

View file

@ -68,7 +68,7 @@ public final class AncientTeleportPlugin extends MagicSpell {
return false;
}
homeTeleport((Player) entity, Location.create(3087, 3495, 0));
entity.dispatch(new SpellCastEvent(SpellBook.ANCIENT, getSpellId(), target));
entity.dispatch(new SpellCastEvent(SpellBook.ANCIENT, getSpellId(), target));
} else if (entity.getTeleporter().send(location.transform(0, RandomFunction.random(3), 0), TeleportType.ANCIENT)) {
if (!super.meetsRequirements(entity, true, true)) {
entity.getTeleporter().getCurrentTeleport().stop();

View file

@ -2,6 +2,8 @@ package content.global.skill.magic.ancient;
import java.util.List;
import core.game.node.entity.combat.CombatStyle;
import core.game.node.entity.combat.MultihitTargetsKt;
import core.game.node.entity.combat.spell.Runes;
import core.game.node.Node;
import core.game.node.entity.Entity;
@ -118,7 +120,8 @@ public final class BloodSpells extends CombatSpell {
if (animation.getId() == 1978 || !entity.getProperties().isMultiZone() || !target.getProperties().isMultiZone()) {
return super.getTargets(entity, target);
}
List<Entity> list = getMultihitTargets(entity, target);
List<Entity> list = MultihitTargetsKt.findMultihitTargets(target.getLocation(), entity, target, entity.getSwingHandler(false), CombatStyle.MAGIC);
BattleState[] targets = new BattleState[list.size()];
int index = 0;
for (Entity e : list) {

View file

@ -2,6 +2,8 @@ package content.global.skill.magic.ancient;
import java.util.List;
import core.game.node.entity.combat.CombatStyle;
import core.game.node.entity.combat.MultihitTargetsKt;
import core.game.node.entity.combat.spell.Runes;
import core.game.node.Node;
import core.game.node.entity.Entity;
@ -131,10 +133,10 @@ public final class IceSpells extends CombatSpell {
return;
}
int ticks = (type.ordinal() - 4) * 8;
if (hasTimerActive(victim, "frozen") || hasTimerActive(victim, "frozen:immunity")) {
if (type == SpellType.BARRAGE) { state.setFrozen(true); }
return;
}
if (hasTimerActive(victim, "frozen") || hasTimerActive(victim, "frozen:immunity")) {
if (type == SpellType.BARRAGE) { state.setFrozen(true); }
return;
}
registerTimer(victim, spawnTimer("frozen", ticks, true));
}
@ -143,7 +145,8 @@ public final class IceSpells extends CombatSpell {
if (animation.getId() == 1978 || !entity.getProperties().isMultiZone() || !target.getProperties().isMultiZone()) {
return super.getTargets(entity, target);
}
List<Entity> list = getMultihitTargets(entity, target);
List<Entity> list = MultihitTargetsKt.findMultihitTargets(target.getLocation(), entity, target, entity.getSwingHandler(false), CombatStyle.MAGIC);
BattleState[] targets = new BattleState[list.size()];
int index = 0;
for (Entity e : list) {

View file

@ -3,6 +3,8 @@
import java.util.List;
import core.game.container.impl.EquipmentContainer;
import core.game.node.entity.combat.CombatStyle;
import core.game.node.entity.combat.MultihitTargetsKt;
import core.plugin.Initializable;
import core.game.node.entity.combat.spell.Runes;
import core.game.node.Node;
@ -121,7 +123,7 @@ public final class MiasmicSpells extends CombatSpell {
@Override
public void fireEffect(Entity entity, Entity victim, BattleState state) {
if (!hasTimerActive(victim, "miasmic:immunity")) {
registerTimer(victim, spawnTimer("miasmic", (getSpellId() - 15) * 20));
registerTimer(victim, spawnTimer("miasmic", (getSpellId() - 15) * 20));
}
}
@ -142,8 +144,8 @@ public final class MiasmicSpells extends CombatSpell {
@Override
public boolean cast(Entity entity, Node target) {
if (!validStaffEquipped(entity) && !GameWorld.getSettings().isDevMode()) {
((Player) entity).getPacketDispatch().sendMessage("You need to be wielding Zuriel's staff in order to cast this spell.");
return false;
((Player) entity).getPacketDispatch().sendMessage("You need to be wielding Zuriel's staff in order to cast this spell.");
return false;
}
if (!meetsRequirements(entity, true, false)) {
return false;
@ -157,7 +159,8 @@ public final class MiasmicSpells extends CombatSpell {
|| !entity.getProperties().isMultiZone() || !target.getProperties().isMultiZone()) {
return super.getTargets(entity, target);
}
List<Entity> list = getMultihitTargets(entity, target);
List<Entity> list = MultihitTargetsKt.findMultihitTargets(target.getLocation(), entity, target, entity.getSwingHandler(false), CombatStyle.MAGIC);
BattleState[] targets = new BattleState[list.size()];
int index = 0;
for (Entity e : list) {

View file

@ -2,7 +2,8 @@ package content.global.skill.magic.ancient;
import java.util.List;
import core.game.node.entity.player.Player;
import core.game.node.entity.combat.CombatStyle;
import core.game.node.entity.combat.MultihitTargetsKt;
import core.game.node.entity.skill.Skills;
import core.game.node.entity.combat.spell.Runes;
import core.game.node.Node;
@ -115,7 +116,8 @@ public final class ShadowSpells extends CombatSpell {
if (animation.getId() == 1978 || !entity.getProperties().isMultiZone() || !target.getProperties().isMultiZone()) {
return super.getTargets(entity, target);
}
List<Entity> list = getMultihitTargets(entity, target);
List<Entity> list = MultihitTargetsKt.findMultihitTargets(target.getLocation(), entity, target, entity.getSwingHandler(false), CombatStyle.MAGIC);
BattleState[] targets = new BattleState[list.size()];
int index = 0;
for (Entity e : list) {

View file

@ -2,6 +2,8 @@ package content.global.skill.magic.ancient;
import java.util.List;
import core.game.node.entity.combat.CombatStyle;
import core.game.node.entity.combat.MultihitTargetsKt;
import core.game.node.entity.combat.spell.Runes;
import core.game.node.Node;
import core.game.node.entity.Entity;
@ -10,7 +12,6 @@ import core.game.node.entity.combat.spell.CombatSpell;
import core.game.node.entity.combat.spell.SpellType;
import core.game.node.entity.impl.Projectile;
import core.game.node.entity.impl.Animator.Priority;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.SpellBookManager.SpellBook;
import core.game.node.item.Item;
import core.game.world.update.flag.context.Animation;
@ -114,7 +115,7 @@ public final class SmokeSpells extends CombatSpell {
@Override
public void fireEffect(Entity entity, Entity victim, BattleState state) {
if (state.getEstimatedHit() > -1) {
applyPoison(victim, entity, type.ordinal() >= SpellType.BLITZ.ordinal() ? 4 : 2);
applyPoison(victim, entity, type.ordinal() >= SpellType.BLITZ.ordinal() ? 4 : 2);
}
}
@ -123,7 +124,8 @@ public final class SmokeSpells extends CombatSpell {
if (animation.getId() == 1978 || !entity.getProperties().isMultiZone() || !target.getProperties().isMultiZone()) {
return super.getTargets(entity, target);
}
List<Entity> list = getMultihitTargets(entity, target);
List<Entity> list = MultihitTargetsKt.findMultihitTargets(target.getLocation(), entity, target, entity.getSwingHandler(false), CombatStyle.MAGIC);
BattleState[] targets = new BattleState[list.size()];
int index = 0;
for (Entity e : list) {

View file

@ -106,7 +106,7 @@ public final class WildernessObeliskPlugin extends OptionHandler {
int index = RandomFunction.random(0, newObelisks.length - 1); //cutting out the last one that is now duplicated
Obelisk newObelisk = newObelisks[index];
// Teleport players standing within a 3-by-3 bounding box
for (Player player : RegionManager.getLocalPlayersBoundingBox(center, 0)) {
for (Player player : RegionManager.getLocalPlayers(center, 1)) {
if (player.timers.getTimer("teleblock") == null) {
player.getPacketDispatch().sendMessage("Ancient magic teleports you somewhere in the wilderness.");
int xOffset = player.getLocation().getX() - center.getX();

View file

@ -0,0 +1,39 @@
package core.game.node.entity.combat
import content.global.skill.summoning.familiar.Familiar
import core.game.node.entity.Entity
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.world.map.Location
import core.game.world.map.RegionManager.getLocalEntities
import core.game.world.map.zone.impl.WildernessZone
fun findMultihitTargets(center: Location, entity: Entity, victim: Entity, swingHandler: CombatSwingHandler, combatStyle: CombatStyle): List<Entity> {
val targetCandidates = getLocalEntities(center, 1).filter { it != entity && swingHandler.canSwing(entity, it) != InteractionType.NO_INTERACT && it.isAttackable(entity, combatStyle, false) }
val targets: List<Entity>?
if (victim is Player) {
val players = targetCandidates.filterIsInstance<Player>()
fun isFamiliarOf(entity: Entity, players: List<Player>): Boolean {
if (entity !is Familiar) {
return false
}
return entity.owner in players
}
targets = targetCandidates.filter { it is Player || isFamiliarOf(it, players) }
} else {
val npcs = targetCandidates.filterIsInstance<NPC>()
fun isOwnerOf(entity: Entity, npcs: List<NPC>): Boolean {
if (entity !is Player) {
return false
}
for (npc in npcs) {
if (npc is Familiar && npc.owner == entity) {
return WildernessZone.getInstance().continueAttack(entity, entity, combatStyle, false)
}
}
return false
}
targets = targetCandidates.filter { it is NPC || isOwnerOf(it, npcs) }
}
return targets.take(9)
}

View file

@ -1,9 +1,6 @@
package core.game.node.entity.combat.spell;
import content.global.skill.summoning.familiar.Familiar;
import core.game.node.entity.combat.BattleState;
import core.game.node.entity.combat.CombatStyle;
import core.game.node.entity.combat.InteractionType;
import core.game.node.Node;
import core.game.node.entity.Entity;
import core.game.node.entity.impl.Animator.Priority;
@ -13,15 +10,10 @@ import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.SpellBookManager;
import core.game.node.entity.player.link.audio.Audio;
import core.game.node.item.Item;
import core.game.world.map.RegionManager;
import core.game.world.map.zone.impl.WildernessZone;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import org.rs09.consts.Sounds;
import java.util.ArrayList;
import java.util.List;
import static core.api.ContentAPIKt.playGlobalAudio;
/**
@ -108,39 +100,6 @@ public abstract class CombatSpell extends MagicSpell {
}
/**
* Gets a list of valid targets for a multihitting spell.
* @param entity The caster of the spell.
* @param victim The primary victim.
* @return The list of targets (the primary target is always at index 0).
*/
public List<Entity> getMultihitTargets(Entity entity, Entity victim) {
// https://oldschool.runescape.wiki/w/Chinchompa - shit source but it's better than what was here before
List<? extends Entity> targetCandidates;
if (victim instanceof Player) {
targetCandidates = RegionManager.getSurroundingPlayers(victim, 9);
} else {
targetCandidates = RegionManager.getSurroundingNPCs(victim, 11);
}
for (Entity e : targetCandidates) {
if (e == victim || e == entity) {
continue;
}
if (CombatStyle.MAGIC.getSwingHandler().canSwing(entity, e) == InteractionType.NO_INTERACT || !e.isAttackable(entity, CombatStyle.MAGIC, false)) {
continue;
}
if (e instanceof Familiar) {
Player owner = ((Familiar) e).getOwner();
if (owner != entity && WildernessZone.getInstance().continueAttack(entity, owner, CombatStyle.MAGIC, true)) {
targetCandidates.add(e);
}
} else {
targetCandidates.add(e);
}
}
return targetCandidates;
}
/**
* Visualizes the impact.
* @param entity The entity.

View file

@ -420,7 +420,7 @@ object RegionManager {
* @param maximum the maximum number of entities to return.
* @return the list.
*/
inline fun<reified T: Entity> getLocalEntities(location: Location, distance: Int, maximum: Int): List<T> {
private inline fun<reified T: Entity> getLocalEntitiesOfType(location: Location, distance: Int): List<T> {
val entities: MutableList<T> = ArrayList(20)
for (cx in (location.absChunkX - 8)..(location.absChunkX + 8)) {
for (cy in (location.absChunkY - 8)..(location.absChunkY + 8)) {
@ -434,20 +434,13 @@ object RegionManager {
}
}
val b = ZoneBorders(location.x - distance, location.y - distance, location.x + distance, location.y + distance)
var filtered = entities.filter { b.insideBorder(it.location.x, it.location.y) }
if (maximum > 0 && filtered.size > maximum) {
filtered = filtered.slice(0 until maximum)
}
return filtered
return entities.filter { b.insideBorder(it.location.x, it.location.y) }
}
@JvmStatic fun getLocalEntities(location: Location, distance: Int): List<Entity> = getLocalEntities<Entity>(location, distance, 0)
@JvmStatic fun getLocalPlayers(location: Location, distance: Int): List<Player> = getLocalEntities<Player>(location, distance, 0)
@JvmStatic fun getLocalNPCs(location: Location, distance: Int): List<NPC> = getLocalEntities<NPC>(location, distance, 0)
@JvmStatic fun getLocalPlayers(location: Location): List<Player> = getLocalEntities<Player>(location, MapDistance.RENDERING.distance, 0)
@JvmStatic fun getLocalNPCs(location: Location): List<NPC> = getLocalEntities<NPC>(location, MapDistance.RENDERING.distance, 0)
@JvmStatic fun getSurroundingPlayers(player: Entity, maximum: Int): List<Player> = getLocalEntities<Player>(player.location, 1, maximum)
@JvmStatic fun getSurroundingNPCs(npc: Entity, maximum: Int): List<NPC> = getLocalEntities<NPC>(npc.location, 1, maximum)
@JvmStatic fun getLocalPlayersBoundingBox(location: Location, maximum: Int): List<Player> = getLocalEntities<Player>(location, 1, maximum)
@JvmStatic fun getLocalEntities(location: Location, distance: Int) = getLocalEntitiesOfType<Entity>(location, distance)
@JvmStatic fun getLocalPlayers(location: Location, distance: Int) = getLocalEntitiesOfType<Player>(location, distance)
@JvmStatic fun getLocalNPCs(location: Location, distance: Int) = getLocalEntitiesOfType<NPC>(location, distance)
@JvmStatic fun getLocalPlayers(location: Location): List<Player> = getLocalPlayers(location, MapDistance.RENDERING.distance)
@JvmStatic fun getLocalNPCs(location: Location): List<NPC> = getLocalNPCs(location, MapDistance.RENDERING.distance)
/**
* Gets a random teleport location in the radius around the given location.