Major farming improvements including (but not limited to):

Farming animation corrections
Farming message updates and additions
Gardeners will chop down fully grown trees for 200 gp
Gardeners will give farming advice
Compost bin debugging admin command ::finishbins restored (finishes any in-progress compost bins)
Compost bin debugging admin command ::resetbins added (resets the player's compost bins to their initial states)
Players can no longer pay gardeners to protect diseased or dead farming patches
Can no longer water dead patches
Weeds will now grow in farming patches as part of the offline catch-up
Trees that are not fully grown can now be dug up
This commit is contained in:
zsrv 2024-10-07 11:15:52 +00:00 committed by Ryan
parent cc8dd4edb4
commit 218a040f8b
20 changed files with 515 additions and 363 deletions

View file

@ -3,160 +3,227 @@ package content.global.dialogue
import content.global.skill.farming.FarmerPayOptionDialogue import content.global.skill.farming.FarmerPayOptionDialogue
import content.global.skill.farming.Farmers import content.global.skill.farming.Farmers
import content.global.skill.farming.FarmingPatch import content.global.skill.farming.FarmingPatch
import core.game.node.entity.npc.NPC import content.global.skill.farming.PatchType
import core.api.*
import core.game.dialogue.FacialExpression
import core.game.dialogue.IfTopic
import core.game.dialogue.Topic
import core.game.node.entity.player.Player import core.game.node.entity.player.Player
import core.game.node.item.Item import core.game.node.item.Item
import core.plugin.Initializable import core.plugin.Initializable
import org.rs09.consts.Items import org.rs09.consts.Items
import core.tools.END_DIALOGUE import core.tools.END_DIALOGUE
import core.tools.START_DIALOGUE
@Initializable @Initializable
class GardenerDialoguePlugin(player: Player? = null) : core.game.dialogue.DialoguePlugin(player) { class GardenerDialoguePlugin(player: Player? = null) : core.game.dialogue.DialoguePlugin(player) {
override fun newInstance(player: Player?): core.game.dialogue.DialoguePlugin {
return GardenerDialoguePlugin(player)
}
override fun open(vararg args: Any?): Boolean {
npc = args[0] as NPC
options("Would you look after my crops for me?","Can you sell me something?")
return true
}
override fun handle(interfaceId: Int, buttonId: Int): Boolean { override fun handle(interfaceId: Int, buttonId: Int): Boolean {
val patches = Farmers.forId(npc.id)!!.patches
when (stage) { when (stage) {
0 -> when(buttonId){ // TODO: Can fruit trees be chopped down by the gardener too?
1 -> player("Would you look after my crops for me?").also { stage = 10 } START_DIALOGUE -> {
2 -> player("Can you sell me something?").also { stage = 30 } val patch = patches[0].getPatchFor(player)
showTopics(
IfTopic(
FacialExpression.ASKING,
"Would you chop my tree down for me?",
1000,
patch.patch.type == PatchType.TREE_PATCH && patch.plantable != null && patch.isGrown()
),
IfTopic(
FacialExpression.ASKING,
"Would you look after my crops for me?",
10,
!(patch.patch.type == PatchType.TREE_PATCH && patch.plantable != null && patch.isGrown())
),
Topic(FacialExpression.ASKING, "Can you give me any farming advice?", 2000),
Topic(FacialExpression.ASKING, "Can you sell me something?", 30),
Topic(FacialExpression.NEUTRAL, "That's all, thanks.", END_DIALOGUE)
)
} }
10 -> npc("I might. Which one were you thinking of?").also { stage++ } 10 -> {
11 -> when(npc.id){ if (patches.size > 1) {
Farmers.ELSTAN.id, Farmers.LYRA.id -> options("The north-western allotment.","The south-eastern allotment.").also { stage = 15 } npc("I might. Which one were you thinking of?").also { stage = 20 }
Farmers.DANTAERA.id, Farmers.KRAGEN.id -> options("The north allotment.","The south allotment.").also { stage = 15 } } else {
else -> player("Uh, that one.").also { stage++ } openPayGardenerDialogue(player, patches[0])
}
} }
12 -> npc("Oh, right. My bad.").also { stage++ } 20 -> when (npc.id) {
13 -> checkPatch(player,Farmers.forId(npc.id)!!.patches[0]) Farmers.ELSTAN.id, Farmers.LYRA.id -> showTopics(
Topic(FacialExpression.NEUTRAL, "The north-western allotment.", 21),
15 -> when(buttonId){ Topic(FacialExpression.NEUTRAL, "The south-eastern allotment.", 22)
1 -> checkPatch(player,Farmers.forId(npc.id)!!.patches[0]) )
2 -> checkPatch(player,Farmers.forId(npc.id)!!.patches[1]) Farmers.DANTAERA.id, Farmers.KRAGEN.id -> showTopics(
Topic(FacialExpression.NEUTRAL, "The northern allotment.", 21),
Topic(FacialExpression.NEUTRAL, "The southern allotment.", 22)
)
} }
21 -> openPayGardenerDialogue(player, patches[0])
22 -> openPayGardenerDialogue(player, patches[1])
30 -> npc("That depends on whether I have it to sell.","What is it that you're looking for?").also { stage++ } 30 -> npc(FacialExpression.NEUTRAL, "That depends on whether I have it to sell. What is it", "that you're looking for?").also { stage++ }
31 -> options("Some plant cure.","A bucket of compost.","A rake.","(See more items)").also { stage = 32 } 31 -> showTopics(
32 -> when(buttonId){ Topic(FacialExpression.NEUTRAL, "Some plant cure.", 100),
1 -> player("Some plant cure.").also { stage = 100 } Topic(FacialExpression.NEUTRAL, "A bucket of compost.", 200),
2 -> player("A bucket of compost.").also { stage = 200 } Topic(FacialExpression.NEUTRAL, "A rake.", 300),
3 -> player("A rake.").also { stage = 300 } Topic("(See more items)", 32, true)
4 -> options("A watering can.","A gardening trowel.","A seed dibber.","(See previous items)").also { stage++ } )
} 32 -> showTopics(
33 -> when(buttonId){ Topic(FacialExpression.NEUTRAL, "A watering can.", 400),
1 -> player("A watering can.").also { stage = 400 } Topic(FacialExpression.NEUTRAL, "A gardening trowel.", 500),
2 -> player("A gardening trowel.").also { stage = 500 } Topic(FacialExpression.NEUTRAL, "A seed dibber.", 600),
3 -> player("A seed dibber.").also { stage = 600 } Topic("(See previous items)", 31, true),
4 -> options("Some plant cure.","A bucket of compost.","A rake.","(See more items)").also { stage = 32 } Topic(FacialExpression.NEUTRAL, "Forget it.", 40, true)
} )
100 -> npc("Plant cure, eh? I might have some put aside for myself.","Tell you what, I'll sell you some plant cure","for 25 gp if you like.").also { stage++ } 40 -> player("Forget it, you don't have anything I need.").also { stage = END_DIALOGUE }
101 -> options("Yes, that sounds like a fair price.","No thanks, I can get that much cheaper.").also { stage++ }
100 -> npc("Plant cure, eh? I might have some put aside for myself.", "Tell you what. I'll sell you some plant cure for 25 gp if", "you like.").also { stage++ }
101 -> options("Yes, that sounds like a fair price.", "No thanks, I can get that much cheaper elsewhere.").also { stage++ }
102 -> when (buttonId) { 102 -> when (buttonId) {
1 -> { 1 -> {
player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE } player(FacialExpression.HAPPY, "Yes, that sounds like a fair price.").also { stage = END_DIALOGUE }
if(player.inventory.remove(Item(995,25))){ if (removeItem(player, Item(Items.COINS_995, 25))) {
player.inventory.add(Item(Items.PLANT_CURE_6036)) addItemOrDrop(player, Items.PLANT_CURE_6036)
} else { } else {
player.sendMessage("You need 25 gp to pay for that.") sendMessage(player, "You need 25 gp to pay for that.")
} }
} }
2 -> end() 2 -> player("No thanks, I can get that much cheaper elsewhere.").also { stage = END_DIALOGUE }
} }
200 -> npc("A bucket of compost, eh? I might have one spare...", "tell you what, I'll sell it to you for 35 gp if you like.").also { stage++ } 200 -> npc("A bucket of compost, eh? I might have one spare...", "tell you what, I'll sell it to you for 35 gp if you like.").also { stage++ }
201 -> options("Yes, that sounds fair.","No thanks, I can get that cheaper.").also { stage++ } 201 -> options("Yes, that sounds like a fair price.", "No thanks, I can get that much cheaper elsewhere.").also { stage++ }
202 -> when (buttonId) { 202 -> when (buttonId) {
1 -> { 1 -> {
player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE } player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE }
if(player.inventory.remove(Item(995,35))){ if (removeItem(player, Item(Items.COINS_995, 35))) {
player.inventory.add(Item(Items.COMPOST_6032)) addItemOrDrop(player, Items.COMPOST_6032)
} else { } else {
player.sendMessage("You need 35 gp to pay for that.") sendMessage(player, "You need 35 gp to pay for that.")
} }
} }
2 -> end() 2 -> player("No thanks, I can get that much cheaper elsewhere.").also { stage = END_DIALOGUE }
} }
300 -> npc("A rake, eh? I might have one spare...", "tell you what, I'll sell it to you for 15 gp if you like.").also { stage++ } 300 -> npc("A rake, eh? I might have one spare...", "tell you what, I'll sell it to you for 15 gp if you like.").also { stage++ }
301 -> options("Yes, that sounds fair.","No thanks, I can get that cheaper.").also { stage++ } 301 -> options("Yes, that sounds like a fair price.", "No thanks, I can get that much cheaper elsewhere.").also { stage++ }
302 -> when (buttonId) { 302 -> when (buttonId) {
1 -> { 1 -> {
player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE } player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE }
if(player.inventory.remove(Item(995,15))){ if (removeItem(player, Item(Items.COINS_995, 15))) {
player.inventory.add(Item(Items.RAKE_5341)) addItemOrDrop(player, Items.RAKE_5341)
} else { } else {
player.sendMessage("You need 15 gp to pay for that.") sendMessage(player, "You need 15 gp to pay for that.")
} }
} }
2 -> end() 2 -> player("No thanks, I can get that much cheaper elsewhere.").also { stage = END_DIALOGUE }
} }
400 -> npc("A watering can, eh? I might have one spare...", "tell you what, I'll sell it to you for 25 gp if you like.").also { stage++ } 400 -> npc("A watering can, eh? I might have one spare...", "tell you what, I'll sell it to you for 25 gp if you like.").also { stage++ }
401 -> options("Yes, that sounds fair.","No thanks, I can get that cheaper.").also { stage++ } 401 -> options("Yes, that sounds like a fair price.", "No thanks, I can get that much cheaper elsewhere.").also { stage++ }
402 -> when(buttonId){ 402 -> when(buttonId){
1 -> { 1 -> {
player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE } player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE }
if(player.inventory.remove(Item(995,25))){ if (removeItem(player, Item(Items.COINS_995, 25))) {
player.inventory.add(Item(Items.WATERING_CAN8_5340)) addItemOrDrop(player, Items.WATERING_CAN8_5340)
} else { } else {
player.sendMessage("You need 25 gp to pay for that.") sendMessage(player, "You need 25 gp to pay for that.")
} }
} }
2 -> end() 2 -> player("No thanks, I can get that much cheaper elsewhere.").also { stage = END_DIALOGUE }
} }
500 -> npc("A gardening trowel, eh? I might have one spare...", "tell you what, I'll sell it to you for 15 gp if you like.").also { stage++ } 500 -> npc("A gardening trowel, eh? I might have one spare...", "tell you what, I'll sell it to you for 15 gp if you like.").also { stage++ }
501 -> options("Yes, that sounds fair.","No thanks, I can get that cheaper.").also { stage++ } 501 -> options("Yes, that sounds like a fair price.", "No thanks, I can get that much cheaper elsewhere.").also { stage++ }
502 -> when (buttonId) { 502 -> when (buttonId) {
1 -> { 1 -> {
player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE } player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE }
if(player.inventory.remove(Item(995,15))){ if (removeItem(player, Item(Items.COINS_995, 15))) {
player.inventory.add(Item(Items.GARDENING_TROWEL_5325)) addItemOrDrop(player, Items.GARDENING_TROWEL_5325)
} else { } else {
player.sendMessage("You need 15 gp to pay for that.") sendMessage(player, "You need 15 gp to pay for that.")
} }
} }
2 -> end() 2 -> player("No thanks, I can get that much cheaper elsewhere.").also { stage = END_DIALOGUE }
} }
600 -> npc("A seed dibber, eh? I might have one spare...", "tell you what, I'll sell it to you for 15 gp if you like.").also { stage++ } 600 -> npc("A seed dibber, eh? I might have one spare...", "tell you what, I'll sell it to you for 15 gp if you like.").also { stage++ }
601 -> options("Yes, that sounds fair.","No thanks, I can get that cheaper.").also { stage++ } 601 -> options("Yes, that sounds like a fair price.", "No thanks, I can get that much cheaper elsewhere.").also { stage++ }
602 -> when (buttonId) { 602 -> when (buttonId) {
1 -> { 1 -> {
player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE } player("Yes, that sounds like a fair price.").also { stage = END_DIALOGUE }
if(player.inventory.remove(Item(995,15))){ if (removeItem(player, Item(Items.COINS_995, 15))) {
player.inventory.add(Item(Items.SEED_DIBBER_5343)) addItemOrDrop(player, Items.SEED_DIBBER_5343)
} else { } else {
player.sendMessage("You need 15 gp to pay for that.") sendMessage(player, "You need 15 gp to pay for that.")
} }
} }
2 -> end() 2 -> player("No thanks, I can get that much cheaper elsewhere.").also { stage = END_DIALOGUE }
}
// Note: This dialogue changes slightly in April 2009, and significantly in December 2009
1000 -> npc(FacialExpression.THINKING, "Why? You look like you could chop it down yourself!").also { stage++ }
1001 -> showTopics(
Topic(FacialExpression.NEUTRAL, "Yes, you're right - I'll do it myself.", END_DIALOGUE),
Topic(FacialExpression.NEUTRAL, "I can't be bothered - I'd rather pay you to do it.", 1020)
)
1020 -> npc(FacialExpression.NEUTRAL, "Well, it's a lot of hard work - if you pay me 200 GP", "I'll chop it down for you.").also { stage++ }
1021 -> {
if (inInventory(player, Items.COINS_995, 200)) {
showTopics(
Topic(FacialExpression.NEUTRAL, "Here's 200GP - chop my tree down please.", 1022),
Topic(FacialExpression.NEUTRAL, "I don't want to pay that much, sorry.", END_DIALOGUE)
)
} else {
player("I don't have that much money on me.").also { stage = END_DIALOGUE } // not authentic
}
}
1022 -> {
end()
if (removeItem(player, Item(Items.COINS_995, 200))) {
patches[0].getPatchFor(player).clear()
}
}
2000 -> {
val advice = arrayOf(
"There are four main Farming areas - Elstan looks after an area south of Falador, Dantaera has one to the north of Catherby, Kragen has one near Ardougne, and Lyra looks after a place in north Morytania.",
"If you want to grow fruit trees you could try a few places: Catherby and Brimhaven have a couple of fruit tree patches, and I hear that the gnomes are big on that sort of thing.",
"Bittercap mushrooms can only be grown in a special patch in Morytania, near the Mort Myre swamp. There the ground is especially dank and suited to growing poisonous fungi.",
"There is a special patch for growing Belladonna - I believe it's somewhere near Draynor Manor, where the ground is a tad 'unblessed'.",
"Don't just throw away your weeds after you've raked a patch - put them in a compost bin and make some compost.",
"Applying compost to a patch will not only reduce the chance that your crops will get diseased, but you will also grow more crops to harvest.",
"Supercompost is far better than normal compost, but more expensive to make. You need to rot the right type of item; show me an item, and I'll tell you if it's super-compostable or not.",
"Tree seeds must be grown in a plantpot of soil into a sapling, and then transferred to a tree patch to continue growing to adulthood.",
"You don't have to buy all your plantpots you know, you can make them yourself on a pottery wheel. If you're a good enough ${if (player!!.isMale) "craftsman" else "craftswoman"}, that is.",
"You can fill plantpots with soil from Farming patches, if you have a gardening trowel.",
"Vegetables, hops and flowers are far more likely to grow healthily if you water them periodically.",
"The only way to cure a bush or tree of disease is to prune away the diseased leaves with a pair of secateurs. For all other crops I would just apply some plant-cure.",
"If you need to be rid of your fruit trees for any reason, all you have to do is chop them down and then dig up the stump.",
"You can put up to ten potatoes, cabbages or onions in vegetable sacks, although you can't have a mix in the same sack.",
"You can put up to five tomatoes, strawberries, apples, bananas or oranges into a fruit basket, although you can't have a mix in the same basket.",
"If you want to make your own sacks and baskets you'll need to use the loom that's near the Farming shop in Falador. If you're a good enough ${if (player!!.isMale) "craftsman" else "craftswoman"}, that is.",
"You can buy all the farming tools from farming shops, which can be found close to the allotments.",
"Hops are good for brewing ales. I believe there's a brewery up in Keldagrim somewhere, and I've heard rumours that a place called Phasmatys used to be good for that type of thing. 'Fore they all died, of course.",
)
npcl(FacialExpression.NEUTRAL, advice.random()).also { stage = START_DIALOGUE }
} }
} }
return true return true
} }
fun checkPatch(player: Player,fPatch: FarmingPatch){ fun openPayGardenerDialogue(player: Player, fPatch: FarmingPatch) {
if(fPatch.getPatchFor(player).isWeedy()){
npc("You don't have anything planted in that patch.","Plant something and I might agree to look after it for you.").also { stage = END_DIALOGUE }
} else if(fPatch.getPatchFor(player).isGrown()){
npc("That patch is already fully grown!","I don't know what you want me to do with it!").also { stage = END_DIALOGUE }
} else if(fPatch.getPatchFor(player).protectionPaid) {
npc("Are you alright? You've already", "paid me for that.").also { stage = END_DIALOGUE }
} else {
end() end()
player.dialogueInterpreter.open(FarmerPayOptionDialogue(fPatch.getPatchFor(player)),npc) openDialogue(player, FarmerPayOptionDialogue(fPatch.getPatchFor(player)), npc)
}
} }
override fun getIds(): IntArray { override fun getIds(): IntArray {

View file

@ -19,6 +19,19 @@ class CompostBin(val player: Player, val bin: CompostBins) {
var finishedTime = 0L var finishedTime = 0L
var isFinished = false var isFinished = false
/**
* Resets the compost bin to its initial state.
*/
fun reset() {
items.clear()
isSuperCompost = true
isTomatoes = true
isClosed = false
finishedTime = 0L
isFinished = false
updateBit()
}
fun isFull() : Boolean { fun isFull() : Boolean {
return items.size == 15 return items.size == 15
} }

View file

@ -61,7 +61,13 @@ class CropHarvester : OptionHandler() {
} }
} }
val anim = when (requiredItem) { val anim = when (requiredItem) {
Items.SPADE_952 -> if (fPatch.type == PatchType.HERB_PATCH) Animation(2282) else Animation(830) Items.SPADE_952 -> {
when (fPatch.type) {
PatchType.HERB_PATCH -> Animation(2282)
PatchType.FLOWER_PATCH -> Animation(2292)
else -> Animation(830)
}
}
Items.SECATEURS_5329 -> if (fPatch.type == PatchType.TREE_PATCH) Animation(2277) else Animation(7227) Items.SECATEURS_5329 -> if (fPatch.type == PatchType.TREE_PATCH) Animation(2277) else Animation(7227)
Items.MAGIC_SECATEURS_7409 -> if (fPatch.type == PatchType.TREE_PATCH) Animation(3340) else Animation(7228) Items.MAGIC_SECATEURS_7409 -> if (fPatch.type == PatchType.TREE_PATCH) Animation(3340) else Animation(7228)
else -> Animation(0) else -> Animation(0)
@ -76,12 +82,15 @@ class CropHarvester : OptionHandler() {
sendMessage(player, "You lack the needed tool to harvest these crops.") sendMessage(player, "You lack the needed tool to harvest these crops.")
return true return true
} }
if (firstHarvest) { val sendHarvestMessages = if (fPatch.type == PatchType.FLOWER_PATCH) false else true
if (sendHarvestMessages && firstHarvest) {
sendMessage(player, "You begin to harvest the $patchName.") sendMessage(player, "You begin to harvest the $patchName.")
firstHarvest = false firstHarvest = false
} }
animate(player, anim) animate(player, anim)
playAudio(player, sound) playAudio(player, sound)
// TODO: If a flower patch is being harvested, delay the clearing of the
// patch until after the animation has played - https://youtu.be/lg4GktlVNUY?t=75
delay = 2 delay = 2
addItem(player, reward.id) addItem(player, reward.id)
rewardXP(player, Skills.FARMING, plantable.harvestXP) rewardXP(player, Skills.FARMING, plantable.harvestXP)
@ -96,7 +105,7 @@ class CropHarvester : OptionHandler() {
patch.clear() patch.clear()
} }
} }
if (patch.cropLives <= 0 || patch.harvestAmt <= 0) { if (sendHarvestMessages && (patch.cropLives <= 0 || patch.harvestAmt <= 0)) {
sendMessage(player, "The $patchName is now empty.") sendMessage(player, "The $patchName is now empty.")
} }
return patch.cropLives <= 0 || patch.harvestAmt <= 0 return patch.cropLives <= 0 || patch.harvestAmt <= 0

