Merge branch 'improve-trees' into 'master'

Improve farming trees:

See merge request 2009scape/2009scape!324
This commit is contained in:
Ceikry 2021-10-29 21:47:53 +00:00
commit b39d811dd0
9 changed files with 199 additions and 63 deletions

View file

@ -1 +1,4 @@
- Trees regrow from their stumps over time, or from Hydra's regrowth scrolls.
- Diseased trees can be cured with secateurs.
- Willow branches can be harvested from willow trees.
- Giant ent improves yields of certain patch types by 50%.

View file

@ -1,17 +1,27 @@
package core.game.node.entity.npc.familiar; package core.game.node.entity.npc.familiar;
import core.plugin.Initializable; import core.game.interaction.NodeUsageEvent;
import core.game.node.entity.skill.summoning.familiar.Familiar; import core.game.interaction.UseWithHandler;
import core.game.node.entity.skill.summoning.familiar.FamiliarSpecial;
import core.game.node.entity.combat.equipment.WeaponInterface; import core.game.node.entity.combat.equipment.WeaponInterface;
import core.game.node.entity.player.Player; import core.game.node.entity.player.Player;
import core.game.node.entity.skill.summoning.familiar.Familiar;
import core.game.node.entity.skill.summoning.familiar.FamiliarSpecial;
import core.game.node.entity.skill.summoning.familiar.Forager;
import core.game.node.item.Item;
import core.plugin.Initializable;
import core.plugin.Plugin;
import core.tools.RandomFunction;
import org.rs09.consts.Items;
import rs09.game.node.entity.skill.farming.FarmingPatch;
import rs09.game.node.entity.skill.farming.PatchType;
/** /**
* Represents the Giant Ent familiar. * Represents the Giant Ent familiar.
* @author Aero * @author Aero
*/ */
@Initializable @Initializable
public class GiantEntNPC extends Familiar { public class GiantEntNPC extends Forager {
private static final Item[] ITEMS = new Item[] { new Item(Items.OAK_LOGS_1521) };
/** /**
* Constructs a new {@code GiantEntNPC} {@code Object}. * Constructs a new {@code GiantEntNPC} {@code Object}.
@ -26,7 +36,7 @@ public class GiantEntNPC extends Familiar {
* @param id The id. * @param id The id.
*/ */
public GiantEntNPC(Player owner, int id) { public GiantEntNPC(Player owner, int id) {
super(owner, id, 4900, 12013, 6, WeaponInterface.STYLE_CONTROLLED); super(owner, id, 4900, 12013, 6, WeaponInterface.STYLE_CONTROLLED, ITEMS);
} }
@Override @Override
@ -44,4 +54,39 @@ public class GiantEntNPC extends Familiar {
return new int[] { 6800, 6801 }; return new int[] { 6800, 6801 };
} }
@Override
protected void configureFamiliar() {
UseWithHandler.addHandler(6800, UseWithHandler.NPC_TYPE, new UseWithHandler(Items.PURE_ESSENCE_7936) {
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
addHandler(6800, UseWithHandler.NPC_TYPE, this);
return this;
}
@Override
public boolean handle(NodeUsageEvent event) {
Player player = event.getPlayer();
player.lock(1);
int runeType = RandomFunction.random(9) < 4 ? Items.EARTH_RUNE_557 : Items.NATURE_RUNE_561;
Item runes = new Item(runeType, 1);
if (player.getInventory().remove(event.getUsedItem())) {
player.getInventory().add(runes);
player.sendMessage(String.format("The giant ent transmutes the pure essence into a %s.", runes.getName().toLowerCase()));
}
return true;
}
});
}
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(RandomFunction.roll(2)) {
reward.setAmount(2 * reward.getAmount());
}
}
}
} }

View file

