From e8c3f3178690873af5c5797ea8f36d651db36c1b Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Wed, 18 Jun 2025 13:35:50 +0000 Subject: [PATCH] Farming improvements White Berry Bushes can now be protected Giant Ent properly increases Belladonna yield Mushrooms now disease properly Fixed various bush bugs Fruit Trees can now be chopped Fixed Scarecrow needing to grow to work Mushrooms should now visually update as each mushroom is picked from the patch Poison Ivy Bushes are now disease immune Poison Ivy Berries picked in the Champion's Guild Patch now finishes a Varrock Diary task --- .../global/skill/farming/CropHarvester.kt | 17 ++++-- .../skill/farming/DigUpPatchDialogue.kt | 8 +++ .../skill/farming/FruitAndBerryPicker.kt | 5 ++ .../global/skill/farming/FruitTreeChopper.kt | 57 ++++++++++++++++++ .../content/global/skill/farming/Patch.kt | 59 +++++++++++++++---- .../content/global/skill/farming/Plantable.kt | 8 +-- .../skill/farming/UseWithPatchHandler.kt | 2 +- .../global/skill/farming/timers/CropGrowth.kt | 4 +- 8 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 Server/src/main/content/global/skill/farming/FruitTreeChopper.kt diff --git a/Server/src/main/content/global/skill/farming/CropHarvester.kt b/Server/src/main/content/global/skill/farming/CropHarvester.kt index 1ba3dd426..e47820bb3 100644 --- a/Server/src/main/content/global/skill/farming/CropHarvester.kt +++ b/Server/src/main/content/global/skill/farming/CropHarvester.kt @@ -15,7 +15,7 @@ import core.plugin.Plugin import org.rs09.consts.Items import org.rs09.consts.Sounds -val livesBased = arrayOf(PatchType.HERB_PATCH, PatchType.CACTUS_PATCH, PatchType.BELLADONNA_PATCH, PatchType.HOPS_PATCH, PatchType.ALLOTMENT, PatchType.EVIL_TURNIP_PATCH) +val livesBased = arrayOf(PatchType.HERB_PATCH, PatchType.CACTUS_PATCH, PatchType.HOPS_PATCH, PatchType.ALLOTMENT) @Initializable class CropHarvester : OptionHandler() { @@ -43,14 +43,16 @@ class CropHarvester : OptionHandler() { override fun pulse(): Boolean { var reward = Item(crop) - val familiar = player.familiarManager.familiar - if (familiar != null && familiar is GiantEntNPC) { - familiar.modifyFarmingReward(fPatch, reward) - } 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) + } + var requiredItem = when (fPatch.type) { PatchType.TREE_PATCH -> Items.SECATEURS_5329 else -> Items.SPADE_952 @@ -92,7 +94,7 @@ class CropHarvester : OptionHandler() { // 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 - addItem(player, reward.id) + addItemOrDrop(player, reward.id,reward.amount) rewardXP(player, Skills.FARMING, plantable.harvestXP) if (patch.patch.type in livesBased) { patch.rollLivesDecrement( @@ -104,6 +106,9 @@ class CropHarvester : OptionHandler() { if (patch.harvestAmt <= 0 && crop == plantable.harvestItem) { patch.clear() } + else if (fPatch.type == PatchType.MUSHROOM_PATCH){ + patch.setCurrentState(patch.getCurrentState() + 1) + } } if (sendHarvestMessages && (patch.cropLives <= 0 || patch.harvestAmt <= 0)) { sendMessage(player, "The $patchName is now empty.") diff --git a/Server/src/main/content/global/skill/farming/DigUpPatchDialogue.kt b/Server/src/main/content/global/skill/farming/DigUpPatchDialogue.kt index a0d70bb07..ab170aa74 100644 --- a/Server/src/main/content/global/skill/farming/DigUpPatchDialogue.kt +++ b/Server/src/main/content/global/skill/farming/DigUpPatchDialogue.kt @@ -30,6 +30,14 @@ class DigUpPatchDialogue(player: Player? = null) : DialoguePlugin(player) { return true } } + if (patch?.patch?.type == PatchType.FRUIT_TREE_PATCH) { + val isTreeStump = patch?.getCurrentState() == patch?.plantable!!.value + 25 + if (patch!!.isGrown() && !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 diff --git a/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt b/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt index f07689362..50bba4a24 100644 --- a/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt +++ b/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt @@ -6,6 +6,7 @@ import core.game.interaction.OptionHandler import core.game.node.Node import content.global.skill.summoning.familiar.GiantEntNPC import core.game.node.entity.player.Player +import core.game.node.entity.player.link.diary.DiaryType import core.game.node.entity.skill.Skills import core.game.node.item.Item import core.game.system.task.Pulse @@ -86,6 +87,10 @@ class FruitAndBerryPicker : OptionHandler() { sendMessage(player, "You pick $determiner ${reward.name.lowercase()}.") } + if (plantable == Plantable.POISON_IVY_SEED && patch.patch == FarmingPatch.CHAMPIONS_GUILD_BUSH){ + player.achievementDiaryManager.finishTask(player, DiaryType.VARROCK, 2, 0) + } + return patch.getFruitOrBerryCount() == 0 } }) diff --git a/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt b/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt new file mode 100644 index 000000000..c335007f6 --- /dev/null +++ b/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt @@ -0,0 +1,57 @@ +package content.global.skill.farming + +import content.data.skill.SkillingTool +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.system.task.Pulse +import core.plugin.Initializable +import core.plugin.Plugin +import core.tools.RandomFunction +import org.rs09.consts.Sounds + +@Initializable +class FruitTreeChopper : OptionHandler() { + override fun newInstance(arg: Any?): Plugin { + SceneryDefinition.setOptionHandler("chop-down",this) + SceneryDefinition.setOptionHandler("chop down",this) + return this + } + + override fun handle(player: Player?, node: Node?, option: String?): Boolean { + player ?: return false + node ?: return false + + val fPatch = FarmingPatch.forObject(node.asScenery()) + fPatch ?: return false + + val patch = fPatch.getPatchFor(player) + + val plantable = patch.plantable + plantable ?: return false + + val animation = SkillingTool.getHatchet(player).animation + + submitIndividualPulse(player, object : Pulse(animation.duration) { + override fun pulse(): Boolean { + animate(player, animation) + val soundIndex = RandomFunction.random(0, woodcuttingSounds.size) + playAudio(player, woodcuttingSounds[soundIndex]) + patch.setCurrentState(patch.getCurrentState() + 19) + sendMessage(player, "You chop down the ${plantable.displayName.lowercase().removeSuffix(" sapling")}.") + return true + } + }) + return true + } + + private val woodcuttingSounds = intArrayOf( + Sounds.WOODCUTTING_HIT_3038, + Sounds.WOODCUTTING_HIT_3039, + Sounds.WOODCUTTING_HIT_3040, + Sounds.WOODCUTTING_HIT_3041, + Sounds.WOODCUTTING_HIT_3042 + ) +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/farming/Patch.kt b/Server/src/main/content/global/skill/farming/Patch.kt index 8965167db..f8ce4dfce 100644 --- a/Server/src/main/content/global/skill/farming/Patch.kt +++ b/Server/src/main/content/global/skill/farming/Patch.kt @@ -29,7 +29,11 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl Plantable.WILLOW_SAPLING -> 0 else -> 1 } - if(plantable != null && plantable?.applicablePatch != PatchType.FLOWER_PATCH) { + if(plantable != null + && plantable?.applicablePatch != PatchType.FLOWER_PATCH + && plantable?.applicablePatch != PatchType.BELLADONNA_PATCH + && plantable?.applicablePatch != PatchType.MUSHROOM_PATCH + && plantable?.applicablePatch != PatchType.EVIL_TURNIP_PATCH) { harvestAmt += compostMod } cropLives = 3 + compostMod @@ -70,8 +74,6 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl var chance = when(patch.type){ PatchType.ALLOTMENT -> 8 //average of 8 per life times 3 lives = average 24 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 } @@ -88,6 +90,11 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl return getCurrentState() in 0..2 } + fun isChoppedFruitTree(): Boolean { + return (patch.type == PatchType.FRUIT_TREE_PATCH) + && getCurrentState() == (plantable?.value ?: 0) + 25 + } + fun isEmptyAndWeeded(): Boolean { return getCurrentState() == 3 } @@ -198,6 +205,10 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl else if(isDiseased && !isDead) setVisualState(getHerbDiseaseValue()) else setVisualState((plantable?.value ?: 0) + currentGrowthStage) } + PatchType.MUSHROOM_PATCH -> { + if(isDead) setVisualState(getMushroomDeathValue()) + else if(isDiseased && !isDead) setVisualState(getMushroomDiseaseValue()) + } else -> {} } } @@ -221,16 +232,27 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl private fun getBushDiseaseValue(): Int{ if(plantable == Plantable.POISON_IVY_SEED){ return (plantable?.value ?: 0) + currentGrowthStage + 12 - } else { + } + else if (plantable == Plantable.REDBERRY_SEED + || plantable == Plantable.CADAVABERRY_SEED){ + return (plantable?.value ?: 0) + currentGrowthStage + 65 + } + else { return (plantable?.value ?: 0) + currentGrowthStage + 64 } } private fun getBushDeathValue(): Int{ if(plantable == Plantable.POISON_IVY_SEED){ - return (plantable?.value ?: 0) + currentGrowthStage + 22 - } else { - return (plantable?.value ?: 0) + currentGrowthStage + 126 + return (plantable?.value ?: 0) + currentGrowthStage + 20 + } + else if (plantable == Plantable.REDBERRY_SEED + || plantable == Plantable.CADAVABERRY_SEED + || plantable == Plantable.WHITEBERRY_SEED){ + return (plantable?.value ?: 0) + currentGrowthStage + 129 + } + else { + return (plantable?.value ?: 0) + currentGrowthStage + 128 } } @@ -258,6 +280,14 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl return (plantable?.value ?: 0) + currentGrowthStage + 16 } + private fun getMushroomDiseaseValue(): Int { + return (plantable?.value ?: 0) + currentGrowthStage + 11 + } + + private fun getMushroomDeathValue(): Int { + return (plantable?.value ?: 0) + currentGrowthStage + 16 + } + private fun getHerbDiseaseValue(): Int { return if (plantable?.value ?: -1 <= 103) { 128 + (((plantable?.ordinal ?: 0) - Plantable.GUAM_SEED.ordinal) * 3) + currentGrowthStage - 1 @@ -275,7 +305,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl } private fun grow(){ - if((isWeedy() || isEmptyAndWeeded()) && getCurrentState() > 0) { + if((isWeedy() || isEmptyAndWeeded() || (plantable == Plantable.SCARECROW && !isGrown())) && getCurrentState() > 0) { nextGrowth = System.currentTimeMillis() + 60000 setCurrentState(getCurrentState() - 1) currentGrowthStage-- @@ -294,7 +324,14 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl CompostType.SUPERCOMPOST -> 13 } - if(patch != FarmingPatch.TROLL_STRONGHOLD_HERB && RandomFunction.random(128) <= (17 - diseaseMod) && !isWatered && !isGrown() && !protectionPaid && !isFlowerProtected() && patch.type != PatchType.EVIL_TURNIP_PATCH && currentGrowthStage != 0){ + if(patch != FarmingPatch.TROLL_STRONGHOLD_HERB + && RandomFunction.random(128) <= (17 - diseaseMod) + && !isWatered && !isGrown() + && !protectionPaid + && !isFlowerProtected() + && patch.type != PatchType.EVIL_TURNIP_PATCH + && plantable != Plantable.POISON_IVY_SEED + && currentGrowthStage != 0){ 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 @@ -389,7 +426,9 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl else -> return false }.getPatchFor(player, false) - return (fpatch.plantable != null && + if (fpatch.plantable == Plantable.SCARECROW && fpatch.plantable == plantable?.protectionFlower){ + return true + } else return (fpatch.plantable != null && (fpatch.plantable == plantable?.protectionFlower || fpatch.plantable == Plantable.forItemID(Items.WHITE_LILY_SEED_14589)) && fpatch.isGrown()) } diff --git a/Server/src/main/content/global/skill/farming/Plantable.kt b/Server/src/main/content/global/skill/farming/Plantable.kt index 83c016c64..ddb8069dd 100644 --- a/Server/src/main/content/global/skill/farming/Plantable.kt +++ b/Server/src/main/content/global/skill/farming/Plantable.kt @@ -14,7 +14,7 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v 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) - SCARECROW(Items.SCARECROW_6059,"scarecrow",33,3,0.0,0.0,0.0,23,PatchType.FLOWER_PATCH,Items.SCARECROW_6059), + SCARECROW(Items.SCARECROW_6059,"scarecrow",36,-3,0.0,0.0,0.0,23,PatchType.FLOWER_PATCH,Items.SCARECROW_6059), // Allotments 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), @@ -53,9 +53,9 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v // Bushes 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(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(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)), + DWELLBERRY_SEED(Items.DWELLBERRY_SEED_5103,"dwellberry bush seed",26,7,31.5,12.0,177.5,36,PatchType.BUSH_PATCH,Items.DWELLBERRIES_2126,Item(Items.STRAWBERRIES5_5406,3)), 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(Items.WHITEBERRY_SEED_5105,"whiteberry bush seed",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,Item(Items.MUSHROOM_6004,8)), 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 @@ -78,7 +78,7 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v // Special 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, "mushroom spore", 6, 7, 61.5, 57.7, 0.0, 53, PatchType.MUSHROOM_PATCH, Items.MUSHROOM_6004), + MUSHROOM_SPORE(Items.MUSHROOM_SPORE_5282, "mushroom spore", 4, 6, 61.5, 57.7, 0.0, 53, PatchType.MUSHROOM_PATCH, Items.MUSHROOM_6004), 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, "evil turnip seed", 4, 1, 41.0, 46.0, 0.0, 42, PatchType.EVIL_TURNIP_PATCH, Items.EVIL_TURNIP_12134) ; diff --git a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt index d24e5252d..538357dd0 100644 --- a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt +++ b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt @@ -235,7 +235,7 @@ class UseWithPatchHandler : InteractionListener { } val p = patch.getPatchFor(player) - if (p.getCurrentState() < 3 && p.isWeedy() && plantable != Plantable.SCARECROW) { + if (p.getCurrentState() < 3 && p.isWeedy()) { sendMessage(player, "This patch needs weeding first.") return@onUseWith true } else if (p.getCurrentState() > 3) { diff --git a/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt index 074e5ae43..46dbfc61f 100644 --- a/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt +++ b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt @@ -41,7 +41,7 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) { //Another more extreme example is if you planted something at 10:31 that takes 5 minutes to grow. 10:35 comes around, it hasn't been 5 minutes, so it doesn't grow, meaning //it actually grows at 10:40, an extra 4 minutes. //this code makes it so crops planted both at 10:31 and 10:34 grow at 10:35 if they are supposed to take 5 minutes for each stage. - if(patch.nextGrowth < (System.currentTimeMillis() + 240_000L) && !patch.isDead){ + if(patch.nextGrowth < (System.currentTimeMillis() + 240_000L) && !patch.isDead && !patch.isChoppedFruitTree()){ patch.nextGrowth = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(patch.getStageGrowthMinutes().toLong()) patch.update() } @@ -58,7 +58,7 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) { for ((_, patch) in patchMap) { val type = patch.patch.type val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH_PATCH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE_PATCH && patch.getFruitOrBerryCount() < 6) - if (shouldPlayCatchup && !patch.isDead) { + if (shouldPlayCatchup && !patch.isDead && !patch.isChoppedFruitTree()) { var stagesToSimulate = if (!patch.isGrown()) { if (patch.isWeedy() || patch.isEmptyAndWeeded()) patch.currentGrowthStage % 4 else patch.plantable!!.stages - patch.currentGrowthStage