View file

@ -24,7 +24,7 @@ class DigUpPatchDialogue(player: Player? = null) : DialoguePlugin(player) {
} }
if (patch?.patch?.type == PatchType.TREE_PATCH) { if (patch?.patch?.type == PatchType.TREE_PATCH) {
val isTreeStump = patch?.getCurrentState() == patch?.plantable!!.value + patch?.plantable!!.stages + 2 val isTreeStump = patch?.getCurrentState() == patch?.plantable!!.value + patch?.plantable!!.stages + 2
if (!isTreeStump) { if (patch!!.isGrown() && !isTreeStump) {
sendMessage(player, "You need to chop this tree down first.") // this message is not authentic sendMessage(player, "You need to chop this tree down first.") // this message is not authentic
stage = 1000 stage = 1000
return true return true

View file

@ -1,16 +1,37 @@
package content.global.skill.farming package content.global.skill.farming
import core.game.node.item.Item import core.api.*
import org.rs09.consts.Items
import core.game.dialogue.DialogueFile import core.game.dialogue.DialogueFile
import core.game.dialogue.FacialExpression
import core.game.dialogue.Topic
import core.game.node.item.Item
import core.tools.END_DIALOGUE import core.tools.END_DIALOGUE
import core.tools.START_DIALOGUE import core.tools.START_DIALOGUE
import org.rs09.consts.Items
class FarmerPayOptionDialogue(val patch: Patch): DialogueFile() { class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false): DialogueFile() {
var item: Item? = null var item: Item? = null
override fun handle(componentID: Int, buttonID: Int) { override fun handle(componentID: Int, buttonID: Int) {
when (stage) { when (stage) {
START_DIALOGUE -> { START_DIALOGUE -> {
if (patch.patch.type == PatchType.TREE_PATCH && patch.plantable != null && patch.isGrown()) {
// This is for the right-click "Pay" option; full dialogue is in GardenerDialoguePlugin
showTopics(
Topic("Yes, get rid of the tree.", 300, true),
Topic("No thanks.", END_DIALOGUE, true),
title = "Pay 200 gp to have the tree chopped down?"
)
} else if (patch.protectionPaid) {
npc("I don't know what you're talking about - I'm already", "looking after that patch for you.").also { stage = 100 }
} else if (patch.isDead) {
npc("That patch is dead - it's too late for me to do", "anything about it now.").also { stage = END_DIALOGUE }
} else if (patch.isDiseased) {
npc("That patch is diseased - I can't look after it", "until it has been cured.").also { stage = END_DIALOGUE } // this dialogue is not authentic
} else if (patch.isWeedy() || patch.isEmptyAndWeeded()) {
npc(FacialExpression.NEUTRAL, "You don't have anything planted in that patch. Plant", "something and I might agree to look after it for you.").also { stage = END_DIALOGUE }
} else if (patch.isGrown()) {
npc("That patch is already fully grown!", "I don't know what you want me to do with it!").also { stage = END_DIALOGUE }
} else {
item = patch.plantable?.protectionItem item = patch.plantable?.protectionItem
val protectionText = when (item?.id) { val protectionText = when (item?.id) {
Items.COMPOST_6032 -> if (item?.amount == 1) "bucket of compost" else "buckets of compost" Items.COMPOST_6032 -> if (item?.amount == 1) "bucket of compost" else "buckets of compost"
@ -26,41 +47,65 @@ class FarmerPayOptionDialogue(val patch: Patch): DialogueFile() {
Items.CACTUS_SPINE_6016 -> "cactus spines" Items.CACTUS_SPINE_6016 -> "cactus spines"
Items.STRAWBERRIES5_5406 -> if (item?.amount == 1) "basket of strawberries" else "baskets of strawberries" Items.STRAWBERRIES5_5406 -> if (item?.amount == 1) "basket of strawberries" else "baskets of strawberries"
Items.BANANAS5_5416 -> if (item?.amount == 1) "basket of bananas" else "baskets of bananas" Items.BANANAS5_5416 -> if (item?.amount == 1) "basket of bananas" else "baskets of bananas"
else -> item?.name?.toLowerCase() else -> item?.name?.lowercase()
} }
if(item == null) npc("Sorry, I won't protect that.").also { stage = END_DIALOGUE } if (item == null) {
else{ npc("Sorry, I won't protect that.").also { stage = END_DIALOGUE }
npc("I would like ${item?.amount} $protectionText","to protect that patch.") } else if (quickPay && !(inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))) {
val amount = if (item?.amount == 1) "one" else item?.amount
npc(FacialExpression.HAPPY, "I want $amount $protectionText for that.")
stage = 200
} else if (quickPay) {
val amount = if (item?.amount == 1) "one" else item?.amount
showTopics(
Topic("Yes", 20, true),
Topic("No", END_DIALOGUE, true),
title = "Pay $amount $protectionText?"
)
} else {
val amount = if (item?.amount == 1) "one" else item?.amount
npc("If you like, but I want $amount $protectionText for that.")
stage++ stage++
} }
} }
1 -> options("Sure!","No, thanks.").also { stage++ }
2 -> {
if(player!!.inventory.containsItem(item)){
player("Here you go.").also { stage = 10 }
} else {
item = Item(item!!.noteChange,item!!.amount)
if(player!!.inventory.containsItem(item)){
player("Here you go.").also { stage = 10 }
} else {
player("I don't have that to give.").also { stage = 20 }
} }
1 -> {
if (!(inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))) {
player("I'm afraid I don't have any of those at the moment.").also { stage = 10 }
} else {
showTopics(
Topic(FacialExpression.NEUTRAL, "Okay, it's a deal.", 20),
Topic(FacialExpression.NEUTRAL, "No, that's too much.", 10)
)
} }
} }
10 -> { 10 -> npc("Well, I'm not wasting my time for free.").also { stage = END_DIALOGUE }
if(player!!.inventory.remove(item)){
npc("Thank you! I'll keep an eye on this patch.").also { stage = END_DIALOGUE }
patch?.protectionPaid = true
} else {
npc("That stuff just... vanished....").also { stage = END_DIALOGUE }
}
}
20 -> { 20 -> {
npc("Come back when you do.") if (removeItem(player!!, item) || removeItem(player!!, note(item!!))) {
stage = END_DIALOGUE patch.protectionPaid = true
// Note: A slight change in this dialogue was seen in a December 2009 video - https://youtu.be/7gVh42ylQ48?t=138
npc("That'll do nicely, ${if (player!!.isMale) "sir" else "madam"}. Leave it with me - I'll make sure", "those crops grow for you.").also { stage = END_DIALOGUE }
} else {
npc("This shouldn't be happening. Please report this.").also { stage = END_DIALOGUE }
}
}
100 -> player("Oh sorry, I forgot.").also { stage = END_DIALOGUE }
// Right-click "Pay" - protect patch - player doesn't have payment
200 -> player(FacialExpression.NEUTRAL, "Thanks, maybe another time.").also { stage = END_DIALOGUE }
// Right-click "Pay" - chop down tree
300 -> {
if (removeItem(player!!, Item(Items.COINS_995, 200))) {
patch.clear()
dialogue("The gardener obligingly removes your tree.").also { stage = END_DIALOGUE }
} else {
dialogue("You need 200 gp to pay for that.").also { stage = END_DIALOGUE } // not authentic
}
} }
} }

View file

@ -1,5 +1,6 @@
package content.global.skill.farming package content.global.skill.farming
import core.api.openDialogue
import core.game.node.Node import core.game.node.Node
import core.game.node.entity.player.Player import core.game.node.entity.player.Player
import core.game.interaction.InteractionListener import core.game.interaction.InteractionListener
@ -21,22 +22,7 @@ class FarmerPayOptionHandler : InteractionListener {
val farmer = Farmers.forId(node.id) ?: return false val farmer = Farmers.forId(node.id) ?: return false
val patch = farmer.patches[index].getPatchFor(player) val patch = farmer.patches[index].getPatchFor(player)
if(patch.plantable == null){ openDialogue(player, FarmerPayOptionDialogue(patch, true), node.asNpc())
player.dialogueInterpreter.sendDialogue("I have nothing to protect in that patch.")
return true
}
if(patch.protectionPaid){
player.dialogueInterpreter.sendDialogue("I have already paid to protect that patch.")
return true
}
if(patch.isGrown()){
player.dialogueInterpreter.sendDialogue("This patch is already fully grown!")
return true
}
player.dialogueInterpreter.open(FarmerPayOptionDialogue(patch),node.asNpc())
return true return true
} }
} }

View file

@ -117,8 +117,8 @@ enum class FarmingPatch(val varbit: Int, val type: PatchType) {
} }
} }
fun getPatchFor(player: Player): Patch{ fun getPatchFor(player: Player, addPatch : Boolean = true): Patch{
var crops = getOrStartTimer <CropGrowth> (player)!! val crops = getOrStartTimer <CropGrowth> (player)
return crops.getPatch(this) return crops.getPatch(this, addPatch)
} }
} }

