Implemented auto splitting of excessively long dialogue lines

Many new farming-related player messages added, and some existing messages updated
Raking animation updated
Herb picking animation updated
Digging up farming patch animation updated
Plant cure animation updated
A scarecrow can be retrieved from a flower patch by digging it up with a spade
Picking fruit/berries stops when running out of inventory space
The player can no longer dig up a tree they have planted before chopping it down
This commit is contained in:
zsrv 2024-02-14 11:33:13 +00:00 committed by Ryan
parent 38c50c465d
commit 1d12dd741f
23 changed files with 728 additions and 520 deletions

View file

@ -2,7 +2,6 @@ package content.global.skill.farming
import core.api.*
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.audio.Audio
import core.game.node.entity.skill.Skills
import core.game.node.item.Item
import core.tools.RandomFunction
@ -26,14 +25,19 @@ class CompostBin(val player: Player, val bin: CompostBins) {
fun close() {
isClosed = true
sendMessage(player, "You close the compost bin.")
// TODO: Add animation - https://youtu.be/B50dwm8fdcQ?t=225 https://youtu.be/BHYgNDLx0s4?t=488
playAudio(player, Sounds.COMPOST_CLOSE_2428)
sendMessage(player, "The contents have begun to rot.")
finishedTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(RandomFunction.random(35,50).toLong())
updateBit()
}
fun open(){
isClosed = false
// TODO: Add animation
playAudio(player, Sounds.COMPOST_OPEN_2429)
sendMessage(player, "You open the compost bin.")
updateBit()
}

View file

@ -1,5 +1,6 @@
package content.global.skill.farming
import core.api.*
import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler
import core.game.node.Node
@ -27,14 +28,14 @@ class CompostBinOptionHandler : OptionHandler() {
val bin = cBin.getBinForPlayer(player)
when (option) {
"close" -> if(!bin.isFull()) player.sendMessage("This shouldn't be happening. Report this.") else bin.close()
"open" -> if(!bin.isFinished) player.sendMessage("I should probably wait until it is done to open it.") else bin.open()
"close" -> if (!bin.isFull()) sendMessage(player, "This shouldn't be happening. Report this.") else bin.close()
"open" -> if (!bin.isFinished) sendMessage(player, "I should probably wait until it is done to open it.") else bin.open()
"take-tomato" -> {
if (!bin.isTomatoes || !bin.isFinished) {
player.sendMessage("This shouldn't be happening. Report this.")
sendMessage(player, "This shouldn't be happening. Report this.")
} else {
if (player.inventory.isFull) {
player.sendMessage("You don't have enough inventory space to do this.")
sendMessage(player, "You don't have enough inventory space to do this.")
} else {
val reward = bin.takeItem()
if (reward != null)

View file

@ -2,6 +2,6 @@ package content.global.skill.farming
enum class CompostType {
NONE,
NORMAL,
SUPER
COMPOST,
SUPERCOMPOST
}

View file

@ -15,7 +15,7 @@ import core.plugin.Plugin
import org.rs09.consts.Items
import org.rs09.consts.Sounds
val livesBased = arrayOf(PatchType.HERB, PatchType.CACTUS, PatchType.BELLADONNA, PatchType.HOPS, PatchType.ALLOTMENT,PatchType.EVIL_TURNIP)
val livesBased = arrayOf(PatchType.HERB_PATCH, PatchType.CACTUS_PATCH, PatchType.BELLADONNA_PATCH, PatchType.HOPS_PATCH, PatchType.ALLOTMENT, PatchType.EVIL_TURNIP_PATCH)
@Initializable
class CropHarvester : OptionHandler() {
@ -34,8 +34,10 @@ class CropHarvester : OptionHandler() {
val fPatch = FarmingPatch.forObject(node.asScenery())
fPatch ?: return null
val patch = fPatch.getPatchFor(player)
val patchName = patch.patch.type.displayName()
val plantable = patch.plantable
plantable ?: return null
var firstHarvest = true
return object : Pulse(0) {
override fun pulse(): Boolean {
@ -45,23 +47,23 @@ class CropHarvester : OptionHandler() {
if (familiar != null && familiar is GiantEntNPC) {
familiar.modifyFarmingReward(fPatch, reward)
}
if(!player.inventory.hasSpaceFor(reward)){
player.sendMessage("You don't have enough inventory space for that.")
if (!hasSpaceFor(player, reward)) {
sendMessage(player, "You have run out of inventory space.")
return true
}
var requiredItem = when (fPatch.type) {
PatchType.HERB, PatchType.TREE -> Items.SECATEURS_5329
PatchType.TREE_PATCH -> Items.SECATEURS_5329
else -> Items.SPADE_952
}
if (requiredItem == Items.SECATEURS_5329) {
if(player.inventory.containsAtLeastOneItem(Items.MAGIC_SECATEURS_7409)){
if (inInventory(player, Items.MAGIC_SECATEURS_7409)) {
requiredItem = Items.MAGIC_SECATEURS_7409
}
}
val anim = when (requiredItem) {
Items.SPADE_952 -> Animation(830)
Items.SECATEURS_5329 -> if (fPatch.type == PatchType.TREE) Animation(2277) else Animation(7227)
Items.MAGIC_SECATEURS_7409 -> if (fPatch.type == PatchType.TREE) Animation(3340) else Animation(7228)
Items.SPADE_952 -> if (fPatch.type == PatchType.HERB_PATCH) Animation(2282) else Animation(830)
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)
else -> Animation(0)
}
val sound = when (requiredItem) {
@ -70,15 +72,19 @@ class CropHarvester : OptionHandler() {
Items.MAGIC_SECATEURS_7409 -> Sounds.FARMING_PICK_2437
else -> 0
}
if(!player.inventory.containsItem(Item(requiredItem))){
player.sendMessage("You lack the needed tool to harvest these crops.")
if (!inInventory(player, requiredItem)) {
sendMessage(player, "You lack the needed tool to harvest these crops.")
return true
}
player.animator.animate(anim)
if (firstHarvest) {
sendMessage(player, "You begin to harvest the $patchName.")
firstHarvest = false
}
animate(player, anim)
playAudio(player, sound)
delay = 2
player.inventory.add(reward)
player.skills.addExperience(Skills.FARMING,plantable.harvestXP)
addItem(player, reward.id)
rewardXP(player, Skills.FARMING, plantable.harvestXP)
if (patch.patch.type in livesBased) {
patch.rollLivesDecrement(
getDynLevel(player, Skills.FARMING),
@ -90,6 +96,9 @@ class CropHarvester : OptionHandler() {
patch.clear()
}
}
if (patch.cropLives <= 0 || patch.harvestAmt <= 0) {
sendMessage(player, "The $patchName is now empty.")
}
return patch.cropLives <= 0 || patch.harvestAmt <= 0
}
}
@ -105,13 +114,18 @@ class CropHarvester : OptionHandler() {
val plantable = patch.plantable
plantable ?: return false
if(patch.isWeedy()){
player.sendMessage("Something seems to have gone wrong here. Report this.")
if (patch.isWeedy() || patch.isEmptyAndWeeded()) {
sendMessage(player, "Something seems to have gone wrong here. Report this.")
return true
}
if (!hasSpaceFor(player, Item(plantable.harvestItem))) {
sendMessage(player, "You don't have enough inventory space to do that.")
return true
}
val pulse = harvestPulse(player, node, plantable.harvestItem) ?: return false
player.pulseManager.run(pulse)
submitIndividualPulse(player, pulse)
return true
}

View file

@ -1,13 +1,9 @@
package content.global.skill.farming
import core.api.playAudio
import core.api.*
import core.game.dialogue.DialoguePlugin
import core.game.interaction.QueueStrength
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.audio.Audio
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import core.game.system.task.Pulse
import core.game.world.update.flag.context.Animation
import core.plugin.Initializable
import org.rs09.consts.Sounds
@ -21,12 +17,20 @@ class DigUpPatchDialogue(player: Player? = null) : DialoguePlugin(player) {
override fun open(vararg args: Any?): Boolean {
patch = args[0] as Patch
if(patch?.isWeedy() == true){
player.dialogueInterpreter.sendDialogue("Use a rake to get rid of weeds.")
if (patch?.isWeedy() == true || patch?.isEmptyAndWeeded() == true) {
sendDialogue(player, "There aren't any crops in this patch to dig up.")
stage = 1000
return true
}
player.dialogueInterpreter.sendOptions("Dig up this patch?","Yes","No")
if (patch?.patch?.type == PatchType.TREE_PATCH) {
val isTreeStump = patch?.getCurrentState() == patch?.plantable!!.value + patch?.plantable!!.stages + 2
if (!isTreeStump) {
sendMessage(player, "You need to chop this tree down first.") // this message is not authentic
stage = 1000
return true
}
}
sendDialogueOptions(player, "Are you sure you want to dig up this patch?", "Yes, I want to clear it for new crops.", "No, I want to leave it as it is.")
stage = 0
return true
}
@ -36,21 +40,36 @@ class DigUpPatchDialogue(player: Player? = null) : DialoguePlugin(player) {
0 -> when (buttonId) {
1 -> {
end()
player.animator.animate(Animation(830))
val anim = getAnimation(830)
sendMessage(player, "You start digging the farming patch...")
queueScript(player, 0, QueueStrength.WEAK) { stage: Int ->
when (stage) {
0 -> {
animate(player, anim)
playAudio(player, Sounds.DIGSPADE_1470)
player.pulseManager.run(object : Pulse(){
override fun pulse(): Boolean {
if(patch?.patch?.type == PatchType.TREE){
if(patch?.getCurrentState() == (patch?.plantable?.value ?: 0) + (patch?.plantable?.stages ?: 0) + 2 && patch?.isWeedy() != true){
if(!player.inventory.add(Item(patch?.plantable?.harvestItem ?: 0))){
GroundItemManager.create(Item(patch?.plantable?.harvestItem ?: 0),player)
return@queueScript delayScript(player,anim.duration + 2)
}
1 -> {
animate(player, anim)
playAudio(player, Sounds.DIGSPADE_1470)
return@queueScript delayScript(player, anim.duration + 1)
}
2 -> {
if (patch?.patch?.type == PatchType.TREE_PATCH) {
if (patch?.getCurrentState() == (patch?.plantable?.value ?: 0) + (patch?.plantable?.stages ?: 0) + 2 && patch?.isWeedy() != true && patch?.isEmptyAndWeeded() != true) {
addItemOrDrop(player, patch?.plantable?.harvestItem ?: 0)
}
}
if (patch?.plantable == Plantable.SCARECROW) {
addItemOrDrop(player, patch?.plantable?.harvestItem ?: 0)
}
patch?.clear()
return true
sendMessage(player, "You have successfully cleared this patch for new crops.")
return@queueScript stopExecuting(player)
}
else -> return@queueScript stopExecuting(player)
}
}
})
}
else -> end()
}

View file

@ -2,7 +2,6 @@ package content.global.skill.farming
import core.api.*
import core.cache.def.impl.SceneryDefinition
import core.cache.def.impl.VarbitDefinition
import core.game.node.scenery.Scenery
import core.game.node.entity.player.Player
import content.global.skill.farming.timers.CropGrowth
@ -20,55 +19,55 @@ enum class FarmingPatch(val varbit: Int, val type: PatchType) {
HARMONY_ISLAND_ALLOTMENT(3402,PatchType.ALLOTMENT),
//Herb
CATHERBY_HERB_CE(781,PatchType.HERB),
S_FALADOR_HERB_NE(780,PatchType.HERB),
ARDOUGNE_HERB_CE(782,PatchType.HERB),
PORT_PHAS_HERB_NE(783,PatchType.HERB),
TROLL_STRONGHOLD_HERB(2788,PatchType.HERB),
CATHERBY_HERB_CE(781,PatchType.HERB_PATCH),
S_FALADOR_HERB_NE(780,PatchType.HERB_PATCH),
ARDOUGNE_HERB_CE(782,PatchType.HERB_PATCH),
PORT_PHAS_HERB_NE(783,PatchType.HERB_PATCH),
TROLL_STRONGHOLD_HERB(2788,PatchType.HERB_PATCH),
//Flower
S_FALADOR_FLOWER_C(728,PatchType.FLOWER),
CATHERBY_FLOWER_C(729,PatchType.FLOWER),
ARDOUGNE_FLOWER_C(730,PatchType.FLOWER),
PORT_PHAS_FLOWER_C(731,PatchType.FLOWER),
WILDERNESS_FLOWER(5067,PatchType.FLOWER),
S_FALADOR_FLOWER_C(728,PatchType.FLOWER_PATCH),
CATHERBY_FLOWER_C(729,PatchType.FLOWER_PATCH),
ARDOUGNE_FLOWER_C(730,PatchType.FLOWER_PATCH),
PORT_PHAS_FLOWER_C(731,PatchType.FLOWER_PATCH),
WILDERNESS_FLOWER(5067,PatchType.FLOWER_PATCH),
//Tree
N_FALADOR_TREE(701,PatchType.TREE),
TAVERLY_TREE(700,PatchType.TREE),
GNOME_STRONGHOLD_TREE(2953,PatchType.TREE),
LUMBRIDGE_TREE(703,PatchType.TREE),
VARROCK_TREE(702,PatchType.TREE),
N_FALADOR_TREE(701,PatchType.TREE_PATCH),
TAVERLY_TREE(700,PatchType.TREE_PATCH),
GNOME_STRONGHOLD_TREE(2953,PatchType.TREE_PATCH),
LUMBRIDGE_TREE(703,PatchType.TREE_PATCH),
VARROCK_TREE(702,PatchType.TREE_PATCH),
//Fruit Tree
GNOME_STRONGHOLD_FRUIT_TREE(704,PatchType.FRUIT_TREE),
CATHERBY_FRUIT_TREE(707,PatchType.FRUIT_TREE),
TREE_GNOME_VILLAGE_FRUIT_TREE(705,PatchType.FRUIT_TREE),
BRIMHAVEN_FRUIT_TREE(706,PatchType.FRUIT_TREE),
LLETYA_FRUIT_TREE(4317,PatchType.FRUIT_TREE),
GNOME_STRONGHOLD_FRUIT_TREE(704,PatchType.FRUIT_TREE_PATCH),
CATHERBY_FRUIT_TREE(707,PatchType.FRUIT_TREE_PATCH),
TREE_GNOME_VILLAGE_FRUIT_TREE(705,PatchType.FRUIT_TREE_PATCH),
BRIMHAVEN_FRUIT_TREE(706,PatchType.FRUIT_TREE_PATCH),
LLETYA_FRUIT_TREE(4317,PatchType.FRUIT_TREE_PATCH),
//Hops
ENTRANA_HOPS(717,PatchType.HOPS),
LUMBRIDGE_HOPS(718,PatchType.HOPS),
MCGRUBOR_HOPS(719,PatchType.HOPS),
YANILLE_HOPS(716,PatchType.HOPS),
ENTRANA_HOPS(717,PatchType.HOPS_PATCH),
LUMBRIDGE_HOPS(718,PatchType.HOPS_PATCH),
MCGRUBOR_HOPS(719,PatchType.HOPS_PATCH),
YANILLE_HOPS(716,PatchType.HOPS_PATCH),
//Bushes
CHAMPIONS_GUILD_BUSH(732,PatchType.BUSH),
RIMMINGTON_BUSH(733,PatchType.BUSH),
ARDOUGNE_BUSH(735,PatchType.BUSH),
ETCETERIA_BUSH(734,PatchType.BUSH),
CHAMPIONS_GUILD_BUSH(732,PatchType.BUSH_PATCH),
RIMMINGTON_BUSH(733,PatchType.BUSH_PATCH),
ARDOUGNE_BUSH(735,PatchType.BUSH_PATCH),
ETCETERIA_BUSH(734,PatchType.BUSH_PATCH),
//Spirit Tree
ETCETERIA_SPIRIT_TREE(722,PatchType.SPIRIT_TREE),
PORT_SARIM_SPIRIT_TREE(720,PatchType.SPIRIT_TREE),
KARAMJA_SPIRIT_TREE(724,PatchType.SPIRIT_TREE),
ETCETERIA_SPIRIT_TREE(722,PatchType.SPIRIT_TREE_PATCH),
PORT_SARIM_SPIRIT_TREE(720,PatchType.SPIRIT_TREE_PATCH),
KARAMJA_SPIRIT_TREE(724,PatchType.SPIRIT_TREE_PATCH),
//Other
DRAYNOR_BELLADONNA(748, PatchType.BELLADONNA),
CANIFIS_MUSHROOM(746, PatchType.MUSHROOM),
ALKHARID_CACTUS(744, PatchType.CACTUS),
EVIL_TURNIP(4291, PatchType.EVIL_TURNIP);
DRAYNOR_BELLADONNA(748, PatchType.BELLADONNA_PATCH),
CANIFIS_MUSHROOM(746, PatchType.MUSHROOM_PATCH),
ALKHARID_CACTUS(744, PatchType.CACTUS_PATCH),
EVIL_TURNIP(4291, PatchType.EVIL_TURNIP_PATCH);
companion object {

View file

@ -46,12 +46,12 @@ class FruitAndBerryPicker : OptionHandler() {
val animation = Animation(2281)
if (patch.getFruitOrBerryCount() <= 0) {
player.sendMessage("This shouldn't be happening. Please report this.")
sendMessage(player, "This shouldn't be happening. Please report this.")
return true
}
if(!player.inventory.hasSpaceFor(Item(plantable.harvestItem))){
player.sendMessage("You do not have enough inventory space for this.")
if (!hasSpaceFor(player, Item(plantable.harvestItem))) {
sendMessage(player, "You don't have enough inventory space to do that.")
return true
}
@ -59,21 +59,33 @@ class FruitAndBerryPicker : OptionHandler() {
patch.nextGrowth = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(45)
}
player.pulseManager.run(object : Pulse(animation.duration){
submitIndividualPulse(player, object : Pulse(animation.duration) {
override fun pulse(): Boolean {
val reward = Item(plantable.harvestItem, 1)
if (!hasSpaceFor(player, reward)) {
sendMessage(player, "You have run out of inventory space.")
return true
}
val familiar = player.familiarManager.familiar
if (familiar != null && familiar is GiantEntNPC) {
familiar.modifyFarmingReward(fPatch, reward)
}
player.animator.animate(animation)
animate(player, animation)
playAudio(player, Sounds.FARMING_PICK_2437)
addItemOrDrop(player, reward.id, reward.amount)
player.skills.addExperience(Skills.FARMING,plantable.harvestXP)
rewardXP(player, Skills.FARMING, plantable.harvestXP)
patch.setCurrentState(patch.getCurrentState() - 1)
if (patch.patch.type == PatchType.CACTUS_PATCH) {
sendMessage(player, "You carefully pick a spine from the cactus.")
} else {
val determiner = if (patch.patch.type == PatchType.BUSH_PATCH) "some" else "a"
sendMessage(player, "You pick $determiner ${reward.name.lowercase()}.")
}
return patch.getFruitOrBerryCount() == 0
}
})

View file

@ -1,12 +1,11 @@
package content.global.skill.farming
import core.api.log
import core.api.*
import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler
import core.game.node.Node
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.tools.SystemLogger
import core.plugin.Initializable
import core.plugin.Plugin
import core.tools.Log
@ -27,24 +26,27 @@ class HealthChecker : OptionHandler(){
val patch = fPatch.getPatchFor(player)
val type = patch.patch.type
if(type != PatchType.BUSH && type != PatchType.FRUIT_TREE && type != PatchType.TREE && type != PatchType.CACTUS){
player.sendMessage("This shouldn't be happening. Please report this.")
if (type != PatchType.BUSH_PATCH && type != PatchType.FRUIT_TREE_PATCH && type != PatchType.TREE_PATCH && type != PatchType.CACTUS_PATCH) {
sendMessage(player, "This shouldn't be happening. Please report this.")
return true
}
if (!patch.isCheckHealth) return true
player.skills.addExperience(Skills.FARMING,patch.plantable?.checkHealthXP ?: 0.0)
rewardXP(player, Skills.FARMING, patch.plantable?.checkHealthXP ?: 0.0)
patch.isCheckHealth = false
when (type) {
PatchType.TREE -> patch.setCurrentState(patch.getCurrentState() + 1)
PatchType.FRUIT_TREE -> patch.setCurrentState(patch.getCurrentState() - 14)
PatchType.BUSH -> patch.setCurrentState(patch.plantable!!.value + patch.plantable!!.stages + 4)
PatchType.CACTUS -> 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 line 36")
PatchType.TREE_PATCH -> patch.setCurrentState(patch.getCurrentState() + 1)
PatchType.FRUIT_TREE_PATCH -> patch.setCurrentState(patch.getCurrentState() - 14)
PatchType.BUSH_PATCH -> {
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.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")
}
if(type == PatchType.FRUIT_TREE){
if (type == PatchType.FRUIT_TREE_PATCH) {
patch.nextGrowth = TimeUnit.MINUTES.toMillis(45)
}

View file

@ -1,11 +1,13 @@
package content.global.skill.farming
import core.api.*
import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler
import core.game.node.Node
import core.game.node.entity.player.Player
import core.plugin.Initializable
import core.plugin.Plugin
import core.tools.prependArticle
@Initializable
class InspectionHandler : OptionHandler() {
@ -19,26 +21,34 @@ class InspectionHandler : OptionHandler() {
player ?: return false
val patch = FarmingPatch.forObject(node.asScenery())
if (patch == null) {
player.sendMessage("This is an improperly handled inspect option. Report this please.")
} else {
sendMessage(player, "This is an improperly handled inspect option. Report this please.")
return true
}
val p = patch.getPatchFor(player)
val status1 = if(p.getCurrentState() <= 2) "This patch needs weeding." else if(p.getCurrentState() == 3) "This patch is weed-free." else {
if(p.isDiseased && !p.isDead) "This patch has become diseased."
else if(p.isDead) "The crops in this patch are dead."
else if(p.plantable == Plantable.SCARECROW) "There is a scarecrow in this patch."
else "This patch has something growing in it."
}
val status2 = if(patch.type == PatchType.ALLOTMENT || patch.type == PatchType.FLOWER || patch.type == PatchType.HOPS){
if(p.isWatered) {
"This patch has been watered."
} else if(p.getCurrentState() > 3 && !p.isGrown() && !p.isDead && !p.isDiseased) {
"This patch could use some water."
} else ""
} else ""
val status3 = if(p.compost == CompostType.NONE) "This patch has not been treated." else "This patch has been treated with ${p.compost.name.toLowerCase()} compost."
player.sendMessage("$status1 $status2")
player.sendMessage(status3)
}
val patchName = p.patch.type.displayName()
val statusPatchType = if (patch == FarmingPatch.TROLL_STRONGHOLD_HERB) "This is a very special herb patch."
else "This is ${prependArticle(patchName)}."
val statusCompost = if (p.compost == CompostType.NONE) "The soil has not been treated."
else "The soil has been treated with ${p.compost.name.lowercase()}."
val statusStage = if (p.plantable == Plantable.SCARECROW) ""
else if (p.isWeedy()) "The patch needs weeding."
else if (p.isEmptyAndWeeded()) "The patch is empty and weeded."
else if (p.isDiseased && !p.isDead) "The patch is diseased and needs attending to before it dies."
else if (p.isDead) "The patch has become infected by disease and has died."
else if (p.isGrown()) "The patch is fully grown."
else "The patch has something growing in it."
val statusGardener = if (patch == FarmingPatch.TROLL_STRONGHOLD_HERB) "My Arm will look after this patch for you."
else if (p.protectionPaid) "A nearby gardener is looking after this patch for you."
else ""
sendMessage(player, "$statusPatchType $statusCompost $statusStage".trim())
if (statusGardener != "") sendMessage(player, statusGardener)
return true
}

View file

@ -5,7 +5,6 @@ import core.game.node.entity.player.Player
import core.tools.Log
import core.tools.RandomFunction
import org.rs09.consts.Items
import core.tools.SystemLogger
import java.util.concurrent.TimeUnit
import kotlin.math.ceil
import kotlin.math.min
@ -21,8 +20,8 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
fun setNewHarvestAmount() {
val compostMod = when(compost) {
CompostType.NONE -> 0
CompostType.NORMAL -> 1
CompostType.SUPER -> 2
CompostType.COMPOST -> 1
CompostType.SUPERCOMPOST -> 2
}
harvestAmt = when (plantable) {
Plantable.LIMPWURT_SEED, Plantable.WOAD_SEED -> 3
@ -30,14 +29,14 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
Plantable.WILLOW_SAPLING -> 0
else -> 1
}
if(plantable != null && plantable?.applicablePatch != PatchType.FLOWER) {
if(plantable != null && plantable?.applicablePatch != PatchType.FLOWER_PATCH) {
harvestAmt += compostMod
}
cropLives = 3 + compostMod
}
fun rollLivesDecrement(farmingLevel: Int, magicSecateurs: Boolean){
if(patch.type == PatchType.HERB){
if(patch.type == PatchType.HERB_PATCH){
//authentic formula thanks to released data.
var herbSaveLow = when(plantable){
Plantable.GUAM_SEED -> min(24 + farmingLevel, 80)
@ -70,10 +69,10 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
//inauthentic formulae based on reported averages due to lack of formula
var chance = when(patch.type){
PatchType.ALLOTMENT -> 8 //average of 8 per life times 3 lives = average 24
PatchType.HOPS -> 6 //average of 6 per life times 3 lives = 18
PatchType.BELLADONNA -> 2 //average of 2 per life times 3 lives = 6
PatchType.EVIL_TURNIP -> 2 //average 2 per, same as BELLADONNA
PatchType.CACTUS -> 3 //average of 3 per life times 3 lives = 9
PatchType.HOPS_PATCH -> 6 //average of 6 per life times 3 lives = 18
PatchType.BELLADONNA_PATCH -> 2 //average of 2 per life times 3 lives = 6
PatchType.EVIL_TURNIP_PATCH -> 2 //average 2 per, same as BELLADONNA
PatchType.CACTUS_PATCH -> 3 //average of 3 per life times 3 lives = 9
else -> 0 // nothing should go here, but if it does, do not give extra crops amd decrement cropLives
}
@ -86,7 +85,11 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
}
fun isWeedy(): Boolean {
return getCurrentState() in 0..3
return getCurrentState() in 0..2
}
fun isEmptyAndWeeded(): Boolean {
return getCurrentState() == 3
}
fun getCurrentState(): Int{
@ -138,14 +141,14 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
fun updateBit(){
if(isCheckHealth){
when(patch.type){
PatchType.FRUIT_TREE -> setVarbit(player, patch.varbit, plantable!!.value + plantable!!.stages + 20)
PatchType.BUSH -> setVarbit(player, patch.varbit, 250 + (plantable!!.ordinal - Plantable.REDBERRY_SEED.ordinal))
PatchType.CACTUS -> setVarbit(player, patch.varbit, 31)
PatchType.FRUIT_TREE_PATCH -> setVarbit(player, patch.varbit, plantable!!.value + plantable!!.stages + 20)
PatchType.BUSH_PATCH -> setVarbit(player, patch.varbit, 250 + (plantable!!.ordinal - Plantable.REDBERRY_SEED.ordinal))
PatchType.CACTUS_PATCH -> setVarbit(player, patch.varbit, 31)
else -> log(this::class.java, Log.WARN, "Invalid setting of isCheckHealth for patch type: " + patch.type.name)
}
} else {
when(patch.type){
PatchType.ALLOTMENT,PatchType.FLOWER,PatchType.HOPS -> {
PatchType.ALLOTMENT,PatchType.FLOWER_PATCH,PatchType.HOPS_PATCH -> {
var state = getUnmodifiedValue()
if (isWatered || isDead) state = state or 0x40
if (isDiseased) state = state or 0x80
@ -153,11 +156,11 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
if (state != getVarbit(player, patch.varbit))
setVisualState(state)
}
PatchType.BUSH -> {
PatchType.BUSH_PATCH -> {
if(isDead) setVisualState(getBushDeathValue())
else if(isDiseased && !isDead) setVisualState(getBushDiseaseValue())
}
PatchType.TREE -> {
PatchType.TREE_PATCH -> {
var state = getVarbit(player, patch.varbit)
if (isDead) state = state or 0x80
@ -166,20 +169,20 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
if (state != getVarbit(player, patch.varbit))
setVisualState(state)
}
PatchType.FRUIT_TREE -> {
PatchType.FRUIT_TREE_PATCH -> {
if(isDead) setVisualState(getFruitTreeDeathValue())
else if(isDiseased && !isDead) setVisualState(getFruitTreeDiseaseValue())
}
PatchType.BELLADONNA -> {
PatchType.BELLADONNA_PATCH -> {
if(isDead) setVisualState(getBelladonnaDeathValue())
else if(isDiseased && !isDead) setVisualState(getBelladonnaDiseaseValue())
else setVisualState((plantable?.value ?: 0) + currentGrowthStage)
}
PatchType.CACTUS -> {
PatchType.CACTUS_PATCH -> {
if(isDead) setVisualState(getCactusDeathValue())
else if(isDiseased && !isDead) setVisualState(getCactusDiseaseValue())
}
PatchType.HERB -> {
PatchType.HERB_PATCH -> {
if(isDead) setVisualState(getHerbDeathValue())
else if(isDiseased && !isDead) setVisualState(getHerbDiseaseValue())
else setVisualState((plantable?.value ?: 0) + currentGrowthStage)
@ -261,7 +264,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
}
private fun grow(){
if(isWeedy() && getCurrentState() > 0) {
if((isWeedy() || isEmptyAndWeeded()) && getCurrentState() > 0) {
nextGrowth = System.currentTimeMillis() + 60000
setCurrentState(getCurrentState() - 1)
currentGrowthStage--
@ -275,28 +278,28 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
diseaseMod = when(compost){
CompostType.NONE -> 0
CompostType.NORMAL -> 8
CompostType.SUPER -> 13
CompostType.COMPOST -> 8
CompostType.SUPERCOMPOST -> 13
}
if(patch != FarmingPatch.TROLL_STRONGHOLD_HERB && RandomFunction.random(128) <= (17 - diseaseMod) && !isWatered && !isGrown() && !protectionPaid && !isFlowerProtected() && patch.type != PatchType.EVIL_TURNIP ){
if(patch != FarmingPatch.TROLL_STRONGHOLD_HERB && RandomFunction.random(128) <= (17 - diseaseMod) && !isWatered && !isGrown() && !protectionPaid && !isFlowerProtected() && patch.type != PatchType.EVIL_TURNIP_PATCH ){
//bush, tree, fruit tree, herb and cactus can not disease on stage 1(0) of growth.
if(!((patch.type == PatchType.BUSH || patch.type == PatchType.TREE || patch.type == PatchType.FRUIT_TREE || patch.type == PatchType.CACTUS || patch.type == PatchType.HERB) && currentGrowthStage == 0)) {
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
return
}
}
if((patch.type == PatchType.FRUIT_TREE || patch.type == PatchType.TREE || patch.type == PatchType.BUSH || patch.type == PatchType.CACTUS) && 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
}
if((patch.type == PatchType.FRUIT_TREE || patch.type == PatchType.BUSH || patch.type == PatchType.CACTUS) && plantable?.stages == currentGrowthStage){
if((patch.type == PatchType.BUSH && getFruitOrBerryCount() < 4) || (patch.type == PatchType.FRUIT_TREE && getFruitOrBerryCount() < 6) || (patch.type == PatchType.CACTUS && getFruitOrBerryCount() < 3)){
if((patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.BUSH_PATCH || patch.type == PatchType.CACTUS_PATCH) && plantable?.stages == currentGrowthStage){
if((patch.type == PatchType.BUSH_PATCH && getFruitOrBerryCount() < 4) || (patch.type == PatchType.FRUIT_TREE_PATCH && getFruitOrBerryCount() < 6) || (patch.type == PatchType.CACTUS_PATCH && getFruitOrBerryCount() < 3)){
setCurrentState(getCurrentState() + 1)
}
}
if(patch.type == PatchType.TREE) {
if(patch.type == PatchType.TREE_PATCH) {
// Willow branches
if(harvestAmt < 6) {
harvestAmt++
@ -313,7 +316,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
}
fun regrowIfTreeStump() {
if(patch.type == PatchType.TREE && plantable != null) {
if(patch.type == PatchType.TREE_PATCH && plantable != null) {
// plantable.value + plantable.stages is the check-health stage, so +1 is the choppable tree, and +2 is the stump
if(getCurrentState() == plantable!!.value + plantable!!.stages + 2) {
setCurrentState(getCurrentState() - 1)
@ -356,7 +359,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
fun getStageGrowthMinutes() : Int {
var minutes = patch.type.stageGrowthTime
if(patch.type == PatchType.FRUIT_TREE && isGrown()) {
if(patch.type == PatchType.FRUIT_TREE_PATCH && isGrown()) {
// Fruit trees take 160 minutes per stage to grow, but
// restocking their fruit should take 40 minutes per fruit
minutes = 40

View file

@ -1,33 +1,45 @@
package content.global.skill.farming
import core.api.playAudio
import core.api.*
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.game.node.item.Item
import core.game.system.task.Pulse
import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items
import org.rs09.consts.Sounds
object PatchRaker {
val RAKE_ANIM = Animation(2273)
val RAKE_ANIM = getAnimation(2273)
@JvmStatic
fun rake(player: Player, patch: FarmingPatch) {
player.pulseManager.run(object : Pulse(){
val p = patch.getPatchFor(player)
val patchName = p.patch.type.displayName()
var firstRake = true
if (p.isEmptyAndWeeded()) {
sendMessage(player, "This $patchName doesn't need weeding right now.")
return
}
submitIndividualPulse(player, object : Pulse() {
override fun pulse(): Boolean {
var patchStage = patch.getPatchFor(player).getCurrentState()
if(patchStage <= 2){
player.animator.animate(RAKE_ANIM)
if (firstRake || patchStage < 2) {
// don't play the animation when on patchStage 2 as it has already
// played three times at this point and the patch will be weed-free
// after the third play
animate(player, RAKE_ANIM)
playAudio(player, Sounds.FARMING_RAKING_2442)
firstRake = false
}
if (delay < 5) {
delay = 5
} else {
patch.getPatchFor(player).currentGrowthStage++
patch.getPatchFor(player).setCurrentState(++patchStage)
player.inventory.add(Item(Items.WEEDS_6055))
player.skills.addExperience(Skills.FARMING,4.0)
addItem(player, Items.WEEDS_6055)
rewardXP(player, Skills.FARMING, 4.0)
}
if (patchStage >= 3) {
resetAnimator(player)
}
return patchStage >= 3
}

View file

@ -2,15 +2,20 @@ package content.global.skill.farming
enum class PatchType(val stageGrowthTime: Int) {
ALLOTMENT(10),
HOPS(10),
TREE(40),
FRUIT_TREE(160),
BUSH(20),
FLOWER(5),
HERB(20),
SPIRIT_TREE(295),
MUSHROOM(30),
BELLADONNA(80),
CACTUS(60),
EVIL_TURNIP(5)
HOPS_PATCH(10),
TREE_PATCH(40),
FRUIT_TREE_PATCH(160),
BUSH_PATCH(20),
FLOWER_PATCH(5),
HERB_PATCH(20),
SPIRIT_TREE_PATCH(295),
MUSHROOM_PATCH(30),
BELLADONNA_PATCH(80),
CACTUS_PATCH(60),
EVIL_TURNIP_PATCH(5);
/**
* Returns the display name of this PatchType.
*/
fun displayName(): String = name.lowercase().replace("_", " ")
}

View file

@ -6,15 +6,15 @@ 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) {
//Flowers
MARIGOLD_SEED(5096,8,4,8.5,47.0,0.0,2,PatchType.FLOWER,Items.MARIGOLDS_6010),
ROSEMARY_SEED(5097,13,4,12.0,66.5,0.0,11,PatchType.FLOWER, Items.ROSEMARY_6014),
NASTURTIUM_SEED(5098,18,4,19.5,111.0,0.0,24,PatchType.FLOWER,Items.NASTURTIUMS_6012),
WOAD_SEED(5099,23,4,20.5,115.5,0.0,25,PatchType.FLOWER,Items.WOAD_LEAF_1793),
LIMPWURT_SEED(5100,28,4,21.5,120.0,0.0,26,PatchType.FLOWER,Items.LIMPWURT_ROOT_225),
WHITE_LILY_SEED(14589,37,4,42.0,250.0,0.0,52,PatchType.FLOWER,Items.WHITE_LILY_14583),
MARIGOLD_SEED(5096,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),
NASTURTIUM_SEED(5098,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),
LIMPWURT_SEED(5100,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),
//Flower(Technically)
SCARECROW(6059,33,3,0.0,0.0,0.0,23,PatchType.FLOWER,Items.SCARECROW_6059),
SCARECROW(6059,33,3,0.0,0.0,0.0,23,PatchType.FLOWER_PATCH,Items.SCARECROW_6059),
//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),
@ -26,61 +26,61 @@ enum class Plantable(val itemID: Int, val value: Int, val stages: Int, val plant
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),
//Hops
BARLEY_SEED(5305,49,4,8.5,9.5,0.0,3,PatchType.HOPS,Items.BARLEY_6006,Item(Items.COMPOST_6032,3)),
HAMMERSTONE_SEED(5307,4,4,9.0,10.0,0.0,4,PatchType.HOPS,Items.HAMMERSTONE_HOPS_5994,Item(Items.MARIGOLDS_6010)),
ASGARNIAN_SEED(5308,11,5,10.9,12.0,0.0,8,PatchType.HOPS,Items.ASGARNIAN_HOPS_5996,Item(Items.ONIONS10_5458)),
JUTE_SEED(5306,56,5,13.0,14.5,0.0,13,PatchType.HOPS,Items.JUTE_FIBRE_5931,Item(Items.BARLEY_MALT_6008,6)),
YANILLIAN_SEED(5309,19,6,14.5,16.0,0.0,16,PatchType.HOPS,Items.YANILLIAN_HOPS_5998,Item(Items.TOMATOES5_5968)),
KRANDORIAN_SEED(5310,28,7,17.5,19.5,0.0,21,PatchType.HOPS,Items.KRANDORIAN_HOPS_6000,Item(Items.CABBAGES10_5478,3)),
WILDBLOOD_SEED(5311,38,8,23.0,26.0,0.0,28,PatchType.HOPS,Items.WILDBLOOD_HOPS_6002,Item(Items.NASTURTIUMS_6012)),
BARLEY_SEED(5305,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)),
ASGARNIAN_SEED(5308,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)),
YANILLIAN_SEED(5309,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)),
WILDBLOOD_SEED(5311,38,8,23.0,26.0,0.0,28,PatchType.HOPS_PATCH,Items.WILDBLOOD_HOPS_6002,Item(Items.NASTURTIUMS_6012)),
//Trees
OAK_SAPLING(5370,8,4,14.0,0.0,467.3,15,PatchType.TREE,Items.OAK_ROOTS_6043,Item(Items.TOMATOES5_5968)),
WILLOW_SAPLING(5371,15,6,25.0,0.0,1456.5,30,PatchType.TREE,Items.WILLOW_ROOTS_6045,Item(Items.APPLES5_5386)),
MAPLE_SAPLING(5372,24,8,45.0,0.0,3403.4,45,PatchType.TREE,Items.MAPLE_ROOTS_6047,Item(Items.ORANGES5_5396)),
YEW_SAPLING(5373,35,10,81.0,0.0,7069.9,60,PatchType.TREE,Items.YEW_ROOTS_6049,Item(Items.CACTUS_SPINE_6016,10)),
MAGIC_SAPLING(5374,48,12,145.5,0.0,13768.3,75,PatchType.TREE,Items.MAGIC_ROOTS_6051,Item(Items.COCONUT_5974,25)),
OAK_SAPLING(5370,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)),
MAPLE_SAPLING(5372,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)),
MAGIC_SAPLING(5374,48,12,145.5,0.0,13768.3,75,PatchType.TREE_PATCH,Items.MAGIC_ROOTS_6051,Item(Items.COCONUT_5974,25)),
//Fruit Trees
APPLE_SAPLING(5496,8,6,22.0,8.5,1199.5,27,PatchType.FRUIT_TREE,Items.COOKING_APPLE_1955,Item(Items.SWEETCORN_5986,9)),
BANANA_SAPLING(5497,35,6,28.0,10.5,1750.5,33,PatchType.FRUIT_TREE,Items.BANANA_1963,Item(Items.APPLES5_5386,4)),
ORANGE_SAPLING(5498,72,6,35.5,13.5,2470.2,39,PatchType.FRUIT_TREE,Items.ORANGE_2108,Item(Items.STRAWBERRIES5_5406,3)),
CURRY_SAPLING(5499,99,6,40.0,15.0,2906.9,42,PatchType.FRUIT_TREE,Items.CURRY_LEAF_5970,Item(Items.BANANAS5_5416,5)),
PINEAPPLE_SAPLING(5500,136,6,57.0,21.5,4605.7,51,PatchType.FRUIT_TREE,Items.PINEAPPLE_2114,Item(Items.WATERMELON_5982,10)),
PAPAYA_SAPLING(5501,163,6,72.0,27.0,6146.4,57,PatchType.FRUIT_TREE,Items.PAPAYA_FRUIT_5972,Item(Items.PINEAPPLE_2114,10)),
PALM_SAPLING(5502,200,6,110.5,41.5,10150.1,68,PatchType.FRUIT_TREE,Items.COCONUT_5974,Item(Items.PAPAYA_FRUIT_5972,15)),
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)),
BANANA_SAPLING(5497,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)),
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)),
PINEAPPLE_SAPLING(5500,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)),
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)),
//Bushes
REDBERRY_SEED(5101,5,5,11.5,4.5,64.0,10,PatchType.BUSH,Items.REDBERRIES_1951,Item(Items.CABBAGES10_5478,4)),
CADAVABERRY_SEED(5102,15,6,18.0,7.0,102.5,22,PatchType.BUSH,Items.CADAVA_BERRIES_753,Item(Items.TOMATOES5_5968,3)),
DWELLBERRY_SEED(5103,26,27,31.5,12.0,177.5,36,PatchType.BUSH,Items.DWELLBERRIES_2126,Item(Items.STRAWBERRIES5_5406,3)),
JANGERBERRY_SEED(5104,38,8,50.5,19.0,284.5,48,PatchType.BUSH,Items.JANGERBERRIES_247,Item(Items.WATERMELON_5982,6)),
WHITEBERRY_SEED(5105,51,8,78.0,29.0,437.5,59,PatchType.BUSH,Items.WHITE_BERRIES_239,null),
POISON_IVY_SEED(5106,197,8,120.0,45.0,675.0,70,PatchType.BUSH,Items.POISON_IVY_BERRIES_6018,null),
REDBERRY_SEED(5101,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)),
DWELLBERRY_SEED(5103,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)),
WHITEBERRY_SEED(5105,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),
//Herbs
GUAM_SEED(5291,4,4,11.0,12.5,0.0,9,PatchType.HERB,Items.GRIMY_GUAM_199),
MARRENTILL_SEED(5292,11,4,13.5,15.0,0.0,14,PatchType.HERB,Items.GRIMY_MARRENTILL_201),
TARROMIN_SEED(5293,18,4,16.0,18.0,0.0,19,PatchType.HERB,Items.GRIMY_TARROMIN_203),
HARRALANDER_SEED(5294,25,4,21.5,24.0,0.0,26,PatchType.HERB,Items.GRIMY_HARRALANDER_205),
RANARR_SEED(5295,32,4,27.0,30.5,0.0,32,PatchType.HERB,Items.GRIMY_RANARR_207),
AVANTOE_SEED(5298,39,4,54.5,61.5,0.0,50,PatchType.HERB,Items.GRIMY_AVANTOE_211),
TOADFLAX_SEED(5296,46,4,34.0,38.5,0.0,38,PatchType.HERB,Items.GRIMY_TOADFLAX_3049),
IRIT_SEED(5297,53,4,43.0,48.5,0.0,44,PatchType.HERB,Items.GRIMY_IRIT_209),
KWUARM_SEED(5299,68,4,69.0,78.0,0.0,56,PatchType.HERB,Items.GRIMY_KWUARM_213),
SNAPDRAGON_SEED(5300,75,4,87.5,98.5,0.0,62,PatchType.HERB,Items.GRIMY_SNAPDRAGON_3051),
CADANTINE_SEED(5301,82,4,106.5,120.0,0.0,67,PatchType.HERB,Items.GRIMY_CADANTINE_215),
LANTADYME_SEED(5302,89,4,134.5,151.5,0.0,73,PatchType.HERB,Items.GRIMY_LANTADYME_2485),
DWARF_WEED_SEED(5303,96,4,170.5,192.0,0.0,79,PatchType.HERB,Items.GRIMY_DWARF_WEED_217),
TORSTOL_SEED(5304,103,4,199.5,224.5,0.0,85,PatchType.HERB,Items.GRIMY_TORSTOL_219),
GOUT_TUBER(6311,192,4,105.0,45.0,0.0,29,PatchType.HERB,Items.GOUTWEED_3261),
SPIRIT_WEED_SEED(12176, 204, 4, 32.0, 36.0, 0.0, 36, PatchType.HERB, Items.GRIMY_SPIRIT_WEED_12174),
GUAM_SEED(5291,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),
TARROMIN_SEED(5293,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),
RANARR_SEED(5295,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),
TOADFLAX_SEED(5296,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),
KWUARM_SEED(5299,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),
CADANTINE_SEED(5301,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),
DWARF_WEED_SEED(5303,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),
GOUT_TUBER(6311,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),
//Other
BELLADONNA_SEED(5281, 4, 4, 91.0, 128.0, 0.0, 63, PatchType.BELLADONNA, Items.CAVE_NIGHTSHADE_2398),
MUSHROOM_SPORE(Items.MUSHROOM_SPORE_5282, 6, 7, 61.5, 57.7, 0.0, 53, PatchType.MUSHROOM, Items.MUSHROOM_6004),
CACTUS_SEED(Items.CACTUS_SEED_5280, 8, 7, 66.5, 25.0, 374.0, 55, PatchType.CACTUS, Items.CACTUS_SPINE_6016),
EVIL_TURNIP_SEED(Items.EVIL_TURNIP_SEED_12148, 4, 1, 41.0, 46.0, 0.0, 42, PatchType.EVIL_TURNIP, Items.EVIL_TURNIP_12134)
BELLADONNA_SEED(5281, 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),
CACTUS_SEED(Items.CACTUS_SEED_5280, 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)
;
constructor(itemID: Int, value: Int, stages: Int, plantingXP: Double, harvestXP: Double, checkHealthXP: Double, requiredLevel: Int, applicablePatch: PatchType, harvestItem: Int, protectionFlower: Plantable)

View file

@ -7,6 +7,7 @@ import core.game.interaction.IntType
import core.game.node.entity.player.Player
import content.global.skill.farming.timers.*
import core.game.interaction.InteractionListener
import core.tools.prependArticle
class SeedlingListener : InteractionListener {
override fun defineListeners() {
@ -27,6 +28,8 @@ class SeedlingListener : InteractionListener {
if (seedling == -1) return false
if (!removeItem(player, seed.id) || !removeItem(player, pot)) return true
addItem(player, seedling)
sendMessage(player, "You sow ${prependArticle(seed.name.lowercase())} in the plantpot.")
sendMessage(player, "It needs watering before it will grow.")
return true
}
@ -51,6 +54,7 @@ class SeedlingListener : InteractionListener {
if (index == -1) return Items.WATERING_CAN_5331
return if (index != WATERING_CANS.size -1) WATERING_CANS[index + 1] else Items.WATERING_CAN_5331
}
fun getSeedling(id: Int) : Int {
return when (id) {
Items.ACORN_5312 -> Items.OAK_SEEDLING_5358

View file

@ -1,7 +1,6 @@
package content.global.skill.farming
import core.api.*
import core.game.component.Component
import core.game.node.entity.player.Player
import core.game.node.item.Item
import org.rs09.consts.Components
@ -15,12 +14,13 @@ class ToolLeprechaunInterface : InterfaceListener {
override fun defineInterfaceListeners() {
onOpen(FARMING_TOOLS) { player, component ->
player.interfaceManager.openSingleTab(Component(TOOLS_SIDE))
openSingleTab(player, TOOLS_SIDE)
return@onOpen true
}
onClose(FARMING_TOOLS) { player, _ ->
player.interfaceManager.closeSingleTab()
closeTabInterface(player)
return@onClose true
}
on(FARMING_TOOLS) { player, _, opcode, buttonID, _, _ ->
@ -34,10 +34,12 @@ class ToolLeprechaunInterface : InterfaceListener {
}
37 -> {
if (!hasWateringCan(player)) {
player.dialogueInterpreter.sendDialogue("You don't have any of those stored.")
sendMessage(player, "You haven't got a watering can stored in here!")
} else {
player.inventory.add(Item(getWateringCan(player)))
setNoWateringCan(player)
if (freeSlots(player) == 0) {
sendMessage(player, "You don't have enough space for that.")
}
if (addItem(player, getWateringCan(player))) setNoWateringCan(player)
}
}
38 -> doWithdrawal(player, Items.GARDENING_TROWEL_5325, ::setHasGardeningTrowel, ::hasGardeningTrowel)
@ -54,31 +56,31 @@ class ToolLeprechaunInterface : InterfaceListener {
19 -> doDeposit(player, Items.SEED_DIBBER_5343, ::setHasDibber, ::hasDibber)
20 -> doDeposit(player, Items.SPADE_952, ::setHasSpade, ::hasSpade)
21 -> {
if(!player.inventory.contains(Items.SECATEURS_5329,1) && !player.inventory.contains(Items.MAGIC_SECATEURS_7409,1)){
player.dialogueInterpreter.sendDialogue("You don't have any of those to store.")
if (!inInventory(player, Items.SECATEURS_5329) && !inInventory(player, Items.MAGIC_SECATEURS_7409)) {
sendMessage(player, "You haven't got any secateurs to store.")
} else if (!hasSecateurs(player)) {
if(player.inventory.contains(Items.MAGIC_SECATEURS_7409,1)){
player.inventory.remove(Item(Items.MAGIC_SECATEURS_7409))
if (inInventory(player, Items.MAGIC_SECATEURS_7409)) {
removeItem(player, Items.MAGIC_SECATEURS_7409)
setHasSecateurs(player,true)
setHasMagicSecateurs(player,true)
} else {
player.inventory.remove(Item(Items.SECATEURS_5329))
removeItem(player, Items.SECATEURS_5329)
setHasSecateurs(player,true)
setHasMagicSecateurs(player,false)
}
} else {
player.dialogueInterpreter.sendDialogue("You already have one of those stored.")
sendMessage(player, "You already have one of those stored.")
}
}
22 -> {
val can = getHighestCan(player)
if (can == null) {
player.dialogueInterpreter.sendDialogue("You don't have any of those to store.")
sendMessage(player, "You haven't got a watering can to store.")
} else if (!hasWateringCan(player)) {
player.inventory.remove(can)
removeItem(player, can)
setWateringCan(player,can)
} else {
player.dialogueInterpreter.sendDialogue("You already have one of those stored.")
sendMessage(player, "You already have one of those stored.")
}
}
23 -> doDeposit(player, Items.GARDENING_TROWEL_5325, ::setHasGardeningTrowel, ::hasGardeningTrowel)
@ -94,38 +96,42 @@ class ToolLeprechaunInterface : InterfaceListener {
private fun doWithdrawal(player: Player?, item: Int, withdrawMethod: (Player?, Boolean) -> Unit, checkMethod: (Player?) -> Boolean) {
player ?: return
if (!checkMethod.invoke(player)) {
player.dialogueInterpreter.sendDialogue("You don't have any of those stored.")
val determiner = if (getItemName(item).lowercase().endsWith("s")) "any" else "a"
sendMessage(player, "You haven't got $determiner ${getItemName(item).lowercase()} stored in here!")
} else {
if(!player.inventory.hasSpaceFor(Item(item))){
player.dialogueInterpreter.sendDialogue("You don't have enough space for that.")
if (!hasSpaceFor(player, Item(item))) {
sendMessage(player, "You don't have enough space for that.")
return
}
withdrawMethod.invoke(player, false)
player.inventory.add(Item(item))
addItem(player, item)
}
}
private fun doDeposit(player: Player?, item: Int, depositMethod: (Player?, Boolean) -> Unit, checkMethod: (Player?) -> Boolean) {
player ?: return
if(!player.inventory.contains(item,1)){
player.dialogueInterpreter.sendDialogue("You don't have any of those to store.")
if (!inInventory(player, item)) {
val determiner = if (getItemName(item).lowercase().endsWith("s")) "any" else "a"
sendMessage(player, "You haven't got $determiner ${getItemName(item).lowercase()} to store.")
return
}
if (!checkMethod.invoke(player)) {
depositMethod.invoke(player, true)
player.inventory.remove(Item(item))
removeItem(player, item)
} else {
player.dialogueInterpreter.sendDialogue("You already have one of those stored.")
sendMessage(player, "You already have one of those stored.")
}
}
private fun doStackedDeposit(player: Player?, item: Int, amount: Int, updateQuantityMethod: (Player?, Int) -> Unit, quantityCheckMethod: (Player?) -> Int) {
player ?: return
val hasAmount = player.inventory.getAmount(item)
val hasAmount = amountInInventory(player, item)
var finalAmount = amount
val spaceLeft = (if (item == Items.BUCKET_1925) 31 else 255) - quantityCheckMethod.invoke(player)
if (hasAmount == 0) {
player.dialogueInterpreter.sendDialogue("You don't have any of those to store.")
val itemName = if (item == Items.BUCKET_1925) "buckets" else getItemName(item).lowercase()
sendMessage(player, "You haven't got any $itemName to store.")
return
}
@ -138,15 +144,11 @@ class ToolLeprechaunInterface : InterfaceListener {
if (amt > spaceLeft) {
amt = spaceLeft
}
if(amt == 0){
player.dialogueInterpreter.sendDialogue("You don't have any of those to store.")
return@sendInputDialogue
}
player.inventory.remove(Item(item,amt))
updateQuantityMethod.invoke(player,amt)
if (removeItem(player, Item(item, amt))) updateQuantityMethod.invoke(player, amt)
}
return
}
if (amount == -1) {
finalAmount = hasAmount
if (finalAmount > spaceLeft) {
@ -157,19 +159,17 @@ class ToolLeprechaunInterface : InterfaceListener {
if (finalAmount > hasAmount) {
finalAmount = hasAmount
}
if(finalAmount > spaceLeft) finalAmount = spaceLeft
if(!player.inventory.contains(item,finalAmount)){
player.dialogueInterpreter.sendDialogue("You don't have any of those to store.")
return
}
if (finalAmount > spaceLeft) {
player.dialogueInterpreter.sendDialogue("You can't store any more of those.")
if (item == Items.BUCKET_1925) {
sendMessage(player, "You can't store that many buckets in here.")
} else {
sendMessage(player, "You can't store that much ${getItemName(item).lowercase()} in here.")
}
return
}
player.inventory.remove(Item(item,finalAmount))
removeItem(player, Item(item, finalAmount))
updateQuantityMethod.invoke(player,finalAmount)
}
@ -177,41 +177,44 @@ class ToolLeprechaunInterface : InterfaceListener {
player ?: return
val hasAmount = quantityCheckMethod.invoke(player)
var finalAmount = amount
if (hasAmount == 0) {
player.dialogueInterpreter.sendDialogue("You don't have any of those stored.")
} else {
val itemName = if (item == Items.BUCKET_1925) "buckets" else getItemName(item).lowercase()
sendMessage(player, "You haven't got any $itemName stored in here!")
return
}
if (amount == -2) {
sendInputDialogue(player, InputType.AMOUNT, "Enter the amount:") { value ->
var amt = value as Int
if (amt > hasAmount) {
amt = hasAmount
}
if(amt > player.inventory.freeSlots()){
amt = player.inventory.freeSlots()
if (amt > freeSlots(player)) {
amt = freeSlots(player)
}
if (amt <= 0) {
player.dialogueInterpreter.sendDialogue("You don't have enough inventory space for that.")
sendMessage(player, "You don't have enough inventory space for that.")
} else {
player.inventory.add(Item(item, amt))
addItem(player, item, amt)
updateQuantityMethod.invoke(player, -amt)
}
}
return
}
if (amount == -1) {
finalAmount = player.inventory.freeSlots()
finalAmount = freeSlots(player)
}
if (finalAmount > hasAmount) {
finalAmount = hasAmount
}
if(!player.inventory.hasSpaceFor(Item(item,finalAmount)) || finalAmount == 0){
player.dialogueInterpreter.sendDialogue("You don't have enough inventory space for that.")
if (!hasSpaceFor(player, Item(item, finalAmount)) || finalAmount == 0) {
sendMessage(player, "You don't have enough inventory space for that.")
return
}
player.inventory.add(Item(item,finalAmount))
addItem(player, item, finalAmount)
updateQuantityMethod.invoke(player, -finalAmount)
}
}
fun getAmount(opcode: Int): Int {
return when (opcode) {

View file

@ -1,7 +1,6 @@
package content.global.skill.farming
import core.api.*
import core.game.node.item.Item
import core.game.system.task.Pulse
import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items
@ -28,55 +27,55 @@ class UseWithBinHandler : InteractionListener {
when (used) {
Items.COMPOST_POTION1_6476, Items.COMPOST_POTION2_6474, Items.COMPOST_POTION3_6472, Items.COMPOST_POTION4_6470 -> {
if (!bin.isSuperCompost && bin.isFinished && !bin.isClosed) {
player.animator.animate(compostPotionAnimation)
player.pulseManager.run(object : Pulse(compostPotionAnimation.duration){
animate(player, compostPotionAnimation)
submitIndividualPulse(player, object : Pulse(compostPotionAnimation.duration) {
override fun pulse(): Boolean {
if(player.inventory.remove(usedNode.asItem())) {
if (removeItem(player, usedNode.asItem())) {
bin.convert()
player.inventory.add(Item(used.getNext()))
addItem(player, used.getNext())
}
return true
}
})
} else {
player.dialogueInterpreter.sendDialogue("You can only do this with an open bin of","finished regular compost.")
sendDialogue(player, "You can only do this with an open bin of finished regular compost.")
}
}
Items.BUCKET_1925 -> {
if (bin.isFinished && !bin.isClosed) {
player.pulseManager.run(object : Pulse(scoopAnimation.duration){
submitIndividualPulse(player, object : Pulse(scoopAnimation.duration) {
override fun pulse(): Boolean {
if (!player.inventory.containsItem(usedNode.asItem())) return true
player.animator.animate(scoopAnimation)
animate(player, scoopAnimation)
val item = bin.takeItem()
if(item != null && player.inventory.remove(usedNode.asItem())){
if (item != null && removeItem(player, usedNode.asItem())) {
player.inventory.add(item)
}
return item == null || !player.inventory.containsItem(usedNode.asItem())
}
})
} else {
player.dialogueInterpreter.sendDialogue("You can only scoop an opened bin of finished compost.")
sendDialogue(player, "You can only scoop an opened bin of finished compost.")
}
}
else ->
if (bin.isFull()) {
player.sendMessage("This compost bin is already full.")
sendMessage(player, "This compost bin is already full.")
return@onUseWith true
} else if (!bin.isFinished) {
player.pulseManager.run(object : Pulse(fillAnim.duration){
submitIndividualPulse(player, object : Pulse(fillAnim.duration) {
override fun pulse(): Boolean {
player.animator.animate(fillAnim)
if(player.inventory.remove(usedNode.asItem())){
animate(player, fillAnim)
if (removeItem(player, usedNode.asItem())) {
bin.addItem(usedNode.asItem())
}
return bin.isFull() || player.inventory.getAmount(usedNode.asItem()) == 0
}
})
} else {
sendMessage (player, "You should empty the remaining compost first.")
sendDialogue(player, "The compost bin must be empty of compost before you can put new items in it.")
}
}
return@onUseWith true

View file

@ -9,6 +9,9 @@ import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.interaction.QueueStrength
import core.tools.StringUtils
import core.tools.prependArticle
import org.rs09.consts.Sounds
class UseWithPatchHandler : InteractionListener {
@ -41,53 +44,81 @@ class UseWithPatchHandler : InteractionListener {
player.faceLocation(with.location)
when (usedItem.id) {
RAKE -> PatchRaker.rake(player,patch)
SEED_DIBBER -> player.sendMessage("I should plant a seed, not the seed dibber.")
SPADE -> player.dialogueInterpreter.open(67984003,patch.getPatchFor(player)) //DigUpPatchDialogue.kt
SEED_DIBBER -> sendMessage(player, "I should plant a seed, not the seed dibber.")
SPADE -> {
val anim = getAnimation(830)
val p = patch.getPatchFor(player)
if (p.isDead) {
sendMessage(player, "You start digging the farming patch...")
queueScript(player, 0, QueueStrength.WEAK) { stage: Int ->
when (stage) {
0 -> {
animate(player, anim)
playAudio(player, Sounds.DIGSPADE_1470)
return@queueScript delayScript(player,anim.duration + 2)
}
1 -> {
animate(player, anim)
playAudio(player, Sounds.DIGSPADE_1470)
return@queueScript delayScript(player, anim.duration + 1)
}
2 -> {
p.clear()
sendMessage(player, "You have successfully cleared this patch for new crops.")
return@queueScript stopExecuting(player)
}
else -> return@queueScript stopExecuting(player)
}
}
} else {
openDialogue(player, 67984003, patch.getPatchFor(player)) // DigUpPatchDialogue.kt
}
}
SECATEURS, MAGIC_SECATEURS -> {
val p = patch.getPatchFor(player)
if(patch.type == PatchType.TREE) {
if (patch.type == PatchType.TREE_PATCH) {
if (p.isDiseased && !p.isDead) {
player.pulseManager.run(object: Pulse(){
submitIndividualPulse(player, object: Pulse() {
override fun pulse(): Boolean {
if (usedItem.id == SECATEURS ) player.animator.animate(secateursTreeAnim) else player.animator.animate(magicSecateursTreeAnim)
if (usedItem.id == SECATEURS) animate(player, secateursTreeAnim) else animate(player, magicSecateursTreeAnim)
p.cureDisease()
return true
}
})
} else if (p.plantable == Plantable.WILLOW_SAPLING && p.harvestAmt > 0) {
val pulse = CropHarvester.harvestPulse(player, with, Items.WILLOW_BRANCH_5933) ?: return@onUseWith false
player.pulseManager.run(pulse)
submitIndividualPulse(player, pulse)
}
}
}
TROWEL, Items.PLANT_POT_5350 -> {
if(!player.inventory.containsAtLeastOneItem(TROWEL)) {
player.sendMessage("You need a trowel to fill plant pots with dirt.")
if (!inInventory(player, TROWEL)) {
sendMessage(player, "You need a trowel to fill plant pots with dirt.")
return@onUseWith true
}
val p = patch.getPatchFor(player)
if(!p.isWeedy()){
player.sendMessage("This patch has something growing in it.")
if (!p.isWeedy() && !p.isEmptyAndWeeded()) {
sendMessage(player, "This patch has something growing in it.")
return@onUseWith true
} else if (p.currentGrowthStage != 3) {
player.sendMessage("I should clear this of weeds before trying to take some dirt.")
sendMessage(player, "I should clear this of weeds before trying to take some dirt.")
return@onUseWith true
}
val potAmount = player.inventory.getAmount(Items.PLANT_POT_5350)
val potAmount = amountInInventory(player, Items.PLANT_POT_5350)
if (potAmount == 0) {
player.sendMessage("You have no plant pots to fill.")
sendMessage(player, "You have no plant pots to fill.")
return@onUseWith true
}
val anim = Animation(2272)
player.pulseManager.run(object : Pulse(anim.duration){
submitIndividualPulse(player, object : Pulse(anim.duration) {
override fun pulse(): Boolean {
if(player.inventory.remove(Item(Items.PLANT_POT_5350))){
player.animator.animate(anim)
player.inventory.add(Item(Items.PLANT_POT_5354))
if (removeItem(player, Items.PLANT_POT_5350)) {
animate(player, anim)
addItem(player, Items.PLANT_POT_5354)
} else return true
return false
}
@ -96,37 +127,52 @@ class UseWithPatchHandler : InteractionListener {
Items.PLANT_CURE_6036 -> {
val p = patch.getPatchFor(player)
val patchName = p.patch.type.displayName()
if (p.isDiseased && !p.isDead) {
player.pulseManager.run(object: Pulse(){
override fun pulse(): Boolean {
player.animator.animate(plantCureAnim)
sendMessage(player, "You treat the $patchName with the plant cure.")
queueScript(player, 0, QueueStrength.WEAK) { stage: Int ->
when (stage) {
0 -> {
animate(player, plantCureAnim)
playAudio(player, Sounds.FARMING_PLANTCURE_2438)
if(player.inventory.remove(usedItem)){
player.inventory.add(Item(Items.VIAL_229))
return@queueScript delayScript(player, plantCureAnim.duration / 2)
}
1 -> {
if (removeItem(player, usedItem)) {
addItem(player, Items.VIAL_229)
p.cureDisease()
sendMessage(player, "It is restored to health.")
}
return@queueScript stopExecuting(player)
}
else -> return@queueScript stopExecuting(player)
}
return true
}
})
} else {
player.sendMessage("I have no reason to do this right now.")
sendMessage(player, "I have no reason to do this right now.")
}
}
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 t = p.patch.type
if(!p.isWatered && p.plantable != Plantable.SCARECROW && (t == PatchType.ALLOTMENT || t == PatchType.FLOWER || t == PatchType.HOPS) && !p.isGrown()){
player.pulseManager.run(object : Pulse(){
if (p.isWatered || p.isEmptyAndWeeded() || p.isGrown() || p.plantable == Plantable.SCARECROW) {
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() {
override fun pulse(): Boolean {
if (p.isWeedy()) {
player.sendMessage("You should grow something first.")
sendMessage(player, "You should grow something first.")
return true
}
player.animator.animate(wateringCanAnim)
if (usedItem.id == Items.WATERING_CAN_5331) {
sendMessage(player, "You need to fill the watering can first.")
return true
}
animate(player, wateringCanAnim)
playAudio(player, Sounds.FARMING_WATERING_2446)
if(player.inventory.remove(usedItem)){
player.inventory.add(Item(usedItem.id.getNext()))
if (removeItem(player, usedItem)) {
addItem(player, usedItem.id.getNext())
p.water()
}
return true
@ -137,25 +183,28 @@ class UseWithPatchHandler : InteractionListener {
Items.SUPERCOMPOST_6034, Items.COMPOST_6032 -> {
val p = patch.getPatchFor(player)
if(p.compost == CompostType.NONE) {
player.animator.animate(pourBucketAnim)
val patchName = p.patch.type.displayName()
if (!p.isEmptyAndWeeded()) {
sendMessage(player, "This patch needs to be empty and weeded to do that.")
} else if (p.compost == CompostType.NONE) {
animate(player, pourBucketAnim)
playAudio(player, Sounds.FARMING_COMPOST_2427)
player.pulseManager.run(object : Pulse(){
override fun pulse(): Boolean {
runTask(player) {
if (player.inventory.remove(usedItem,false)) {
p.compost = if(usedItem.id == Items.SUPERCOMPOST_6034) CompostType.SUPER else CompostType.NORMAL
if(p.compost == CompostType.SUPER) rewardXP(player, Skills.FARMING, 26.0) else rewardXP(player, Skills.FARMING, 18.5)
if(p.plantable != null && p.plantable?.applicablePatch != PatchType.FLOWER) {
p.harvestAmt += if(p.compost == CompostType.NORMAL) 1 else if(p.compost == CompostType.SUPER) 2 else 0
sendMessage(player, "You treat the $patchName with ${usedItem.name.lowercase()}.")
p.compost = if (usedItem.id == Items.SUPERCOMPOST_6034) CompostType.SUPERCOMPOST else CompostType.COMPOST
if (p.compost == CompostType.SUPERCOMPOST) rewardXP(player, Skills.FARMING, 26.0) else rewardXP(player, Skills.FARMING, 18.5)
if (p.plantable != null && p.plantable?.applicablePatch != PatchType.FLOWER_PATCH) {
p.harvestAmt += if (p.compost == CompostType.COMPOST) 1 else if (p.compost == CompostType.SUPERCOMPOST) 2 else 0
}
p.cropLives += if(p.compost == CompostType.SUPER) 2 else 1
player.inventory.add(Item(Items.BUCKET_1925))
p.cropLives += if (p.compost == CompostType.SUPERCOMPOST) 2 else 1
addItem(player, Items.BUCKET_1925)
}
return true
return@runTask
}
})
} else {
player.sendMessage("This patch has already been treated with compost.")
sendMessage(player, "This $patchName has already been treated with ${p.compost.name.lowercase()}.")
}
}
@ -163,26 +212,28 @@ class UseWithPatchHandler : InteractionListener {
val plantable = Plantable.forItemID(usedItem.id) ?: return@onUseWith false
if (plantable.applicablePatch != patch.type) {
player.sendMessage("You can't plant that seed in this patch.")
val seedNamePlural = StringUtils.plusS(plantable.name.replace("_", " ").lowercase())
val patchType = if (plantable.applicablePatch == PatchType.ALLOTMENT) "a vegetable patch" else prependArticle(plantable.applicablePatch.displayName())
sendMessage(player, "You can only plant $seedNamePlural in $patchType.")
return@onUseWith true
}
if(plantable.requiredLevel > player.skills.getLevel(Skills.FARMING)){
player.sendMessage("You need a Farming level of ${plantable.requiredLevel} to plant this.")
if (!hasLevelDyn(player, Skills.FARMING, plantable.requiredLevel)) {
sendMessage(player, "You need a Farming level of ${plantable.requiredLevel} to plant this.")
return@onUseWith true
}
val p = patch.getPatchFor(player)
if(p.getCurrentState() < 3 && p.isWeedy()){
player.sendMessage("You must weed your patch before you can plant a seed in it.")
if (p.getCurrentState() < 3 && p.isWeedy() && plantable != Plantable.SCARECROW) {
sendMessage(player, "This patch needs weeding first.")
return@onUseWith true
} else if (p.getCurrentState() > 3) {
player.sendMessage("There is already something growing in this patch.")
sendMessage(player, "There is already something growing in this patch.")
return@onUseWith true
}
val plantItem =
if(patch.type == PatchType.ALLOTMENT) Item(plantable.itemID,3) else if(patch.type == PatchType.HOPS){
if (patch.type == PatchType.ALLOTMENT) Item(plantable.itemID,3) else if (patch.type == PatchType.HOPS_PATCH) {
if (plantable == Plantable.JUTE_SEED) Item(plantable.itemID,3) else Item(plantable.itemID,4)
} else {
Item(plantable.itemID,1)
@ -190,36 +241,49 @@ class UseWithPatchHandler : InteractionListener {
if (patch.type == PatchType.ALLOTMENT) {
if (!player.inventory.containsItem(plantItem)) {
player.sendMessage("You need 3 seeds to plant an allotment patch.")
sendMessage(player, "You need 3 seeds to plant an allotment patch.")
return@onUseWith true
}
}
if(patch.type != PatchType.FRUIT_TREE && patch.type != PatchType.TREE){
if(!player.inventory.contains(Items.SEED_DIBBER_5343,1)){
player.sendMessage("You need a seed dibber to plant that.")
if (patch.type != PatchType.FRUIT_TREE_PATCH && patch.type != PatchType.TREE_PATCH) {
if (!inInventory(player, Items.SEED_DIBBER_5343)) {
sendMessage(player, "You need a seed dibber to plant that.")
return@onUseWith true
}
} else {
if(!player.inventory.contains(Items.SPADE_952,1)){
player.sendMessage("You need a spade to plant that.")
if (!inInventory(player, Items.SPADE_952) && plantable != Plantable.SCARECROW) {
sendMessage(player, "You need a spade to plant that.")
return@onUseWith true
}
}
player.lock()
if(player.inventory.remove(plantItem)) {
player.animator.animate(Animation(2291))
if (removeItem(player, plantItem)) {
if (plantable != Plantable.SCARECROW) {
animate(player, 2291)
playAudio(player, Sounds.FARMING_DIBBING_2432)
player.pulseManager.run(object : Pulse(3) {
}
val delay = if (plantable == Plantable.SCARECROW) 0 else 3
submitIndividualPulse(player, object : Pulse(delay) {
override fun pulse(): Boolean {
if (plantable == Plantable.JUTE_SEED && patch == FarmingPatch.MCGRUBOR_HOPS && !player.achievementDiaryManager.hasCompletedTask(DiaryType.SEERS_VILLAGE, 0, 7)) {
player.achievementDiaryManager.finishTask(player, DiaryType.SEERS_VILLAGE, 0, 7)
}
p.plant(plantable)
player.skills.addExperience(Skills.FARMING, plantable.plantingXP)
rewardXP(player, Skills.FARMING, plantable.plantingXP)
p.setNewHarvestAmount()
if(p.patch.type == PatchType.TREE || p.patch.type == PatchType.FRUIT_TREE){
player.inventory.add(Item(Items.PLANT_POT_5350))
if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH) {
addItem(player, Items.PLANT_POT_5350)
}
val itemAmount = if (plantItem.amount == 1) "a" else plantItem.amount
val itemName = if (plantItem.amount == 1) getItemName(plantItem.id).lowercase() else StringUtils.plusS(getItemName(plantItem.id).lowercase())
val patchName = p.patch.type.displayName()
if (plantable == Plantable.SCARECROW) {
sendMessage(player, "You place the scarecrow in the $patchName.")
} else {
sendMessage(player, "You plant $itemAmount $itemName in the $patchName.")
}
player.unlock()
return true
}
@ -236,7 +300,29 @@ class UseWithPatchHandler : InteractionListener {
for (p in Plantable.values()) {
allowedNodes.add(p.itemID)
}
allowedNodes.addAll(arrayListOf(RAKE,SEED_DIBBER,SPADE,SECATEURS,MAGIC_SECATEURS,TROWEL,Items.SUPERCOMPOST_6034,Items.COMPOST_6032,Items.PLANT_CURE_6036,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.PLANT_POT_5350))
allowedNodes.addAll(
arrayListOf(
RAKE,
SEED_DIBBER,
SPADE,
SECATEURS,
MAGIC_SECATEURS,
TROWEL,
Items.SUPERCOMPOST_6034,
Items.COMPOST_6032,
Items.PLANT_CURE_6036,
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.PLANT_POT_5350
)
)
}
private fun Int.getNext(): Int {

View file

@ -1,6 +1,5 @@
package content.global.skill.farming.timers
import core.api.*
import core.tools.*
import core.game.system.timer.*
import org.json.simple.*
@ -58,12 +57,12 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) {
private fun runOfflineCatchupLogic() {
for ((_, patch) in patchMap) {
val type = patch.patch.type
val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE && 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){
var stagesToSimulate = if (!patch.isGrown()) patch.plantable!!.stages - patch.currentGrowthStage else 0
if (type == PatchType.BUSH)
if (type == PatchType.BUSH_PATCH)
stagesToSimulate += Math.min(4, 4 - patch.getFruitOrBerryCount())
if (type == PatchType.FRUIT_TREE)
if (type == PatchType.FRUIT_TREE_PATCH)
stagesToSimulate += Math.min(6, 6 - patch.getFruitOrBerryCount())
val nowTime = System.currentTimeMillis()

View file

@ -328,7 +328,7 @@ class LunarListeners : SpellListener("lunar"), Commands {
return
}
val patch = fPatch.getPatchFor(player)
if(!patch.isDiseased && !patch.isWeedy()){
if(!patch.isDiseased && !patch.isWeedy() && !patch.isEmptyAndWeeded()){
sendMessage(player, "It is growing just fine.")
return
}
@ -642,7 +642,7 @@ class LunarListeners : SpellListener("lunar"), Commands {
animate(player, Animations.LUNAR_SPELLBOOK_FERTILE_SOIL_4413)
sendGraphics(Graphics.LUNAR_SPELLBOOK_FERTILE_SOIL_724, target.location)
playGlobalAudio(target.location, Sounds.LUNAR_FERTILIZE_2891)
patch.compost = CompostType.SUPER
patch.compost = CompostType.SUPERCOMPOST
sendMessage(player, "You fertilize the soil.")
addXP(player, 87.0)
}

View file

@ -16,7 +16,6 @@ import core.ServerStore.Companion.getInt
import core.api.*
import core.cache.def.impl.ItemDefinition
import org.rs09.consts.Items
import org.rs09.consts.Sounds
enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)? = null) {
BAREFISTED_SMITHING("cape_perks:barefisted-smithing"),
@ -59,7 +58,7 @@ enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)?
val possibleSeeds = Plantable.values()
for(i in 0 until 10){
var seed = possibleSeeds.random()
while(seed == Plantable.SCARECROW || seed.applicablePatch == PatchType.FRUIT_TREE || seed.applicablePatch == PatchType.TREE || seed.applicablePatch == PatchType.SPIRIT_TREE){
while(seed == Plantable.SCARECROW || seed.applicablePatch == PatchType.FRUIT_TREE_PATCH || seed.applicablePatch == PatchType.TREE_PATCH || seed.applicablePatch == PatchType.SPIRIT_TREE_PATCH){
seed = possibleSeeds.random()
}
val reward = core.game.node.item.Item(seed.itemID)

View file

@ -77,10 +77,10 @@ public class GiantEntNPC extends Forager {
public void modifyFarmingReward(FarmingPatch fPatch, Item reward) {
PatchType patchType = fPatch.getType();
if(patchType == PatchType.FRUIT_TREE ||
patchType == PatchType.BUSH ||
patchType == PatchType.BELLADONNA ||
patchType == PatchType.CACTUS) {
if(patchType == PatchType.FRUIT_TREE_PATCH ||
patchType == PatchType.BUSH_PATCH ||
patchType == PatchType.BELLADONNA_PATCH ||
patchType == PatchType.CACTUS_PATCH) {
if(RandomFunction.roll(2)) {
reward.setAmount(2 * reward.getAmount());
}

View file

@ -767,10 +767,15 @@ fun emote(entity: Entity, emote: Emotes) {
/**
* Sends a message to the given player.
*
* The message will be split on word boundaries into multiple lines so
* that none of the lines will overflow the player's message box.
*
* @param player the player to send the message to.
* @param message the message to send.
*/
fun sendMessage(player: Player, message: String) {
player.sendMessage(message)
player.sendMessages(*splitLines(message, 86))
}
/**

View file

@ -2,12 +2,20 @@ package core.api
import java.util.*
import kotlin.math.ceil
object DialUtils {
val tagRegex = "<([A-Za-z0-9=/]+)>".toRegex()
fun removeMatches(message: String, regex: Regex): String {
return regex.replace(message, "")
}
}
/**
* Automatically split a single continuous string into multiple comma-separated lines.
* Should this not work out for any reason, you should fallback to standard npc and player methods for dialogue.
*/
fun splitLines(message: String, perLineLimit: Int = 54): Array<String> {
var lines = Array(ceil(message.length / perLineLimit.toFloat()).toInt()) {""}
var lines = Array(ceil(DialUtils.removeMatches(message, DialUtils.tagRegex).length / perLineLimit.toFloat()).toInt()) { "" }
//short circuit when possible because it's cheaper.
if (lines.size == 1) {
@ -20,8 +28,28 @@ fun splitLines(message: String, perLineLimit: Int = 54) : Array<String> {
val line = StringBuilder()
var accumulator = 0
val openTags = LinkedList<String>()
fun pushLine() {
if (line.isEmpty()) return
// find all tags that were opened or closed in the line
for (lineTag in DialUtils.tagRegex.findAll(line)) {
if (lineTag.value.get(1) == '/') {
// closing tag encountered; remove it from the list of open tags
for (openTag in openTags.descendingIterator()) {
val lineTagName = lineTag.value.substring(2, lineTag.value.length - 1)
val openTagName = openTag.substring(1, lineTag.value.length - 2)
if (lineTagName == openTagName) {
openTags.remove(openTag)
break
}
}
} else {
openTags.add(lineTag.value)
}
}
//allow array to be resized - there are specific edgecases where it becomes necessary. (Check the unit test for example)
if (lines.size == index)
lines = lines.plus(line.toString())
@ -30,11 +58,15 @@ fun splitLines(message: String, perLineLimit: Int = 54) : Array<String> {
index++
line.clear()
accumulator = 0
// if any unclosed tags remain, add them to the beginning of the new line
for (tag in openTags) line.append(tag)
openTags.clear()
}
while (!tokenQueue.isEmpty()) {
val shouldSpace = line.isNotEmpty()
accumulator += tokenQueue.peek().length
val shouldSpace = DialUtils.removeMatches(line.toString(), DialUtils.tagRegex).isNotEmpty()
accumulator += DialUtils.removeMatches(tokenQueue.peek(), DialUtils.tagRegex).length
if (shouldSpace) accumulator += 1
if (accumulator > perLineLimit) {