Rewrote spellbook swap

Corrected spellbook swap spell selection timeout
This commit is contained in:
dam
2026-04-19 13:10:34 +03:00
committed by Ryan
parent 2806c08f7b
commit d67fb26749
14 changed files with 361 additions and 234 deletions
@@ -30,6 +30,17 @@ public final class AutocastSelectPlugin extends ComponentPlugin {
if (!player.getAttribute("autocast_select", false)) { if (!player.getAttribute("autocast_select", false)) {
return true; return true;
} }
if (player.timers.getTimer("spellbook:swap") != null) {
player.getPacketDispatch().sendMessage("You cannot autocast while using Spellbook Swap.");
player.removeAttribute("autocast_select");
player.removeAttribute("autocast_component");
final WeaponInterface w = player.getExtension(WeaponInterface.class);
if (w != null) {
w.setAttackStyle(3);
player.getInterfaceManager().openTab(w);
}
return true;
}
player.removeAttribute("autocast_select"); player.removeAttribute("autocast_select");
final WeaponInterface w = player.getExtension(WeaponInterface.class); final WeaponInterface w = player.getExtension(WeaponInterface.class);
if (w != null) { if (w != null) {
@@ -1,15 +1,23 @@
package content.global.skill.magic package content.global.skill.magic
import core.api.* import content.data.Quests
import core.api.animate
import core.api.hasLevelStat
import core.api.hasRequirement
import core.api.lock
import core.api.playAudio
import core.api.sendMessage
import core.game.event.SpellbookChangeEvent
import core.game.interaction.IntType import core.game.interaction.IntType
import core.game.interaction.InteractionListener import core.game.interaction.InteractionListener
import core.game.node.Node import core.game.node.Node
import core.game.node.entity.combat.equipment.WeaponInterface
import core.game.node.entity.player.Player import core.game.node.entity.player.Player
import core.game.node.entity.player.link.SpellBookManager
import core.game.node.entity.player.link.SpellBookManager.SpellBook import core.game.node.entity.player.link.SpellBookManager.SpellBook
import core.game.node.entity.skill.Skills import core.game.node.entity.skill.Skills
import org.rs09.consts.Scenery import org.rs09.consts.Scenery
import org.rs09.consts.Sounds import org.rs09.consts.Sounds
import content.data.Quests
class MagicAltarListener : InteractionListener { class MagicAltarListener : InteractionListener {
override fun defineListeners() { override fun defineListeners() {
@@ -36,26 +44,36 @@ class MagicAltarListener : InteractionListener {
return true return true
} }
private fun swapSpellBook(player: Player, altar: Node) { private fun swapSpellBook(player : Player, altar : Node) {
lock(player, 3) lock(player, 3)
playAudio(player, Sounds.PRAYER_RECHARGE_2674) playAudio(player, Sounds.PRAYER_RECHARGE_2674)
animate(player, 645) animate(player, 645)
if (altar.id == ANCIENT_ALTAR) {
if (altar.id == ANCIENT_ALTAR) { player.skills.decrementPrayerPoints(player.skills.prayerPoints)
player.skills.decrementPrayerPoints(player.skills.prayerPoints) }
} val weaponInterface = player.getExtension<WeaponInterface>(WeaponInterface::class.java)
if (weaponInterface != null && player.properties.autocastSpell != null) {
if (SpellBook.forInterface(player.spellBookManager.spellBook) == if (altar.id == ANCIENT_ALTAR) SpellBook.ANCIENT else SpellBook.LUNAR) { weaponInterface.selectAutoSpell(-1, true)
sendMessage(player, if (altar.id == ANCIENT_ALTAR) "You feel a strange drain upon your memory..." else "Modern spells activated!") }
player.spellBookManager.setSpellBook(SpellBook.MODERN) if (SpellBook.forInterface(player.spellBookManager.spellBook) == if (altar.id == ANCIENT_ALTAR) SpellBook.ANCIENT else SpellBook.LUNAR) {
player.spellBookManager.update(player) player.dispatch(SpellbookChangeEvent(
} else { SpellBook.forInterface(player.spellBookManager.spellBook),
sendMessage(player, if (altar.id == ANCIENT_ALTAR) "You feel a strange wisdom fill your mind..." else "Lunar spells activated!") SpellBook.MODERN,
player.spellBookManager.setSpellBook(if (altar.id == ANCIENT_ALTAR) SpellBook.ANCIENT else SpellBook.LUNAR) SpellBookManager.SpellbookChangeSource.ALTAR))
player.spellBookManager.update(player) sendMessage(player, if (altar.id == ANCIENT_ALTAR) "You feel a strange drain upon your memory..." else "Modern spells activated!")
} player.spellBookManager.setSpellBook(SpellBook.MODERN)
} player.spellBookManager.update(player)
} else {
player.dispatch(SpellbookChangeEvent(
SpellBook.forInterface(player.spellBookManager.spellBook),
if (altar.id == ANCIENT_ALTAR) SpellBook.ANCIENT else SpellBook.LUNAR,
SpellBookManager.SpellbookChangeSource.ALTAR))
sendMessage(player, if (altar.id == ANCIENT_ALTAR) "You feel a strange wisdom fill your mind..." else "Lunar spells activated!")
player.spellBookManager.setSpellBook(if (altar.id == ANCIENT_ALTAR) SpellBook.ANCIENT else SpellBook.LUNAR)
player.spellBookManager.update(player)
}
}
companion object { companion object {
private const val ANCIENT_ALTAR = Scenery.ALTAR_6552 private const val ANCIENT_ALTAR = Scenery.ALTAR_6552
@@ -1,13 +1,25 @@
package content.global.skill.magic package content.global.skill.magic
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.combat.spell.CombinationRune import core.game.node.entity.combat.spell.CombinationRune
import core.game.node.entity.combat.spell.MagicStaff import core.game.node.entity.combat.spell.MagicStaff
import core.game.node.entity.combat.spell.Runes import core.game.node.entity.combat.spell.Runes
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.item.Item import core.game.node.item.Item
object SpellUtils { object SpellUtils {
/**
* Validates that the spell packet's interface matches the player's actual server-side spellbook.
* This prevents exploits where the client has a stale spellbook interface (e.g., after Spellbook Swap reverts).
* @param player The player casting the spell
* @param packetInterfaceId The interface ID from the client's spell packet
* @return true if the interfaces match (valid), false if there's a client/server desync
*/
@JvmStatic
fun validateSpellbookInterface(player : Player, packetInterfaceId : Int) : Boolean {
val actualSpellbook = player.spellBookManager.spellBook
return packetInterfaceId == actualSpellbook
}
fun usingStaff(p: Player, rune: Int): Boolean { fun usingStaff(p: Player, rune: Int): Boolean {
val weapon = p.equipment[3] ?: return false val weapon = p.equipment[3] ?: return false
val staff = MagicStaff.forId(rune) ?: return false val staff = MagicStaff.forId(rune) ?: return false
@@ -1,10 +1,21 @@
package content.global.skill.magic; package content.global.skill.magic;
import content.data.Quests;
import core.game.component.Component; import core.game.component.Component;
import core.game.dialogue.DialoguePlugin; import core.game.dialogue.DialoguePlugin;
import core.game.event.SpellbookChangeEvent;
import core.game.interaction.Listener;
import core.game.node.entity.player.Player; import core.game.node.entity.player.Player;
import core.plugin.Initializable;
import core.game.node.entity.player.link.SpellBookManager.SpellBook; import core.game.node.entity.player.link.SpellBookManager.SpellBook;
import core.game.node.entity.player.link.SpellBookManager.SpellbookChangeSource;
import core.game.node.entity.skill.Skills;
import core.game.node.item.Item;
import core.plugin.Initializable;
import java.util.ArrayList;
import static core.api.ContentAPIKt.hasRequirement;
/** /**
* Handles the SpellbookSwapDialogue dialogue. * Handles the SpellbookSwapDialogue dialogue.
@@ -73,8 +84,27 @@ public class SpellbookSwapDialogue extends DialoguePlugin {
break; break;
} }
final SpellBook book = type == 1 ? SpellBook.ANCIENT : SpellBook.MODERN; final SpellBook book = type == 1 ? SpellBook.ANCIENT : SpellBook.MODERN;
if (book == SpellBook.ANCIENT && !hasRequirement(player, Quests.DESERT_TREASURE)) {
player.getPacketDispatch().sendMessage("You need to complete Desert Treasure to use Ancient Magicks.");
end();
return true;
}
// Remove runes for Spellbook Swap
ArrayList<Item> runes = player.getAttribute("spell:runes", new ArrayList<>());
if (!runes.isEmpty() && player.getInventory().remove(runes.toArray(new Item[0]))) {
player.removeAttribute("spell:runes");
player.removeAttribute("tablet-spell");
}
// Award XP
player.skills.addExperience(Skills.MAGIC, 130);
// Change book
player.dispatch(new SpellbookChangeEvent(
SpellBook.LUNAR,
book,
SpellbookChangeSource.SPELLBOOK_SWAP_CAST));
player.getSpellBookManager().setSpellBook(book); player.getSpellBookManager().setSpellBook(book);
player.getInterfaceManager().openTab(new Component(book.getInterfaceId())); player.getInterfaceManager().openTab(new Component(book.getInterfaceId()));
player.getPacketDispatch().sendMessage("You have 2 minutes before your spellbook changes back to the Lunar Spellbook!");
end(); end();
break; break;
} }
@@ -1,14 +1,15 @@
package content.global.skill.magic.ancient; package content.global.skill.magic.ancient;
import core.game.node.entity.player.Player; import core.game.event.SpellCastEvent;
import core.game.node.entity.player.link.diary.DiaryType;
import core.game.node.entity.combat.spell.MagicSpell;
import core.game.node.entity.combat.spell.Runes;
import core.game.node.Node; import core.game.node.Node;
import core.game.node.entity.Entity; import core.game.node.entity.Entity;
import core.game.node.entity.combat.spell.MagicSpell;
import core.game.node.entity.combat.spell.Runes;
import core.game.node.entity.combat.spell.SpellType; import core.game.node.entity.combat.spell.SpellType;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.SpellBookManager.SpellBook; import core.game.node.entity.player.link.SpellBookManager.SpellBook;
import core.game.node.entity.player.link.TeleportManager.TeleportType; import core.game.node.entity.player.link.TeleportManager.TeleportType;
import core.game.node.entity.player.link.diary.DiaryType;
import core.game.node.item.Item; import core.game.node.item.Item;
import core.game.world.GameWorld; import core.game.world.GameWorld;
import core.game.world.map.Location; import core.game.world.map.Location;
@@ -67,6 +68,7 @@ public final class AncientTeleportPlugin extends MagicSpell {
return false; return false;
} }
homeTeleport((Player) entity, Location.create(3087, 3495, 0)); homeTeleport((Player) entity, Location.create(3087, 3495, 0));
entity.dispatch(new SpellCastEvent(SpellBook.ANCIENT, getSpellId(), target));
} else if (entity.getTeleporter().send(location.transform(0, RandomFunction.random(3), 0), TeleportType.ANCIENT)) { } else if (entity.getTeleporter().send(location.transform(0, RandomFunction.random(3), 0), TeleportType.ANCIENT)) {
if (!super.meetsRequirements(entity, true, true)) { if (!super.meetsRequirements(entity, true, true)) {
entity.getTeleporter().getCurrentTeleport().stop(); entity.getTeleporter().getCurrentTeleport().stop();
@@ -24,12 +24,17 @@ import core.game.system.config.NPCConfigParser
import core.game.system.task.Pulse import core.game.system.task.Pulse
import core.game.system.timer.impl.PoisonImmunity import core.game.system.timer.impl.PoisonImmunity
import core.game.system.timer.impl.SkillRestore import core.game.system.timer.impl.SkillRestore
import core.game.system.timer.impl.SpellbookSwap
import core.game.world.map.Location import core.game.world.map.Location
import core.game.world.map.RegionManager import core.game.world.map.RegionManager
import core.game.world.repository.Repository import core.game.world.repository.Repository
import core.game.world.update.flag.context.Animation import core.game.world.update.flag.context.Animation
import core.tools.RandomFunction import core.tools.RandomFunction
import org.rs09.consts.* import org.rs09.consts.Animations
import org.rs09.consts.Components
import org.rs09.consts.Graphics
import org.rs09.consts.Items
import org.rs09.consts.Sounds
import kotlin.math.floor import kotlin.math.floor
class LunarListeners : SpellListener("lunar"), Commands { class LunarListeners : SpellListener("lunar"), Commands {
@@ -280,9 +285,14 @@ class LunarListeners : SpellListener("lunar"), Commands {
*/ */
// Level 96 // Level 96
/** onCast(Lunar.SPELLBOOK_SWAP, NONE) { player, _ ->
* Spellbook Swap requires(player, 96, arrayOf(
*/ Item(Items.LAW_RUNE_563, 1),
Item(Items.COSMIC_RUNE_564, 2),
Item(Items.ASTRAL_RUNE_9075, 3)
))
spellbookSwap(player)
}
} }
// Spell handlers // Spell handlers
@@ -783,11 +793,20 @@ class LunarListeners : SpellListener("lunar"), Commands {
/** /**
* Heal Group * Heal Group
*/ */
// Level 96 // Level 96
/** private fun spellbookSwap(player : Player) {
* Spellbook Swap // Runes are removed on successful swap to another spellbook in SpellbookSwapDialogue.java
*/ // removeRunes(player, true)
lock(player, 4)
visualizeSpell(player, 6299, 1062)
player.scripts.removeWeakScripts()
player.dialogueInterpreter.open(3264731)
registerTimer(player, SpellbookSwap())
// XP is awarded in SpellbookSwapDialogue.java similarly to rune removal handling
// addXP(player, 130.0)
setDelay(player, false)
}
// Other/Multi spell use-case // Other/Multi spell use-case
private fun sendTeleport(player: Player, xp: Double, loc: Location){ private fun sendTeleport(player: Player, xp: Double, loc: Location){
@@ -1,84 +0,0 @@
package content.global.skill.magic.lunar;
import core.game.component.Component;
import core.game.node.entity.combat.spell.MagicSpell;
import core.game.node.entity.combat.spell.Runes;
import core.game.node.Node;
import core.game.node.entity.Entity;
import core.game.node.entity.combat.spell.SpellType;
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.system.task.Pulse;
import core.game.world.GameWorld;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.plugin.Initializable;
import core.plugin.Plugin;
import core.tools.RandomFunction;
/**
* The spellbook swap spell.
* @author 'Vexia
*/
@Initializable
public class SpellbookSwapSpell extends MagicSpell {
/**
* Represents the animation of this spell.
*/
private final Animation ANIMATION = new Animation(6299);
/**
* Represents the graphics of this spell.
*/
private final Graphics GRAPHIC = new Graphics(1062);
/**
* Constructs a new {@code SpellbookSwapSpell} {@code Object}.
*/
public SpellbookSwapSpell() {
super(SpellBook.LUNAR, 96, 130, null, null, null, new Item[] { new Item(Runes.LAW_RUNE.getId(), 1), new Item(Runes.COSMIC_RUNE.getId(), 2), new Item(Runes.ASTRAL_RUNE.getId(), 3) });
}
@Override
public Plugin<SpellType> newInstance(SpellType arg) throws Throwable {
SpellBook.LUNAR.register(12, this);
return this;
}
@Override
public boolean cast(Entity entity, Node target) {
final Player player = (Player) entity;
if (!super.meetsRequirements(player, true, true)) {
return false;
}
player.lock(9);
player.animate(ANIMATION);
player.graphics(GRAPHIC);
player.getDialogueInterpreter().open(3264731);
final int id = RandomFunction.random(1, 500000);
player.setAttribute("spell:swap", id);
GameWorld.getPulser().submit(new Pulse(20, player) {
@Override
public boolean pulse() {
if (player.getAttribute("spell:swap", 0) == id) {
removeTemporarySpell(player);
}
return true;
}
});
return true;
}
/**
* Method used to remove the temp spell swap.
* @param player the player.
*/
public static void removeTemporarySpell(final Player player) {
player.removeAttribute("spell:swap");
player.getSpellBookManager().setSpellBook(SpellBook.LUNAR);
player.getInterfaceManager().openTab(new Component(SpellBook.LUNAR.getInterfaceId()));
}
}
@@ -1,23 +1,29 @@
package content.global.skill.skillcapeperks package content.global.skill.skillcapeperks
import core.game.component.Component import content.data.Quests
import core.game.node.entity.player.Player import content.global.skill.farming.PatchType
import core.game.node.entity.player.link.SpellBookManager import content.global.skill.farming.Plantable
import core.game.node.entity.player.link.TeleportManager
import content.global.skill.runecrafting.Altar import content.global.skill.runecrafting.Altar
import core.game.world.map.Location
import core.game.world.map.zone.impl.DarkZone
import core.plugin.Initializable
import core.game.world.GameWorld
import content.global.skill.farming.*
import core.ServerStore import core.ServerStore
import core.ServerStore.Companion.getBoolean import core.ServerStore.Companion.getBoolean
import core.ServerStore.Companion.getInt import core.ServerStore.Companion.getInt
import core.api.* import core.api.closeDialogue
import core.api.getAttribute
import core.api.hasRequirement
import core.api.sendDialogue
import core.api.sendMessage
import core.api.teleport
import core.cache.def.impl.ItemDefinition import core.cache.def.impl.ItemDefinition
import core.tools.END_DIALOGUE import core.game.component.Component
import org.rs09.consts.Items import core.game.event.SpellbookChangeEvent
import content.data.Quests import core.game.node.entity.combat.equipment.WeaponInterface
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.SpellBookManager
import core.game.node.entity.player.link.TeleportManager
import core.game.world.GameWorld
import core.game.world.map.Location
import core.game.world.map.zone.impl.DarkZone
import core.plugin.Initializable
enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)? = null) { enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)? = null) {
BAREFISTED_SMITHING("cape_perks:barefisted-smithing"), BAREFISTED_SMITHING("cape_perks:barefisted-smithing"),
@@ -157,70 +163,74 @@ enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)?
} }
@Initializable @Initializable
class MagicCapeDialogue(player: Player? = null): core.game.dialogue.DialoguePlugin(player){ class MagicCapeDialogue(player : Player? = null) : core.game.dialogue.DialoguePlugin(player) {
override fun newInstance(player: Player?): core.game.dialogue.DialoguePlugin { override fun newInstance(player : Player?) : core.game.dialogue.DialoguePlugin {
return MagicCapeDialogue(player) return MagicCapeDialogue(player)
} }
override fun open(vararg args: Any?): Boolean { override fun open(vararg args : Any?) : Boolean {
when(player.spellBookManager.spellBook){ when (player.spellBookManager.spellBook) {
SpellBookManager.SpellBook.ANCIENT.interfaceId -> options("Modern","Lunar") SpellBookManager.SpellBook.ANCIENT.interfaceId -> options("Modern", "Lunar")
SpellBookManager.SpellBook.MODERN.interfaceId -> options("Ancient","Lunar") SpellBookManager.SpellBook.MODERN.interfaceId -> options("Ancient", "Lunar")
SpellBookManager.SpellBook.LUNAR.interfaceId -> options ("Modern","Ancient") SpellBookManager.SpellBook.LUNAR.interfaceId -> options("Modern", "Ancient")
} }
return true return true
} }
override fun handle(interfaceId: Int, buttonId: Int): Boolean { override fun handle(interfaceId : Int, buttonId : Int) : Boolean {
val spellbook = when(player.spellBookManager.spellBook){ val spellbook = when (player.spellBookManager.spellBook) {
SpellBookManager.SpellBook.ANCIENT.interfaceId -> { SpellBookManager.SpellBook.ANCIENT.interfaceId -> {
when(buttonId){ when (buttonId) {
1 -> SpellBookManager.SpellBook.MODERN 1 -> SpellBookManager.SpellBook.MODERN
2 -> SpellBookManager.SpellBook.LUNAR 2 -> SpellBookManager.SpellBook.LUNAR
else -> null else -> null
} }
} }
SpellBookManager.SpellBook.MODERN.interfaceId -> { SpellBookManager.SpellBook.MODERN.interfaceId -> {
when(buttonId){ when (buttonId) {
1 -> SpellBookManager.SpellBook.ANCIENT 1 -> SpellBookManager.SpellBook.ANCIENT
2 -> SpellBookManager.SpellBook.LUNAR 2 -> SpellBookManager.SpellBook.LUNAR
else -> null else -> null
} }
} }
SpellBookManager.SpellBook.LUNAR.interfaceId -> { SpellBookManager.SpellBook.LUNAR.interfaceId -> {
when(buttonId){ when (buttonId) {
1 -> SpellBookManager.SpellBook.MODERN 1 -> SpellBookManager.SpellBook.MODERN
2 -> SpellBookManager.SpellBook.ANCIENT 2 -> SpellBookManager.SpellBook.ANCIENT
else -> null else -> null
} }
} }
else -> null else -> null
} }
end() end()
if(spellbook != null){ if (spellbook != null) {
if (spellbook == SpellBookManager.SpellBook.ANCIENT) { if (spellbook == SpellBookManager.SpellBook.ANCIENT) {
if (!hasRequirement(player, Quests.DESERT_TREASURE)) if (!hasRequirement(player, Quests.DESERT_TREASURE)) return true
return true } else if (spellbook == SpellBookManager.SpellBook.LUNAR) {
} if (!hasRequirement(player, Quests.LUNAR_DIPLOMACY)) return true
else if (spellbook == SpellBookManager.SpellBook.LUNAR) { }
if (!hasRequirement(player, Quests.LUNAR_DIPLOMACY)) val weaponInterface = player.getExtension<WeaponInterface>(WeaponInterface::class.java)
return true if (weaponInterface != null && player.properties.autocastSpell != null) {
} weaponInterface.selectAutoSpell(-1, true)
player.spellBookManager.setSpellBook(spellbook) }
player.interfaceManager.openTab(Component(spellbook.interfaceId)) player.dispatch(SpellbookChangeEvent(
player.incrementAttribute("/save:cape_perks:librarian-magus-charges",-1) SpellBookManager.SpellBook.forInterface(player.spellBookManager.spellBook),
} spellbook,
return true SpellBookManager.SpellbookChangeSource.MAGIC_CAPE_PERK))
} player.spellBookManager.setSpellBook(spellbook)
player.interfaceManager.openTab(Component(spellbook.interfaceId))
override fun getIds(): IntArray { player.incrementAttribute("/save:cape_perks:librarian-magus-charges", -1)
return intArrayOf(509871234) }
} return true
}
override fun getIds() : IntArray {
return intArrayOf(509871234)
}
} }
@Initializable @Initializable
+1
View File
@@ -23,6 +23,7 @@ object Event {
@JvmStatic val AttributeSet = AttributeSetEvent::class.java @JvmStatic val AttributeSet = AttributeSetEvent::class.java
@JvmStatic val AttributeRemoved = AttributeRemoveEvent::class.java @JvmStatic val AttributeRemoved = AttributeRemoveEvent::class.java
@JvmStatic val SpellCast = SpellCastEvent::class.java @JvmStatic val SpellCast = SpellCastEvent::class.java
@JvmStatic val SpellbookChanged = SpellbookChangeEvent::class.java
@JvmStatic val ItemAlchemized = ItemAlchemizationEvent::class.java @JvmStatic val ItemAlchemized = ItemAlchemizationEvent::class.java
@JvmStatic val ItemEquipped = ItemEquipEvent::class.java @JvmStatic val ItemEquipped = ItemEquipEvent::class.java
@JvmStatic val ItemUnequipped = ItemUnequipEvent::class.java @JvmStatic val ItemUnequipped = ItemUnequipEvent::class.java
@@ -6,6 +6,7 @@ import core.game.node.Node
import core.game.node.entity.Entity import core.game.node.entity.Entity
import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPC
import core.game.node.entity.player.link.SpellBookManager.SpellBook import core.game.node.entity.player.link.SpellBookManager.SpellBook
import core.game.node.entity.player.link.SpellBookManager.SpellbookChangeSource
import core.game.node.entity.player.link.TeleportManager.TeleportType import core.game.node.entity.player.link.TeleportManager.TeleportType
import core.game.node.entity.player.link.prayer.PrayerType import core.game.node.entity.player.link.prayer.PrayerType
import core.game.node.item.Item import core.game.node.item.Item
@@ -34,6 +35,7 @@ data class InterfaceCloseEvent(val component: Component) : Event
data class AttributeSetEvent(val entity: Entity, val attribute: String, val value: Any) : Event data class AttributeSetEvent(val entity: Entity, val attribute: String, val value: Any) : Event
data class AttributeRemoveEvent(val entity: Entity, val attribute: String) : Event data class AttributeRemoveEvent(val entity: Entity, val attribute: String) : Event
data class SpellCastEvent(val spellBook: SpellBook, val spellId: Int, val target: Node? = null) : Event data class SpellCastEvent(val spellBook: SpellBook, val spellId: Int, val target: Node? = null) : Event
data class SpellbookChangeEvent(val oldSpellBook: SpellBook, val newSpellBook: SpellBook, val source: SpellbookChangeSource) : Event
data class ItemAlchemizationEvent(val itemId: Int, val isHigh: Boolean) : Event data class ItemAlchemizationEvent(val itemId: Int, val isHigh: Boolean) : Event
data class ItemEquipEvent(val itemId: Int, val slotId: Int) : Event data class ItemEquipEvent(val itemId: Int, val slotId: Int) : Event
data class ItemUnequipEvent(val itemId: Int, val slotId: Int) : Event data class ItemUnequipEvent(val itemId: Int, val slotId: Int) : Event
@@ -1,8 +1,6 @@
package core.game.node.entity.combat.spell; package core.game.node.entity.combat.spell;
import core.game.component.Component;
import core.game.event.SpellCastEvent; import core.game.event.SpellCastEvent;
import core.game.node.entity.skill.Skills;
import core.game.node.Node; import core.game.node.Node;
import core.game.node.entity.Entity; import core.game.node.entity.Entity;
import core.game.node.entity.combat.CombatSwingHandler; import core.game.node.entity.combat.CombatSwingHandler;
@@ -10,6 +8,7 @@ import core.game.node.entity.combat.equipment.WeaponInterface;
import core.game.node.entity.player.Player; import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.SpellBookManager.SpellBook; import core.game.node.entity.player.link.SpellBookManager.SpellBook;
import core.game.node.entity.player.link.audio.Audio; import core.game.node.entity.player.link.audio.Audio;
import core.game.node.entity.skill.Skills;
import core.game.node.item.Item; import core.game.node.item.Item;
import core.game.world.GameWorld; import core.game.world.GameWorld;
import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Animation;
@@ -121,11 +120,6 @@ public abstract class MagicSpell implements Plugin<SpellType> {
p.faceTemporary((Entity) target, 1); p.faceTemporary((Entity) target, 1);
} }
if (spell.cast(p, target)) { if (spell.cast(p, target)) {
if (book != SpellBook.LUNAR && p.getAttribute("spell:swap", 0) != 0) {
p.removeAttribute("spell:swap");
p.getSpellBookManager().setSpellBook(SpellBook.LUNAR);
p.getInterfaceManager().openTab(new Component(SpellBook.LUNAR.getInterfaceId()));
}
if (!combatSpell) { if (!combatSpell) {
p.getSkills().addExperience(Skills.MAGIC, spell.getExperience(p), true); p.getSkills().addExperience(Skills.MAGIC, spell.getExperience(p), true);
} }
@@ -55,6 +55,16 @@ public final class SpellBookManager {
return spellBook; return spellBook;
} }
/**
* All the possible ways a SpellBook can get changed.
*/
public enum SpellbookChangeSource {
ALTAR,
MAGIC_CAPE_PERK,
SPELLBOOK_SWAP_CAST,
SPELLBOOK_SWAP_RESTORE
}
/** /**
* Represents a characters spell book. * Represents a characters spell book.
* @author 'Vexia * @author 'Vexia
@@ -134,5 +144,4 @@ public final class SpellBookManager {
return spells.get(buttonId); return spells.get(buttonId);
} }
} }
} }
@@ -0,0 +1,99 @@
package core.game.system.timer.impl
import core.api.Event.DialogueClosed
import core.api.Event.SpellCast
import core.api.Event.SpellbookChanged
import core.api.clearLogoutListener
import core.api.registerLogoutListener
import core.api.removeTimer
import core.game.event.*
import core.game.node.entity.Entity
import core.game.node.entity.combat.equipment.WeaponInterface
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.SpellBookManager.SpellBook
import core.game.node.entity.player.link.SpellBookManager.SpellbookChangeSource
import core.game.system.timer.PersistTimer
import core.tools.minutesToTicks
/**
* A timer dedicated to the handling of the Lunar spell Spellbook Swap.
*/
class SpellbookSwap : PersistTimer(runInterval = minutesToTicks(2), identifier = "spellbook:swap") {
private val spellCastHook = object : EventHook<SpellCastEvent> {
override fun process(entity : Entity, event : SpellCastEvent) {
if (event.spellBook == SpellBook.LUNAR && event.spellId == 12) return // Ignore spellbook swap itself
revertSpellbook(entity)
removeTimer<SpellbookSwap>(entity)
}
}
private val spellBookChangeHook = object : EventHook<SpellbookChangeEvent> {
override fun process(entity : Entity, event : SpellbookChangeEvent) {
if (event.source != SpellbookChangeSource.SPELLBOOK_SWAP_CAST
&& event.source != SpellbookChangeSource.SPELLBOOK_SWAP_RESTORE) {
removeTimer<SpellbookSwap>(entity)
}
}
}
private val dialogueCloseHook = object : EventHook<DialogueCloseEvent> {
override fun process(entity : Entity, event : DialogueCloseEvent) {
if (event.dialogue?.getIds()?.contains(3264731) != true) return // Spellbook swap selection dialogue
if (entity !is Player) return
if (SpellBook.forInterface(entity.spellBookManager.spellBook) == SpellBook.LUNAR) {
entity.removeAttribute("spell:runes")
removeTimer<SpellbookSwap>(entity)
}
}
}
override fun run(entity : Entity) : Boolean {
revertSpellbook(entity)
return false
}
override fun onRegister(entity : Entity) {
entity.hook(SpellCast, spellCastHook)
entity.hook(SpellbookChanged, spellBookChangeHook)
entity.hook(DialogueClosed, dialogueCloseHook)
if (entity is Player) {
registerLogoutListener(entity, "spellbook:swap") { player ->
revertSpellbook(player)
}
}
}
override fun onRemoval(entity : Entity) {
entity.unhook(spellCastHook)
entity.unhook(spellBookChangeHook)
entity.unhook(dialogueCloseHook)
if (entity is Player) {
clearLogoutListener(entity, "spellbook:swap")
}
}
private fun revertSpellbook(entity : Entity) {
if (entity !is Player) return
if (SpellBook.forInterface(entity.spellBookManager.spellBook) == SpellBook.LUNAR) return
// Clear autocast
val weaponInterface = entity.getExtension<WeaponInterface>(WeaponInterface::class.java)
if (weaponInterface != null && entity.properties.autocastSpell != null) {
weaponInterface.selectAutoSpell(-1, true)
}
// Close autocast selection interface if it's still open
if (entity.getAttribute("autocast_select", false)) {
entity.removeAttribute("autocast_select")
entity.removeAttribute("autocast_component")
if (weaponInterface != null) {
entity.interfaceManager.openTab(weaponInterface)
}
}
entity.dispatch(SpellbookChangeEvent(
SpellBook.forInterface(entity.spellBookManager.spellBook),
SpellBook.LUNAR,
SpellbookChangeSource.SPELLBOOK_SWAP_RESTORE))
entity.spellBookManager.setSpellBook(SpellBook.LUNAR)
entity.spellBookManager.update(entity)
}
}
@@ -1,62 +1,61 @@
package core.net.packet package core.net.packet
import core.game.event.ButtonClickEvent import content.global.ame.events.maze.MazeInterface
import content.global.handlers.iface.ge.StockMarket
import content.global.skill.magic.SpellListener
import content.global.skill.magic.SpellListeners
import content.global.skill.magic.SpellUtils
import content.global.skill.summoning.familiar.FamiliarSpecial
import core.ServerConstants
import core.api.getAttribute import core.api.getAttribute
import core.api.log
import core.api.sendMessage import core.api.sendMessage
import core.api.tryPop import core.api.tryPop
import core.api.utils.Vector
import core.cache.def.impl.ItemDefinition import core.cache.def.impl.ItemDefinition
import core.cache.def.impl.NPCDefinition import core.cache.def.impl.NPCDefinition
import core.cache.def.impl.SceneryDefinition import core.cache.def.impl.SceneryDefinition
import core.game.container.Container import core.game.container.Container
import core.game.container.impl.BankContainer import core.game.container.impl.BankContainer
import core.game.node.Node import core.game.event.ButtonClickEvent
import core.game.node.entity.player.Player
import core.game.node.entity.player.info.Rights
import core.game.node.entity.player.info.login.LoginConfiguration
import core.game.node.entity.player.link.SpellBookManager
import core.game.node.entity.combat.spell.MagicSpell
import content.global.ame.events.maze.MazeInterface
import content.global.skill.summoning.familiar.FamiliarSpecial
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import core.game.node.scenery.Scenery
import core.game.system.communication.ClanRank
import core.game.system.communication.CommunicationInfo
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.update.flag.context.ChatMessage
import core.game.world.update.flag.*
import core.net.amsc.MSPacketRepository
import core.net.packet.context.PlayerContext
import core.net.packet.out.ClearMinimapFlag
import org.rs09.consts.Components
import proto.management.ClanMessage
import proto.management.JoinClanRequest
import proto.management.LeaveClanRequest
import core.ServerConstants
import core.game.ge.GrandExchange.Companion.getOfferStats import core.game.ge.GrandExchange.Companion.getOfferStats
import core.game.ge.GrandExchange.Companion.getRecommendedPrice import core.game.ge.GrandExchange.Companion.getRecommendedPrice
import core.game.ge.GrandExchangeOffer import core.game.ge.GrandExchangeOffer
import core.game.ge.PriceIndex import core.game.ge.PriceIndex
import content.global.handlers.iface.ge.StockMarket
import content.global.skill.magic.SpellListener
import content.global.skill.magic.SpellListeners
import content.global.skill.magic.SpellUtils
import core.api.log
import core.game.interaction.* import core.game.interaction.*
import core.game.node.Node
import core.game.node.entity.combat.spell.MagicSpell
import core.game.node.entity.player.Player
import core.game.node.entity.player.info.LogType import core.game.node.entity.player.info.LogType
import core.game.node.entity.player.info.PlayerMonitor import core.game.node.entity.player.info.PlayerMonitor
import core.tools.SystemLogger import core.game.node.entity.player.info.Rights
import core.game.node.entity.player.info.login.LoginConfiguration
import core.game.node.entity.player.link.SpellBookManager
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import core.game.node.scenery.Scenery
import core.game.system.command.CommandSystem import core.game.system.command.CommandSystem
import core.game.system.communication.ClanRank
import core.game.system.communication.CommunicationInfo
import core.game.system.communication.GlobalChat import core.game.system.communication.GlobalChat
import core.game.system.task.Pulse
import core.game.world.GameWorld import core.game.world.GameWorld
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.repository.Repository import core.game.world.repository.Repository
import core.game.world.update.flag.EntityFlag
import core.game.world.update.flag.context.ChatMessage
import core.net.amsc.MSPacketRepository
import core.net.packet.context.PlayerContext
import core.net.packet.`in`.Packet import core.net.packet.`in`.Packet
import core.net.packet.`in`.RunScript import core.net.packet.`in`.RunScript
import core.net.packet.out.ClearMinimapFlag
import core.tools.Log import core.tools.Log
import core.worker.ManagementEvents import core.worker.ManagementEvents
import core.api.utils.Vector import org.rs09.consts.Components
import proto.management.ClanMessage
import proto.management.JoinClanRequest
import proto.management.LeaveClanRequest
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import java.lang.Math.min import java.lang.Math.min
@@ -423,6 +422,11 @@ object PacketProcessor {
return return
if (player.getAttribute("magic:delay", -1) > GameWorld.ticks) if (player.getAttribute("magic:delay", -1) > GameWorld.ticks)
return return
// Validate spell packet interface matches player's actual spellbook
// This is to prevent player being able to cast spells when client has stale interface (e.g. after Spellbook swap reverts)
if (iface in intArrayOf(192, 193, 430)) {
if (!SpellUtils.validateSpellbookInterface(player, iface)) return
}
val book = SpellUtils.getBookFromInterface(iface) val book = SpellUtils.getBookFromInterface(iface)
if (book != "none") if (book != "none")
SpellListeners.run(child, type, book, player, target) SpellListeners.run(child, type, book, player, target)