View file

@ -1,43 +0,0 @@
package content.global.skill.farming
import core.api.*
import core.Util.clamp
import core.game.node.entity.player.Player
import core.game.system.task.Pulse
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.json.simple.JSONArray
import org.json.simple.JSONObject
import core.game.node.entity.state.PlayerState
import core.game.node.entity.state.State
import core.tools.SystemLogger
import java.util.concurrent.TimeUnit
import content.global.skill.farming.timers.*
@PlayerState("farming")
/**
* Kept around solely for the purpose of porting save data from this old system to the new one.
* //TODO REMOVE BY END OF 2023
**/
class FarmingState(player: Player? = null) : State(player) {
override fun save(root: JSONObject) {}
override fun parse(_data: JSONObject) {
player ?: return
if(_data.containsKey("farming-bins")){
_data["bins"] = _data["farming-bins"]
val timer = getOrStartTimer <Compost> (player)
timer.parse (_data, player)
}
if(_data.containsKey("farming-patches")){
_data["patches"] = _data["farming-patches"]
val timer = getOrStartTimer <CropGrowth> (player)
timer.parse(_data, player)
}
}
override fun newInstance(player: Player?): State {
return FarmingState(player)
}
override fun createPulse() {}
}

View file

@ -36,13 +36,22 @@ class HealthChecker : OptionHandler() {
rewardXP(player, Skills.FARMING, patch.plantable?.checkHealthXP ?: 0.0) rewardXP(player, Skills.FARMING, patch.plantable?.checkHealthXP ?: 0.0)
patch.isCheckHealth = false patch.isCheckHealth = false
when (type) { when (type) {
PatchType.TREE_PATCH -> patch.setCurrentState(patch.getCurrentState() + 1) PatchType.TREE_PATCH -> {
PatchType.FRUIT_TREE_PATCH -> patch.setCurrentState(patch.getCurrentState() - 14) patch.setCurrentState(patch.getCurrentState() + 1)
PatchType.BUSH_PATCH -> { sendMessage(player, "You examine the tree for signs of disease and find that it is in perfect health.")
sendMessage(player, "You examine the bush for signs of disease and find that it's in perfect health.") }
patch.setCurrentState(patch.plantable!!.value + patch.plantable!!.stages + 4) PatchType.FRUIT_TREE_PATCH -> {
patch.setCurrentState(patch.getCurrentState() - 14)
sendMessage(player, "You examine the tree for signs of disease and find that it is in perfect health.")
}
PatchType.BUSH_PATCH -> {
patch.setCurrentState(patch.plantable!!.value + patch.plantable!!.stages + 4)
sendMessage(player, "You examine the bush for signs of disease and find that it's in perfect health.")
}
PatchType.CACTUS_PATCH -> {
patch.setCurrentState(patch.plantable!!.value + patch.plantable!!.stages + 3)
sendMessage(player, "You examine the cactus for signs of disease and find that it is in perfect health.")
} }
PatchType.CACTUS_PATCH -> patch.setCurrentState(patch.plantable!!.value + patch.plantable!!.stages + 3)
else -> log(this::class.java, Log.ERR, "Unreachable patch type from when(type) switch in HealthChecker.kt") else -> log(this::class.java, Log.ERR, "Unreachable patch type from when(type) switch in HealthChecker.kt")
} }

View file

@ -123,6 +123,9 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
log(this::class.java, Log.DEBUG, "Patch for ${player.username} at varbit ${patch.varbit} with plantable ${plantable?.name ?: "none"} was set to diseased at stage $currentGrowthStage, which isn't valid.") log(this::class.java, Log.DEBUG, "Patch for ${player.username} at varbit ${patch.varbit} with plantable ${plantable?.name ?: "none"} was set to diseased at stage $currentGrowthStage, which isn't valid.")
return (state and (0x80.inv())) return (state and (0x80.inv()))
} }
else if (state in listOf(0, 1, 2, 3)){
// we're weedy (or an empty plot) as normal just continue
}
else { else {
log (this::class.java, Log.ERR, "Patch for ${player.username} at varbit ${patch.varbit} with plantable ${plantable?.name ?: "none"} was set to state $state at growth stage $currentGrowthStage, which isn't valid. We're not sure why this is happening.") log (this::class.java, Log.ERR, "Patch for ${player.username} at varbit ${patch.varbit} with plantable ${plantable?.name ?: "none"} was set to state $state at growth stage $currentGrowthStage, which isn't valid. We're not sure why this is happening.")
} }
@ -134,6 +137,13 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
return compost != CompostType.NONE return compost != CompostType.NONE
} }
/**
* Returns true if the patch is fully grown.
*
* Note: This returns true if the patch is fully weedy.
* Use `plantable == null` to check if a patch does
* not have anything planted.
*/
fun isGrown(): Boolean{ fun isGrown(): Boolean{
return currentGrowthStage == (plantable?.stages ?: 0) return currentGrowthStage == (plantable?.stages ?: 0)
} }
@ -276,19 +286,19 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
return return
} }
diseaseMod = when(compost){ // This is so a cheat can force disease
diseaseMod = if (diseaseMod < 0) -128 else when(compost){
CompostType.NONE -> 0 CompostType.NONE -> 0
CompostType.COMPOST -> 8 CompostType.COMPOST -> 8
CompostType.SUPERCOMPOST -> 13 CompostType.SUPERCOMPOST -> 13
} }
if(patch != FarmingPatch.TROLL_STRONGHOLD_HERB && RandomFunction.random(128) <= (17 - diseaseMod) && !isWatered && !isGrown() && !protectionPaid && !isFlowerProtected() && patch.type != PatchType.EVIL_TURNIP_PATCH ){ if(patch != FarmingPatch.TROLL_STRONGHOLD_HERB && RandomFunction.random(128) <= (17 - diseaseMod) && !isWatered && !isGrown() && !protectionPaid && !isFlowerProtected() && patch.type != PatchType.EVIL_TURNIP_PATCH && currentGrowthStage != 0){
//bush, tree, fruit tree, herb and cactus can not disease on stage 1(0) of growth.
if(!((patch.type == PatchType.BUSH_PATCH || patch.type == PatchType.TREE_PATCH || patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.CACTUS_PATCH || patch.type == PatchType.HERB_PATCH) && currentGrowthStage == 0)) {
isDiseased = true isDiseased = true
// If we manually set disease mod reset it back to 0 so that crops can naturally grow after being treated/accidentally attempted to disease when they cannot be
if (diseaseMod < 0) diseaseMod = 0
return return
} }
}
if((patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.TREE_PATCH || patch.type == PatchType.BUSH_PATCH || patch.type == PatchType.CACTUS_PATCH) && plantable != null && plantable?.stages == currentGrowthStage + 1){ if((patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.TREE_PATCH || patch.type == PatchType.BUSH_PATCH || patch.type == PatchType.CACTUS_PATCH) && plantable != null && plantable?.stages == currentGrowthStage + 1){
isCheckHealth = true isCheckHealth = true
@ -376,7 +386,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
FarmingPatch.CATHERBY_ALLOTMENT_S,FarmingPatch.CATHERBY_ALLOTMENT_N -> FarmingPatch.CATHERBY_FLOWER_C FarmingPatch.CATHERBY_ALLOTMENT_S,FarmingPatch.CATHERBY_ALLOTMENT_N -> FarmingPatch.CATHERBY_FLOWER_C
FarmingPatch.PORT_PHAS_ALLOTMENT_SE,FarmingPatch.PORT_PHAS_ALLOTMENT_NW -> FarmingPatch.PORT_PHAS_FLOWER_C FarmingPatch.PORT_PHAS_ALLOTMENT_SE,FarmingPatch.PORT_PHAS_ALLOTMENT_NW -> FarmingPatch.PORT_PHAS_FLOWER_C
else -> return false else -> return false
}.getPatchFor(player) }.getPatchFor(player, false)
return (fpatch.plantable != null && return (fpatch.plantable != null &&
(fpatch.plantable == plantable?.protectionFlower || fpatch.plantable == Plantable.forItemID(Items.WHITE_LILY_SEED_14589)) (fpatch.plantable == plantable?.protectionFlower || fpatch.plantable == Plantable.forItemID(Items.WHITE_LILY_SEED_14589))

View file

@ -15,7 +15,7 @@ object PatchRaker {
val p = patch.getPatchFor(player) val p = patch.getPatchFor(player)
val patchName = p.patch.type.displayName() val patchName = p.patch.type.displayName()
var firstRake = true var firstRake = true
if (p.isEmptyAndWeeded()) { if (!p.isWeedy()) {
sendMessage(player, "This $patchName doesn't need weeding right now.") sendMessage(player, "This $patchName doesn't need weeding right now.")
return return
} }

View file

@ -3,88 +3,88 @@ package content.global.skill.farming
import core.game.node.item.Item import core.game.node.item.Item
import org.rs09.consts.Items import org.rs09.consts.Items
enum class Plantable(val itemID: Int, val value: Int, val stages: Int, val plantingXP: Double, val harvestXP: Double, val checkHealthXP: Double, val requiredLevel: Int, val applicablePatch: PatchType, val harvestItem: Int, val protectionItem: Item? = null,val protectionFlower: Plantable? = null) { enum class Plantable(val itemID: Int, val displayName: String, val value: Int, val stages: Int, val plantingXP: Double, val harvestXP: Double, val checkHealthXP: Double, val requiredLevel: Int, val applicablePatch: PatchType, val harvestItem: Int, val protectionItem: Item? = null, val protectionFlower: Plantable? = null) {
// Flowers // Flowers
MARIGOLD_SEED(5096,8,4,8.5,47.0,0.0,2,PatchType.FLOWER_PATCH,Items.MARIGOLDS_6010), MARIGOLD_SEED(Items.MARIGOLD_SEED_5096,"marigold seed",8,4,8.5,47.0,0.0,2,PatchType.FLOWER_PATCH,Items.MARIGOLDS_6010),
ROSEMARY_SEED(5097,13,4,12.0,66.5,0.0,11,PatchType.FLOWER_PATCH, Items.ROSEMARY_6014), ROSEMARY_SEED(Items.ROSEMARY_SEED_5097,"rosemary seed",13,4,12.0,66.5,0.0,11,PatchType.FLOWER_PATCH, Items.ROSEMARY_6014),
NASTURTIUM_SEED(5098,18,4,19.5,111.0,0.0,24,PatchType.FLOWER_PATCH,Items.NASTURTIUMS_6012), NASTURTIUM_SEED(Items.NASTURTIUM_SEED_5098,"nasturtium seed",18,4,19.5,111.0,0.0,24,PatchType.FLOWER_PATCH,Items.NASTURTIUMS_6012),
WOAD_SEED(5099,23,4,20.5,115.5,0.0,25,PatchType.FLOWER_PATCH,Items.WOAD_LEAF_1793), WOAD_SEED(Items.WOAD_SEED_5099,"woad seed",23,4,20.5,115.5,0.0,25,PatchType.FLOWER_PATCH,Items.WOAD_LEAF_1793),
LIMPWURT_SEED(5100,28,4,21.5,120.0,0.0,26,PatchType.FLOWER_PATCH,Items.LIMPWURT_ROOT_225), LIMPWURT_SEED(Items.LIMPWURT_SEED_5100,"limpwurt seed",28,4,21.5,120.0,0.0,26,PatchType.FLOWER_PATCH,Items.LIMPWURT_ROOT_225),
WHITE_LILY_SEED(14589,37,4,42.0,250.0,0.0,52,PatchType.FLOWER_PATCH,Items.WHITE_LILY_14583), WHITE_LILY_SEED(Items.WHITE_LILY_SEED_14589,"white lily seed",37,4,42.0,250.0,0.0,52,PatchType.FLOWER_PATCH,Items.WHITE_LILY_14583),
//Flower(Technically) // Flower (technically)
SCARECROW(6059,33,3,0.0,0.0,0.0,23,PatchType.FLOWER_PATCH,Items.SCARECROW_6059), SCARECROW(Items.SCARECROW_6059,"scarecrow",33,3,0.0,0.0,0.0,23,PatchType.FLOWER_PATCH,Items.SCARECROW_6059),
// Allotments // Allotments
POTATO_SEED(5318, 6, 4, 8.0, 9.0, 0.0, 1, PatchType.ALLOTMENT, Items.POTATO_1942,Item(Items.COMPOST_6032,2),MARIGOLD_SEED), POTATO_SEED(Items.POTATO_SEED_5318, "potato seed", 6, 4, 8.0, 9.0, 0.0, 1, PatchType.ALLOTMENT, Items.POTATO_1942,Item(Items.COMPOST_6032,2),MARIGOLD_SEED),
ONION_SEED(5319, 13, 4, 9.5, 10.5,0.0, 5, PatchType.ALLOTMENT,Items.ONION_1957,Item(Items.POTATOES10_5438),MARIGOLD_SEED), ONION_SEED(Items.ONION_SEED_5319, "onion seed", 13, 4, 9.5, 10.5,0.0, 5, PatchType.ALLOTMENT,Items.ONION_1957,Item(Items.POTATOES10_5438),MARIGOLD_SEED),
CABBAGE_SEED(5324, 20, 4, 10.0, 11.5, 0.0,7, PatchType.ALLOTMENT,Items.CABBAGE_1965,Item(Items.ONIONS10_5458),ROSEMARY_SEED), CABBAGE_SEED(Items.CABBAGE_SEED_5324, "cabbage seed", 20, 4, 10.0, 11.5, 0.0,7, PatchType.ALLOTMENT,Items.CABBAGE_1965,Item(Items.ONIONS10_5458),ROSEMARY_SEED),
TOMATO_SEED(5322,27,4,12.5,14.0,0.0,12,PatchType.ALLOTMENT,Items.TOMATO_1982,Item(Items.CABBAGES10_5478,2),MARIGOLD_SEED), TOMATO_SEED(Items.TOMATO_SEED_5322,"tomato seed",27,4,12.5,14.0,0.0,12,PatchType.ALLOTMENT,Items.TOMATO_1982,Item(Items.CABBAGES10_5478,2),MARIGOLD_SEED),
SWEETCORN_SEED(5320,34,6,17.0,19.0,0.0,20,PatchType.ALLOTMENT,Items.SWEETCORN_5986,Item(Items.JUTE_FIBRE_5931,10),SCARECROW), SWEETCORN_SEED(Items.SWEETCORN_SEED_5320,"sweetcorn seed",34,6,17.0,19.0,0.0,20,PatchType.ALLOTMENT,Items.SWEETCORN_5986,Item(Items.JUTE_FIBRE_5931,10),SCARECROW),
STRAWBERRY_SEED(5323,43,6,26.0,29.0,0.0,31,PatchType.ALLOTMENT,Items.STRAWBERRY_5504,Item(Items.APPLES5_5386)), STRAWBERRY_SEED(Items.STRAWBERRY_SEED_5323,"strawberry seed",43,6,26.0,29.0,0.0,31,PatchType.ALLOTMENT,Items.STRAWBERRY_5504,Item(Items.APPLES5_5386)),
WATERMELON_SEED(5321,52,8,48.5,54.5,0.0,47,PatchType.ALLOTMENT,Items.WATERMELON_5982,Item(Items.CURRY_LEAF_5970,10),NASTURTIUM_SEED), WATERMELON_SEED(Items.WATERMELON_SEED_5321,"watermelon seed",52,8,48.5,54.5,0.0,47,PatchType.ALLOTMENT,Items.WATERMELON_5982,Item(Items.CURRY_LEAF_5970,10),NASTURTIUM_SEED),
// Hops // Hops
BARLEY_SEED(5305,49,4,8.5,9.5,0.0,3,PatchType.HOPS_PATCH,Items.BARLEY_6006,Item(Items.COMPOST_6032,3)), BARLEY_SEED(Items.BARLEY_SEED_5305,"barley seed",49,4,8.5,9.5,0.0,3,PatchType.HOPS_PATCH,Items.BARLEY_6006,Item(Items.COMPOST_6032,3)),
HAMMERSTONE_SEED(5307,4,4,9.0,10.0,0.0,4,PatchType.HOPS_PATCH,Items.HAMMERSTONE_HOPS_5994,Item(Items.MARIGOLDS_6010)), HAMMERSTONE_SEED(Items.HAMMERSTONE_SEED_5307,"Hammerstone hop seed",4,4,9.0,10.0,0.0,4,PatchType.HOPS_PATCH,Items.HAMMERSTONE_HOPS_5994,Item(Items.MARIGOLDS_6010)),
ASGARNIAN_SEED(5308,11,5,10.9,12.0,0.0,8,PatchType.HOPS_PATCH,Items.ASGARNIAN_HOPS_5996,Item(Items.ONIONS10_5458)), ASGARNIAN_SEED(Items.ASGARNIAN_SEED_5308,"Asgarnian hop seed",11,5,10.9,12.0,0.0,8,PatchType.HOPS_PATCH,Items.ASGARNIAN_HOPS_5996,Item(Items.ONIONS10_5458)),
JUTE_SEED(5306,56,5,13.0,14.5,0.0,13,PatchType.HOPS_PATCH,Items.JUTE_FIBRE_5931,Item(Items.BARLEY_MALT_6008,6)), JUTE_SEED(Items.JUTE_SEED_5306,"jute plant seed",56,5,13.0,14.5,0.0,13,PatchType.HOPS_PATCH,Items.JUTE_FIBRE_5931,Item(Items.BARLEY_MALT_6008,6)),
YANILLIAN_SEED(5309,19,6,14.5,16.0,0.0,16,PatchType.HOPS_PATCH,Items.YANILLIAN_HOPS_5998,Item(Items.TOMATOES5_5968)), YANILLIAN_SEED(Items.YANILLIAN_SEED_5309,"Yanillian hop seed",19,6,14.5,16.0,0.0,16,PatchType.HOPS_PATCH,Items.YANILLIAN_HOPS_5998,Item(Items.TOMATOES5_5968)),
KRANDORIAN_SEED(5310,28,7,17.5,19.5,0.0,21,PatchType.HOPS_PATCH,Items.KRANDORIAN_HOPS_6000,Item(Items.CABBAGES10_5478,3)), KRANDORIAN_SEED(Items.KRANDORIAN_SEED_5310,"Krandorian hop seed",28,7,17.5,19.5,0.0,21,PatchType.HOPS_PATCH,Items.KRANDORIAN_HOPS_6000,Item(Items.CABBAGES10_5478,3)),
WILDBLOOD_SEED(5311,38,8,23.0,26.0,0.0,28,PatchType.HOPS_PATCH,Items.WILDBLOOD_HOPS_6002,Item(Items.NASTURTIUMS_6012)), WILDBLOOD_SEED(Items.WILDBLOOD_SEED_5311,"Wildblood hop seed",38,8,23.0,26.0,0.0,28,PatchType.HOPS_PATCH,Items.WILDBLOOD_HOPS_6002,Item(Items.NASTURTIUMS_6012)),
// Trees // Trees
OAK_SAPLING(5370,8,4,14.0,0.0,467.3,15,PatchType.TREE_PATCH,Items.OAK_ROOTS_6043,Item(Items.TOMATOES5_5968)), OAK_SAPLING(Items.OAK_SAPLING_5370,"oak sapling",8,4,14.0,0.0,467.3,15,PatchType.TREE_PATCH,Items.OAK_ROOTS_6043,Item(Items.TOMATOES5_5968)),
WILLOW_SAPLING(5371,15,6,25.0,0.0,1456.5,30,PatchType.TREE_PATCH,Items.WILLOW_ROOTS_6045,Item(Items.APPLES5_5386)), WILLOW_SAPLING(Items.WILLOW_SAPLING_5371,"willow sapling",15,6,25.0,0.0,1456.5,30,PatchType.TREE_PATCH,Items.WILLOW_ROOTS_6045,Item(Items.APPLES5_5386)),
MAPLE_SAPLING(5372,24,8,45.0,0.0,3403.4,45,PatchType.TREE_PATCH,Items.MAPLE_ROOTS_6047,Item(Items.ORANGES5_5396)), MAPLE_SAPLING(Items.MAPLE_SAPLING_5372,"maple sapling",24,8,45.0,0.0,3403.4,45,PatchType.TREE_PATCH,Items.MAPLE_ROOTS_6047,Item(Items.ORANGES5_5396)),
YEW_SAPLING(5373,35,10,81.0,0.0,7069.9,60,PatchType.TREE_PATCH,Items.YEW_ROOTS_6049,Item(Items.CACTUS_SPINE_6016,10)), YEW_SAPLING(Items.YEW_SAPLING_5373,"yew sapling",35,10,81.0,0.0,7069.9,60,PatchType.TREE_PATCH,Items.YEW_ROOTS_6049,Item(Items.CACTUS_SPINE_6016,10)),
MAGIC_SAPLING(5374,48,12,145.5,0.0,13768.3,75,PatchType.TREE_PATCH,Items.MAGIC_ROOTS_6051,Item(Items.COCONUT_5974,25)), MAGIC_SAPLING(Items.MAGIC_SAPLING_5374,"magic Tree sapling",48,12,145.5,0.0,13768.3,75,PatchType.TREE_PATCH,Items.MAGIC_ROOTS_6051,Item(Items.COCONUT_5974,25)),
// Fruit Trees // Fruit Trees
APPLE_SAPLING(5496,8,6,22.0,8.5,1199.5,27,PatchType.FRUIT_TREE_PATCH,Items.COOKING_APPLE_1955,Item(Items.SWEETCORN_5986,9)), APPLE_SAPLING(Items.APPLE_SAPLING_5496,"apple tree sapling",8,6,22.0,8.5,1199.5,27,PatchType.FRUIT_TREE_PATCH,Items.COOKING_APPLE_1955,Item(Items.SWEETCORN_5986,9)),
BANANA_SAPLING(5497,35,6,28.0,10.5,1750.5,33,PatchType.FRUIT_TREE_PATCH,Items.BANANA_1963,Item(Items.APPLES5_5386,4)), BANANA_SAPLING(Items.BANANA_SAPLING_5497,"banana tree sapling",35,6,28.0,10.5,1750.5,33,PatchType.FRUIT_TREE_PATCH,Items.BANANA_1963,Item(Items.APPLES5_5386,4)),
ORANGE_SAPLING(5498,72,6,35.5,13.5,2470.2,39,PatchType.FRUIT_TREE_PATCH,Items.ORANGE_2108,Item(Items.STRAWBERRIES5_5406,3)), ORANGE_SAPLING(Items.ORANGE_SAPLING_5498,"orange tree sapling",72,6,35.5,13.5,2470.2,39,PatchType.FRUIT_TREE_PATCH,Items.ORANGE_2108,Item(Items.STRAWBERRIES5_5406,3)),
CURRY_SAPLING(5499,99,6,40.0,15.0,2906.9,42,PatchType.FRUIT_TREE_PATCH,Items.CURRY_LEAF_5970,Item(Items.BANANAS5_5416,5)), CURRY_SAPLING(Items.CURRY_SAPLING_5499,"curry tree sapling",99,6,40.0,15.0,2906.9,42,PatchType.FRUIT_TREE_PATCH,Items.CURRY_LEAF_5970,Item(Items.BANANAS5_5416,5)),
PINEAPPLE_SAPLING(5500,136,6,57.0,21.5,4605.7,51,PatchType.FRUIT_TREE_PATCH,Items.PINEAPPLE_2114,Item(Items.WATERMELON_5982,10)), PINEAPPLE_SAPLING(Items.PINEAPPLE_SAPLING_5500,"pineapple plant",136,6,57.0,21.5,4605.7,51,PatchType.FRUIT_TREE_PATCH,Items.PINEAPPLE_2114,Item(Items.WATERMELON_5982,10)),
PAPAYA_SAPLING(5501,163,6,72.0,27.0,6146.4,57,PatchType.FRUIT_TREE_PATCH,Items.PAPAYA_FRUIT_5972,Item(Items.PINEAPPLE_2114,10)), PAPAYA_SAPLING(Items.PAPAYA_SAPLING_5501,"papaya tree sapling",163,6,72.0,27.0,6146.4,57,PatchType.FRUIT_TREE_PATCH,Items.PAPAYA_FRUIT_5972,Item(Items.PINEAPPLE_2114,10)),
PALM_SAPLING(5502,200,6,110.5,41.5,10150.1,68,PatchType.FRUIT_TREE_PATCH,Items.COCONUT_5974,Item(Items.PAPAYA_FRUIT_5972,15)), PALM_SAPLING(Items.PALM_SAPLING_5502,"palm tree sapling",200,6,110.5,41.5,10150.1,68,PatchType.FRUIT_TREE_PATCH,Items.COCONUT_5974,Item(Items.PAPAYA_FRUIT_5972,15)),
// Bushes // Bushes
REDBERRY_SEED(5101,5,5,11.5,4.5,64.0,10,PatchType.BUSH_PATCH,Items.REDBERRIES_1951,Item(Items.CABBAGES10_5478,4)), REDBERRY_SEED(Items.REDBERRY_SEED_5101,"redberry bush seed",5,5,11.5,4.5,64.0,10,PatchType.BUSH_PATCH,Items.REDBERRIES_1951,Item(Items.CABBAGES10_5478,4)),
CADAVABERRY_SEED(5102,15,6,18.0,7.0,102.5,22,PatchType.BUSH_PATCH,Items.CADAVA_BERRIES_753,Item(Items.TOMATOES5_5968,3)), CADAVABERRY_SEED(Items.CADAVABERRY_SEED_5102,"cadavaberry bush seed",15,6,18.0,7.0,102.5,22,PatchType.BUSH_PATCH,Items.CADAVA_BERRIES_753,Item(Items.TOMATOES5_5968,3)),
DWELLBERRY_SEED(5103,26,27,31.5,12.0,177.5,36,PatchType.BUSH_PATCH,Items.DWELLBERRIES_2126,Item(Items.STRAWBERRIES5_5406,3)), DWELLBERRY_SEED(Items.DWELLBERRY_SEED_5103,"dwellberry bush seed",26,27,31.5,12.0,177.5,36,PatchType.BUSH_PATCH,Items.DWELLBERRIES_2126,Item(Items.STRAWBERRIES5_5406,3)),
JANGERBERRY_SEED(5104,38,8,50.5,19.0,284.5,48,PatchType.BUSH_PATCH,Items.JANGERBERRIES_247,Item(Items.WATERMELON_5982,6)), JANGERBERRY_SEED(Items.JANGERBERRY_SEED_5104,"jangerberry bush seed",38,8,50.5,19.0,284.5,48,PatchType.BUSH_PATCH,Items.JANGERBERRIES_247,Item(Items.WATERMELON_5982,6)),
WHITEBERRY_SEED(5105,51,8,78.0,29.0,437.5,59,PatchType.BUSH_PATCH,Items.WHITE_BERRIES_239,null), WHITEBERRY_SEED(Items.WHITEBERRY_SEED_5105,"whiteberry bush seed",51,8,78.0,29.0,437.5,59,PatchType.BUSH_PATCH,Items.WHITE_BERRIES_239,null),
POISON_IVY_SEED(5106,197,8,120.0,45.0,675.0,70,PatchType.BUSH_PATCH,Items.POISON_IVY_BERRIES_6018,null), POISON_IVY_SEED(Items.POISON_IVY_SEED_5106,"poison ivy bush seed",197,8,120.0,45.0,675.0,70,PatchType.BUSH_PATCH,Items.POISON_IVY_BERRIES_6018,null),
// Herbs // Herbs
GUAM_SEED(5291,4,4,11.0,12.5,0.0,9,PatchType.HERB_PATCH,Items.GRIMY_GUAM_199), GUAM_SEED(Items.GUAM_SEED_5291,"guam seed",4,4,11.0,12.5,0.0,9,PatchType.HERB_PATCH,Items.GRIMY_GUAM_199),
MARRENTILL_SEED(5292,11,4,13.5,15.0,0.0,14,PatchType.HERB_PATCH,Items.GRIMY_MARRENTILL_201), MARRENTILL_SEED(Items.MARRENTILL_SEED_5292,"marrentill seed",11,4,13.5,15.0,0.0,14,PatchType.HERB_PATCH,Items.GRIMY_MARRENTILL_201),
TARROMIN_SEED(5293,18,4,16.0,18.0,0.0,19,PatchType.HERB_PATCH,Items.GRIMY_TARROMIN_203), TARROMIN_SEED(Items.TARROMIN_SEED_5293,"tarromin seed",18,4,16.0,18.0,0.0,19,PatchType.HERB_PATCH,Items.GRIMY_TARROMIN_203),
HARRALANDER_SEED(5294,25,4,21.5,24.0,0.0,26,PatchType.HERB_PATCH,Items.GRIMY_HARRALANDER_205), HARRALANDER_SEED(Items.HARRALANDER_SEED_5294,"harralander seed",25,4,21.5,24.0,0.0,26,PatchType.HERB_PATCH,Items.GRIMY_HARRALANDER_205),
RANARR_SEED(5295,32,4,27.0,30.5,0.0,32,PatchType.HERB_PATCH,Items.GRIMY_RANARR_207), RANARR_SEED(Items.RANARR_SEED_5295,"ranarr seed",32,4,27.0,30.5,0.0,32,PatchType.HERB_PATCH,Items.GRIMY_RANARR_207),
AVANTOE_SEED(5298,39,4,54.5,61.5,0.0,50,PatchType.HERB_PATCH,Items.GRIMY_AVANTOE_211), AVANTOE_SEED(Items.AVANTOE_SEED_5298,"avantoe seed",39,4,54.5,61.5,0.0,50,PatchType.HERB_PATCH,Items.GRIMY_AVANTOE_211),
TOADFLAX_SEED(5296,46,4,34.0,38.5,0.0,38,PatchType.HERB_PATCH,Items.GRIMY_TOADFLAX_3049), TOADFLAX_SEED(Items.TOADFLAX_SEED_5296,"toadflax seed",46,4,34.0,38.5,0.0,38,PatchType.HERB_PATCH,Items.GRIMY_TOADFLAX_3049),
IRIT_SEED(5297,53,4,43.0,48.5,0.0,44,PatchType.HERB_PATCH,Items.GRIMY_IRIT_209), IRIT_SEED(Items.IRIT_SEED_5297,"irit seed",53,4,43.0,48.5,0.0,44,PatchType.HERB_PATCH,Items.GRIMY_IRIT_209),
KWUARM_SEED(5299,68,4,69.0,78.0,0.0,56,PatchType.HERB_PATCH,Items.GRIMY_KWUARM_213), KWUARM_SEED(Items.KWUARM_SEED_5299,"kwuarm seed",68,4,69.0,78.0,0.0,56,PatchType.HERB_PATCH,Items.GRIMY_KWUARM_213),
SNAPDRAGON_SEED(5300,75,4,87.5,98.5,0.0,62,PatchType.HERB_PATCH,Items.GRIMY_SNAPDRAGON_3051), SNAPDRAGON_SEED(Items.SNAPDRAGON_SEED_5300,"snapdragon seed",75,4,87.5,98.5,0.0,62,PatchType.HERB_PATCH,Items.GRIMY_SNAPDRAGON_3051),
CADANTINE_SEED(5301,82,4,106.5,120.0,0.0,67,PatchType.HERB_PATCH,Items.GRIMY_CADANTINE_215), CADANTINE_SEED(Items.CADANTINE_SEED_5301,"cadantine seed",82,4,106.5,120.0,0.0,67,PatchType.HERB_PATCH,Items.GRIMY_CADANTINE_215),
LANTADYME_SEED(5302,89,4,134.5,151.5,0.0,73,PatchType.HERB_PATCH,Items.GRIMY_LANTADYME_2485), LANTADYME_SEED(Items.LANTADYME_SEED_5302,"lantadyme seed",89,4,134.5,151.5,0.0,73,PatchType.HERB_PATCH,Items.GRIMY_LANTADYME_2485),
DWARF_WEED_SEED(5303,96,4,170.5,192.0,0.0,79,PatchType.HERB_PATCH,Items.GRIMY_DWARF_WEED_217), DWARF_WEED_SEED(Items.DWARF_WEED_SEED_5303,"dwarf weed seed",96,4,170.5,192.0,0.0,79,PatchType.HERB_PATCH,Items.GRIMY_DWARF_WEED_217),
TORSTOL_SEED(5304,103,4,199.5,224.5,0.0,85,PatchType.HERB_PATCH,Items.GRIMY_TORSTOL_219), TORSTOL_SEED(Items.TORSTOL_SEED_5304,"torstol seed",103,4,199.5,224.5,0.0,85,PatchType.HERB_PATCH,Items.GRIMY_TORSTOL_219),
GOUT_TUBER(6311,192,4,105.0,45.0,0.0,29,PatchType.HERB_PATCH,Items.GOUTWEED_3261), GOUT_TUBER(Items.GOUT_TUBER_6311,"gout tuber",192,4,105.0,45.0,0.0,29,PatchType.HERB_PATCH,Items.GOUTWEED_3261),
SPIRIT_WEED_SEED(12176, 204, 4, 32.0, 36.0, 0.0, 36, PatchType.HERB_PATCH, Items.GRIMY_SPIRIT_WEED_12174), SPIRIT_WEED_SEED(Items.SPIRIT_WEED_SEED_12176,"spirit weed seed", 204, 4, 32.0, 36.0, 0.0, 36, PatchType.HERB_PATCH, Items.GRIMY_SPIRIT_WEED_12174),
//Other // Special
BELLADONNA_SEED(5281, 4, 4, 91.0, 128.0, 0.0, 63, PatchType.BELLADONNA_PATCH, Items.CAVE_NIGHTSHADE_2398), BELLADONNA_SEED(Items.BELLADONNA_SEED_5281, "belladonna seed", 4, 4, 91.0, 128.0, 0.0, 63, PatchType.BELLADONNA_PATCH, Items.CAVE_NIGHTSHADE_2398),
MUSHROOM_SPORE(Items.MUSHROOM_SPORE_5282, 6, 7, 61.5, 57.7, 0.0, 53, PatchType.MUSHROOM_PATCH, Items.MUSHROOM_6004), MUSHROOM_SPORE(Items.MUSHROOM_SPORE_5282, "mushroom spore", 6, 7, 61.5, 57.7, 0.0, 53, PatchType.MUSHROOM_PATCH, Items.MUSHROOM_6004),
CACTUS_SEED(Items.CACTUS_SEED_5280, 8, 7, 66.5, 25.0, 374.0, 55, PatchType.CACTUS_PATCH, Items.CACTUS_SPINE_6016), CACTUS_SEED(Items.CACTUS_SEED_5280, "cactus seed", 8, 7, 66.5, 25.0, 374.0, 55, PatchType.CACTUS_PATCH, Items.CACTUS_SPINE_6016),
EVIL_TURNIP_SEED(Items.EVIL_TURNIP_SEED_12148, 4, 1, 41.0, 46.0, 0.0, 42, PatchType.EVIL_TURNIP_PATCH, Items.EVIL_TURNIP_12134) EVIL_TURNIP_SEED(Items.EVIL_TURNIP_SEED_12148, "evil turnip seed", 4, 1, 41.0, 46.0, 0.0, 42, PatchType.EVIL_TURNIP_PATCH, Items.EVIL_TURNIP_12134)
; ;
constructor(itemID: Int, value: Int, stages: Int, plantingXP: Double, harvestXP: Double, checkHealthXP: Double, requiredLevel: Int, applicablePatch: PatchType, harvestItem: Int, protectionFlower: Plantable) constructor(itemID: Int, displayName: String, value: Int, stages: Int, plantingXP: Double, harvestXP: Double, checkHealthXP: Double, requiredLevel: Int, applicablePatch: PatchType, harvestItem: Int, protectionFlower: Plantable)
: this(itemID,value,stages,plantingXP,harvestXP,checkHealthXP,requiredLevel,applicablePatch,harvestItem,null,protectionFlower) : this(itemID,displayName,value,stages,plantingXP,harvestXP,checkHealthXP,requiredLevel,applicablePatch,harvestItem,null,protectionFlower)
companion object { companion object {
@JvmField @JvmField
val plantables = values().map { it.itemID to it }.toMap() val plantables = values().map { it.itemID to it }.toMap()

View file

@ -69,7 +69,7 @@ class ToolLeprechaunInterface : InterfaceListener {
setHasMagicSecateurs(player,false) setHasMagicSecateurs(player,false)
} }
} else { } else {
sendMessage(player, "You already have one of those stored.") sendMessage(player, "You cannot store more than one pair of secateurs in here.")
} }
} }
22 -> { 22 -> {
@ -80,7 +80,7 @@ class ToolLeprechaunInterface : InterfaceListener {
removeItem(player, can) removeItem(player, can)
setWateringCan(player,can) setWateringCan(player,can)
} else { } else {
sendMessage(player, "You already have one of those stored.") sendMessage(player, "You cannot store more than one watering can in here.")
} }
} }
23 -> doDeposit(player, Items.GARDENING_TROWEL_5325, ::setHasGardeningTrowel, ::hasGardeningTrowel) 23 -> doDeposit(player, Items.GARDENING_TROWEL_5325, ::setHasGardeningTrowel, ::hasGardeningTrowel)
@ -119,7 +119,15 @@ class ToolLeprechaunInterface : InterfaceListener {
depositMethod.invoke(player, true) depositMethod.invoke(player, true)
removeItem(player, item) removeItem(player, item)
} else { } else {
sendMessage(player, "You already have one of those stored.") val itemName = when (item) {
// secateurs and watering cans are handled separately
Items.RAKE_5341 -> "rake"
Items.SEED_DIBBER_5343 -> "dibber"
Items.SPADE_952 -> "spade"
Items.GARDENING_TROWEL_5325 -> "trowel"
else -> getItemName(item).lowercase()
}
sendMessage(player, "You cannot store more than one $itemName in here.")
} }
} }

