Merge branch 2009scape:master into master

This commit is contained in:
Tooze 2026-04-19 15:44:25 +00:00
commit 275cda0dcf
39 changed files with 813 additions and 463 deletions

View file

@ -1,31 +1,37 @@
package content.global.bots
import content.global.skill.crafting.spinning.SpinningItem
import content.global.skill.crafting.spinning.SpinningPulse
import core.game.bots.*
import core.game.interaction.DestinationFlag
import core.game.interaction.MovementPulse
import core.game.node.entity.skill.Skills
import content.global.skill.crafting.spinning.SpinningItem
import content.global.skill.crafting.spinning.SpinningPulse
import core.game.node.item.Item
import core.game.world.map.Location
import core.game.world.map.path.Pathfinder
import org.rs09.consts.Items
import core.game.bots.SkillingBotAssembler
import core.game.bots.Script
@PlayerCompatible
@ScriptName("Seers' Village Flax Spinning")
@ScriptDescription("Pick flax and spin it into string. Start in the flax field near Seers' Village.")
@ScriptIdentifier("seers_flax")
class SeersFlax : Script(){
val waypoint1 = Location(2730, 3460, 0) // South of Seers' Village
val waypoint2 = Location(2737, 3441, 0) // Outside flax field
var state = State.PICKING
var stage = 0
var doorOpen = false
override fun tick() {
when(state){
State.PICKING -> {
val flax = scriptAPI.getNearestNode(2646,true)
flax?.interaction?.handle(bot,flax.interaction[1])
if(bot.inventory.getAmount(Items.FLAX_1779) > 25){
if (bot.inventory.freeSlots() == 0) {
state = State.TO_SPINNER
return
}
val flax = scriptAPI.getNearestNode("Flax", true)
scriptAPI.interact(bot, flax, "Pick")
}
State.TO_SPINNER -> {
@ -101,14 +107,13 @@ class SeersFlax : Script(){
}
State.RETURN_TO_FLAX -> {
if(bot.location == Location.create(2756, 3478, 0))
Pathfinder.find(bot,Location.create(2726, 3486, 0)).walk(bot)
if(stage == 0)
Pathfinder.find(bot,Location.create(2726, 3486, 0)).walk(bot).also { stage++ }
when(bot.location){
Location.create(2726, 3486, 0) -> Pathfinder.find(bot,Location.create(2729, 3469, 0)).walk(bot)
Location.create(2729, 3469, 0) -> Pathfinder.find(bot,Location.create(2734, 3447, 0)).walk(bot)
Location.create(2734, 3447, 0) -> state = State.PICKING.also { stage = 0 }
val flax = scriptAPI.getNearestNode("Flax", true)
if (flax != null) {
state = State.PICKING
} else if (bot.location.withinMaxnormDistance(waypoint1, 5)) {
Pathfinder.find(bot, waypoint2).walk(bot)
} else {
Pathfinder.find(bot, waypoint1).walk(bot)
}
}
@ -124,7 +129,6 @@ class SeersFlax : Script(){
State.TELE_CAMELOT -> {
scriptAPI.teleport(Location.create(2756, 3478, 0))
stage = 0
state = State.RETURN_TO_FLAX
}

View file

@ -30,6 +30,17 @@ public final class AutocastSelectPlugin extends ComponentPlugin {
if (!player.getAttribute("autocast_select", false)) {
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");
final WeaponInterface w = player.getExtension(WeaponInterface.class);
if (w != null) {

View file

@ -50,15 +50,14 @@ class EmptyOptionListener : InteractionListener {
POTION(Items.POTION_195, Items.VIAL_229, "You empty the vial.", Sounds.LIQUID_2401),
BURNT_STEW(Items.BURNT_STEW_2005, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401),
NETTLE_TEA(Items.NETTLE_TEA_4239, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401),
CUP_OF_TEA(Items.CUP_OF_TEA_4242, Items.EMPTY_CUP_1980, "You empty the cup of tea.", Sounds.LIQUID_2401),
NETTLE_WATER(Items.NETTLE_WATER_4237, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401),
NETTLE_TEA_MILKY(Items.NETTLE_TEA_4240, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401),
NETTLE_TEA_CUP(Items.CUP_OF_TEA_4242, Items.EMPTY_CUP_1980, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
NETTLE_TEA_CUP(Items.CUP_OF_TEA_4242, Items.EMPTY_CUP_1980, "You empty the cup of tea.", Sounds.LIQUID_2401),
NETTLE_TEA_CUP_MILKY(Items.CUP_OF_TEA_4243, Items.EMPTY_CUP_1980, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
TEA_CUP_WATER(Items.CUP_OF_WATER_4458, Items.EMPTY_CUP_1980, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
TEA_CUP_HOT_WATER(Items.CUP_OF_HOT_WATER_4460, Items.EMPTY_CUP_1980, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
NETTLE_TEA_PORCELAIN(Items.CUP_OF_TEA_4245, Items.PORCELAIN_CUP_4244, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
NETTLE_TEA_PORCELAIN_MILKY(Items.CUP_OF_TEA_4246, Items.PORCELAIN_CUP_4244, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
NETTLE_WATER(Items.NETTLE_WATER_4237, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401),
NETTLE_TEA_MILKY(Items.NETTLE_TEA_4240, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401),
TEA_CUP_WATER(Items.CUP_OF_WATER_4458, Items.EMPTY_CUP_1980, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
TEA_CUP_HOT_WATER(Items.CUP_OF_HOT_WATER_4460, Items.EMPTY_CUP_1980, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
BUILDERS_TEA1(Items.CUP_OF_TEA_7730, Items.EMPTY_CUP_7728, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
BUILDERS_TEA1_MILKY(Items.CUP_OF_TEA_7731, Items.EMPTY_CUP_7728, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),
BUILDERS_TEA2(Items.CUP_OF_TEA_7733, Items.PORCELAIN_CUP_7732, "You empty the contents of the cup onto the floor.", Sounds.LIQUID_2401),

View file

@ -7,6 +7,7 @@ import core.api.utils.WeightBasedTable
import core.api.utils.WeightedItem
import core.game.interaction.InteractionListener
import core.game.interaction.IntType
import content.global.activity.ttrail.TreasureTrailManager
class ImplingJarListener : InteractionListener {
@ -15,8 +16,14 @@ class ImplingJarListener : InteractionListener {
override fun defineListeners() {
on(JARS, IntType.ITEM, "loot"){ player, node ->
val jar = node.asItem()
val table = ImplingLoot.forId(jar.id) ?: return@on false
val effectiveTable = if (TreasureTrailManager.getInstance(player).hasClue()) {
table.excluding(*WeightBasedTable.CLUE_SCROLL_SLOTS)
} else {
table
}
val loot = ImplingLoot.forId(jar.id)?.roll()?.firstOrNull() ?: return@on false
val loot = effectiveTable.roll().firstOrNull() ?: return@on false
if(removeItem(player, jar, Container.INVENTORY)) {
addItemOrDrop(player, loot.id, loot.amount)

View file

@ -0,0 +1,32 @@
package content.global.handlers.item
import content.data.LightSource
import core.api.*
import core.game.interaction.InteractionListener
import core.game.interaction.IntType
import org.rs09.consts.Items
import kotlin.collections.toIntArray
class LightSourceListeners : InteractionListener {
override fun defineListeners() {
val lightSourceProducts = LightSource.values().map { it.product.id }.toIntArray()
on(lightSourceProducts, ITEM, "drop") { player, light ->
val active = LightSource.getActiveLightSource(player).product.id
if (player.location.isInRegion(10648) && light.id == active) { //Temple of Ikov dungeon
sendMessage(player, "Dropping the " + LightSource.getActiveLightSource(player).product.name.lowercase() + " would leave you without a light source.")
return@on false
}
val removed = removeItem(player, light.id)
if (removed) {
var droppedId = light.id
if (light.id == Items.MINING_HELMET_5013) {
droppedId = Items.MINING_HELMET_5014
sendMessage(player, "The helmet goes out as you drop it.")
}
produceGroundItem(player, droppedId)
}
return@on true
}
}
}

View file

@ -1,17 +0,0 @@
package content.global.handlers.item
import core.api.*
import core.game.interaction.InteractionListener
import core.game.interaction.IntType
import org.rs09.consts.Items
class MiningHelmetListener : InteractionListener {
override fun defineListeners() {
on(Items.MINING_HELMET_5013, IntType.ITEM, "drop") { player, _ ->
val removed = removeItem(player, Items.MINING_HELMET_5013)
if (removed) produceGroundItem(player, Items.MINING_HELMET_5014)
sendMessage(player, "The helmet goes out as you drop it.")
return@on true
}
}
}

View file

@ -1,84 +0,0 @@
package content.global.handlers.item.withnpc
import core.api.removeItem
import core.api.sendChat
import core.api.sendMessage
import content.global.skill.prayer.Bones
import core.game.node.entity.npc.NPC
import core.game.node.item.Item
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import core.game.interaction.InteractionListener
import core.game.interaction.IntType
class BonesOnStrayDog : InteractionListener {
override fun defineListeners() {
val bones = Bones.array
val dogs = intArrayOf(
NPCs.STRAY_DOG_4766,
NPCs.STRAY_DOG_4767,
NPCs.STRAY_DOG_5917,
NPCs.STRAY_DOG_5918
)
onUseWith(IntType.NPC, bones, *dogs) { player, used, with ->
used as Item; with as NPC
var woof = "Woof"
if (removeItem(player, used)) {
sendMessage(
player,
"You feed the ${with.definition.name.lowercase()} your ${used.definition.name.lowercase()}."
)
when (used.id) {
Items.BURNT_BONES_528 -> {
sendMessage(
player,
"The dog looks at you, disappointed, but takes the bones anyway."
)
woof = "Woof..."
}
Items.FAYRG_BONES_4830,
Items.RAURG_BONES_4832,
Items.OURG_BONES_4834 -> {
sendMessage(
player,
"The dog looks at you, confused, but takes the bones anyway."
)
woof = "Woof..?"
}
Items.BABYDRAGON_BONES_534,
Items.BIG_BONES_532 -> {
sendMessage(
player,
"The dog seems to be very happy."
)
woof = "Woof!"
}
Items.WYVERN_BONES_6812,
Items.DRAGON_BONES_536 -> {
sendMessage(
player,
"The dog seems to be extremely overjoyed."
)
woof = "WOOF!"
}
}
sendChat(with, woof)
}
return@onUseWith true
}
}
}

View file

@ -0,0 +1,118 @@
package content.global.handlers.item.withnpc
import core.api.removeItem
import core.api.sendChat
import core.api.sendMessage
import content.global.skill.prayer.Bones
import core.game.node.entity.npc.NPC
import core.game.node.item.Item
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import core.game.interaction.InteractionListener
import core.game.interaction.IntType
import content.data.Meat
import content.data.MeatState
import core.api.animate
import core.game.world.update.flag.context.Animation
import org.rs09.consts.Animations
/**
* Represents the option Listener use-with on a stray dog.
* Use bones, raw & cooked meats
*/
class FeedOnStrayDog : InteractionListener {
override fun defineListeners() {
val bones = Bones.array
val consumableMeats = Meat.values()
.filter { it.state == MeatState.INEDIBLE_RAW || it.state == MeatState.EDIBLE_COOKED }
.map { it.id }
.toIntArray()
val allAllowedItems = bones + consumableMeats
val dogs = intArrayOf(
NPCs.STRAY_DOG_4766,
NPCs.STRAY_DOG_4767,
NPCs.STRAY_DOG_5917,
NPCs.STRAY_DOG_5918
)
onUseWith(IntType.NPC, allAllowedItems, *dogs) { player, used, with ->
used as Item; with as NPC
var woof = "Woof"
if (removeItem(player, used)) {
sendMessage(
player,
"You feed the ${with.definition.name.lowercase()} your ${used.definition.name.lowercase()}."
)
animate(player, Animations.HUMAN_BURYING_BONES_827)
when {
bones.contains(used.id) -> {
when (used.id) {
Items.BURNT_BONES_528 -> {
sendMessage(
player,
"The dog looks at you, disappointed, but takes the bones anyway."
)
woof = "Woof..."
}
Items.FAYRG_BONES_4830,
Items.RAURG_BONES_4832,
Items.OURG_BONES_4834 -> {
sendMessage(
player,
"The dog looks at you, confused, but takes the bones anyway."
)
woof = "Woof..?"
}
Items.BABYDRAGON_BONES_534,
Items.BIG_BONES_532 -> {
sendMessage(
player,
"The dog seems to be very happy."
)
woof = "Woof!"
}
Items.WYVERN_BONES_6812,
Items.DRAGON_BONES_536 -> {
sendMessage(
player,
"The dog seems to be extremely overjoyed."
)
woof = "WOOF!"
}
}
}
consumableMeats.contains(used.id) -> {
sendMessage(
player,
"The dog wagged its tail happily."
)
animate(with, Animation(4777))
woof = "Woof woof!"
}
}
sendChat(with, woof)
}
return@onUseWith true
}
}
}

View file

@ -0,0 +1,80 @@
package content.global.handlers.npc
import core.api.*
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import org.rs09.consts.NPCs
import core.game.node.entity.npc.NPC
import core.game.node.entity.npc.NPCBehavior
import core.game.world.update.flag.context.Animation
import core.game.node.entity.player.Player
import core.game.world.map.Direction
import core.game.world.map.Location
import org.rs09.consts.Animations
import org.rs09.consts.Sounds
private val strayDogsIds = intArrayOf(
NPCs.STRAY_DOG_4766,
NPCs.STRAY_DOG_4767,
NPCs.STRAY_DOG_5917,
NPCs.STRAY_DOG_5918
)
/**
* Represents the option Listener used to 'shoo-away' a stray dog.
* Convert from legacy plugin src/main/content/region/misthalin/varrock/handlers/ShooAwayStrayDogPlugin.java
* @author Vexia + beckrickert
*
*/
class ShooAwayStrayDogListener : NPCBehavior(*strayDogsIds), InteractionListener {
override fun defineListeners() {
on(strayDogsIds,IntType.NPC, "shoo-away") { player, node ->
val theDog = node as NPC
//Pause dog location
lock(theDog, 2)
stopWalk(theDog)
theDog.faceTemporary(player, 1)
sendChat(player,"Thbbbbt!")
animate(player, Animations.HUMAN_BLOW_RASPBERRY_2110)
//Dog whine sound
playAudio(player, Sounds.WOLF_DEATH_911)
sendChat(theDog,"Whine!")
//Dog whine kneeling down animation
animate(theDog, Animation(4774))
//run away from player
dogWhineRunAway(player, theDog)
return@on true
}
}
}
fun dogWhineRunAway(player: Player, theDog: NPC) {
val playerCurLoc = player.getLocation()
val dogCurLoc = theDog.getLocation()
//Get dog direction is facing from player's location
val dogDirection = Direction.getDirection(dogCurLoc, playerCurLoc)
val dogDirectionOpp = dogDirection.opposite
val xWalkLoc = dogCurLoc.x + (dogDirectionOpp.stepX * 12)
val yWalkLoc = dogCurLoc.y + (dogDirectionOpp.stepY * 12)
val dogWalkToNewLoc = Location(xWalkLoc, yWalkLoc, dogCurLoc.z)
//unlock entity movement so it can run away
unlock(theDog)
//allow to "moon-walk" to the new location
//Does weird stuff or ignores this when in 'following' mode
//theDog.face(player)
forceWalk(theDog, dogWalkToNewLoc, "dumb")
}

View file

@ -53,10 +53,23 @@ public final class RemovalDialogue extends DialoguePlugin {
@Override
public boolean open(Object... args) {
pos = (int[]) args[1];
plane = player.getLocation().getZ();
if (HouseManager.isInDungeon(player)) {
plane = 3;
}
if (args.length > 2 && args[2] instanceof Integer) {
plane = (Integer) args[2];
} else {
plane = player.getLocation().getZ();
if (HouseManager.isInDungeon(player)) {
plane = 3;
}
}
// check if room exists
room = player.getHouseManager().getRooms()[plane][pos[0]][pos[1]];
if (room == null || room.getProperties().isRoof()) {
interpreter.sendPlainMessage(false, "There is no room there to remove.");
return false;
}
room = player.getHouseManager().getRooms()[plane][pos[0]][pos[1]];
player.getDialogueInterpreter().sendOptions("Remove the " + (room != null ? room.getProperties().getName() : "room") + "?", "Yes", "No");
stage = 0;

View file

@ -29,15 +29,19 @@ public final class StaircasePlugin extends OptionHandler {
public Plugin<Object> newInstance(Object arg) throws Throwable {
ClassScanner.definePlugin(new BuildDialogue());
ClassScanner.definePlugin(new ClimbPohLadder());
for (int i = 13497; i < 13507; i++) {
// 13497 - 13502 are normal stairs
// 13503 - 13506 are spiral stairs
for (int i = 13497; i <= 13506; i++) {
SceneryDefinition.forId(i).getHandlers().put("option:climb", this);
SceneryDefinition.forId(i).getHandlers().put("option:climb-up", this);
SceneryDefinition.forId(i).getHandlers().put("option:climb-down", this);
SceneryDefinition.forId(i).getHandlers().put("option:remove-room", this);
}
// 13409 is dungeon entrance
SceneryDefinition.forId(13409).getHandlers().put("option:enter", this);
SceneryDefinition.forId(13409).getHandlers().put("option:remove-room", this);
for (int id = 13328; id < 13331; id++) {
// 13328 - 13330 are dungeon ladders
for (int id = 13328; id <= 13330; id++) {
SceneryDefinition.forId(id).getHandlers().put("option:climb", this);
SceneryDefinition.forId(id).getHandlers().put("option:remove-room", this);
}
@ -69,13 +73,47 @@ public final class StaircasePlugin extends OptionHandler {
SceneryBuilder.replace(object, object.transform(object.getId() - 3));
return true;
case "remove-room":
if (player.getLocation().getZ() != 0) {
player.getPacketDispatch().sendMessage("The room below is supporting this room!");
return true;
}
return false;
int currentZ = player.getLocation().getZ();
int chunkX = player.getLocation().getChunkX();
int chunkY = player.getLocation().getChunkY();
// must be in building mode
if(!player.getHouseManager().isBuildingMode()) {
player.getPacketDispatch().sendMessage("You can only do this in building mode.");
return true;
}
// if you're in the dungeon, try and remove the room at Z = 0 from the house region.
if (HouseManager.isInDungeon(player)) {
player.getDialogueInterpreter().open("con:remove", null, new int[]{chunkX, chunkY}, 0);
// From ground floor (z=0), stairs could lead to 1st floor (z=1) or the dungeon (z=3)
} else {
// garden dungeon entrance and oubliette ladders don't need a height selection
if(node.getId() == 13409 || node.getId() == 13675 || node.getId() == 13676 || node.getId() == 13677 || node.getId() == 13678 || node.getId() == 13679 || node.getId() == 13680) {
player.getDialogueInterpreter().open("con:remove", null, new int[]{chunkX, chunkY}, 3);
} else {
player.getDialogueInterpreter().sendOptions("Remove which room?", "Room Above", "Room Below", "Cancel");
player.getDialogueInterpreter().addAction((interId, btn) -> {
if (btn == 2) { // Above
player.getDialogueInterpreter().open("con:remove", null, new int[]{chunkX, chunkY}, currentZ + 1);
} else if (btn == 3) { // Below
if (currentZ - 1 == -1) {
player.getDialogueInterpreter().open("con:remove", null, new int[]{chunkX, chunkY}, 3);
} else {
player.getDialogueInterpreter().open("con:remove", null, new int[]{chunkX, chunkY}, currentZ - 1);
}
}
});
}
}
return true;
case "climb":
if (house.getDungeonRegion() == player.getViewport().getRegion()) {
if (HouseManager.isInDungeon(player)) {
climb(player, 1, house, object);
return true;
}
@ -379,10 +417,11 @@ public final class StaircasePlugin extends OptionHandler {
for (Direction d : dirs) {
if (d == room.getRotation()) {
r.setRotation(d);
Hotspot stairs = room.getStairs();
int index = stairs != null ? stairs.getDecorationIndex() : -1;
// todo this just puts a default set of stairs in the dungeon. The below code works for skill/quest hall, but not a dungeon entrance.
//Hotspot stairs = room.getStairs();
//int index = stairs != null ? stairs.getDecorationIndex() : -1;
BuildingUtils.buildRoom(player, r, plane, roomX, roomY, r.getExits(), true);
r.getStairs().setDecorationIndex(index);
r.getStairs().setDecorationIndex(/*index*/1);
end();
return true;
}

View file

@ -1,18 +0,0 @@
package content.global.skill.cooking
import org.rs09.consts.Items
import core.api.replaceSlot
import core.api.removeItem
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.node.item.Item
class NettleWaterListener : InteractionListener {
override fun defineListeners() {
onUseWith(IntType.ITEM, Items.BOWL_OF_WATER_1921, Items.NETTLES_4241) { player, used, with ->
replaceSlot(player, used.asItem().slot, Item(Items.NETTLE_WATER_4237), used.asItem())
removeItem(player, with.asItem())
return@onUseWith true
}
}
}

View file

@ -0,0 +1,13 @@
package content.global.skill.hunter.falconry
import core.game.node.Node
import core.game.world.map.zone.ZoneBorders
/**
* Used to define the zoneBorders for an MapArea listener
*/
object FalconryZone {
val piscatorisFalconryZones = arrayOf(
ZoneBorders(2364, 3573, 2394, 3620, 0)
)
}

View file

@ -0,0 +1,36 @@
package content.global.skill.hunter.falconry
import core.api.*
import core.api.MapArea
import core.game.activity.ActivityManager
import core.game.node.entity.Entity
import core.game.world.map.zone.ZoneBorders
import core.game.node.entity.player.Player
import org.rs09.consts.Items
class PiscFalconryListener: MapArea {
override fun defineAreaBorders(): Array<ZoneBorders> {
return FalconryZone.piscatorisFalconryZones
}
/**
* When player leaves by any method other than logout/disconnect.
*/
override fun areaLeave(entity: Entity, logout: Boolean) {
if (entity is Player) {
if (!logout) {
// Legacy compatibility of FalconryActivityPlugin.java
removeAttribute(entity, "falconry")
// Compatibility with StileShortcut.kt
ActivityManager.getActivity("falconry").leave(entity, false)
removeAll(entity, Items.FALCONERS_GLOVE_10023)
removeAll(entity, Items.FALCONERS_GLOVE_10023, Container.EQUIPMENT)
removeAll(entity, Items.FALCONERS_GLOVE_10024)
removeAll(entity, Items.FALCONERS_GLOVE_10024, Container.EQUIPMENT)
}
}
}
}

View file

@ -1,15 +1,23 @@
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.InteractionListener
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.link.SpellBookManager
import core.game.node.entity.player.link.SpellBookManager.SpellBook
import core.game.node.entity.skill.Skills
import org.rs09.consts.Scenery
import org.rs09.consts.Sounds
import content.data.Quests
class MagicAltarListener : InteractionListener {
override fun defineListeners() {
@ -36,26 +44,36 @@ class MagicAltarListener : InteractionListener {
return true
}
private fun swapSpellBook(player: Player, altar: Node) {
lock(player, 3)
playAudio(player, Sounds.PRAYER_RECHARGE_2674)
animate(player, 645)
if (altar.id == ANCIENT_ALTAR) {
player.skills.decrementPrayerPoints(player.skills.prayerPoints)
}
if (SpellBook.forInterface(player.spellBookManager.spellBook) == if (altar.id == ANCIENT_ALTAR) SpellBook.ANCIENT else SpellBook.LUNAR) {
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 {
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)
}
}
private fun swapSpellBook(player : Player, altar : Node) {
lock(player, 3)
playAudio(player, Sounds.PRAYER_RECHARGE_2674)
animate(player, 645)
if (altar.id == ANCIENT_ALTAR) {
player.skills.decrementPrayerPoints(player.skills.prayerPoints)
}
val weaponInterface = player.getExtension<WeaponInterface>(WeaponInterface::class.java)
if (weaponInterface != null && player.properties.autocastSpell != null) {
weaponInterface.selectAutoSpell(-1, true)
}
if (SpellBook.forInterface(player.spellBookManager.spellBook) == if (altar.id == ANCIENT_ALTAR) SpellBook.ANCIENT else SpellBook.LUNAR) {
player.dispatch(SpellbookChangeEvent(
SpellBook.forInterface(player.spellBookManager.spellBook),
SpellBook.MODERN,
SpellBookManager.SpellbookChangeSource.ALTAR))
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 {
private const val ANCIENT_ALTAR = Scenery.ALTAR_6552

View file

@ -1,13 +1,25 @@
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.MagicStaff
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
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 {
val weapon = p.equipment[3] ?: return false
val staff = MagicStaff.forId(rune) ?: return false

View file

@ -1,10 +1,21 @@
package content.global.skill.magic;
import content.data.Quests;
import core.game.component.Component;
import core.game.dialogue.DialoguePlugin;
import core.game.event.SpellbookChangeEvent;
import core.game.interaction.Listener;
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.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.
@ -73,8 +84,27 @@ public class SpellbookSwapDialogue extends DialoguePlugin {
break;
}
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.getInterfaceManager().openTab(new Component(book.getInterfaceId()));
player.getPacketDispatch().sendMessage("You have 2 minutes before your spellbook changes back to the Lunar Spellbook!");
end();
break;
}

View file

@ -1,14 +1,15 @@
package content.global.skill.magic.ancient;
import core.game.node.entity.player.Player;
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.event.SpellCastEvent;
import core.game.node.Node;
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.player.Player;
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.diary.DiaryType;
import core.game.node.item.Item;
import core.game.world.GameWorld;
import core.game.world.map.Location;
@ -67,6 +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));
} 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

@ -24,12 +24,17 @@ import core.game.system.config.NPCConfigParser
import core.game.system.task.Pulse
import core.game.system.timer.impl.PoisonImmunity
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.RegionManager
import core.game.world.repository.Repository
import core.game.world.update.flag.context.Animation
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
class LunarListeners : SpellListener("lunar"), Commands {
@ -280,9 +285,14 @@ class LunarListeners : SpellListener("lunar"), Commands {
*/
// Level 96
/**
* Spellbook Swap
*/
onCast(Lunar.SPELLBOOK_SWAP, NONE) { player, _ ->
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
@ -783,11 +793,20 @@ class LunarListeners : SpellListener("lunar"), Commands {
/**
* Heal Group
*/
// Level 96
/**
* Spellbook Swap
*/
// Level 96
private fun spellbookSwap(player : Player) {
// 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
private fun sendTeleport(player: Player, xp: Double, loc: Location){

View file

@ -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()));
}
}

View file

@ -1,23 +1,29 @@
package content.global.skill.skillcapeperks
import core.game.component.Component
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.SpellBookManager
import core.game.node.entity.player.link.TeleportManager
import content.data.Quests
import content.global.skill.farming.PatchType
import content.global.skill.farming.Plantable
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.Companion.getBoolean
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.tools.END_DIALOGUE
import org.rs09.consts.Items
import content.data.Quests
import core.game.component.Component
import core.game.event.SpellbookChangeEvent
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) {
BAREFISTED_SMITHING("cape_perks:barefisted-smithing"),
@ -157,70 +163,74 @@ enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)?
}
@Initializable
class MagicCapeDialogue(player: Player? = null): core.game.dialogue.DialoguePlugin(player){
override fun newInstance(player: Player?): core.game.dialogue.DialoguePlugin {
return MagicCapeDialogue(player)
}
class MagicCapeDialogue(player : Player? = null) : core.game.dialogue.DialoguePlugin(player) {
override fun newInstance(player : Player?) : core.game.dialogue.DialoguePlugin {
return MagicCapeDialogue(player)
}
override fun open(vararg args: Any?): Boolean {
when(player.spellBookManager.spellBook){
SpellBookManager.SpellBook.ANCIENT.interfaceId -> options("Modern","Lunar")
SpellBookManager.SpellBook.MODERN.interfaceId -> options("Ancient","Lunar")
SpellBookManager.SpellBook.LUNAR.interfaceId -> options ("Modern","Ancient")
}
return true
}
override fun open(vararg args : Any?) : Boolean {
when (player.spellBookManager.spellBook) {
SpellBookManager.SpellBook.ANCIENT.interfaceId -> options("Modern", "Lunar")
SpellBookManager.SpellBook.MODERN.interfaceId -> options("Ancient", "Lunar")
SpellBookManager.SpellBook.LUNAR.interfaceId -> options("Modern", "Ancient")
}
return true
}
override fun handle(interfaceId: Int, buttonId: Int): Boolean {
val spellbook = when(player.spellBookManager.spellBook){
SpellBookManager.SpellBook.ANCIENT.interfaceId -> {
when(buttonId){
1 -> SpellBookManager.SpellBook.MODERN
2 -> SpellBookManager.SpellBook.LUNAR
else -> null
}
}
override fun handle(interfaceId : Int, buttonId : Int) : Boolean {
val spellbook = when (player.spellBookManager.spellBook) {
SpellBookManager.SpellBook.ANCIENT.interfaceId -> {
when (buttonId) {
1 -> SpellBookManager.SpellBook.MODERN
2 -> SpellBookManager.SpellBook.LUNAR
else -> null
}
}
SpellBookManager.SpellBook.MODERN.interfaceId -> {
when(buttonId){
1 -> SpellBookManager.SpellBook.ANCIENT
2 -> SpellBookManager.SpellBook.LUNAR
else -> null
}
}
SpellBookManager.SpellBook.MODERN.interfaceId -> {
when (buttonId) {
1 -> SpellBookManager.SpellBook.ANCIENT
2 -> SpellBookManager.SpellBook.LUNAR
else -> null
}
}
SpellBookManager.SpellBook.LUNAR.interfaceId -> {
when(buttonId){
1 -> SpellBookManager.SpellBook.MODERN
2 -> SpellBookManager.SpellBook.ANCIENT
else -> null
}
}
SpellBookManager.SpellBook.LUNAR.interfaceId -> {
when (buttonId) {
1 -> SpellBookManager.SpellBook.MODERN
2 -> SpellBookManager.SpellBook.ANCIENT
else -> null
}
}
else -> null
}
else -> null
}
end()
if(spellbook != null){
if (spellbook == SpellBookManager.SpellBook.ANCIENT) {
if (!hasRequirement(player, Quests.DESERT_TREASURE))
return true
}
else if (spellbook == SpellBookManager.SpellBook.LUNAR) {
if (!hasRequirement(player, Quests.LUNAR_DIPLOMACY))
return true
}
player.spellBookManager.setSpellBook(spellbook)
player.interfaceManager.openTab(Component(spellbook.interfaceId))
player.incrementAttribute("/save:cape_perks:librarian-magus-charges",-1)
}
return true
}
override fun getIds(): IntArray {
return intArrayOf(509871234)
}
end()
if (spellbook != null) {
if (spellbook == SpellBookManager.SpellBook.ANCIENT) {
if (!hasRequirement(player, Quests.DESERT_TREASURE)) return true
} else if (spellbook == SpellBookManager.SpellBook.LUNAR) {
if (!hasRequirement(player, Quests.LUNAR_DIPLOMACY)) return true
}
val weaponInterface = player.getExtension<WeaponInterface>(WeaponInterface::class.java)
if (weaponInterface != null && player.properties.autocastSpell != null) {
weaponInterface.selectAutoSpell(-1, true)
}
player.dispatch(SpellbookChangeEvent(
SpellBookManager.SpellBook.forInterface(player.spellBookManager.spellBook),
spellbook,
SpellBookManager.SpellbookChangeSource.MAGIC_CAPE_PERK))
player.spellBookManager.setSpellBook(spellbook)
player.interfaceManager.openTab(Component(spellbook.interfaceId))
player.incrementAttribute("/save:cape_perks:librarian-magus-charges", -1)
}
return true
}
override fun getIds() : IntArray {
return intArrayOf(509871234)
}
}
@Initializable

View file

@ -14,6 +14,7 @@ import core.game.interaction.IntType
import core.game.node.entity.player.Player
import core.game.node.item.Item
import org.rs09.consts.Sounds
import content.global.activity.ttrail.TreasureTrailManager
class ThievingListeners : InteractionListener {
@ -66,7 +67,12 @@ class ThievingListeners : InteractionListener {
}
player.animator.animate(PICKPOCKET_ANIM)
val lootTable = pickpocketRoll(player, pickpocketData.low, pickpocketData.high, pickpocketData.table)
val effectiveTable = if (TreasureTrailManager.getInstance(player).hasClue()) {
pickpocketData.table.excluding(*WeightBasedTable.CLUE_SCROLL_SLOTS)
} else {
pickpocketData.table
}
val lootTable = pickpocketRoll(player, pickpocketData.low, pickpocketData.high, effectiveTable)
if(lootTable == null){
node.asNpc().face(player)
node.asNpc().animator.animate(NPC_ANIM)

View file

@ -146,7 +146,7 @@ public final class SpinolypNPC extends AbstractNPC {
super.impact(entity, victim, state);
if (super.getType() == CombatStyle.MAGIC && state.getEstimatedHit() > 0) {
victim.getSkills().decrementPrayerPoints(1);
} else {
} else if (super.getType() == CombatStyle.RANGE && state.getEstimatedHit() > 0) {
if (RandomFunction.random(20) == 5) {
applyPoison(victim, entity, 30);
}

View file

@ -276,8 +276,7 @@ class PlagueCityListeners : InteractionListener {
}
}
1 -> playerl(FacialExpression.NEUTRAL, "I'm a friend of Jethick's, I have come to return a book he borrowed.").also { stage++ }
// todo change this back after sendItemDialogue is fixed for having a single line before See #1885
2 -> npc(FacialExpression.FRIENDLY, "", "Oh... why didn't you say, come in then.", "").also { stage++ }
2 -> npc(FacialExpression.FRIENDLY, "Oh... why didn't you say, come in then.").also { stage++ }
3 -> sendItemDialogue(player!!, Items.BOOK_1509, "You hand the book to Ted as you enter.").also {
DoorActionHandler.handleAutowalkDoor(player, getScenery(2531, 3328, 0))
setQuestStage(player!!, Quests.PLAGUE_CITY, 9)

View file

@ -137,20 +137,6 @@ class TempleOfIkovListeners : InteractionListener {
teleport(player, Location.create(2649, 9804, 0))
}
// don't let light extinguish or cape take off
val lightSourceProducts = LightSource.values().map { it.product.id }.toIntArray()
on(lightSourceProducts, ITEM, "drop") { player, light ->
val active = LightSource.getActiveLightSource(player).product.id
if (player.location.isInRegion(10648) && light.id == active) {
sendMessage(player, "Dropping the " + LightSource.getActiveLightSource(player).product.name.lowercase() + " would leave you without a light source.")
return@on false
}
val removed = removeItem(player, light.id)
if (removed) produceGroundItem(player, light.id)
return@on true
}
// B: Attach lever, authentic if you log out, the lever is lost, and you have to do that bridge again
onUseWith(SCENERY, Items.LEVER_83, Scenery.LEVER_BRACKET_86) { player, used, with ->
removeItem(player, used)

View file

@ -22,11 +22,11 @@ class TribalTotem : Quest(Quests.TRIBAL_TOTEM,126,125,1,200,0,1,5){
val started = player?.questRepository?.getStage(Quests.TRIBAL_TOTEM)!! > 0
if(!started){
line(player,"I can start this quest by speaking to !!Kangai Mau?? in",line++)
line(player,"!!Shrimp & Parrot?? restaurant in Brimhaven.",line++)
line(player,"I can start this quest by speaking to !!Kangai Mau?? in !!the??",line++)
line(player,"!!Shrimp & Parrot?? restaurant in Brimhaven",line++)
line += 1
line(player,"To complete this quest I need:",line++)
line(player,"!!Level 21 Theiving??",line++, player?.skills?.getStaticLevel(Skills.THIEVING)!! >= 21)
line(player,"!!Level 21 Thieving??",line++, player?.skills?.getStaticLevel(Skills.THIEVING)!! >= 21)
}
else if(started && stage != 100){
if(stage >= 10){

View file

@ -420,7 +420,7 @@ public final class GertrudeDialogue extends DialoguePlugin {
* @return the item.
*/
public Item getKitten() {
return new Item(RandomFunction.random(1555, 1560));
return new Item(RandomFunction.random(1555, 1561));
}
@Override

View file

@ -1,43 +0,0 @@
package content.region.misthalin.varrock.handlers;
import core.cache.def.impl.NPCDefinition;
import core.game.interaction.OptionHandler;
import core.game.node.Node;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.world.update.flag.context.Animation;
import core.plugin.Initializable;
import core.plugin.Plugin;
/**
* Represents the option plugin used to shoo away a dog.
* @author 'Vexia
* @version 1.0
*/
@Initializable
public final class ShooAwayStrayDogPlugin extends OptionHandler {
/**
* Represents the animatio to use.
*/
private static final Animation ANIMATION = new Animation(2110);
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
NPCDefinition.forId(5917).getHandlers().put("option:shoo-away", this);
NPCDefinition.setOptionHandler("shoo-away", this);
return this;
}
@Override
public boolean handle(Player player, Node node, String option) {
player.sendChat("Thbbbbt!");
player.animate(ANIMATION);
NPC dog = (NPC) node;
dog.sendChat("Whine!");
dog.moveStep();
dog.getPulseManager().clear();
return true;
}
}

View file

@ -23,6 +23,7 @@ object Event {
@JvmStatic val AttributeSet = AttributeSetEvent::class.java
@JvmStatic val AttributeRemoved = AttributeRemoveEvent::class.java
@JvmStatic val SpellCast = SpellCastEvent::class.java
@JvmStatic val SpellbookChanged = SpellbookChangeEvent::class.java
@JvmStatic val ItemAlchemized = ItemAlchemizationEvent::class.java
@JvmStatic val ItemEquipped = ItemEquipEvent::class.java
@JvmStatic val ItemUnequipped = ItemUnequipEvent::class.java

View file

@ -182,6 +182,22 @@ open class WeightBasedTable : ArrayList<WeightedItem>() {
return this
}
fun excluding(vararg ids: Int): WeightBasedTable {
val idSet = ids.toHashSet()
val filtered = WeightBasedTable()
for (item in this) {
if (item.id !in idSet) {
filtered.add(item)
}
}
for (item in guaranteedItems) {
if (item.id !in idSet) {
filtered.add(item)
}
}
return filtered
}
companion object {
@JvmStatic
fun create(vararg items: WeightedItem): WeightBasedTable {
@ -197,6 +213,7 @@ open class WeightBasedTable : ArrayList<WeightedItem>() {
val SLOT_CLUE_EASY = Items.TOOLKIT_1
val SLOT_CLUE_MEDIUM = Items.ROTTEN_POTATO_5733
val SLOT_CLUE_HARD = Items.GRANITE_LOBSTER_POUCH_12070
val CLUE_SCROLL_SLOTS = intArrayOf(SLOT_CLUE_EASY, SLOT_CLUE_MEDIUM, SLOT_CLUE_HARD)
val SLOT_CELEDT = Items.NULL_799
val SLOT_USDT = Items.SACRED_CLAY_POUCH_CLASS_1_14422
val SLOT_HDT = Items.SACRED_CLAY_POUCH_CLASS_2_14424

View file

@ -348,7 +348,7 @@ public final class DialogueInterpreter {
ItemDefinition itemDef = ItemDefinition.forId(item.getId());
player.getPacketDispatch().sendItemOnInterface(item.getId(), item.getAmount(), interfaceId, 2);
player.getPacketDispatch().sendAngleOnInterface(interfaceId, 2, itemDef.getModelZoom() / 2, itemDef.getModelRotationX(), itemDef.getModelRotationY());
player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 45, 46);
player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 45, 45);
// Hide the second item which seems to be used for double items (child 1)
player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, true);
// Open the chatbox only after everything is set to avoid lag and flashing default strings (Line 1, Title)
@ -524,11 +524,12 @@ public final class DialogueInterpreter {
player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, true);
player.getPacketDispatch().sendInterfaceConfig(interfaceId, 2, false);
player.getPacketDispatch().sendAnimationInterface(expression, interfaceId, 2);
player.getPacketDispatch().sendItemOnInterface(-1, 1, interfaceId, 1);
if (npc) {
player.getPacketDispatch().sendItemOnInterface(-1, 1, interfaceId, 1);
player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 45, 45);
player.getPacketDispatch().sendItemOnInterface(-1, 1, interfaceId, 2);
player.getPacketDispatch().sendNpcOnInterface(npcId, interfaceId, 2);
player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 45, 45);
player.getPacketDispatch().sendAngleOnInterface(interfaceId, 2, 2150, 40, 1882);
player.getPacketDispatch().sendString(NPCDefinition.forId(npcId).getName(), interfaceId, 3);
} else {
player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 426, 45); // 423 is 47 * 9

View file

@ -6,6 +6,7 @@ import core.game.node.Node
import core.game.node.entity.Entity
import core.game.node.entity.npc.NPC
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.prayer.PrayerType
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 AttributeRemoveEvent(val entity: Entity, val attribute: String) : 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 ItemEquipEvent(val itemId: Int, val slotId: Int) : Event
data class ItemUnequipEvent(val itemId: Int, val slotId: Int) : Event

View file

@ -1,8 +1,6 @@
package core.game.node.entity.combat.spell;
import core.game.component.Component;
import core.game.event.SpellCastEvent;
import core.game.node.entity.skill.Skills;
import core.game.node.Node;
import core.game.node.entity.Entity;
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.link.SpellBookManager.SpellBook;
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.world.GameWorld;
import core.game.world.update.flag.context.Animation;
@ -121,11 +120,6 @@ public abstract class MagicSpell implements Plugin<SpellType> {
p.faceTemporary((Entity) target, 1);
}
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) {
p.getSkills().addExperience(Skills.MAGIC, spell.getExperience(p), true);
}

View file

@ -55,6 +55,16 @@ public final class SpellBookManager {
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.
* @author 'Vexia
@ -134,5 +144,4 @@ public final class SpellBookManager {
return spells.get(buttonId);
}
}
}

View file

@ -81,6 +81,33 @@ class SpawnCommandSet : CommandSet(Privilege.ADMIN){
player.inventory.add(item)
}
/**
* Spawns multiple items with the given ID
*/
define("items", usage = "::items <lt>start-id<gt> <lt>end-id<gt> [amount]", description = "Loops through item ids from 'start-id' to 'end-id' and spawns 'amount' of them in your inventory."){player,args ->
if (args.size < 3) {
reject(player,"You must specify start and end item ids.")
return@define
}
val start = args[1].toIntOrNull() ?: return@define
val end = args[2].toIntOrNull() ?: return@define
var amount = (args.getOrNull(3) ?: "1").toInt()
for (id in start..end) {
if (id > Cache.getItemDefinitionsSize()) {
reject(player,"Item ID '$id' out of range.")
return@define
}
val item = Item(id, amount)
val max = player.inventory.getMaximumAdd(item)
if (amount > max) {
amount = max
}
item.setAmount(amount)
player.inventory.add(item)
}
}
/**
* Spawn object with given ID at the player's location
*/

View file

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

View file

@ -257,13 +257,21 @@ public final class RegionPlane {
public void add(GroundItem item) {
Location l = item.getLocation();
RegionChunk c = getRegionChunk(l.getLocalX() / RegionChunk.SIZE, l.getLocalY() / RegionChunk.SIZE);
// This adds it to the chunk to be interactable with.
if (!c.getItems().add(item)) {
return;
}
GroundItem g = (GroundItem) item;
if (g.isPrivate()) {
if (g.getDropper() != null) {
PacketRepository.send(ConstructGroundItem.class, new BuildItemContext(g.getDropper(), item));
// Limit it to the region(8x8 square) you are in +/-2 regions, as the chunk loader will load it all over again.
// This is based on MapChunkRenderer which seems to "update" 2 chunks away.
// Look at RegionChunk.java ConstructGroundItem.write(buffer, item);
if (Math.abs(g.getDropper().getLocation().getRegionX() - l.getRegionX()) <= 2 &&
Math.abs(g.getDropper().getLocation().getRegionY() - l.getRegionY()) <= 2 &&
g.getDropper().getLocation().getZ() == l.getZ()) {
PacketRepository.send(ConstructGroundItem.class, new BuildItemContext(g.getDropper(), item));
}
}
return;
}

View file

@ -1,62 +1,61 @@
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.log
import core.api.sendMessage
import core.api.tryPop
import core.api.utils.Vector
import core.cache.def.impl.ItemDefinition
import core.cache.def.impl.NPCDefinition
import core.cache.def.impl.SceneryDefinition
import core.game.container.Container
import core.game.container.impl.BankContainer
import core.game.node.Node
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.event.ButtonClickEvent
import core.game.ge.GrandExchange.Companion.getOfferStats
import core.game.ge.GrandExchange.Companion.getRecommendedPrice
import core.game.ge.GrandExchangeOffer
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.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.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.communication.ClanRank
import core.game.system.communication.CommunicationInfo
import core.game.system.communication.GlobalChat
import core.game.system.task.Pulse
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.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`.RunScript
import core.net.packet.out.ClearMinimapFlag
import core.tools.Log
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.StringWriter
import java.lang.Math.min
@ -423,6 +422,11 @@ object PacketProcessor {
return
if (player.getAttribute("magic:delay", -1) > GameWorld.ticks)
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)
if (book != "none")
SpellListeners.run(child, type, book, player, target)

View file

@ -61,9 +61,9 @@ title: More changes in Gielinor
tags: news
layout: newspost
date: {current_date} 00:00:00 +0000
authors: ryannathans
authors: "Update Bot"
excerpt: "There have been some more changes in Gielinor..."
modtype: "Lead Developer"
modtype: "Bot"
avatar: avatar8fa9.gif
---
Greetings Explorers