diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json
index 61ddd7589..c1bff50f6 100644
--- a/Server/data/configs/item_configs.json
+++ b/Server/data/configs/item_configs.json
@@ -115906,8 +115906,8 @@
"bonuses": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
},
{
- "destroy_message": "You can get another of these from Doric.",
- "examine": "Doric's instructions for the task he gave you.",
+ "destroy_message": "You can get another by talking about jobs with citizens and tutors around Lumbridge.",
+ "examine": "This reminds me of my current job and who set me to the task.",
"durability": null,
"name": "Task list",
"tradeable": "false",
diff --git a/Server/src/main/content/global/activity/jobs/CancelJobDialogueFile.kt b/Server/src/main/content/global/activity/jobs/CancelJobDialogueFile.kt
deleted file mode 100644
index 62238ad76..000000000
--- a/Server/src/main/content/global/activity/jobs/CancelJobDialogueFile.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package content.global.activity.jobs
-
-import core.api.*
-import content.global.activity.jobs.impl.GatheringJobs
-import content.global.activity.jobs.impl.SlayingJobs
-import core.game.dialogue.FacialExpression
-import core.game.node.entity.npc.NPC
-import core.game.node.item.Item
-import org.rs09.consts.Items
-import core.game.dialogue.DialogueFile
-import core.game.dialogue.Topic
-import core.tools.END_DIALOGUE
-import core.tools.START_DIALOGUE
-
-class CancelJobDialogueFile : DialogueFile() {
- override fun handle(componentID: Int, buttonID: Int) {
- when (stage) {
- START_DIALOGUE -> showTopics(
- Topic(core.game.dialogue.FacialExpression.HALF_ASKING, "How am I doing on my job?", 1),
- Topic(core.game.dialogue.FacialExpression.HALF_GUILTY, "I'd like to cancel my job.", 10)
- )
-
- 1 -> {
- val jobType = JobType.values()[getAttribute(player!!, "jobs:type", 0)]
- val jobId = getAttribute(player!!, "jobs:id", 0)
- val needed = getAttribute(player!!, "jobs:amount", 0)
-
- when (jobType) {
- JobType.GATHERING -> {
- sendItemDialogue(
- player!!,
- Item(GatheringJobs.values()[jobId].itemId),
- "You still need to gather $needed more."
- )
- }
-
- JobType.SLAYING -> {
- sendDialogue(
- player!!,
- "You still need to kill $needed more ${NPC(SlayingJobs.values()[jobId].ids[0]).name.lowercase()}"
- )
- }
- }
-
- stage = END_DIALOGUE
- }
-
- 10 -> npc("It will cost 500 coins.")
- .also { stage++ }
-
- 11 -> showTopics(
- Topic(core.game.dialogue.FacialExpression.HAPPY, "Yes, please.", 20),
- Topic(core.game.dialogue.FacialExpression.NEUTRAL, "No, thanks.", 30)
- )
-
- 20 -> npc("Alright then, hand over the money.")
- .also { stage++ }
-
- 21 -> if (inInventory(player!!, Items.COINS_995, 500)) {
- player("Here you go.")
-
- removeItem(player!!, Item(Items.COINS_995, 500))
- removeAttribute(player!!, "jobs:id")
- removeAttribute(player!!, "jobs:amount")
- removeAttribute(player!!, "jobs:original_amount")
- removeAttribute(player!!, "jobs:type")
-
- stage = END_DIALOGUE
- } else {
- player(
- core.game.dialogue.FacialExpression.HALF_WORRIED,
- "Oh, I don't seem to have the money..."
- ).also { stage++ }
- }
-
- 22 -> npc("Ah, that sucks! Get to work.")
- .also { stage = END_DIALOGUE }
-
- 30 -> npc("Alright then, get to work!")
- .also { stage = END_DIALOGUE }
- }
- }
-}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/CheckJobDialogueFile.kt b/Server/src/main/content/global/activity/jobs/CheckJobDialogueFile.kt
new file mode 100644
index 000000000..fc3a2e58a
--- /dev/null
+++ b/Server/src/main/content/global/activity/jobs/CheckJobDialogueFile.kt
@@ -0,0 +1,174 @@
+package content.global.activity.jobs
+
+import content.global.activity.jobs.impl.BoneBuryingJobs
+import content.global.activity.jobs.impl.CombatJobs
+import content.global.activity.jobs.impl.ProductionJobs
+import core.api.addItem
+import core.api.amountInInventory
+import core.api.hasAnItem
+import core.game.dialogue.DialogueFile
+import core.game.dialogue.FacialExpression
+import core.game.dialogue.IfTopic
+import core.game.dialogue.Topic
+import core.game.node.entity.npc.NPC
+import core.game.node.item.Item
+import core.tools.END_DIALOGUE
+import core.tools.START_DIALOGUE
+import core.tools.StringUtils
+import org.rs09.consts.Items
+
+/**
+ * The dialogue file to handle a player asking about work with the employer that assigned them
+ * their current job.
+ */
+class CheckJobDialogueFile : DialogueFile() {
+ companion object {
+ const val TURN_IN_ITEMS_1 = 1
+ const val TURN_IN_ITEMS_2 = 2
+ const val TURN_IN_ITEMS_3 = 3
+ const val TURN_IN_ITEMS_4 = 4
+ const val TURN_IN_ITEMS_5 = 5
+ const val REMIND_JOB = 101
+ const val GET_TASK_LIST = 201
+ const val GET_NEW_JOB_1 = 301
+ const val GET_NEW_JOB_2 = 302
+ const val GET_NEW_JOB_3 = 303
+ const val GET_NEW_JOB_4 = 304
+ const val GET_NEW_JOB_5 = 305
+ const val GET_NEW_JOB_NONE = 306
+ const val GET_NEW_JOB_REACHED_LIMIT = 307
+ }
+
+ override fun handle(componentID: Int, buttonID: Int) {
+ val playerJobManager: JobManager = JobManager.getInstance(player!!)
+ val job = playerJobManager.job ?: return
+ val originalAmount = playerJobManager.jobOriginalAmount
+
+ when (stage) {
+ START_DIALOGUE -> showTopics(
+ IfTopic(
+ FacialExpression.HAPPY,
+ "I have the items you wanted.",
+ TURN_IN_ITEMS_1,
+ (job.type == JobType.PRODUCTION && amountInInventory(player!!, (job as ProductionJobs).itemId) > 0)
+ ), Topic(
+ FacialExpression.HALF_ASKING, "Can you remind me what job you gave me?", REMIND_JOB
+ ), Topic(
+ FacialExpression.HALF_ASKING, "Can I have a task list to remind me of my job?", GET_TASK_LIST
+ ), Topic(
+ FacialExpression.HALF_GUILTY, "I'd like a new job please.", GET_NEW_JOB_1
+ )
+ )
+
+ TURN_IN_ITEMS_1 -> {
+ job as ProductionJobs
+ val itemName = Item(job.itemId).name.lowercase()
+ val itemNamePluralized = if (playerJobManager.jobAmount > 1) StringUtils.plusS(itemName) else itemName
+ npcl("Splendid! Now, I needed $originalAmount $itemNamePluralized.")
+ stage = TURN_IN_ITEMS_2
+ }
+
+ TURN_IN_ITEMS_2 -> {
+ if (playerJobManager.jobAmount == originalAmount) {
+ npcl("So far, you haven't brought me any, so I'm waiting for ${playerJobManager.jobAmount}.")
+ } else {
+ npcl("So far, you have brought me ${originalAmount - playerJobManager.jobAmount}, so I'm waiting for ${playerJobManager.jobAmount}.")
+ }
+ stage = TURN_IN_ITEMS_3
+ }
+
+ TURN_IN_ITEMS_3 -> {
+ if (amountInInventory(player!!, (job as ProductionJobs).itemId) >= playerJobManager.jobAmount) {
+ npcl("I can see that you have plenty, would you like to hand some over to complete your job?")
+ } else {
+ npcl("I can see that you have some, would you like to hand them over?")
+ }
+ stage = TURN_IN_ITEMS_4
+ }
+
+ TURN_IN_ITEMS_4 -> showTopics(
+ Topic("Yes, I'll give you what I have.", TURN_IN_ITEMS_5, skipPlayer = true),
+ Topic("No, I'll keep hold of it for now.", END_DIALOGUE, skipPlayer = true)
+ )
+
+ TURN_IN_ITEMS_5 -> {
+ playerJobManager.turnInItems()
+
+ if (playerJobManager.jobAmount == 0) {
+ playerJobManager.rewardPlayer()
+ npcl("There you go, thanks for your help. You're quite a skilled worker!")
+ } else {
+ npcl("Thanks for your help. Come back when you've got more!")
+ }
+ stage = END_DIALOGUE
+ }
+
+ REMIND_JOB -> {
+ when (job.type) {
+ JobType.PRODUCTION -> {
+ job as ProductionJobs
+ val itemName = Item(job.itemId).name.lowercase()
+ val itemNamePluralized =
+ if (playerJobManager.jobAmount > 1) StringUtils.plusS(itemName) else itemName
+ npcl("You still need to gather ${playerJobManager.jobAmount} more $itemNamePluralized.")
+ }
+
+ JobType.BONE_BURYING -> {
+ job as BoneBuryingJobs
+ val boneName = Item(job.boneIds.first()).name.lowercase()
+ val boneNamePluralized =
+ if (playerJobManager.jobAmount > 1) StringUtils.plusS(boneName) else boneName
+ npcl("You still need to bury ${playerJobManager.jobAmount} more $boneNamePluralized in the ${job.buryArea.title}.")
+ }
+
+ JobType.COMBAT -> {
+ job as CombatJobs
+ val monsterName = NPC(job.monsterIds.first()).name.lowercase()
+ val monsterNamePluralized =
+ if (playerJobManager.jobAmount > 1) StringUtils.plusS(monsterName) else monsterName
+ npcl("You still need to kill ${playerJobManager.jobAmount} more $monsterNamePluralized.")
+ }
+ }
+ stage = END_DIALOGUE
+ }
+
+ GET_TASK_LIST -> {
+ if (hasAnItem(player!!, Items.TASK_LIST_13464).exists()) {
+ npcl("You already have one of those!")
+ } else {
+ when (addItem(player!!, Items.TASK_LIST_13464)) {
+ true -> npcl("Here you go then.")
+ false -> dialogue("You don't have enough space in your inventory.")
+ }
+ }
+
+ stage = END_DIALOGUE
+ }
+
+ GET_NEW_JOB_1 -> npcl("Let me see if I've got any work for you.").also {
+ if (playerJobManager.hasReachedJobLimit()) {
+ stage = GET_NEW_JOB_REACHED_LIMIT
+ } else {
+ playerJobManager.generate(npc!!)
+ stage = if (playerJobManager.hasJob()) GET_NEW_JOB_2 else GET_NEW_JOB_NONE
+ }
+ }
+
+ GET_NEW_JOB_2 -> npcl(playerJobManager.getAssignmentMessage()).also { stage = GET_NEW_JOB_3 }
+ // Historically, the player would also have the option to ask for an easier task
+ GET_NEW_JOB_3 -> playerl("Okay, off I go!").also { stage = GET_NEW_JOB_4 }
+ GET_NEW_JOB_4 -> npcl("There, I've updated your task list to show your new job. Best of luck!").also {
+ stage = GET_NEW_JOB_5
+ }
+
+ GET_NEW_JOB_5 -> playerl("Thanks.").also { stage = END_DIALOGUE }
+ GET_NEW_JOB_NONE -> npcl("I'm sorry, I don't currently have any jobs that you're qualified for.").also {
+ stage = END_DIALOGUE
+ }
+
+ GET_NEW_JOB_REACHED_LIMIT -> npcl("You've hit your limit for the day. Come back tomorrow!").also {
+ stage = END_DIALOGUE
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/Job.kt b/Server/src/main/content/global/activity/jobs/Job.kt
new file mode 100644
index 000000000..f4558328c
--- /dev/null
+++ b/Server/src/main/content/global/activity/jobs/Job.kt
@@ -0,0 +1,23 @@
+package content.global.activity.jobs
+
+import content.global.activity.jobs.impl.Employers
+import core.tools.RandomFunction
+
+/**
+ * The interface for a job that a player can be assigned.
+ *
+ * TODO: Once player data is stored in a database with support for migrations, implement a per-player field to store
+ * the player's current employer, so that multiple employers can assign the same job, but the player will only be
+ * able to turn the job into the employer that assigned it to them.
+ */
+interface Job {
+ val type: JobType
+ val lower: Int
+ val upper: Int
+
+ val employer: Employers
+
+ fun getAmount(): Int {
+ return RandomFunction.random(lower, upper + 1)
+ }
+}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/JobManager.kt b/Server/src/main/content/global/activity/jobs/JobManager.kt
index cfeac8b51..001aeae6f 100644
--- a/Server/src/main/content/global/activity/jobs/JobManager.kt
+++ b/Server/src/main/content/global/activity/jobs/JobManager.kt
@@ -1,114 +1,215 @@
package content.global.activity.jobs
+import content.global.activity.jobs.impl.BoneBuryingJobs
+import content.global.activity.jobs.impl.CombatJobs
+import content.global.activity.jobs.impl.ProductionJobs
+import core.ServerStore
+import core.ServerStore.Companion.getInt
import core.api.*
-import content.global.activity.jobs.impl.GatheringJobs
-import content.global.activity.jobs.impl.SlayingJobs
+import core.game.event.BoneBuryEvent
+import core.game.event.EventHook
+import core.game.event.JobAssignmentEvent
+import core.game.event.NPCKillEvent
+import core.game.node.entity.Entity
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.item.Item
+import core.tools.StringUtils
import org.json.simple.JSONObject
import org.rs09.consts.Items
-import core.ServerStore
-import core.ServerStore.Companion.getInt
+import java.lang.Integer.min
-object JobManager {
- @JvmStatic
- fun updateJobRemaining(player: Player, amount: Int = 0) {
- val cur = getAttribute(player, "jobs:amount", 0)
- val new = cur - amount
- val jobType = JobType.values()[getAttribute(player, "jobs:type", 0)]
- val jobId = getAttribute(player, "jobs:id", 0)
+/**
+ * Manages the player's job data & progression.
+ *
+ * @source https://runescape.wiki/w/Jobs?oldid=831782
+ */
+class JobManager(val player: Player? = null) : LoginListener, PersistPlayer {
+ var job: Job? = null
+ var jobAmount: Int = 0
+ var jobOriginalAmount: Int = 0
- when (jobType) {
- JobType.GATHERING -> { }
+ private fun hasLevelRequirement(job: ProductionJobs): Boolean {
+ if (player == null) return false
+ if (player.isArtificial) return false
- JobType.SLAYING -> {
- val job = SlayingJobs.values()[jobId]
-
- if (new % 5 == 0 && new > 0) {
- sendMessage(
- player,
- "You have $new ${NPC(job.ids[0]).name.lowercase()} left to go."
- )
- } else if (new == 0) {
- sendMessage(
- player,
- "
You have completed your job."
- )
- }
- }
- }
-
- setAttribute(player, "jobs:amount", new)
+ return getStatLevel(player, job.skill) >= job.lvlReq
}
- @JvmStatic
- fun rewardPlayer(player: Player, npc: NPC) {
- val amt = getAttribute(player, "jobs:original_amount", 0)
- val jobType = JobType.values()[getAttribute(player, "jobs:type", 0)]
- val jobId = getAttribute(player, "jobs:id", 0)
+ fun hasJob(): Boolean = this.job != null
- when (jobType) {
- JobType.GATHERING -> {
- val it = Item(GatheringJobs.values()[jobId].itemId)
- var amount = amountInInventory(player, it.id)
- val needed = getAttribute(player, "jobs:amount", 0)
+ // Historically the job limit would be 6 jobs per hour of play-time, using 3 per day for balance purposes
+ fun hasReachedJobLimit(): Boolean = player?.let { getStoreFile().getInt(it.username.lowercase()) >= 3 } ?: false
- if (amount == 0) {
- openDialogue(player, CancelJobDialogueFile(), npc)
- return
- }
+ fun generate(npc: NPC) {
+ if (player == null) return
+ if (player.isArtificial) return
- if (amount < needed) {
- sendItemDialogue(
- player,
- GatheringJobs.values()[jobId].itemId,
- "You still need to gather ${needed - amount} more."
- )
+ val instance = getInstance(player)
- removeItem(player, Item(it.id, amount))
- setAttribute(player, "jobs:amount", needed - amount)
-
- return
- }
-
- if (amount > needed) {
- amount = needed
- }
-
- removeItem(player, Item(it.id, amount))
- }
-
- JobType.SLAYING -> {
- val needed = getAttribute(player, "jobs:amount", 0)
-
- if (needed > 0) {
- sendDialogue(
- player,
- "You still need to kill $needed more ${NPC(SlayingJobs.values()[jobId].ids[0]).name.lowercase()}."
- )
-
- return
- }
-
- sendDialogue(
- player,
- "Excellent work, thank you! Here's your reward."
+ // Create a list of jobs that the player can do and are given by the NPC being interacted with
+ val potentialJobs: List = (
+ ProductionJobs.values().filter { hasLevelRequirement(it) }
+ + BoneBuryingJobs.values()
+ + CombatJobs.values()
)
- }
- }
+ .filter { job -> job.employer.npcId == npc.id }
- addItemOrDrop(player, Items.COINS_995, 250 * amt)
+ val job = potentialJobs.randomOrNull() ?: return
- removeAttribute(player, "jobs:id")
- removeAttribute(player, "jobs:amount")
- removeAttribute(player, "jobs:original_amount")
- removeAttribute(player, "jobs:type")
+ instance.job = job
+ this.jobAmount = job.getAmount()
+ this.jobOriginalAmount = instance.jobAmount
+ player.dispatch(JobAssignmentEvent(job.type, npc))
getStoreFile()[player.username.lowercase()] = getStoreFile().getInt(player.username.lowercase()) + 1
}
- fun getStoreFile(): JSONObject {
- return ServerStore.getArchive("daily-jobs-tracking")
+ fun getAssignmentMessage(): String? {
+ if (player == null) return null
+ if (player.isArtificial) return null
+
+ val instance = getInstance(player)
+ val job = instance.job ?: return null
+
+ return when (job.type) {
+ JobType.PRODUCTION -> {
+ job as ProductionJobs
+ val itemName = Item(job.itemId).name.lowercase()
+ val itemNamePluralized = if (this.jobAmount > 1) StringUtils.plusS(itemName) else itemName
+ "Okay, your job is to bring me ${this.jobAmount} $itemNamePluralized."
+ }
+
+ JobType.BONE_BURYING -> {
+ job as BoneBuryingJobs
+ val boneName = Item(job.boneIds.first()).name.lowercase()
+ val boneNamePluralized = if (this.jobAmount > 1) StringUtils.plusS(boneName) else boneName
+ "Okay, your job is to bury ${this.jobAmount} $boneNamePluralized in the ${job.buryArea.title}."
+ }
+
+ JobType.COMBAT -> {
+ job as CombatJobs
+ val monsterName = NPC(job.monsterIds.first()).name.lowercase()
+ val monsterNamePluralized = if (this.jobAmount > 1) StringUtils.plusS(monsterName) else monsterName
+ "Okay, your job is to kill ${this.jobAmount} $monsterNamePluralized."
+ }
+ }
+ }
+
+ /**
+ * TODO: Allow players to turn-in noted items (authentic to 2009)
+ */
+ fun turnInItems() {
+ if (player == null) return
+ if (player.isArtificial) return
+
+ val instance = getInstance(player)
+ val job = instance.job ?: return
+ if (job.type != JobType.PRODUCTION) return
+
+ val itemId = (job as ProductionJobs).itemId
+ val amountHeld = amountInInventory(player, itemId)
+ val amountToTurnIn = min(amountHeld, instance.jobAmount)
+ removeItem(player, Item(itemId, amountToTurnIn))
+ instance.jobAmount -= amountToTurnIn
+ }
+
+ fun rewardPlayer() {
+ if (player == null) return
+ if (player.isArtificial) return
+
+ val instance = getInstance(player)
+
+ addItemOrDrop(player, Items.COINS_995, 250 * instance.jobOriginalAmount)
+
+ instance.job = null
+ instance.jobAmount = -1
+ instance.jobOriginalAmount = -1
+ }
+
+ override fun login(player: Player) {
+ if (player.isArtificial) return
+
+ val jobManager = JobManager(player)
+ setAttribute(player, "job-manager", jobManager)
+
+ player.hook(Event.BoneBuried, object : EventHook {
+ override fun process(entity: Entity, event: BoneBuryEvent) {
+ if (entity !is Player) return
+ if (entity.isArtificial) return
+
+ val instance = getInstance(entity)
+
+ if (!instance.hasJob()) return
+ if (instance.job?.type != JobType.BONE_BURYING) return
+ if (instance.jobAmount == 0) return
+
+ val job = (instance.job as BoneBuryingJobs)
+ if (inBorders(entity, job.buryArea.zoneBorder) && event.boneId in job.boneIds) {
+ instance.jobAmount--
+ }
+ }
+ })
+ player.hook(Event.NPCKilled, object : EventHook {
+ override fun process(entity: Entity, event: NPCKillEvent) {
+ if (entity !is Player) return
+ if (entity.isArtificial) return
+
+ val instance = getInstance(entity)
+
+ if (!instance.hasJob()) return
+ if (instance.job?.type != JobType.COMBAT) return
+ if (instance.jobAmount == 0) return
+
+ val job = (instance.job as CombatJobs)
+ if (event.npc.id in job.monsterIds) {
+ instance.jobAmount--
+ }
+ }
+ })
+ }
+
+ // TODO: Implement proper saving of job data to JSONObject once player save migration is possible
+ override fun savePlayer(player: Player, save: JSONObject) {
+ val instance = getInstance(player)
+ val jobId: Int = instance.job?.let {
+ when (it.type) {
+ JobType.PRODUCTION -> ProductionJobs.values().indexOf(it)
+ JobType.COMBAT -> CombatJobs.values().indexOf(it)
+ JobType.BONE_BURYING -> BoneBuryingJobs.values().indexOf(it)
+ }
+ } ?: -1
+
+ setAttribute(player, "/save:jobs:id", jobId)
+ setAttribute(player, "/save:jobs:type", instance.job?.type?.ordinal ?: -1)
+ setAttribute(player, "/save:jobs:amount", instance.jobAmount)
+ setAttribute(player, "/save:jobs:original_amount", instance.jobOriginalAmount)
+ }
+
+ // TODO: Implement proper loading of job data from JSONObject once player save migration is possible
+ override fun parsePlayer(player: Player, data: JSONObject) {
+ val instance = getInstance(player)
+ val jobId = getAttribute(player, "jobs:id", -1)
+ val jobType = getAttribute(player, "jobs:type", -1)
+
+ instance.job = when (JobType.values().getOrNull(jobType)) {
+ JobType.PRODUCTION -> ProductionJobs.values().getOrNull(jobId)
+ JobType.COMBAT -> CombatJobs.values().getOrNull(jobId)
+ JobType.BONE_BURYING -> BoneBuryingJobs.values().getOrNull(jobId)
+ else -> null
+ }
+ instance.jobAmount = getAttribute(player, "jobs:amount", -1)
+ instance.jobOriginalAmount = getAttribute(player, "jobs:original_amount", -1)
+ }
+
+ companion object {
+ @JvmStatic
+ fun getInstance(player: Player): JobManager {
+ return player.getAttribute("job-manager", JobManager(player))
+ }
+
+ fun getStoreFile(): JSONObject {
+ return ServerStore.getArchive("daily-jobs-tracking")
+ }
}
}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/JobType.kt b/Server/src/main/content/global/activity/jobs/JobType.kt
index fc1c6bb65..c34a67811 100644
--- a/Server/src/main/content/global/activity/jobs/JobType.kt
+++ b/Server/src/main/content/global/activity/jobs/JobType.kt
@@ -1,6 +1,13 @@
package content.global.activity.jobs
+/**
+ * An enum of the different types of jobs that a player can be assigned.
+ *
+ * Note: Due to how player save files keep track of the player's current job, it is essential that
+ * new entries are only appended to the end of this enum, and the ordering of existing entries is not changed.
+ */
enum class JobType {
- GATHERING,
- SLAYING
+ PRODUCTION,
+ COMBAT,
+ BONE_BURYING
}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/StartJobDialogueFile.kt b/Server/src/main/content/global/activity/jobs/StartJobDialogueFile.kt
new file mode 100644
index 000000000..b431663e7
--- /dev/null
+++ b/Server/src/main/content/global/activity/jobs/StartJobDialogueFile.kt
@@ -0,0 +1,139 @@
+package content.global.activity.jobs
+
+import content.global.activity.jobs.impl.BoneBuryingJobs
+import content.global.activity.jobs.impl.CombatJobs
+import content.global.activity.jobs.impl.ProductionJobs
+import core.api.addItem
+import core.api.hasAnItem
+import core.game.dialogue.DialogueFile
+import core.game.dialogue.FacialExpression
+import core.game.dialogue.Topic
+import core.game.node.entity.npc.NPC
+import core.game.node.item.Item
+import core.tools.END_DIALOGUE
+import core.tools.START_DIALOGUE
+import core.tools.StringUtils
+import org.rs09.consts.Items
+
+/**
+ * The dialogue file to handle a player asking about work with any employer which they aren't currently working for.
+ */
+class StartJobDialogueFile : DialogueFile() {
+ companion object {
+ const val GET_TASK_LIST_1 = 1
+ const val GET_TASK_LIST_2 = 2
+ const val GET_TASK_LIST_YES = 3
+ const val GET_JOB_1 = 101
+ const val GET_JOB_2 = 102
+ const val GET_JOB_3 = 103
+ const val GET_JOB_4 = 104
+ const val GET_JOB_5 = 105
+ const val GET_JOB_NONE = 201
+ const val GET_JOB_EMPLOYED_1 = 301
+ const val GET_JOB_EMPLOYED_2 = 302
+ const val GET_JOB_LIMIT_REACHED = 401
+ }
+
+ override fun handle(componentID: Int, buttonID: Int) {
+ val playerJobManager: JobManager = JobManager.getInstance(player!!)
+
+ when (stage) {
+ START_DIALOGUE -> playerl(FacialExpression.ASKING, "Do you have any jobs for me?").also {
+ stage = if (hasAnItem(player!!, Items.TASK_LIST_13464).exists()) {
+ if (playerJobManager.hasJob()) {
+ GET_JOB_EMPLOYED_1
+ } else {
+ GET_JOB_1
+ }
+ } else {
+ GET_TASK_LIST_1
+ }
+ }
+
+ GET_TASK_LIST_1 -> npcl("Well, first of all, I can see that you don't have a task list to remind you about your current job. Would you like a task list now?").also {
+ stage = GET_TASK_LIST_2
+ }
+
+ GET_TASK_LIST_2 -> showTopics(
+ Topic("Yes, please.", GET_TASK_LIST_YES),
+ Topic("No, thanks.", if (playerJobManager.hasJob()) GET_JOB_EMPLOYED_1 else GET_JOB_1)
+ )
+
+ GET_TASK_LIST_YES -> {
+ when (addItem(player!!, Items.TASK_LIST_13464)) {
+ true -> npcl("Here you go then.").also {
+ stage = if (playerJobManager.hasJob()) GET_JOB_EMPLOYED_1 else GET_JOB_1
+ }
+
+ false -> dialogue("You don't have enough space in your inventory.").also { stage = END_DIALOGUE }
+ }
+ }
+
+ GET_JOB_1 -> npcl("Let me see if I've got any work for you.").also {
+ if (playerJobManager.hasReachedJobLimit()) {
+ stage = GET_JOB_LIMIT_REACHED
+ } else {
+ playerJobManager.generate(npc!!)
+ stage = if (playerJobManager.hasJob()) GET_JOB_2 else GET_JOB_NONE
+ }
+ }
+
+ GET_JOB_2 -> npcl(playerJobManager.getAssignmentMessage()).also { stage = GET_JOB_3 }
+ // Historically, the player would also have the option to ask for an easier task
+ GET_JOB_3 -> playerl("Okay, off I go!").also { stage = GET_JOB_4 }
+ GET_JOB_4 -> npcl("There, I've updated your task list to show your new job. Best of luck!").also {
+ stage = GET_JOB_5
+ }
+
+ GET_JOB_5 -> playerl("Thanks.").also { stage = END_DIALOGUE }
+ GET_JOB_EMPLOYED_1 -> {
+ val job = playerJobManager.job ?: return
+ val employerName = NPC(job.employer.npcId).name
+ val amount = playerJobManager.jobAmount
+ val originalAmount = playerJobManager.jobOriginalAmount
+
+ if (amount == 0) {
+ npcl("Hang about. Aren't you working for $employerName? It looks like you've complete their job, return to them for your reward.")
+ stage = END_DIALOGUE
+ } else {
+ when (job.type) {
+ JobType.PRODUCTION -> {
+ job as ProductionJobs
+ val itemName = Item(job.itemId).name.lowercase()
+ val itemNamePluralized = if (originalAmount > 1) StringUtils.plusS(itemName) else itemName
+ npcl("Hang about. Aren't you working for $employerName? You were asked for $originalAmount $itemNamePluralized and still have $amount more to go.")
+ }
+
+ JobType.BONE_BURYING -> {
+ job as BoneBuryingJobs
+ val boneName = Item(job.boneIds.first()).name.lowercase()
+ val boneNamePluralized = if (originalAmount > 1) StringUtils.plusS(boneName) else boneName
+ npcl("Hang about. Aren't you working for $employerName? You were asked to bury $originalAmount $boneNamePluralized in the ${job.buryArea.title} and still have $amount more to go.")
+ }
+
+ JobType.COMBAT -> {
+ job as CombatJobs
+ val monsterName = NPC(job.monsterIds.first()).name.lowercase()
+ val monsterNamePluralized = if (originalAmount > 1) StringUtils.plusS(monsterName) else monsterName
+ npcl("Hang about. Aren't you working for $employerName? You were asked to kill $originalAmount $monsterNamePluralized and still have $amount more to go.")
+ }
+ }
+ stage = GET_JOB_EMPLOYED_2
+ }
+ }
+
+ GET_JOB_EMPLOYED_2 -> showTopics(
+ Topic("Oh yes. I better go and finish their job first.", END_DIALOGUE),
+ Topic("I'd like a new job instead, please.", GET_JOB_1)
+ )
+
+ GET_JOB_NONE -> npcl("I'm sorry, I don't currently have any jobs that you're qualified for.").also {
+ stage = END_DIALOGUE
+ }
+
+ GET_JOB_LIMIT_REACHED -> npcl("You've hit your limit for the day. Come back tomorrow!").also {
+ stage = END_DIALOGUE
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/TaskListReadListener.kt b/Server/src/main/content/global/activity/jobs/TaskListReadListener.kt
new file mode 100644
index 000000000..2e3c06d7b
--- /dev/null
+++ b/Server/src/main/content/global/activity/jobs/TaskListReadListener.kt
@@ -0,0 +1,70 @@
+package content.global.activity.jobs
+
+import content.global.activity.jobs.impl.BoneBuryingJobs
+import content.global.activity.jobs.impl.CombatJobs
+import content.global.activity.jobs.impl.ProductionJobs
+import core.api.sendDialogue
+import core.api.sendItemDialogue
+import core.api.sendNPCDialogue
+import core.game.interaction.InteractionListener
+import core.game.node.entity.npc.NPC
+import core.game.node.item.Item
+import core.tools.StringUtils
+import org.rs09.consts.Items
+
+/**
+ * Handles the read option of the Task List item.
+ */
+class TaskListReadListener : InteractionListener {
+ override fun defineListeners() {
+ on(Items.TASK_LIST_13464, ITEM, "read") { player, _ ->
+ val playerJobManager: JobManager = JobManager.getInstance(player)
+ val job = playerJobManager.job
+ val amount = playerJobManager.jobAmount
+
+ if (job == null) {
+ sendDialogue(player, "I have not been assigned a job!")
+ return@on false
+ }
+
+ if(amount == 0) {
+ sendDialogue(
+ player, "I have completed my job. I should return to ${NPC(job.employer.npcId).name} for my reward."
+ )
+ return@on true
+ }
+
+ when (job.type) {
+ JobType.PRODUCTION -> {
+ job as ProductionJobs
+ val itemName = Item(job.itemId).name.lowercase()
+ val itemNamePluralized = if (amount > 1) StringUtils.plusS(itemName) else itemName
+ sendItemDialogue(
+ player, job.itemId, "My job is to gather $amount more $itemNamePluralized."
+ )
+ }
+
+ JobType.BONE_BURYING -> {
+ job as BoneBuryingJobs
+ val boneName = Item(job.boneIds.first()).name.lowercase()
+ val boneNamePluralized = if (amount > 1) StringUtils.plusS(boneName) else boneName
+ sendItemDialogue(
+ player,
+ job.boneIds.first(),
+ "My job is to bury $amount more $boneNamePluralized in the ${job.buryArea.title}."
+ )
+ }
+
+ JobType.COMBAT -> {
+ job as CombatJobs
+ val monsterName = NPC(job.monsterIds.first()).name.lowercase()
+ val monsterNamePluralized = if (amount > 1) StringUtils.plusS(monsterName) else monsterName
+ sendNPCDialogue(
+ player, job.monsterIds.first(), "My job is to kill $amount more $monsterNamePluralized."
+ )
+ }
+ }
+ return@on true
+ }
+ }
+}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/WorkForInteractionListener.kt b/Server/src/main/content/global/activity/jobs/WorkForInteractionListener.kt
index ad987feea..8d09d7dfd 100644
--- a/Server/src/main/content/global/activity/jobs/WorkForInteractionListener.kt
+++ b/Server/src/main/content/global/activity/jobs/WorkForInteractionListener.kt
@@ -1,224 +1,45 @@
package content.global.activity.jobs
-import content.global.activity.jobs.impl.GatheringJobs
-import content.global.activity.jobs.impl.SlayingJobs
-import core.api.*
-import core.game.event.EventHook
-import core.game.event.JobAssignmentEvent
-import core.game.event.NPCKillEvent
-import core.game.node.entity.Entity
-import core.game.node.entity.npc.NPC
-import core.game.node.entity.player.Player
-import core.game.node.item.Item
-import org.rs09.consts.Items
-import org.rs09.consts.NPCs
-import core.ServerStore.Companion.getInt
-import core.api.Event
-import core.game.interaction.InteractionListener
+import content.global.activity.jobs.impl.Employers
+import core.api.openDialogue
+import core.api.sendNPCDialogue
import core.game.interaction.IntType
+import core.game.interaction.InteractionListener
+import core.game.node.entity.npc.NPC
/**
* Handles the work-for actions for the NPCs
* @author Ceikry
* @author vddCore
*/
-class WorkForInteractionListener : InteractionListener, LoginListener {
- companion object {
- private val WEAPON_ICONS = arrayListOf(
- Items.BRONZE_SCIMITAR_1321,
- Items.STEEL_SCIMITAR_1325,
- Items.RUNE_SCIMITAR_1333,
- Items.BRONZE_MACE_1422,
- Items.MITHRIL_WARHAMMER_1343
- )
-
- private val EMPLOYERS_AND_JOBS = mapOf(
- NPCs.HANS_0 to listOf(
- GatheringJobs.AIR_RUNE,
- GatheringJobs.COWHIDES,
- GatheringJobs.WATER_RUNE
- ),
-
- NPCs.AGGIE_922 to listOf(
- GatheringJobs.ASHES,
- GatheringJobs.AIR_RUNE,
- GatheringJobs.WATER_RUNE
- ),
-
- NPCs.GILLIE_GROATS_3807 to listOf(
- GatheringJobs.COWHIDES
- ),
-
- NPCs.COOKING_TUTOR_4899 to listOf(
- GatheringJobs.CAKE,
- GatheringJobs.ANCHOVY_PIZZA,
- GatheringJobs.COOKED_SALMON,
- GatheringJobs.COOKED_TROUT,
- GatheringJobs.MEAT_PIE,
- GatheringJobs.MEAT_PIZZA,
- GatheringJobs.PLAIN_PIZZA
- ),
-
- NPCs.FISHING_TUTOR_4901 to listOf(
- GatheringJobs.RAW_SALMON,
- GatheringJobs.RAW_SHRIMP,
- GatheringJobs.RAW_TROUT
- ),
-
- NPCs.MINING_TUTOR_4902 to listOf(
- GatheringJobs.COPPER_ORE,
- GatheringJobs.TIN_ORE,
- GatheringJobs.COAL,
- GatheringJobs.IRON_ORE,
- GatheringJobs.SILVER_ORE,
- GatheringJobs.GOLD_ORE
- ),
-
- NPCs.PRAYER_TUTOR_4903 to listOf(
- GatheringJobs.SILVER_ORE
- ),
-
- NPCs.SMELTING_TUTOR_4904 to listOf(
- GatheringJobs.BRONZE_BAR,
- GatheringJobs.IRON_BAR,
- GatheringJobs.STEEL_BAR
- ),
-
- NPCs.WOODCUTTING_TUTOR_4906 to listOf(
- GatheringJobs.LOG,
- GatheringJobs.OAK,
- GatheringJobs.WILLOW
- )
- )
-
- private val EMPLOYER_JOB_TYPES = mapOf(
- NPCs.HANS_0 to JobType.GATHERING,
- NPCs.MELEE_TUTOR_705 to JobType.SLAYING,
- NPCs.AGGIE_922 to JobType.GATHERING,
- NPCs.RANGED_TUTOR_1861 to JobType.SLAYING,
- NPCs.GILLIE_GROATS_3807 to JobType.GATHERING,
- NPCs.MAGIC_TUTOR_4707 to JobType.SLAYING,
- NPCs.COOKING_TUTOR_4899 to JobType.GATHERING,
- NPCs.FISHING_TUTOR_4901 to JobType.GATHERING,
- NPCs.MINING_TUTOR_4902 to JobType.GATHERING,
- NPCs.PRAYER_TUTOR_4903 to JobType.GATHERING,
- NPCs.SMELTING_TUTOR_4904 to JobType.GATHERING,
- NPCs.WOODCUTTING_TUTOR_4906 to JobType.GATHERING
- )
-
- private val EMPLOYER_IDS = EMPLOYER_JOB_TYPES.keys.toIntArray()
- }
-
-
- override fun login(player: Player) {
- val remaining = getAttribute(player, "jobs:amount", -1)
- val jobType = JobType.values()[getAttribute(player, "jobs:type", 0)]
-
- if (jobType == JobType.SLAYING && remaining > 0) {
- player.hook(Event.NPCKilled, JobKillHook)
- }
- }
-
+class WorkForInteractionListener : InteractionListener {
override fun defineListeners() {
- on(EMPLOYER_IDS, IntType.NPC, "work-for") { player, node ->
- if (JobManager.getStoreFile().getInt(player.username.lowercase()) == 3) {
- sendNPCDialogue(player, node.id, "You've hit your limit for the day. Come back tomorrow!")
+ on(
+ Employers.values().map { employer -> employer.npcId }.toIntArray(),
+ IntType.NPC,
+ "work-for"
+ ) { player, node ->
+ val playerJobManager = JobManager.getInstance(player)
+ val job = playerJobManager.job
+
+ // Check if the player is talking to the NPC that gave them their job
+ if (job != null && job.employer.npcId == node.id) {
+ if (playerJobManager.jobAmount == 0) {
+ playerJobManager.rewardPlayer()
+ sendNPCDialogue(
+ player,
+ job.employer.npcId,
+ "There you go, thanks for your help. You're quite a skilled worker!"
+ )
+ } else {
+ openDialogue(player, CheckJobDialogueFile(), node as NPC)
+ }
+
return@on true
}
- if (getAttribute(player, "jobs:id", -1) != -1) {
- JobManager.rewardPlayer(player, node.asNpc())
- return@on true
- }
-
- var amount: Int
- val jobType = EMPLOYER_JOB_TYPES[node.id] ?: return@on false
- val jobId = when (jobType) {
- JobType.GATHERING -> {
- val job = EMPLOYERS_AND_JOBS[node.id]?.filter { checkRequirement(player, it) }?.randomOrNull()
-
- if (job == null) {
- sendNPCDialogue(
- player,
- node.id,
- "I'm sorry, I don't currently have any jobs that you're qualified for.",
- core.game.dialogue.FacialExpression.HALF_THINKING
- )
- return@on true
- }
-
- amount = job.getAmount()
- job.ordinal
- }
-
- JobType.SLAYING -> {
- SlayingJobs.values().random().ordinal.also {
- amount = SlayingJobs.values()[it].getAmount()
- }
- }
- }
-
- when (jobType) {
- JobType.GATHERING -> {
- val job = GatheringJobs.values()[jobId]
-
- sendItemDialogue(
- player,
- job.itemId,
- "You are assigned to gather $amount ${Item(job.itemId).name.lowercase()}"
- )
- }
-
- JobType.SLAYING -> {
- val job = SlayingJobs.values()[jobId]
-
- sendItemDialogue(
- player,
- WEAPON_ICONS.random(),
- "You are assigned to kill $amount ${NPC(job.ids[0]).name.lowercase()}"
- )
-
- player.unhook(JobKillHook) //try to unhook just in case they already had it hooked, unlikely but possible.
- player.hook(Event.NPCKilled, JobKillHook)
- }
- }
-
- setAttribute(player, "/save:jobs:id", jobId)
- setAttribute(player, "/save:jobs:amount", amount)
- setAttribute(player, "/save:jobs:original_amount", amount)
- setAttribute(player, "/save:jobs:type", jobType.ordinal)
-
- player.dispatch(JobAssignmentEvent(jobType, node.asNpc()))
+ openDialogue(player, StartJobDialogueFile(), node as NPC)
return@on true
}
}
-
- private fun checkRequirement(player: Player, jobs: GatheringJobs?): Boolean {
- jobs ?: return true
-
- if (getStatLevel(player, jobs.skill) < jobs.lvlReq) {
- return false
- }
-
- return true
- }
-
- private object JobKillHook : EventHook {
- override fun process(entity: Entity, event: NPCKillEvent) {
- if (entity !is Player) return
- if (entity.isArtificial) return
-
- val job = getAttribute(entity, "jobs:id", -1)
-
- if (job !in 0 until SlayingJobs.values().size) {
- return
- }
-
- val ids = SlayingJobs.values()[job].ids
-
- if (event.npc.id in ids) {
- JobManager.updateJobRemaining(entity, 1)
- }
- }
- }
}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/impl/BoneBuryingJobs.kt b/Server/src/main/content/global/activity/jobs/impl/BoneBuryingJobs.kt
new file mode 100644
index 000000000..fad0448e0
--- /dev/null
+++ b/Server/src/main/content/global/activity/jobs/impl/BoneBuryingJobs.kt
@@ -0,0 +1,30 @@
+package content.global.activity.jobs.impl
+
+import content.global.activity.jobs.Job
+import content.global.activity.jobs.JobType
+import core.game.world.map.zone.ZoneBorders
+import org.rs09.consts.Items
+
+/**
+ * An enum of the possible bone-burying jobs that can be assigned to a player.
+ *
+ * Note: Due to how player save files keep track of the player's current job, it is essential that
+ * new entries are only appended to the end of this enum, and the ordering of existing entries is not changed.
+ */
+enum class BoneBuryingJobs(val buryArea: BuryArea, val boneIds: List, override val employer: Employers) : Job {
+ BURY_DRAYNOR(BuryArea.DRAYNOR_WHEAT_FIELD, Bones.BONES, Employers.PRAYER_TUTOR),
+ BURY_LUMBRIDGE_GRAVEYARD(BuryArea.LUMBRIDGE_GRAVEYARD, Bones.BONES, Employers.PRAYER_TUTOR);
+
+ override val type = JobType.BONE_BURYING
+ override val lower = 22
+ override val upper = 26
+
+ enum class BuryArea(val zoneBorder: ZoneBorders, val title: String) {
+ DRAYNOR_WHEAT_FIELD(ZoneBorders(3107, 3265, 3132, 3293), "wheat field east of Draynor Village"),
+ LUMBRIDGE_GRAVEYARD(ZoneBorders(3238, 3191, 3252, 3204), "Lumbridge Graveyard");
+ }
+
+ object Bones {
+ val BONES = listOf(Items.BONES_526, Items.BONES_2530)
+ }
+}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/impl/CombatJobs.kt b/Server/src/main/content/global/activity/jobs/impl/CombatJobs.kt
new file mode 100644
index 000000000..cfe444f51
--- /dev/null
+++ b/Server/src/main/content/global/activity/jobs/impl/CombatJobs.kt
@@ -0,0 +1,209 @@
+package content.global.activity.jobs.impl
+
+import content.global.activity.jobs.Job
+import content.global.activity.jobs.JobType
+import org.rs09.consts.NPCs
+
+/**
+ * An enum of the possible combat jobs that can be assigned to a player.
+ *
+ * Note: Due to how player save files keep track of the player's current job, it is essential that
+ * new entries are only appended to the end of this enum, and the ordering of existing entries is not changed.
+ */
+enum class CombatJobs(val monsterIds: List, override val employer: Employers) : Job {
+ CHICKENS(Monsters.CHICKENS, Employers.RANGED_TUTOR),
+ COWS(Monsters.COWS, Employers.MAGIC_TUTOR),
+ GIANT_SPIDERS(Monsters.GIANT_SPIDERS, Employers.MAGIC_TUTOR),
+ SCORPIONS(Monsters.SCORPIONS, Employers.MELEE_TUTOR),
+ GOBLINS(Monsters.GOBLINS, Employers.MELEE_TUTOR);
+
+ override val type = JobType.COMBAT
+ override val lower = 22
+ override val upper = 28
+
+ object Monsters {
+ val GIANT_SPIDERS = listOf(NPCs.GIANT_SPIDER_59, NPCs.GIANT_SPIDER_60)
+ val SCORPIONS = listOf(NPCs.SCORPION_107, NPCs.SCORPION_1477, NPCs.SCORPION_4402, NPCs.SCORPION_4403)
+ val GOBLINS = listOf(
+ NPCs.GOBLIN_100,
+ NPCs.GOBLIN_101,
+ NPCs.GOBLIN_102,
+ NPCs.GOBLIN_1769,
+ NPCs.GOBLIN_1770,
+ NPCs.GOBLIN_1771,
+ NPCs.GOBLIN_1772,
+ NPCs.GOBLIN_1773,
+ NPCs.GOBLIN_1774,
+ NPCs.GOBLIN_1775,
+ NPCs.GOBLIN_1776,
+ NPCs.GOBLIN_2274,
+ NPCs.GOBLIN_2275,
+ NPCs.GOBLIN_2276,
+ NPCs.GOBLIN_2277,
+ NPCs.GOBLIN_2278,
+ NPCs.GOBLIN_2279,
+ NPCs.GOBLIN_2280,
+ NPCs.GOBLIN_2281,
+ NPCs.GOBLIN_2678,
+ NPCs.GOBLIN_2679,
+ NPCs.GOBLIN_2680,
+ NPCs.GOBLIN_2681,
+ NPCs.GOBLIN_3264,
+ NPCs.GOBLIN_3265,
+ NPCs.GOBLIN_3266,
+ NPCs.GOBLIN_3267,
+ NPCs.GOBLIN_3726,
+ NPCs.GOBLIN_4261,
+ NPCs.GOBLIN_4262,
+ NPCs.GOBLIN_4263,
+ NPCs.GOBLIN_4264,
+ NPCs.GOBLIN_4265,
+ NPCs.GOBLIN_4266,
+ NPCs.GOBLIN_4267,
+ NPCs.GOBLIN_4268,
+ NPCs.GOBLIN_4269,
+ NPCs.GOBLIN_4270,
+ NPCs.GOBLIN_4271,
+ NPCs.GOBLIN_4272,
+ NPCs.GOBLIN_4273,
+ NPCs.GOBLIN_4274,
+ NPCs.GOBLIN_4275,
+ NPCs.GOBLIN_4276,
+ NPCs.GOBLIN_4407,
+ NPCs.GOBLIN_4408,
+ NPCs.GOBLIN_4409,
+ NPCs.GOBLIN_4410,
+ NPCs.GOBLIN_4411,
+ NPCs.GOBLIN_4412,
+ NPCs.GOBLIN_444,
+ NPCs.GOBLIN_445,
+ NPCs.GOBLIN_4479,
+ NPCs.GOBLIN_4480,
+ NPCs.GOBLIN_4481,
+ NPCs.GOBLIN_4482,
+ NPCs.GOBLIN_4483,
+ NPCs.GOBLIN_4484,
+ NPCs.GOBLIN_4485,
+ NPCs.GOBLIN_4486,
+ NPCs.GOBLIN_4487,
+ NPCs.GOBLIN_4488,
+ NPCs.GOBLIN_4489,
+ NPCs.GOBLIN_4490,
+ NPCs.GOBLIN_4491,
+ NPCs.GOBLIN_4492,
+ NPCs.GOBLIN_4499,
+ NPCs.GOBLIN_4633,
+ NPCs.GOBLIN_4634,
+ NPCs.GOBLIN_4635,
+ NPCs.GOBLIN_4636,
+ NPCs.GOBLIN_4637,
+ NPCs.GOBLIN_5855,
+ NPCs.GOBLIN_5856,
+ NPCs.GOBLIN_6125,
+ NPCs.GOBLIN_6126,
+ NPCs.GOBLIN_6132,
+ NPCs.GOBLIN_6133,
+ NPCs.GOBLIN_6279,
+ NPCs.GOBLIN_6280,
+ NPCs.GOBLIN_6281,
+ NPCs.GOBLIN_6282,
+ NPCs.GOBLIN_6283,
+ NPCs.GOBLIN_6284,
+ NPCs.GOBLIN_6402,
+ NPCs.GOBLIN_6403,
+ NPCs.GOBLIN_6404,
+ NPCs.GOBLIN_6405,
+ NPCs.GOBLIN_6406,
+ NPCs.GOBLIN_6407,
+ NPCs.GOBLIN_6408,
+ NPCs.GOBLIN_6409,
+ NPCs.GOBLIN_6410,
+ NPCs.GOBLIN_6411,
+ NPCs.GOBLIN_6412,
+ NPCs.GOBLIN_6413,
+ NPCs.GOBLIN_6414,
+ NPCs.GOBLIN_6415,
+ NPCs.GOBLIN_6416,
+ NPCs.GOBLIN_6417,
+ NPCs.GOBLIN_6418,
+ NPCs.GOBLIN_6419,
+ NPCs.GOBLIN_6420,
+ NPCs.GOBLIN_6421,
+ NPCs.GOBLIN_6422,
+ NPCs.GOBLIN_6423,
+ NPCs.GOBLIN_6424,
+ NPCs.GOBLIN_6425,
+ NPCs.GOBLIN_6426,
+ NPCs.GOBLIN_6427,
+ NPCs.GOBLIN_6428,
+ NPCs.GOBLIN_6429,
+ NPCs.GOBLIN_6430,
+ NPCs.GOBLIN_6431,
+ NPCs.GOBLIN_6432,
+ NPCs.GOBLIN_6433,
+ NPCs.GOBLIN_6434,
+ NPCs.GOBLIN_6435,
+ NPCs.GOBLIN_6436,
+ NPCs.GOBLIN_6437,
+ NPCs.GOBLIN_6438,
+ NPCs.GOBLIN_6439,
+ NPCs.GOBLIN_6440,
+ NPCs.GOBLIN_6441,
+ NPCs.GOBLIN_6442,
+ NPCs.GOBLIN_6443,
+ NPCs.GOBLIN_6444,
+ NPCs.GOBLIN_6445,
+ NPCs.GOBLIN_6446,
+ NPCs.GOBLIN_6447,
+ NPCs.GOBLIN_6448,
+ NPCs.GOBLIN_6449,
+ NPCs.GOBLIN_6450,
+ NPCs.GOBLIN_6451,
+ NPCs.GOBLIN_6452,
+ NPCs.GOBLIN_6453,
+ NPCs.GOBLIN_6454,
+ NPCs.GOBLIN_6455,
+ NPCs.GOBLIN_6456,
+ NPCs.GOBLIN_6457,
+ NPCs.GOBLIN_6458,
+ NPCs.GOBLIN_6459,
+ NPCs.GOBLIN_6460,
+ NPCs.GOBLIN_6461,
+ NPCs.GOBLIN_6462,
+ NPCs.GOBLIN_6463,
+ NPCs.GOBLIN_6464,
+ NPCs.GOBLIN_6465,
+ NPCs.GOBLIN_6466,
+ NPCs.GOBLIN_6467,
+ NPCs.GOBLIN_6490,
+ NPCs.GOBLIN_6491,
+ NPCs.GOBLIN_6492,
+ NPCs.GOBLIN_6493,
+ NPCs.GOBLIN_6494,
+ NPCs.GOBLIN_6495,
+ NPCs.GOBLIN_7964,
+ NPCs.GOBLIN_7965
+ )
+ val COWS = listOf(
+ NPCs.COW_81,
+ NPCs.COW_397,
+ NPCs.COW_955,
+ NPCs.COW_1767,
+ NPCs.COW_3309,
+ NPCs.COW_CALF_1766,
+ NPCs.COW_CALF_1768,
+ NPCs.COW_CALF_2310
+ )
+ val CHICKENS = listOf(
+ NPCs.CHICKEN_1017,
+ NPCs.CHICKEN_1401,
+ NPCs.CHICKEN_1402,
+ NPCs.CHICKEN_2313,
+ NPCs.CHICKEN_2314,
+ NPCs.CHICKEN_2315,
+ NPCs.CHICKEN_288,
+ NPCs.CHICKEN_41,
+ NPCs.CHICKEN_951,
+ )
+ }
+}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/impl/Employers.kt b/Server/src/main/content/global/activity/jobs/impl/Employers.kt
new file mode 100644
index 000000000..2d1fc165c
--- /dev/null
+++ b/Server/src/main/content/global/activity/jobs/impl/Employers.kt
@@ -0,0 +1,24 @@
+package content.global.activity.jobs.impl
+
+import org.rs09.consts.NPCs
+
+/**
+ * An enum of NPCs that can assign the player a job.
+ *
+ * TODO: Add the rest of the era-correct employers
+ */
+enum class Employers(val npcId: Int) {
+ WOODCUTTING_TUTOR(NPCs.WOODCUTTING_TUTOR_4906),
+ MAGIC_TUTOR(NPCs.MAGIC_TUTOR_4707),
+ MELEE_TUTOR(NPCs.MELEE_TUTOR_705),
+ RANGED_TUTOR(NPCs.RANGED_TUTOR_1861),
+ COOKING_TUTOR(NPCs.COOKING_TUTOR_4899),
+ CRAFTING_TUTOR(NPCs.CRAFTING_TUTOR_4900),
+ FISHING_TUTOR(NPCs.FISHING_TUTOR_4901),
+ MINING_TUTOR(NPCs.MINING_TUTOR_4902),
+ SMELTING_TUTOR(NPCs.SMELTING_TUTOR_4904),
+ PRAYER_TUTOR(NPCs.PRAYER_TUTOR_4903),
+ HANS(NPCs.HANS_0),
+ GILLIE_GROATS(NPCs.GILLIE_GROATS_3807),
+ AGGIE(NPCs.AGGIE_922);
+}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/impl/GatheringJobs.kt b/Server/src/main/content/global/activity/jobs/impl/GatheringJobs.kt
deleted file mode 100644
index ddb4c0973..000000000
--- a/Server/src/main/content/global/activity/jobs/impl/GatheringJobs.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package content.global.activity.jobs.impl
-
-import core.game.node.entity.skill.Skills
-import core.tools.RandomFunction
-
-enum class GatheringJobs(val lower: Int, val upper: Int, val lvlReq: Int, val itemId: Int,val skill: Int) {
- LOG(20, 28, 1, 1511, Skills.WOODCUTTING),
- COWHIDES(20, 28, 1, 1739, 0),
- OAK(22, 28, 15, 1521, Skills.WOODCUTTING),
- WATER_RUNE(20, 28, 5, 555, Skills.RUNECRAFTING),
- EARTH_RUNE(20, 28, 9, 557, Skills.RUNECRAFTING),
- FIRE_RUNE(20, 28, 14, 554, Skills.RUNECRAFTING),
- AIR_RUNE(20, 28, 1, 556, Skills.RUNECRAFTING),
- RUNE_ESS(20, 28, 1, 1436, Skills.MINING),
- BALL_OF_WOOL(20, 28, 1, 1759, Skills.CRAFTING),
- LEATHER_GLOVE(20, 28, 1, 1059, Skills.CRAFTING),
- WILLOW(22, 28, 30, 1519, Skills.WOODCUTTING),
- LEATHER_BOOT(24, 24, 1, 1061, Skills.CRAFTING),
- BRONZE_DAGGER(24, 24, 1, 1205, Skills.SMITHING),
- BRONZE_HELMS(22, 22, 1, 1139, Skills.SMITHING),
- BRONZE_FULL_HELM(10, 10, 7, 1155, Skills.SMITHING),
- BRONZE_CHAINBODY(10, 10, 11, 1103, Skills.SMITHING),
- BRONZE_2H(10, 10, 14, 1307, Skills.SMITHING),
- BRONZE_SCIM(10, 10, 5, 1321, Skills.SMITHING),
- BRONZE_PLATEBODY(9, 10, 18, 1117, Skills.SMITHING),
- BRONZE_PLATELEGS(9, 10, 16, 1075, Skills.SMITHING),
- BRONZE_PLATESKIRTS(9, 10, 16, 1087, Skills.SMITHING),
- BRONZE_WARHAMMER(9, 9, 9, 1337, Skills.SMITHING),
- IRON_DAGGER(13, 13, 15, 1203, Skills.SMITHING),
- IRON_CHAINBODY(10, 10, 26, 1101, Skills.SMITHING),
- IRON_2H(9, 9, 29, 1309, Skills.SMITHING),
- STEEL_BATTLEAXE(9, 9, 40, 1365, Skills.SMITHING),
- STEEL_SCIMITAR(9, 9, 35, 1325, Skills.SMITHING),
- STEEL_PLATEBODY(9, 10, 48, 1119, Skills.SMITHING),
- STEEL_WARHAMMER(9, 10, 39, 1339, Skills.SMITHING),
- LEATHER_CHAPS(22, 28, 18, 1095, Skills.CRAFTING),
- LEATHER_BODY(22, 28, 14, 1129, Skills.CRAFTING),
- LEATHER_COWL(22, 28, 9, 1167, Skills.CRAFTING),
- LEATHER_COIF(22, 28, 38, 1169, Skills.CRAFTING),
- RAW_SHRIMP(22, 28, 1, 317, Skills.FISHING),
- COOKED_SHRIMP(22, 28, 1, 315, Skills.COOKING),
- RAW_CRAYFISH(22, 28, 1, 13435, Skills.FISHING),
- COOKED_CRAYFISH(22, 28, 1, 13433, Skills.COOKING),
- RAW_TROUT(22, 28, 20, 335, Skills.FISHING),
- COOKED_TROUT(22, 28, 15, 333, Skills.COOKING),
- RAW_SALMON(22, 28, 30, 331, Skills.FISHING),
- COOKED_SALMON(22, 28, 25, 329, Skills.COOKING),
- CAKE(12, 16, 40, 1891, Skills.COOKING),
- MEAT_PIE(12, 16, 20, 2327, Skills.COOKING),
- PLAIN_PIZZA(12, 16, 35, 2289, Skills.COOKING),
- MEAT_PIZZA(12, 16, 45, 2293, Skills.COOKING),
- ANCHOVY_PIZZA(12, 16, 55, 2297, Skills.COOKING),
- REDBERRY_PIE(12, 16, 10, 2325, Skills.COOKING),
- COPPER_ORE(22, 28, 1, 436, Skills.MINING),
- TIN_ORE(23, 26, 1, 438, Skills.MINING),
- IRON_ORE(24, 24, 15, 440, Skills.MINING),
- SILVER_ORE(22, 28, 20, 442, Skills.MINING),
- COAL(22, 26, 30, 453, Skills.MINING),
- GOLD_ORE(22, 24, 40, 444, Skills.MINING),
- BRONZE_BAR(10, 12, 1, 2349, Skills.SMITHING),
- IRON_BAR(22, 28, 15, 2351, Skills.SMITHING),
- STEEL_BAR(22, 28, 30, 2353, Skills.SMITHING),
- ASHES(26, 26, 1, 592, 0);
-
- fun getAmount(): Int {
- return RandomFunction.random(lower,upper + 1)
- }
-}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/impl/ProductionJobs.kt b/Server/src/main/content/global/activity/jobs/impl/ProductionJobs.kt
new file mode 100644
index 000000000..04bdd5467
--- /dev/null
+++ b/Server/src/main/content/global/activity/jobs/impl/ProductionJobs.kt
@@ -0,0 +1,80 @@
+package content.global.activity.jobs.impl
+
+import content.global.activity.jobs.Job
+import content.global.activity.jobs.JobType
+import core.game.node.entity.skill.Skills
+
+/**
+ * An enum of the possible production jobs that can be assigned to a player.
+ *
+ * Note: Due to how player save files keep track of the player's current job, it is essential that
+ * new entries are only appended to the end of this enum, and the ordering of existing entries is not changed.
+ */
+enum class ProductionJobs(
+ override val lower: Int,
+ override val upper: Int,
+ val lvlReq: Int,
+ val itemId: Int,
+ val skill: Int,
+ override val employer: Employers
+) : Job {
+ LOG(20, 28, 1, 1511, Skills.WOODCUTTING, Employers.WOODCUTTING_TUTOR),
+ COWHIDES(20, 28, 1, 1739, 0, Employers.HANS),
+ OAK(22, 28, 15, 1521, Skills.WOODCUTTING, Employers.WOODCUTTING_TUTOR),
+ WATER_RUNE(20, 28, 5, 555, Skills.RUNECRAFTING, Employers.HANS),
+ EARTH_RUNE(20, 28, 9, 557, Skills.RUNECRAFTING, Employers.AGGIE),
+ FIRE_RUNE(20, 28, 14, 554, Skills.RUNECRAFTING, Employers.AGGIE),
+ AIR_RUNE(20, 28, 1, 556, Skills.RUNECRAFTING, Employers.HANS),
+ RUNE_ESS(20, 28, 1, 1436, Skills.MINING, Employers.MINING_TUTOR),
+ BALL_OF_WOOL(20, 28, 1, 1759, Skills.CRAFTING, Employers.GILLIE_GROATS),
+ LEATHER_GLOVE(20, 28, 1, 1059, Skills.CRAFTING, Employers.GILLIE_GROATS),
+ WILLOW(22, 28, 30, 1519, Skills.WOODCUTTING, Employers.WOODCUTTING_TUTOR),
+ LEATHER_BOOT(24, 24, 1, 1061, Skills.CRAFTING, Employers.HANS),
+ BRONZE_DAGGER(24, 24, 1, 1205, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ BRONZE_HELMS(22, 22, 1, 1139, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ BRONZE_FULL_HELM(10, 10, 7, 1155, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ BRONZE_CHAINBODY(10, 10, 11, 1103, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ BRONZE_2H(10, 10, 14, 1307, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ BRONZE_SCIM(10, 10, 5, 1321, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ BRONZE_PLATEBODY(9, 10, 18, 1117, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ BRONZE_PLATELEGS(9, 10, 16, 1075, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ BRONZE_PLATESKIRTS(9, 10, 16, 1087, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ BRONZE_WARHAMMER(9, 9, 9, 1337, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ IRON_DAGGER(13, 13, 15, 1203, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ IRON_CHAINBODY(10, 10, 26, 1101, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ IRON_2H(9, 9, 29, 1309, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ STEEL_BATTLEAXE(9, 9, 40, 1365, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ STEEL_SCIMITAR(9, 9, 35, 1325, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ STEEL_PLATEBODY(9, 10, 48, 1119, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ STEEL_WARHAMMER(9, 10, 39, 1339, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ LEATHER_CHAPS(22, 28, 18, 1095, Skills.CRAFTING, Employers.CRAFTING_TUTOR),
+ LEATHER_BODY(22, 28, 14, 1129, Skills.CRAFTING, Employers.CRAFTING_TUTOR),
+ LEATHER_COWL(22, 28, 9, 1167, Skills.CRAFTING, Employers.CRAFTING_TUTOR),
+ LEATHER_COIF(22, 28, 38, 1169, Skills.CRAFTING, Employers.CRAFTING_TUTOR),
+ RAW_SHRIMP(22, 28, 1, 317, Skills.FISHING, Employers.FISHING_TUTOR),
+ COOKED_SHRIMP(22, 28, 1, 315, Skills.COOKING, Employers.COOKING_TUTOR),
+ RAW_CRAYFISH(22, 28, 1, 13435, Skills.FISHING, Employers.FISHING_TUTOR),
+ COOKED_CRAYFISH(22, 28, 1, 13433, Skills.COOKING, Employers.COOKING_TUTOR),
+ RAW_TROUT(22, 28, 20, 335, Skills.FISHING, Employers.FISHING_TUTOR),
+ COOKED_TROUT(22, 28, 15, 333, Skills.COOKING, Employers.COOKING_TUTOR),
+ RAW_SALMON(22, 28, 30, 331, Skills.FISHING, Employers.FISHING_TUTOR),
+ COOKED_SALMON(22, 28, 25, 329, Skills.COOKING, Employers.COOKING_TUTOR),
+ CAKE(12, 16, 40, 1891, Skills.COOKING, Employers.GILLIE_GROATS),
+ MEAT_PIE(12, 16, 20, 2327, Skills.COOKING, Employers.COOKING_TUTOR),
+ PLAIN_PIZZA(12, 16, 35, 2289, Skills.COOKING, Employers.COOKING_TUTOR),
+ MEAT_PIZZA(12, 16, 45, 2293, Skills.COOKING, Employers.COOKING_TUTOR),
+ ANCHOVY_PIZZA(12, 16, 55, 2297, Skills.COOKING, Employers.COOKING_TUTOR),
+ REDBERRY_PIE(12, 16, 10, 2325, Skills.COOKING, Employers.GILLIE_GROATS),
+ COPPER_ORE(22, 28, 1, 436, Skills.MINING, Employers.MINING_TUTOR),
+ TIN_ORE(23, 26, 1, 438, Skills.MINING, Employers.MINING_TUTOR),
+ IRON_ORE(24, 24, 15, 440, Skills.MINING, Employers.MINING_TUTOR),
+ SILVER_ORE(22, 28, 20, 442, Skills.MINING, Employers.PRAYER_TUTOR),
+ COAL(22, 26, 30, 453, Skills.MINING, Employers.MINING_TUTOR),
+ GOLD_ORE(22, 24, 40, 444, Skills.MINING, Employers.MINING_TUTOR),
+ BRONZE_BAR(10, 12, 1, 2349, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ IRON_BAR(22, 28, 15, 2351, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ STEEL_BAR(22, 28, 30, 2353, Skills.SMITHING, Employers.SMELTING_TUTOR),
+ ASHES(26, 26, 1, 592, 0, Employers.AGGIE);
+
+ override val type = JobType.PRODUCTION
+}
\ No newline at end of file
diff --git a/Server/src/main/content/global/activity/jobs/impl/SlayingJobs.kt b/Server/src/main/content/global/activity/jobs/impl/SlayingJobs.kt
deleted file mode 100644
index 5afdbf90b..000000000
--- a/Server/src/main/content/global/activity/jobs/impl/SlayingJobs.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package content.global.activity.jobs.impl
-
-import core.tools.RandomFunction
-
-enum class SlayingJobs(val lower: Int, val upper: Int, vararg val ids: Int) {
- CHICKEN(25,26,41,1017),
- COW(25,25,397,1766,81,1767),
- GIANT_SPIDER(22,25,59,60),
- SCORPION(25,25,107,108,109,144,4402,4403),
- GOBLIN(25,25,100,101,102,298,299,444,445,489,745,1769,1770,1771,1772,1773,1774,1775,1776,2274,2275,2276,2277,2278,2279,2280,2281,2678,2679,2680,2681,3060,3264,3265,3266,3267);
-
- fun getAmount(): Int {
- return RandomFunction.random(lower,upper + 1)
- }
-}
\ No newline at end of file
diff --git a/Server/src/main/content/global/skill/prayer/BoneBuryingOptionPlugin.java b/Server/src/main/content/global/skill/prayer/BoneBuryingOptionPlugin.java
index 2422af243..bf1491456 100644
--- a/Server/src/main/content/global/skill/prayer/BoneBuryingOptionPlugin.java
+++ b/Server/src/main/content/global/skill/prayer/BoneBuryingOptionPlugin.java
@@ -1,6 +1,7 @@
package content.global.skill.prayer;
import core.cache.def.impl.ItemDefinition;
+import core.game.event.BoneBuryEvent;
import core.plugin.Initializable;
import core.game.node.entity.skill.Skills;
import core.game.interaction.OptionHandler;
@@ -62,7 +63,8 @@ public final class BoneBuryingOptionPlugin extends OptionHandler {
@Override
public boolean pulse() {
player.getPacketDispatch().sendMessage("You bury the bones.");
- player.getSkills().addExperience(Skills.PRAYER, bonee.equals(Bones.LAVA_DRAGON_BONES) && player.getLocation().getY() >= 3794 && player.getLocation().getY() <= 3859 ? bonee.getExperience() * 3 : bonee.getExperience(), true);
+ player.getSkills().addExperience(Skills.PRAYER, bonee.equals(Bones.LAVA_DRAGON_BONES) && player.getLocation().getY() >= 3794 && player.getLocation().getY() <= 3859 ? bonee.getExperience() * 3 : bonee.getExperience(), true);
+ player.dispatch(new BoneBuryEvent(bonee.getItemId()));
return true;
}
});
diff --git a/Server/src/main/core/api/Event.kt b/Server/src/main/core/api/Event.kt
index 7e8e31ee6..bb315bc97 100644
--- a/Server/src/main/core/api/Event.kt
+++ b/Server/src/main/core/api/Event.kt
@@ -5,6 +5,7 @@ import core.game.event.*
object Event {
@JvmStatic val ResourceProduced = ResourceProducedEvent::class.java
@JvmStatic val NPCKilled = NPCKillEvent::class.java
+ @JvmStatic val BoneBuried = BoneBuryEvent::class.java
@JvmStatic val Teleported = TeleportEvent::class.java
@JvmStatic val FireLit = LitFireEvent::class.java
@JvmStatic val LightSourceLit = LitLightSourceEvent::class.java
diff --git a/Server/src/main/core/game/event/Events.kt b/Server/src/main/core/game/event/Events.kt
index b58d799a3..3b511fcc4 100644
--- a/Server/src/main/core/game/event/Events.kt
+++ b/Server/src/main/core/game/event/Events.kt
@@ -15,6 +15,7 @@ import content.global.skill.magic.TeleportMethod
data class ResourceProducedEvent(val itemId: Int, val amount: Int, val source: Node, val original: Int = -1) : Event
data class NPCKillEvent(val npc: NPC) : Event
+data class BoneBuryEvent(val boneId: Int) : Event
data class TeleportEvent(val type: TeleportType, val method: TeleportMethod, val source: Any, val location: Location) : Event
data class LitFireEvent(val logId: Int) : Event
data class LitLightSourceEvent(val litLightSourceId: Int) : Event
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 c9d7ab19d..4194f45b7 100644
--- a/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt
+++ b/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt
@@ -1,5 +1,7 @@
package core.game.system.command.sets
+import content.global.activity.jobs.JobManager
+import core.api.removeAttribute
import core.api.sendMessage
import core.cache.Cache
import core.cache.def.impl.DataMap
@@ -51,6 +53,15 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
sendMessage(player, "All achievement diaries cleared successfully.")
}
+ define("clearjob", Privilege.ADMIN) { player, _ ->
+ val playerJobManager = JobManager.getInstance(player)
+ playerJobManager.job = null
+ playerJobManager.jobAmount = -1
+ playerJobManager.jobOriginalAmount = -1
+
+ sendMessage(player, "Job cleared successfully.")
+ }
+
define("region", Privilege.STANDARD, "", "Prints your current Region ID.") {player, args ->
sendMessage(player, "Region ID: ${player.viewport.region.regionId}")
}