@ -1,10 +1,14 @@
package core.game.node.entity.npc.familiar; package core.game.node.entity.npc.familiar;
import core.plugin.Initializable; import core.game.node.Node;
import core.game.node.entity.skill.summoning.familiar.Familiar;
import core.game.node.entity.skill.summoning.familiar.FamiliarSpecial;
import core.game.node.entity.combat.equipment.WeaponInterface; import core.game.node.entity.combat.equipment.WeaponInterface;
import core.game.node.entity.player.Player; import core.game.node.entity.player.Player;
import core.game.node.entity.skill.summoning.familiar.Familiar;
import core.game.node.entity.skill.summoning.familiar.FamiliarSpecial;
import core.game.node.scenery.Scenery;
import core.plugin.Initializable;
import rs09.game.node.entity.skill.farming.FarmingPatch;
import rs09.game.node.entity.skill.farming.Patch;
/** /**
* Represents the Hydra familiar. * Represents the Hydra familiar.
@ -36,6 +40,17 @@ public class HydraNPC extends Familiar {
@Override @Override
protected boolean specialMove(FamiliarSpecial special) { protected boolean specialMove(FamiliarSpecial special) {
Node node = special.getNode();
if(node instanceof Scenery) {
Scenery scenery = (Scenery)node;
FarmingPatch farmingPatch = FarmingPatch.forObject(scenery);
if(farmingPatch != null) {
Patch patch = farmingPatch.getPatchFor(owner);
patch.regrowIfTreeStump();
return true;
}
}
return false; return false;
} }

View file

@ -48,7 +48,6 @@ public class UnicornStallionNPC extends Familiar {
Player player = (Player) special.getNode(); Player player = (Player) special.getNode();
player.getAudioManager().send(4372); player.getAudioManager().send(4372);
visualize(Animation.create(8267), Graphics.create(1356)); visualize(Animation.create(8267), Graphics.create(1356));
player.getSettings().updateRunEnergy(player.getSettings().getRunEnergy() * 0.10);
player.getSkills().heal((int) (player.getSkills().getMaximumLifepoints() * 0.15)); player.getSkills().heal((int) (player.getSkills().getMaximumLifepoints() * 0.15));
return true; return true;
} }

View file

@ -4,6 +4,7 @@ import api.ContentAPI
import core.cache.def.impl.SceneryDefinition import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler import core.game.interaction.OptionHandler
import core.game.node.Node import core.game.node.Node
import core.game.node.entity.npc.familiar.GiantEntNPC
import core.game.node.entity.player.Player import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills import core.game.node.entity.skill.Skills
import core.game.node.item.Item import core.game.node.item.Item
@ -11,21 +12,85 @@ import core.game.system.task.Pulse
import core.game.world.update.flag.context.Animation import core.game.world.update.flag.context.Animation
import core.plugin.Initializable import core.plugin.Initializable
import core.plugin.Plugin import core.plugin.Plugin
import core.tools.RandomFunction
import org.rs09.consts.Items import org.rs09.consts.Items
val livesBased = arrayOf(PatchType.HERB, PatchType.CACTUS, PatchType.BELLADONNA, PatchType.HOPS, PatchType.ALLOTMENT,PatchType.EVIL_TURNIP)
val spadeAnim = Animation(830)
@Initializable @Initializable
class CropHarvester : OptionHandler() { class CropHarvester : OptionHandler() {
val livesBased = arrayOf(PatchType.HERB, PatchType.CACTUS, PatchType.BELLADONNA, PatchType.HOPS, PatchType.ALLOTMENT,PatchType.EVIL_TURNIP)
val spadeAnim = Animation(830)
override fun newInstance(arg: Any?): Plugin<Any> { override fun newInstance(arg: Any?): Plugin<Any> {
SceneryDefinition.setOptionHandler("harvest",this) SceneryDefinition.setOptionHandler("harvest",this)
SceneryDefinition.setOptionHandler("pick",this) SceneryDefinition.setOptionHandler("pick",this)
return this return this
} }
companion object {
@JvmStatic
fun harvestPulse(player: Player?, node: Node?, crop: Int): Pulse? {
player ?: return null
node ?: return null
val fPatch = FarmingPatch.forObject(node.asScenery())
fPatch ?: return null
val patch = fPatch.getPatchFor(player)
val plantable = patch.plantable
plantable ?: return null
return object : Pulse(0) {
override fun pulse(): Boolean {
var reward = Item(crop, 1)
val familiar = player.familiarManager.familiar
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.")
return true
}
var requiredItem = when(fPatch.type){
PatchType.HERB -> Items.SECATEURS_5329
else -> Items.SPADE_952
}
if(requiredItem == Items.SECATEURS_5329){
if(player.inventory.contains(Items.MAGIC_SECATEURS_7409,1)){
requiredItem = Items.MAGIC_SECATEURS_7409
}
}
val anim = when(requiredItem){
Items.SPADE_952 -> Animation(830)
Items.SECATEURS_5329 -> Animation(7227)
Items.MAGIC_SECATEURS_7409 -> Animation(7228)
else -> Animation(0)
}
if(!player.inventory.containsItem(Item(requiredItem))){
player.sendMessage("You lack the needed tool to harvest these crops.")
return true
}
player.animator.animate(anim)
delay = 2
player.inventory.add(reward)
player.skills.addExperience(Skills.FARMING,plantable.harvestXP)
if(patch.patch.type in livesBased){
patch.rollLivesDecrement(
ContentAPI.getDynLevel(player, Skills.FARMING),
requiredItem == Items.MAGIC_SECATEURS_7409
)
} else {
patch.harvestAmt--
if(patch.harvestAmt <= 0 && crop == plantable.harvestItem){
patch.clear()
}
}
return patch.cropLives <= 0 || patch.harvestAmt <= 0
}
}
}
}
override fun handle(player: Player?, node: Node?, option: String?): Boolean { override fun handle(player: Player?, node: Node?, option: String?): Boolean {
player ?: return false player ?: return false
node ?: return false node ?: return false
@ -40,49 +105,8 @@ class CropHarvester : OptionHandler() {
return true return true
} }
player.pulseManager.run(object : Pulse(0){ val pulse = harvestPulse(player, node, plantable.harvestItem) ?: return false
override fun pulse(): Boolean { player.pulseManager.run(pulse)
if(!player.inventory.hasSpaceFor(Item(plantable.harvestItem,1))){
player.sendMessage("You don't have enough inventory space for that.")
return true
}
var requiredItem = when(fPatch.type){
PatchType.HERB -> Items.SECATEURS_5329
else -> Items.SPADE_952
}
if(requiredItem == Items.SECATEURS_5329){
if(player.inventory.contains(Items.MAGIC_SECATEURS_7409,1)){
requiredItem = Items.MAGIC_SECATEURS_7409
}
}
val anim = when(requiredItem){
Items.SPADE_952 -> Animation(830)
Items.SECATEURS_5329 -> Animation(7227)
Items.MAGIC_SECATEURS_7409 -> Animation(7228)
else -> Animation(0)
}
if(!player.inventory.containsItem(Item(requiredItem))){
player.sendMessage("You lack the needed tool to harvest these crops.")
return true
}
player.animator.animate(anim)
delay = 2
player.inventory.add(Item(plantable.harvestItem,1))
player.skills.addExperience(Skills.FARMING,plantable.harvestXP)
if(patch.patch.type in livesBased){
patch.rollLivesDecrement(
ContentAPI.getDynLevel(player, Skills.FARMING),
requiredItem == Items.MAGIC_SECATEURS_7409
)
} else {
patch.harvestAmt--
if(patch.harvestAmt <= 0){
patch.clear()
}
}
return patch.cropLives <= 0 || patch.harvestAmt <= 0
}
})
return true return true
} }

View file

@ -4,6 +4,7 @@ import api.ContentAPI
import core.cache.def.impl.SceneryDefinition import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler import core.game.interaction.OptionHandler
import core.game.node.Node import core.game.node.Node
import core.game.node.entity.npc.familiar.GiantEntNPC
import core.game.node.entity.player.Player import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills import core.game.node.entity.skill.Skills
import core.game.node.item.Item import core.game.node.item.Item
@ -42,7 +43,6 @@ class FruitAndBerryPicker : OptionHandler() {
plantable ?: return false plantable ?: return false
val animation = Animation(2281) val animation = Animation(2281)
val reward = Item(plantable.harvestItem)
if(patch.getFruitOrBerryCount() <= 0){ if(patch.getFruitOrBerryCount() <= 0){
player.sendMessage("This shouldn't be happening. Please report this.") player.sendMessage("This shouldn't be happening. Please report this.")
@ -60,6 +60,13 @@ class FruitAndBerryPicker : OptionHandler() {
player.pulseManager.run(object : Pulse(animation.duration){ player.pulseManager.run(object : Pulse(animation.duration){
override fun pulse(): Boolean { override fun pulse(): Boolean {
val reward = Item(plantable.harvestItem, 1)
val familiar = player.familiarManager.familiar
if(familiar != null && familiar is GiantEntNPC) {
familiar.modifyFarmingReward(fPatch, reward)
}
player.animator.animate(animation) player.animator.animate(animation)
ContentAPI.addItemOrDrop(player,reward.id,reward.amount) ContentAPI.addItemOrDrop(player,reward.id,reward.amount)
player.skills.addExperience(Skills.FARMING,plantable.harvestXP) player.skills.addExperience(Skills.FARMING,plantable.harvestXP)

View file

@ -25,6 +25,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
harvestAmt = when (plantable) { harvestAmt = when (plantable) {
Plantable.LIMPWURT_SEED, Plantable.WOAD_SEED -> 3 Plantable.LIMPWURT_SEED, Plantable.WOAD_SEED -> 3
Plantable.MUSHROOM_SPORE -> 6 Plantable.MUSHROOM_SPORE -> 6
Plantable.WILLOW_SAPLING -> 0
else -> 1 else -> 1
} }
if(plantable != null && plantable?.applicablePatch != PatchType.FLOWER) { if(plantable != null && plantable?.applicablePatch != PatchType.FLOWER) {
@ -254,12 +255,30 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
setCurrentState(getCurrentState() + 1) setCurrentState(getCurrentState() + 1)
} }
} }
if(patch.type == PatchType.TREE) {
// Willow branches
if(harvestAmt < 6) {
harvestAmt++;
}
}
if(plantable?.stages ?: 0 > currentGrowthStage && !isGrown()){ if(plantable?.stages ?: 0 > currentGrowthStage && !isGrown()){
currentGrowthStage++ currentGrowthStage++
setCurrentState(getCurrentState() + 1) setCurrentState(getCurrentState() + 1)
isWatered = false isWatered = false
} }
regrowIfTreeStump()
}
fun regrowIfTreeStump() {
if(patch.type == PatchType.TREE && 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)
isWatered = false
}
}
} }
fun update(){ fun update(){

View file

@ -7,6 +7,7 @@ import core.game.node.item.Item
import core.game.system.task.Pulse import core.game.system.task.Pulse
import core.game.world.update.flag.context.Animation import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items import org.rs09.consts.Items
import rs09.game.node.entity.skill.farming.CropHarvester
object UseWithPatchHandler{ object UseWithPatchHandler{
val RAKE = Items.RAKE_5341 val RAKE = Items.RAKE_5341
@ -17,6 +18,7 @@ object UseWithPatchHandler{
val pourBucketAnim = Animation(2283) val pourBucketAnim = Animation(2283)
val wateringCanAnim = Animation(2293) val wateringCanAnim = Animation(2293)
val plantCureAnim = Animation(2288) val plantCureAnim = Animation(2288)
val secateursAnim = Animation(7227)
@JvmField @JvmField
val allowedNodes = ArrayList<Int>() val allowedNodes = ArrayList<Int>()
@ -46,7 +48,23 @@ object UseWithPatchHandler{
RAKE -> PatchRaker.rake(player,patch) RAKE -> PatchRaker.rake(player,patch)
SEED_DIBBER -> player.sendMessage("I should plant a seed, not the seed dibber.") SEED_DIBBER -> player.sendMessage("I should plant a seed, not the seed dibber.")
SPADE -> player.dialogueInterpreter.open(67984003,patch.getPatchFor(player)) //DigUpPatchDialogue.kt SPADE -> player.dialogueInterpreter.open(67984003,patch.getPatchFor(player)) //DigUpPatchDialogue.kt
SECATEURS -> {} SECATEURS -> {
val p = patch.getPatchFor(player)
if(patch.type == PatchType.TREE) {
if(p.isDiseased && !p.isDead) {
player.pulseManager.run(object: Pulse(){
override fun pulse(): Boolean {
player.animator.animate(secateursAnim)
p.cureDisease()
return true
}
})
} else if(p.plantable == Plantable.WILLOW_SAPLING && p.harvestAmt > 0) {
val pulse = CropHarvester.harvestPulse(player, event.usedWith, Items.WILLOW_BRANCH_5933) ?: return false
player.pulseManager.run(pulse)
}
}
}
TROWEL -> { TROWEL -> {
val p = patch.getPatchFor(player) val p = patch.getPatchFor(player)
if(!p.isWeedy()){ if(!p.isWeedy()){

View file

@ -148,7 +148,13 @@ class FarmingState(player: Player? = null) : State(player) {
if(patch.nextGrowth < System.currentTimeMillis() && !patch.isDead){ if(patch.nextGrowth < System.currentTimeMillis() && !patch.isDead){
patch.update() patch.update()
patch.nextGrowth = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(patch.patch.type.stageGrowthTime.toLong()) var minutes = patch.patch.type.stageGrowthTime.toLong()
if(patch.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
}
patch.nextGrowth = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(minutes)
} }
} }