diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 0f40597c1..a64a8aa5a 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -72424,5 +72424,21 @@ "examine": "A delicate creature from this strange realm.", "name": "Fairy", "id": "4443" + }, + { + "examine": "A very strange plant.", + "death_animation": "355", + "name": "Strange Plant", + "defence_level": "300", + "melee_animation": "353", + "lifepoints": "100", + "id": "408", + "magic_level": "200", + "defence_animation": "354" + }, + { + "examine": "A very strange plant.", + "name": "Strange Plant", + "id": "407" } ] \ No newline at end of file diff --git a/Server/src/main/content/global/ame/RandomEvents.kt b/Server/src/main/content/global/ame/RandomEvents.kt index d60132753..7e07d744a 100644 --- a/Server/src/main/content/global/ame/RandomEvents.kt +++ b/Server/src/main/content/global/ame/RandomEvents.kt @@ -14,6 +14,7 @@ import content.global.ame.events.rivertroll.RiverTrollRENPC import content.global.ame.events.rockgolem.RockGolemRENPC import content.global.ame.events.sandwichlady.SandwichLadyRENPC import content.global.ame.events.shade.ShadeRENPC +import content.global.ame.events.strangeplant.StrangePlantNPC import content.global.ame.events.swarm.SwarmNPC import content.global.ame.events.treespirit.TreeSpiritRENPC import content.global.ame.events.zombie.ZombieRENPC @@ -44,6 +45,7 @@ enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = n )), DRILL_DEMON(npc = SeargentDamienNPC()), EVIL_CHICKEN(npc = EvilChickenNPC()), + STRANGE_PLANT(npc = StrangePlantNPC()), SWARM(npc = SwarmNPC()), EVIL_BOB(npc = EvilBobNPC(), skillIds = intArrayOf(Skills.FISHING, Skills.MAGIC)), DRUNKEN_DWARF(npc = DrunkenDwarfNPC()), diff --git a/Server/src/main/content/global/ame/events/strangeplant/StrangePlantBehavior.kt b/Server/src/main/content/global/ame/events/strangeplant/StrangePlantBehavior.kt new file mode 100644 index 000000000..cd80ad3d9 --- /dev/null +++ b/Server/src/main/content/global/ame/events/strangeplant/StrangePlantBehavior.kt @@ -0,0 +1,35 @@ +package content.global.ame.events.strangeplant + +import core.api.applyPoison +import core.game.node.entity.Entity +import core.game.node.entity.combat.BattleState +import core.game.node.entity.combat.CombatStyle +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import core.game.node.entity.player.Player +import core.game.system.timer.impl.AntiMacro +import core.tools.RandomFunction +import org.rs09.consts.NPCs + +class StrangePlantBehavior() : NPCBehavior(NPCs.STRANGE_PLANT_408) { + override fun canBeAttackedBy(self: NPC, attacker: Entity, style: CombatStyle, shouldSendMessage: Boolean): Boolean { + return !(attacker !is Player || AntiMacro.getEventNpc(attacker.asPlayer()) != self) + } + + override fun beforeAttackFinalized(self: NPC, victim: Entity, state: BattleState) { + state.estimatedHit = RandomFunction.getRandom(3) + if (RandomFunction.roll(10)) + applyPoison(victim, self, 10) + } + + override fun beforeDamageReceived(self: NPC, attacker: Entity, state: BattleState) { + if (state.estimatedHit > 3) + state.estimatedHit = RandomFunction.getRandom(3) + if (state.secondaryHit > 3) + state.secondaryHit = RandomFunction.getRandom(3) + } + + override fun onDeathStarted(self: NPC, killer: Entity) { + AntiMacro.terminateEventNpc(killer.asPlayer()) + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/strangeplant/StrangePlantListener.kt b/Server/src/main/content/global/ame/events/strangeplant/StrangePlantListener.kt new file mode 100644 index 000000000..be182fd47 --- /dev/null +++ b/Server/src/main/content/global/ame/events/strangeplant/StrangePlantListener.kt @@ -0,0 +1,33 @@ +package content.global.ame.events.strangeplant + +import core.api.addItemOrDrop +import core.api.getAttribute +import core.api.sendMessage +import core.api.setAttribute +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.system.timer.impl.AntiMacro +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +class StrangePlantListener : InteractionListener { + override fun defineListeners() { + on(NPCs.STRANGE_PLANT_407, IntType.NPC, "pick") { player, node -> + if (AntiMacro.getEventNpc(player) != node) { + sendMessage(player, "This isn't your strange plant.") + return@on true + } + + if (!getAttribute(node.asNpc(), "fruit-grown", false)) { + sendMessage(player, "The fruit isn't ready to be picked.") + return@on true + } + + if (!getAttribute(node.asNpc(), "fruit-picked", false)) { + setAttribute(node.asNpc(), "fruit-picked", true) + addItemOrDrop(player, Items.STRANGE_FRUIT_464) + } + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/strangeplant/StrangePlantNPC.kt b/Server/src/main/content/global/ame/events/strangeplant/StrangePlantNPC.kt new file mode 100644 index 000000000..a1eca2fd8 --- /dev/null +++ b/Server/src/main/content/global/ame/events/strangeplant/StrangePlantNPC.kt @@ -0,0 +1,85 @@ +package content.global.ame.events.strangeplant + +import content.global.ame.RandomEventNPC +import core.api.* +import core.api.utils.WeightBasedTable +import core.game.interaction.QueueStrength +import core.game.node.entity.npc.NPC +import core.game.world.update.flag.context.Animation +import core.tools.minutesToTicks +import core.tools.secondsToTicks +import org.rs09.consts.NPCs + +class StrangePlantNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.STRANGE_PLANT_407) { + private val strangePlantGrowAnim = Animation(348) + private val strangePlantTransformAnim = Animation(351) + private val strangePlantPickedAnim = Animation(350) + private var fruitPicked = false + private var transformed = false + private var attacking = false + + override fun init() { + spawnLocation = getPathableCardinal(player, player.location) + super.init() + ticksLeft = minutesToTicks(1) + animate(this, strangePlantGrowAnim) + queueScript(this, strangePlantGrowAnim.duration + 2, QueueStrength.SOFT) { + setAttribute(this, "fruit-grown", true) + return@queueScript stopExecuting(this) + } + } + + override fun tick() { + super.tick() + if (getAttribute(this, "fruit-picked", false) && !fruitPicked) { + fruitPicked = true + ticksLeft = secondsToTicks(60) + queueScript(this, 1, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + this.animate(strangePlantPickedAnim) + return@queueScript delayScript(this, strangePlantPickedAnim.duration - 1) + } + 1 -> { + this.isInvisible = true + terminate() + return@queueScript stopExecuting(this) + } + else -> return@queueScript stopExecuting(this) + } + } + } + if (ticksLeft <= secondsToTicks(10)) { + ticksLeft = secondsToTicks(10) + if (!attacking) { + attacking = true + queueScript(this, 1, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + animate(this, strangePlantTransformAnim) + return@queueScript delayScript(this, strangePlantTransformAnim.duration) + } + 1 -> { + this.transform(NPCs.STRANGE_PLANT_408) + this.behavior = StrangePlantBehavior() + this.attack(player) + transformed = true + return@queueScript stopExecuting(this) + } + else -> return@queueScript stopExecuting(this) + } + } + } + if (transformed && !this.inCombat()) + this.attack(player) + } + } + + override fun follow() { + if (transformed) + super.follow() + } + + override fun talkTo(npc: NPC) { + } +} \ No newline at end of file diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index ae2f42df1..670c885aa 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -2191,6 +2191,23 @@ fun getPathableRandomLocalCoordinate(target: Entity, radius: Int, center: Locati return target.location } +/** + * Returns a pathable cardinal location in a 1 tile radius in order of west, south, east, north. + * @param entity the entity used to check if the loc is pathable + * @param center the center location + */ +fun getPathableCardinal(target: Entity, center: Location) : Location { + var tiles = center.cardinalTiles + + for (tile in tiles) { + val path = Pathfinder.find(center, tile, target.size()) + if (path.isSuccessful && !path.isMoveNear) + return tile + } + + return center +} + /** * Returns the player's active slayer task. * @author bushtail diff --git a/Server/src/main/core/game/world/map/Location.java b/Server/src/main/core/game/world/map/Location.java index 013c81ff0..ea7d1794c 100644 --- a/Server/src/main/core/game/world/map/Location.java +++ b/Server/src/main/core/game/world/map/Location.java @@ -294,10 +294,10 @@ public final class Location extends Node { public ArrayList getCardinalTiles() { ArrayList locs = new ArrayList<>(); - locs.add(transform(0, 1, 0)); - locs.add(transform(0, -1, 0)); locs.add(transform(-1, 0, 0)); + locs.add(transform(0, -1, 0)); locs.add(transform(1, 0, 0)); + locs.add(transform(0, 1, 0)); return locs; }