Merge branch 'farming-updates' into 'master'

Miscellaneous farming fixes

See merge request 2009scape/2009scape!211
This commit is contained in:
Ceikry
2021-07-30 12:42:46 +00:00
11 changed files with 213 additions and 40 deletions
@@ -204,6 +204,7 @@ public final class DialogueInterpreter {
public boolean close() {
if (dialogue != null || dialogueStage != null) {
actions.clear();
if (player.getInterfaceManager().getChatbox() != null && player.getInterfaceManager().getChatbox().getCloseEvent() != null) {
return true;
}
@@ -211,8 +212,10 @@ public final class DialogueInterpreter {
dialogueStage = null;
player.getInterfaceManager().closeChatbox();
}
if (dialogue != null && dialogue.close()) {
if (dialogue != null) {
DialoguePlugin d = dialogue;
dialogue = null;
d.close();
}
}
return dialogue == null && dialogueStage == null;
@@ -122,6 +122,7 @@ public abstract class DialoguePlugin implements Plugin<Player> {
public boolean close() {
player.getInterfaceManager().closeChatbox();
player.getInterfaceManager().openChatbox(137);
if(file != null) file.end();
finished = true;
return true;
}
+22 -2
View File
@@ -167,6 +167,26 @@ object ContentAPI {
if(!player.inventory.add(item)) GroundItemManager.create(item,player)
}
/**
* Clears an NPC with the "poof" smoke graphics commonly seen with random event NPCs.
* @param npc the NPC object to initialize
*/
@JvmStatic
fun poofClear(npc: NPC){
submitWorldPulse(object : Pulse(){
var counter = 0
override fun pulse(): Boolean {
when(counter++){
2 -> {
npc.isInvisible = true; Graphics.send(Graphics(86), npc.location)
}
3 -> npc.clear().also { return true }
}
return false
}
})
}
/**
* Check if an item exists in a player's bank
* @param player the player whose bank to check
@@ -546,8 +566,8 @@ object ContentAPI {
@JvmStatic
fun openDialogue(player: Player, dialogue: Any, vararg args: Any) {
when (dialogue) {
is Int -> player.dialogueInterpreter.open(dialogue, args)
is DialogueFile -> player.dialogueInterpreter.open(dialogue, args)
is Int -> player.dialogueInterpreter.open(dialogue, *args)
is DialogueFile -> player.dialogueInterpreter.open(dialogue, *args)
else -> SystemLogger.logErr("Invalid object type passed to openDialogue() -> ${dialogue.javaClass.simpleName}")
}
}
@@ -1,5 +1,6 @@
package rs09.game.content.ame
import api.ContentAPI
import core.game.interaction.MovementPulse
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
@@ -36,18 +37,7 @@ abstract class RandomEventNPC(id: Int) : NPC(id) {
open fun terminate(){
player.antiMacroHandler.event = null
if(initialized){
Pulser.submit(object : Pulse(){
var counter = 0
override fun pulse(): Boolean {
when(counter++){
2 -> {
isInvisible = true; Graphics.send(SMOKE_GRAPHICS, location)
}
3 -> clear().also { return true }
}
return false
}
})
ContentAPI.poofClear(this)
}
}
@@ -75,7 +75,7 @@ abstract class DialogueFile {
return player(expr, *splitLines(msg!!))
}
fun end(){
open fun end(){
if(interpreter != null) interpreter!!.close()
}
@@ -0,0 +1,98 @@
package rs09.game.content.zone
import api.ContentAPI
import core.game.content.dialogue.FacialExpression
import core.game.node.entity.Entity
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.game.system.task.Pulse
import core.game.world.map.zone.MapZone
import core.game.world.map.zone.ZoneBorders
import core.game.world.map.zone.ZoneBuilder
import core.plugin.Initializable
import core.plugin.Plugin
import org.rs09.consts.NPCs
import rs09.game.content.dialogue.DialogueFile
import rs09.tools.secondsToTicks
import java.util.concurrent.TimeUnit
@Initializable
class FarmingPatchZone : MapZone("farming patch", true), Plugin<Any> {
val playersInZone = hashMapOf<Player,Int>()
override fun configure() {
registerRegion(12083)
registerRegion(10548)
register(ZoneBorders(3594,3521,3608,3532))
ContentAPI.submitWorldPulse(zonePulse)
}
override fun newInstance(arg: Any?): Plugin<Any> {
ZoneBuilder.configure(this)
return this
}
override fun fireEvent(identifier: String?, vararg args: Any?): Any {
return Unit
}
override fun enter(e: Entity?): Boolean {
if(e is Player && playersInZone[e] == null && ContentAPI.getStatLevel(e, Skills.FARMING) <= 15) {
playersInZone[e] = 0
}
return super.enter(e)
}
override fun leave(e: Entity?, logout: Boolean): Boolean {
if(e is Player){
playersInZone.remove(e)
}
return super.enter(e)
}
val zonePulse = object : Pulse(){
override fun pulse(): Boolean {
playersInZone.toList().forEach { (player, ticks) ->
if(ticks == secondsToTicks(TimeUnit.MINUTES.toSeconds(5).toInt())){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
ContentAPI.openDialogue(player, SpiritDialogue(true), npc)
} else if (ticks == secondsToTicks(TimeUnit.MINUTES.toSeconds(10).toInt())){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
ContentAPI.openDialogue(player, SpiritDialogue(false), npc)
playersInZone.remove(player)
}
playersInZone[player] = ticks + 1
}
return false
}
}
internal class SpiritDialogue(val is5: Boolean) : DialogueFile(){
override fun handle(componentID: Int, buttonID: Int) {
when(stage){
0 -> {
if(is5) npcl(FacialExpression.NEUTRAL, "In case you didn't know, you don't have to stand by your Farming patch. Your crops will grow even if you're not around.").also { stage++ }
else npcl(FacialExpression.NEUTRAL, " Did you know that if your Farming patch has fully grown, it will never catch disease or die? It will be perfectly safe until you choose to harvest it.").also { stage++ }
}
1 -> end()
}
}
override fun end(){
super.end()
ContentAPI.poofClear(npc ?: return)
}
}
}
@@ -1,5 +1,6 @@
package rs09.game.node.entity.skill.farming
import api.ContentAPI
import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler
import core.game.node.Node
@@ -66,12 +67,8 @@ class CropHarvester : OptionHandler() {
delay = 2
player.inventory.add(Item(plantable.harvestItem,1))
player.skills.addExperience(Skills.FARMING,plantable.harvestXP)
patch.harvestAmt--
if(patch.harvestAmt <= 0){
patch.clear()
}
return patch.harvestAmt <= 0
patch.rollLivesDecrement(ContentAPI.getDynLevel(player, Skills.FARMING), requiredItem == Items.MAGIC_SECATEURS_7409)
return patch.cropLives <= 0
}
})
@@ -4,6 +4,8 @@ import core.game.node.entity.player.Player
import core.tools.RandomFunction
import rs09.game.system.SystemLogger
import java.util.concurrent.TimeUnit
import kotlin.math.ceil
import kotlin.math.min
class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantable?, var currentGrowthStage: Int, var isDiseased: Boolean, var isDead: Boolean, var isWatered: Boolean, var nextGrowth: Long, var harvestAmt: Int, var isCheckHealth: Boolean) {
constructor(player: Player, patch: FarmingPatch) : this(player,patch,null,0,false,false,false,0L,0,false)
@@ -11,22 +13,58 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
var diseaseMod = 0
var compost = CompostType.NONE
var protectionPaid = false
var cropLives = 3
fun setNewHarvestAmount() {
if(patch.type == PatchType.ALLOTMENT){
harvestAmt = RandomFunction.random(4,17)
} else if(patch.type == PatchType.FLOWER || patch.type == PatchType.EVIL_TURNIP) {
harvestAmt = when (plantable) {
Plantable.LIMPWURT_SEED, Plantable.WOAD_SEED -> 3
else -> 1
cropLives = 3
}
fun rollLivesDecrement(farmingLevel: Int, magicSecateurs: Boolean){
if(patch.type == PatchType.HERB){
//authentic formula thanks to released data.
var herbSaveLow = when(plantable){
Plantable.GUAM_SEED -> min(24 + farmingLevel, 80)
Plantable.MARRENTILL_SEED -> min(28 + farmingLevel, 80)
Plantable.TARROMIN_SEED -> min(31 + farmingLevel, 80)
Plantable.HARRALANDER_SEED -> min(36 + farmingLevel, 80)
Plantable.GOUT_TUBER -> min(39 + farmingLevel, 80)
Plantable.RANARR_SEED -> min(39 + farmingLevel, 80)
Plantable.SPIRIT_WEED_SEED -> min(43 + farmingLevel, 80)
Plantable.TOADFLAX_SEED -> min(43 + farmingLevel, 80)
Plantable.IRIT_SEED -> min(46 + farmingLevel, 80)
Plantable.AVANTOE_SEED -> min(50 + farmingLevel, 80)
Plantable.KWUARM_SEED -> min(54 + farmingLevel, 80)
Plantable.SNAPDRAGON_SEED -> min(57 + farmingLevel, 80)
Plantable.CADANTINE_SEED -> min(60 + farmingLevel, 80)
Plantable.LANTADYME_SEED -> min(64 + farmingLevel, 80)
Plantable.DWARF_WEED_SEED -> min(67 + farmingLevel, 80)
Plantable.TORSTOL_SEED -> min(71 + farmingLevel, 80)
else -> -1
}
if(magicSecateurs) herbSaveLow = ceil(1.10 * herbSaveLow).toInt()
val rand = RandomFunction.random(256)
if(rand > herbSaveLow){
cropLives -= 1
}
} else if(patch.type == PatchType.HOPS){
harvestAmt = RandomFunction.random(3,35)
} else {
harvestAmt = RandomFunction.random(3,5)
//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.CACTUS -> 3 //average of 3 per life times 3 lives = 9
else -> return
}
if(magicSecateurs) chance += ceil(1.10 * chance).toInt() //will increase average yield by roughly 3.
if(RandomFunction.roll(chance)) cropLives -= 1
}
if(compost == CompostType.NORMAL) harvestAmt += 1
if(compost == CompostType.SUPER) harvestAmt += 2
if(cropLives <= 0) clear()
}
fun isWeedy(): Boolean {
@@ -82,6 +120,11 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
if(isDead) player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, getCactusDeathValue())
else if(isDiseased && !isDead) player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, getCactusDiseaseValue())
}
PatchType.HERB -> {
if(isDead) player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, getHerbDeathValue())
else if(isDiseased && !isDead) player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, getHerbDiseaseValue())
else player.varpManager.get(patch.varpIndex).setVarbit(patch.varpOffset, (plantable?.value ?: 0) + currentGrowthStage)
}
else -> {}
}
}
@@ -144,6 +187,22 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
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
} else if (plantable == Plantable.SPIRIT_WEED_SEED) {
211 + currentGrowthStage - 1
} else {
198 + currentGrowthStage -1
}
}
private fun getHerbDeathValue(): Int {
return if(plantable == Plantable.GOUT_TUBER){
201 + currentGrowthStage - 1
} else 170 + currentGrowthStage - 1
}
private fun grow(){
if(isWeedy() && getCurrentState() > 0) {
nextGrowth = System.currentTimeMillis() + 60000
@@ -164,8 +223,8 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
}
if(RandomFunction.random(128) <= (17 - diseaseMod) && !isWatered && !isGrown() && !protectionPaid && !isFlowerProtected() && patch.type != PatchType.EVIL_TURNIP){
//bush, tree, fruit tree 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) && currentGrowthStage == 0)) {
//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)) {
isDiseased = true
return
}
@@ -63,17 +63,18 @@ enum class Plantable(val itemID: Int, val value: Int, val stages: Int, val plant
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),
GOUT_TUBER(6311,32,4,105.0,45.0,0.0,29,PatchType.HERB,Items.GOUTWEED_3261),
RANARR_SEED(5295,39,4,27.0,30.5,0.0,32,PatchType.HERB,Items.GRIMY_RANARR_207),
TOADFLAX_SEED(5296,47,4,34.0,38.5,0.0,38,PatchType.HERB,Items.GRIMY_TOADFLAX_3049),
IRIT_SEED(5297,54,4,43.0,48.5,0.0,44,PatchType.HERB,Items.GRIMY_IRIT_209),
AVANTOE_SEED(5298,204,4,54.5,61.5,0.0,50,PatchType.HERB,Items.GRIMY_AVANTOE_211),
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),
//Other
BELLADONNA_SEED(5281, 4, 4, 91.0, 128.0, 0.0, 63, PatchType.BELLADONNA, Items.CAVE_NIGHTSHADE_2398),
@@ -128,6 +128,7 @@ object UseWithPatchHandler{
if(p.plantable != null){
p.harvestAmt += if(p.compost == CompostType.NORMAL) 1 else if(p.compost == CompostType.SUPER) 2 else 0
}
p.cropLives += if(p.compost == CompostType.SUPER) 2 else 1
player.inventory.add(Item(Items.BUCKET_1925))
}
return true
@@ -50,6 +50,7 @@ class FarmingState(player: Player? = null) : State(player) {
p.put("patch-checkHealth",patch.isCheckHealth)
p.put("patch-compost",patch.compost.ordinal)
p.put("patch-paidprot",patch.protectionPaid)
p.put("patch-croplives", patch.cropLives)
patches.add(p)
}
val bins = JSONArray()
@@ -91,10 +92,12 @@ class FarmingState(player: Player? = null) : State(player) {
val savedState = p["patch-state"].toString().toInt()
val compostOrdinal = p["patch-compost"].toString().toInt()
val protectionPaid = p["patch-paidprot"] as Boolean
val cropLives = if(p["patch-croplives"] != null) p["patch-croplives"].toString().toInt() else 3
val fPatch = FarmingPatch.values()[patchOrdinal]
val plantable = if(patchPlantableOrdinal != -1) Plantable.values()[patchPlantableOrdinal] else null
val patch = Patch(player,fPatch,plantable,patchStage,patchDiseased,patchDead,patchWatered,nextGrowth,harvestAmt,checkHealth)
patch.cropLives = cropLives
patch.compost = CompostType.values()[compostOrdinal]
patch.protectionPaid = protectionPaid
patch.setCurrentState(savedState)