diff --git a/Server/src/main/content/global/bots/SeersFlax.kt b/Server/src/main/content/global/bots/SeersFlax.kt index d39942005..f8dcb56ab 100644 --- a/Server/src/main/content/global/bots/SeersFlax.kt +++ b/Server/src/main/content/global/bots/SeersFlax.kt @@ -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 } diff --git a/Server/src/main/content/global/handlers/iface/AutocastSelectPlugin.java b/Server/src/main/content/global/handlers/iface/AutocastSelectPlugin.java index a010c7bb5..9213cf0f6 100644 --- a/Server/src/main/content/global/handlers/iface/AutocastSelectPlugin.java +++ b/Server/src/main/content/global/handlers/iface/AutocastSelectPlugin.java @@ -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) { diff --git a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt index c58bf62ed..71239cc7c 100644 --- a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt +++ b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt @@ -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), diff --git a/Server/src/main/content/global/handlers/item/ImplingJarListener.kt b/Server/src/main/content/global/handlers/item/ImplingJarListener.kt index 66158c78a..9fbd4060b 100644 --- a/Server/src/main/content/global/handlers/item/ImplingJarListener.kt +++ b/Server/src/main/content/global/handlers/item/ImplingJarListener.kt @@ -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) diff --git a/Server/src/main/content/global/handlers/item/LightSourceListeners.kt b/Server/src/main/content/global/handlers/item/LightSourceListeners.kt new file mode 100644 index 000000000..989746869 --- /dev/null +++ b/Server/src/main/content/global/handlers/item/LightSourceListeners.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/item/MiningHelmetListener.kt b/Server/src/main/content/global/handlers/item/MiningHelmetListener.kt deleted file mode 100644 index 755a1cbde..000000000 --- a/Server/src/main/content/global/handlers/item/MiningHelmetListener.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/item/withnpc/BonesOnStrayDog.kt b/Server/src/main/content/global/handlers/item/withnpc/BonesOnStrayDog.kt deleted file mode 100644 index e6e7fd03a..000000000 --- a/Server/src/main/content/global/handlers/item/withnpc/BonesOnStrayDog.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/item/withnpc/FeedOnStrayDog.kt b/Server/src/main/content/global/handlers/item/withnpc/FeedOnStrayDog.kt new file mode 100644 index 000000000..1c1013ac4 --- /dev/null +++ b/Server/src/main/content/global/handlers/item/withnpc/FeedOnStrayDog.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/npc/ShooAwayStrayDogListener.kt b/Server/src/main/content/global/handlers/npc/ShooAwayStrayDogListener.kt new file mode 100644 index 000000000..e159f89ab --- /dev/null +++ b/Server/src/main/content/global/handlers/npc/ShooAwayStrayDogListener.kt @@ -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") + +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/construction/RemovalDialogue.java b/Server/src/main/content/global/skill/construction/RemovalDialogue.java index 5b2a9f869..3edbc360f 100644 --- a/Server/src/main/content/global/skill/construction/RemovalDialogue.java +++ b/Server/src/main/content/global/skill/construction/RemovalDialogue.java @@ -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; diff --git a/Server/src/main/content/global/skill/construction/decoration/StaircasePlugin.java b/Server/src/main/content/global/skill/construction/decoration/StaircasePlugin.java index a1fe9ed3e..8c22d84ab 100644 --- a/Server/src/main/content/global/skill/construction/decoration/StaircasePlugin.java +++ b/Server/src/main/content/global/skill/construction/decoration/StaircasePlugin.java @@ -29,15 +29,19 @@ public final class StaircasePlugin extends OptionHandler { public Plugin 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; } diff --git a/Server/src/main/content/global/skill/cooking/NettleWaterListener.kt b/Server/src/main/content/global/skill/cooking/NettleWaterListener.kt deleted file mode 100644 index bf52e8067..000000000 --- a/Server/src/main/content/global/skill/cooking/NettleWaterListener.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/hunter/falconry/FalconryZone.kt b/Server/src/main/content/global/skill/hunter/falconry/FalconryZone.kt new file mode 100644 index 000000000..6256604de --- /dev/null +++ b/Server/src/main/content/global/skill/hunter/falconry/FalconryZone.kt @@ -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) + ) +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/hunter/falconry/PiscFalconryListener.kt b/Server/src/main/content/global/skill/hunter/falconry/PiscFalconryListener.kt new file mode 100644 index 000000000..c226a4fab --- /dev/null +++ b/Server/src/main/content/global/skill/hunter/falconry/PiscFalconryListener.kt @@ -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 { + 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) + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/magic/MagicAltarListener.kt b/Server/src/main/content/global/skill/magic/MagicAltarListener.kt index eb4e61fc5..70ec07e25 100644 --- a/Server/src/main/content/global/skill/magic/MagicAltarListener.kt +++ b/Server/src/main/content/global/skill/magic/MagicAltarListener.kt @@ -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::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 diff --git a/Server/src/main/content/global/skill/magic/SpellUtils.kt b/Server/src/main/content/global/skill/magic/SpellUtils.kt index a80d646a5..beafbf64d 100644 --- a/Server/src/main/content/global/skill/magic/SpellUtils.kt +++ b/Server/src/main/content/global/skill/magic/SpellUtils.kt @@ -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 diff --git a/Server/src/main/content/global/skill/magic/SpellbookSwapDialogue.java b/Server/src/main/content/global/skill/magic/SpellbookSwapDialogue.java index 8f9a62c8e..b794a87e0 100644 --- a/Server/src/main/content/global/skill/magic/SpellbookSwapDialogue.java +++ b/Server/src/main/content/global/skill/magic/SpellbookSwapDialogue.java @@ -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 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; } diff --git a/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java b/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java index 8b8e2c693..a9c95ab5e 100644 --- a/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java +++ b/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java @@ -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(); diff --git a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt index 3654d666d..8d70f99cb 100644 --- a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt +++ b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt @@ -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){ diff --git a/Server/src/main/content/global/skill/magic/lunar/SpellbookSwapSpell.java b/Server/src/main/content/global/skill/magic/lunar/SpellbookSwapSpell.java deleted file mode 100644 index 4c152b0af..000000000 --- a/Server/src/main/content/global/skill/magic/lunar/SpellbookSwapSpell.java +++ /dev/null @@ -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 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())); - } -} diff --git a/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt b/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt index eae541c70..7bb35b190 100644 --- a/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt +++ b/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt @@ -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::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 diff --git a/Server/src/main/content/global/skill/thieving/ThievingListeners.kt b/Server/src/main/content/global/skill/thieving/ThievingListeners.kt index d6123fcda..ef687a850 100644 --- a/Server/src/main/content/global/skill/thieving/ThievingListeners.kt +++ b/Server/src/main/content/global/skill/thieving/ThievingListeners.kt @@ -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) diff --git a/Server/src/main/content/region/fremennik/waterbirth/handlers/SpinolypNPC.java b/Server/src/main/content/region/fremennik/waterbirth/handlers/SpinolypNPC.java index 7836a1c2c..e887f5263 100644 --- a/Server/src/main/content/region/fremennik/waterbirth/handlers/SpinolypNPC.java +++ b/Server/src/main/content/region/fremennik/waterbirth/handlers/SpinolypNPC.java @@ -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); } diff --git a/Server/src/main/content/region/kandarin/ardougne/quest/plaguecity/PlagueCityListeners.kt b/Server/src/main/content/region/kandarin/ardougne/quest/plaguecity/PlagueCityListeners.kt index 99270a12b..0c8ef60a0 100644 --- a/Server/src/main/content/region/kandarin/ardougne/quest/plaguecity/PlagueCityListeners.kt +++ b/Server/src/main/content/region/kandarin/ardougne/quest/plaguecity/PlagueCityListeners.kt @@ -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) diff --git a/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt b/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt index 135e3dc92..7f4c3d09c 100644 --- a/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt +++ b/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt @@ -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) diff --git a/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemQuest.kt b/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemQuest.kt index 13c07751b..c89baea9e 100644 --- a/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemQuest.kt +++ b/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemQuest.kt @@ -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){ diff --git a/Server/src/main/content/region/misthalin/varrock/dialogue/GertrudeDialogue.java b/Server/src/main/content/region/misthalin/varrock/dialogue/GertrudeDialogue.java index 9fcbd6bb2..e9a4ea493 100644 --- a/Server/src/main/content/region/misthalin/varrock/dialogue/GertrudeDialogue.java +++ b/Server/src/main/content/region/misthalin/varrock/dialogue/GertrudeDialogue.java @@ -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 diff --git a/Server/src/main/content/region/misthalin/varrock/handlers/ShooAwayStrayDogPlugin.java b/Server/src/main/content/region/misthalin/varrock/handlers/ShooAwayStrayDogPlugin.java deleted file mode 100644 index d21dee850..000000000 --- a/Server/src/main/content/region/misthalin/varrock/handlers/ShooAwayStrayDogPlugin.java +++ /dev/null @@ -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 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; - } - -} diff --git a/Server/src/main/core/api/Event.kt b/Server/src/main/core/api/Event.kt index 1c1bc1ea1..a572dc4bb 100644 --- a/Server/src/main/core/api/Event.kt +++ b/Server/src/main/core/api/Event.kt @@ -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 diff --git a/Server/src/main/core/api/utils/WeightBasedTable.kt b/Server/src/main/core/api/utils/WeightBasedTable.kt index a890d4d15..39505c241 100644 --- a/Server/src/main/core/api/utils/WeightBasedTable.kt +++ b/Server/src/main/core/api/utils/WeightBasedTable.kt @@ -182,6 +182,22 @@ open class WeightBasedTable : ArrayList() { 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() { 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 diff --git a/Server/src/main/core/game/dialogue/DialogueInterpreter.java b/Server/src/main/core/game/dialogue/DialogueInterpreter.java index 85a0e94a8..ccc4a94c0 100644 --- a/Server/src/main/core/game/dialogue/DialogueInterpreter.java +++ b/Server/src/main/core/game/dialogue/DialogueInterpreter.java @@ -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 diff --git a/Server/src/main/core/game/event/Events.kt b/Server/src/main/core/game/event/Events.kt index 900b41629..b11d6241b 100644 --- a/Server/src/main/core/game/event/Events.kt +++ b/Server/src/main/core/game/event/Events.kt @@ -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 diff --git a/Server/src/main/core/game/node/entity/combat/spell/MagicSpell.java b/Server/src/main/core/game/node/entity/combat/spell/MagicSpell.java index 12059a76c..896a3ec62 100644 --- a/Server/src/main/core/game/node/entity/combat/spell/MagicSpell.java +++ b/Server/src/main/core/game/node/entity/combat/spell/MagicSpell.java @@ -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 { 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); } diff --git a/Server/src/main/core/game/node/entity/player/link/SpellBookManager.java b/Server/src/main/core/game/node/entity/player/link/SpellBookManager.java index f8cabd581..0c29bf5b4 100644 --- a/Server/src/main/core/game/node/entity/player/link/SpellBookManager.java +++ b/Server/src/main/core/game/node/entity/player/link/SpellBookManager.java @@ -58,6 +58,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 @@ -137,5 +147,4 @@ public final class SpellBookManager { return spells.get(buttonId); } } - } \ No newline at end of file diff --git a/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt b/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt index de41b11a8..468bdaf96 100644 --- a/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt @@ -81,6 +81,33 @@ class SpawnCommandSet : CommandSet(Privilege.ADMIN){ player.inventory.add(item) } + + /** + * Spawns multiple items with the given ID + */ + define("items", usage = "::items start-id end-id [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 */ diff --git a/Server/src/main/core/game/system/timer/impl/SpellbookSwap.kt b/Server/src/main/core/game/system/timer/impl/SpellbookSwap.kt new file mode 100644 index 000000000..b689e3a0a --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/SpellbookSwap.kt @@ -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 { + override fun process(entity : Entity, event : SpellCastEvent) { + if (event.spellBook == SpellBook.LUNAR && event.spellId == 12) return // Ignore spellbook swap itself + revertSpellbook(entity) + removeTimer(entity) + } + } + + private val spellBookChangeHook = object : EventHook { + override fun process(entity : Entity, event : SpellbookChangeEvent) { + if (event.source != SpellbookChangeSource.SPELLBOOK_SWAP_CAST + && event.source != SpellbookChangeSource.SPELLBOOK_SWAP_RESTORE) { + removeTimer(entity) + } + } + } + + private val dialogueCloseHook = object : EventHook { + 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(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::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) + } +} diff --git a/Server/src/main/core/game/world/map/RegionPlane.java b/Server/src/main/core/game/world/map/RegionPlane.java index 6557bf62b..f83724ec0 100644 --- a/Server/src/main/core/game/world/map/RegionPlane.java +++ b/Server/src/main/core/game/world/map/RegionPlane.java @@ -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; } diff --git a/Server/src/main/core/net/packet/PacketProcessor.kt b/Server/src/main/core/net/packet/PacketProcessor.kt index d76b82db8..6ec7d04ba 100644 --- a/Server/src/main/core/net/packet/PacketProcessor.kt +++ b/Server/src/main/core/net/packet/PacketProcessor.kt @@ -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) diff --git a/make-release.py b/make-release.py index bae99d2ee..4b898b4e2 100755 --- a/make-release.py +++ b/make-release.py @@ -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