From 52633544ff54e6ac5c04ce388a99e9d89ec912cd Mon Sep 17 00:00:00 2001 From: RiL <13897849-RiL0@users.noreply.gitlab.com> Date: Tue, 9 May 2023 00:02:51 +0000 Subject: [PATCH] Implemented ::addxp command (admin), add xp to a skill Implemented ::charge command (admin), get and set the charge of an item Implemented ::setslayertask command (admin), set the slayer task npc with optional quantity --- Server/src/main/content/data/ChargedItem.kt | 80 ++++++++++++++++++ .../handlers/item/withobject/HatStand.kt | 2 +- .../skill/gather/mining/MiningListener.kt | 2 +- .../skill/gather/mining/MiningSkillPulse.kt | 2 +- .../skill/slayer/SlayerEquipmentFlags.kt | 6 +- .../castlewars/CastleWarsListeners.kt | 2 +- Server/src/main/core/api/ApiExtensions.kt | 11 +-- Server/src/main/core/api/EquipmentSlot.kt | 31 ++++++- .../node/entity/combat/MeleeSwingHandler.kt | 2 +- .../system/command/sets/MiscCommandSet.kt | 18 +++- .../system/command/sets/SlayerCommandSet.kt | 19 ++++- .../system/command/sets/SystemCommandSet.kt | 82 +++++++++++++++++-- 12 files changed, 228 insertions(+), 29 deletions(-) create mode 100644 Server/src/main/content/data/ChargedItem.kt diff --git a/Server/src/main/content/data/ChargedItem.kt b/Server/src/main/content/data/ChargedItem.kt new file mode 100644 index 000000000..06f3d4093 --- /dev/null +++ b/Server/src/main/content/data/ChargedItem.kt @@ -0,0 +1,80 @@ +package content.data + +import core.api.toIntArray +import core.cache.def.impl.ItemDefinition +import org.rs09.consts.Items + +/** + * Represents a distinct charged item, i.e. items with (#). + * Not to be confused with items with internal charge, like degradation. + * @author RiL + */ +enum class ChargedItem(val ids: IntArray) { + AMULET_OF_GLORY((Items.AMULET_OF_GLORY4_1712 downTo Items.AMULET_OF_GLORY_1704 step 2).toIntArray()), + RING_OF_DUELLING((Items.RING_OF_DUELLING8_2552..Items.RING_OF_DUELLING1_2566 step 2).toIntArray()), + GAMES_NECKLACE((Items.GAMES_NECKLACE8_3853..Items.GAMES_NECKLACE1_3867 step 2).toIntArray()), + BROODOO_SHIELDA((Items.BROODOO_SHIELD_10_6215..Items.BROODOO_SHIELD_6235 step 2).toIntArray()), + BROODOO_SHIELDB((Items.BROODOO_SHIELD_10_6237..Items.BROODOO_SHIELD_6257 step 2).toIntArray()), + BROODOO_SHIELDC((Items.BROODOO_SHIELD_10_6259..Items.BROODOO_SHIELD_6279 step 2).toIntArray()), + ROD_OF_IVANDIS((Items.ROD_OF_IVANDIS10_7639..Items.ROD_OF_IVANDIS1_7648).toIntArray()), + BLACK_MASK((Items.BLACK_MASK_10_8901..Items.BLACK_MASK_8921 step 2).toIntArray()), + AMULET_OF_GLORYT((Items.AMULET_OF_GLORYT4_10354..Items.AMULET_OF_GLORYT_10362 step 2).toIntArray()), + CASTLEWAR_BRACE((Items.CASTLEWAR_BRACE3_11079..Items.CASTLEWAR_BRACE1_11083 step 2).toIntArray()), + FORINTHRY_BRACE((Items.FORINTHRY_BRACE5_11095..Items.FORINTHRY_BRACE1_11103 step 2).toIntArray()), + SKILLS_NECKLACE((Items.SKILLS_NECKLACE4_11105..Items.SKILLS_NECKLACE_11113 step 2).toIntArray()), + COMBAT_BRACELET((Items.COMBAT_BRACELET4_11118..Items.COMBAT_BRACELET_11126 step 2).toIntArray()), + DIGSITE_PENDANT((Items.DIGSITE_PENDANT_5_11194 downTo Items.DIGSITE_PENDANT_1_11190).toIntArray()), + VOID_SEAL((Items.VOID_SEAL8_11666..Items.VOID_SEAL1_11673).toIntArray()), + AMULET_OF_FARMING((Items.AMULET_OF_FARMING8_12622 downTo Items.AMULET_OF_FARMING1_12608 step 2).toIntArray()), + IVANDIS_FLAIL((Items.IVANDIS_FLAIL_30_13117..Items.IVANDIS_FLAIL_1_13146).toIntArray()), + RING_OF_SLAYING((Items.RING_OF_SLAYING8_13281..Items.RING_OF_SLAYING1_13288).toIntArray()), + RING_OF_WEALTH((Items.RING_OF_WEALTH4_14646 downTo Items.RING_OF_WEALTH_14638 step 2).toIntArray()); + + /** + * Get the item id for a specific [charge]. + * If [charge] is outside the valid range, return the item id with the closest charge. + */ + fun forCharge(charge: Int): Int { + return ids[maxCharge() - if (charge < 1) 1 + maxCharge() - ids.size else if (charge > maxCharge()) maxCharge() else charge] + } + + /** + * Get the max charge of this ChargedItem. + */ + fun maxCharge(): Int = maxCharges[ordinal] + + companion object { + private val CHARGE_REGEX = Regex("""\(\D?(\d+)\)""") + private val idMap = HashMap() + private val maxCharges = IntArray(values().size) + + init { + values().forEach { chargedItem -> + maxCharges[chargedItem.ordinal] = getMaxCharge(chargedItem) + chargedItem.ids.forEach { idMap[it] = chargedItem } + } + } + + private fun getMaxCharge(chargedItem: ChargedItem): Int { + return CHARGE_REGEX.find(ItemDefinition.forId(chargedItem.ids.first()).name)!!.groups[1]!!.value.toInt() + } + + /** + * Check if the item id is a charged item. + */ + fun contains(id: Int): Boolean = idMap.containsKey(id) + + /** + * Get the ChargedItem enum for an item id, or null if it's not a charged item. + */ + fun forId(id: Int): ChargedItem? = idMap[id] + + /** + * Get the charge value of an item id, or null if it's not a charged item. + */ + fun getCharge(id: Int): Int? { + val chargedItem = forId(id) ?: return null + return chargedItem.maxCharge() - chargedItem.ids.indexOf(id) + } + } +} diff --git a/Server/src/main/content/global/handlers/item/withobject/HatStand.kt b/Server/src/main/content/global/handlers/item/withobject/HatStand.kt index c9f3a3dde..91205e535 100644 --- a/Server/src/main/content/global/handlers/item/withobject/HatStand.kt +++ b/Server/src/main/content/global/handlers/item/withobject/HatStand.kt @@ -8,7 +8,7 @@ import core.game.interaction.IntType class HatStand : InteractionListener { - val hats = ItemDefinition.getDefinitions().values.filter { it.getConfiguration("equipment_slot",0) == EquipmentSlot.HAT.ordinal }.map { it.id }.toIntArray() + val hats = ItemDefinition.getDefinitions().values.filter { it.getConfiguration("equipment_slot",0) == EquipmentSlot.HEAD.ordinal }.map { it.id }.toIntArray() val hat_stand = 374 override fun defineListeners() { diff --git a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt index 9f743c7e5..b07f318b0 100644 --- a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt +++ b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt @@ -112,7 +112,7 @@ class MiningListener : InteractionListener { chance = (chance / 1.5).toInt() altered = true } - val necklace = getItemFromEquipment(player, EquipmentSlot.AMULET) + val necklace = getItemFromEquipment(player, EquipmentSlot.NECK) if (necklace != null && necklace.id in 1705..1713) { chance = (chance / 1.5).toInt() altered = true diff --git a/Server/src/main/content/global/skill/gather/mining/MiningSkillPulse.kt b/Server/src/main/content/global/skill/gather/mining/MiningSkillPulse.kt index a485930b3..7784f5646 100644 --- a/Server/src/main/content/global/skill/gather/mining/MiningSkillPulse.kt +++ b/Server/src/main/content/global/skill/gather/mining/MiningSkillPulse.kt @@ -178,7 +178,7 @@ class MiningSkillPulse(private val player: Player, private val node: Node) : Pul chance = (chance / 1.5).toInt() altered = true } - val necklace = getItemFromEquipment(player, EquipmentSlot.AMULET) + val necklace = getItemFromEquipment(player, EquipmentSlot.NECK) if (necklace != null && necklace.id in 1705..1713) { chance = (chance / 1.5).toInt() altered = true diff --git a/Server/src/main/content/global/skill/slayer/SlayerEquipmentFlags.kt b/Server/src/main/content/global/skill/slayer/SlayerEquipmentFlags.kt index 06cc8583e..c953bf9de 100644 --- a/Server/src/main/content/global/skill/slayer/SlayerEquipmentFlags.kt +++ b/Server/src/main/content/global/skill/slayer/SlayerEquipmentFlags.kt @@ -24,10 +24,10 @@ object SlayerEquipmentFlags { else if(hasItem(player, Items.NOSE_PEG_4168)) flags = 1 else if(hasItem(player, Items.EARMUFFS_4166)) flags = flags or (1 shl 1) else if(hasItem(player, Items.FACE_MASK_4164)) flags = flags or (1 shl 2) - else if((getItemFromEquipment(player, EquipmentSlot.HAT)?.id ?: 0) in blackMasks) flags = flags or (1 shl 3) + else if((getItemFromEquipment(player, EquipmentSlot.HEAD)?.id ?: 0) in blackMasks) flags = flags or (1 shl 3) else if(hasItem(player, Items.SPINY_HELMET_4551)) flags = flags or (1 shl 4) - if((getItemFromEquipment(player, EquipmentSlot.AMULET)?.id ?: 0) == Items.WITCHWOOD_ICON_8923) flags = flags or (1 shl 7) + if((getItemFromEquipment(player, EquipmentSlot.NECK)?.id ?: 0) == Items.WITCHWOOD_ICON_8923) flags = flags or (1 shl 7) if((getItemFromEquipment(player, EquipmentSlot.SHIELD)?.id ?: 0) == Items.MIRROR_SHIELD_4156) flags = flags or (1 shl 8) SlayerManager.getInstance(player).flags.equipmentFlags = flags } @@ -78,7 +78,7 @@ object SlayerEquipmentFlags { } private fun hasItem(player: Player, id: Int): Boolean{ - return (getItemFromEquipment(player, EquipmentSlot.HAT)?.id ?: 0) == id + return (getItemFromEquipment(player, EquipmentSlot.HEAD)?.id ?: 0) == id } fun isSlayerEq(item: Int): Boolean{ diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt index 64d3bb4a0..f557001df 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt @@ -158,7 +158,7 @@ class CastleWarsListeners : InteractionListener { private fun capeOrHelmetError(player: Player): String? { val wornCape = getItemFromEquipment(player, EquipmentSlot.CAPE)?.id ?: -1 - val wornHelmet = getItemFromEquipment(player, EquipmentSlot.HAT)?.id ?: -1 + val wornHelmet = getItemFromEquipment(player, EquipmentSlot.HEAD)?.id ?: -1 if (wornCape != -1 || wornHelmet != -1) return "You can't wear hats, capes, or helms in the arena." return null diff --git a/Server/src/main/core/api/ApiExtensions.kt b/Server/src/main/core/api/ApiExtensions.kt index 9a2e4c230..fc8efda5a 100644 --- a/Server/src/main/core/api/ApiExtensions.kt +++ b/Server/src/main/core/api/ApiExtensions.kt @@ -4,14 +4,9 @@ import core.game.node.item.Item import java.util.* import kotlin.collections.ArrayList -fun IntRange.toIntArray(): IntArray { - if (last < first) - return IntArray(0) - - val result = IntArray(last - first + 1) - var index = 0 - for (element in this) - result[index++] = element +fun IntProgression.toIntArray(): IntArray { + val result = IntArray((last - first) / step + 1) + forEachIndexed { index, i -> result[index] = i } return result } diff --git a/Server/src/main/core/api/EquipmentSlot.kt b/Server/src/main/core/api/EquipmentSlot.kt index 53a60fcf6..b0812cb23 100644 --- a/Server/src/main/core/api/EquipmentSlot.kt +++ b/Server/src/main/core/api/EquipmentSlot.kt @@ -1,9 +1,9 @@ package core.api enum class EquipmentSlot { - HAT, + HEAD, CAPE, - AMULET, + NECK, WEAPON, CHEST, SHIELD, @@ -14,5 +14,30 @@ enum class EquipmentSlot { FEET, HIDDEN_3, RING, - AMMO + AMMO; + + companion object { + private val slotMap = HashMap() + + init { + slotMap["head"] = HEAD; slotMap["helm"] = HEAD; slotMap ["helmet"] = HEAD + slotMap["cape"] = CAPE; slotMap["back"] = CAPE + slotMap["neck"] = NECK; slotMap["amulet"] = NECK + slotMap["weapon"] = WEAPON; slotMap["main"] = WEAPON + slotMap["chest"] = CHEST; slotMap["body"] = CHEST; slotMap["torso"] = CHEST + slotMap["shield"] = SHIELD; slotMap["off"] = SHIELD + slotMap["legs"] = LEGS; slotMap["leg"] = LEGS + slotMap["hands"] = HANDS; slotMap["hand"] = HANDS; slotMap["brace"] = HANDS; slotMap["bracelet"] = HANDS; + slotMap["feet"] = FEET + slotMap["ring"] = RING + slotMap["ammo"] = AMMO; slotMap["ammunition"] = AMMO + } + + /** + * Return the equipment slot by name. Return null if matching no slot. + */ + fun slotByName(input: String): EquipmentSlot? { + return slotMap[input.lowercase()] + } + } } \ No newline at end of file diff --git a/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt b/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt index 1a5df544e..5d1380423 100644 --- a/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt +++ b/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt @@ -145,7 +145,7 @@ open class MeleeSwingHandler // attack bonus for specialized equipments (salve amulets, slayer equips) if (entity is Player) { - val amuletId = getItemFromEquipment(entity, EquipmentSlot.AMULET)?.id ?: 0 + val amuletId = getItemFromEquipment(entity, EquipmentSlot.NECK)?.id ?: 0 if ((amuletId == Items.SALVE_AMULET_4081 || amuletId == Items.SALVE_AMULETE_10588) && checkUndead(victimName)) { effectiveAttackLevel *= if (amuletId == Items.SALVE_AMULET_4081) 1.15 else 1.2 } else if (getSlayerTask(entity)?.ids?.contains((entity.properties.combatPulse?.getVictim()?.id ?: 0)) == true) { diff --git a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt index c7f2b7f6c..dc63825c9 100644 --- a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt @@ -29,7 +29,6 @@ import core.game.ge.GrandExchange import content.global.handlers.iface.RulesAndInfo import content.global.skill.farming.FarmingState import content.minigame.fishingtrawler.TrawlerLoot -import core.tools.SystemLogger import core.game.system.command.CommandMapping import core.game.system.command.Privilege import core.game.world.repository.Repository @@ -362,7 +361,7 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ * Set a specific skill to a specific level */ define("setlevel", Privilege.ADMIN, "::setlevel SKILL NAME LEVEL", "Sets SKILL NAME to LEVEL."){player,args -> - if(args.size < 2) reject(player,"Usage: ::setlevel skillname level") + if(args.size != 3) reject(player,"Usage: ::setlevel skillname level") val skillname = args[1] val desiredLevel: Int? = args[2].toIntOrNull() if(desiredLevel == null){ @@ -378,6 +377,21 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ player.skills.updateCombatLevel() } + /** + * Add xp to skill + */ + define("addxp", Privilege.ADMIN, "::addxp skill name | id xp", "Add xp to skill") { player, args -> + if (args.size != 3) reject(player, "Usage: ::addxp skill name | id xp") + + val skill = args[1].toIntOrNull() ?: Skills.getSkillByName(args[1]) + if (skill < 0 || skill >= Skills.NUM_SKILLS) reject(player, "Must use valid skill name or id.") + + val xp = args[2].toDoubleOrNull() + if (xp == null || xp <= 0) reject(player, "Xp must be a positive number.") + + player.skills.addExperience(skill, xp!!) + } + define("completediaries", Privilege.ADMIN, "", "Completes all diaries."){player,_ -> player.achievementDiaryManager.diarys.forEach { for(level in it.taskCompleted.indices){ diff --git a/Server/src/main/core/game/system/command/sets/SlayerCommandSet.kt b/Server/src/main/core/game/system/command/sets/SlayerCommandSet.kt index 117e06cd0..0f24a4eac 100644 --- a/Server/src/main/core/game/system/command/sets/SlayerCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/SlayerCommandSet.kt @@ -1,9 +1,12 @@ package core.game.system.command.sets +import content.global.skill.slayer.Master import content.global.skill.slayer.SlayerManager +import content.global.skill.slayer.SlayerUtils +import content.global.skill.slayer.Tasks import core.game.node.entity.npc.NPC -import core.plugin.Initializable import core.game.system.command.Privilege +import core.plugin.Initializable @Initializable class SlayerCommandSet : CommandSet(Privilege.ADMIN){ @@ -35,5 +38,19 @@ class SlayerCommandSet : CommandSet(Privilege.ADMIN){ SlayerManager.getInstance(player).slayerPoints = amount!! notify(player, "Set slayer points to $amount.") } + + define("setslayertask", Privilege.ADMIN, "::setslayertask npc id [amount]", "Set the slayer task to npc. Amount optional.") { player, args -> + if (args.size < 2) reject(player, "Usage: ::setslayertask npc id [amount]") + + val npc = (args[1].toIntOrNull() ?: reject(player, "Must enter valid npc id")) as Int + val task = (Tasks.forId(npc) ?: reject(player, "Must enter valid npc id")) as Tasks + val amount = args.getOrNull(2)?.toIntOrNull() + ?.let { if (it !in 1..255) reject(player, "Amount must be an integer: 1-255.") else it } as Int? + + val slayer = SlayerManager.getInstance(player) + if (slayer.hasTask()) slayer.task = task else SlayerUtils.assign(player, task, Master.values().random()) + if (amount != null) slayer.amount = amount + player.varpManager.get(2502).setVarbit(0, slayer.flags.taskFlags shr 4).send(player) + } } } \ No newline at end of file diff --git a/Server/src/main/core/game/system/command/sets/SystemCommandSet.kt b/Server/src/main/core/game/system/command/sets/SystemCommandSet.kt index 724c928c1..2581bb4bb 100644 --- a/Server/src/main/core/game/system/command/sets/SystemCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/SystemCommandSet.kt @@ -1,19 +1,17 @@ package core.game.system.command.sets -import core.api.InputType -import core.api.runTask -import core.api.sendDialogue -import core.api.sendInputDialogue +import content.data.ChargedItem +import core.api.* import core.cache.def.impl.ItemDefinition +import core.game.node.entity.player.Player import core.game.node.item.Item import core.game.system.SystemManager import core.game.system.SystemState -import core.plugin.Initializable -import org.rs09.consts.Items -import core.tools.SystemLogger import core.game.system.command.Privilege import core.game.world.GameWorld import core.game.world.repository.Repository +import core.plugin.Initializable +import org.rs09.consts.Items import kotlin.system.exitProcess @Initializable @@ -236,5 +234,75 @@ class SystemCommandSet : CommandSet(Privilege.ADMIN) { exitProcess(0) } + /** + * Command to get and set the charge of an item. + * Can handle both the internal charge used for things like degradation, and distinct (#) charge that's different items. + * The default behavior without any flags is to get the internal charge of an item. + * param equipment slot name | item id - selects the item, either by using the equipment slot name, or item id. + * param sd - optional flags, where s = set charge, d = distinct charge. + * param charge - the charge to set on the item. Only necessary when the set flag is toggled. + * @author RiL + */ + define("charge", Privilege.ADMIN, "::charge equipment slot name | item id [sd] [charge]", "Get/set the charge of an item. Flags: s = set, d = distinct(#).") { player, args -> + if (args.size < 2) reject( + player, + "Usage: ::charge equipment slot name | item id [sd] [charge]", + "Get internal: ::charge 4718", + "Get distinct: ::charge ring d", + "Set internal: ::charge head s 500", + "Set distinct: ::charge 1704 sd 4" + ) + + @Suppress("UNCHECKED_CAST") + val itemInfo = (args[1].toIntOrNull()?.let { + getItemAndContainer(player, it) ?: reject(player, "Item not found: $it.") + } ?: EquipmentSlot.slotByName(args[1])?.ordinal?.let { + Pair(player.equipment.get(it) ?: reject(player, "No item equipped at slot: ${args[1]}."), player.equipment) + } ?: reject(player, "No slot: ${args[1]}.")) as Pair + + val item = itemInfo.first + + when (val flags = args.getOrElse(2) { "" }) { + "" -> { // get internal charge + notify(player, "${item.name}: ${item.charge} charge.") + } + + "d" -> { // get distinct charge + ChargedItem.forId(item.id)?.run { + notify(player, "${item.name}: (${ChargedItem.getCharge(item.id)}) charge.") + } ?: reject(player, "${item.name} is not a distinct(#) item.") + } + + "s", "sd", "ds" -> { + if (args.size < 4) reject(player, "Must enter a charge value.") + val charge = (args[3].toIntOrNull() ?: reject(player, "Charge must be an integer.")) as Int + if (flags == "s") { // set internal charge + if (charge < 1) reject(player, "Charge must be a positive integer.") + item.charge = charge + notify(player, "Set internal charge of ${item.name} to $charge.") + } else { // set distinct charge + ChargedItem.forId(item.id)?.forCharge(charge)?.let { + itemInfo.second.replace(Item(it), item.slot) + notify(player, "Set distinct charge of ${item.name} to (${ChargedItem.getCharge(it)}).") + } ?: reject(player, "${item.name} is not a distinct(#) item.") + } + } + + else -> { + reject(player, "Please enter valid flags: no flag/empty, s, d, sd.", "Use ::charge for examples.") + } + } + } + } + + /** + * Search for item in player and return item and container if found. Return null if not found + */ + private fun getItemAndContainer(player: Player, id: Int): Pair? { + if (id !in 0 until ItemDefinition.getDefinitions().size) return null + arrayOf(player.inventory, player.equipment, player.bankPrimary, player.bankSecondary).forEach { container -> + container.toArray().firstOrNull { it?.id == id }?.let { return Pair(it, container) } + } + return null } }