Improve farming trees:

- 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%.
This commit is contained in:
Avi Weinstock 2021-10-29 14:44:26 -04:00
parent 36f3e0ae88
commit 469934fc70
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;
import core.plugin.Initializable;
import core.game.node.entity.skill.summoning.familiar.Familiar;
import core.game.node.entity.skill.summoning.familiar.FamiliarSpecial;
import core.game.interaction.NodeUsageEvent;
import core.game.interaction.UseWithHandler;
import core.game.node.entity.combat.equipment.WeaponInterface;
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.
* @author Aero
*/
@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}.
@ -26,7 +36,7 @@ public class GiantEntNPC extends Familiar {
* @param id The 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
@ -44,4 +54,39 @@ public class GiantEntNPC extends Familiar {
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;
import core.plugin.Initializable;
import core.game.node.entity.skill.summoning.familiar.Familiar;
import core.game.node.entity.skill.summoning.familiar.FamiliarSpecial;
import core.game.node.Node;
import core.game.node.entity.combat.equipment.WeaponInterface;
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.
@ -36,6 +40,17 @@ public class HydraNPC extends Familiar {
@Override
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;
}

View file

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

View file

@ -4,6 +4,7 @@ import api.ContentAPI
import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler
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.skill.Skills
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.plugin.Initializable
import core.plugin.Plugin
import core.tools.RandomFunction
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
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> {
SceneryDefinition.setOptionHandler("harvest",this)
SceneryDefinition.setOptionHandler("pick",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 {
player ?: return false
node ?: return false
@ -40,51 +105,10 @@ class CropHarvester : OptionHandler() {
return true
}
player.pulseManager.run(object : Pulse(0){
override fun pulse(): Boolean {
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
}
})
val pulse = harvestPulse(player, node, plantable.harvestItem) ?: return false
player.pulseManager.run(pulse)
return true
}
}
}

View file

@ -4,6 +4,7 @@ import api.ContentAPI
import core.cache.def.impl.SceneryDefinition
import core.game.interaction.OptionHandler
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.skill.Skills
import core.game.node.item.Item
@ -42,7 +43,6 @@ class FruitAndBerryPicker : OptionHandler() {
plantable ?: return false
val animation = Animation(2281)
val reward = Item(plantable.harvestItem)
if(patch.getFruitOrBerryCount() <= 0){
player.sendMessage("This shouldn't be happening. Please report this.")
@ -60,6 +60,13 @@ class FruitAndBerryPicker : OptionHandler() {
player.pulseManager.run(object : Pulse(animation.duration){
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)
ContentAPI.addItemOrDrop(player,reward.id,reward.amount)
player.skills.addExperience(Skills.FARMING,plantable.harvestXP)
@ -72,4 +79,4 @@ class FruitAndBerryPicker : OptionHandler() {
return true
}
}
}

View file

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

View file

@ -7,6 +7,7 @@ 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 rs09.game.node.entity.skill.farming.CropHarvester
object UseWithPatchHandler{
val RAKE = Items.RAKE_5341
@ -17,6 +18,7 @@ object UseWithPatchHandler{
val pourBucketAnim = Animation(2283)
val wateringCanAnim = Animation(2293)
val plantCureAnim = Animation(2288)
val secateursAnim = Animation(7227)
@JvmField
val allowedNodes = ArrayList<Int>()
@ -46,7 +48,23 @@ object UseWithPatchHandler{
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
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 -> {
val p = patch.getPatchFor(player)
if(!p.isWeedy()){

View file

@ -148,7 +148,13 @@ class FarmingState(player: Player? = null) : State(player) {
if(patch.nextGrowth < System.currentTimeMillis() && !patch.isDead){
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)
}
}
@ -164,4 +170,4 @@ class FarmingState(player: Player? = null) : State(player) {
}
}
}
}
}