Implemented farming spirit trees

This commit is contained in:
Syndromeramo
2025-07-15 12:13:02 +00:00
committed by Ryan
parent 2a438ce349
commit 5c13610776
7 changed files with 122 additions and 20 deletions
@@ -11,6 +11,14 @@ import org.rs09.consts.Items
class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false): DialogueFile() {
var item: Item? = null
var itemSecondary: Item? = null
var itemTertiary: Item? = null
fun hasAllItems(): Boolean {
return (inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))
&& (inInventory(player!!, itemSecondary!!.id, itemSecondary!!.amount) || inInventory(player!!, note(itemSecondary!!).id, note(itemSecondary!!).amount))
&& (inInventory(player!!, itemTertiary!!.id, itemTertiary!!.amount) || inInventory(player!!, note(itemTertiary!!).id, note(itemTertiary!!).amount))
}
override fun handle(componentID: Int, buttonID: Int) {
when (stage) {
START_DIALOGUE -> {
@@ -33,6 +41,9 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false):
npc("That patch is already fully grown!", "I don't know what you want me to do with it!").also { stage = END_DIALOGUE }
} else {
item = patch.plantable?.protectionItem
itemSecondary = patch.plantable?.protectionItemSecondary
itemTertiary = patch.plantable?.protectionItemTertiary
val protectionText = when (item?.id) {
Items.COMPOST_6032 -> if (item?.amount == 1) "bucket of compost" else "buckets of compost"
Items.POTATOES10_5438 -> if (item?.amount == 1) "sack of potatoes" else "sacks of potatoes"
@@ -51,10 +62,22 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false):
}
if (item == null) {
npc("Sorry, I won't protect that.").also { stage = END_DIALOGUE }
} else if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH && quickPay
&& !hasAllItems()) {
val amount = if (item?.amount == 1) "one" else item?.amount
npc(FacialExpression.HAPPY, "I want $amount $protectionText, one monkey bar,", "and one ground tooth for that.")
stage = 200
} else if (quickPay && !(inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))) {
val amount = if (item?.amount == 1) "one" else item?.amount
npc(FacialExpression.HAPPY, "I want $amount $protectionText for that.")
stage = 200
} else if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH && quickPay) {
showTopics(
//Found a 2011 source for quick-pay, but no earlier dialogue sources (https://www.youtube.com/watch?v=RdIcNH50v7I)
Topic("Yes", 20, true),
Topic("No", END_DIALOGUE, true),
title = "Pay the gnome?"
)
} else if (quickPay) {
val amount = if (item?.amount == 1) "one" else item?.amount
showTopics(
@@ -62,6 +85,10 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false):
Topic("No", END_DIALOGUE, true),
title = "Pay $amount $protectionText?"
)
} else if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH) {
val amount = if (item?.amount == 1) "one" else item?.amount
npc("If you like, but I want $amount $protectionText,", "one monkey bar, and one ground tooth for that.")
stage++
} else {
val amount = if (item?.amount == 1) "one" else item?.amount
npc("If you like, but I want $amount $protectionText for that.")
@@ -71,7 +98,9 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false):
}
1 -> {
if (!(inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))) {
if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH && !hasAllItems()) {
player("I'm afraid I don't have any of those at the moment.").also { stage = 10 }
} else if (!(inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))) {
player("I'm afraid I don't have any of those at the moment.").also { stage = 10 }
} else {
showTopics(
@@ -84,7 +113,14 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false):
10 -> npc("Well, I'm not wasting my time for free.").also { stage = END_DIALOGUE }
20 -> {
if (removeItem(player!!, item) || removeItem(player!!, note(item!!))) {
if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH
&& (removeItem(player!!, item) || removeItem(player!!, note(item!!)))
&& (removeItem(player!!, itemSecondary) || removeItem(player!!, note(itemSecondary!!)))
&& (removeItem(player!!, itemTertiary) || removeItem(player!!, note(itemTertiary!!)))) {
patch.protectionPaid = true
npc("That'll do nicely, ${if (player!!.isMale) "sir" else "madam"}. Leave it with me - I'll make sure", "that tree grows up good and strong.").also { stage = END_DIALOGUE }
} else if (patch.patch.type != PatchType.SPIRIT_TREE_PATCH
&& removeItem(player!!, item) || removeItem(player!!, note(item!!))) {
patch.protectionPaid = true
// Note: A slight change in this dialogue was seen in a December 2009 video - https://youtu.be/7gVh42ylQ48?t=138
npc("That'll do nicely, ${if (player!!.isMale) "sir" else "madam"}. Leave it with me - I'll make sure", "those crops grow for you.").also { stage = END_DIALOGUE }
@@ -21,7 +21,11 @@ enum class Farmers(val id: Int, val patches: Array<FarmingPatch>) {
FRANCIS(2327, arrayOf(FarmingPatch.ENTRANA_HOPS)),
DREVEN(2335, arrayOf(FarmingPatch.CHAMPIONS_GUILD_BUSH)),
TARIA(2336, arrayOf(FarmingPatch.RIMMINGTON_BUSH)),
TORRELL(2338, arrayOf(FarmingPatch.ARDOUGNE_BUSH));
TORRELL(2338, arrayOf(FarmingPatch.ARDOUGNE_BUSH)),
FRIZZY(4560, arrayOf(FarmingPatch.PORT_SARIM_SPIRIT_TREE)),
YULF(4561, arrayOf(FarmingPatch.ETCETERIA_SPIRIT_TREE)),
PRAISTAN(4562, arrayOf(FarmingPatch.KARAMJA_SPIRIT_TREE))
;
companion object{
@JvmField
@@ -26,7 +26,7 @@ class HealthChecker : OptionHandler() {
val patch = fPatch.getPatchFor(player)
val type = patch.patch.type
if (type != PatchType.BUSH_PATCH && type != PatchType.FRUIT_TREE_PATCH && type != PatchType.TREE_PATCH && type != PatchType.CACTUS_PATCH) {
if (!patch.isCheckable()) {
sendMessage(player, "This shouldn't be happening. Please report this.")
return true
}
@@ -44,6 +44,10 @@ class HealthChecker : OptionHandler() {
patch.setCurrentState(patch.getCurrentState() - 14)
sendMessage(player, "You examine the tree for signs of disease and find that it is in perfect health.")
}
PatchType.SPIRIT_TREE_PATCH -> {
patch.setCurrentState(patch.getCurrentState() - 24)
sendMessage(player, "You examine the tree for signs of disease and find that it is in perfect health.")
}
PatchType.BUSH_PATCH -> {
patch.setCurrentState(patch.plantable!!.value + patch.plantable!!.stages + 4)
sendMessage(player, "You examine the bush for signs of disease and find that it's in perfect health.")
@@ -16,6 +16,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
var compost = CompostType.NONE
var protectionPaid = false
var cropLives = 3
val checkablePatches = arrayOf(PatchType.TREE_PATCH, PatchType.BUSH_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH, PatchType.CACTUS_PATCH)
fun setNewHarvestAmount() {
val compostMod = when(compost) {
@@ -162,6 +163,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
PatchType.BUSH_PATCH -> setVarbit(player, patch.varbit, 250 + (plantable!!.ordinal - Plantable.REDBERRY_SEED.ordinal))
PatchType.CACTUS_PATCH -> setVarbit(player, patch.varbit, 31)
PatchType.TREE_PATCH -> setVarbit(player, patch.varbit, plantable!!.value + plantable!!.stages)
PatchType.SPIRIT_TREE_PATCH -> setVarbit(player, patch.varbit, plantable!!.value + plantable!!.stages + 24)
else -> log(this::class.java, Log.WARN, "Invalid setting of isCheckHealth for patch type: " + patch.type.name + "at" + patch.name)
}
} else {
@@ -187,6 +189,10 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
if (state != getVarbit(player, patch.varbit))
setVisualState(state)
}
PatchType.SPIRIT_TREE_PATCH -> {
if(isDead) setVisualState(getSpiritTreeDeathValue())
else if(isDiseased && !isDead) setVisualState(getSpiritTreeDiseaseValue())
}
PatchType.FRUIT_TREE_PATCH -> {
if(isDead) setVisualState(getFruitTreeDeathValue())
else if(isDiseased && !isDead) setVisualState(getFruitTreeDiseaseValue())
@@ -256,6 +262,14 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
}
}
private fun getSpiritTreeDiseaseValue(): Int {
return (plantable?.value ?: 0) + currentGrowthStage + 12
}
private fun getSpiritTreeDeathValue(): Int {
return (plantable?.value ?: 0) + currentGrowthStage + 24
}
private fun getFruitTreeDiseaseValue(): Int {
return (plantable?.value ?: 0) + currentGrowthStage + 12
}
@@ -338,7 +352,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
return
}
if((patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.TREE_PATCH || patch.type == PatchType.BUSH_PATCH || patch.type == PatchType.CACTUS_PATCH) && plantable != null && plantable?.stages == currentGrowthStage + 1){
if(isCheckable() && plantable != null && plantable?.stages == currentGrowthStage + 1){
isCheckHealth = true
}
@@ -432,4 +446,8 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
(fpatch.plantable == plantable?.protectionFlower || fpatch.plantable == Plantable.forItemID(Items.WHITE_LILY_SEED_14589))
&& fpatch.isGrown())
}
fun isCheckable(): Boolean{
return (patch.type in checkablePatches)
}
}
@@ -3,7 +3,7 @@ package content.global.skill.farming
import core.game.node.item.Item
import org.rs09.consts.Items
enum class Plantable(val itemID: Int, val displayName: String, val value: Int, val stages: Int, val plantingXP: Double, val harvestXP: Double, val checkHealthXP: Double, val requiredLevel: Int, val applicablePatch: PatchType, val harvestItem: Int, val protectionItem: Item? = null, val protectionFlower: Plantable? = null) {
enum class Plantable(val itemID: Int, val displayName: String, val value: Int, val stages: Int, val plantingXP: Double, val harvestXP: Double, val checkHealthXP: Double, val requiredLevel: Int, val applicablePatch: PatchType, val harvestItem: Int, val protectionItem: Item? = null, val protectionFlower: Plantable? = null, val protectionItemSecondary: Item? = null, val protectionItemTertiary: Item? = null) {
// Flowers
MARIGOLD_SEED(Items.MARIGOLD_SEED_5096,"marigold seed",8,4,8.5,47.0,0.0,2,PatchType.FLOWER_PATCH,Items.MARIGOLDS_6010),
@@ -80,7 +80,8 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v
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", 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)
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),
SPIRIT_SAPLING(Items.SPIRIT_SAPLING_5375, "spirit tree sapling", 8, 12, 199.5, 0.0, 19301.0, 83, PatchType.SPIRIT_TREE_PATCH, 0, Item(Items.MONKEY_NUTS_4012, 5), null, Item(Items.MONKEY_BAR_4014, 1), Item(Items.GROUND_TOOTH_9082, 1) )
;
constructor(itemID: Int, displayName: String, value: Int, stages: Int, plantingXP: Double, harvestXP: Double, checkHealthXP: Double, requiredLevel: Int, applicablePatch: PatchType, harvestItem: Int, protectionFlower: Plantable)
@@ -38,6 +38,7 @@ class UseWithPatchHandler : InteractionListener {
onUseWith(IntType.SCENERY, allowedNodes.toIntArray(), *FarmingPatch.patchNodes.toIntArray()) { player, used, with ->
val patch = FarmingPatch.forObject(with.asScenery()) ?: return@onUseWith true
val usedItem = used.asItem()
val trimmablePatches = arrayOf(PatchType.TREE_PATCH, PatchType.BUSH_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH)
if (patch == FarmingPatch.TROLL_STRONGHOLD_HERB) {
if (!hasRequirement(player, Quests.MY_ARMS_BIG_ADVENTURE))
@@ -79,7 +80,7 @@ class UseWithPatchHandler : InteractionListener {
}
SECATEURS, MAGIC_SECATEURS -> {
val p = patch.getPatchFor(player)
if (patch.type == PatchType.TREE_PATCH) {
if (patch.type in trimmablePatches) {
if (p.isDiseased && !p.isDead) {
submitIndividualPulse(player, object: Pulse() {
override fun pulse(): Boolean {
@@ -131,7 +132,7 @@ class UseWithPatchHandler : InteractionListener {
Items.PLANT_CURE_6036 -> {
val p = patch.getPatchFor(player)
val patchName = p.patch.type.displayName()
if (p.isDiseased && !p.isDead) {
if (p.isDiseased && !p.isDead && patch.type !in trimmablePatches) {
sendMessage(player, "You treat the $patchName with the plant cure.")
queueScript(player, 0, QueueStrength.WEAK) { stage: Int ->
when (stage) {
@@ -260,12 +261,17 @@ class UseWithPatchHandler : InteractionListener {
}*/
val requiredItem = when (patch.type) {
PatchType.TREE_PATCH, PatchType.FRUIT_TREE_PATCH -> Items.SPADE_952
PatchType.TREE_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH -> Items.SPADE_952
PatchType.FLOWER_PATCH -> if (plantable == Plantable.SCARECROW) null else Items.SEED_DIBBER_5343
else -> Items.SEED_DIBBER_5343
}
if (requiredItem != null && !inInventory(player, requiredItem)) {
sendMessage(player, "You need ${prependArticle(requiredItem.asItem().name.lowercase())} to plant that.")
sendDialogue(player, "You need ${prependArticle(requiredItem.asItem().name.lowercase())} to plant that.")
return@onUseWith true
}
val spiritTreePlanted = (getVarbit(player, 720) > 3 || getVarbit(player, 722) > 3 || getVarbit(player, 724) > 3)
if (plantable == Plantable.SPIRIT_SAPLING && spiritTreePlanted){
sendDialogue(player, "You already have a spirit tree. You can't plant another one.")
return@onUseWith true
}
@@ -278,7 +284,7 @@ class UseWithPatchHandler : InteractionListener {
}
animate(player, anim)
playAudio(player, sound)
val delay = if (patch.type == PatchType.TREE_PATCH || patch.type == PatchType.FRUIT_TREE_PATCH || plantable == Plantable.SCARECROW) 3 else 0
val delay = if (patch.type == PatchType.TREE_PATCH || patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.SPIRIT_TREE_PATCH || plantable == Plantable.SCARECROW) 3 else 0
return@queueScript delayScript(player,anim.duration + delay)
}
1 -> {
@@ -289,12 +295,12 @@ class UseWithPatchHandler : InteractionListener {
p.plant(plantable)
rewardXP(player, Skills.FARMING, plantable.plantingXP)
p.setNewHarvestAmount()
if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH) {
if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH || p.patch.type == PatchType.SPIRIT_TREE_PATCH) {
addItem(player, Items.PLANT_POT_5350)
}
val itemAmount =
if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH) "the"
if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH || p.patch.type == PatchType.SPIRIT_TREE_PATCH) "the"
else if (plantItem.amount == 1) "a"
else plantItem.amount
val itemName =
@@ -1,9 +1,5 @@
package content.global.travel.trees
import core.api.isQuestComplete
import core.api.openDialogue
import core.api.sendDialogue
import core.api.teleport
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.diary.DiaryType
@@ -18,9 +14,10 @@ import core.game.interaction.IntType
import core.game.world.GameWorld.Pulser
import core.tools.END_DIALOGUE
import content.data.Quests
import core.api.*
class GnomeSpiritTreeListener: InteractionListener {
val spiritTrees = intArrayOf(1317,1293,1294)
val spiritTrees = intArrayOf(1317,1293,1294,8355)
override fun defineListeners() {
on(spiritTrees, IntType.SCENERY, "talk-to"){ player, _ ->
@@ -39,7 +36,11 @@ class GnomeSpiritTreeTeleportDialogue: DialogueFile() {
Location(2542, 3170, 0),
Location(2461, 3444, 0),
Location(2556, 3259, 0),
Location(3184, 3508, 0)
Location(3184, 3508, 0),
Location(3060, 3256, 0), //Port Sarim
Location(2613, 3856, 0), //Etceteria
Location(2800, 3203, 0) //Brimhaven
)
private val ANIMATIONS = arrayOf(Animation(7082), Animation(7084))
@@ -86,6 +87,15 @@ class GnomeSpiritTreeTeleportDialogue: DialogueFile() {
stage = END_DIALOGUE
return
}
var plantedSpiritTreeLocation = when {
getVarbit(player!!, 720) == 20 -> "Port Sarim"
getVarbit(player!!, 722) == 20 -> "Etceteria"
getVarbit(player!!, 724) == 20 -> "Brimhaven"
else -> null
//There should never be a case where more than one spirit tree is planted.
}
if(plantedSpiritTreeLocation == null) {
when (stage) {
0 -> interpreter!!.sendOptions(
"Where would you like to go?",
@@ -102,6 +112,29 @@ class GnomeSpiritTreeTeleportDialogue: DialogueFile() {
}
}
}
else when (stage) {
0 -> interpreter!!.sendOptions(
"Where would you like to go?",
"Tree Gnome Village",
"Tree Gnome Stronghold",
"Battlefield of Khazard",
"Grand Exchange",
plantedSpiritTreeLocation
).also { stage++ }
1 -> when (buttonID) {
1 -> sendTeleport(player!!, LOCATIONS[0])
2 -> sendTeleport(player!!, LOCATIONS[1])
3 -> sendTeleport(player!!, LOCATIONS[2])
4 -> sendTeleport(player!!, LOCATIONS[3])
5 -> when (plantedSpiritTreeLocation) {
"Port Sarim" -> sendTeleport(player!!,LOCATIONS[4])
"Etceteria" -> sendTeleport(player!!,LOCATIONS[5])
"Brimhaven" -> sendTeleport(player!!,LOCATIONS[6])
else -> stage = END_DIALOGUE
}
}
}
}
}
class GnomeSpiritTreeDialogue: DialogueFile() {