diff --git a/Server/detekt.yml b/Server/detekt.yml index f66960704..3a3977905 100644 --- a/Server/detekt.yml +++ b/Server/detekt.yml @@ -696,9 +696,9 @@ style: UseOrEmpty: active: true UseRequire: - active: true + active: false UseRequireNotNull: - active: true + active: false UselessCallOnNotNull: active: true UtilityClassWithPublicConstructor: diff --git a/Server/src/main/content/global/handlers/iface/GenericItemSelect.kt b/Server/src/main/content/global/handlers/iface/GenericItemSelect.kt new file mode 100644 index 000000000..48b4b3318 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/GenericItemSelect.kt @@ -0,0 +1,62 @@ +package content.global.handlers.iface + +import core.api.* +import core.tools.* +import core.game.interaction.* +import core.game.system.task.Pulse +import core.game.node.entity.player.Player + +class GenericItemSelect : InterfaceListener { + val GENERIC_ITEM_SELECT_IFACE = 12 + + override fun defineInterfaceListeners() { + onOpen(GENERIC_ITEM_SELECT_IFACE) {player, _ -> + player.pulseManager.run(object : Pulse() { + override fun pulse() : Boolean { + return false + } + override fun stop() { + super.stop() + player.interfaceManager.closeSingleTab() + } + }) + return@onOpen true + } + + on(GENERIC_ITEM_SELECT_IFACE){player, _, opcode, buttonID, slot, _ -> + processResponse(player, opcode, slot) + return@on true + } + + onClose(GENERIC_ITEM_SELECT_IFACE) {player, _ -> + removeAttribute(player, "itemselect-callback") + return@onClose true + } + } + + private fun processResponse (player: Player, opcode: Int, slot: Int) { + val callback = getAttribute<((Int, Int) -> Unit)?>(player, "itemselect-callback", null) + if (callback == null) { + log (this::class.java, Log.WARN, "${player.name} is trying to use an item select prompt with no callback!") + return + } + + val optionIndex = when (opcode) { + 155 -> 0 + 196 -> 1 + 124 -> 2 + 199 -> 3 + 234 -> 4 + 9 -> 10 + else -> -1 + } + if (optionIndex == -1) { + log (this::class.java, Log.WARN, "${player.name} is clicking a right-click index that we don't know the opcode for yet, lol. Here's the opcode: $opcode") + return + } + + callback.invoke(slot, optionIndex) + removeAttribute (player, "itemselect-callback") + player.interfaceManager.closeSingleTab() + } +} diff --git a/Server/src/main/content/global/handlers/item/ExplorersRingPlugin.kt b/Server/src/main/content/global/handlers/item/ExplorersRingPlugin.kt index 912045136..f8dc9de65 100644 --- a/Server/src/main/content/global/handlers/item/ExplorersRingPlugin.kt +++ b/Server/src/main/content/global/handlers/item/ExplorersRingPlugin.kt @@ -1,9 +1,6 @@ package content.global.handlers.item -import core.api.hasLevelStat -import core.api.sendMessage -import core.api.teleport -import core.api.visualize +import core.api.* import core.game.node.entity.player.Player import core.game.node.entity.player.link.TeleportManager.TeleportType import core.game.node.entity.skill.Skills @@ -15,12 +12,8 @@ import core.ServerStore.Companion.getBoolean import core.ServerStore.Companion.getInt import core.game.interaction.IntType import core.game.interaction.InteractionListener +import content.global.skill.magic.modern.ModernListeners -/** - * Handles the explorers ring. - * - * @author Vexia - */ class ExplorersRingPlugin : InteractionListener { val RINGS = intArrayOf(Items.EXPLORERS_RING_1_13560, Items.EXPLORERS_RING_2_13561, Items.EXPLORERS_RING_3_13562) @@ -44,19 +37,30 @@ class ExplorersRingPlugin : InteractionListener { return@on true } + //Sources Used + // - https://www.youtube.com/watch?v=PEA0fkRK0-A&t=64s&pp=ygUcMjAwOSBydW5lc2NhcGUgZXhwbG9yZXIgcmluZw%3D%3D + // - https://runescape.wiki/w/Explorer%27s_ring?oldid=899927 on(RINGS, IntType.ITEM, "low-alchemy"){ player, _ -> if (!hasLevelStat(player, Skills.MAGIC, 21)) { sendMessage(player,"You need a Magic level of 21 in order to do that.") return@on true } - if(getStoreFile().getBoolean(player.username.toLowerCase() + ":alchs")){ - sendMessage(player, "You have claimed all the charges you can for one day.") + val remaining = getStoreFile().getInt(player.username.lowercase() + ":alchs", 30) + if (remaining <= 0) { + sendMessage(player, "You have used up all of your charges for the day.") return@on true } - sendMessage(player,"You grant yourself with 30 free low alchemy charges.") // todo this implementation is not correct, see https://www.youtube.com/watch?v=UbUIF2Kw_Dw - - getStoreFile()[player.username.toLowerCase() + ":alchs"] = true - + sendDialogue(player, "Choose the item you wish to convert to coins.") + addDialogueAction (player) {_,_ -> + sendItemSelect (player, "Choose") { slot, optionIndex -> + val item = player.inventory[slot] + if (item == null) + return@sendItemSelect + if (!ModernListeners().alchemize(player, item, false)) + return@sendItemSelect + getStoreFile()[player.username.lowercase() + ":alchs"] = remaining - 1 + } + } return@on true } diff --git a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt index 44500155a..a6e6c563d 100644 --- a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt +++ b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt @@ -221,19 +221,19 @@ class ModernListeners : SpellListener("modern"){ setDelay(player,false) } - private fun alchemize(player: Player,item: Item,high:Boolean){ - if(item.name == "Coins") player.sendMessage("You can't alchemize something that's already gold!").also { return } - if(!item.definition.isTradeable) player.sendMessage("You can't cast this spell on something like that.").also { return } + public fun alchemize(player: Player, item: Item, high: Boolean) : Boolean { + if(item.name == "Coins") player.sendMessage("You can't alchemize something that's already gold!").also { return false } + if(!item.definition.isTradeable) player.sendMessage("You can't cast this spell on something like that.").also { return false } if(player.zoneMonitor.isInZone("Alchemists' Playground")){ player.sendMessage("You can only alch items from the cupboards!") - return + return false } val coins = Item(995, item.definition.getAlchemyValue(high)) if (coins.amount > 0 && !player.inventory.hasSpaceFor(coins)) { player.sendMessage("Not enough space in your inventory!") - return + return false } if (player.pulseManager.current !is MovementPulse) { @@ -259,6 +259,7 @@ class ModernListeners : SpellListener("modern"){ addXP(player, if (high) 65.0 else 31.0) showMagicTab(player) setDelay(player, 5) + return true } private fun sendTeleport(player: Player, xp: Double, location: Location){ diff --git a/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java b/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java index 5d51e68aa..25f059d22 100644 --- a/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java +++ b/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java @@ -118,7 +118,7 @@ public final class SlayerMasterDialogue extends DialoguePlugin { @Override public boolean handle(int interfaceId, int buttonId) { - rerolls = ServerStore.getInt(getStoreFile(), player.getUsername().toLowerCase()); + rerolls = ServerStore.getInt(getStoreFile(), player.getUsername().toLowerCase(), 0); if (isDiary) { switch (stage) { case 999: diff --git a/Server/src/main/core/ServerStore.kt b/Server/src/main/core/ServerStore.kt index ec58ebfe1..ad2678584 100644 --- a/Server/src/main/core/ServerStore.kt +++ b/Server/src/main/core/ServerStore.kt @@ -101,14 +101,14 @@ class ServerStore : PersistWorld { } @JvmStatic - fun JSONObject.getInt(key: String): Int { + fun JSONObject.getInt(key: String, default: Int = 0): Int { return when(val value = this[key]){ is Long -> value.toInt() is Double -> value.toInt() is Float -> value.toInt() is Int -> value - is Nothing -> 0 - else -> 0 + is Nothing -> default + else -> default } } diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 7f35d5de0..fd9f9ce9d 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -1585,6 +1585,50 @@ fun sendGraphics(gfx: G, location: Location) { } } +/** + * Instructs the given player's client to execute a given CS2 script with the given arguments + * @param player the Player + * @param scriptId the ID of the CS2 script to execute + * @param arguments the various arguments passed to the script. +**/ +fun runcs2 (player: Player, scriptId: Int, vararg arguments: Any) { + var typeString = StringBuilder() + var finalArgs = Array (arguments.size) { null } + try { + for (i in 0 until arguments.size) { + val arg = arguments[i] + if (arg is Int) + typeString.append("i") + else if (arg is String) + typeString.append("s") + else + throw IllegalArgumentException("Argument at index $i ($arg) is not an acceptable type! Only string and int are accepted.") + finalArgs[arguments.size - i - 1] = arg + } + player.packetDispatch.sendRunScript(scriptId, typeString.toString(), *finalArgs) + } catch (e: Exception) { + e.printStackTrace() + } +} + +/** + * Opens a generic item selection prompt with a glowing background, with your own callback to handle the selection. + * @param player the player we are openinig the prompt for + * @param options the right-click options the items should have + * @param callback a callback to handle the selection. The parameters passed to the callback are the slot in the inventory of the selected item, and the 0-9 index of the option clicked. +**/ +fun sendItemSelect (player: Player, vararg options: String, callback: (slot: Int, optionIndex: Int) -> Unit) { + player.interfaceManager.openSingleTab(Component(12)) + val scriptArgs = arrayOf ((12 shl 16) + 18, 93, 4, 7, 0, -1, "", "", "", "", "", "", "", "", "") + for (i in 0 until kotlin.math.min(9, options.size)) + scriptArgs[6 + i] = options[i] + runcs2(player, 150, *scriptArgs) + val settings = IfaceSettingsBuilder() + .enableOptions(0 until 9) + .build() + player.packetDispatch.sendIfaceSettings(settings, 18, 12, 0, 28) + setAttribute(player, "itemselect-callback", callback) +} fun announceIfRare(player: Player, item: Item) { if (item.definition.getConfiguration(ItemConfigParser.RARE_ITEM, false)) { diff --git a/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt b/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt index fb10da547..1f74393b7 100644 --- a/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt @@ -24,6 +24,7 @@ import core.game.system.command.Privilege import java.io.BufferedWriter import java.io.File import java.io.FileWriter +import java.util.Arrays import core.net.packet.PacketWriteQueue import core.tools.Log @@ -41,6 +42,20 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) { } } + define("cs2", Privilege.ADMIN, "::cs2 id args", "Allows you to call arbitrary cs2 scripts during runtime") {player, args -> + var scriptArgs = ArrayList() + if (args.size == 2) { + runcs2(player, args[1].toIntOrNull() ?: return@define) + return@define + } + else if (args.size > 2) { + for (i in 2 until args.size) { + scriptArgs.add(args[i].toIntOrNull() ?: args[i]) + } + runcs2(player, args[1].toIntOrNull() ?: return@define, *(scriptArgs.toTypedArray().also { player.debug (Arrays.toString(it)) })) + } + } + define("cleardiary", Privilege.ADMIN) { player, _ -> for (type in DiaryType.values()) { val diary = player.achievementDiaryManager.getDiary(type)