diff --git a/CHANGELOG b/CHANGELOG index 8b1378917..ab915f55d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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%. diff --git a/Server/src/main/java/core/game/node/entity/npc/familiar/GiantEntNPC.java b/Server/src/main/java/core/game/node/entity/npc/familiar/GiantEntNPC.java index 3b7d6d376..e516c4a78 100644 --- a/Server/src/main/java/core/game/node/entity/npc/familiar/GiantEntNPC.java +++ b/Server/src/main/java/core/game/node/entity/npc/familiar/GiantEntNPC.java @@ -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 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()); + } + } + } } diff --git a/Server/src/main/java/core/game/node/entity/npc/familiar/HydraNPC.java b/Server/src/main/java/core/game/node/entity/npc/familiar/HydraNPC.java index 347781dd5..19f4ad882 100644 --- a/Server/src/main/java/core/game/node/entity/npc/familiar/HydraNPC.java +++ b/Server/src/main/java/core/game/node/entity/npc/familiar/HydraNPC.java @@ -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; } diff --git a/Server/src/main/java/core/game/node/entity/npc/familiar/UnicornStallionNPC.java b/Server/src/main/java/core/game/node/entity/npc/familiar/UnicornStallionNPC.java index 36d1f01ce..0d6f6a07b 100644 --- a/Server/src/main/java/core/game/node/entity/npc/familiar/UnicornStallionNPC.java +++ b/Server/src/main/java/core/game/node/entity/npc/familiar/UnicornStallionNPC.java @@ -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; } diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/CropHarvester.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/CropHarvester.kt index c887ed0f6..58652ab18 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/CropHarvester.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/CropHarvester.kt @@ -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 { 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 } -} \ No newline at end of file +} diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/FruitAndBerryPicker.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/FruitAndBerryPicker.kt index 9b4ed0d52..d2b2bc32c 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/FruitAndBerryPicker.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/FruitAndBerryPicker.kt @@ -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 } -} \ No newline at end of file +} diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Patch.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Patch.kt index 309b6b9b1..f186f6d3b 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Patch.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/Patch.kt @@ -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(){ diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/UseWithPatchHandler.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/UseWithPatchHandler.kt index ae45353ca..872ad0001 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/UseWithPatchHandler.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/farming/UseWithPatchHandler.kt @@ -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() @@ -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()){ diff --git a/Server/src/main/kotlin/rs09/game/node/entity/state/newsys/states/FarmingState.kt b/Server/src/main/kotlin/rs09/game/node/entity/state/newsys/states/FarmingState.kt index 2024508a9..c785b9712 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/state/newsys/states/FarmingState.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/state/newsys/states/FarmingState.kt @@ -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) { } } } -} \ No newline at end of file +}