View file

@ -5,7 +5,6 @@ import core.game.node.entity.player.link.diary.DiaryType
import core.game.node.entity.skill.Skills import core.game.node.entity.skill.Skills
import core.game.node.item.Item import core.game.node.item.Item
import core.game.system.task.Pulse import core.game.system.task.Pulse
import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items import org.rs09.consts.Items
import core.game.interaction.IntType import core.game.interaction.IntType
import core.game.interaction.InteractionListener import core.game.interaction.InteractionListener
@ -21,11 +20,14 @@ class UseWithPatchHandler : InteractionListener {
val SECATEURS = Items.SECATEURS_5329 val SECATEURS = Items.SECATEURS_5329
val MAGIC_SECATEURS = Items.MAGIC_SECATEURS_7409 val MAGIC_SECATEURS = Items.MAGIC_SECATEURS_7409
val TROWEL = Items.GARDENING_TROWEL_5325 val TROWEL = Items.GARDENING_TROWEL_5325
val pourBucketAnim = Animation(2283) val spadeDigAnim = getAnimation(830)
val wateringCanAnim = Animation(2293) val trowelDigAnim = getAnimation(2272)
val plantCureAnim = Animation(2288) val pourBucketAnim = getAnimation(2283)
val secateursTreeAnim = Animation(2277) val seedDibberAnim = getAnimation(2291)
val magicSecateursTreeAnim = Animation(3340) val wateringCanAnim = getAnimation(2293)
val plantCureAnim = getAnimation(2288)
val secateursTreeAnim = getAnimation(2277)
val magicSecateursTreeAnim = getAnimation(3340)
@JvmField @JvmField
val allowedNodes = ArrayList<Int>() val allowedNodes = ArrayList<Int>()
@ -46,7 +48,7 @@ class UseWithPatchHandler : InteractionListener {
RAKE -> PatchRaker.rake(player,patch) RAKE -> PatchRaker.rake(player,patch)
SEED_DIBBER -> sendMessage(player, "I should plant a seed, not the seed dibber.") SEED_DIBBER -> sendMessage(player, "I should plant a seed, not the seed dibber.")
SPADE -> { SPADE -> {
val anim = getAnimation(830) val anim = spadeDigAnim
val p = patch.getPatchFor(player) val p = patch.getPatchFor(player)
if (p.isDead) { if (p.isDead) {
sendMessage(player, "You start digging the farming patch...") sendMessage(player, "You start digging the farming patch...")
@ -112,7 +114,7 @@ class UseWithPatchHandler : InteractionListener {
return@onUseWith true return@onUseWith true
} }
val anim = Animation(2272) val anim = trowelDigAnim
submitIndividualPulse(player, object : Pulse(anim.duration) { submitIndividualPulse(player, object : Pulse(anim.duration) {
override fun pulse(): Boolean { override fun pulse(): Boolean {
@ -156,15 +158,21 @@ class UseWithPatchHandler : InteractionListener {
Items.WATERING_CAN_5331,Items.WATERING_CAN1_5333,Items.WATERING_CAN2_5334,Items.WATERING_CAN3_5335,Items.WATERING_CAN4_5336,Items.WATERING_CAN5_5337,Items.WATERING_CAN6_5338,Items.WATERING_CAN7_5339,Items.WATERING_CAN8_5340 -> { Items.WATERING_CAN_5331,Items.WATERING_CAN1_5333,Items.WATERING_CAN2_5334,Items.WATERING_CAN3_5335,Items.WATERING_CAN4_5336,Items.WATERING_CAN5_5337,Items.WATERING_CAN6_5338,Items.WATERING_CAN7_5339,Items.WATERING_CAN8_5340 -> {
val p = patch.getPatchFor(player) val p = patch.getPatchFor(player)
val t = p.patch.type val t = p.patch.type
if (p.isWatered || p.isEmptyAndWeeded() || p.isGrown() || p.plantable == Plantable.SCARECROW) { if (t == PatchType.ALLOTMENT || t == PatchType.FLOWER_PATCH || t == PatchType.HOPS_PATCH) {
sendMessage(player, "This patch doesn't need watering.")
} else if (t == PatchType.ALLOTMENT || t == PatchType.FLOWER_PATCH || t == PatchType.HOPS_PATCH) {
submitIndividualPulse(player, object : Pulse() { submitIndividualPulse(player, object : Pulse() {
override fun pulse(): Boolean { override fun pulse(): Boolean {
if (p.isWeedy()) { if (p.isWeedy() || p.isEmptyAndWeeded()) {
sendMessage(player, "You should grow something first.") sendMessage(player, "You should grow something first.")
return true return true
} }
if (p.isWatered || p.isGrown() || p.plantable == Plantable.SCARECROW) {
sendMessage(player, "This patch doesn't need watering.")
return true
}
if (p.isDiseased || p.isDead) {
sendMessage(player, "Water isn't going to cure that!")
return true
}
if (usedItem.id == Items.WATERING_CAN_5331) { if (usedItem.id == Items.WATERING_CAN_5331) {
sendMessage(player, "You need to fill the watering can first.") sendMessage(player, "You need to fill the watering can first.")
return true return true
@ -178,6 +186,8 @@ class UseWithPatchHandler : InteractionListener {
return true return true
} }
}) })
} else {
sendMessage(player, "This patch doesn't need watering.")
} }
} }
@ -212,9 +222,9 @@ class UseWithPatchHandler : InteractionListener {
val plantable = Plantable.forItemID(usedItem.id) ?: return@onUseWith false val plantable = Plantable.forItemID(usedItem.id) ?: return@onUseWith false
if (plantable.applicablePatch != patch.type) { if (plantable.applicablePatch != patch.type) {
val seedNamePlural = StringUtils.plusS(plantable.name.replace("_", " ").lowercase()) val plantableNamePlural = StringUtils.plusS(plantable.displayName)
val patchType = if (plantable.applicablePatch == PatchType.ALLOTMENT) "a vegetable patch" else prependArticle(plantable.applicablePatch.displayName()) val patchType = if (plantable.applicablePatch == PatchType.ALLOTMENT) "a vegetable patch" else prependArticle(plantable.applicablePatch.displayName())
sendMessage(player, "You can only plant $seedNamePlural in $patchType.") sendMessage(player, "You can only plant $plantableNamePlural in $patchType.")
return@onUseWith true return@onUseWith true
} }
@ -232,37 +242,40 @@ class UseWithPatchHandler : InteractionListener {
return@onUseWith true return@onUseWith true
} }
val plantItem = val plantItem = when (patch.type) {
if (patch.type == PatchType.ALLOTMENT) Item(plantable.itemID,3) else if (patch.type == PatchType.HOPS_PATCH) { PatchType.ALLOTMENT -> Item(plantable.itemID, 3)
if (plantable == Plantable.JUTE_SEED) Item(plantable.itemID,3) else Item(plantable.itemID,4) PatchType.HOPS_PATCH -> if (plantable == Plantable.JUTE_SEED) Item(plantable.itemID, 3) else Item(plantable.itemID, 4)
} else { else -> Item(plantable.itemID,1)
Item(plantable.itemID,1)
} }
if (patch.type == PatchType.ALLOTMENT) {
if (!player.inventory.containsItem(plantItem)) { if (!player.inventory.containsItem(plantItem)) {
sendMessage(player, "You need 3 seeds to plant an allotment patch.") val seedPlural = if (plantItem.amount == 1) "seed" else "seeds"
sendMessage(player, "You need ${plantItem.amount} $seedPlural to plant ${prependArticle(patch.type.displayName())}.")
return@onUseWith true return@onUseWith true
} }
val requiredItem = when (patch.type) {
PatchType.TREE_PATCH, PatchType.FRUIT_TREE_PATCH -> Items.SPADE_952
PatchType.FLOWER_PATCH -> if (plantable == Plantable.SCARECROW) null else Items.SEED_DIBBER_5343
else -> Items.SEED_DIBBER_5343
} }
if (patch.type != PatchType.FRUIT_TREE_PATCH && patch.type != PatchType.TREE_PATCH) { if (requiredItem != null && !inInventory(player, requiredItem)) {
if (!inInventory(player, Items.SEED_DIBBER_5343)) { sendMessage(player, "You need ${prependArticle(requiredItem.asItem().name.lowercase())} to plant that.")
sendMessage(player, "You need a seed dibber to plant that.")
return@onUseWith true return@onUseWith true
} }
} else {
if (!inInventory(player, Items.SPADE_952) && plantable != Plantable.SCARECROW) {
sendMessage(player, "You need a spade to plant that.")
return@onUseWith true
}
}
player.lock() player.lock()
if (removeItem(player, plantItem)) { if (removeItem(player, plantItem)) {
if (plantable != Plantable.SCARECROW) { when (requiredItem) {
animate(player, 2291) Items.SPADE_952 -> {
animate(player, spadeDigAnim)
playAudio(player, Sounds.DIGSPADE_1470)
}
Items.SEED_DIBBER_5343 -> {
animate(player, seedDibberAnim)
playAudio(player, Sounds.FARMING_DIBBING_2432) playAudio(player, Sounds.FARMING_DIBBING_2432)
} }
val delay = if (plantable == Plantable.SCARECROW) 0 else 3 }
val delay = if (patch.type == PatchType.TREE_PATCH || patch.type == PatchType.FRUIT_TREE_PATCH || plantable == Plantable.SCARECROW) 0 else 3
submitIndividualPulse(player, object : Pulse(delay) { submitIndividualPulse(player, object : Pulse(delay) {
override fun pulse(): Boolean { override fun pulse(): Boolean {
if (plantable == Plantable.JUTE_SEED && patch == FarmingPatch.MCGRUBOR_HOPS && !player.achievementDiaryManager.hasCompletedTask(DiaryType.SEERS_VILLAGE, 0, 7)) { if (plantable == Plantable.JUTE_SEED && patch == FarmingPatch.MCGRUBOR_HOPS && !player.achievementDiaryManager.hasCompletedTask(DiaryType.SEERS_VILLAGE, 0, 7)) {
@ -275,8 +288,11 @@ class UseWithPatchHandler : InteractionListener {
addItem(player, Items.PLANT_POT_5350) addItem(player, Items.PLANT_POT_5350)
} }
val itemAmount = if (plantItem.amount == 1) "a" else plantItem.amount val itemAmount =
val itemName = if (plantItem.amount == 1) getItemName(plantItem.id).lowercase() else StringUtils.plusS(getItemName(plantItem.id).lowercase()) if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH) "the"
else if (plantItem.amount == 1) "a"
else plantItem.amount
val itemName = if (plantItem.amount == 1) plantable.displayName else StringUtils.plusS(plantable.displayName)
val patchName = p.patch.type.displayName() val patchName = p.patch.type.displayName()
if (plantable == Plantable.SCARECROW) { if (plantable == Plantable.SCARECROW) {
sendMessage(player, "You place the scarecrow in the $patchName.") sendMessage(player, "You place the scarecrow in the $patchName.")

View file

@ -58,12 +58,18 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) {
for ((_, patch) in patchMap) { for ((_, patch) in patchMap) {
val type = patch.patch.type val type = patch.patch.type
val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH_PATCH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE_PATCH && patch.getFruitOrBerryCount() < 6) val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH_PATCH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE_PATCH && patch.getFruitOrBerryCount() < 6)
if(shouldPlayCatchup && patch.plantable != null && !patch.isDead){ if (shouldPlayCatchup && !patch.isDead) {
var stagesToSimulate = if (!patch.isGrown()) patch.plantable!!.stages - patch.currentGrowthStage else 0 var stagesToSimulate = if (!patch.isGrown()) {
if (patch.isWeedy() || patch.isEmptyAndWeeded()) patch.currentGrowthStage % 4
else patch.plantable!!.stages - patch.currentGrowthStage
} else 0
if (patch.plantable != null) {
if (type == PatchType.BUSH_PATCH) if (type == PatchType.BUSH_PATCH)
stagesToSimulate += Math.min(4, 4 - patch.getFruitOrBerryCount()) stagesToSimulate += Math.min(4, 4 - patch.getFruitOrBerryCount())
if (type == PatchType.FRUIT_TREE_PATCH) if (type == PatchType.FRUIT_TREE_PATCH)
stagesToSimulate += Math.min(6, 6 - patch.getFruitOrBerryCount()) stagesToSimulate += Math.min(6, 6 - patch.getFruitOrBerryCount())
}
val nowTime = System.currentTimeMillis() val nowTime = System.currentTimeMillis()
var simulatedTime = patch.nextGrowth var simulatedTime = patch.nextGrowth
@ -77,8 +83,8 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) {
} }
} }
fun getPatch(patch: FarmingPatch): Patch { fun getPatch(patch: FarmingPatch, addPatch: Boolean ): Patch {
return patchMap[patch] ?: (Patch(player,patch).also { patchMap[patch] = it }) return patchMap[patch] ?: (Patch(player,patch).also { if (addPatch) patchMap[patch] = it })
} }
fun getPatches(): MutableCollection<Patch>{ fun getPatches(): MutableCollection<Patch>{

View file

@ -204,7 +204,7 @@ public class WoodcuttingSkillPulse extends Pulse {
if (resource.isFarming()) { if (resource.isFarming()) {
FarmingPatch fPatch = FarmingPatch.forObject(node.asScenery()); FarmingPatch fPatch = FarmingPatch.forObject(node.asScenery());
if(fPatch != null) { if(fPatch != null) {
Patch patch = fPatch.getPatchFor(player); Patch patch = fPatch.getPatchFor(player, true);
patch.setCurrentState(patch.getCurrentState() + 1); patch.setCurrentState(patch.getCurrentState() + 1);
} }
return true; return true;

View file

@ -321,29 +321,37 @@ class LunarListeners : SpellListener("lunar"), Commands {
// Level 66 // Level 66
fun curePlant(player: Player, obj: Scenery) { fun curePlant(player: Player, obj: Scenery) {
if (CompostBins.forObject(obj) != null) {
sendMessage(player, "Bins don't often get diseased.")
return
}
val fPatch = FarmingPatch.forObject(obj) val fPatch = FarmingPatch.forObject(obj)
if (fPatch == null) { if (fPatch == null) {
sendMessage(player, "You attempt to cast Cure Plant on ${obj.definition.name}!") sendMessage(player, "Umm... this spell won't cure that!")
sendMessage(player, "Nothing interesting happens.")
return return
} }
val patch = fPatch.getPatchFor(player) val patch = fPatch.getPatchFor(player)
if(!patch.isDiseased && !patch.isWeedy() && !patch.isEmptyAndWeeded()){
sendMessage(player, "It is growing just fine.")
return
}
if (patch.isWeedy()) { if (patch.isWeedy()) {
sendMessage(player, "The weeds are healthy enough already.") sendMessage(player, "The weeds are healthy enough already.")
return return
} }
if(patch.isDead){ if (patch.isEmptyAndWeeded()) {
sendMessage(player, "It says 'Cure' not 'Resurrect'. Although death may arise from disease, it is not in itself a disease and hence cannot be cured. So there.") sendMessage(player, "There's nothing there to cure.")
return return
} }
if (patch.isGrown()) { if (patch.isGrown()) {
sendMessage(player, "That's not diseased.") sendMessage(player, "That's not diseased.")
return return
} }
if (patch.isDead) {
sendMessage(player, "It says 'Cure' not 'Resurrect'. Although death may arise from disease, it is not in itself a disease and hence cannot be cured. So there.")
return
}
if (!patch.isDiseased) {
sendMessage(player, "It is growing just fine.")
return
}
patch.cureDisease() patch.cureDisease()
removeRunes(player) removeRunes(player)
addXP(player,60.0) addXP(player,60.0)

View file

@ -43,7 +43,7 @@ public class HydraNPC extends Familiar {
Scenery scenery = (Scenery)node; Scenery scenery = (Scenery)node;
FarmingPatch farmingPatch = FarmingPatch.forObject(scenery); FarmingPatch farmingPatch = FarmingPatch.forObject(scenery);
if(farmingPatch != null) { if(farmingPatch != null) {
Patch patch = farmingPatch.getPatchFor(owner); Patch patch = farmingPatch.getPatchFor(owner, true);
patch.regrowIfTreeStump(); patch.regrowIfTreeStump();
return true; return true;
} }

View file

@ -108,8 +108,8 @@ abstract class DialogueFile {
interpreter!!.sendDialogues(entity, expression, *messages) interpreter!!.sendDialogues(entity, expression, *messages)
} }
open fun options(vararg options: String?) { open fun options(vararg options: String?, title: String = "Select an Option") {
interpreter!!.sendOptions("Select an Option", *options) interpreter!!.sendOptions(title, *options)
} }
/** /**
@ -151,7 +151,7 @@ abstract class DialogueFile {
player?.dialogueInterpreter?.sendDialogue(*messages) player?.dialogueInterpreter?.sendDialogue(*messages)
} }
fun showTopics(vararg topics: Topic<*>): Boolean { fun showTopics(vararg topics: Topic<*>, title: String = "Select an Option"): Boolean {
val validTopics = ArrayList<String>() val validTopics = ArrayList<String>()
topics.filter { if(it is IfTopic) it.showCondition else true }.forEach { topics.filter { if(it is IfTopic) it.showCondition else true }.forEach {
topic -> interpreter!!.activeTopics.add(topic) topic -> interpreter!!.activeTopics.add(topic)
@ -172,7 +172,7 @@ abstract class DialogueFile {
interpreter!!.activeTopics.clear() interpreter!!.activeTopics.clear()
return false return false
} }
else { options(*validTopics.toTypedArray()) else { options(*validTopics.toTypedArray(), title = title)
return false return false
} }
} }

View file

@ -532,6 +532,24 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){
} }
define("finishbins", Privilege.ADMIN, "", "Finishes any in-progress compost bins."){ player, _ -> define("finishbins", Privilege.ADMIN, "", "Finishes any in-progress compost bins."){ player, _ ->
val bins = getOrStartTimer<Compost>(player).getBins()
for (bin in bins) {
if (!bin.isFinished && bin.isClosed) bin.finish()
}
}
define("resetbins", Privilege.ADMIN, "", "Resets the player's compost bins to their initial states."){ player, _ ->
val bins = getOrStartTimer<Compost>(player).getBins()
for (bin in bins) bin.reset()
}
define("diseasecrops", Privilege.ADMIN, "", "Disease all crops"){ player, _ ->
val state = getOrStartTimer<CropGrowth>(player)
for (patch in state.getPatches()){
patch.diseaseMod = -128
patch.nextGrowth = System.currentTimeMillis() + 1
}
state.run(player)
} }
define("addcredits", Privilege.ADMIN){ player, _ -> define("addcredits", Privilege.ADMIN){ player, _ ->