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